[ package:vm_service ] Migrate Observatory service tests (o* - r*)
Also:
- added some missing RPC error kinds to `RPCErrorKind`
- started making use of `update_line_numbers.dart` to automatically fix
line numbers in service tests. Will go back through the existing
tests to update them to use this script in a future CL.
Change-Id: I99a443ca59429eec829e5f289c0f396562cde89b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333980
Reviewed-by: Derek Xu <derekx@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 07ada22..9d8038e 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 13.1.0-dev
+- Add the following error codes to `RPCErrorKind`:
+ - `kVmMustBePaused`
+ - `kCannotAddBreakpoint`
+ - `kIsolateMustBeRunnable`
+ - `kIsolateCannotBeResumed`
+ - `kIsolateIsReloading`
+ - `kIsolateCannotReload`
+ - `kIsolateNoReloadChangesApplied`
+ - `kInvalidTimelineRequest`
+
## 13.0.0
- Add Dart IO extension methods:
- `isSocketProfilingAvailable`
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index bb4898b..c688ad9 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -1934,15 +1934,40 @@
/// The requested feature is disabled.
kFeatureDisabled(code: 100, message: 'Feature is disabled'),
+ /// The VM must be paused when performing this operation.
+ kVmMustBePaused(code: 101, message: 'The VM must be paused'),
+
+ /// Unable to add a breakpoint at the specified line or function.
+ kCannotAddBreakpoint(
+ code: 102,
+ message: 'Unable to add breakpoint at specified line or function'),
+
/// The stream has already been subscribed to.
kStreamAlreadySubscribed(code: 103, message: 'Stream already subscribed'),
/// The stream has not been subscribed to.
kStreamNotSubscribed(code: 104, message: 'Stream not subscribed'),
+ /// Isolate must first be runnable.
+ kIsolateMustBeRunnable(code: 105, message: 'Isolate must be runnable'),
+
/// Isolate must first be paused.
kIsolateMustBePaused(code: 106, message: 'Isolate must be paused'),
+ /// The isolate could not be resumed.
+ kIsolateCannotBeResumed(
+ code: 107, message: 'The isolate could not be resumed'),
+
+ /// The isolate is currently reloading.
+ kIsolateIsReloading(code: 108, message: 'The isolate is currently reloading'),
+
+ /// The isolate could not be reloaded due to an unhandled exception.
+ kIsolateCannotReload(code: 109, message: 'The isolate could not be reloaded'),
+
+ /// The isolate reload resulted in no changes being applied.
+ kIsolateNoReloadChangesApplied(
+ code: 110, message: 'No reload changes applied'),
+
/// The service has already been registered.
kServiceAlreadyRegistered(code: 111, message: 'Service already registered'),
@@ -1953,6 +1978,12 @@
kExpressionCompilationError(
code: 113, message: 'Expression compilation error'),
+ /// The timeline related request could not be completed due to the current configuration.
+ kInvalidTimelineRequest(
+ code: 114,
+ message:
+ 'Invalid timeline request for the current timeline configuration'),
+
/// The custom stream does not exist.
kCustomStreamDoesNotExist(code: 130, message: 'Custom stream does not exist'),
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 44f31c9..9b3b084 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -21,6 +21,7 @@
markdown: any
path: any
pub_semver: any
+ stack_trace: any
test_package: any
test: any
vm_service_protos: any
diff --git a/pkg/vm_service/test/common/service_test_common.dart b/pkg/vm_service/test/common/service_test_common.dart
index fce4fce..13e75a9 100644
--- a/pkg/vm_service/test/common/service_test_common.dart
+++ b/pkg/vm_service/test/common/service_test_common.dart
@@ -5,6 +5,7 @@
library service_test_common;
import 'dart:async';
+import 'dart:typed_data';
import 'package:path/path.dart';
import 'package:test/test.dart';
@@ -528,6 +529,34 @@
}
}
+Future<HeapSnapshotGraph> fetchHeapSnapshot(
+ VmService service,
+ IsolateRef isolateRef,
+) async {
+ final isolateId = isolateRef.id!;
+ final completer = Completer<void>();
+ late final StreamSubscription sub;
+ final data = <ByteData>[];
+ sub = service.onHeapSnapshotEvent.listen((event) async {
+ data.add(event.data!);
+ if (event.last == true) {
+ sub.cancel();
+ await service.streamCancel(EventStreams.kHeapSnapshot);
+ completer.complete();
+ }
+ });
+ await service.streamListen(EventStreams.kHeapSnapshot);
+ await service.requestHeapSnapshot(isolateId);
+ await completer.future;
+ return HeapSnapshotGraph.fromChunks(data);
+}
+
+IsolateTest reloadSources({bool pause = false}) {
+ return (VmService service, IsolateRef isolateRef) async {
+ await service.reloadSources(isolateRef.id!, pause: pause);
+ };
+}
+
IsolateTest hasLocalVarInTopStackFrame(String varName) {
return (VmService service, IsolateRef isolateRef) async {
print("Checking we have variable '$varName' in the top frame");
diff --git a/pkg/vm_service/test/object_graph_identity_hash_test.dart b/pkg/vm_service/test/object_graph_identity_hash_test.dart
index 9ada6aa..c5ecdb1 100644
--- a/pkg/vm_service/test/object_graph_identity_hash_test.dart
+++ b/pkg/vm_service/test/object_graph_identity_hash_test.dart
@@ -1,64 +1,65 @@
-// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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:collection';
-
-import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+import 'common/service_test_common.dart';
import 'common/test_helper.dart';
+@pragma('vm:entry-point') // Prevent obfuscation
class Foo {}
+@pragma('vm:entry-point') // Prevent obfuscation
class Bar {}
class Container1 {
- @pragma("vm:entry-point")
- Foo foo = Foo();
- @pragma("vm:entry-point")
- Bar bar = Bar();
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final foo = Foo();
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final bar = Bar();
}
class Container2 {
Container2(this.foo);
- @pragma("vm:entry-point")
- Foo foo;
- @pragma("vm:entry-point")
- Bar bar = Bar();
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final Foo foo;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final bar = Bar();
}
class Container3 {
- @pragma("vm:entry-point")
- int number = 42;
- @pragma("vm:entry-point")
- double doub = 3.14;
- @pragma("vm:entry-point")
- String foo = 'foobar';
- @pragma("vm:entry-point")
- bool bar = false;
- @pragma("vm:entry-point")
- late Map baz;
- @pragma("vm:entry-point")
- late LinkedHashMap linkedBaz;
- @pragma("vm:entry-point")
- late List list;
- @pragma("vm:entry-point")
- late List unmodifiableList;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final number = 42;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final doub = 3.14;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final foo = 'foobar';
+ @pragma('vm:entry-point') // Prevent obfuscation
+ final bar = false;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ late final Map<String, String> baz;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ late final List<int> list;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ late final List<void> unmodifiableList;
Container3() {
baz = {
'a': 'b',
};
- linkedBaz = LinkedHashMap.from(baz);
list = [1, 2, 3];
- unmodifiableList = List.empty();
+ unmodifiableList = List<void>.empty();
}
}
+@pragma('vm:entry-point') // Prevent obfuscation
late Container1 c1;
+@pragma('vm:entry-point') // Prevent obfuscation
late Container2 c2;
+@pragma('vm:entry-point') // Prevent obfuscation
late Container3 c3;
void script() {
@@ -78,16 +79,19 @@
late HeapSnapshotGraph snapshot3;
final tests = <IsolateTest>[
- (VmService service, IsolateRef isolate) async {
- snapshot1 = await HeapSnapshotGraph.getSnapshot(service, isolate);
+ (VmService service, IsolateRef isolateRef) async {
+ snapshot1 = await fetchHeapSnapshot(service, isolateRef);
- Iterable<HeapSnapshotObject> container1s = snapshot1.objects.where(
- (HeapSnapshotObject obj) => obj.klass.name == 'Container1',
+ final container1s = snapshot1.objects.where(
+ (obj) => obj.klass.name == 'Container1',
);
expect(container1s.length, 1);
final c1Obj = container1s.first;
+ c1Obj.successors.forEach((element) {
+ print(element.klass.name);
+ });
snapshot1Foo = c1Obj.successors.firstWhere(
(element) => element.klass.name == 'Foo',
);
@@ -104,11 +108,10 @@
true,
);
},
- (VmService service, IsolateRef isolate) async {
- snapshot2 = await HeapSnapshotGraph.getSnapshot(service, isolate);
- ;
- Iterable<HeapSnapshotObject> container2s = snapshot2.objects.where(
- (HeapSnapshotObject obj) => obj.klass.name == 'Container2',
+ (VmService service, IsolateRef isolateRef) async {
+ snapshot2 = await fetchHeapSnapshot(service, isolateRef);
+ final container2s = snapshot2.objects.where(
+ (obj) => obj.klass.name == 'Container2',
);
expect(container2s.length, 1);
@@ -138,10 +141,10 @@
true,
);
},
- (VmService service, IsolateRef isolate) async {
- snapshot3 = await HeapSnapshotGraph.getSnapshot(service, isolate);
- Iterable<HeapSnapshotObject> container3s = snapshot3.objects.where(
- (HeapSnapshotObject obj) => obj.klass.name == 'Container3',
+ (VmService service, IsolateRef isolateRef) async {
+ snapshot3 = await fetchHeapSnapshot(service, isolateRef);
+ final container3s = snapshot3.objects.where(
+ (obj) => obj.klass.name == 'Container3',
);
expect(container3s.length, 1);
final c3Obj = container3s.first;
@@ -151,7 +154,7 @@
},
];
-main([args = const <String>[]]) => runIsolateTests(
+void main([args = const <String>[]]) => runIsolateTests(
args,
tests,
'object_graph_identity_hash_test.dart',
diff --git a/pkg/vm_service/test/parameters_in_scope_at_entry_test.dart b/pkg/vm_service/test/parameters_in_scope_at_entry_test.dart
new file mode 100644
index 0000000..cd90fe6
--- /dev/null
+++ b/pkg/vm_service/test/parameters_in_scope_at_entry_test.dart
@@ -0,0 +1,86 @@
+// 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:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+const LINE_A = 32;
+const LINE_B = LINE_A + 1;
+const LINE_C = LINE_B + 3;
+const LINE_D = LINE_C + 1;
+
+String foo(String param) {
+ return param;
+}
+
+String Function(String) fooClosure() {
+ String theClosureFunction(String param) {
+ return param;
+ }
+
+ return theClosureFunction;
+}
+
+void testMain() {
+ debugger(); // LINE_A
+ foo('in-scope'); // LINE_B
+
+ final f = fooClosure();
+ debugger(); // LINE_C
+ f('in-scope'); // LINE_D
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ stepInto,
+ hasStoppedAtBreakpoint,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.frames!, isNotEmpty);
+ final top = stack.frames!.first;
+ expect(top.function!.name, 'foo');
+ expect(top.vars!.length, equals(1));
+ final param = top.vars![0];
+ expect(param.name, 'param');
+ expect(param.value.valueAsString, 'in-scope');
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_C),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_D),
+ stepInto,
+ hasStoppedAtBreakpoint,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.frames!, isNotEmpty);
+ final top = stack.frames!.first;
+ expect(top.function!.name, 'theClosureFunction');
+ expect(top.vars!.length, equals(1));
+ final param = top.vars![0];
+ expect(param.name, 'param');
+ expect(param.value.valueAsString, 'in-scope');
+ },
+ resumeIsolate,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'parameters_in_scope_at_entry_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/pause_idle_isolate_test.dart b/pkg/vm_service/test/pause_idle_isolate_test.dart
new file mode 100644
index 0000000..1a54b3d
--- /dev/null
+++ b/pkg/vm_service/test/pause_idle_isolate_test.dart
@@ -0,0 +1,62 @@
+// 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 'dart:isolate' show ReceivePort;
+
+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 = 20;
+
+late final ReceivePort receivePort;
+
+void testMain() {
+ receivePort = ReceivePort();
+ debugger(); // LINE_A
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ // Wait for the isolate to become idle. We detect this by querying
+ // the stack until it becomes empty.
+ int frameCount;
+ do {
+ final stack = await service.getStack(isolateId);
+ frameCount = stack.frames!.length;
+ print('Frames: $frameCount');
+ await Future.delayed(const Duration(milliseconds: 10));
+ } while (frameCount > 0);
+ print('Isolate is idle.');
+ final isolate = await service.getIsolate(isolateId);
+ expect(isolate.pauseEvent!.kind, EventKind.kResume);
+
+ // Make sure that the isolate receives an interrupt even when it is
+ // idle. (https://github.com/dart-lang/sdk/issues/24349)
+ final interruptFuture = hasPausedFor(
+ service,
+ isolate,
+ EventKind.kPauseInterrupted,
+ );
+ print('Pausing...');
+ await service.pause(isolateId);
+ await interruptFuture;
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_idle_isolate_test.dart',
+ testeeConcurrent: testMain,
+ verbose_vm: true,
+ extraArgs: ['--trace-service', '--trace-service-verbose'],
+ );
diff --git a/pkg/vm_service/test/pause_on_exception_from_slow_path_test.dart b/pkg/vm_service/test/pause_on_exception_from_slow_path_test.dart
new file mode 100644
index 0000000..26c30e4
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_exception_from_slow_path_test.dart
@@ -0,0 +1,35 @@
+// 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=--deterministic --optimization-counter-threshold=1000
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+class X {
+ late String _y;
+
+ @pragma('vm:never-inline')
+ String get y => _y;
+}
+
+void testeeMain() async {
+ final x = X();
+ x._y = '';
+ for (int i = 0; i < 2000; i++) x.y;
+
+ X().y;
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_exception_from_slow_path_test.dart',
+ pause_on_unhandled_exceptions: true,
+ testeeConcurrent: testeeMain,
+ extraArgs: extraDebuggingArgs,
+ );
diff --git a/pkg/vm_service/test/pause_on_start_and_exit_test.dart b/pkg/vm_service/test/pause_on_start_and_exit_test.dart
new file mode 100644
index 0000000..f7faf25
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_start_and_exit_test.dart
@@ -0,0 +1,54 @@
+// 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/service_test_common.dart';
+import 'common/test_helper.dart';
+
+void testMain() {
+ print('Hello');
+}
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ // Grab the timestamp.
+ final pausetime1 = isolate.pauseEvent!.timestamp;
+ expect(pausetime1, isNotNull);
+
+ // Reload the isolate.
+ final reloaded = await service.getIsolate(isolateId);
+ // Verify that it is the same.
+ expect(pausetime1, reloaded.pauseEvent!.timestamp);
+ },
+ resumeIsolate,
+ hasStoppedAtExit,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ // Grab the timestamp.
+ final pausetime1 = isolate.pauseEvent!.timestamp;
+ expect(pausetime1, isNotNull);
+
+ // Reload the isolate.
+ final reloaded = await service.getIsolate(isolateId);
+ // Verify that it is the same.
+ expect(pausetime1, reloaded.pauseEvent!.timestamp);
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_start_and_exit_test.dart',
+ testeeConcurrent: testMain,
+ pause_on_start: true,
+ pause_on_exit: true,
+ verbose_vm: true,
+ extraArgs: ['--trace-service', '--trace-service-verbose'],
+ );
diff --git a/pkg/vm_service/test/pause_on_start_then_step_test.dart b/pkg/vm_service/test/pause_on_start_then_step_test.dart
new file mode 100644
index 0000000..c2aee63
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_start_then_step_test.dart
@@ -0,0 +1,30 @@
+// 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 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const LINE_A = 21;
+
+void testMain() {
+ print('Hello');
+}
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ stepInto,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+];
+
+void main([args = const <String>[]]) /* LINE_A */ => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_start_then_step_test.dart',
+ testeeConcurrent: testMain,
+ pause_on_start: true,
+ pause_on_exit: true,
+ verbose_vm: true,
+ extraArgs: ['--trace-service', '--trace-service-verbose'],
+ );
diff --git a/pkg/vm_service/test/pause_on_unhandled_async_exceptions2_test.dart b/pkg/vm_service/test/pause_on_unhandled_async_exceptions2_test.dart
new file mode 100644
index 0000000..bbc9fe9
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_unhandled_async_exceptions2_test.dart
@@ -0,0 +1,69 @@
+// 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=
+// VMOptions=--optimization-counter-threshold=90
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 42;
+// AUTOGENERATED END
+
+class Foo {}
+
+Never doThrow() {
+ throw 'TheException';
+}
+
+Future<Never> asyncThrower() async {
+ await 0; // force async gap
+ doThrow();
+}
+
+Future<void> testeeMain() async {
+ // Trigger optimization via OSR.
+ int s = 0;
+ for (int i = 0; i < 100; i++) {
+ s += i;
+ }
+ print(s);
+ // No try ... catch.
+ await asyncThrower(); // LINE_A
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.asyncCausalFrames, isNotNull);
+ final asyncStack = stack.asyncCausalFrames!;
+ expect(asyncStack.length, greaterThanOrEqualTo(4));
+ expect(asyncStack[0].function!.name, 'doThrow');
+ expect(asyncStack[1].function!.name, 'asyncThrower');
+ expect(asyncStack[2].kind, FrameKind.kAsyncSuspensionMarker);
+ expect(asyncStack[3].kind, FrameKind.kAsyncCausal);
+ expect(asyncStack[3].function!.name, 'testeeMain');
+ expect(asyncStack[3].location!.line, LINE_A);
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_unhandled_async_exceptions2_test.dart',
+ pause_on_unhandled_exceptions: true,
+ testeeConcurrent: testeeMain,
+ extraArgs: extraDebuggingArgs,
+ );
diff --git a/pkg/vm_service/test/pause_on_unhandled_async_exceptions3_test.dart b/pkg/vm_service/test/pause_on_unhandled_async_exceptions3_test.dart
new file mode 100644
index 0000000..3684b45
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_unhandled_async_exceptions3_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.
+
+// Verify that debugger can stop on an unhandled exception thrown from async
+// function. Regression test for https://dartbug.com/38697.
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 26;
+const LINE_B = 31;
+const LINE_C = 34;
+// AUTOGENERATED END
+
+throwException() async {
+ throw 'exception'; // LINE_A
+}
+
+testeeMain() async {
+ try {
+ await throwException(); // LINE_B
+ } finally {
+ try {
+ await throwException(); // LINE_C
+ } finally {}
+ }
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_A),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.frames, isNotEmpty);
+ expect(stack.frames![0].function!.name, 'throwException');
+ expect(stack.frames![0].location!.line, LINE_A);
+ },
+ resumeIsolate,
+ hasStoppedWithUnhandledException,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.frames, isNotEmpty);
+ // await in testeeMain
+ expect(stack.frames![0].location!.line, LINE_B);
+ },
+ resumeIsolate,
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedWithUnhandledException,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.frames, isNotEmpty);
+ expect(stack.frames![0].location!.line, LINE_C);
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_unhandled_async_exceptions3_test.dart',
+ pause_on_unhandled_exceptions: true,
+ testeeConcurrent: testeeMain,
+ );
diff --git a/pkg/vm_service/test/pause_on_unhandled_async_exceptions4_test.dart b/pkg/vm_service/test/pause_on_unhandled_async_exceptions4_test.dart
new file mode 100644
index 0000000..e74e907
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_unhandled_async_exceptions4_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.
+
+// Verifies that execution can be stopped on an unhandled exception
+// in async method which is not awaited.
+// Regression test for https://github.com/dart-lang/sdk/issues/51175.
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+Future<Never> doThrow() async {
+ await null; // force async gap
+ throw 'TheException';
+}
+
+Future<void> testeeMain() async {
+ doThrow();
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.asyncCausalFrames, isNotNull);
+ final asyncStack = stack.asyncCausalFrames!;
+ expect(asyncStack.length, greaterThanOrEqualTo(2));
+ expect(asyncStack[0].function!.name, 'doThrow');
+ expect(asyncStack[1].kind, FrameKind.kAsyncSuspensionMarker);
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_unhandled_async_exceptions4_test.dart',
+ pause_on_unhandled_exceptions: true,
+ testeeConcurrent: testeeMain,
+ extraArgs: extraDebuggingArgs,
+ );
diff --git a/pkg/vm_service/test/pause_on_unhandled_async_exceptions_test.dart b/pkg/vm_service/test/pause_on_unhandled_async_exceptions_test.dart
new file mode 100644
index 0000000..318eec5
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_unhandled_async_exceptions_test.dart
@@ -0,0 +1,91 @@
+// 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.
+
+// ignore_for_file: unused_catch_clause
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 62;
+// AUTOGENERATED END
+
+class Foo {}
+
+String doThrow() {
+ throw 'TheException';
+ // ignore: dead_code
+ return 'end of doThrow';
+}
+
+Future<void> asyncThrower() async {
+ await 0; // force async gap
+ doThrow();
+}
+
+void testeeMain() async {
+ try {
+ // This is a regression case for https://dartbug.com/53334:
+ // we should recognize `then(..., onError: ...)` as a catch
+ // all exception handler.
+ await asyncThrower().then((v) => v, onError: (e, st) {
+ // Caught and ignored.
+ });
+
+ await asyncThrower().onError((error, stackTrace) {
+ // Caught and ignored.
+ });
+
+ try {
+ await asyncThrower();
+ } on String catch (e) {
+ // Caught and ignored.
+ }
+
+ try {
+ await asyncThrower();
+ } catch (e) {
+ // Caught and ignored.
+ }
+
+ // This does not catch the exception.
+ try {
+ await asyncThrower(); // LINE_A
+ } on double catch (e) {}
+ } on Foo catch (e) {}
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.asyncCausalFrames, isNotNull);
+ final asyncStack = stack.asyncCausalFrames!;
+ expect(asyncStack.length, greaterThanOrEqualTo(4));
+ expect(asyncStack[0].function!.name, 'doThrow');
+ expect(asyncStack[1].function!.name, 'asyncThrower');
+ expect(asyncStack[2].kind, FrameKind.kAsyncSuspensionMarker);
+ expect(asyncStack[3].kind, FrameKind.kAsyncCausal);
+ expect(asyncStack[3].function!.name, 'testeeMain');
+ expect(asyncStack[3].location!.line, LINE_A);
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_unhandled_async_exceptions_test.dart',
+ pause_on_unhandled_exceptions: true,
+ testeeConcurrent: testeeMain,
+ extraArgs: extraDebuggingArgs,
+ );
diff --git a/pkg/vm_service/test/pause_on_unhandled_async_exceptions_zones_test.dart b/pkg/vm_service/test/pause_on_unhandled_async_exceptions_zones_test.dart
new file mode 100644
index 0000000..1948b2a
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_unhandled_async_exceptions_zones_test.dart
@@ -0,0 +1,90 @@
+// 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:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 61;
+// AUTOGENERATED END
+
+class Foo {}
+
+String doThrow() {
+ throw 'TheException';
+ // ignore: dead_code
+ return 'end of doThrow';
+}
+
+Future<void> asyncThrower() async {
+ await 0; // force async gap
+ doThrow();
+}
+
+Future<void> testeeMain() async {
+ try {
+ // This is a regression case for https://dartbug.com/53334:
+ // we should recognize `then(..., onError: ...)` as a catch
+ // all exception handler.
+ await asyncThrower().then((v) => v, onError: (e, st) {
+ // Caught and ignored.
+ });
+
+ await asyncThrower().onError((error, stackTrace) {
+ // Caught and ignored.
+ });
+
+ try {
+ await asyncThrower();
+ } on String {
+ // Caught and ignored.
+ }
+
+ try {
+ await asyncThrower();
+ } catch (e) {
+ // Caught and ignored.
+ }
+
+ // This does not catch the exception.
+ try {
+ await asyncThrower(); // LINE_A.
+ } on double {}
+ } on Foo {}
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.asyncCausalFrames, isNotNull);
+ final asyncStack = stack.asyncCausalFrames!;
+ expect(asyncStack.length, greaterThanOrEqualTo(4));
+ expect(asyncStack[0].function!.name, 'doThrow');
+ expect(asyncStack[1].function!.name, 'asyncThrower');
+ expect(asyncStack[2].kind, FrameKind.kAsyncSuspensionMarker);
+ expect(asyncStack[3].kind, FrameKind.kAsyncCausal);
+ expect(asyncStack[3].function!.name, 'testeeMain');
+ expect(asyncStack[3].location!.line, LINE_A);
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_unhandled_async_exceptions_zones_test.dart',
+ pause_on_unhandled_exceptions: true,
+ testeeConcurrent: () => Chain.capture(testeeMain),
+ extraArgs: extraDebuggingArgs,
+ );
diff --git a/pkg/vm_service/test/pause_on_unhandled_exceptions_catcherror_test.dart b/pkg/vm_service/test/pause_on_unhandled_exceptions_catcherror_test.dart
new file mode 100644
index 0000000..68076e4
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_unhandled_exceptions_catcherror_test.dart
@@ -0,0 +1,47 @@
+// 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.
+
+// Regression: https://github.com/dart-lang/sdk/issues/37953
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+Future<void> throwAsync() async {
+ await Future.delayed(const Duration(milliseconds: 10));
+ throw 'Throw from throwAsync!';
+}
+
+Future<void> nestedThrowAsync() async {
+ await Future.delayed(const Duration(milliseconds: 10));
+ await throwAsync();
+}
+
+Future<void> testeeMain() async {
+ await throwAsync().then((v) {
+ print('Hello from then()!');
+ }).catchError((e, st) {
+ print('Caught in catchError: $e!');
+ });
+ // Make sure we can chain through off-stack awaiters as well.
+ try {
+ await nestedThrowAsync();
+ } catch (e) {
+ print('Caught in catch: $e!');
+ }
+}
+
+final tests = <IsolateTest>[
+ // We shouldn't get any debugger breaks before exit as all exceptions are
+ // caught (via `Future.catchError()`).
+ hasStoppedAtExit,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'pause_on_unhandled_exceptions_catcherror_test.dart',
+ pause_on_unhandled_exceptions: true,
+ pause_on_exit: true,
+ testeeConcurrent: testeeMain,
+ );
diff --git a/pkg/vm_service/test/pause_on_unhandled_exceptions_test.dart b/pkg/vm_service/test/pause_on_unhandled_exceptions_test.dart
new file mode 100644
index 0000000..21571e8
--- /dev/null
+++ b/pkg/vm_service/test/pause_on_unhandled_exceptions_test.dart
@@ -0,0 +1,31 @@
+// 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';
+
+Never doThrow() {
+ throw 'TheException';
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ expect(stack.frames, isNotEmpty);
+ expect(stack.frames![0].function!.name, 'doThrow');
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTestsSynchronous(
+ args,
+ tests,
+ 'pause_on_unhandled_exceptions_test.dart',
+ pause_on_unhandled_exceptions: true,
+ testeeConcurrent: doThrow,
+ );
diff --git a/pkg/vm_service/test/positive_token_pos_test.dart b/pkg/vm_service/test/positive_token_pos_test.dart
new file mode 100644
index 0000000..7875d87
--- /dev/null
+++ b/pkg/vm_service/test/positive_token_pos_test.dart
@@ -0,0 +1,69 @@
+// 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';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_C = 29;
+const LINE_A = 33;
+const LINE_B = 34;
+// AUTOGENERATED END
+
+const LINE_B_COL = 3;
+const LINE_C_COL = 1;
+
+Future<void> helper() async {
+ // LINE_C
+}
+
+void testMain() {
+ debugger(); // LINE_A
+ helper(); // LINE_B
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ stepInto,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final stack = await service.getStack(isolateId);
+ final frames = stack.frames!;
+ expect(frames.length, greaterThan(2));
+
+ // We used to return a negative token position for this frame.
+ // See issue #27128.
+ var frame = frames[0];
+ expect(frame.function!.name, 'helper');
+ expect(await frame.location!.line, LINE_C + 1);
+ expect(await frame.location!.column, LINE_C_COL);
+
+ frame = frames[1];
+ expect(frame.function!.name, 'testMain');
+ expect(await frame.location!.line, LINE_B);
+ expect(await frame.location!.column, LINE_B_COL);
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'positive_token_pos_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/private_rpcs/reachable_size_test.dart b/pkg/vm_service/test/private_rpcs/reachable_size_test.dart
new file mode 100644
index 0000000..c41a4d0
--- /dev/null
+++ b/pkg/vm_service/test/private_rpcs/reachable_size_test.dart
@@ -0,0 +1,113 @@
+// 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 Pair {
+ // Make sure these fields are not removed by the tree shaker.
+ @pragma('vm:entry-point') // Prevent obfuscation
+ dynamic x;
+ @pragma('vm:entry-point') // Prevent obfuscation
+ dynamic y;
+}
+
+@pragma('vm:entry-point') // Prevent obfuscation
+dynamic p1;
+@pragma('vm:entry-point') // Prevent obfuscation
+dynamic p2;
+
+buildGraph() {
+ p1 = Pair();
+ p2 = Pair();
+
+ // Adds to both reachable and retained size.
+ p1.x = <dynamic>[];
+ p2.x = <dynamic>[];
+
+ // Adds to reachable size only.
+ p1.y = p2.y = <dynamic>[];
+}
+
+extension on VmService {
+ Future<int> getReachableSize(String isolateId, String targetId) async {
+ final result = await callMethod(
+ '_getReachableSize',
+ isolateId: isolateId,
+ args: {
+ 'targetId': targetId,
+ },
+ ) as InstanceRef;
+
+ return int.parse(result.valueAsString!);
+ }
+
+ Future<int> getRetainedSize(String isolateId, String targetId) async {
+ final result = await callMethod(
+ '_getRetainedSize',
+ isolateId: isolateId,
+ args: {
+ 'targetId': targetId,
+ },
+ ) as InstanceRef;
+
+ return int.parse(result.valueAsString!);
+ }
+}
+
+final tests = <IsolateTest>[
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLibId = isolate.rootLib!.id!;
+
+ final p1Ref = await service.evaluate(
+ isolateId,
+ rootLibId,
+ 'p1',
+ ) as InstanceRef;
+ final p1 = await service.getObject(isolateId, p1Ref.id!) as Instance;
+
+ final p2Ref = await service.evaluate(
+ isolateId,
+ rootLibId,
+ 'p2',
+ ) as InstanceRef;
+ final p2 = await service.getObject(isolateId, p2Ref.id!) as Instance;
+
+ // In general, shallow <= retained <= reachable. In this program,
+ // 0 < shallow < retained < reachable.
+
+ final p1_shallow = p1.size!;
+ final p1_retained = await service.getRetainedSize(isolateId, p1.id!);
+ final p1_reachable = await service.getReachableSize(isolateId, p1.id!);
+
+ expect(0, lessThan(p1_shallow));
+ expect(p1_shallow, lessThan(p1_retained));
+ expect(p1_retained, lessThan(p1_reachable));
+
+ final p2_shallow = p2.size!;
+ final p2_retained = await service.getRetainedSize(isolateId, p2.id!);
+ final p2_reachable = await service.getReachableSize(isolateId, p2.id!);
+
+ expect(0, lessThan(p2_shallow));
+ expect(p2_shallow, lessThan(p2_retained));
+ expect(p2_retained, lessThan(p2_reachable));
+
+ expect(p1_shallow, p2_shallow);
+ expect(p1_retained, p2_retained);
+ expect(p1_reachable, p2_reachable);
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'reachable_size_test.dart',
+ testeeBefore: buildGraph,
+ );
diff --git a/pkg/vm_service/test/regexp_function_test.dart b/pkg/vm_service/test/regexp_function_test.dart
new file mode 100644
index 0000000..a3fdfe5
--- /dev/null
+++ b/pkg/vm_service/test/regexp_function_test.dart
@@ -0,0 +1,80 @@
+// 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=
+// VMOptions=--interpret_irregexp
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/expect.dart';
+import 'common/test_helper.dart';
+
+// Make sure these variables are not removed by the tree shaker.
+@pragma('vm:entry-point')
+late RegExp regex0;
+@pragma('vm:entry-point')
+late RegExp regex;
+
+void script() {
+ // Check the internal NUL doesn't trip up the name scrubbing in the vm.
+ regex0 = RegExp('with internal \u{0} NUL');
+ regex = RegExp(r'(\w+)');
+ final str = 'Parse my string';
+ final matches = regex.allMatches(str); // Run to generate bytecode.
+ Expect.equals(matches.length, 3);
+}
+
+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 variables = rootLib.variables!;
+
+ final fieldRef = variables.singleWhere((v) => v.name == 'regex');
+ final field = await service.getObject(
+ isolateId,
+ fieldRef.id!,
+ ) as Field;
+
+ final regexRef = field.staticValue as InstanceRef;
+ expect(regexRef.kind, InstanceKind.kRegExp);
+
+ final regex = await service.getObject(
+ isolateId,
+ regexRef.id!,
+ ) as Instance;
+
+ final regexJson = regex.json!;
+ if (regexJson
+ case {
+ '_oneByteBytecode': {'kind': InstanceKind.kUint8List},
+ // No two-byte string subject was used.
+ '_twoByteBytecode': {'kind': InstanceKind.kNull},
+ } when !regexJson.containsKey('_oneByteFunction')) {
+ // Running with interpreted regexp.
+ } else if (regexJson
+ case {
+ '_oneByteFunction': {'type': '@Function'},
+ '_twoByteFunction': {'type': '@Function'},
+ '_externalOneByteFunction': {'type': '@Function'},
+ '_externalTwoByteFunction': {'type': '@Function'},
+ }) {
+ // Running with compiled regexp.
+ } else {
+ fail('Unexpected JSON structure: ${regex.json!}');
+ }
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'regexp_function_test.dart',
+ testeeBefore: script,
+ );
diff --git a/pkg/vm_service/test/regress_28443_test.dart b/pkg/vm_service/test/regress_28443_test.dart
new file mode 100644
index 0000000..760321f
--- /dev/null
+++ b/pkg/vm_service/test/regress_28443_test.dart
@@ -0,0 +1,73 @@
+// 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 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 36;
+const LINE_B = 41;
+const LINE_C = 45;
+// AUTOGENERATED END
+
+class VMServiceClient {
+ VMServiceClient(this.x);
+ Future<void> close() => Future.microtask(() => print('close'));
+ final String x;
+}
+
+Future<void> collect() async {
+ final uri = 'abc';
+ late VMServiceClient vmService;
+ await Future.microtask(() async {
+ try {
+ vmService = VMServiceClient(uri);
+ await Future.microtask(() => throw new TimeoutException('here'));
+ } on Object {
+ vmService.close();
+ rethrow; // LINE_A
+ }
+ });
+}
+
+Future<void> test_code() async /* LINE_B */ {
+ try {
+ await collect();
+ } on TimeoutException {
+ print('ok'); // LINE_C
+ }
+}
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ markDartColonLibrariesDebuggable,
+ setBreakpointAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ setBreakpointAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ setBreakpointAtLine(LINE_C),
+ stepOut,
+ resumeIsolate,
+ stoppedAtLine(LINE_C),
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'regress_28443_test.dart',
+ testeeConcurrent: test_code,
+ pause_on_start: true,
+ pause_on_exit: false,
+ );
diff --git a/pkg/vm_service/test/regress_28980_test.dart b/pkg/vm_service/test/regress_28980_test.dart
new file mode 100644
index 0000000..b2539c4
--- /dev/null
+++ b/pkg/vm_service/test/regress_28980_test.dart
@@ -0,0 +1,78 @@
+// 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 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 26;
+const LINE_B = 44;
+// AUTOGENERATED END
+
+late A _lock;
+bool _lockEnabled = true;
+
+String flutterRoot = 'abc';
+
+A foo(String a, String b, String c, String d) {
+ return A(); // LINE_A
+}
+
+class A {
+ Future lock() => Future.microtask(() => print('lock'));
+ final path = 'path';
+}
+
+class FileSystemException {}
+
+Future<void> test_code() async {
+ if (!_lockEnabled) return null;
+ _lock = foo(flutterRoot, 'bin', 'cache', 'lockfile');
+ bool locked = false;
+ bool printed = false;
+ while (!locked) {
+ try {
+ await _lock.lock();
+ locked = true; // LINE_B
+ } on FileSystemException {
+ if (!printed) {
+ print('Print path: ${_lock.path}');
+ print('Just another line...');
+ printed = true;
+ }
+ await Future<Null>.delayed(const Duration(milliseconds: 50));
+ }
+ }
+}
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ markDartColonLibrariesDebuggable,
+ setBreakpointAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ setBreakpointAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stepInto,
+ stepInto,
+ stepInto,
+ resumeIsolate,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'regress_28980_test.dart',
+ testeeConcurrent: test_code,
+ pause_on_start: true,
+ pause_on_exit: false,
+ );
diff --git a/pkg/vm_service/test/regress_34841_lib.dart b/pkg/vm_service/test/regress_34841_lib.dart
new file mode 100644
index 0000000..b4c4283
--- /dev/null
+++ b/pkg/vm_service/test/regress_34841_lib.dart
@@ -0,0 +1,10 @@
+// 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.
+
+mixin Foo {
+ String baz() => StackTrace.current.toString();
+ final String foo = () {
+ return StackTrace.current.toString();
+ }();
+}
diff --git a/pkg/vm_service/test/regress_34841_test.dart b/pkg/vm_service/test/regress_34841_test.dart
new file mode 100644
index 0000000..9addd77
--- /dev/null
+++ b/pkg/vm_service/test/regress_34841_test.dart
@@ -0,0 +1,88 @@
+// 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.
+
+// While it's not (currently) necessary, add some noise here to push down token
+// positions in this file compared to the file regress_34841_lib.dart.
+// This is to ensure that any possible tokens in that file are just comments
+// (i.e. not actual) positions in this file.
+
+import 'dart:developer';
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+import 'regress_34841_lib.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 34;
+// AUTOGENERATED END
+
+class Bar extends Object with Foo {}
+
+void testFunction() {
+ final bar = Bar();
+ print(bar.foo);
+ print(bar.baz());
+ debugger(); // LINE_A
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ (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 script = await service.getObject(
+ isolateId,
+ rootLib.scripts!.first.id!,
+ ) as Script;
+
+ final report = await service.getSourceReport(
+ isolateId,
+ [SourceReportKind.kCoverage],
+ scriptId: script.id!,
+ forceCompile: true,
+ );
+
+ final ranges = report.ranges!;
+ final coveragePlaces = <int>[];
+ for (final range in ranges) {
+ final coverage = range.coverage!;
+ for (int i in coverage.hits!) {
+ coveragePlaces.add(i);
+ }
+ for (int i in coverage.misses!) {
+ coveragePlaces.add(i);
+ }
+ }
+ expect(ranges, isNotEmpty);
+
+ // Make sure we can translate it all.
+ for (int place in coveragePlaces) {
+ int? line = script.getLineNumberFromTokenPos(place);
+ int? column = script.getColumnNumberFromTokenPos(place);
+ if (line == null || column == null) {
+ throw 'Token $place translated to $line:$column';
+ }
+ }
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'regress_34841_test.dart',
+ testeeConcurrent: testFunction,
+ );
diff --git a/pkg/vm_service/test/regress_45684_test.dart b/pkg/vm_service/test/regress_45684_test.dart
new file mode 100644
index 0000000..8734d05
--- /dev/null
+++ b/pkg/vm_service/test/regress_45684_test.dart
@@ -0,0 +1,50 @@
+// 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
+
+// Check that a try/finally is not treated as a try/catch:
+// http://dartbug.com/45684.
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 32;
+// AUTOGENERATED END
+
+void tryFinally(Function() code) {
+ // There is a synthetic try/catch inside try/finally but it is not authored
+ // by the user, so debugger should not consider that this try/catch is
+ // going to handle the exception.
+ try {
+ code();
+ } finally {}
+}
+
+Never syncThrow() {
+ throw 'Hello from syncThrow!'; // LINE_A
+}
+
+void testMain() {
+ tryFinally(syncThrow);
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_A),
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'regress_45684_test.dart',
+ testeeConcurrent: testMain,
+ pause_on_unhandled_exceptions: true,
+ );
diff --git a/pkg/vm_service/test/regress_46419_test.dart b/pkg/vm_service/test/regress_46419_test.dart
new file mode 100644
index 0000000..304d5fa
--- /dev/null
+++ b/pkg/vm_service/test/regress_46419_test.dart
@@ -0,0 +1,99 @@
+// 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 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 31;
+const LINE_B = 34;
+const LINE_C = 41;
+const LINE_D = 48;
+// AUTOGENERATED END
+
+// DO NOT REORDER BEYOND THIS POINT
+bool testing = false;
+void printSync() {
+ print('sync');
+ if (testing) {
+ // LINE_A
+ // We'll never reach this code, but setting a breakpoint here will result in
+ // the breakpoint being resolved below at line LINE_C.
+ print('unreachable'); // LINE_B
+ }
+}
+
+printSyncStar() sync* {
+ // We'll end up resolving breakpoint 1 to this location instead of at LINE_A
+ // if #46419 regresses.
+ print('sync*'); // LINE_C
+}
+
+testeeDo() {
+ printSync();
+ final iterator = printSyncStar();
+
+ print('middle'); // LINE_D
+
+ iterator.toList();
+}
+// END DO NOT REORDER SECTION
+
+late Breakpoint bp1;
+late Breakpoint bp2;
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ (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!.first.id!;
+
+ bp1 = await service.addBreakpoint(isolateId, scriptId, LINE_B);
+ print('BP1 - $bp1');
+ bp2 = await service.addBreakpoint(isolateId, scriptId, LINE_D);
+ print('BP2 - $bp2');
+
+ final done = Completer<void>();
+ late final StreamSubscription sub;
+ sub = service.onDebugEvent.listen((event) async {
+ if (event.kind == EventKind.kPauseBreakpoint) {
+ expect(event.pauseBreakpoints!.length, 1);
+ final bp = event.pauseBreakpoints!.first;
+ print('Hit $bp');
+ expect(bp, bp2);
+ sub.cancel();
+ await service.streamCancel(EventStreams.kDebug);
+ await service.resume(isolateId);
+ done.complete();
+ }
+ });
+ await service.streamListen(EventStreams.kDebug);
+ await service.resume(isolateId);
+ await done.future;
+ }
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'regress_46419_test.dart',
+ testeeConcurrent: testeeDo,
+ pause_on_start: true,
+ );
diff --git a/pkg/vm_service/test/reload_sources_test.dart b/pkg/vm_service/test/reload_sources_test.dart
new file mode 100644
index 0000000..e873c03
--- /dev/null
+++ b/pkg/vm_service/test/reload_sources_test.dart
@@ -0,0 +1,69 @@
+// 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:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'common/test_helper.dart';
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 23;
+// AUTOGENERATED END
+
+void testMain() {
+ debugger(); // LINE_A.
+ print('1');
+ while (true) {}
+}
+
+Future<void> isolateIsRunning(VmService service, IsolateRef isolateRef) async {
+ final isolate = await service.getIsolate(isolateRef.id!);
+ final pauseEvent = isolate.pauseEvent;
+ final isPaused = pauseEvent == null
+ ? false
+ : isolate.pauseEvent!.kind != EventKind.kResume;
+ final topFrame = pauseEvent?.topFrame;
+ expect(!isPaused && topFrame != null, true);
+}
+
+final tests = <IsolateTest>[
+ // Stopped at 'debugger' statement.
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ // Reload sources and request to pause post reload. The pause request will be
+ // ignored because we are already paused at a breakpoint.
+ reloadSources(pause: true),
+ // Ensure that we are still stopped at a breakpoint.
+ hasStoppedAtBreakpoint,
+ // Resume the isolate into the while loop.
+ resumeIsolate,
+ // Reload sources and request to pause post reload. The pause request will
+ // be respected because we are not already paused.
+ reloadSources(pause: true),
+ // Ensure that we are paused post reload request.
+ hasStoppedPostRequest,
+ // Resume the isolate.
+ resumeIsolate,
+ // Verify that it is running.
+ isolateIsRunning,
+ // Reload sources and do not request to pause post reload.
+ reloadSources(),
+ // Verify that it is running.
+ isolateIsRunning,
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'reload_sources_test.dart',
+ testeeConcurrent: testMain,
+ );
diff --git a/pkg/vm_service/test/resume_shutdown_race_test.dart b/pkg/vm_service/test/resume_shutdown_race_test.dart
new file mode 100644
index 0000000..1f6addf7
--- /dev/null
+++ b/pkg/vm_service/test/resume_shutdown_race_test.dart
@@ -0,0 +1,84 @@
+// 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=--pause-isolates-on-exit --enable-vm-service=0 --disable-service-auth-codes
+
+// See b/271314180.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:developer';
+import 'dart:io';
+import 'dart:isolate';
+
+const childCount = 4;
+
+void child(i) {
+ print('Child $i');
+ // Paused-at-exit.
+}
+
+void main() {
+ for (int i = 0; i < childCount; i++) {
+ Isolate.spawn(child, i);
+ }
+ Isolate.spawn(resumer, null);
+ print('Parent');
+ // Paused-at-exit.
+}
+
+Future<Map<String, dynamic>> get(
+ String method,
+ Map<String, dynamic> arguments,
+) async {
+ final info = await Service.getInfo();
+ final uri = info.serverUri!.replace(path: method, queryParameters: arguments);
+ final client = HttpClient();
+ try {
+ final request = await client.getUrl(uri);
+ final response = await request.close();
+ final string = await response.transform(utf8.decoder).join();
+ return jsonDecode(string);
+ } finally {
+ client.close();
+ }
+}
+
+Future<Never> resumer(_) async {
+ try {
+ // Wait for the main isolate and children to all be paused at exit.
+ final paused = <String>[];
+ do {
+ paused.clear();
+ final vm = (await get('getVM', {}))['result'];
+ for (Map<String, dynamic> isolate in vm['isolates']) {
+ final id = isolate['id'];
+ isolate = (await get('getIsolate', {'isolateId': id}))['result'];
+ if ((isolate['pauseEvent'] != null) &&
+ (isolate['pauseEvent']['kind'] == 'PauseExit')) {
+ paused.add(id);
+ }
+ }
+ } while (paused.length != childCount + 1);
+
+ // Resume the main isolate and children. When the main isolate resumes, it
+ // will exit and trigger VM shutdown. The VM shutdown will send the OOB kill
+ // message to children and so race with the resume message. No matter how
+ // the race resolves, the children should exit and the VM shutdown should
+ // not hang with
+ // Attempt:138 waiting for isolate child to check in
+ // ...
+ for (final id in paused) {
+ await get('resume', {'isolateId': id}).then((v) => print(v));
+ }
+ } catch (e, st) {
+ print(e);
+ print(st);
+ rethrow;
+ }
+
+ // This isolate itself will be paused-at-exit with no resume message coming,
+ // but should exit because of the VM shutdown.
+ throw StateError('Unreachable');
+}
diff --git a/pkg/vm_service/test/rewind_optimized_out_test.dart b/pkg/vm_service/test/rewind_optimized_out_test.dart
new file mode 100644
index 0000000..0b3e814
--- /dev/null
+++ b/pkg/vm_service/test/rewind_optimized_out_test.dart
@@ -0,0 +1,102 @@
+// 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';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_0 = 39;
+const LINE_A = 41;
+const LINE_B = 46;
+const LINE_C = 49;
+const LINE_D = 53;
+// AUTOGENERATED END
+
+int global = 0;
+
+@pragma('vm:never-inline')
+int b3(int x) {
+ int sum = 0;
+ try {
+ for (int i = 0; i < x; i++) {
+ sum += x;
+ }
+ } catch (e) {
+ print('caught $e');
+ }
+ if (global >= 100) {
+ debugger(); // LINE_0.
+ }
+ global = global + 1; // LINE_A.
+ return sum;
+}
+
+@pragma('vm:prefer-inline')
+int b2(x) => b3(x); // LINE_B.
+
+@pragma('vm:prefer-inline')
+int b1(x) => b2(x); // LINE_C.
+
+void test() {
+ while (true) {
+ b1(10000); // LINE_D.
+ }
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_0),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+
+ // We are at our breakpoint with global=100.
+ final result = await service.evaluate(
+ isolateId, isolate.rootLib!.id!, 'global') as InstanceRef;
+ expect(result.valueAsString, '100');
+
+ // Rewind the top stack frame.
+ bool caughtException = false;
+ try {
+ await service.resume(isolateId, step: StepOption.kRewind);
+ fail('Unreachable');
+ } on RPCError catch (e) {
+ caughtException = true;
+ expect(e.code, RPCErrorKind.kIsolateCannotBeResumed.code);
+ expect(
+ e.details,
+ startsWith('Cannot rewind to frame 1 due to conflicting compiler '
+ 'optimizations. Run the vm with --no-prune-dead-locals '
+ 'to disallow these optimizations. Next valid rewind '
+ 'frame is '));
+ }
+ expect(caughtException, true);
+ },
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'rewind_optimized_out_test.dart',
+ testeeConcurrent: test,
+ extraArgs: [
+ '--trace-rewind',
+ '--prune-dead-locals',
+ '--no-background-compilation',
+ '--optimization-counter-threshold=10',
+ ],
+ );
diff --git a/pkg/vm_service/test/rewind_test.dart b/pkg/vm_service/test/rewind_test.dart
new file mode 100644
index 0000000..e2df063
--- /dev/null
+++ b/pkg/vm_service/test/rewind_test.dart
@@ -0,0 +1,194 @@
+// 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';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_0 = 39;
+const LINE_A = 41;
+const LINE_B = 46;
+const LINE_C = 49;
+const LINE_D = 53;
+// AUTOGENERATED END
+
+int global = 0;
+
+@pragma('vm:never-inline')
+int b3(int x) {
+ int sum = 0;
+ try {
+ for (int i = 0; i < x; i++) {
+ sum += x;
+ }
+ } catch (e) {
+ print('caught $e');
+ }
+ if (global >= 100) {
+ debugger(); // LINE_0.
+ }
+ global = global + 1; // LINE_A.
+ return sum;
+}
+
+@pragma('vm:prefer-inline')
+int b2(x) => b3(x); // LINE_B.
+
+@pragma('vm:prefer-inline')
+int b1(x) => b2(x); // LINE_C.
+
+void test() {
+ while (true) {
+ b1(10000); // LINE_D.
+ }
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_0),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ // We are not able to rewind frame 0.
+ bool caughtException = false;
+ try {
+ await service.resume(
+ isolateId,
+ step: StepOption.kRewind,
+ frameIndex: 0,
+ );
+ fail('Unreachable');
+ } on RPCError catch (e) {
+ caughtException = true;
+ expect(e.code, RPCErrorKind.kIsolateCannotBeResumed.code);
+ expect(e.details, 'Frame must be in bounds [1..8]: saw 0');
+ }
+ expect(caughtException, true);
+ },
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ // We are not able to rewind frame 13.
+ bool caughtException = false;
+ try {
+ await service.resume(
+ isolateId,
+ step: StepOption.kRewind,
+ frameIndex: 13,
+ );
+ fail('Unreachable');
+ } on RPCError catch (e) {
+ caughtException = true;
+ expect(e.code, RPCErrorKind.kIsolateCannotBeResumed.code);
+ expect(e.details, 'Frame must be in bounds [1..8]: saw 13');
+ }
+ expect(caughtException, true);
+ },
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLibId = isolate.rootLib!.id!;
+
+ // We are at our breakpoint with global=100.
+ final result = await service.evaluate(
+ isolateId,
+ rootLibId,
+ 'global',
+ ) as InstanceRef;
+ print('global is $result');
+ expect(result.valueAsString, '100');
+
+ // Rewind the top stack frame.
+ await service.resume(isolateId, step: StepOption.kRewind, frameIndex: 1);
+ },
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_0),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLibId = isolate.rootLib!.id!;
+
+ // global still is equal to 100. We did not execute 'global++'.
+ final result = await service.evaluate(
+ isolateId,
+ rootLibId,
+ 'global',
+ ) as InstanceRef;
+ print('global is $result');
+ expect(result.valueAsString, '100');
+
+ // Rewind up to 'test'/
+ await service.resume(isolateId, step: StepOption.kRewind, frameIndex: 3);
+ },
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_D),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLibId = isolate.rootLib!.id!;
+
+ // Reset global to 0 and start again.
+ final result = await service.evaluate(
+ isolateId,
+ rootLibId,
+ 'global = 0',
+ ) as InstanceRef;
+ expect(result.valueAsString, '0');
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_0),
+ stepOver,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLibId = isolate.rootLib!.id!;
+
+ // We are at our breakpoint with global=100.
+ final result = await service.evaluate(
+ isolateId,
+ rootLibId,
+ 'global',
+ ) as InstanceRef;
+ print('global is $result');
+ expect(result.valueAsString, '100');
+
+ // Rewind the top 2 stack frames.
+ await service.resume(isolateId, step: StepOption.kRewind, frameIndex: 2);
+ },
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_C),
+];
+
+void main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'rewind_test.dart',
+ testeeConcurrent: test,
+ extraArgs: [
+ '--trace-rewind',
+ '--no-prune-dead-locals',
+ '--no-background-compilation',
+ '--optimization-counter-threshold=10',
+ ],
+ );
diff --git a/pkg/vm_service/test/update_line_numbers.dart b/pkg/vm_service/test/update_line_numbers.dart
new file mode 100644
index 0000000..fce70d1
--- /dev/null
+++ b/pkg/vm_service/test/update_line_numbers.dart
@@ -0,0 +1,71 @@
+// 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:io';
+
+// This script updates LINE_* constants in the given test.
+void main(List<String> args) {
+ if (args.length != 1 || !args[0].endsWith('.dart')) {
+ print('Usage: ${Platform.executable} ${Platform.script} <test-file>');
+ exit(1);
+ }
+
+ final inputFile = File(args[0]);
+
+ final content = inputFile.readAsLinesSync();
+
+ const autogeneratedStart = '// AUTOGENERATED START';
+ const autogeneratedEnd = '// AUTOGENERATED END';
+
+ final lineConstantPattern = RegExp(r'^const( int)? LINE_\w+ = \d+;$');
+
+ final prefix = content
+ .takeWhile((line) =>
+ !lineConstantPattern.hasMatch(line) && autogeneratedStart != line)
+ .toList();
+
+ final suffix = content
+ .skip(prefix.length)
+ .skipWhile(
+ (line) => line.startsWith('//') || lineConstantPattern.hasMatch(line))
+ .toList();
+
+ final lineCommentPattern =
+ RegExp(r' // (LINE_\w+)\.?$|/\*\s*(LINE_\w+)\s*\*/');
+ final mapping = <String, int>{};
+ for (var i = 0; i < suffix.length; i++) {
+ final line = suffix[i];
+ final m = lineCommentPattern.firstMatch(line);
+ if (m != null) {
+ mapping[(m[1] ?? m[2])!] = i;
+ }
+ }
+
+ final header = [
+ ...prefix,
+ autogeneratedStart,
+ '//',
+ '// Update these constants by running:',
+ '//',
+ '// dart pkg/vm_service/test/update_line_numbers.dart '
+ '<test.dart>',
+ '//',
+ ];
+
+ // Mapping currently contains 0 based indices into suffix.
+ // Convert them into 1 based line numbers taking into account that we will
+ // generate a header + one line for each LINE_* constant plus
+ // autogeneratedEnd marker.
+ mapping
+ .updateAll((_, value) => 1 + header.length + mapping.length + 1 + value);
+
+ inputFile.writeAsString([
+ ...header,
+ for (var entry in mapping.entries) 'const ${entry.key} = ${entry.value};',
+ autogeneratedEnd,
+ ...suffix,
+ '',
+ ].join('\n'));
+ print('Updated ${inputFile}');
+}
diff --git a/pkg/vm_service/tool/dart/generate_dart_client.dart b/pkg/vm_service/tool/dart/generate_dart_client.dart
index cf0cdfa..d4fa532 100644
--- a/pkg/vm_service/tool/dart/generate_dart_client.dart
+++ b/pkg/vm_service/tool/dart/generate_dart_client.dart
@@ -265,15 +265,37 @@
/// The requested feature is disabled.
kFeatureDisabled(code: 100, message: 'Feature is disabled'),
+ /// The VM must be paused when performing this operation.
+ kVmMustBePaused(code: 101, message: 'The VM must be paused'),
+
+ /// Unable to add a breakpoint at the specified line or function.
+ kCannotAddBreakpoint(code: 102,
+ message: 'Unable to add breakpoint at specified line or function'),
+
/// The stream has already been subscribed to.
kStreamAlreadySubscribed(code: 103, message: 'Stream already subscribed'),
/// The stream has not been subscribed to.
kStreamNotSubscribed(code: 104, message: 'Stream not subscribed'),
+ /// Isolate must first be runnable.
+ kIsolateMustBeRunnable(code: 105, message: 'Isolate must be runnable'),
+
/// Isolate must first be paused.
kIsolateMustBePaused(code: 106, message: 'Isolate must be paused'),
+ /// The isolate could not be resumed.
+ kIsolateCannotBeResumed(code: 107, message: 'The isolate could not be resumed'),
+
+ /// The isolate is currently reloading.
+ kIsolateIsReloading(code: 108, message: 'The isolate is currently reloading'),
+
+ /// The isolate could not be reloaded due to an unhandled exception.
+ kIsolateCannotReload(code: 109, message: 'The isolate could not be reloaded'),
+
+ /// The isolate reload resulted in no changes being applied.
+ kIsolateNoReloadChangesApplied(code: 110, message: 'No reload changes applied'),
+
/// The service has already been registered.
kServiceAlreadyRegistered(code: 111, message: 'Service already registered'),
@@ -284,6 +306,10 @@
kExpressionCompilationError(
code: 113, message: 'Expression compilation error'),
+ /// The timeline related request could not be completed due to the current configuration.
+ kInvalidTimelineRequest(code: 114,
+ message: 'Invalid timeline request for the current timeline configuration'),
+
/// The custom stream does not exist.
kCustomStreamDoesNotExist(code: 130, message: 'Custom stream does not exist'),
diff --git a/pkg/vm_service/tool/dart/generate_dart_interface.dart b/pkg/vm_service/tool/dart/generate_dart_interface.dart
index 2053f9f..c0c2fc7 100644
--- a/pkg/vm_service/tool/dart/generate_dart_interface.dart
+++ b/pkg/vm_service/tool/dart/generate_dart_interface.dart
@@ -19,7 +19,7 @@
import 'dart:async';
-import 'package:vm_service/vm_service.dart' hide ServiceExtensionRegistry, VmServerConnection, VmServiceInterface;
+import 'package:vm_service/vm_service.dart';
import 'service_extension_registry.dart';