[ package:vm_service ] Migrate Observatory tests (i* -> l*)
Change-Id: I3c60484af4d8fff1b5742bbe57f2c318d3cb0e43
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333180
Reviewed-by: Derek Xu <derekx@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 92e8c62..44f31c9 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -17,6 +17,7 @@
async: any
collection: any
lints: any
+ logging: any
markdown: any
path: any
pub_semver: any
diff --git a/pkg/vm_service/test/common/service_test_common.dart b/pkg/vm_service/test/common/service_test_common.dart
index c4a675b..0e8c265 100644
--- a/pkg/vm_service/test/common/service_test_common.dart
+++ b/pkg/vm_service/test/common/service_test_common.dart
@@ -527,3 +527,38 @@
expect(result.kind!, kind);
}
}
+
+IsolateTest hasLocalVarInTopStackFrame(String varName) {
+ return (VmService service, IsolateRef isolateRef) async {
+ print("Checking we have variable '$varName' in the top frame");
+
+ final isolateId = isolateRef.id!;
+ // Make sure that the isolate has stopped.
+ final isolate = await service.getIsolate(isolateId);
+ expect(isolate.pauseEvent, isNotNull);
+ expect(isolate.pauseEvent!.kind, isNot(EventKind.kResume));
+
+ final stack = await service.getStack(isolateId);
+ final frames = stack.frames!;
+ expect(frames.length, greaterThanOrEqualTo(1));
+
+ final top = frames[0];
+ final vars = top.vars!;
+ for (final variable in vars) {
+ if (variable.name == varName) {
+ return;
+ }
+ }
+ final sb = StringBuffer();
+ sb.write('Expected to find $varName in top awaiter stack frame, found ');
+ if (vars.isEmpty) {
+ sb.writeln('no variables');
+ } else {
+ sb.writeln('these instead:');
+ for (var variable in vars) {
+ sb.writeln('\t${variable.name}');
+ }
+ }
+ throw sb.toString();
+ };
+}
diff --git a/pkg/vm_service/test/implicit_getter_setter_test.dart b/pkg/vm_service/test/implicit_getter_setter_test.dart
new file mode 100644
index 0000000..6c6d8c3
--- /dev/null
+++ b/pkg/vm_service/test/implicit_getter_setter_test.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:async';
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+
+class A {
+ double field = 0.0;
+}
+
+void script() {
+ for (int i = 0; i < 10; i++) {
+ A();
+ }
+}
+
+Future<void> testGetter(VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLib = await service.getObject(
+ isolateId,
+ isolate.rootLib!.id!,
+ ) as Library;
+ expect(rootLib.classes!.length, 1);
+
+ final classA = await service.getObject(
+ isolateId,
+ rootLib.classes![0].id!,
+ ) as Class;
+ expect(classA.name, 'A');
+ // Find getter.
+ FuncRef? getterFuncRef;
+ for (final function in classA.functions!) {
+ if (function.name == 'field') {
+ getterFuncRef = function;
+ break;
+ }
+ }
+ expect(getterFuncRef, isNotNull);
+
+ final getterFunc = await service.getObject(
+ isolateId,
+ getterFuncRef!.id!,
+ ) as Func;
+ final fieldRef = FieldRef.parse(getterFunc.json!['_field']);
+ expect(fieldRef, isNotNull);
+ final field = await service.getObject(
+ isolateId,
+ fieldRef!.id!,
+ ) as Field;
+ expect(field, isNotNull);
+ expect(field.name, 'field');
+
+ final classDoubleRef = Class.parse(field.json!['_guardClass']);
+ expect(classDoubleRef, isNotNull);
+ final classDouble = await service.getObject(
+ isolateId,
+ classDoubleRef!.id!,
+ ) as Class;
+ expect(classDouble.name, '_Double');
+}
+
+Future<void> testSetter(VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLib = await service.getObject(
+ isolateId,
+ isolate.rootLib!.id!,
+ ) as Library;
+ expect(rootLib.classes!.length, 1);
+
+ final classA = await service.getObject(
+ isolateId,
+ rootLib.classes![0].id!,
+ ) as Class;
+ expect(classA.name, 'A');
+ // Find setter.
+ FuncRef? setterFuncRef;
+ for (final function in classA.functions!) {
+ if (function.name == 'field=') {
+ setterFuncRef = function;
+ break;
+ }
+ }
+ expect(setterFuncRef, isNotNull);
+ final setterFunc = await service.getObject(
+ isolateId,
+ setterFuncRef!.id!,
+ ) as Func;
+
+ final fieldRef = FieldRef.parse(setterFunc.json!['_field']);
+ final field = await service.getObject(
+ isolateId,
+ fieldRef!.id!,
+ ) as Field;
+ expect(field, isNotNull);
+ expect(field.name, 'field');
+
+ final classDoubleRef = Class.parse(field.json!['_guardClass']);
+ expect(classDoubleRef, isNotNull);
+ final classDouble = await service.getObject(
+ isolateId,
+ classDoubleRef!.id!,
+ ) as Class;
+ expect(classDouble.name, '_Double');
+}
+
+final tests = <IsolateTest>[
+ testGetter,
+ testSetter,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'implicit_getter_setter_test.dart',
+ testeeBefore: script,
+ );
diff --git a/pkg/vm_service/test/inbound_references_test.dart b/pkg/vm_service/test/inbound_references_test.dart
new file mode 100644
index 0000000..c4239bc
--- /dev/null
+++ b/pkg/vm_service/test/inbound_references_test.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2023, 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.
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+
+@pragma("vm:entry-point") // Prevent obfuscation
+class Node {
+ // Make sure this field is not removed by the tree shaker.
+ @pragma("vm:entry-point") // Prevent obfuscation
+ var edge;
+}
+
+class Edge {}
+
+@pragma("vm:entry-point") // Prevent obfuscation
+var n, e, array;
+
+void script() {
+ n = Node();
+ e = Edge();
+ n.edge = e;
+ array = List<dynamic>.filled(2, null);
+ array[0] = n;
+ array[1] = e;
+}
+
+final tests = <IsolateTest>[
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLib = await service.getObject(
+ isolateId,
+ isolate.rootLib!.id!,
+ ) as Library;
+ final fieldRef = rootLib.variables!.where((v) => v.name == 'e').single;
+ final field = await service.getObject(isolateId, fieldRef.id!) as Field;
+ final e = field.staticValue! as InstanceRef;
+ final response = await service.getInboundReferences(
+ isolateId,
+ e.id!,
+ 100,
+ );
+ final references = response.references!;
+
+ void hasReferenceSuchThat(bool Function(InboundReference) predicate) {
+ expect(references.any(predicate), isTrue);
+ }
+
+ // Assert inst is referenced by at least n, array, and the top-level
+ // field e.
+ hasReferenceSuchThat((r) =>
+ r.parentField != null &&
+ r.parentField!.name == 'edge' &&
+ r.source is InstanceRef &&
+ (r.source as InstanceRef).classRef!.name == 'Node');
+ hasReferenceSuchThat(
+ (r) =>
+ r.parentListIndex == 1 &&
+ r.source is InstanceRef &&
+ (r.source as InstanceRef).kind == InstanceKind.kList,
+ );
+ hasReferenceSuchThat(
+ (r) => r.source is FieldRef && (r.source as FieldRef).name == 'e',
+ );
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'inbound_references_test.dart',
+ testeeBefore: script,
+ );
diff --git a/pkg/vm_service/test/instance_field_order_rpc_test.dart b/pkg/vm_service/test/instance_field_order_rpc_test.dart
new file mode 100644
index 0000000..b10ce24
--- /dev/null
+++ b/pkg/vm_service/test/instance_field_order_rpc_test.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2023, 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.
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+
+class Super {
+ // Make sure these fields are not removed by the tree shaker.
+ @pragma("vm:entry-point")
+ final z = 1;
+ @pragma("vm:entry-point")
+ final y = 2;
+}
+
+class Sub extends Super {
+ @pragma("vm:entry-point")
+ final y = 3;
+ @pragma("vm:entry-point")
+ final x = 4;
+}
+
+@pragma("vm:entry-point")
+Sub getSub() => Sub();
+
+final tests = <IsolateTest>[
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ // Call eval to get a Dart list.
+ final evalResult = await service.invoke(
+ isolateId,
+ isolate.rootLib!.id!,
+ 'getSub',
+ [],
+ ) as InstanceRef;
+ final result = await service.getObject(
+ isolateId,
+ evalResult.id!,
+ ) as Instance;
+
+ expect(result.kind, InstanceKind.kPlainInstance);
+ expect(result.classRef!.name, 'Sub');
+ expect(result.size, isPositive);
+ final fields = result.fields!;
+ expect(fields.length, 4);
+ expect(fields[0].decl!.name, 'z');
+ expect(fields[0].value.valueAsString, '1');
+ expect(fields[1].decl!.name, 'y');
+ expect(fields[1].value.valueAsString, '2');
+ expect(fields[2].decl!.name, 'y');
+ expect(fields[2].value.valueAsString, '3');
+ expect(fields[3].decl!.name, 'x');
+ expect(fields[3].value.valueAsString, '4');
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'instance_field_order_rpc_test.dart',
+ );
diff --git a/pkg/vm_service/test/invoke_skip_breakpoint.dart b/pkg/vm_service/test/invoke_skip_breakpoint.dart
new file mode 100644
index 0000000..1c8c11b
--- /dev/null
+++ b/pkg/vm_service/test/invoke_skip_breakpoint.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:developer';
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const int LINE_A = 17;
+const int LINE_B = LINE_A + 5;
+
+String bar() {
+ print('bar'); // LINE_A
+ return 'bar';
+}
+
+void testMain() {
+ debugger(); // LINE_B
+ bar();
+ print('Done');
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ setBreakpointAtLine(LINE_A),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final result = await service.invoke(
+ isolateId,
+ isolate.rootLib!.id!,
+ 'bar',
+ [],
+ disableBreakpoints: true,
+ ) as InstanceRef;
+ expect(result.valueAsString, 'bar');
+ },
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'invoke_skip_breakpoint.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/isolate_lifecycle_test.dart b/pkg/vm_service/test/isolate_lifecycle_test.dart
new file mode 100644
index 0000000..eb955b1
--- /dev/null
+++ b/pkg/vm_service/test/isolate_lifecycle_test.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate' as I;
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const LINE_A = 24;
+
+final spawnCount = 4;
+final resumeCount = spawnCount ~/ 2;
+final isolates = [];
+
+void spawnEntry(int i) {}
+
+Future<void> during() async {
+ debugger(); // LINE_A
+ // Spawn spawnCount long lived isolates.
+ for (int i = 0; i < spawnCount; i++) {
+ final isolate = await I.Isolate.spawn(spawnEntry, i);
+ isolates.add(isolate);
+ }
+ print('spawned all isolates');
+}
+
+Future<int> numPaused(VmService service) async {
+ final vm = await service.getVM();
+ int paused = 0;
+ for (final isolateRef in vm.isolates!) {
+ final isolate = await service.getIsolate(isolateRef.id!);
+ if (isolate.pauseEvent != null &&
+ isolate.pauseEvent!.kind == EventKind.kPauseExit) {
+ paused++;
+ }
+ }
+ return paused;
+}
+
+final tests = <VMTest>[
+ (VmService service) async {
+ final vm = await service.getVM();
+ final isolates = vm.isolates!;
+ expect(isolates.length, 1);
+ await hasStoppedAtBreakpoint(service, isolates[0]);
+ await stoppedAtLine(LINE_A)(service, isolates[0]);
+ },
+ (VmService service) async {
+ int startCount = 0;
+ int runnableCount = 0;
+
+ final completer = Completer<void>();
+ late final StreamSubscription sub;
+ sub = service.onIsolateEvent.listen((event) async {
+ if (event.kind == EventKind.kIsolateStart) {
+ startCount++;
+ }
+ if (event.kind == EventKind.kIsolateRunnable) {
+ runnableCount++;
+ }
+ if (runnableCount == spawnCount) {
+ sub.cancel();
+ await service.streamCancel(EventStreams.kIsolate);
+ completer.complete();
+ }
+ });
+ await service.streamListen(EventStreams.kIsolate);
+
+ VM vm = await service.getVM();
+ expect(vm.isolates!.length, 1);
+
+ // Resume and wait for the isolates to spawn.
+ await service.resume(vm.isolates![0].id!);
+ await completer.future;
+ expect(startCount, spawnCount);
+ expect(runnableCount, spawnCount);
+ vm = await service.getVM();
+ expect(vm.isolates!.length, spawnCount + 1);
+ },
+ (VmService service) async {
+ final completer = Completer<void>();
+ if (await numPaused(service) < (spawnCount + 1)) {
+ late final StreamSubscription sub;
+ sub = service.onDebugEvent.listen((event) async {
+ if (event.kind == EventKind.kPauseExit) {
+ if (await numPaused(service) == (spawnCount + 1)) {
+ sub.cancel();
+ await service.streamCancel(EventStreams.kDebug);
+ completer.complete();
+ }
+ }
+ });
+ await service.streamListen(EventStreams.kDebug);
+ await completer.future;
+ }
+ expect(await numPaused(service), spawnCount + 1);
+ },
+ (VmService service) async {
+ int resumedReceived = 0;
+ final completer = Completer<void>();
+ late final StreamSubscription sub;
+ sub = service.onIsolateEvent.listen((event) async {
+ if (event.kind == EventKind.kIsolateExit) {
+ resumedReceived++;
+ if (resumedReceived >= resumeCount) {
+ sub.cancel();
+ await service.streamCancel(EventStreams.kIsolate);
+ completer.complete();
+ }
+ }
+ });
+ await service.streamListen(EventStreams.kIsolate);
+
+ // Resume a subset of the isolates.
+ int resumesIssued = 0;
+
+ final vm = await service.getVM();
+ final isolateList = vm.isolates!;
+ for (final isolate in isolateList) {
+ if (isolate.name!.endsWith('main')) {
+ continue;
+ }
+ try {
+ resumesIssued++;
+ await service.resume(isolate.id!);
+ } catch (_) {}
+ if (resumesIssued == resumeCount) {
+ break;
+ }
+ }
+ await completer.future;
+ },
+ (VmService service) async {
+ expect(await numPaused(service), spawnCount + 1 - resumeCount);
+ },
+];
+
+void main([args = const <String>[]]) => runVMTests(
+ args,
+ tests,
+ 'isolate_lifecycle_test.dart',
+ testeeConcurrent: during,
+ pause_on_exit: true,
+ );
diff --git a/pkg/vm_service/test/issue_25465_test.dart b/pkg/vm_service/test/issue_25465_test.dart
new file mode 100644
index 0000000..f88eb8b
--- /dev/null
+++ b/pkg/vm_service/test/issue_25465_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, 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.
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+const int LINE_A = 15;
+const int LINE_B = LINE_A + 1;
+
+testMain() {
+ final foo; // LINE_A
+ foo = 42; // LINE_B
+ print(foo);
+}
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ // Add breakpoints.
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLib = await service.getObject(
+ isolateId,
+ isolate.rootLib!.id!,
+ ) as Library;
+ final scriptId = rootLib.scripts![0].id!;
+
+ final bpt1 = await service.addBreakpoint(isolateId, scriptId, LINE_A);
+ final bpt2 = await service.addBreakpoint(isolateId, scriptId, LINE_B);
+ expect(bpt1.location!.line, LINE_A);
+ expect(bpt2.location!.line, LINE_B);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final breakpoints = isolate.breakpoints!;
+ expect(breakpoints.length, 2);
+ for (final bpt in isolate.breakpoints!) {
+ await service.removeBreakpoint(isolateId, bpt.id!);
+ }
+ },
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'issue_25465_test.dart',
+ testeeConcurrent: testMain,
+ pause_on_start: true,
+ );
diff --git a/pkg/vm_service/test/issue_27238_test.dart b/pkg/vm_service/test/issue_27238_test.dart
new file mode 100644
index 0000000..ceb7b3a
--- /dev/null
+++ b/pkg/vm_service/test/issue_27238_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, 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.
+// VMOptions=--verbose_debug
+
+import 'dart:async';
+import 'dart:developer';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const int LINE_0 = 20;
+const int LINE_A = LINE_0 + 1;
+const int LINE_B = LINE_A + 3;
+const int LINE_C = LINE_B + 1;
+const int LINE_D = LINE_C + 2;
+const int LINE_E = LINE_D + 1;
+
+testMain() async {
+ debugger(); // LINE_0.
+ final future1 = Future.value(); // LINE_A.
+ final future2 = Future.value();
+
+ await future1; // LINE_B.
+ await future2; // LINE_C.
+
+ print('foo1'); // LINE_D.
+ print('foo2'); // LINE_E.
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_0),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ smartNext,
+ hasStoppedAtBreakpoint,
+ smartNext,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ smartNext,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_C),
+ smartNext,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_D),
+ resumeIsolate,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'issue_27238_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/issue_27287_test.dart b/pkg/vm_service/test/issue_27287_test.dart
new file mode 100644
index 0000000..4f3cd01
--- /dev/null
+++ b/pkg/vm_service/test/issue_27287_test.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, 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.
+// VMOptions=--verbose_debug
+
+import 'dart:developer';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const int LINE_0 = 18;
+const int LINE_A = LINE_0 + 1;
+const int LINE_B = LINE_A + 1;
+
+late int libVariable;
+
+void testMain() {
+ debugger(); // LINE_0
+ print('Before'); // LINE_A
+ libVariable = 0; // LINE_B
+ print('and after');
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_0),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ stepOver,
+ // Check that debugger stops at assignment to top-level variable.
+ stoppedAtLine(LINE_B),
+ resumeIsolate,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'issue_27287_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/issue_30555_test.dart b/pkg/vm_service/test/issue_30555_test.dart
new file mode 100644
index 0000000..86f44a4
--- /dev/null
+++ b/pkg/vm_service/test/issue_30555_test.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate' as I;
+
+import 'package:vm_service/vm_service.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const LINE_A = 21;
+const LINE_B = LINE_A + 8;
+const LINE_C = LINE_B + 2;
+const LINE_D = LINE_C + 2;
+
+void isolate(I.SendPort port) {
+ final receive = I.RawReceivePort((_) {
+ debugger(); // LINE_A
+ throw Exception();
+ });
+ port.send(receive.sendPort);
+}
+
+void test() {
+ final receive = I.RawReceivePort((port) {
+ debugger(); // LINE_B
+ port.send(null);
+ debugger(); // LINE_C
+ port.send(null);
+ debugger(); // LINE_D
+ });
+ I.Isolate.spawn(isolate, receive.sendPort);
+}
+
+late final IsolateRef firstIsolate;
+late final IsolateRef secondIsolate;
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ (VmService service, IsolateRef isolateRef) async {
+ firstIsolate = isolateRef;
+
+ // Capture the second isolate when it spawns.
+ final completer = Completer<void>();
+ late final StreamSubscription sub;
+ sub = service.onDebugEvent.listen((event) async {
+ if (event.kind == EventKind.kPauseStart) {
+ secondIsolate = event.isolate!;
+ await sub.cancel();
+ await service.streamCancel(EventStreams.kDebug);
+ completer.complete();
+ }
+ });
+ await service.streamListen(EventStreams.kDebug);
+
+ // Resume and wait for the second isolate to spawn.
+ await resumeIsolate(service, firstIsolate);
+ await completer.future;
+
+ // Resume the second isolate.
+ await resumeIsolate(service, secondIsolate);
+
+ // First isolate should pause at LINE_B.
+ await hasStoppedAtBreakpoint(service, firstIsolate);
+ await stoppedAtLine(LINE_B)(service, firstIsolate);
+ await resumeIsolate(service, firstIsolate);
+
+ // First isolate should pause at LINE_C and second isolate should pause at
+ // LINE_A.
+ await Future.wait([
+ hasStoppedAtBreakpoint(service, firstIsolate).then(
+ (_) => stoppedAtLine(LINE_C)(service, firstIsolate),
+ ),
+ hasStoppedAtBreakpoint(service, secondIsolate).then(
+ (_) => stoppedAtLine(LINE_A)(service, secondIsolate),
+ ),
+ ]);
+
+ // Resume the second isolate.
+ await resumeIsolate(service, secondIsolate);
+
+ // The second isolate should exit due to an exception.
+ await hasStoppedAtExit(service, secondIsolate);
+
+ // Resume the first isolate.
+ await resumeIsolate(service, firstIsolate);
+
+ // The first isolate should pause at LINE_D.
+ await hasStoppedAtBreakpoint(service, firstIsolate);
+ await stoppedAtLine(LINE_D)(service, firstIsolate);
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'issue_30555_test.dart',
+ pause_on_start: true,
+ pause_on_exit: true,
+ testeeConcurrent: test,
+ );
diff --git a/pkg/vm_service/test/kill_paused_test.dart b/pkg/vm_service/test/kill_paused_test.dart
new file mode 100644
index 0000000..715ae44
--- /dev/null
+++ b/pkg/vm_service/test/kill_paused_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:developer';
+
+import 'package:vm_service/vm_service.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+const LINE_A = 15;
+
+void testMain() {
+ debugger(); // LINE_A
+ print('1');
+ while (true) {}
+}
+
+final tests = <IsolateTest>[
+ // Stopped at 'debugger' statement.
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ // Kill the app.
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ await service.kill(isolateId);
+ }
+];
+
+void main([args = const <String>[]]) async => runIsolateTests(
+ args,
+ tests,
+ 'kill_paused_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/kill_running_test.dart b/pkg/vm_service/test/kill_running_test.dart
new file mode 100644
index 0000000..3e89da4
--- /dev/null
+++ b/pkg/vm_service/test/kill_running_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, 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.
+
+import 'package:vm_service/vm_service.dart';
+
+import 'common/test_helper.dart';
+
+void testMain() {
+ print('1');
+ while (true) {}
+}
+
+final tests = <IsolateTest>[
+ // Kill the app.
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ await service.kill(isolateId);
+ }
+];
+
+void main([args = const <String>[]]) async => runIsolateTests(
+ args,
+ tests,
+ 'kill_running_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/local_variable_declaration_test.dart b/pkg/vm_service/test/local_variable_declaration_test.dart
new file mode 100644
index 0000000..2d4eaa9
--- /dev/null
+++ b/pkg/vm_service/test/local_variable_declaration_test.dart
@@ -0,0 +1,177 @@
+// Copyright (c) 2023, 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.
+// VMOptions=--verbose_debug
+
+import 'dart:developer';
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const LINE_A = 19;
+const LINE_B = LINE_A + 9;
+const LINE_C = LINE_B + 5;
+
+void testParameters(int jjjj, int oooo, [int? hhhh, int? nnnn]) {
+ debugger(); // LINE_A
+}
+
+void testMain() {
+ // ignore: unused_local_variable
+ int? xxx, yyyy, zzzzz;
+ for (int i = 0; i < 1; i++) {
+ // ignore: unused_local_variable
+ final foo = () {};
+ debugger(); // LINE_B
+ }
+ final bar = () {
+ print(xxx);
+ print(yyyy);
+ debugger(); // LINE_C
+ };
+ bar();
+ testParameters(0, 0);
+}
+
+String? getLine(Script script, int line) {
+ final index = line - script.lineOffset! - 1;
+ final lines = script.source!.split('\n');
+ if (lines.length < index) {
+ return null;
+ }
+ return lines[index];
+}
+
+String? getToken(Script script, int tokenPos) {
+ final line = script.getLineNumberFromTokenPos(tokenPos);
+ int? col = script.getColumnNumberFromTokenPos(tokenPos);
+ if ((line == null) || (col == null)) {
+ return null;
+ }
+ // Line and column numbers start at 1 in the VM.
+ --col;
+ final sourceLine = getLine(script, line);
+ if (sourceLine == null) {
+ return null;
+ }
+ final length = guessTokenLength(script, line, col);
+ if (length == null) {
+ return sourceLine.substring(col);
+ }
+ return sourceLine.substring(col, col + length);
+}
+
+bool _isOperatorChar(int c) {
+ switch (c) {
+ case 25: // %
+ case 26: // &
+ case 42: // *
+ case 43: // +
+ case 45: // -:
+ case 47: // /
+ case 60: // <
+ case 61: // =
+ case 62: // >
+ case 94: // ^
+ case 124: // |
+ case 126: // ~
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool _isInitialIdentifierChar(int c) {
+ if (c >= 65 && c <= 90) return true; // Upper
+ if (c >= 97 && c <= 122) return true; // Lower
+ if (c == 95) return true; // Underscore
+ if (c == 36) return true; // Dollar
+ return false;
+}
+
+bool _isIdentifierChar(int c) {
+ if (_isInitialIdentifierChar(c)) return true;
+ return c >= 48 && c <= 57; // Digit
+}
+
+int? guessTokenLength(Script script, int line, int column) {
+ String source = getLine(script, line)!;
+
+ int pos = column;
+ if (pos >= source.length) {
+ return null;
+ }
+
+ final c = source.codeUnitAt(pos);
+ if (c == 123) return 1; // { - Map literal
+
+ if (c == 91) return 1; // [ - List literal, index, index assignment
+
+ if (c == 40) return 1; // ( - Closure call
+
+ if (_isOperatorChar(c)) {
+ while (++pos < source.length && _isOperatorChar(source.codeUnitAt(pos)));
+ return pos - column;
+ }
+
+ if (_isInitialIdentifierChar(c)) {
+ while (++pos < source.length && _isIdentifierChar(source.codeUnitAt(pos)));
+ return pos - column;
+ }
+
+ return null;
+}
+
+Future<void> verifyVariables(VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ final frames = stack.frames!;
+ expect(frames.length, greaterThanOrEqualTo(1));
+ // Grab the top frame.
+ final frame = frames.first;
+ // Grab the script.
+ final script = await service.getObject(
+ isolateId,
+ frame.location!.script!.id!,
+ ) as Script;
+
+ // Ensure that the token at each declaration position is the name of the
+ // variable.
+ final variables = frame.vars!;
+ for (final variable in variables) {
+ final declarationTokenPos = variable.declarationTokenPos!;
+ final name = variable.name!;
+ final token = getToken(script, declarationTokenPos);
+ // When running from an appjit snapshot, sources aren't available so the returned token will
+ // be null.
+ if (token != null) {
+ expect(name, token);
+ }
+ }
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ verifyVariables,
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_C),
+ // We have stopped in the anonymous closure assigned to bar. Verify that
+ // variables captured in the context have valid declaration positions.
+ verifyVariables,
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ verifyVariables,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'local_variable_declaration_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/local_variable_in_awaiter_async_frame_test.dart b/pkg/vm_service/test/local_variable_in_awaiter_async_frame_test.dart
new file mode 100644
index 0000000..1207d22
--- /dev/null
+++ b/pkg/vm_service/test/local_variable_in_awaiter_async_frame_test.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:developer';
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const LINE_A = 13;
+const LINE_B = 14;
+
+Future<String> testFunction(String caption) async {
+ await Future.delayed(Duration(milliseconds: 1)); // LINE_A
+ return caption; // LINE_B
+}
+
+Future<void> testMain() async {
+ debugger();
+ final str = await testFunction('The caption');
+ print(str);
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ setBreakpointAtLine(LINE_A),
+ setBreakpointAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ hasLocalVarInTopStackFrame('caption'),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ hasLocalVarInTopStackFrame('caption'),
+ hasLocalVarInTopStackFrame('caption'),
+ resumeIsolate,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'local_variable_in_awaiter_async_frame_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/logging_test.dart b/pkg/vm_service/test/logging_test.dart
new file mode 100644
index 0000000..5181b20
--- /dev/null
+++ b/pkg/vm_service/test/logging_test.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:developer';
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+import 'package:logging/logging.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const LINE_A = 32;
+const LINE_B = LINE_A + 2;
+
+void init() {
+ Logger.root.level = Level.ALL;
+ Logger.root.onRecord.listen((logRecord) {
+ log(logRecord.message,
+ time: logRecord.time,
+ sequenceNumber: logRecord.sequenceNumber,
+ level: logRecord.level.value,
+ name: logRecord.loggerName,
+ zone: null,
+ error: logRecord.error,
+ stackTrace: logRecord.stackTrace);
+ });
+}
+
+void run() {
+ debugger();
+ Logger.root.fine('Hey Buddy!');
+ debugger();
+ Logger.root.info('YES');
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ resumeIsolateAndAwaitEvent(EventStreams.kLogging, (event) {
+ expect(event.kind, EventKind.kLogging);
+ expect(event.logRecord!.sequenceNumber, 0);
+ expect(event.logRecord!.message!.valueAsString, 'Hey Buddy!');
+ expect(event.logRecord!.level, Level.FINE.value);
+ expect(event.logRecord!.time, isNotNull);
+ }),
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ resumeIsolateAndAwaitEvent(EventStreams.kLogging, (event) {
+ expect(event.kind, EventKind.kLogging);
+ expect(event.logRecord!.sequenceNumber, 1);
+ expect(event.logRecord!.message!.valueAsString, 'YES');
+ expect(event.logRecord!.level, Level.INFO.value);
+ expect(event.logRecord!.time, isNotNull);
+ }),
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'logging_test.dart',
+ testeeBefore: init,
+ testeeConcurrent: run,
+ );