blob: d4d89796fbc2d9162c0cc3bb893be6fbae6833a2 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@TestOn('vm')
@Timeout(Duration(minutes: 2))
import 'package:test/test.dart';
import 'package:test_common/logging.dart';
import 'package:test_common/test_sdk_configuration.dart';
import 'package:vm_service/vm_service.dart';
import 'package:vm_service_interface/vm_service_interface.dart';
import 'fixtures/context.dart';
import 'fixtures/project.dart';
import 'fixtures/utilities.dart';
void main() {
// Enable verbose logging for debugging.
final debug = false;
final provider = TestSdkConfigurationProvider(verbose: debug);
tearDownAll(provider.dispose);
group('shared context |', () {
for (var nullSafety in NullSafety.values) {
group('${nullSafety.name} null safety |', () {
final project = TestProject.testPackage(nullSafety: nullSafety);
final context = TestContext(project, provider);
setUpAll(() async {
setCurrentLogWriter(debug: debug);
await context.setUp(
testSettings: TestSettings(
compilationMode: CompilationMode.frontendServer,
enableExpressionEvaluation: true,
verboseCompiler: debug,
),
);
});
tearDownAll(() async {
await context.tearDown();
});
group('callStack |', () {
late VmServiceInterface service;
VM vm;
late Isolate isolate;
late String isolateId;
ScriptList scripts;
late ScriptRef mainScript;
late ScriptRef testLibraryScript;
late Stream<Event> stream;
setUp(() async {
setCurrentLogWriter(debug: debug);
service = context.service;
vm = await service.getVM();
isolate = await service.getIsolate(vm.isolates!.first.id!);
isolateId = isolate.id!;
scripts = await service.getScripts(isolateId);
await service.streamListen('Debug');
stream = service.onEvent('Debug');
final testPackage = project.packageName;
mainScript = scripts.scripts!
.firstWhere((each) => each.uri!.contains('main.dart'));
testLibraryScript = scripts.scripts!.firstWhere(
(each) =>
each.uri!.contains('package:$testPackage/test_library.dart'),
);
});
tearDown(() async {
await service.resume(isolateId);
});
Future<void> onBreakPoint(
BreakpointTestData breakpoint,
Future<void> Function() body,
) async {
Breakpoint? bp;
try {
final bpId = breakpoint.bpId;
final script = breakpoint.script;
final line =
await context.findBreakpointLine(bpId, isolateId, script);
bp = await context.service
.addBreakpointWithScriptUri(isolateId, script.uri!, line);
expect(bp, isNotNull);
expect(bp.location, _matchBpLocation(script, line, 0));
await stream.firstWhere(
(Event event) => event.kind == EventKind.kPauseBreakpoint,
);
await body();
} finally {
// Remove breakpoint so it doesn't impact other tests or retries.
if (bp != null) {
await context.service.removeBreakpoint(isolateId, bp.id!);
}
}
}
Future<void> testCallStack(
List<BreakpointTestData> breakpoints, {
int frameIndex = 1,
}) async {
// Find lines the breakpoints are located on.
final lines = await Future.wait(
breakpoints.map(
(frame) => context.findBreakpointLine(
frame.bpId,
isolateId,
frame.script,
),
),
);
// Get current stack.
final stack = await service.getStack(isolateId);
// Verify the stack is correct.
expect(stack.frames!.length, greaterThanOrEqualTo(lines.length));
final expected = [
for (var i = 0; i < lines.length; i++)
_matchFrame(
breakpoints[i].script,
breakpoints[i].function,
lines[i],
),
];
expect(stack.frames, containsAll(expected));
// Verify that expression evaluation is not failing.
final instance =
await service.evaluateInFrame(isolateId, frameIndex, 'true');
expect(instance, isA<InstanceRef>());
}
test('breakpoint succeeds with correct callstack', () async {
// Expected breakpoints on the stack
final breakpoints = [
BreakpointTestData(
'printEnclosingObject',
'printEnclosingObject',
mainScript,
),
BreakpointTestData(
'printEnclosingFunctionMultiLine',
'printNestedObjectsMultiLine',
mainScript,
),
BreakpointTestData(
'callPrintEnclosingFunctionMultiLine',
'<closure>',
mainScript,
),
];
await onBreakPoint(
breakpoints[0],
() => testCallStack(breakpoints),
);
});
test('expression evaluation succeeds on parent frame', () async {
// Expected breakpoints on the stack
final breakpoints = [
BreakpointTestData(
'testLibraryClassConstructor',
'new',
testLibraryScript,
),
BreakpointTestData(
'createLibraryObject',
'printFieldFromLibraryClass',
mainScript,
),
BreakpointTestData(
'callPrintFieldFromLibraryClass',
'<closure>',
mainScript,
),
];
await onBreakPoint(
breakpoints[0],
() => testCallStack(breakpoints, frameIndex: 2),
);
});
test('breakpoint inside a line gives correct callstack', () async {
// Expected breakpoints on the stack
final breakpoints = [
BreakpointTestData(
'newEnclosedClass',
'new',
mainScript,
),
BreakpointTestData(
'printNestedObjectMultiLine',
'printNestedObjectsMultiLine',
mainScript,
),
BreakpointTestData(
'callPrintEnclosingFunctionMultiLine',
'<closure>',
mainScript,
),
];
await onBreakPoint(
breakpoints[0],
() => testCallStack(breakpoints),
);
});
test('breakpoint gives correct callstack after step out', () async {
// Expected breakpoints on the stack
final breakpoints = [
BreakpointTestData(
'newEnclosedClass',
'new',
mainScript,
),
BreakpointTestData(
'printEnclosingObjectMultiLine',
'printNestedObjectsMultiLine',
mainScript,
),
BreakpointTestData(
'callPrintEnclosingFunctionMultiLine',
'<closure>',
mainScript,
),
];
await onBreakPoint(breakpoints[0], () async {
await service.resume(isolateId, step: 'Out');
await stream.firstWhere(
(Event event) => event.kind == EventKind.kPauseInterrupted,
);
return testCallStack([breakpoints[1], breakpoints[2]]);
});
});
test('breakpoint gives correct callstack after step in', () async {
// Expected breakpoints on the stack
final breakpoints = [
BreakpointTestData(
'newEnclosedClass',
'new',
mainScript,
),
BreakpointTestData(
'printNestedObjectMultiLine',
'printNestedObjectsMultiLine',
mainScript,
),
BreakpointTestData(
'callPrintEnclosingFunctionMultiLine',
'<closure>',
mainScript,
),
];
await onBreakPoint(breakpoints[1], () async {
await service.resume(isolateId, step: 'Into');
await stream.firstWhere(
(Event event) => event.kind == EventKind.kPauseInterrupted,
);
return testCallStack(breakpoints);
});
});
test('breakpoint gives correct callstack after step into chain calls',
() async {
// Expected breakpoints on the stack
final breakpoints = [
BreakpointTestData(
'createObjectWithMethod',
'createObject',
mainScript,
),
BreakpointTestData(
// This is currently incorrect, should be printObjectMultiLine.
// See issue: https://github.com/dart-lang/sdk/issues/48874
'printMultiLine',
'printObjectMultiLine',
mainScript,
),
BreakpointTestData(
'callPrintObjectMultiLine',
'<closure>',
mainScript,
),
];
final bp = BreakpointTestData(
'printMultiLine',
'printObjectMultiLine',
mainScript,
);
await onBreakPoint(bp, () async {
await service.resume(isolateId, step: 'Into');
await stream.firstWhere(
(Event event) => event.kind == EventKind.kPauseInterrupted,
);
return testCallStack(breakpoints);
});
});
});
});
}
});
}
Matcher _matchFrame(ScriptRef script, String function, int line) => isA<Frame>()
.having((frame) => frame.code!.name, 'function', function)
.having(
(frame) => frame.location,
'location',
_matchFrameLocation(script, line),
);
Matcher _matchBpLocation(ScriptRef script, int line, int column) =>
isA<SourceLocation>()
.having((loc) => loc.script, 'script', equals(script))
.having((loc) => loc.line, 'line', equals(line))
.having((loc) => loc.column, 'column', greaterThanOrEqualTo(column));
Matcher _matchFrameLocation(ScriptRef script, int line) => isA<SourceLocation>()
.having((loc) => loc.script, 'script', equals(script))
.having((loc) => loc.line, 'line', equals(line));
class BreakpointTestData {
String bpId;
String function;
ScriptRef script;
BreakpointTestData(this.bpId, this.function, this.script);
}