[inspector] Add disabled tests for 'Restart frame' 2/2

Doc: https://bit.ly/revive-restart-frame
Context: https://crrev.com/c/3582395 (whole feature)

This CL adds the second batch of inspector tests for the upcoming
"Restart frame" feature. Landing the tests upfront allows us to
better discuss the proposed API as well as think early about
corner cases we should test.

The tests check for the functionality of `Debugger.restartFrame`, as
well as the newly added parameter `canBeRestarted` in
the `Debugger.paused` event.

Bug: chromium:1303521
Change-Id: Ie9dda100cdc5217a4e4cc2f0cf7019a33d124120
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3585947
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Kim-Anh Tran <kimanh@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80037}
This commit is contained in:
Simon Zünd 2022-04-14 09:32:40 +02:00 committed by V8 LUCI CQ
parent 80f9d34685
commit f6cb798753
12 changed files with 531 additions and 0 deletions

View File

@ -0,0 +1,17 @@
Checks that requesting to restart a non-existant frame fails cleanly
Paused at (after evaluation):
(function foo() { #debugger; })();
Pause stack:
foo:1 (canBeRestarted = true)
Attempting to restart frame with non-existent index 2
{
error : {
code : -32000
message : Restarting frame failed
}
id : <messageId>
}

View File

@ -0,0 +1,23 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
utils.load('test/inspector/debugger/restart-frame/restart-frame-test.js');
const {session, Protocol} =
InspectorTest.start('Checks that requesting to restart a non-existant frame fails cleanly');
session.setupScriptMap();
(async () => {
await Protocol.Debugger.enable();
await RestartFrameTest.evaluateAndWaitForPause(`
(function foo() { debugger; })();
`);
InspectorTest.log('Attempting to restart frame with non-existent index 2');
InspectorTest.logMessage(
await Protocol.Debugger.restartFrame({ callFrameId: '1.1.2', mode: 'StepInto' }));
InspectorTest.completeTest();
})();

View File

@ -0,0 +1,51 @@
Checks that restart frame fails for generator or async functions.
Check that an async function cannot be restarted.
Paused at (after evaluation):
(async function asyncFn() {
#debugger;
})();
Pause stack:
asyncFn:2 (canBeRestarted = false)
Restarting function "asyncFn" ...
Failed to restart function "asyncFn":
{
code : -32000
message : Restarting frame failed
}
Check that a generator function cannot be restarted.
Paused at (after evaluation):
yield 10;
#debugger;
yield 20;
Pause stack:
generatorFn:3 (canBeRestarted = false)
Restarting function "generatorFn" ...
Failed to restart function "generatorFn":
{
code : -32000
message : Restarting frame failed
}
Check that a function cannot be restarted when a generator function is on the stack above
Paused at (after evaluation):
function breaker() { #debugger; }
function* genFn() {
Pause stack:
breaker:1 (canBeRestarted = true)
genFn:4 (canBeRestarted = false)
bar:10 (canBeRestarted = false)
Restarting function "bar" ...
Failed to restart function "bar":
{
code : -32000
message : Restarting frame failed
}

View File

@ -0,0 +1,61 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
utils.load('test/inspector/debugger/restart-frame/restart-frame-test.js');
const {session, contextGroup, Protocol} =
InspectorTest.start('Checks that restart frame fails for generator or async functions.');
session.setupScriptMap();
async function testCase(description, snippet, restartFrameIndex) {
InspectorTest.log('');
InspectorTest.log(description);
const { callFrames, evaluatePromise } = await RestartFrameTest.evaluateAndWaitForPause(snippet);
// These are negative tests where the following call is expected to fail.
await RestartFrameTest.restartFrameAndWaitForPause(callFrames, restartFrameIndex);
// All snippets are written so a single resume is enough.
Protocol.Debugger.resume();
await evaluatePromise;
}
(async () => {
await Protocol.Debugger.enable();
await testCase('Check that an async function cannot be restarted.', `
(async function asyncFn() {
debugger;
})();
`, 0);
await testCase('Check that a generator function cannot be restarted.', `
function* generatorFn() {
yield 10;
debugger;
yield 20;
}
const gen1 = generatorFn();
gen1.next();
gen1.next();
`, 0);
await testCase('Check that a function cannot be restarted when a generator function is on the stack above', `
function breaker() { debugger; }
function* genFn() {
yield 10;
breaker();
yield 20;
}
const gen2 = genFn();
function bar() { // We want to restart bar.
gen2.next();
gen2.next();
}
bar();
`, 2);
InspectorTest.completeTest();
})();

View File

@ -0,0 +1,16 @@
Checks that restart frame fails when embedder frames would be unwound
Paused at (after evaluation):
function breaker() {
#debugger;
}
Pause stack:
breaker:2 (canBeRestarted = true)
entrypoint:5 (canBeRestarted = false)
Restarting function "entrypoint" ...
Failed to restart function "entrypoint":
{
code : -32000
message : Restarting frame failed
}

View File

@ -0,0 +1,31 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
utils.load('test/inspector/debugger/restart-frame/restart-frame-test.js');
const {session, contextGroup, Protocol} =
InspectorTest.start('Checks that restart frame fails when embedder frames would be unwound');
session.setupScriptMap();
contextGroup.addScript(`
function breaker() {
debugger;
}
function entrypoint() {
inspector.callbackForTests(breaker);
}
`, 0, 0, 'test.js');
(async () => {
await Protocol.Debugger.enable();
const { callFrames } = await RestartFrameTest.evaluateAndWaitForPause('entrypoint()');
// Restart the `entrypoint` frame. Inbetween is the C++ API method `callbackForTests`.
const restartFrameIndex = 1; // 0 is `breaker`, 1 is `entrypoint`.
await RestartFrameTest.restartFrameAndWaitForPause(callFrames, restartFrameIndex);
InspectorTest.completeTest();
})();

View File

@ -0,0 +1,20 @@
Checks that restarting an inlined frame works.
Optimization status for function "h"? optimized
Paused at (after evaluation):
console.log('f');
return a + b;# // Breakpoint is set here.
}
Pause stack:
f:3 (canBeRestarted = true)
g:8 (canBeRestarted = true)
h:13 (canBeRestarted = true)
Optimization status for function "h" after we paused? optimized
Restarting function "g" ...
Paused at (after restart):
function g(a, b) { // We want to restart 'g'.
#console.log('g');
return 2 + f(a, b);
Called functions: h,g,f,g,f

View File

@ -0,0 +1,73 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
utils.load('test/inspector/debugger/restart-frame/restart-frame-test.js');
const {session, contextGroup, Protocol} =
InspectorTest.start('Checks that restarting an inlined frame works.');
session.setupScriptMap();
contextGroup.addScript(`
function f(a, b) {
console.log('f');
return a + b; // Breakpoint is set here.
}
function g(a, b) { // We want to restart 'g'.
console.log('g');
return 2 + f(a, b);
}
function h() {
console.log('h');
return 1 + g(3, 2);
}
function isOptimized(f) {
return (%GetOptimizationStatus(f) & 16) ? "optimized" : "unoptimized";
}
%NeverOptimizeFunction(f); // Inline 'g' into 'h', but never 'f'.
%PrepareFunctionForOptimization(h);
for (let i = 0; i < 10; ++i) h();
%OptimizeFunctionOnNextCall(h);
h();
`, 0, 0, 'test.js');
(async () => {
await Protocol.Debugger.enable();
await Protocol.Runtime.enable();
const consoleMessages = [];
Protocol.Runtime.onConsoleAPICalled(({ params }) => {
consoleMessages.push(params.args[0].value);
});
let { result: { result: { value } } } = await Protocol.Runtime.evaluate({ expression: 'isOptimized(h)' });
InspectorTest.log(`Optimization status for function "h"? ${value}`);
await Protocol.Debugger.setBreakpointByUrl({ url: 'test.js', lineNumber: 4 });
const { callFrames, evaluatePromise } = await RestartFrameTest.evaluateAndWaitForPause('h()');
({ result: { result: { value } } } = await Protocol.Runtime.evaluate({ expression: 'isOptimized(h)' }));
InspectorTest.log(`Optimization status for function "h" after we paused? ${value}`);
await RestartFrameTest.restartFrameAndWaitForPause(callFrames, 1);
// Restarting the frame means we hit the breakpoint a second time.
Protocol.Debugger.resume();
await Protocol.Debugger.oncePaused();
await Protocol.Debugger.resume();
await evaluatePromise;
InspectorTest.log(`Called functions: ${consoleMessages.join()}`);
InspectorTest.completeTest();
})();

View File

@ -0,0 +1,139 @@
Checks that restarting every frame on the stack works.
---- Restarting at frame index 0 ----
Paused at (after evaluation):
console.log('A');
#debugger;
return 'Magic value';
Pause stack:
A:3 (canBeRestarted = true)
B:8 (canBeRestarted = true)
C:13 (canBeRestarted = true)
D:18 (canBeRestarted = true)
E:22 (canBeRestarted = true)
F:26 (canBeRestarted = true)
Restarting function "A" ...
Paused at (after restart):
function A() {
#console.log('A');
debugger;
Evaluating to: Magic value
Called functions: F,E,D,C,B,A,A
---- Restarting at frame index 1 ----
Paused at (after evaluation):
console.log('A');
#debugger;
return 'Magic value';
Pause stack:
A:3 (canBeRestarted = true)
B:8 (canBeRestarted = true)
C:13 (canBeRestarted = true)
D:18 (canBeRestarted = true)
E:22 (canBeRestarted = true)
F:26 (canBeRestarted = true)
Restarting function "B" ...
Paused at (after restart):
function B(param1, param2) {
#console.log('B');
return A();
Evaluating to: Magic value
Called functions: F,E,D,C,B,A,B,A
---- Restarting at frame index 2 ----
Paused at (after evaluation):
console.log('A');
#debugger;
return 'Magic value';
Pause stack:
A:3 (canBeRestarted = true)
B:8 (canBeRestarted = true)
C:13 (canBeRestarted = true)
D:18 (canBeRestarted = true)
E:22 (canBeRestarted = true)
F:26 (canBeRestarted = true)
Restarting function "C" ...
Paused at (after restart):
function C() {
#console.log('C');
// Function call with argument adapter is intentional.
Evaluating to: Magic value
Called functions: F,E,D,C,B,A,C,B,A
---- Restarting at frame index 3 ----
Paused at (after evaluation):
console.log('A');
#debugger;
return 'Magic value';
Pause stack:
A:3 (canBeRestarted = true)
B:8 (canBeRestarted = true)
C:13 (canBeRestarted = true)
D:18 (canBeRestarted = true)
E:22 (canBeRestarted = true)
F:26 (canBeRestarted = true)
Restarting function "D" ...
Paused at (after restart):
function D() {
#console.log('D');
// Function call with argument adapter is intentional.
Evaluating to: Magic value
Called functions: F,E,D,C,B,A,D,C,B,A
---- Restarting at frame index 4 ----
Paused at (after evaluation):
console.log('A');
#debugger;
return 'Magic value';
Pause stack:
A:3 (canBeRestarted = true)
B:8 (canBeRestarted = true)
C:13 (canBeRestarted = true)
D:18 (canBeRestarted = true)
E:22 (canBeRestarted = true)
F:26 (canBeRestarted = true)
Restarting function "E" ...
Paused at (after restart):
function E() {
#console.log('E');
return D();
Evaluating to: Magic value
Called functions: F,E,D,C,B,A,E,D,C,B,A
---- Restarting at frame index 5 ----
Paused at (after evaluation):
console.log('A');
#debugger;
return 'Magic value';
Pause stack:
A:3 (canBeRestarted = true)
B:8 (canBeRestarted = true)
C:13 (canBeRestarted = true)
D:18 (canBeRestarted = true)
E:22 (canBeRestarted = true)
F:26 (canBeRestarted = true)
Restarting function "F" ...
Paused at (after restart):
function F() {
#console.log('F');
return E();
Evaluating to: Magic value
Called functions: F,E,D,C,B,A,F,E,D,C,B,A

View File

@ -0,0 +1,71 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
utils.load('test/inspector/debugger/restart-frame/restart-frame-test.js');
const {session, contextGroup, Protocol} =
InspectorTest.start('Checks that restarting every frame on the stack works.');
session.setupScriptMap();
contextGroup.addScript(`
function A() {
console.log('A');
debugger;
return 'Magic value';
}
function B(param1, param2) {
console.log('B');
return A();
}
function C() {
console.log('C');
// Function call with argument adapter is intentional.
return B();
}
function D() {
console.log('D');
// Function call with argument adapter is intentional.
return C(42, 'foo');
}
function E() {
console.log('E');
return D();
}
function F() {
console.log('F');
return E();
}
`, 0, 0, 'test.js');
async function testCase(frameIndex) {
const { callFrames, evaluatePromise } = await RestartFrameTest.evaluateAndWaitForPause('F()');
await RestartFrameTest.restartFrameAndWaitForPause(callFrames, frameIndex);
Protocol.Debugger.resume(); // Resuming hits the 'debugger' stmt again.
await Protocol.Debugger.oncePaused();
await Protocol.Debugger.resume();
const { result: { result: { value } } } = await evaluatePromise;
InspectorTest.log(`Evaluating to: ${value}`);
}
(async () => {
await Protocol.Debugger.enable();
await Protocol.Runtime.enable();
const consoleMessages = [];
Protocol.Runtime.onConsoleAPICalled(({ params }) => {
consoleMessages.push(params.args[0].value);
});
for (let index = 0; index < 6; ++index) {
InspectorTest.log(`\n---- Restarting at frame index ${index} ----`);
await testCase(index);
InspectorTest.log(`Called functions: ${consoleMessages.join()}`);
consoleMessages.splice(0);
}
InspectorTest.completeTest();
})();

View File

@ -510,6 +510,9 @@ class InspectorExtension : public InspectorIsolateData::SetupGlobalTask {
inspector->Set(isolate, "newExceptionWithMetaData",
v8::FunctionTemplate::New(
isolate, &InspectorExtension::newExceptionWithMetaData));
inspector->Set(isolate, "callbackForTests",
v8::FunctionTemplate::New(
isolate, &InspectorExtension::CallbackForTests));
global->Set(isolate, "inspector", inspector);
}
@ -771,6 +774,21 @@ class InspectorExtension : public InspectorIsolateData::SetupGlobalTask {
args[2].As<v8::String>()));
args.GetReturnValue().Set(error);
}
static void CallbackForTests(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 1 || !args[0]->IsFunction()) {
FATAL("Internal error: callbackForTests(function).");
}
v8::Isolate* isolate = args.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(args[0]);
v8::MaybeLocal<v8::Value> result =
callback->Call(context, v8::Undefined(isolate), 0, nullptr);
args.GetReturnValue().Set(result.ToLocalChecked());
}
};
int InspectorTestMain(int argc, char* argv[]) {

View File

@ -328,10 +328,15 @@
'debugger/regression-424142': [SKIP],
'debugger/remove-breakpoint-at-breakpoint': [SKIP],
'debugger/resource-name-to-url': [SKIP],
'debugger/restart-frame/fails-for-non-existant-index': [SKIP],
'debugger/restart-frame/fails-for-resumables': [SKIP],
'debugger/restart-frame/fails-with-embedder-frames': [SKIP],
'debugger/restart-frame/fails-without-mode-param': [SKIP],
'debugger/restart-frame/restart-inlined-frame': [SKIP],
'debugger/restart-frame/restart-top-frame-debugger-stmt': [SKIP],
'debugger/restart-frame/restart-top-frame-local-variables': [SKIP],
'debugger/restart-frame/restart-top-frame-with-breakpoint': [SKIP],
'debugger/restart-frame/restart-various-frames': [SKIP],
'debugger/restore-breakpoint': [SKIP],
'debugger/return-break-locations': [SKIP],
'debugger/scope-skip-variables-with-empty-name': [SKIP],
@ -534,4 +539,10 @@
'runtime/evaluate-without-side-effects-i18n': [SKIP],
}], # no_i18n == True
##############################################################################
['lite_mode or variant in (nooptimization, jitless, assert_types)', {
# Test relies on TurboFan being enabled.
'debugger/restart-frame/restart-inlined-frame': [SKIP],
}], # lite_mode or variant in (nooptimization, jitless, assert_types)
]