| // 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 |
| // |
| // Tests breakpoint pausing and resuming with many isolates running and pausing |
| // simultaneously. |
| |
| import 'dart:async'; |
| import 'dart:collection'; |
| import 'dart:developer'; |
| import 'dart:isolate' as dart_isolate; |
| |
| import 'package:test/test.dart'; |
| import 'package:vm_service/vm_service.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 = 45; |
| const LINE_C = 54; |
| // AUTOGENERATED END |
| |
| /* LINE_A */ void foo(args) { |
| print('${dart_isolate.Isolate.current.debugName}: $args'); |
| final sendPort = args[0] as dart_isolate.SendPort; |
| final int i = args[1] as int; |
| sendPort.send('reply from foo: $i'); |
| } |
| |
| Future<void> testMain() async { |
| final rps = List<dart_isolate.ReceivePort>.generate( |
| nIsolates, |
| (i) => dart_isolate.ReceivePort(), |
| ); |
| |
| print('Isolate count: $nIsolates\n\n\n\n'); |
| debugger(); // LINE_B |
| for (int i = 0; i < nIsolates; i++) { |
| await dart_isolate.Isolate.spawn( |
| foo, |
| [rps[i].sendPort, i], |
| debugName: 'foo$i', |
| ); |
| } |
| print(await Future.wait(rps.map((rp) => rp.first))); |
| debugger(); // LINE_C |
| } |
| |
| int nIsolates = 0; |
| final completerAtFoo = List<Completer>.generate(nIsolates, (_) => Completer()); |
| |
| Isolate? activeIsolate; |
| final pendingToResume = ListQueue<Isolate>(); |
| |
| late final StreamSubscription<Event> debugStreamSubscription; |
| |
| final tests = <IsolateTest>[ |
| hasPausedAtStart, |
| resumeIsolate, |
| hasStoppedAtBreakpoint, |
| stoppedAtLine(LINE_B), |
| stepOver, |
| hasStoppedAtBreakpoint, |
| stoppedAtLine(LINE_B + 1), |
| (VmService service, IsolateRef isolateRef) async { |
| // Set up a listener to wait for child isolate launch and breakpoint events. |
| debugStreamSubscription = service.onDebugEvent.listen((Event event) async { |
| switch (event.kind) { |
| case EventKind.kPauseStart: |
| { |
| final isolateId = event.isolate!.id!; |
| final childIsolate = await service.getIsolate(isolateId); |
| |
| for (final libRef in childIsolate.libraries!) { |
| final lib = |
| await service.getObject(isolateId, libRef.id!) as Library; |
| |
| // Set a breakpoint in the newly started isolate. |
| if (lib.uri!.endsWith( |
| 'break_on_function_many_child_isolates_test.dart', |
| )) { |
| final foo = lib.functions!.singleWhere((f) => f.name == 'foo'); |
| await service.addBreakpointAtEntry(isolateId, foo.id!); |
| break; |
| } |
| } |
| |
| // Keep track of the list of started isolates and the most recently |
| // resumed isolate. |
| if (activeIsolate == null) { |
| activeIsolate = childIsolate; |
| await service.resume(isolateId); |
| } else { |
| pendingToResume.addLast(childIsolate); |
| } |
| return; |
| } |
| case EventKind.kPauseBreakpoint: |
| { |
| final name = event.isolate!.name!; |
| if (!name.startsWith('foo')) { |
| return; |
| } |
| |
| final ndx = int.parse(name.substring('foo'.length)); |
| final isolateId = event.isolate!.id!; |
| |
| // Ensure the isolate has stopped at the expected line. |
| final stack = await service.getStack(isolateId); |
| final top = stack.frames![0]; |
| final script = await service.getObject( |
| isolateId, |
| top.location!.script!.id!, |
| ) as Script; |
| expect( |
| script.getLineNumberFromTokenPos(top.location!.tokenPos!), |
| LINE_A, |
| ); |
| |
| // Resume the isolate to let it run to completion. |
| expect(activeIsolate != null, true); |
| await service.resume(activeIsolate!.id!); |
| |
| completerAtFoo[ndx].complete(); |
| |
| // Resume the next isolate from its paused on start state. |
| if (pendingToResume.isNotEmpty) { |
| activeIsolate = pendingToResume.removeFirst(); |
| await service.resume(activeIsolate!.id!); |
| } |
| return; |
| } |
| } |
| }); |
| await service.streamListen(EventStreams.kDebug); |
| }, |
| resumeIsolate, |
| (VmService service, IsolateRef isolateRef) async { |
| await Future.wait(completerAtFoo.map((c) => c.future)); |
| await service.streamCancel(EventStreams.kDebug); |
| await debugStreamSubscription.cancel(); |
| }, |
| hasStoppedAtBreakpoint, |
| stoppedAtLine(LINE_C), |
| stepOver, |
| hasStoppedAtBreakpoint, |
| stoppedAtLine(LINE_C + 1), |
| resumeIsolate, |
| ]; |
| |
| Future runIsolateBreakpointPauseTest( |
| List<String> args, { |
| required String scriptName, |
| required int isolateCount, |
| }) { |
| nIsolates = isolateCount; |
| return runIsolateTests( |
| args, |
| tests, |
| scriptName, |
| testeeConcurrent: testMain, |
| pauseOnStart: true, |
| ); |
| } |
| |
| void main(List<String> args) => runIsolateBreakpointPauseTest( |
| args, |
| scriptName: 'break_on_function_many_child_isolates_test.dart', |
| isolateCount: 30, |
| ); |