From 868e4d1a6f90bd6b22362f32747cfb614ccd7498 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 10 Sep 2024 14:36:46 +1000 Subject: [PATCH 01/19] WIP --- pkgs/coverage/CHANGELOG.md | 4 + pkgs/coverage/lib/src/collect.dart | 159 +++++++++------- pkgs/coverage/lib/src/util.dart | 177 ++++++++++++++++++ pkgs/coverage/test/chrome_test.dart | 2 +- pkgs/coverage/test/collect_coverage_test.dart | 55 ------ .../test/test_files/test_app_isolate.dart | 3 + 6 files changed, 275 insertions(+), 125 deletions(-) diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index e14e83c8d..45e589a7c 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.9.3-wip + +- Fix bug where tests involving multiple isolates never finish (#520). + ## 1.9.2 - Fix repository link in pubspec. diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index fa298b50c..d77e01624 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -25,8 +25,8 @@ const _debugTokenPositions = bool.fromEnvironment('DEBUG_COVERAGE'); /// If [resume] is true, all isolates will be resumed once coverage collection /// is complete. /// -/// If [waitPaused] is true, collection will not begin until all isolates are -/// in the paused state. +/// If [waitPaused] is true, collection will not begin for an isolate until it +/// is in the paused state. /// /// If [includeDart] is true, code coverage for core `dart:*` libraries will be /// collected. @@ -93,12 +93,21 @@ Future> collect(Uri serviceUri, bool resume, } try { - if (waitPaused) { - await _waitIsolatesPaused(service, timeout: timeout); - } - - return await _getAllCoverage(service, includeDart, functionCoverage, - branchCoverage, scopedOutput, isolateIds, coverableLineCache); + final job = _CollectionJob( + service, + includeDart, + functionCoverage, + [ + SourceReportKind.kCoverage, + if (branchCoverage) SourceReportKind.kBranchCoverage, + ], + scopedOutput ?? {}, + isolateIds, + coverableLineCache); + return { + 'type': 'CodeCoverage', + 'coverage': await job.collectAll(waitPaused, resume), + }; } finally { if (resume) { await _resumeIsolates(service); @@ -109,64 +118,98 @@ Future> collect(Uri serviceUri, bool resume, } } -Future> _getAllCoverage( - VmService service, - bool includeDart, - bool functionCoverage, - bool branchCoverage, - Set? scopedOutput, - Set? isolateIds, - Map>? coverableLineCache) async { - scopedOutput ??= {}; - final vm = await service.getVM(); - final allCoverage = >[]; - - final sourceReportKinds = [ - SourceReportKind.kCoverage, - if (branchCoverage) SourceReportKind.kBranchCoverage, - ]; +class _CollectionJob { + // Inputs. + final VmService _service; + final bool _includeDart; + final bool _functionCoverage; + final List _sourceReportKinds; + final Set _scopedOutput; + final Set? _isolateIds; + final Map>? _coverableLineCache; + + // State. + final List? _librariesAlreadyCompiled; + final _coveredIsolateGroups = {}; + final _coveredIsolates = {}; + + // Output. + final _allCoverage = >[]; + + _CollectionJob( + this._service, + this._includeDart, + this._functionCoverage, + this._sourceReportKinds, + this._scopedOutput, + this._isolateIds, + this._coverableLineCache) + : _librariesAlreadyCompiled = _coverableLineCache?.keys.toList() {} + + Future>> collectAll( + bool waitPaused, bool resume) async { + if (waitPaused) { + await _collectPausedIsolatesUntilAllExit(); + print("!!! EXITING !!!"); + } else { + for (final isolateRef in await getAllIsolates(_service)) { + await _collectOne(isolateRef); + } + } + return _allCoverage; + } - final librariesAlreadyCompiled = coverableLineCache?.keys.toList(); + Future _collectPausedIsolatesUntilAllExit() { + return IsolatePausedListener(_service, + (IsolateRef isolateRef, bool isLastIsolateInGroup) async { + if (isLastIsolateInGroup) { + print(" Collecting for ${isolateRef.name}"); + await _collectOne(isolateRef); + print(" DONE collecting for ${isolateRef.name}"); + } + print(" Resuming ${isolateRef.name}"); + await _service.resume(isolateRef.id!); + print(" DONE Resuming ${isolateRef.name}"); + }).listenUntilAllExited(); + } - // Program counters are shared between isolates in the same group. So we need - // to make sure we're only gathering coverage data for one isolate in each - // group, otherwise we'll double count the hits. - final coveredIsolateGroups = {}; + Future _collectOne(IsolateRef isolateRef) async { + if (!(_isolateIds?.contains(isolateRef.id) ?? true)) return; - for (var isolateRef in vm.isolates!) { - if (isolateIds != null && !isolateIds.contains(isolateRef.id)) continue; + // _coveredIsolateGroups is only used for the !waitPaused flow The + // waitPaused flow only ever calls _collectOne once per isolate group. final isolateGroupId = isolateRef.isolateGroupId; if (isolateGroupId != null) { - if (coveredIsolateGroups.contains(isolateGroupId)) continue; - coveredIsolateGroups.add(isolateGroupId); + if (_coveredIsolateGroups.contains(isolateGroupId)) return; + _coveredIsolateGroups.add(isolateGroupId); } late final SourceReport isolateReport; try { - isolateReport = await service.getSourceReport( + isolateReport = await _service.getSourceReport( isolateRef.id!, - sourceReportKinds, + _sourceReportKinds, forceCompile: true, reportLines: true, - libraryFilters: scopedOutput.isNotEmpty - ? List.from(scopedOutput.map((filter) => 'package:$filter/')) + libraryFilters: _scopedOutput.isNotEmpty + ? List.from(_scopedOutput.map((filter) => 'package:$filter/')) : null, - librariesAlreadyCompiled: librariesAlreadyCompiled, + librariesAlreadyCompiled: _librariesAlreadyCompiled, ); } on SentinelException { - continue; + return; } + final coverage = await _processSourceReport( - service, + _service, isolateRef, isolateReport, - includeDart, - functionCoverage, - coverableLineCache, - scopedOutput); - allCoverage.addAll(coverage); + _includeDart, + _functionCoverage, + _coverableLineCache, + _scopedOutput); + _allCoverage.addAll(coverage); } - return {'type': 'CodeCoverage', 'coverage': allCoverage}; } Future _resumeIsolates(VmService service) async { @@ -190,29 +233,6 @@ Future _resumeIsolates(VmService service) async { } } -Future _waitIsolatesPaused(VmService service, {Duration? timeout}) async { - final pauseEvents = { - EventKind.kPauseStart, - EventKind.kPauseException, - EventKind.kPauseExit, - EventKind.kPauseInterrupted, - EventKind.kPauseBreakpoint - }; - - Future allPaused() async { - final vm = await service.getVM(); - if (vm.isolates!.isEmpty) throw StateError('No isolates.'); - for (var isolateRef in vm.isolates!) { - final isolate = await service.getIsolate(isolateRef.id!); - if (!pauseEvents.contains(isolate.pauseEvent!.kind)) { - throw StateError('Unpaused isolates remaining.'); - } - } - } - - return retry(allPaused, _retryInterval, timeout: timeout); -} - /// Returns the line number to which the specified token position maps. /// /// Performs a binary search within the script's token position table to locate @@ -278,6 +298,7 @@ Future>> _processSourceReport( return; } final funcName = await _getFuncName(service, isolateRef, func); + // TODO(liama): Is this still necessary, or is location.line valid? final tokenPos = location.tokenPos!; final line = _getLineFromTokenPos(script, tokenPos); if (line == null) { diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index 7ffe74728..578315a4d 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:vm_service/vm_service.dart'; + // TODO(cbracken) make generic /// Retries the specified function with the specified interval and returns /// the result on successful completion. @@ -179,3 +181,178 @@ Future serviceUriFromProcess(Stream procStdout) { }); return serviceUriCompleter.future; } + +Future> getAllIsolates(VmService service) async => + (await service.getVM()).isolates!; + +/// Buffers VM service isolate [Event]s until [flush] is called. +/// +/// [flush] passes each buffered event to the handler function. After that, any +/// further events are immediately passed to the handler. [flush] returns a +/// future that completes when all the events in the queue have been handled (as +/// well as any events that arrive while flush is in progress). +class IsolateEventBuffer { + final Future Function(Event event) _handler; + final _buffer = Queue(); + var _flushed = true; + + ServiceEventBuffer(this._handler); + + Future add(Event event) async { + if (_flushed) { + await _handler(event); + } else { + _buffer.add(event); + } + } + + Future flush() async { + while (_buffer.isNotEmpty) { + final event = _buffer.removeFirst(); + await _handler(event); + } + _flushed = true; + } +} + +class _GroupState { + // IDs of the isolates running in this group. + final _running = {}; + + // IDs of the isolates paused just before exiting in this group. + final _paused = {}; + + // IDs of the isolates that have exited in this group. + final _exited = {}; + + bool noRunningIsolates => _running.isEmpty; + bool noIsolates => _running.isEmpty && _paused.isEmpty; + + bool start(String id) { + if (_pause.contains(id) || _exited.contains(id)) return false; + _running.add(id); + return true; + } + + bool pause(String id) { + if (_exited.contains(id)) return false; + _running.remove(id); + _paused.add(id); + return true; + } + + void exit(String id) { + _paused.remove(id); + _running.remove(id); + _exited.add(id); + } +} + +/// Calls onIsolatePaused whenever an isolate reaches the pause-on-exit state, +/// and passes a flag stating whether that isolate is the last one in the group. +class IsolatePausedListener { + final VmService _service; + final Future Function(IsolateRef isolate, bool isLastIsolateInGroup) + _onIsolatePaused; + final _allExitedCompleter = Completer(); + final _isolateGroups = {}; + + IsolatePausedListener(this.service, this.onIsolatePaused); + + /// Starts listening and returns a future that completes when all isolates + /// have exited. + Future listenUntilAllExited() async { + // NOTE: Why is this class so complicated? + // - We only receive start/pause/exit events that arrive after we've + // subscribed (using _service.streamListen below). + // - So after we subscribe, we have to backfill any isolates that are + // already started/paused by looking at the current isolates. + // - But since that backfill is an async process, we may get isolate events + // arriving during that process. + // - So we buffer all the received events until the backfill is complete. + // - That means we can receive duplicate add/pause events: one from the + // backfill, and one from a real event that arrived during the backfill. + // - So the _onStart/_onPause/_onExit methods need to be robust to + // duplicate events (and out-of-order events to some extent, as the + // backfill's [add, pause] events and the real [add, pause, exit] events + // can be interleaved). + final eventBuffer = IsolateEventBuffer((Event event) { + switch(event.kind) { + case EventKind.kIsolateStart: + return _onStart(event.isolate!); + case EventKind.kPauseExit: + return _onPause(event.isolate!); + case EventKind.kIsolateExit: + return _onExit(event.isolate!); + } + }); + + // Listen for isolate open/close events. + _service.onIsolateEvent.listen(eventBuffer.add); + await _service.streamListen(EventStreams.kIsolate); + + // Listen for isolate paused events. + _service.onDebugEvent.listen(eventBuffer.add); + await _service.streamListen(EventStreams.kDebug); + + // Backfill. Add/pause isolates that existed before we subscribed. + final isolates = await getAllIsolates(_service); + for (final isolateRef in isolates) { + _onStart(isolateRef); + final isolate = await _service.getIsolate(isolateRef.id!); + if (isolate.pauseEvent!.kind == EventKind.kPauseExit) { + await _onPause(isolateRef); + } + } + + // Flush the buffered stream events, and the start processing them as they + // arrive. + await eventBuffer.flush(); + + // If the user forgot to pass --pause-isolates-on-exit, there's a chance + // that all the isolates exited before we subscribed to the exit event. To + // make sure we're not waiting on allExited forever, bail if isolateGroups + // is empty at this point. + if (isolateGroups.isEmpty) return; + + await _allExitedCompleter.future; + } + + void _onStart(IsolateRef isolateRef) { + print("Start event for ${isolateRef.name}"); + final group = (isolateGroups[isolateRef.isolateGroupId!] ??= _GroupState()); + group.add(isolateRef.id!); + } + + Future _onPause(IsolateRef isolateRef) async { + print("Pause event for ${isolateRef.name}"); + final String groupId = isolateRef.isolateGroupId!; + final group = isolateGroups[groupId]; + if (group == null) { + // See NOTE in listenUntilAllExited. + return; + } + + group.pause(isolateRef.id!); + await _onIsolatePaused(isolateRef, group.noRunningIsolates); + print(" DONE Pause event for ${isolateRef.name}"); + } + + void _onExit(IsolateRef isolateRef) { + print("Exit event for ${isolateRef.name}"); + final String groupId = isolateRef.isolateGroupId!; + final group = isolateGroups[groupId]; + if (group == null) { + // Like the pause event, if the group is null it's due to a race condition + // with + return; + } + group.exit(isolateRef.id!); + if (group.noIsolates) { + isolateGroups.remove(groupId); + } + if (isolateGroups.isEmpty && !_allExitedCompleter.isCompleted()) { + _allExitedCompleter.complete(); + } + } +} diff --git a/pkgs/coverage/test/chrome_test.dart b/pkgs/coverage/test/chrome_test.dart index 165692fd4..50e7600f4 100644 --- a/pkgs/coverage/test/chrome_test.dart +++ b/pkgs/coverage/test/chrome_test.dart @@ -2,7 +2,7 @@ // 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. -// TODO(#388): Fix and re-enable this test. +// TODO(https://github.com/dart-lang/tools/issues/494): Fix and re-enable this. @TestOn('!windows') library; diff --git a/pkgs/coverage/test/collect_coverage_test.dart b/pkgs/coverage/test/collect_coverage_test.dart index 3c61ee5b8..7e048ce12 100644 --- a/pkgs/coverage/test/collect_coverage_test.dart +++ b/pkgs/coverage/test/collect_coverage_test.dart @@ -166,61 +166,6 @@ void main() { ); }); - test('HitMap.parseJson, old VM without branch coverage', () async { - final resultString = await _collectCoverage(true, true); - final jsonResult = json.decode(resultString) as Map; - final coverage = jsonResult['coverage'] as List; - final hitMap = await HitMap.parseJson( - coverage.cast>(), - ); - expect(hitMap, contains(_sampleAppFileUri)); - - final isolateFile = hitMap[_isolateLibFileUri]; - final expectedHits = { - 11: 1, - 12: 1, - 13: 1, - 15: 0, - 19: 1, - 23: 1, - 24: 2, - 28: 1, - 29: 1, - 30: 1, - 32: 0, - 38: 1, - 39: 1, - 41: 1, - 42: 3, - 43: 1, - 44: 3, - 45: 1, - 48: 1, - 49: 1, - 51: 1, - 54: 1, - 55: 1, - 56: 1, - 59: 1, - 60: 1, - 62: 1, - 63: 1, - 64: 1, - 66: 1, - 67: 1, - 68: 1 - }; - expect(isolateFile?.lineHits, expectedHits); - expect(isolateFile?.funcHits, {11: 1, 19: 1, 23: 1, 28: 1, 38: 1}); - expect(isolateFile?.funcNames, { - 11: 'fooSync', - 19: 'BarClass.BarClass', - 23: 'BarClass.baz', - 28: 'fooAsync', - 38: 'isolateTask' - }); - }); - test('parseCoverage', () async { final tempDir = await Directory.systemTemp.createTemp('coverage.test.'); diff --git a/pkgs/coverage/test/test_files/test_app_isolate.dart b/pkgs/coverage/test/test_files/test_app_isolate.dart index e7ade553c..d10cbb3ff 100644 --- a/pkgs/coverage/test/test_files/test_app_isolate.dart +++ b/pkgs/coverage/test/test_files/test_app_isolate.dart @@ -38,6 +38,9 @@ Future fooAsync(int x) async { void isolateTask(List threeThings) { sleep(const Duration(milliseconds: 500)); + // Regression test for https://github.com/dart-lang/tools/issues/520. + Isolate.run(() => print('Another isolate'), debugName: 'Grandchild'); + fooSync(answer); fooAsync(answer).then((_) { final port = threeThings.first as SendPort; From c9e2ca35addb3048d96e72479e293487e7b431ee Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 10:26:34 +1000 Subject: [PATCH 02/19] wip --- pkgs/coverage/lib/src/collect.dart | 56 +++++++++++---- pkgs/coverage/lib/src/util.dart | 72 ++++++++++++------- pkgs/coverage/test/collect_coverage_test.dart | 4 +- pkgs/coverage/test/test_files/test_app.dart | 2 + .../test/test_files/test_app_isolate.dart | 8 +-- 5 files changed, 97 insertions(+), 45 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index d77e01624..71699d1c7 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -77,8 +77,9 @@ Future> collect(Uri serviceUri, bool resume, final socket = await WebSocket.connect('$uri', compression: options); final controller = StreamController(); socket.listen((data) => controller.add(data as String), onDone: () { - controller.close(); - service.dispose(); + // controller.close(); + print(">>> Disposing VM service 0"); + // service.dispose(); }); service = VmService(controller.stream, socket.add, log: StdoutLog(), disposeHandler: socket.close); @@ -86,6 +87,7 @@ Future> collect(Uri serviceUri, bool resume, } on TimeoutException { // The signature changed in vm_service version 6.0.0. // ignore: await_only_futures + print(">>> Disposing VM service 1"); await service.dispose(); rethrow; } @@ -106,14 +108,15 @@ Future> collect(Uri serviceUri, bool resume, coverableLineCache); return { 'type': 'CodeCoverage', - 'coverage': await job.collectAll(waitPaused, resume), + 'coverage': await job.collectAll(waitPaused), }; } finally { - if (resume) { + if (resume && !waitPaused) { await _resumeIsolates(service); } // The signature changed in vm_service version 6.0.0. // ignore: await_only_futures + print(">>> Disposing VM service 2"); await service.dispose(); } } @@ -147,7 +150,7 @@ class _CollectionJob { : _librariesAlreadyCompiled = _coverableLineCache?.keys.toList() {} Future>> collectAll( - bool waitPaused, bool resume) async { + bool waitPaused) async { if (waitPaused) { await _collectPausedIsolatesUntilAllExit(); print("!!! EXITING !!!"); @@ -159,33 +162,49 @@ class _CollectionJob { return _allCoverage; } - Future _collectPausedIsolatesUntilAllExit() { - return IsolatePausedListener(_service, + Future _collectPausedIsolatesUntilAllExit() async { + IsolateRef? mainIsolateRef; + await IsolatePausedListener(_service, (IsolateRef isolateRef, bool isLastIsolateInGroup) async { - if (isLastIsolateInGroup) { - print(" Collecting for ${isolateRef.name}"); - await _collectOne(isolateRef); - print(" DONE collecting for ${isolateRef.name}"); + try { + if (isLastIsolateInGroup) { + print(" Collecting for ${isolateRef.name}"); + await _collectOne(isolateRef); + print(" DONE collecting for ${isolateRef.name}"); + } + } finally { + print(" Resuming ${isolateRef.name}"); + // if (isolateRef.name != 'main') { + if (true) { + await _service.resume(isolateRef.id!); + } else { + mainIsolateRef = isolateRef; + } + print(" DONE Resuming ${isolateRef.name}"); } - print(" Resuming ${isolateRef.name}"); - await _service.resume(isolateRef.id!); - print(" DONE Resuming ${isolateRef.name}"); }).listenUntilAllExited(); + if (mainIsolateRef != null) await _service.resume(mainIsolateRef!.id!); } Future _collectOne(IsolateRef isolateRef) async { + print(" _collectOne0"); if (!(_isolateIds?.contains(isolateRef.id) ?? true)) return; - // _coveredIsolateGroups is only used for the !waitPaused flow The + // _coveredIsolateGroups is only relevant for the !waitPaused flow. The // waitPaused flow only ever calls _collectOne once per isolate group. + print(" _collectOne1"); final isolateGroupId = isolateRef.isolateGroupId; if (isolateGroupId != null) { + print(" _collectOne2"); if (_coveredIsolateGroups.contains(isolateGroupId)) return; + print(" _collectOne3"); _coveredIsolateGroups.add(isolateGroupId); } + print(" _collectOne4"); late final SourceReport isolateReport; try { + print(" _collectOne5"); isolateReport = await _service.getSourceReport( isolateRef.id!, _sourceReportKinds, @@ -196,10 +215,16 @@ class _CollectionJob { : null, librariesAlreadyCompiled: _librariesAlreadyCompiled, ); + print(" _collectOne6"); } on SentinelException { + print(" _collectOne7"); return; + } catch (e) { + print(" _collectOne6b"); + print(e); } +print(" _collectOne8"); final coverage = await _processSourceReport( _service, isolateRef, @@ -208,6 +233,7 @@ class _CollectionJob { _functionCoverage, _coverableLineCache, _scopedOutput); + print(" _collectOne9"); _allCoverage.addAll(coverage); } } diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index 578315a4d..ce6c7ccb6 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'dart:io'; @@ -196,7 +197,7 @@ class IsolateEventBuffer { final _buffer = Queue(); var _flushed = true; - ServiceEventBuffer(this._handler); + IsolateEventBuffer(this._handler); Future add(Event event) async { if (_flushed) { @@ -215,7 +216,12 @@ class IsolateEventBuffer { } } -class _GroupState { +/// Keeps track of isolates in an isolate group. +/// +/// Isolates are expected to go through either [start] -> [pause] -> [exit] or +/// simply [start] -> [exit]. [start] and [pause] return false if that sequence +/// is violated. +class IsolateGroupState { // IDs of the isolates running in this group. final _running = {}; @@ -225,11 +231,11 @@ class _GroupState { // IDs of the isolates that have exited in this group. final _exited = {}; - bool noRunningIsolates => _running.isEmpty; - bool noIsolates => _running.isEmpty && _paused.isEmpty; + bool get noRunningIsolates => _running.isEmpty; + bool get noIsolates => _running.isEmpty && _paused.isEmpty; bool start(String id) { - if (_pause.contains(id) || _exited.contains(id)) return false; + if (_paused.contains(id) || _exited.contains(id)) return false; _running.add(id); return true; } @@ -255,9 +261,12 @@ class IsolatePausedListener { final Future Function(IsolateRef isolate, bool isLastIsolateInGroup) _onIsolatePaused; final _allExitedCompleter = Completer(); - final _isolateGroups = {}; + final _isolateGroups = {}; + bool _started = false; + int _numAwaitedPauseCallbacks = 0; + - IsolatePausedListener(this.service, this.onIsolatePaused); + IsolatePausedListener(this._service, this._onIsolatePaused); /// Starts listening and returns a future that completes when all isolates /// have exited. @@ -276,7 +285,7 @@ class IsolatePausedListener { // duplicate events (and out-of-order events to some extent, as the // backfill's [add, pause] events and the real [add, pause, exit] events // can be interleaved). - final eventBuffer = IsolateEventBuffer((Event event) { + final eventBuffer = IsolateEventBuffer((Event event) async { switch(event.kind) { case EventKind.kIsolateStart: return _onStart(event.isolate!); @@ -309,49 +318,64 @@ class IsolatePausedListener { // arrive. await eventBuffer.flush(); - // If the user forgot to pass --pause-isolates-on-exit, there's a chance - // that all the isolates exited before we subscribed to the exit event. To - // make sure we're not waiting on allExited forever, bail if isolateGroups - // is empty at this point. - if (isolateGroups.isEmpty) return; - await _allExitedCompleter.future; } void _onStart(IsolateRef isolateRef) { print("Start event for ${isolateRef.name}"); - final group = (isolateGroups[isolateRef.isolateGroupId!] ??= _GroupState()); - group.add(isolateRef.id!); + print(" ${isolateRef.id}"); + print(" ${isolateRef.number}"); + print(" ${isolateRef.isSystemIsolate}"); + print(" ${isolateRef.isolateGroupId}"); + final groupId = isolateRef.isolateGroupId!; + final group = (_isolateGroups[groupId] ??= IsolateGroupState()); + group.start(isolateRef.id!); + _started = true; } Future _onPause(IsolateRef isolateRef) async { + if (_allExitedCompleter.isCompleted) return; print("Pause event for ${isolateRef.name}"); final String groupId = isolateRef.isolateGroupId!; - final group = isolateGroups[groupId]; + final group = _isolateGroups[groupId]; if (group == null) { // See NOTE in listenUntilAllExited. return; } - group.pause(isolateRef.id!); - await _onIsolatePaused(isolateRef, group.noRunningIsolates); + if (group.pause(isolateRef.id!)) { + ++_numAwaitedPauseCallbacks; + try { + await _onIsolatePaused(isolateRef, group.noRunningIsolates); + } finally { + print(" DONE Pause finally for ${isolateRef.name}"); + --_numAwaitedPauseCallbacks; + _maybeFinish(); + } + } print(" DONE Pause event for ${isolateRef.name}"); } void _onExit(IsolateRef isolateRef) { print("Exit event for ${isolateRef.name}"); final String groupId = isolateRef.isolateGroupId!; - final group = isolateGroups[groupId]; + final group = _isolateGroups[groupId]; if (group == null) { - // Like the pause event, if the group is null it's due to a race condition - // with + // See NOTE in listenUntilAllExited. return; } group.exit(isolateRef.id!); if (group.noIsolates) { - isolateGroups.remove(groupId); + _isolateGroups.remove(groupId); } - if (isolateGroups.isEmpty && !_allExitedCompleter.isCompleted()) { + _maybeFinish(); + } + + void _maybeFinish() { + print("MAYBE FINISH: ${_allExitedCompleter.isCompleted} ${_isolateGroups.isEmpty} ${_numAwaitedPauseCallbacks}"); + if (_allExitedCompleter.isCompleted) return; + if (_started && _numAwaitedPauseCallbacks == 0 && _isolateGroups.isEmpty) { + print(" >>> FINISH <<<"); _allExitedCompleter.complete(); } } diff --git a/pkgs/coverage/test/collect_coverage_test.dart b/pkgs/coverage/test/collect_coverage_test.dart index 7e048ce12..495760ff8 100644 --- a/pkgs/coverage/test/collect_coverage_test.dart +++ b/pkgs/coverage/test/collect_coverage_test.dart @@ -2,8 +2,8 @@ // 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. -@Retry(3) -library; +// @Retry(3) +// library; import 'dart:async'; import 'dart:convert' show json; diff --git a/pkgs/coverage/test/test_files/test_app.dart b/pkgs/coverage/test/test_files/test_app.dart index 1e5070336..e3b1fd172 100644 --- a/pkgs/coverage/test/test_files/test_app.dart +++ b/pkgs/coverage/test/test_files/test_app.dart @@ -30,6 +30,7 @@ Future main() async { } final port = ReceivePort(); + // final exitPort = ReceivePort(); final isolate = await Isolate.spawn(isolateTask, [port.sendPort, 1, 2], paused: true); @@ -47,6 +48,7 @@ Future main() async { final result = await retry(() async => 42, const Duration(seconds: 1)) as int; print(result); + // await exitPort.first; } int usedMethod(int a, int b) { diff --git a/pkgs/coverage/test/test_files/test_app_isolate.dart b/pkgs/coverage/test/test_files/test_app_isolate.dart index d10cbb3ff..e758b7f42 100644 --- a/pkgs/coverage/test/test_files/test_app_isolate.dart +++ b/pkgs/coverage/test/test_files/test_app_isolate.dart @@ -35,12 +35,9 @@ Future fooAsync(int x) async { /// The number of covered lines is tested and expected to be 4. /// /// If you modify this method, you may have to update the tests! -void isolateTask(List threeThings) { +void isolateTask(List threeThings) async { sleep(const Duration(milliseconds: 500)); - // Regression test for https://github.com/dart-lang/tools/issues/520. - Isolate.run(() => print('Another isolate'), debugName: 'Grandchild'); - fooSync(answer); fooAsync(answer).then((_) { final port = threeThings.first as SendPort; @@ -69,5 +66,8 @@ void isolateTask(List threeThings) { print('9'); // coverage:ignore-start print('10'); print('11'); // coverage:ignore-line + + // Regression test for https://github.com/dart-lang/tools/issues/520. + await Isolate.run(() => print('Isolate.run'), debugName: 'Grandchild'); // coverage:ignore-end } From 8d4d8e2d5992c9cd8c2b1901e483690e3e2f4436 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 11:22:50 +1000 Subject: [PATCH 03/19] Working --- pkgs/coverage/lib/src/collect.dart | 21 +++------------- pkgs/coverage/lib/src/util.dart | 27 +++++++++++++++++++++ pkgs/coverage/test/test_files/test_app.dart | 2 -- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 71699d1c7..010eb952b 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -163,27 +163,14 @@ class _CollectionJob { } Future _collectPausedIsolatesUntilAllExit() async { - IsolateRef? mainIsolateRef; await IsolatePausedListener(_service, (IsolateRef isolateRef, bool isLastIsolateInGroup) async { - try { - if (isLastIsolateInGroup) { - print(" Collecting for ${isolateRef.name}"); - await _collectOne(isolateRef); - print(" DONE collecting for ${isolateRef.name}"); - } - } finally { - print(" Resuming ${isolateRef.name}"); - // if (isolateRef.name != 'main') { - if (true) { - await _service.resume(isolateRef.id!); - } else { - mainIsolateRef = isolateRef; - } - print(" DONE Resuming ${isolateRef.name}"); + if (isLastIsolateInGroup) { + print(" Collecting for ${isolateRef.name}"); + await _collectOne(isolateRef); + print(" DONE collecting for ${isolateRef.name}"); } }).listenUntilAllExited(); - if (mainIsolateRef != null) await _service.resume(mainIsolateRef!.id!); } Future _collectOne(IsolateRef isolateRef) async { diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index ce6c7ccb6..2f42b7da0 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -264,6 +264,7 @@ class IsolatePausedListener { final _isolateGroups = {}; bool _started = false; int _numAwaitedPauseCallbacks = 0; + IsolateRef? _mainIsolate; IsolatePausedListener(this._service, this._onIsolatePaused); @@ -285,6 +286,9 @@ class IsolatePausedListener { // duplicate events (and out-of-order events to some extent, as the // backfill's [add, pause] events and the real [add, pause, exit] events // can be interleaved). + // - Finally, we resume each isolate after the its pause callback is done. + // But we need to delay resuming the main isolate until everything else + // is finished, because the VM shuts down once the main isolate exits. final eventBuffer = IsolateEventBuffer((Event event) async { switch(event.kind) { case EventKind.kIsolateStart: @@ -319,6 +323,12 @@ class IsolatePausedListener { await eventBuffer.flush(); await _allExitedCompleter.future; + + // Resume the main isolate. + if (_mainIsolate != null) { + print(" Resuming main isolate"); + await _service.resume(_mainIsolate!.id!); + } } void _onStart(IsolateRef isolateRef) { @@ -349,6 +359,7 @@ class IsolatePausedListener { await _onIsolatePaused(isolateRef, group.noRunningIsolates); } finally { print(" DONE Pause finally for ${isolateRef.name}"); + await _maybeResumeIsolate(isolateRef); --_numAwaitedPauseCallbacks; _maybeFinish(); } @@ -356,6 +367,22 @@ class IsolatePausedListener { print(" DONE Pause event for ${isolateRef.name}"); } + static bool _isMainIsolate(IsolateRef isolateRef) { + return isolateRef.name == 'main'; + } + + Future _maybeResumeIsolate(IsolateRef isolateRef) async { + if (_mainIsolate == null && _isMainIsolate(isolateRef)) { + print(" Deferring main isolate resumption"); + _mainIsolate = isolateRef; + // Pretend the main isolate has exited. + _onExit(isolateRef); + } else { + print(" Resuming isolate: ${isolateRef.name}"); + await _service.resume(isolateRef.id!); + } + } + void _onExit(IsolateRef isolateRef) { print("Exit event for ${isolateRef.name}"); final String groupId = isolateRef.isolateGroupId!; diff --git a/pkgs/coverage/test/test_files/test_app.dart b/pkgs/coverage/test/test_files/test_app.dart index e3b1fd172..1e5070336 100644 --- a/pkgs/coverage/test/test_files/test_app.dart +++ b/pkgs/coverage/test/test_files/test_app.dart @@ -30,7 +30,6 @@ Future main() async { } final port = ReceivePort(); - // final exitPort = ReceivePort(); final isolate = await Isolate.spawn(isolateTask, [port.sendPort, 1, 2], paused: true); @@ -48,7 +47,6 @@ Future main() async { final result = await retry(() async => 42, const Duration(seconds: 1)) as int; print(result); - // await exitPort.first; } int usedMethod(int a, int b) { From 783ea523e77c494375dfe9aca0b3f064c94e4f48 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 12:37:39 +1000 Subject: [PATCH 04/19] cleaning up --- pkgs/coverage/lib/src/collect.dart | 23 ++--------------------- pkgs/coverage/lib/src/util.dart | 24 +++++++++--------------- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 010eb952b..ec7777e08 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -77,9 +77,8 @@ Future> collect(Uri serviceUri, bool resume, final socket = await WebSocket.connect('$uri', compression: options); final controller = StreamController(); socket.listen((data) => controller.add(data as String), onDone: () { - // controller.close(); - print(">>> Disposing VM service 0"); - // service.dispose(); + controller.close(); + service.dispose(); }); service = VmService(controller.stream, socket.add, log: StdoutLog(), disposeHandler: socket.close); @@ -87,7 +86,6 @@ Future> collect(Uri serviceUri, bool resume, } on TimeoutException { // The signature changed in vm_service version 6.0.0. // ignore: await_only_futures - print(">>> Disposing VM service 1"); await service.dispose(); rethrow; } @@ -116,7 +114,6 @@ Future> collect(Uri serviceUri, bool resume, } // The signature changed in vm_service version 6.0.0. // ignore: await_only_futures - print(">>> Disposing VM service 2"); await service.dispose(); } } @@ -153,7 +150,6 @@ class _CollectionJob { bool waitPaused) async { if (waitPaused) { await _collectPausedIsolatesUntilAllExit(); - print("!!! EXITING !!!"); } else { for (final isolateRef in await getAllIsolates(_service)) { await _collectOne(isolateRef); @@ -166,32 +162,24 @@ class _CollectionJob { await IsolatePausedListener(_service, (IsolateRef isolateRef, bool isLastIsolateInGroup) async { if (isLastIsolateInGroup) { - print(" Collecting for ${isolateRef.name}"); await _collectOne(isolateRef); - print(" DONE collecting for ${isolateRef.name}"); } }).listenUntilAllExited(); } Future _collectOne(IsolateRef isolateRef) async { - print(" _collectOne0"); if (!(_isolateIds?.contains(isolateRef.id) ?? true)) return; // _coveredIsolateGroups is only relevant for the !waitPaused flow. The // waitPaused flow only ever calls _collectOne once per isolate group. - print(" _collectOne1"); final isolateGroupId = isolateRef.isolateGroupId; if (isolateGroupId != null) { - print(" _collectOne2"); if (_coveredIsolateGroups.contains(isolateGroupId)) return; - print(" _collectOne3"); _coveredIsolateGroups.add(isolateGroupId); } - print(" _collectOne4"); late final SourceReport isolateReport; try { - print(" _collectOne5"); isolateReport = await _service.getSourceReport( isolateRef.id!, _sourceReportKinds, @@ -202,16 +190,10 @@ class _CollectionJob { : null, librariesAlreadyCompiled: _librariesAlreadyCompiled, ); - print(" _collectOne6"); } on SentinelException { - print(" _collectOne7"); return; - } catch (e) { - print(" _collectOne6b"); - print(e); } -print(" _collectOne8"); final coverage = await _processSourceReport( _service, isolateRef, @@ -220,7 +202,6 @@ print(" _collectOne8"); _functionCoverage, _coverableLineCache, _scopedOutput); - print(" _collectOne9"); _allCoverage.addAll(coverage); } } diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index 2f42b7da0..4d206dc20 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -326,17 +326,11 @@ class IsolatePausedListener { // Resume the main isolate. if (_mainIsolate != null) { - print(" Resuming main isolate"); await _service.resume(_mainIsolate!.id!); } } void _onStart(IsolateRef isolateRef) { - print("Start event for ${isolateRef.name}"); - print(" ${isolateRef.id}"); - print(" ${isolateRef.number}"); - print(" ${isolateRef.isSystemIsolate}"); - print(" ${isolateRef.isolateGroupId}"); final groupId = isolateRef.isolateGroupId!; final group = (_isolateGroups[groupId] ??= IsolateGroupState()); group.start(isolateRef.id!); @@ -345,7 +339,6 @@ class IsolatePausedListener { Future _onPause(IsolateRef isolateRef) async { if (_allExitedCompleter.isCompleted) return; - print("Pause event for ${isolateRef.name}"); final String groupId = isolateRef.isolateGroupId!; final group = _isolateGroups[groupId]; if (group == null) { @@ -358,33 +351,36 @@ class IsolatePausedListener { try { await _onIsolatePaused(isolateRef, group.noRunningIsolates); } finally { - print(" DONE Pause finally for ${isolateRef.name}"); await _maybeResumeIsolate(isolateRef); --_numAwaitedPauseCallbacks; _maybeFinish(); } } - print(" DONE Pause event for ${isolateRef.name}"); } static bool _isMainIsolate(IsolateRef isolateRef) { + // HACK: This should pretty reliably detect the main isolate, but it's not + // foolproof and relies on unstable features. The Dart standalone embedder + // and Flutter both call the main isolate "main", and they both also list + // this isolate first when querying isolates from the VM service. So + // selecting the first isolate named "main" combines these conditions and + // should be reliable enough for now, while we wait for a better test. + // TODO(https://github.com/dart-lang/sdk/issues/56732): Switch to more + // reliable test when it's available. return isolateRef.name == 'main'; } Future _maybeResumeIsolate(IsolateRef isolateRef) async { - if (_mainIsolate == null && _isMainIsolate(isolateRef)) { - print(" Deferring main isolate resumption"); + if (_mainIsolate == null && await _isMainIsolate(isolateRef)) { _mainIsolate = isolateRef; // Pretend the main isolate has exited. _onExit(isolateRef); } else { - print(" Resuming isolate: ${isolateRef.name}"); await _service.resume(isolateRef.id!); } } void _onExit(IsolateRef isolateRef) { - print("Exit event for ${isolateRef.name}"); final String groupId = isolateRef.isolateGroupId!; final group = _isolateGroups[groupId]; if (group == null) { @@ -399,10 +395,8 @@ class IsolatePausedListener { } void _maybeFinish() { - print("MAYBE FINISH: ${_allExitedCompleter.isCompleted} ${_isolateGroups.isEmpty} ${_numAwaitedPauseCallbacks}"); if (_allExitedCompleter.isCompleted) return; if (_started && _numAwaitedPauseCallbacks == 0 && _isolateGroups.isEmpty) { - print(" >>> FINISH <<<"); _allExitedCompleter.complete(); } } From 63f2ebe9a3798c4401e90a053bfca18486bafa65 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 13:30:49 +1000 Subject: [PATCH 05/19] Fix test --- pkgs/coverage/lib/src/collect.dart | 45 +++++++++++++------ pkgs/coverage/lib/src/util.dart | 5 +-- pkgs/coverage/test/collect_coverage_test.dart | 17 ++++++- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index ec7777e08..021715d56 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -93,21 +93,15 @@ Future> collect(Uri serviceUri, bool resume, } try { - final job = _CollectionJob( + return await _getAllCoverage( service, includeDart, functionCoverage, - [ - SourceReportKind.kCoverage, - if (branchCoverage) SourceReportKind.kBranchCoverage, - ], - scopedOutput ?? {}, + branchCoverage, + scopedOutput, isolateIds, - coverableLineCache); - return { - 'type': 'CodeCoverage', - 'coverage': await job.collectAll(waitPaused), - }; + coverableLineCache, + waitPaused); } finally { if (resume && !waitPaused) { await _resumeIsolates(service); @@ -118,6 +112,32 @@ Future> collect(Uri serviceUri, bool resume, } } +Future> _getAllCoverage( + VmService service, + bool includeDart, + bool functionCoverage, + bool branchCoverage, + Set? scopedOutput, + Set? isolateIds, + Map>? coverableLineCache, + bool waitPaused) async { + final job = _CollectionJob( + service, + includeDart, + functionCoverage, + [ + SourceReportKind.kCoverage, + if (branchCoverage) SourceReportKind.kBranchCoverage, + ], + scopedOutput ?? {}, + isolateIds, + coverableLineCache); + return { + 'type': 'CodeCoverage', + 'coverage': await job.collectAll(waitPaused), + }; +} + class _CollectionJob { // Inputs. final VmService _service; @@ -146,8 +166,7 @@ class _CollectionJob { this._coverableLineCache) : _librariesAlreadyCompiled = _coverableLineCache?.keys.toList() {} - Future>> collectAll( - bool waitPaused) async { + Future>> collectAll(bool waitPaused) async { if (waitPaused) { await _collectPausedIsolatesUntilAllExit(); } else { diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index 4d206dc20..f104fd4af 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -259,14 +259,13 @@ class IsolateGroupState { class IsolatePausedListener { final VmService _service; final Future Function(IsolateRef isolate, bool isLastIsolateInGroup) - _onIsolatePaused; + _onIsolatePaused; final _allExitedCompleter = Completer(); final _isolateGroups = {}; bool _started = false; int _numAwaitedPauseCallbacks = 0; IsolateRef? _mainIsolate; - IsolatePausedListener(this._service, this._onIsolatePaused); /// Starts listening and returns a future that completes when all isolates @@ -290,7 +289,7 @@ class IsolatePausedListener { // But we need to delay resuming the main isolate until everything else // is finished, because the VM shuts down once the main isolate exits. final eventBuffer = IsolateEventBuffer((Event event) async { - switch(event.kind) { + switch (event.kind) { case EventKind.kIsolateStart: return _onStart(event.isolate!); case EventKind.kPauseExit: diff --git a/pkgs/coverage/test/collect_coverage_test.dart b/pkgs/coverage/test/collect_coverage_test.dart index 495760ff8..1fad495e4 100644 --- a/pkgs/coverage/test/collect_coverage_test.dart +++ b/pkgs/coverage/test/collect_coverage_test.dart @@ -149,7 +149,8 @@ void main() { 64: 1, 66: 1, 67: 1, - 68: 1 + 68: 1, + 71: 3, }; expect(isolateFile?.lineHits, expectedHits); expect(isolateFile?.funcHits, {11: 1, 19: 1, 23: 1, 28: 1, 38: 1}); @@ -162,7 +163,19 @@ void main() { }); expect( isolateFile?.branchHits, - {11: 1, 12: 1, 15: 0, 19: 1, 23: 1, 28: 1, 29: 1, 32: 0, 38: 1, 42: 1}, + { + 11: 1, + 12: 1, + 15: 0, + 19: 1, + 23: 1, + 28: 1, + 29: 1, + 32: 0, + 38: 1, + 42: 1, + 71: 1, + }, ); }); From 6637e3a0add9ff78e5fc45b2d135dbe9c6a0d2a2 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 14:30:12 +1000 Subject: [PATCH 06/19] More clean up --- pkgs/coverage/lib/src/collect.dart | 132 +++++++----------- pkgs/coverage/lib/src/util.dart | 2 +- .../test/collect_coverage_mock_test.dart | 2 +- pkgs/coverage/test/collect_coverage_test.dart | 4 +- 4 files changed, 53 insertions(+), 87 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 021715d56..0b503128e 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -98,7 +98,7 @@ Future> collect(Uri serviceUri, bool resume, includeDart, functionCoverage, branchCoverage, - scopedOutput, + scopedOutput ?? {}, isolateIds, coverableLineCache, waitPaused); @@ -117,112 +117,78 @@ Future> _getAllCoverage( bool includeDart, bool functionCoverage, bool branchCoverage, - Set? scopedOutput, + Set scopedOutput, Set? isolateIds, Map>? coverableLineCache, bool waitPaused) async { - final job = _CollectionJob( - service, - includeDart, - functionCoverage, - [ - SourceReportKind.kCoverage, - if (branchCoverage) SourceReportKind.kBranchCoverage, - ], - scopedOutput ?? {}, - isolateIds, - coverableLineCache); - return { - 'type': 'CodeCoverage', - 'coverage': await job.collectAll(waitPaused), - }; -} + final vm = await service.getVM(); + final allCoverage = >[]; -class _CollectionJob { - // Inputs. - final VmService _service; - final bool _includeDart; - final bool _functionCoverage; - final List _sourceReportKinds; - final Set _scopedOutput; - final Set? _isolateIds; - final Map>? _coverableLineCache; - - // State. - final List? _librariesAlreadyCompiled; - final _coveredIsolateGroups = {}; - final _coveredIsolates = {}; - - // Output. - final _allCoverage = >[]; - - _CollectionJob( - this._service, - this._includeDart, - this._functionCoverage, - this._sourceReportKinds, - this._scopedOutput, - this._isolateIds, - this._coverableLineCache) - : _librariesAlreadyCompiled = _coverableLineCache?.keys.toList() {} - - Future>> collectAll(bool waitPaused) async { - if (waitPaused) { - await _collectPausedIsolatesUntilAllExit(); - } else { - for (final isolateRef in await getAllIsolates(_service)) { - await _collectOne(isolateRef); - } - } - return _allCoverage; - } + final sourceReportKinds = [ + SourceReportKind.kCoverage, + if (branchCoverage) SourceReportKind.kBranchCoverage, + ]; - Future _collectPausedIsolatesUntilAllExit() async { - await IsolatePausedListener(_service, - (IsolateRef isolateRef, bool isLastIsolateInGroup) async { - if (isLastIsolateInGroup) { - await _collectOne(isolateRef); - } - }).listenUntilAllExited(); - } + final librariesAlreadyCompiled = coverableLineCache?.keys.toList(); + + // Program counters are shared between isolates in the same group. So we need + // to make sure we're only gathering coverage data for one isolate in each + // group, otherwise we'll double count the hits. + final coveredIsolateGroups = {}; - Future _collectOne(IsolateRef isolateRef) async { - if (!(_isolateIds?.contains(isolateRef.id) ?? true)) return; + Future collectIsolate(IsolateRef isolateRef) async { + if (!(isolateIds?.contains(isolateRef.id) ?? true)) return; - // _coveredIsolateGroups is only relevant for the !waitPaused flow. The - // waitPaused flow only ever calls _collectOne once per isolate group. + // coveredIsolateGroups is only relevant for the !waitPaused flow. The + // waitPaused flow achieves the same once-per-group behavior using the + // isLastIsolateInGroup flag. final isolateGroupId = isolateRef.isolateGroupId; if (isolateGroupId != null) { - if (_coveredIsolateGroups.contains(isolateGroupId)) return; - _coveredIsolateGroups.add(isolateGroupId); + if (coveredIsolateGroups.contains(isolateGroupId)) return; + coveredIsolateGroups.add(isolateGroupId); } late final SourceReport isolateReport; try { - isolateReport = await _service.getSourceReport( + isolateReport = await service.getSourceReport( isolateRef.id!, - _sourceReportKinds, + sourceReportKinds, forceCompile: true, reportLines: true, - libraryFilters: _scopedOutput.isNotEmpty - ? List.from(_scopedOutput.map((filter) => 'package:$filter/')) + libraryFilters: scopedOutput.isNotEmpty + ? List.from(scopedOutput.map((filter) => 'package:$filter/')) : null, - librariesAlreadyCompiled: _librariesAlreadyCompiled, + librariesAlreadyCompiled: librariesAlreadyCompiled, ); } on SentinelException { return; } final coverage = await _processSourceReport( - _service, + service, isolateRef, isolateReport, - _includeDart, - _functionCoverage, - _coverableLineCache, - _scopedOutput); - _allCoverage.addAll(coverage); + includeDart, + functionCoverage, + coverableLineCache, + scopedOutput); + allCoverage.addAll(coverage); + } + + if (waitPaused) { + await IsolatePausedListener(service, + (IsolateRef isolateRef, bool isLastIsolateInGroup) async { + if (isLastIsolateInGroup) { + await collectIsolate(isolateRef); + } + }).listenUntilAllExited(); + } else { + for (final isolateRef in await getAllIsolates(service)) { + await collectIsolate(isolateRef); + } } + + return {'type': 'CodeCoverage', 'coverage': allCoverage}; } Future _resumeIsolates(VmService service) async { @@ -333,7 +299,7 @@ Future>> _processSourceReport( if (!scopedOutput.includesScript(scriptUriString)) { // Sometimes a range's script can be different to the function's script // (eg mixins), so we have to re-check the scope filter. - // See https://github.com/dart-lang/coverage/issues/495 + // See https://github.com/dart-lang/tools/issues/530 continue; } final scriptUri = Uri.parse(scriptUriString!); @@ -342,7 +308,7 @@ Future>> _processSourceReport( // SourceReportCoverage.misses: to add zeros to the coverage result for all // the lines that don't have a hit. Afterwards, add all the lines that were // hit or missed to the cache, so that the next coverage collection won't - // need to compile this libarry. + // need to compile this library. final coverableLines = coverableLineCache?.putIfAbsent(scriptUriString, () => {}); diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index f104fd4af..4b17d757e 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -184,7 +184,7 @@ Future serviceUriFromProcess(Stream procStdout) { } Future> getAllIsolates(VmService service) async => - (await service.getVM()).isolates!; + (await service.getVM()).isolates ?? []; /// Buffers VM service isolate [Event]s until [flush] is called. /// diff --git a/pkgs/coverage/test/collect_coverage_mock_test.dart b/pkgs/coverage/test/collect_coverage_mock_test.dart index 2c173d509..372bd48b7 100644 --- a/pkgs/coverage/test/collect_coverage_mock_test.dart +++ b/pkgs/coverage/test/collect_coverage_mock_test.dart @@ -367,7 +367,7 @@ void main() { test( 'Collect coverage, scoped output, ' 'handles SourceReports that contain unfiltered ranges', () async { - // Regression test for https://github.com/dart-lang/coverage/issues/495 + // Regression test for https://github.com/dart-lang/tools/issues/530 final service = _mockService(4, 13); when(service.getSourceReport( 'isolate', diff --git a/pkgs/coverage/test/collect_coverage_test.dart b/pkgs/coverage/test/collect_coverage_test.dart index 1fad495e4..1be2e102b 100644 --- a/pkgs/coverage/test/collect_coverage_test.dart +++ b/pkgs/coverage/test/collect_coverage_test.dart @@ -2,8 +2,8 @@ // 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. -// @Retry(3) -// library; +@Retry(3) +library; import 'dart:async'; import 'dart:convert' show json; From 63177b1d514fb6a6e15b64b0c3712778db35c49b Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 15:48:13 +1000 Subject: [PATCH 07/19] Fix analysis --- pkgs/coverage/lib/src/collect.dart | 6 +- .../lib/src/isolate_paused_listener.dart | 229 ++++++++++++ pkgs/coverage/lib/src/util.dart | 216 ----------- pkgs/coverage/pubspec.yaml | 3 +- .../collect_coverage_mock_test.mocks.dart | 338 ++++++------------ .../test/test_files/test_app_isolate.dart | 4 +- 6 files changed, 342 insertions(+), 454 deletions(-) create mode 100644 pkgs/coverage/lib/src/isolate_paused_listener.dart diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 0b503128e..264043452 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -8,6 +8,7 @@ import 'dart:io'; import 'package:vm_service/vm_service.dart'; import 'hitmap.dart'; +import 'isolate_paused_listener.dart'; import 'util.dart'; const _retryInterval = Duration(milliseconds: 200); @@ -98,7 +99,7 @@ Future> collect(Uri serviceUri, bool resume, includeDart, functionCoverage, branchCoverage, - scopedOutput ?? {}, + scopedOutput, isolateIds, coverableLineCache, waitPaused); @@ -121,7 +122,6 @@ Future> _getAllCoverage( Set? isolateIds, Map>? coverableLineCache, bool waitPaused) async { - final vm = await service.getVM(); final allCoverage = >[]; final sourceReportKinds = [ @@ -181,7 +181,7 @@ Future> _getAllCoverage( if (isLastIsolateInGroup) { await collectIsolate(isolateRef); } - }).listenUntilAllExited(); + }).waitUntilAllExited(); } else { for (final isolateRef in await getAllIsolates(service)) { await collectIsolate(isolateRef); diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart new file mode 100644 index 000000000..96051d111 --- /dev/null +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -0,0 +1,229 @@ +// Copyright (c) 2014, 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:collection'; + +import 'package:meta/meta.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'util.dart'; + +/// Calls onIsolatePaused whenever an isolate reaches the pause-on-exit state, +/// and passes a flag stating whether that isolate is the last one in the group. +class IsolatePausedListener { + IsolatePausedListener(this._service, this._onIsolatePaused); + + final VmService _service; + final Future Function(IsolateRef isolate, bool isLastIsolateInGroup) + _onIsolatePaused; + final _allExitedCompleter = Completer(); + int _numAwaitedPauseCallbacks = 0; + IsolateRef? _mainIsolate; + + @visibleForTesting + final isolateGroups = {}; + + /// Starts listening and returns a future that completes when all isolates + /// have exited. + Future waitUntilAllExited() async { + // NOTE: Why is this class so complicated? + // - We only receive start/pause/exit events that arrive after we've + // subscribed (using _service.streamListen below). + // - So after we subscribe, we have to backfill any isolates that are + // already started/paused by looking at the current isolates. + // - But since that backfill is an async process, we may get isolate events + // arriving during that process. + // - So we buffer all the received events until the backfill is complete. + // - That means we can receive duplicate add/pause events: one from the + // backfill, and one from a real event that arrived during the backfill. + // - So the _onStart/_onPause/_onExit methods need to be robust to + // duplicate events (and out-of-order events to some extent, as the + // backfill's [add, pause] events and the real [add, pause, exit] events + // can be interleaved). + // - Finally, we resume each isolate after the its pause callback is done. + // But we need to delay resuming the main isolate until everything else + // is finished, because the VM shuts down once the main isolate exits. + final eventBuffer = IsolateEventBuffer((Event event) async { + switch (event.kind) { + case EventKind.kIsolateStart: + return _onStart(event.isolate!); + case EventKind.kPauseExit: + return _onPause(event.isolate!); + case EventKind.kIsolateExit: + return _onExit(event.isolate!); + } + }); + + // Listen for isolate open/close events. + _service.onIsolateEvent.listen(eventBuffer.add); + await _service.streamListen(EventStreams.kIsolate); + + // Listen for isolate paused events. + _service.onDebugEvent.listen(eventBuffer.add); + await _service.streamListen(EventStreams.kDebug); + + // Backfill. Add/pause isolates that existed before we subscribed. + final isolates = await getAllIsolates(_service); + for (final isolateRef in isolates) { + _onStart(isolateRef); + final isolate = await _service.getIsolate(isolateRef.id!); + if (isolate.pauseEvent!.kind == EventKind.kPauseExit) { + await _onPause(isolateRef); + } + } + + // Flush the buffered stream events, and the start processing them as they + // arrive. + await eventBuffer.flush(); + + await _allExitedCompleter.future; + + // Resume the main isolate. + if (_mainIsolate != null) { + await _service.resume(_mainIsolate!.id!); + } + } + + void _onStart(IsolateRef isolateRef) { + final groupId = isolateRef.isolateGroupId!; + final group = isolateGroups[groupId] ??= IsolateGroupState(); + group.start(isolateRef.id!); + } + + Future _onPause(IsolateRef isolateRef) async { + if (_allExitedCompleter.isCompleted) return; + final groupId = isolateRef.isolateGroupId!; + final group = isolateGroups[groupId]; + if (group == null) { + // See NOTE in waitUntilAllExited. + return; + } + + if (group.pause(isolateRef.id!)) { + ++_numAwaitedPauseCallbacks; + try { + await _onIsolatePaused(isolateRef, group.noRunningIsolates); + } finally { + await _maybeResumeIsolate(isolateRef); + --_numAwaitedPauseCallbacks; + _maybeFinish(); + } + } + } + + static bool _isMainIsolate(IsolateRef isolateRef) { + // HACK: This should pretty reliably detect the main isolate, but it's not + // foolproof and relies on unstable features. The Dart standalone embedder + // and Flutter both call the main isolate "main", and they both also list + // this isolate first when querying isolates from the VM service. So + // selecting the first isolate named "main" combines these conditions and + // should be reliable enough for now, while we wait for a better test. + // TODO(https://github.com/dart-lang/sdk/issues/56732): Switch to more + // reliable test when it's available. + return isolateRef.name == 'main'; + } + + Future _maybeResumeIsolate(IsolateRef isolateRef) async { + if (_mainIsolate == null && _isMainIsolate(isolateRef)) { + _mainIsolate = isolateRef; + // Pretend the main isolate has exited. + _onExit(isolateRef); + } else { + await _service.resume(isolateRef.id!); + } + } + + void _onExit(IsolateRef isolateRef) { + final groupId = isolateRef.isolateGroupId!; + final group = isolateGroups[groupId]; + if (group == null) { + // See NOTE in waitUntilAllExited. + return; + } + group.exit(isolateRef.id!); + if (group.noIsolates) { + isolateGroups.remove(groupId); + } + _maybeFinish(); + } + + void _maybeFinish() { + if (_allExitedCompleter.isCompleted) return; + if (_numAwaitedPauseCallbacks == 0 && isolateGroups.isEmpty) { + _allExitedCompleter.complete(); + } + } +} + +/// Keeps track of isolates in an isolate group. +/// +/// Isolates are expected to go through either [start] -> [pause] -> [exit] or +/// simply [start] -> [exit]. [start] and [pause] return false if that sequence +/// is violated. +class IsolateGroupState { + // IDs of the isolates running in this group. + @visibleForTesting + final running = {}; + + // IDs of the isolates paused just before exiting in this group. + @visibleForTesting + final paused = {}; + + // IDs of the isolates that have exited in this group. + @visibleForTesting + final exited = {}; + + bool get noRunningIsolates => running.isEmpty; + bool get noIsolates => running.isEmpty && paused.isEmpty; + + bool start(String id) { + if (paused.contains(id) || exited.contains(id)) return false; + running.add(id); + return true; + } + + bool pause(String id) { + if (exited.contains(id)) return false; + running.remove(id); + paused.add(id); + return true; + } + + void exit(String id) { + paused.remove(id); + running.remove(id); + exited.add(id); + } +} + +/// Buffers VM service isolate [Event]s until [flush] is called. +/// +/// [flush] passes each buffered event to the handler function. After that, any +/// further events are immediately passed to the handler. [flush] returns a +/// future that completes when all the events in the queue have been handled (as +/// well as any events that arrive while flush is in progress). +class IsolateEventBuffer { + IsolateEventBuffer(this._handler); + + final Future Function(Event event) _handler; + final _buffer = Queue(); + var _flushed = true; + + Future add(Event event) async { + if (_flushed) { + await _handler(event); + } else { + _buffer.add(event); + } + } + + Future flush() async { + while (_buffer.isNotEmpty) { + final event = _buffer.removeFirst(); + await _handler(event); + } + _flushed = true; + } +} diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart index 4b17d757e..cc7f58425 100644 --- a/pkgs/coverage/lib/src/util.dart +++ b/pkgs/coverage/lib/src/util.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:collection'; import 'dart:convert'; import 'dart:io'; @@ -185,218 +184,3 @@ Future serviceUriFromProcess(Stream procStdout) { Future> getAllIsolates(VmService service) async => (await service.getVM()).isolates ?? []; - -/// Buffers VM service isolate [Event]s until [flush] is called. -/// -/// [flush] passes each buffered event to the handler function. After that, any -/// further events are immediately passed to the handler. [flush] returns a -/// future that completes when all the events in the queue have been handled (as -/// well as any events that arrive while flush is in progress). -class IsolateEventBuffer { - final Future Function(Event event) _handler; - final _buffer = Queue(); - var _flushed = true; - - IsolateEventBuffer(this._handler); - - Future add(Event event) async { - if (_flushed) { - await _handler(event); - } else { - _buffer.add(event); - } - } - - Future flush() async { - while (_buffer.isNotEmpty) { - final event = _buffer.removeFirst(); - await _handler(event); - } - _flushed = true; - } -} - -/// Keeps track of isolates in an isolate group. -/// -/// Isolates are expected to go through either [start] -> [pause] -> [exit] or -/// simply [start] -> [exit]. [start] and [pause] return false if that sequence -/// is violated. -class IsolateGroupState { - // IDs of the isolates running in this group. - final _running = {}; - - // IDs of the isolates paused just before exiting in this group. - final _paused = {}; - - // IDs of the isolates that have exited in this group. - final _exited = {}; - - bool get noRunningIsolates => _running.isEmpty; - bool get noIsolates => _running.isEmpty && _paused.isEmpty; - - bool start(String id) { - if (_paused.contains(id) || _exited.contains(id)) return false; - _running.add(id); - return true; - } - - bool pause(String id) { - if (_exited.contains(id)) return false; - _running.remove(id); - _paused.add(id); - return true; - } - - void exit(String id) { - _paused.remove(id); - _running.remove(id); - _exited.add(id); - } -} - -/// Calls onIsolatePaused whenever an isolate reaches the pause-on-exit state, -/// and passes a flag stating whether that isolate is the last one in the group. -class IsolatePausedListener { - final VmService _service; - final Future Function(IsolateRef isolate, bool isLastIsolateInGroup) - _onIsolatePaused; - final _allExitedCompleter = Completer(); - final _isolateGroups = {}; - bool _started = false; - int _numAwaitedPauseCallbacks = 0; - IsolateRef? _mainIsolate; - - IsolatePausedListener(this._service, this._onIsolatePaused); - - /// Starts listening and returns a future that completes when all isolates - /// have exited. - Future listenUntilAllExited() async { - // NOTE: Why is this class so complicated? - // - We only receive start/pause/exit events that arrive after we've - // subscribed (using _service.streamListen below). - // - So after we subscribe, we have to backfill any isolates that are - // already started/paused by looking at the current isolates. - // - But since that backfill is an async process, we may get isolate events - // arriving during that process. - // - So we buffer all the received events until the backfill is complete. - // - That means we can receive duplicate add/pause events: one from the - // backfill, and one from a real event that arrived during the backfill. - // - So the _onStart/_onPause/_onExit methods need to be robust to - // duplicate events (and out-of-order events to some extent, as the - // backfill's [add, pause] events and the real [add, pause, exit] events - // can be interleaved). - // - Finally, we resume each isolate after the its pause callback is done. - // But we need to delay resuming the main isolate until everything else - // is finished, because the VM shuts down once the main isolate exits. - final eventBuffer = IsolateEventBuffer((Event event) async { - switch (event.kind) { - case EventKind.kIsolateStart: - return _onStart(event.isolate!); - case EventKind.kPauseExit: - return _onPause(event.isolate!); - case EventKind.kIsolateExit: - return _onExit(event.isolate!); - } - }); - - // Listen for isolate open/close events. - _service.onIsolateEvent.listen(eventBuffer.add); - await _service.streamListen(EventStreams.kIsolate); - - // Listen for isolate paused events. - _service.onDebugEvent.listen(eventBuffer.add); - await _service.streamListen(EventStreams.kDebug); - - // Backfill. Add/pause isolates that existed before we subscribed. - final isolates = await getAllIsolates(_service); - for (final isolateRef in isolates) { - _onStart(isolateRef); - final isolate = await _service.getIsolate(isolateRef.id!); - if (isolate.pauseEvent!.kind == EventKind.kPauseExit) { - await _onPause(isolateRef); - } - } - - // Flush the buffered stream events, and the start processing them as they - // arrive. - await eventBuffer.flush(); - - await _allExitedCompleter.future; - - // Resume the main isolate. - if (_mainIsolate != null) { - await _service.resume(_mainIsolate!.id!); - } - } - - void _onStart(IsolateRef isolateRef) { - final groupId = isolateRef.isolateGroupId!; - final group = (_isolateGroups[groupId] ??= IsolateGroupState()); - group.start(isolateRef.id!); - _started = true; - } - - Future _onPause(IsolateRef isolateRef) async { - if (_allExitedCompleter.isCompleted) return; - final String groupId = isolateRef.isolateGroupId!; - final group = _isolateGroups[groupId]; - if (group == null) { - // See NOTE in listenUntilAllExited. - return; - } - - if (group.pause(isolateRef.id!)) { - ++_numAwaitedPauseCallbacks; - try { - await _onIsolatePaused(isolateRef, group.noRunningIsolates); - } finally { - await _maybeResumeIsolate(isolateRef); - --_numAwaitedPauseCallbacks; - _maybeFinish(); - } - } - } - - static bool _isMainIsolate(IsolateRef isolateRef) { - // HACK: This should pretty reliably detect the main isolate, but it's not - // foolproof and relies on unstable features. The Dart standalone embedder - // and Flutter both call the main isolate "main", and they both also list - // this isolate first when querying isolates from the VM service. So - // selecting the first isolate named "main" combines these conditions and - // should be reliable enough for now, while we wait for a better test. - // TODO(https://github.com/dart-lang/sdk/issues/56732): Switch to more - // reliable test when it's available. - return isolateRef.name == 'main'; - } - - Future _maybeResumeIsolate(IsolateRef isolateRef) async { - if (_mainIsolate == null && await _isMainIsolate(isolateRef)) { - _mainIsolate = isolateRef; - // Pretend the main isolate has exited. - _onExit(isolateRef); - } else { - await _service.resume(isolateRef.id!); - } - } - - void _onExit(IsolateRef isolateRef) { - final String groupId = isolateRef.isolateGroupId!; - final group = _isolateGroups[groupId]; - if (group == null) { - // See NOTE in listenUntilAllExited. - return; - } - group.exit(isolateRef.id!); - if (group.noIsolates) { - _isolateGroups.remove(groupId); - } - _maybeFinish(); - } - - void _maybeFinish() { - if (_allExitedCompleter.isCompleted) return; - if (_started && _numAwaitedPauseCallbacks == 0 && _isolateGroups.isEmpty) { - _allExitedCompleter.complete(); - } - } -} diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index c0f26bfcd..abf5e7ff4 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.9.2 +version: 1.9.3-wip description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage @@ -10,6 +10,7 @@ dependencies: args: ^2.0.0 glob: ^2.1.2 logging: ^1.0.0 + meta: ^1.16.0 package_config: ^2.0.0 path: ^1.8.0 source_maps: ^0.10.10 diff --git a/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart b/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart index 86f596af0..9bf56ceaa 100644 --- a/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart +++ b/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart @@ -42,8 +42,8 @@ class _FakeSuccess_1 extends _i1.SmartFake implements _i2.Success { ); } -class _FakeIdZone_2 extends _i1.SmartFake implements _i2.IdZone { - _FakeIdZone_2( +class _FakeResponse_2 extends _i1.SmartFake implements _i2.Response { + _FakeResponse_2( Object parent, Invocation parentInvocation, ) : super( @@ -52,19 +52,9 @@ class _FakeIdZone_2 extends _i1.SmartFake implements _i2.IdZone { ); } -class _FakeResponse_3 extends _i1.SmartFake implements _i2.Response { - _FakeResponse_3( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeAllocationProfile_4 extends _i1.SmartFake +class _FakeAllocationProfile_3 extends _i1.SmartFake implements _i2.AllocationProfile { - _FakeAllocationProfile_4( + _FakeAllocationProfile_3( Object parent, Invocation parentInvocation, ) : super( @@ -73,8 +63,8 @@ class _FakeAllocationProfile_4 extends _i1.SmartFake ); } -class _FakeCpuSamples_5 extends _i1.SmartFake implements _i2.CpuSamples { - _FakeCpuSamples_5( +class _FakeCpuSamples_4 extends _i1.SmartFake implements _i2.CpuSamples { + _FakeCpuSamples_4( Object parent, Invocation parentInvocation, ) : super( @@ -83,8 +73,8 @@ class _FakeCpuSamples_5 extends _i1.SmartFake implements _i2.CpuSamples { ); } -class _FakeClassList_6 extends _i1.SmartFake implements _i2.ClassList { - _FakeClassList_6( +class _FakeClassList_5 extends _i1.SmartFake implements _i2.ClassList { + _FakeClassList_5( Object parent, Invocation parentInvocation, ) : super( @@ -93,8 +83,8 @@ class _FakeClassList_6 extends _i1.SmartFake implements _i2.ClassList { ); } -class _FakeFlagList_7 extends _i1.SmartFake implements _i2.FlagList { - _FakeFlagList_7( +class _FakeFlagList_6 extends _i1.SmartFake implements _i2.FlagList { + _FakeFlagList_6( Object parent, Invocation parentInvocation, ) : super( @@ -103,9 +93,9 @@ class _FakeFlagList_7 extends _i1.SmartFake implements _i2.FlagList { ); } -class _FakeInboundReferences_8 extends _i1.SmartFake +class _FakeInboundReferences_7 extends _i1.SmartFake implements _i2.InboundReferences { - _FakeInboundReferences_8( + _FakeInboundReferences_7( Object parent, Invocation parentInvocation, ) : super( @@ -114,8 +104,8 @@ class _FakeInboundReferences_8 extends _i1.SmartFake ); } -class _FakeInstanceSet_9 extends _i1.SmartFake implements _i2.InstanceSet { - _FakeInstanceSet_9( +class _FakeInstanceSet_8 extends _i1.SmartFake implements _i2.InstanceSet { + _FakeInstanceSet_8( Object parent, Invocation parentInvocation, ) : super( @@ -124,8 +114,8 @@ class _FakeInstanceSet_9 extends _i1.SmartFake implements _i2.InstanceSet { ); } -class _FakeInstanceRef_10 extends _i1.SmartFake implements _i2.InstanceRef { - _FakeInstanceRef_10( +class _FakeInstanceRef_9 extends _i1.SmartFake implements _i2.InstanceRef { + _FakeInstanceRef_9( Object parent, Invocation parentInvocation, ) : super( @@ -134,8 +124,8 @@ class _FakeInstanceRef_10 extends _i1.SmartFake implements _i2.InstanceRef { ); } -class _FakeIsolate_11 extends _i1.SmartFake implements _i2.Isolate { - _FakeIsolate_11( +class _FakeIsolate_10 extends _i1.SmartFake implements _i2.Isolate { + _FakeIsolate_10( Object parent, Invocation parentInvocation, ) : super( @@ -144,8 +134,8 @@ class _FakeIsolate_11 extends _i1.SmartFake implements _i2.Isolate { ); } -class _FakeIsolateGroup_12 extends _i1.SmartFake implements _i2.IsolateGroup { - _FakeIsolateGroup_12( +class _FakeIsolateGroup_11 extends _i1.SmartFake implements _i2.IsolateGroup { + _FakeIsolateGroup_11( Object parent, Invocation parentInvocation, ) : super( @@ -154,8 +144,8 @@ class _FakeIsolateGroup_12 extends _i1.SmartFake implements _i2.IsolateGroup { ); } -class _FakeEvent_13 extends _i1.SmartFake implements _i2.Event { - _FakeEvent_13( +class _FakeEvent_12 extends _i1.SmartFake implements _i2.Event { + _FakeEvent_12( Object parent, Invocation parentInvocation, ) : super( @@ -164,8 +154,8 @@ class _FakeEvent_13 extends _i1.SmartFake implements _i2.Event { ); } -class _FakeMemoryUsage_14 extends _i1.SmartFake implements _i2.MemoryUsage { - _FakeMemoryUsage_14( +class _FakeMemoryUsage_13 extends _i1.SmartFake implements _i2.MemoryUsage { + _FakeMemoryUsage_13( Object parent, Invocation parentInvocation, ) : super( @@ -174,8 +164,8 @@ class _FakeMemoryUsage_14 extends _i1.SmartFake implements _i2.MemoryUsage { ); } -class _FakeScriptList_15 extends _i1.SmartFake implements _i2.ScriptList { - _FakeScriptList_15( +class _FakeScriptList_14 extends _i1.SmartFake implements _i2.ScriptList { + _FakeScriptList_14( Object parent, Invocation parentInvocation, ) : super( @@ -184,8 +174,8 @@ class _FakeScriptList_15 extends _i1.SmartFake implements _i2.ScriptList { ); } -class _FakeObj_16 extends _i1.SmartFake implements _i2.Obj { - _FakeObj_16( +class _FakeObj_15 extends _i1.SmartFake implements _i2.Obj { + _FakeObj_15( Object parent, Invocation parentInvocation, ) : super( @@ -194,9 +184,9 @@ class _FakeObj_16 extends _i1.SmartFake implements _i2.Obj { ); } -class _FakePerfettoCpuSamples_17 extends _i1.SmartFake +class _FakePerfettoCpuSamples_16 extends _i1.SmartFake implements _i2.PerfettoCpuSamples { - _FakePerfettoCpuSamples_17( + _FakePerfettoCpuSamples_16( Object parent, Invocation parentInvocation, ) : super( @@ -205,9 +195,9 @@ class _FakePerfettoCpuSamples_17 extends _i1.SmartFake ); } -class _FakePerfettoTimeline_18 extends _i1.SmartFake +class _FakePerfettoTimeline_17 extends _i1.SmartFake implements _i2.PerfettoTimeline { - _FakePerfettoTimeline_18( + _FakePerfettoTimeline_17( Object parent, Invocation parentInvocation, ) : super( @@ -216,8 +206,8 @@ class _FakePerfettoTimeline_18 extends _i1.SmartFake ); } -class _FakePortList_19 extends _i1.SmartFake implements _i2.PortList { - _FakePortList_19( +class _FakePortList_18 extends _i1.SmartFake implements _i2.PortList { + _FakePortList_18( Object parent, Invocation parentInvocation, ) : super( @@ -226,8 +216,8 @@ class _FakePortList_19 extends _i1.SmartFake implements _i2.PortList { ); } -class _FakeRetainingPath_20 extends _i1.SmartFake implements _i2.RetainingPath { - _FakeRetainingPath_20( +class _FakeRetainingPath_19 extends _i1.SmartFake implements _i2.RetainingPath { + _FakeRetainingPath_19( Object parent, Invocation parentInvocation, ) : super( @@ -236,9 +226,9 @@ class _FakeRetainingPath_20 extends _i1.SmartFake implements _i2.RetainingPath { ); } -class _FakeProcessMemoryUsage_21 extends _i1.SmartFake +class _FakeProcessMemoryUsage_20 extends _i1.SmartFake implements _i2.ProcessMemoryUsage { - _FakeProcessMemoryUsage_21( + _FakeProcessMemoryUsage_20( Object parent, Invocation parentInvocation, ) : super( @@ -247,8 +237,8 @@ class _FakeProcessMemoryUsage_21 extends _i1.SmartFake ); } -class _FakeStack_22 extends _i1.SmartFake implements _i2.Stack { - _FakeStack_22( +class _FakeStack_21 extends _i1.SmartFake implements _i2.Stack { + _FakeStack_21( Object parent, Invocation parentInvocation, ) : super( @@ -257,8 +247,8 @@ class _FakeStack_22 extends _i1.SmartFake implements _i2.Stack { ); } -class _FakeProtocolList_23 extends _i1.SmartFake implements _i2.ProtocolList { - _FakeProtocolList_23( +class _FakeProtocolList_22 extends _i1.SmartFake implements _i2.ProtocolList { + _FakeProtocolList_22( Object parent, Invocation parentInvocation, ) : super( @@ -267,8 +257,8 @@ class _FakeProtocolList_23 extends _i1.SmartFake implements _i2.ProtocolList { ); } -class _FakeSourceReport_24 extends _i1.SmartFake implements _i2.SourceReport { - _FakeSourceReport_24( +class _FakeSourceReport_23 extends _i1.SmartFake implements _i2.SourceReport { + _FakeSourceReport_23( Object parent, Invocation parentInvocation, ) : super( @@ -277,8 +267,8 @@ class _FakeSourceReport_24 extends _i1.SmartFake implements _i2.SourceReport { ); } -class _FakeVersion_25 extends _i1.SmartFake implements _i2.Version { - _FakeVersion_25( +class _FakeVersion_24 extends _i1.SmartFake implements _i2.Version { + _FakeVersion_24( Object parent, Invocation parentInvocation, ) : super( @@ -287,8 +277,8 @@ class _FakeVersion_25 extends _i1.SmartFake implements _i2.Version { ); } -class _FakeVM_26 extends _i1.SmartFake implements _i2.VM { - _FakeVM_26( +class _FakeVM_25 extends _i1.SmartFake implements _i2.VM { + _FakeVM_25( Object parent, Invocation parentInvocation, ) : super( @@ -297,8 +287,8 @@ class _FakeVM_26 extends _i1.SmartFake implements _i2.VM { ); } -class _FakeTimeline_27 extends _i1.SmartFake implements _i2.Timeline { - _FakeTimeline_27( +class _FakeTimeline_26 extends _i1.SmartFake implements _i2.Timeline { + _FakeTimeline_26( Object parent, Invocation parentInvocation, ) : super( @@ -307,8 +297,8 @@ class _FakeTimeline_27 extends _i1.SmartFake implements _i2.Timeline { ); } -class _FakeTimelineFlags_28 extends _i1.SmartFake implements _i2.TimelineFlags { - _FakeTimelineFlags_28( +class _FakeTimelineFlags_27 extends _i1.SmartFake implements _i2.TimelineFlags { + _FakeTimelineFlags_27( Object parent, Invocation parentInvocation, ) : super( @@ -317,8 +307,8 @@ class _FakeTimelineFlags_28 extends _i1.SmartFake implements _i2.TimelineFlags { ); } -class _FakeTimestamp_29 extends _i1.SmartFake implements _i2.Timestamp { - _FakeTimestamp_29( +class _FakeTimestamp_28 extends _i1.SmartFake implements _i2.Timestamp { + _FakeTimestamp_28( Object parent, Invocation parentInvocation, ) : super( @@ -327,8 +317,8 @@ class _FakeTimestamp_29 extends _i1.SmartFake implements _i2.Timestamp { ); } -class _FakeUriList_30 extends _i1.SmartFake implements _i2.UriList { - _FakeUriList_30( +class _FakeUriList_29 extends _i1.SmartFake implements _i2.UriList { + _FakeUriList_29( Object parent, Invocation parentInvocation, ) : super( @@ -337,8 +327,8 @@ class _FakeUriList_30 extends _i1.SmartFake implements _i2.UriList { ); } -class _FakeReloadReport_31 extends _i1.SmartFake implements _i2.ReloadReport { - _FakeReloadReport_31( +class _FakeReloadReport_30 extends _i1.SmartFake implements _i2.ReloadReport { + _FakeReloadReport_30( Object parent, Invocation parentInvocation, ) : super( @@ -347,8 +337,8 @@ class _FakeReloadReport_31 extends _i1.SmartFake implements _i2.ReloadReport { ); } -class _FakeFuture_32 extends _i1.SmartFake implements _i3.Future { - _FakeFuture_32( +class _FakeFuture_31 extends _i1.SmartFake implements _i3.Future { + _FakeFuture_31( Object parent, Invocation parentInvocation, ) : super( @@ -582,87 +572,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { )), ) as _i3.Future<_i2.Success>); - @override - _i3.Future<_i2.IdZone> createIdZone( - String? isolateId, - String? backingBufferKind, - String? idAssignmentPolicy, { - int? capacity, - }) => - (super.noSuchMethod( - Invocation.method( - #createIdZone, - [ - isolateId, - backingBufferKind, - idAssignmentPolicy, - ], - {#capacity: capacity}, - ), - returnValue: _i3.Future<_i2.IdZone>.value(_FakeIdZone_2( - this, - Invocation.method( - #createIdZone, - [ - isolateId, - backingBufferKind, - idAssignmentPolicy, - ], - {#capacity: capacity}, - ), - )), - ) as _i3.Future<_i2.IdZone>); - - @override - _i3.Future<_i2.Success> deleteIdZone( - String? isolateId, - String? idZoneId, - ) => - (super.noSuchMethod( - Invocation.method( - #deleteIdZone, - [ - isolateId, - idZoneId, - ], - ), - returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1( - this, - Invocation.method( - #deleteIdZone, - [ - isolateId, - idZoneId, - ], - ), - )), - ) as _i3.Future<_i2.Success>); - - @override - _i3.Future<_i2.Success> invalidateIdZone( - String? isolateId, - String? idZoneId, - ) => - (super.noSuchMethod( - Invocation.method( - #invalidateIdZone, - [ - isolateId, - idZoneId, - ], - ), - returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1( - this, - Invocation.method( - #invalidateIdZone, - [ - isolateId, - idZoneId, - ], - ), - )), - ) as _i3.Future<_i2.Success>); - @override _i3.Future<_i2.Response> invoke( String? isolateId, @@ -670,7 +579,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? selector, List? argumentIds, { bool? disableBreakpoints, - String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -681,12 +589,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { selector, argumentIds, ], - { - #disableBreakpoints: disableBreakpoints, - #idZoneId: idZoneId, - }, + {#disableBreakpoints: disableBreakpoints}, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( this, Invocation.method( #invoke, @@ -696,10 +601,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { selector, argumentIds, ], - { - #disableBreakpoints: disableBreakpoints, - #idZoneId: idZoneId, - }, + {#disableBreakpoints: disableBreakpoints}, ), )), ) as _i3.Future<_i2.Response>); @@ -711,7 +613,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? expression, { Map? scope, bool? disableBreakpoints, - String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -724,10 +625,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, - #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( this, Invocation.method( #evaluate, @@ -739,7 +639,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, - #idZoneId: idZoneId, }, ), )), @@ -752,7 +651,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? expression, { Map? scope, bool? disableBreakpoints, - String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -765,10 +663,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, - #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( this, Invocation.method( #evaluateInFrame, @@ -780,7 +677,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, - #idZoneId: idZoneId, }, ), )), @@ -802,7 +698,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { }, ), returnValue: - _i3.Future<_i2.AllocationProfile>.value(_FakeAllocationProfile_4( + _i3.Future<_i2.AllocationProfile>.value(_FakeAllocationProfile_3( this, Invocation.method( #getAllocationProfile, @@ -832,7 +728,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #classId: classId, }, ), - returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_5( + returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_4( this, Invocation.method( #getAllocationTraces, @@ -853,7 +749,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getClassList, [isolateId], ), - returnValue: _i3.Future<_i2.ClassList>.value(_FakeClassList_6( + returnValue: _i3.Future<_i2.ClassList>.value(_FakeClassList_5( this, Invocation.method( #getClassList, @@ -877,7 +773,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { timeExtentMicros, ], ), - returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_5( + returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_4( this, Invocation.method( #getCpuSamples, @@ -896,7 +792,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getFlagList, [], ), - returnValue: _i3.Future<_i2.FlagList>.value(_FakeFlagList_7( + returnValue: _i3.Future<_i2.FlagList>.value(_FakeFlagList_6( this, Invocation.method( #getFlagList, @@ -909,9 +805,8 @@ class MockVmService extends _i1.Mock implements _i2.VmService { _i3.Future<_i2.InboundReferences> getInboundReferences( String? isolateId, String? targetId, - int? limit, { - String? idZoneId, - }) => + int? limit, + ) => (super.noSuchMethod( Invocation.method( #getInboundReferences, @@ -920,10 +815,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], - {#idZoneId: idZoneId}, ), returnValue: - _i3.Future<_i2.InboundReferences>.value(_FakeInboundReferences_8( + _i3.Future<_i2.InboundReferences>.value(_FakeInboundReferences_7( this, Invocation.method( #getInboundReferences, @@ -932,7 +826,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], - {#idZoneId: idZoneId}, ), )), ) as _i3.Future<_i2.InboundReferences>); @@ -944,7 +837,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { int? limit, { bool? includeSubclasses, bool? includeImplementers, - String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -957,10 +849,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, - #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.InstanceSet>.value(_FakeInstanceSet_9( + returnValue: _i3.Future<_i2.InstanceSet>.value(_FakeInstanceSet_8( this, Invocation.method( #getInstances, @@ -972,7 +863,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, - #idZoneId: idZoneId, }, ), )), @@ -984,7 +874,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? objectId, { bool? includeSubclasses, bool? includeImplementers, - String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -996,10 +885,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, - #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.InstanceRef>.value(_FakeInstanceRef_10( + returnValue: _i3.Future<_i2.InstanceRef>.value(_FakeInstanceRef_9( this, Invocation.method( #getInstancesAsList, @@ -1010,7 +898,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, - #idZoneId: idZoneId, }, ), )), @@ -1022,7 +909,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolate, [isolateId], ), - returnValue: _i3.Future<_i2.Isolate>.value(_FakeIsolate_11( + returnValue: _i3.Future<_i2.Isolate>.value(_FakeIsolate_10( this, Invocation.method( #getIsolate, @@ -1038,7 +925,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolateGroup, [isolateGroupId], ), - returnValue: _i3.Future<_i2.IsolateGroup>.value(_FakeIsolateGroup_12( + returnValue: _i3.Future<_i2.IsolateGroup>.value(_FakeIsolateGroup_11( this, Invocation.method( #getIsolateGroup, @@ -1054,7 +941,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolatePauseEvent, [isolateId], ), - returnValue: _i3.Future<_i2.Event>.value(_FakeEvent_13( + returnValue: _i3.Future<_i2.Event>.value(_FakeEvent_12( this, Invocation.method( #getIsolatePauseEvent, @@ -1070,7 +957,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getMemoryUsage, [isolateId], ), - returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_14( + returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_13( this, Invocation.method( #getMemoryUsage, @@ -1087,7 +974,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolateGroupMemoryUsage, [isolateGroupId], ), - returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_14( + returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_13( this, Invocation.method( #getIsolateGroupMemoryUsage, @@ -1103,7 +990,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getScripts, [isolateId], ), - returnValue: _i3.Future<_i2.ScriptList>.value(_FakeScriptList_15( + returnValue: _i3.Future<_i2.ScriptList>.value(_FakeScriptList_14( this, Invocation.method( #getScripts, @@ -1118,7 +1005,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? objectId, { int? offset, int? count, - String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -1130,10 +1016,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #offset: offset, #count: count, - #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.Obj>.value(_FakeObj_16( + returnValue: _i3.Future<_i2.Obj>.value(_FakeObj_15( this, Invocation.method( #getObject, @@ -1144,7 +1029,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #offset: offset, #count: count, - #idZoneId: idZoneId, }, ), )), @@ -1166,7 +1050,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { }, ), returnValue: - _i3.Future<_i2.PerfettoCpuSamples>.value(_FakePerfettoCpuSamples_17( + _i3.Future<_i2.PerfettoCpuSamples>.value(_FakePerfettoCpuSamples_16( this, Invocation.method( #getPerfettoCpuSamples, @@ -1194,7 +1078,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { }, ), returnValue: - _i3.Future<_i2.PerfettoTimeline>.value(_FakePerfettoTimeline_18( + _i3.Future<_i2.PerfettoTimeline>.value(_FakePerfettoTimeline_17( this, Invocation.method( #getPerfettoVMTimeline, @@ -1213,7 +1097,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getPorts, [isolateId], ), - returnValue: _i3.Future<_i2.PortList>.value(_FakePortList_19( + returnValue: _i3.Future<_i2.PortList>.value(_FakePortList_18( this, Invocation.method( #getPorts, @@ -1226,9 +1110,8 @@ class MockVmService extends _i1.Mock implements _i2.VmService { _i3.Future<_i2.RetainingPath> getRetainingPath( String? isolateId, String? targetId, - int? limit, { - String? idZoneId, - }) => + int? limit, + ) => (super.noSuchMethod( Invocation.method( #getRetainingPath, @@ -1237,9 +1120,8 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], - {#idZoneId: idZoneId}, ), - returnValue: _i3.Future<_i2.RetainingPath>.value(_FakeRetainingPath_20( + returnValue: _i3.Future<_i2.RetainingPath>.value(_FakeRetainingPath_19( this, Invocation.method( #getRetainingPath, @@ -1248,7 +1130,6 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], - {#idZoneId: idZoneId}, ), )), ) as _i3.Future<_i2.RetainingPath>); @@ -1261,7 +1142,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { [], ), returnValue: - _i3.Future<_i2.ProcessMemoryUsage>.value(_FakeProcessMemoryUsage_21( + _i3.Future<_i2.ProcessMemoryUsage>.value(_FakeProcessMemoryUsage_20( this, Invocation.method( #getProcessMemoryUsage, @@ -1274,26 +1155,19 @@ class MockVmService extends _i1.Mock implements _i2.VmService { _i3.Future<_i2.Stack> getStack( String? isolateId, { int? limit, - String? idZoneId, }) => (super.noSuchMethod( Invocation.method( #getStack, [isolateId], - { - #limit: limit, - #idZoneId: idZoneId, - }, + {#limit: limit}, ), - returnValue: _i3.Future<_i2.Stack>.value(_FakeStack_22( + returnValue: _i3.Future<_i2.Stack>.value(_FakeStack_21( this, Invocation.method( #getStack, [isolateId], - { - #limit: limit, - #idZoneId: idZoneId, - }, + {#limit: limit}, ), )), ) as _i3.Future<_i2.Stack>); @@ -1304,7 +1178,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getSupportedProtocols, [], ), - returnValue: _i3.Future<_i2.ProtocolList>.value(_FakeProtocolList_23( + returnValue: _i3.Future<_i2.ProtocolList>.value(_FakeProtocolList_22( this, Invocation.method( #getSupportedProtocols, @@ -1342,7 +1216,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #librariesAlreadyCompiled: librariesAlreadyCompiled, }, ), - returnValue: _i3.Future<_i2.SourceReport>.value(_FakeSourceReport_24( + returnValue: _i3.Future<_i2.SourceReport>.value(_FakeSourceReport_23( this, Invocation.method( #getSourceReport, @@ -1369,7 +1243,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVersion, [], ), - returnValue: _i3.Future<_i2.Version>.value(_FakeVersion_25( + returnValue: _i3.Future<_i2.Version>.value(_FakeVersion_24( this, Invocation.method( #getVersion, @@ -1384,7 +1258,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVM, [], ), - returnValue: _i3.Future<_i2.VM>.value(_FakeVM_26( + returnValue: _i3.Future<_i2.VM>.value(_FakeVM_25( this, Invocation.method( #getVM, @@ -1407,7 +1281,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #timeExtentMicros: timeExtentMicros, }, ), - returnValue: _i3.Future<_i2.Timeline>.value(_FakeTimeline_27( + returnValue: _i3.Future<_i2.Timeline>.value(_FakeTimeline_26( this, Invocation.method( #getVMTimeline, @@ -1426,7 +1300,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVMTimelineFlags, [], ), - returnValue: _i3.Future<_i2.TimelineFlags>.value(_FakeTimelineFlags_28( + returnValue: _i3.Future<_i2.TimelineFlags>.value(_FakeTimelineFlags_27( this, Invocation.method( #getVMTimelineFlags, @@ -1441,7 +1315,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVMTimelineMicros, [], ), - returnValue: _i3.Future<_i2.Timestamp>.value(_FakeTimestamp_29( + returnValue: _i3.Future<_i2.Timestamp>.value(_FakeTimestamp_28( this, Invocation.method( #getVMTimelineMicros, @@ -1495,7 +1369,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { ], {#local: local}, ), - returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_30( + returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_29( this, Invocation.method( #lookupResolvedPackageUris, @@ -1521,7 +1395,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { uris, ], ), - returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_30( + returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_29( this, Invocation.method( #lookupPackageUris, @@ -1577,7 +1451,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #packagesUri: packagesUri, }, ), - returnValue: _i3.Future<_i2.ReloadReport>.value(_FakeReloadReport_31( + returnValue: _i3.Future<_i2.ReloadReport>.value(_FakeReloadReport_30( this, Invocation.method( #reloadSources, @@ -1755,7 +1629,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { value, ], ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( this, Invocation.method( #setFlag, @@ -1940,7 +1814,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #args: args, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( this, Invocation.method( #callMethod, @@ -1968,7 +1842,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #args: args, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( this, Invocation.method( #callServiceExtension, @@ -2017,7 +1891,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { ), (T v) => _i3.Future.value(v), ) ?? - _FakeFuture_32( + _FakeFuture_31( this, Invocation.method( #wrapFuture, diff --git a/pkgs/coverage/test/test_files/test_app_isolate.dart b/pkgs/coverage/test/test_files/test_app_isolate.dart index e758b7f42..8bbc6be31 100644 --- a/pkgs/coverage/test/test_files/test_app_isolate.dart +++ b/pkgs/coverage/test/test_files/test_app_isolate.dart @@ -39,11 +39,11 @@ void isolateTask(List threeThings) async { sleep(const Duration(milliseconds: 500)); fooSync(answer); - fooAsync(answer).then((_) { + unawaited(fooAsync(answer).then((_) { final port = threeThings.first as SendPort; final sum = (threeThings[1] as int) + (threeThings[2] as int); port.send(sum); - }); + })); final bar = BarClass(123); bar.baz(); From f51dc956bca1f33a7616176db66c2a7bf3b531c9 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 16:10:59 +1000 Subject: [PATCH 08/19] Remove _numAwaitedPauseCallbacks --- pkgs/coverage/lib/src/isolate_paused_listener.dart | 6 +----- pkgs/coverage/test/collect_coverage_test.dart | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 96051d111..5a82afea2 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -19,7 +19,6 @@ class IsolatePausedListener { final Future Function(IsolateRef isolate, bool isLastIsolateInGroup) _onIsolatePaused; final _allExitedCompleter = Completer(); - int _numAwaitedPauseCallbacks = 0; IsolateRef? _mainIsolate; @visibleForTesting @@ -102,13 +101,10 @@ class IsolatePausedListener { } if (group.pause(isolateRef.id!)) { - ++_numAwaitedPauseCallbacks; try { await _onIsolatePaused(isolateRef, group.noRunningIsolates); } finally { await _maybeResumeIsolate(isolateRef); - --_numAwaitedPauseCallbacks; - _maybeFinish(); } } } @@ -151,7 +147,7 @@ class IsolatePausedListener { void _maybeFinish() { if (_allExitedCompleter.isCompleted) return; - if (_numAwaitedPauseCallbacks == 0 && isolateGroups.isEmpty) { + if (isolateGroups.isEmpty) { _allExitedCompleter.complete(); } } diff --git a/pkgs/coverage/test/collect_coverage_test.dart b/pkgs/coverage/test/collect_coverage_test.dart index 1be2e102b..7262757be 100644 --- a/pkgs/coverage/test/collect_coverage_test.dart +++ b/pkgs/coverage/test/collect_coverage_test.dart @@ -132,7 +132,7 @@ void main() { 38: 1, 39: 1, 41: 1, - 42: 3, + 42: 4, 43: 1, 44: 3, 45: 1, From 8e1c33eca7b7cf553d2d3319faec7a305735d70b Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 16:44:31 +1000 Subject: [PATCH 09/19] Simplify more --- .../lib/src/isolate_paused_listener.dart | 49 ++++++------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 5a82afea2..97e82d4d1 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -28,8 +28,8 @@ class IsolatePausedListener { /// have exited. Future waitUntilAllExited() async { // NOTE: Why is this class so complicated? - // - We only receive start/pause/exit events that arrive after we've - // subscribed (using _service.streamListen below). + // - We only receive start/pause events that arrive after we've subscribed, + // using _service.streamListen below. // - So after we subscribe, we have to backfill any isolates that are // already started/paused by looking at the current isolates. // - But since that backfill is an async process, we may get isolate events @@ -37,10 +37,9 @@ class IsolatePausedListener { // - So we buffer all the received events until the backfill is complete. // - That means we can receive duplicate add/pause events: one from the // backfill, and one from a real event that arrived during the backfill. - // - So the _onStart/_onPause/_onExit methods need to be robust to - // duplicate events (and out-of-order events to some extent, as the - // backfill's [add, pause] events and the real [add, pause, exit] events - // can be interleaved). + // - So the _onStart/_onPause methods need to be robust to duplicate events + // (and out-of-order events to some extent, as the backfill's events and + // the real] events can be interleaved). // - Finally, we resume each isolate after the its pause callback is done. // But we need to delay resuming the main isolate until everything else // is finished, because the VM shuts down once the main isolate exits. @@ -50,8 +49,6 @@ class IsolatePausedListener { return _onStart(event.isolate!); case EventKind.kPauseExit: return _onPause(event.isolate!); - case EventKind.kIsolateExit: - return _onExit(event.isolate!); } }); @@ -85,26 +82,24 @@ class IsolatePausedListener { } } + IsolateGroupState _getGroup(IsolateRef isolateRef) => + isolateGroups[isolateRef.isolateGroupId!] ??= IsolateGroupState(); + void _onStart(IsolateRef isolateRef) { - final groupId = isolateRef.isolateGroupId!; - final group = isolateGroups[groupId] ??= IsolateGroupState(); - group.start(isolateRef.id!); + if (_allExitedCompleter.isCompleted) return; + _getGroup(isolateRef).start(isolateRef.id!); } Future _onPause(IsolateRef isolateRef) async { if (_allExitedCompleter.isCompleted) return; - final groupId = isolateRef.isolateGroupId!; - final group = isolateGroups[groupId]; - if (group == null) { - // See NOTE in waitUntilAllExited. - return; - } - + final group = _getGroup(isolateRef); if (group.pause(isolateRef.id!)) { try { await _onIsolatePaused(isolateRef, group.noRunningIsolates); } finally { await _maybeResumeIsolate(isolateRef); + group.exit(isolateRef.id!); + _maybeFinish(); } } } @@ -124,30 +119,14 @@ class IsolatePausedListener { Future _maybeResumeIsolate(IsolateRef isolateRef) async { if (_mainIsolate == null && _isMainIsolate(isolateRef)) { _mainIsolate = isolateRef; - // Pretend the main isolate has exited. - _onExit(isolateRef); } else { await _service.resume(isolateRef.id!); } } - void _onExit(IsolateRef isolateRef) { - final groupId = isolateRef.isolateGroupId!; - final group = isolateGroups[groupId]; - if (group == null) { - // See NOTE in waitUntilAllExited. - return; - } - group.exit(isolateRef.id!); - if (group.noIsolates) { - isolateGroups.remove(groupId); - } - _maybeFinish(); - } - void _maybeFinish() { if (_allExitedCompleter.isCompleted) return; - if (isolateGroups.isEmpty) { + if (isolateGroups.values.every((group) => group.noIsolates)) { _allExitedCompleter.complete(); } } From 1b7384c6daa554b0dfaea4bc59eb067046cf562d Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 1 Oct 2024 17:15:16 +1000 Subject: [PATCH 10/19] Fix pubspec --- pkgs/coverage/CHANGELOG.md | 2 +- pkgs/coverage/lib/src/isolate_paused_listener.dart | 10 ++++++---- pkgs/coverage/pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md index 45e589a7c..9ef5103b6 100644 --- a/pkgs/coverage/CHANGELOG.md +++ b/pkgs/coverage/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.9.3-wip +## 1.10.0-wip - Fix bug where tests involving multiple isolates never finish (#520). diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 97e82d4d1..81faf5c94 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -33,8 +33,11 @@ class IsolatePausedListener { // - So after we subscribe, we have to backfill any isolates that are // already started/paused by looking at the current isolates. // - But since that backfill is an async process, we may get isolate events - // arriving during that process. - // - So we buffer all the received events until the backfill is complete. + // arriving during that process. Eg, a lone pause event received before + // the backfill would complete the _allExitedCompleter before any other + // isolate groups have been seen. + // - The simplest and most robust way of solving this issue is to buffer + // all the received events until the backfill is complete. // - That means we can receive duplicate add/pause events: one from the // backfill, and one from a real event that arrived during the backfill. // - So the _onStart/_onPause methods need to be robust to duplicate events @@ -61,8 +64,7 @@ class IsolatePausedListener { await _service.streamListen(EventStreams.kDebug); // Backfill. Add/pause isolates that existed before we subscribed. - final isolates = await getAllIsolates(_service); - for (final isolateRef in isolates) { + for (final isolateRef in await getAllIsolates(_service)) { _onStart(isolateRef); final isolate = await _service.getIsolate(isolateRef.id!); if (isolate.pauseEvent!.kind == EventKind.kPauseExit) { diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index abf5e7ff4..4f5de816b 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -1,5 +1,5 @@ name: coverage -version: 1.9.3-wip +version: 1.10.0-wip description: Coverage data manipulation and formatting repository: https://github.com/dart-lang/tools/tree/main/pkgs/coverage From 6925b50220803269d7b3cc006cc38afefe961e8f Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 2 Oct 2024 11:04:06 +1000 Subject: [PATCH 11/19] Simplify and factorize for easier unit tests --- .../lib/src/isolate_paused_listener.dart | 172 +++++++++--------- pkgs/coverage/test/run_and_collect_test.dart | 2 +- 2 files changed, 90 insertions(+), 84 deletions(-) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 81faf5c94..edb168d0c 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -27,54 +27,7 @@ class IsolatePausedListener { /// Starts listening and returns a future that completes when all isolates /// have exited. Future waitUntilAllExited() async { - // NOTE: Why is this class so complicated? - // - We only receive start/pause events that arrive after we've subscribed, - // using _service.streamListen below. - // - So after we subscribe, we have to backfill any isolates that are - // already started/paused by looking at the current isolates. - // - But since that backfill is an async process, we may get isolate events - // arriving during that process. Eg, a lone pause event received before - // the backfill would complete the _allExitedCompleter before any other - // isolate groups have been seen. - // - The simplest and most robust way of solving this issue is to buffer - // all the received events until the backfill is complete. - // - That means we can receive duplicate add/pause events: one from the - // backfill, and one from a real event that arrived during the backfill. - // - So the _onStart/_onPause methods need to be robust to duplicate events - // (and out-of-order events to some extent, as the backfill's events and - // the real] events can be interleaved). - // - Finally, we resume each isolate after the its pause callback is done. - // But we need to delay resuming the main isolate until everything else - // is finished, because the VM shuts down once the main isolate exits. - final eventBuffer = IsolateEventBuffer((Event event) async { - switch (event.kind) { - case EventKind.kIsolateStart: - return _onStart(event.isolate!); - case EventKind.kPauseExit: - return _onPause(event.isolate!); - } - }); - - // Listen for isolate open/close events. - _service.onIsolateEvent.listen(eventBuffer.add); - await _service.streamListen(EventStreams.kIsolate); - - // Listen for isolate paused events. - _service.onDebugEvent.listen(eventBuffer.add); - await _service.streamListen(EventStreams.kDebug); - - // Backfill. Add/pause isolates that existed before we subscribed. - for (final isolateRef in await getAllIsolates(_service)) { - _onStart(isolateRef); - final isolate = await _service.getIsolate(isolateRef.id!); - if (isolate.pauseEvent!.kind == EventKind.kPauseExit) { - await _onPause(isolateRef); - } - } - - // Flush the buffered stream events, and the start processing them as they - // arrive. - await eventBuffer.flush(); + await listenToIsolateLifecycleEvents(_service, _onStart, _onPause, _onExit); await _allExitedCompleter.future; @@ -95,14 +48,29 @@ class IsolatePausedListener { Future _onPause(IsolateRef isolateRef) async { if (_allExitedCompleter.isCompleted) return; final group = _getGroup(isolateRef); - if (group.pause(isolateRef.id!)) { - try { - await _onIsolatePaused(isolateRef, group.noRunningIsolates); - } finally { - await _maybeResumeIsolate(isolateRef); - group.exit(isolateRef.id!); - _maybeFinish(); - } + group.pause(isolateRef.id!); + try { + await _onIsolatePaused(isolateRef, group.noRunningIsolates); + } finally { + await _maybeResumeIsolate(isolateRef); + } + } + + Future _maybeResumeIsolate(IsolateRef isolateRef) async { + if (_mainIsolate == null && _isMainIsolate(isolateRef)) { + _mainIsolate = isolateRef; + // Pretend this isolate has exited so _allExitedCompleter can complete. + _onExit(isolateRef); + } else { + await _service.resume(isolateRef.id!); + } + } + + void _onExit(IsolateRef isolateRef) { + if (_allExitedCompleter.isCompleted) return; + _getGroup(isolateRef).exit(isolateRef.id!); + if (isolateGroups.values.every((group) => group.noLiveIsolates)) { + _allExitedCompleter.complete(); } } @@ -117,28 +85,74 @@ class IsolatePausedListener { // reliable test when it's available. return isolateRef.name == 'main'; } +} - Future _maybeResumeIsolate(IsolateRef isolateRef) async { - if (_mainIsolate == null && _isMainIsolate(isolateRef)) { - _mainIsolate = isolateRef; - } else { - await _service.resume(isolateRef.id!); - } +/// Listens to isolate start and pause events, and backfills events for isolates +/// that existed before listening started. +/// +/// Ensures that: +/// - Every [onIsolatePaused] and [onIsolateExited] call will be preceeded by +/// an [onIsolateStarted] call for the same isolate. +/// - Not every [onIsolateExited] call will be preceeded by a [onIsolatePaused] +/// call, but a [onIsolatePaused] will never follow a [onIsolateExited]. +/// - Each callback will only be called once per isolate. +Future listenToIsolateLifecycleEvents( + VmService service, + void Function(IsolateRef isolate) onIsolateStarted, + Future Function(IsolateRef isolate) onIsolatePaused, + void Function(IsolateRef isolate) onIsolateExited) async { + final started = {}; + void onStart(IsolateRef isolateRef) { + if (started.add(isolateRef.id!)) onIsolateStarted(isolateRef); } - void _maybeFinish() { - if (_allExitedCompleter.isCompleted) return; - if (isolateGroups.values.every((group) => group.noIsolates)) { - _allExitedCompleter.complete(); + final paused = {}; + Future onPause(IsolateRef isolateRef) async { + onStart(isolateRef); + if (paused.add(isolateRef.id!)) await onIsolatePaused(isolateRef); + } + + final exited = {}; + void onExit(IsolateRef isolateRef) { + onStart(isolateRef); + paused.add(isolateRef.id!); + if (exited.add(isolateRef.id!)) onIsolateExited(isolateRef); + } + + final eventBuffer = IsolateEventBuffer((Event event) async { + switch (event.kind) { + case EventKind.kIsolateStart: + return onStart(event.isolate!); + case EventKind.kPauseExit: + return await onPause(event.isolate!); + case EventKind.kIsolateExit: + return onExit(event.isolate!); + } + }); + + // Listen for isolate start/exit events. + service.onIsolateEvent.listen(eventBuffer.add); + await service.streamListen(EventStreams.kIsolate); + + // Listen for isolate paused events. + service.onDebugEvent.listen(eventBuffer.add); + await service.streamListen(EventStreams.kDebug); + + // Backfill. Add/pause isolates that existed before we subscribed. + for (final isolateRef in await getAllIsolates(service)) { + onStart(isolateRef); + final isolate = await service.getIsolate(isolateRef.id!); + if (isolate.pauseEvent!.kind == EventKind.kPauseExit) { + await onPause(isolateRef); } } + + // Flush the buffered stream events, and the start processing them as they + // arrive. + await eventBuffer.flush(); } /// Keeps track of isolates in an isolate group. -/// -/// Isolates are expected to go through either [start] -> [pause] -> [exit] or -/// simply [start] -> [exit]. [start] and [pause] return false if that sequence -/// is violated. class IsolateGroupState { // IDs of the isolates running in this group. @visibleForTesting @@ -148,30 +162,22 @@ class IsolateGroupState { @visibleForTesting final paused = {}; - // IDs of the isolates that have exited in this group. - @visibleForTesting - final exited = {}; - bool get noRunningIsolates => running.isEmpty; - bool get noIsolates => running.isEmpty && paused.isEmpty; + bool get noLiveIsolates => running.isEmpty && paused.isEmpty; - bool start(String id) { - if (paused.contains(id) || exited.contains(id)) return false; + void start(String id) { + paused.remove(id); running.add(id); - return true; } - bool pause(String id) { - if (exited.contains(id)) return false; + void pause(String id) { running.remove(id); paused.add(id); - return true; } void exit(String id) { - paused.remove(id); running.remove(id); - exited.add(id); + paused.remove(id); } } diff --git a/pkgs/coverage/test/run_and_collect_test.dart b/pkgs/coverage/test/run_and_collect_test.dart index 733fc7440..b6569f714 100644 --- a/pkgs/coverage/test/run_and_collect_test.dart +++ b/pkgs/coverage/test/run_and_collect_test.dart @@ -112,7 +112,7 @@ void checkHitmap(Map hitMap) { 38: 1, 39: 1, 41: 1, - 42: 3, + 42: 4, 43: 1, 44: 3, 45: 1, From e7991ba378ff3eb1f555b6e0ba1cd02735f8ab58 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 2 Oct 2024 11:27:49 +1000 Subject: [PATCH 12/19] Regen mocks --- .../collect_coverage_mock_test.mocks.dart | 338 ++++++++++++------ 1 file changed, 232 insertions(+), 106 deletions(-) diff --git a/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart b/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart index 9bf56ceaa..86f596af0 100644 --- a/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart +++ b/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart @@ -42,8 +42,8 @@ class _FakeSuccess_1 extends _i1.SmartFake implements _i2.Success { ); } -class _FakeResponse_2 extends _i1.SmartFake implements _i2.Response { - _FakeResponse_2( +class _FakeIdZone_2 extends _i1.SmartFake implements _i2.IdZone { + _FakeIdZone_2( Object parent, Invocation parentInvocation, ) : super( @@ -52,9 +52,19 @@ class _FakeResponse_2 extends _i1.SmartFake implements _i2.Response { ); } -class _FakeAllocationProfile_3 extends _i1.SmartFake +class _FakeResponse_3 extends _i1.SmartFake implements _i2.Response { + _FakeResponse_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAllocationProfile_4 extends _i1.SmartFake implements _i2.AllocationProfile { - _FakeAllocationProfile_3( + _FakeAllocationProfile_4( Object parent, Invocation parentInvocation, ) : super( @@ -63,8 +73,8 @@ class _FakeAllocationProfile_3 extends _i1.SmartFake ); } -class _FakeCpuSamples_4 extends _i1.SmartFake implements _i2.CpuSamples { - _FakeCpuSamples_4( +class _FakeCpuSamples_5 extends _i1.SmartFake implements _i2.CpuSamples { + _FakeCpuSamples_5( Object parent, Invocation parentInvocation, ) : super( @@ -73,8 +83,8 @@ class _FakeCpuSamples_4 extends _i1.SmartFake implements _i2.CpuSamples { ); } -class _FakeClassList_5 extends _i1.SmartFake implements _i2.ClassList { - _FakeClassList_5( +class _FakeClassList_6 extends _i1.SmartFake implements _i2.ClassList { + _FakeClassList_6( Object parent, Invocation parentInvocation, ) : super( @@ -83,8 +93,8 @@ class _FakeClassList_5 extends _i1.SmartFake implements _i2.ClassList { ); } -class _FakeFlagList_6 extends _i1.SmartFake implements _i2.FlagList { - _FakeFlagList_6( +class _FakeFlagList_7 extends _i1.SmartFake implements _i2.FlagList { + _FakeFlagList_7( Object parent, Invocation parentInvocation, ) : super( @@ -93,9 +103,9 @@ class _FakeFlagList_6 extends _i1.SmartFake implements _i2.FlagList { ); } -class _FakeInboundReferences_7 extends _i1.SmartFake +class _FakeInboundReferences_8 extends _i1.SmartFake implements _i2.InboundReferences { - _FakeInboundReferences_7( + _FakeInboundReferences_8( Object parent, Invocation parentInvocation, ) : super( @@ -104,8 +114,8 @@ class _FakeInboundReferences_7 extends _i1.SmartFake ); } -class _FakeInstanceSet_8 extends _i1.SmartFake implements _i2.InstanceSet { - _FakeInstanceSet_8( +class _FakeInstanceSet_9 extends _i1.SmartFake implements _i2.InstanceSet { + _FakeInstanceSet_9( Object parent, Invocation parentInvocation, ) : super( @@ -114,8 +124,8 @@ class _FakeInstanceSet_8 extends _i1.SmartFake implements _i2.InstanceSet { ); } -class _FakeInstanceRef_9 extends _i1.SmartFake implements _i2.InstanceRef { - _FakeInstanceRef_9( +class _FakeInstanceRef_10 extends _i1.SmartFake implements _i2.InstanceRef { + _FakeInstanceRef_10( Object parent, Invocation parentInvocation, ) : super( @@ -124,8 +134,8 @@ class _FakeInstanceRef_9 extends _i1.SmartFake implements _i2.InstanceRef { ); } -class _FakeIsolate_10 extends _i1.SmartFake implements _i2.Isolate { - _FakeIsolate_10( +class _FakeIsolate_11 extends _i1.SmartFake implements _i2.Isolate { + _FakeIsolate_11( Object parent, Invocation parentInvocation, ) : super( @@ -134,8 +144,8 @@ class _FakeIsolate_10 extends _i1.SmartFake implements _i2.Isolate { ); } -class _FakeIsolateGroup_11 extends _i1.SmartFake implements _i2.IsolateGroup { - _FakeIsolateGroup_11( +class _FakeIsolateGroup_12 extends _i1.SmartFake implements _i2.IsolateGroup { + _FakeIsolateGroup_12( Object parent, Invocation parentInvocation, ) : super( @@ -144,8 +154,8 @@ class _FakeIsolateGroup_11 extends _i1.SmartFake implements _i2.IsolateGroup { ); } -class _FakeEvent_12 extends _i1.SmartFake implements _i2.Event { - _FakeEvent_12( +class _FakeEvent_13 extends _i1.SmartFake implements _i2.Event { + _FakeEvent_13( Object parent, Invocation parentInvocation, ) : super( @@ -154,8 +164,8 @@ class _FakeEvent_12 extends _i1.SmartFake implements _i2.Event { ); } -class _FakeMemoryUsage_13 extends _i1.SmartFake implements _i2.MemoryUsage { - _FakeMemoryUsage_13( +class _FakeMemoryUsage_14 extends _i1.SmartFake implements _i2.MemoryUsage { + _FakeMemoryUsage_14( Object parent, Invocation parentInvocation, ) : super( @@ -164,8 +174,8 @@ class _FakeMemoryUsage_13 extends _i1.SmartFake implements _i2.MemoryUsage { ); } -class _FakeScriptList_14 extends _i1.SmartFake implements _i2.ScriptList { - _FakeScriptList_14( +class _FakeScriptList_15 extends _i1.SmartFake implements _i2.ScriptList { + _FakeScriptList_15( Object parent, Invocation parentInvocation, ) : super( @@ -174,8 +184,8 @@ class _FakeScriptList_14 extends _i1.SmartFake implements _i2.ScriptList { ); } -class _FakeObj_15 extends _i1.SmartFake implements _i2.Obj { - _FakeObj_15( +class _FakeObj_16 extends _i1.SmartFake implements _i2.Obj { + _FakeObj_16( Object parent, Invocation parentInvocation, ) : super( @@ -184,9 +194,9 @@ class _FakeObj_15 extends _i1.SmartFake implements _i2.Obj { ); } -class _FakePerfettoCpuSamples_16 extends _i1.SmartFake +class _FakePerfettoCpuSamples_17 extends _i1.SmartFake implements _i2.PerfettoCpuSamples { - _FakePerfettoCpuSamples_16( + _FakePerfettoCpuSamples_17( Object parent, Invocation parentInvocation, ) : super( @@ -195,9 +205,9 @@ class _FakePerfettoCpuSamples_16 extends _i1.SmartFake ); } -class _FakePerfettoTimeline_17 extends _i1.SmartFake +class _FakePerfettoTimeline_18 extends _i1.SmartFake implements _i2.PerfettoTimeline { - _FakePerfettoTimeline_17( + _FakePerfettoTimeline_18( Object parent, Invocation parentInvocation, ) : super( @@ -206,8 +216,8 @@ class _FakePerfettoTimeline_17 extends _i1.SmartFake ); } -class _FakePortList_18 extends _i1.SmartFake implements _i2.PortList { - _FakePortList_18( +class _FakePortList_19 extends _i1.SmartFake implements _i2.PortList { + _FakePortList_19( Object parent, Invocation parentInvocation, ) : super( @@ -216,8 +226,8 @@ class _FakePortList_18 extends _i1.SmartFake implements _i2.PortList { ); } -class _FakeRetainingPath_19 extends _i1.SmartFake implements _i2.RetainingPath { - _FakeRetainingPath_19( +class _FakeRetainingPath_20 extends _i1.SmartFake implements _i2.RetainingPath { + _FakeRetainingPath_20( Object parent, Invocation parentInvocation, ) : super( @@ -226,9 +236,9 @@ class _FakeRetainingPath_19 extends _i1.SmartFake implements _i2.RetainingPath { ); } -class _FakeProcessMemoryUsage_20 extends _i1.SmartFake +class _FakeProcessMemoryUsage_21 extends _i1.SmartFake implements _i2.ProcessMemoryUsage { - _FakeProcessMemoryUsage_20( + _FakeProcessMemoryUsage_21( Object parent, Invocation parentInvocation, ) : super( @@ -237,8 +247,8 @@ class _FakeProcessMemoryUsage_20 extends _i1.SmartFake ); } -class _FakeStack_21 extends _i1.SmartFake implements _i2.Stack { - _FakeStack_21( +class _FakeStack_22 extends _i1.SmartFake implements _i2.Stack { + _FakeStack_22( Object parent, Invocation parentInvocation, ) : super( @@ -247,8 +257,8 @@ class _FakeStack_21 extends _i1.SmartFake implements _i2.Stack { ); } -class _FakeProtocolList_22 extends _i1.SmartFake implements _i2.ProtocolList { - _FakeProtocolList_22( +class _FakeProtocolList_23 extends _i1.SmartFake implements _i2.ProtocolList { + _FakeProtocolList_23( Object parent, Invocation parentInvocation, ) : super( @@ -257,8 +267,8 @@ class _FakeProtocolList_22 extends _i1.SmartFake implements _i2.ProtocolList { ); } -class _FakeSourceReport_23 extends _i1.SmartFake implements _i2.SourceReport { - _FakeSourceReport_23( +class _FakeSourceReport_24 extends _i1.SmartFake implements _i2.SourceReport { + _FakeSourceReport_24( Object parent, Invocation parentInvocation, ) : super( @@ -267,8 +277,8 @@ class _FakeSourceReport_23 extends _i1.SmartFake implements _i2.SourceReport { ); } -class _FakeVersion_24 extends _i1.SmartFake implements _i2.Version { - _FakeVersion_24( +class _FakeVersion_25 extends _i1.SmartFake implements _i2.Version { + _FakeVersion_25( Object parent, Invocation parentInvocation, ) : super( @@ -277,8 +287,8 @@ class _FakeVersion_24 extends _i1.SmartFake implements _i2.Version { ); } -class _FakeVM_25 extends _i1.SmartFake implements _i2.VM { - _FakeVM_25( +class _FakeVM_26 extends _i1.SmartFake implements _i2.VM { + _FakeVM_26( Object parent, Invocation parentInvocation, ) : super( @@ -287,8 +297,8 @@ class _FakeVM_25 extends _i1.SmartFake implements _i2.VM { ); } -class _FakeTimeline_26 extends _i1.SmartFake implements _i2.Timeline { - _FakeTimeline_26( +class _FakeTimeline_27 extends _i1.SmartFake implements _i2.Timeline { + _FakeTimeline_27( Object parent, Invocation parentInvocation, ) : super( @@ -297,8 +307,8 @@ class _FakeTimeline_26 extends _i1.SmartFake implements _i2.Timeline { ); } -class _FakeTimelineFlags_27 extends _i1.SmartFake implements _i2.TimelineFlags { - _FakeTimelineFlags_27( +class _FakeTimelineFlags_28 extends _i1.SmartFake implements _i2.TimelineFlags { + _FakeTimelineFlags_28( Object parent, Invocation parentInvocation, ) : super( @@ -307,8 +317,8 @@ class _FakeTimelineFlags_27 extends _i1.SmartFake implements _i2.TimelineFlags { ); } -class _FakeTimestamp_28 extends _i1.SmartFake implements _i2.Timestamp { - _FakeTimestamp_28( +class _FakeTimestamp_29 extends _i1.SmartFake implements _i2.Timestamp { + _FakeTimestamp_29( Object parent, Invocation parentInvocation, ) : super( @@ -317,8 +327,8 @@ class _FakeTimestamp_28 extends _i1.SmartFake implements _i2.Timestamp { ); } -class _FakeUriList_29 extends _i1.SmartFake implements _i2.UriList { - _FakeUriList_29( +class _FakeUriList_30 extends _i1.SmartFake implements _i2.UriList { + _FakeUriList_30( Object parent, Invocation parentInvocation, ) : super( @@ -327,8 +337,8 @@ class _FakeUriList_29 extends _i1.SmartFake implements _i2.UriList { ); } -class _FakeReloadReport_30 extends _i1.SmartFake implements _i2.ReloadReport { - _FakeReloadReport_30( +class _FakeReloadReport_31 extends _i1.SmartFake implements _i2.ReloadReport { + _FakeReloadReport_31( Object parent, Invocation parentInvocation, ) : super( @@ -337,8 +347,8 @@ class _FakeReloadReport_30 extends _i1.SmartFake implements _i2.ReloadReport { ); } -class _FakeFuture_31 extends _i1.SmartFake implements _i3.Future { - _FakeFuture_31( +class _FakeFuture_32 extends _i1.SmartFake implements _i3.Future { + _FakeFuture_32( Object parent, Invocation parentInvocation, ) : super( @@ -572,6 +582,87 @@ class MockVmService extends _i1.Mock implements _i2.VmService { )), ) as _i3.Future<_i2.Success>); + @override + _i3.Future<_i2.IdZone> createIdZone( + String? isolateId, + String? backingBufferKind, + String? idAssignmentPolicy, { + int? capacity, + }) => + (super.noSuchMethod( + Invocation.method( + #createIdZone, + [ + isolateId, + backingBufferKind, + idAssignmentPolicy, + ], + {#capacity: capacity}, + ), + returnValue: _i3.Future<_i2.IdZone>.value(_FakeIdZone_2( + this, + Invocation.method( + #createIdZone, + [ + isolateId, + backingBufferKind, + idAssignmentPolicy, + ], + {#capacity: capacity}, + ), + )), + ) as _i3.Future<_i2.IdZone>); + + @override + _i3.Future<_i2.Success> deleteIdZone( + String? isolateId, + String? idZoneId, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteIdZone, + [ + isolateId, + idZoneId, + ], + ), + returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1( + this, + Invocation.method( + #deleteIdZone, + [ + isolateId, + idZoneId, + ], + ), + )), + ) as _i3.Future<_i2.Success>); + + @override + _i3.Future<_i2.Success> invalidateIdZone( + String? isolateId, + String? idZoneId, + ) => + (super.noSuchMethod( + Invocation.method( + #invalidateIdZone, + [ + isolateId, + idZoneId, + ], + ), + returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1( + this, + Invocation.method( + #invalidateIdZone, + [ + isolateId, + idZoneId, + ], + ), + )), + ) as _i3.Future<_i2.Success>); + @override _i3.Future<_i2.Response> invoke( String? isolateId, @@ -579,6 +670,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? selector, List? argumentIds, { bool? disableBreakpoints, + String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -589,9 +681,12 @@ class MockVmService extends _i1.Mock implements _i2.VmService { selector, argumentIds, ], - {#disableBreakpoints: disableBreakpoints}, + { + #disableBreakpoints: disableBreakpoints, + #idZoneId: idZoneId, + }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( this, Invocation.method( #invoke, @@ -601,7 +696,10 @@ class MockVmService extends _i1.Mock implements _i2.VmService { selector, argumentIds, ], - {#disableBreakpoints: disableBreakpoints}, + { + #disableBreakpoints: disableBreakpoints, + #idZoneId: idZoneId, + }, ), )), ) as _i3.Future<_i2.Response>); @@ -613,6 +711,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? expression, { Map? scope, bool? disableBreakpoints, + String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -625,9 +724,10 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, + #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( this, Invocation.method( #evaluate, @@ -639,6 +739,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, + #idZoneId: idZoneId, }, ), )), @@ -651,6 +752,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? expression, { Map? scope, bool? disableBreakpoints, + String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -663,9 +765,10 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, + #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( this, Invocation.method( #evaluateInFrame, @@ -677,6 +780,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #scope: scope, #disableBreakpoints: disableBreakpoints, + #idZoneId: idZoneId, }, ), )), @@ -698,7 +802,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { }, ), returnValue: - _i3.Future<_i2.AllocationProfile>.value(_FakeAllocationProfile_3( + _i3.Future<_i2.AllocationProfile>.value(_FakeAllocationProfile_4( this, Invocation.method( #getAllocationProfile, @@ -728,7 +832,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #classId: classId, }, ), - returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_4( + returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_5( this, Invocation.method( #getAllocationTraces, @@ -749,7 +853,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getClassList, [isolateId], ), - returnValue: _i3.Future<_i2.ClassList>.value(_FakeClassList_5( + returnValue: _i3.Future<_i2.ClassList>.value(_FakeClassList_6( this, Invocation.method( #getClassList, @@ -773,7 +877,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { timeExtentMicros, ], ), - returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_4( + returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_5( this, Invocation.method( #getCpuSamples, @@ -792,7 +896,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getFlagList, [], ), - returnValue: _i3.Future<_i2.FlagList>.value(_FakeFlagList_6( + returnValue: _i3.Future<_i2.FlagList>.value(_FakeFlagList_7( this, Invocation.method( #getFlagList, @@ -805,8 +909,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { _i3.Future<_i2.InboundReferences> getInboundReferences( String? isolateId, String? targetId, - int? limit, - ) => + int? limit, { + String? idZoneId, + }) => (super.noSuchMethod( Invocation.method( #getInboundReferences, @@ -815,9 +920,10 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], + {#idZoneId: idZoneId}, ), returnValue: - _i3.Future<_i2.InboundReferences>.value(_FakeInboundReferences_7( + _i3.Future<_i2.InboundReferences>.value(_FakeInboundReferences_8( this, Invocation.method( #getInboundReferences, @@ -826,6 +932,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], + {#idZoneId: idZoneId}, ), )), ) as _i3.Future<_i2.InboundReferences>); @@ -837,6 +944,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { int? limit, { bool? includeSubclasses, bool? includeImplementers, + String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -849,9 +957,10 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, + #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.InstanceSet>.value(_FakeInstanceSet_8( + returnValue: _i3.Future<_i2.InstanceSet>.value(_FakeInstanceSet_9( this, Invocation.method( #getInstances, @@ -863,6 +972,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, + #idZoneId: idZoneId, }, ), )), @@ -874,6 +984,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? objectId, { bool? includeSubclasses, bool? includeImplementers, + String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -885,9 +996,10 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, + #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.InstanceRef>.value(_FakeInstanceRef_9( + returnValue: _i3.Future<_i2.InstanceRef>.value(_FakeInstanceRef_10( this, Invocation.method( #getInstancesAsList, @@ -898,6 +1010,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #includeSubclasses: includeSubclasses, #includeImplementers: includeImplementers, + #idZoneId: idZoneId, }, ), )), @@ -909,7 +1022,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolate, [isolateId], ), - returnValue: _i3.Future<_i2.Isolate>.value(_FakeIsolate_10( + returnValue: _i3.Future<_i2.Isolate>.value(_FakeIsolate_11( this, Invocation.method( #getIsolate, @@ -925,7 +1038,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolateGroup, [isolateGroupId], ), - returnValue: _i3.Future<_i2.IsolateGroup>.value(_FakeIsolateGroup_11( + returnValue: _i3.Future<_i2.IsolateGroup>.value(_FakeIsolateGroup_12( this, Invocation.method( #getIsolateGroup, @@ -941,7 +1054,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolatePauseEvent, [isolateId], ), - returnValue: _i3.Future<_i2.Event>.value(_FakeEvent_12( + returnValue: _i3.Future<_i2.Event>.value(_FakeEvent_13( this, Invocation.method( #getIsolatePauseEvent, @@ -957,7 +1070,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getMemoryUsage, [isolateId], ), - returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_13( + returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_14( this, Invocation.method( #getMemoryUsage, @@ -974,7 +1087,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getIsolateGroupMemoryUsage, [isolateGroupId], ), - returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_13( + returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_14( this, Invocation.method( #getIsolateGroupMemoryUsage, @@ -990,7 +1103,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getScripts, [isolateId], ), - returnValue: _i3.Future<_i2.ScriptList>.value(_FakeScriptList_14( + returnValue: _i3.Future<_i2.ScriptList>.value(_FakeScriptList_15( this, Invocation.method( #getScripts, @@ -1005,6 +1118,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { String? objectId, { int? offset, int? count, + String? idZoneId, }) => (super.noSuchMethod( Invocation.method( @@ -1016,9 +1130,10 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #offset: offset, #count: count, + #idZoneId: idZoneId, }, ), - returnValue: _i3.Future<_i2.Obj>.value(_FakeObj_15( + returnValue: _i3.Future<_i2.Obj>.value(_FakeObj_16( this, Invocation.method( #getObject, @@ -1029,6 +1144,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { { #offset: offset, #count: count, + #idZoneId: idZoneId, }, ), )), @@ -1050,7 +1166,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { }, ), returnValue: - _i3.Future<_i2.PerfettoCpuSamples>.value(_FakePerfettoCpuSamples_16( + _i3.Future<_i2.PerfettoCpuSamples>.value(_FakePerfettoCpuSamples_17( this, Invocation.method( #getPerfettoCpuSamples, @@ -1078,7 +1194,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { }, ), returnValue: - _i3.Future<_i2.PerfettoTimeline>.value(_FakePerfettoTimeline_17( + _i3.Future<_i2.PerfettoTimeline>.value(_FakePerfettoTimeline_18( this, Invocation.method( #getPerfettoVMTimeline, @@ -1097,7 +1213,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getPorts, [isolateId], ), - returnValue: _i3.Future<_i2.PortList>.value(_FakePortList_18( + returnValue: _i3.Future<_i2.PortList>.value(_FakePortList_19( this, Invocation.method( #getPorts, @@ -1110,8 +1226,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { _i3.Future<_i2.RetainingPath> getRetainingPath( String? isolateId, String? targetId, - int? limit, - ) => + int? limit, { + String? idZoneId, + }) => (super.noSuchMethod( Invocation.method( #getRetainingPath, @@ -1120,8 +1237,9 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], + {#idZoneId: idZoneId}, ), - returnValue: _i3.Future<_i2.RetainingPath>.value(_FakeRetainingPath_19( + returnValue: _i3.Future<_i2.RetainingPath>.value(_FakeRetainingPath_20( this, Invocation.method( #getRetainingPath, @@ -1130,6 +1248,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { targetId, limit, ], + {#idZoneId: idZoneId}, ), )), ) as _i3.Future<_i2.RetainingPath>); @@ -1142,7 +1261,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { [], ), returnValue: - _i3.Future<_i2.ProcessMemoryUsage>.value(_FakeProcessMemoryUsage_20( + _i3.Future<_i2.ProcessMemoryUsage>.value(_FakeProcessMemoryUsage_21( this, Invocation.method( #getProcessMemoryUsage, @@ -1155,19 +1274,26 @@ class MockVmService extends _i1.Mock implements _i2.VmService { _i3.Future<_i2.Stack> getStack( String? isolateId, { int? limit, + String? idZoneId, }) => (super.noSuchMethod( Invocation.method( #getStack, [isolateId], - {#limit: limit}, + { + #limit: limit, + #idZoneId: idZoneId, + }, ), - returnValue: _i3.Future<_i2.Stack>.value(_FakeStack_21( + returnValue: _i3.Future<_i2.Stack>.value(_FakeStack_22( this, Invocation.method( #getStack, [isolateId], - {#limit: limit}, + { + #limit: limit, + #idZoneId: idZoneId, + }, ), )), ) as _i3.Future<_i2.Stack>); @@ -1178,7 +1304,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getSupportedProtocols, [], ), - returnValue: _i3.Future<_i2.ProtocolList>.value(_FakeProtocolList_22( + returnValue: _i3.Future<_i2.ProtocolList>.value(_FakeProtocolList_23( this, Invocation.method( #getSupportedProtocols, @@ -1216,7 +1342,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #librariesAlreadyCompiled: librariesAlreadyCompiled, }, ), - returnValue: _i3.Future<_i2.SourceReport>.value(_FakeSourceReport_23( + returnValue: _i3.Future<_i2.SourceReport>.value(_FakeSourceReport_24( this, Invocation.method( #getSourceReport, @@ -1243,7 +1369,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVersion, [], ), - returnValue: _i3.Future<_i2.Version>.value(_FakeVersion_24( + returnValue: _i3.Future<_i2.Version>.value(_FakeVersion_25( this, Invocation.method( #getVersion, @@ -1258,7 +1384,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVM, [], ), - returnValue: _i3.Future<_i2.VM>.value(_FakeVM_25( + returnValue: _i3.Future<_i2.VM>.value(_FakeVM_26( this, Invocation.method( #getVM, @@ -1281,7 +1407,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #timeExtentMicros: timeExtentMicros, }, ), - returnValue: _i3.Future<_i2.Timeline>.value(_FakeTimeline_26( + returnValue: _i3.Future<_i2.Timeline>.value(_FakeTimeline_27( this, Invocation.method( #getVMTimeline, @@ -1300,7 +1426,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVMTimelineFlags, [], ), - returnValue: _i3.Future<_i2.TimelineFlags>.value(_FakeTimelineFlags_27( + returnValue: _i3.Future<_i2.TimelineFlags>.value(_FakeTimelineFlags_28( this, Invocation.method( #getVMTimelineFlags, @@ -1315,7 +1441,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #getVMTimelineMicros, [], ), - returnValue: _i3.Future<_i2.Timestamp>.value(_FakeTimestamp_28( + returnValue: _i3.Future<_i2.Timestamp>.value(_FakeTimestamp_29( this, Invocation.method( #getVMTimelineMicros, @@ -1369,7 +1495,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { ], {#local: local}, ), - returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_29( + returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_30( this, Invocation.method( #lookupResolvedPackageUris, @@ -1395,7 +1521,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { uris, ], ), - returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_29( + returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_30( this, Invocation.method( #lookupPackageUris, @@ -1451,7 +1577,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #packagesUri: packagesUri, }, ), - returnValue: _i3.Future<_i2.ReloadReport>.value(_FakeReloadReport_30( + returnValue: _i3.Future<_i2.ReloadReport>.value(_FakeReloadReport_31( this, Invocation.method( #reloadSources, @@ -1629,7 +1755,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { value, ], ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( this, Invocation.method( #setFlag, @@ -1814,7 +1940,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #args: args, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( this, Invocation.method( #callMethod, @@ -1842,7 +1968,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { #args: args, }, ), - returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_2( + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3( this, Invocation.method( #callServiceExtension, @@ -1891,7 +2017,7 @@ class MockVmService extends _i1.Mock implements _i2.VmService { ), (T v) => _i3.Future.value(v), ) ?? - _FakeFuture_31( + _FakeFuture_32( this, Invocation.method( #wrapFuture, From 6c05c8affc6e8bb6ac731bcc44925c60fe8d1a69 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 2 Oct 2024 11:37:47 +1000 Subject: [PATCH 13/19] Fix test --- pkgs/coverage/lib/src/isolate_paused_listener.dart | 2 +- pkgs/coverage/test/run_and_collect_test.dart | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index edb168d0c..613e6724e 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2024, 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. diff --git a/pkgs/coverage/test/run_and_collect_test.dart b/pkgs/coverage/test/run_and_collect_test.dart index b6569f714..e371f9000 100644 --- a/pkgs/coverage/test/run_and_collect_test.dart +++ b/pkgs/coverage/test/run_and_collect_test.dart @@ -73,7 +73,6 @@ class ThrowingResolver implements Resolver { void checkIgnoredLinesInFilesCache( Map>?> ignoredLinesInFilesCache) { - expect(ignoredLinesInFilesCache.length, 4); final keys = ignoredLinesInFilesCache.keys.toList(); final testAppKey = keys.where((element) => element.endsWith('test_app.dart')).single; @@ -88,7 +87,7 @@ void checkIgnoredLinesInFilesCache( [51, 51], [53, 57], [62, 65], - [66, 69] + [66, 72] ]); } From 1685679f034785b9adafdea33b9d315959ed51fc Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 2 Oct 2024 16:26:46 +1000 Subject: [PATCH 14/19] Force onExit to wait for onPaused --- .../lib/src/isolate_paused_listener.dart | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 613e6724e..71f6e2da1 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -95,6 +95,8 @@ class IsolatePausedListener { /// an [onIsolateStarted] call for the same isolate. /// - Not every [onIsolateExited] call will be preceeded by a [onIsolatePaused] /// call, but a [onIsolatePaused] will never follow a [onIsolateExited]. +/// - [onIsolateExited] will always run after [onIsolatePaused] completes, even +/// if an exit event arrives while [onIsolatePaused] is being awaited. /// - Each callback will only be called once per isolate. Future listenToIsolateLifecycleEvents( VmService service, @@ -106,17 +108,21 @@ Future listenToIsolateLifecycleEvents( if (started.add(isolateRef.id!)) onIsolateStarted(isolateRef); } - final paused = {}; + final paused = >{}; Future onPause(IsolateRef isolateRef) async { onStart(isolateRef); - if (paused.add(isolateRef.id!)) await onIsolatePaused(isolateRef); + await (paused[isolateRef.id!] ??= onIsolatePaused(isolateRef)); } final exited = {}; - void onExit(IsolateRef isolateRef) { + Future onExit(IsolateRef isolateRef) async { onStart(isolateRef); - paused.add(isolateRef.id!); - if (exited.add(isolateRef.id!)) onIsolateExited(isolateRef); + if (exited.add(isolateRef.id!)) { + // Wait for in-progress pause callbacks. Otherwise prevent future pause + // callbacks from running. + await (paused[isolateRef.id!] ??= Future.value()); + onIsolateExited(isolateRef); + } } final eventBuffer = IsolateEventBuffer((Event event) async { @@ -126,7 +132,7 @@ Future listenToIsolateLifecycleEvents( case EventKind.kPauseExit: return await onPause(event.isolate!); case EventKind.kIsolateExit: - return onExit(event.isolate!); + return await onExit(event.isolate!); } }); @@ -142,7 +148,7 @@ Future listenToIsolateLifecycleEvents( for (final isolateRef in await getAllIsolates(service)) { onStart(isolateRef); final isolate = await service.getIsolate(isolateRef.id!); - if (isolate.pauseEvent!.kind == EventKind.kPauseExit) { + if (isolate.pauseEvent?.kind == EventKind.kPauseExit) { await onPause(isolateRef); } } @@ -192,7 +198,7 @@ class IsolateEventBuffer { final Future Function(Event event) _handler; final _buffer = Queue(); - var _flushed = true; + var _flushed = false; Future add(Event event) async { if (_flushed) { From 0f68e124218402703b6306578d628550106bdcbb Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 3 Oct 2024 11:41:29 +1000 Subject: [PATCH 15/19] Typedefs --- .../lib/src/isolate_paused_listener.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 71f6e2da1..a66c5c57d 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -10,14 +10,19 @@ import 'package:vm_service/vm_service.dart'; import 'util.dart'; +typedef SyncIsolateCallback = void Function(IsolateRef isolate); +typedef AsyncIsolateCallback = Future Function(IsolateRef isolate); +typedef AsyncIsolatePausedCallback = Future Function( + IsolateRef isolate, bool isLastIsolateInGroup); +typedef AsyncVmServiceEventCallback = Future Function(Event event); + /// Calls onIsolatePaused whenever an isolate reaches the pause-on-exit state, /// and passes a flag stating whether that isolate is the last one in the group. class IsolatePausedListener { IsolatePausedListener(this._service, this._onIsolatePaused); final VmService _service; - final Future Function(IsolateRef isolate, bool isLastIsolateInGroup) - _onIsolatePaused; + final AsyncIsolatePausedCallback _onIsolatePaused; final _allExitedCompleter = Completer(); IsolateRef? _mainIsolate; @@ -100,9 +105,9 @@ class IsolatePausedListener { /// - Each callback will only be called once per isolate. Future listenToIsolateLifecycleEvents( VmService service, - void Function(IsolateRef isolate) onIsolateStarted, - Future Function(IsolateRef isolate) onIsolatePaused, - void Function(IsolateRef isolate) onIsolateExited) async { + SyncIsolateCallback onIsolateStarted, + AsyncIsolateCallback onIsolatePaused, + SyncIsolateCallback onIsolateExited) async { final started = {}; void onStart(IsolateRef isolateRef) { if (started.add(isolateRef.id!)) onIsolateStarted(isolateRef); @@ -196,7 +201,7 @@ class IsolateGroupState { class IsolateEventBuffer { IsolateEventBuffer(this._handler); - final Future Function(Event event) _handler; + final AsyncVmServiceEventCallback _handler; final _buffer = Queue(); var _flushed = false; From 42b06546076769e9a3ce8a612412715891dfee74 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 3 Oct 2024 17:10:40 +1000 Subject: [PATCH 16/19] Fix a couple more edge cases and add a test --- pkgs/coverage/lib/src/collect.dart | 2 +- .../lib/src/isolate_paused_listener.dart | 108 ++- .../test/isolate_paused_listener_test.dart | 741 ++++++++++++++++++ 3 files changed, 818 insertions(+), 33 deletions(-) create mode 100644 pkgs/coverage/test/isolate_paused_listener_test.dart diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 264043452..4be9e37fd 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -181,7 +181,7 @@ Future> _getAllCoverage( if (isLastIsolateInGroup) { await collectIsolate(isolateRef); } - }).waitUntilAllExited(); + }, stderr.writeln).waitUntilAllExited(); } else { for (final isolateRef in await getAllIsolates(service)) { await collectIsolate(isolateRef); diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index a66c5c57d..3ac5ca98b 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -15,67 +15,100 @@ typedef AsyncIsolateCallback = Future Function(IsolateRef isolate); typedef AsyncIsolatePausedCallback = Future Function( IsolateRef isolate, bool isLastIsolateInGroup); typedef AsyncVmServiceEventCallback = Future Function(Event event); +typedef SyncErrorLogger = void Function(String message); /// Calls onIsolatePaused whenever an isolate reaches the pause-on-exit state, /// and passes a flag stating whether that isolate is the last one in the group. class IsolatePausedListener { - IsolatePausedListener(this._service, this._onIsolatePaused); + IsolatePausedListener(this._service, this._onIsolatePaused, this._log); final VmService _service; final AsyncIsolatePausedCallback _onIsolatePaused; - final _allExitedCompleter = Completer(); - IsolateRef? _mainIsolate; + final SyncErrorLogger _log; - @visibleForTesting - final isolateGroups = {}; + final _isolateGroups = {}; + + int _numNonMainIsolates = 0; + final _allNonMainIsolatesExited = Completer(); + bool _finished = false; + + IsolateRef? _mainIsolate; + final _mainIsolatePaused = Completer(); /// Starts listening and returns a future that completes when all isolates /// have exited. Future waitUntilAllExited() async { await listenToIsolateLifecycleEvents(_service, _onStart, _onPause, _onExit); - await _allExitedCompleter.future; + await _allNonMainIsolatesExited.future; // Resume the main isolate. - if (_mainIsolate != null) { - await _service.resume(_mainIsolate!.id!); + try { + if (_mainIsolate != null) { + if (await _mainIsolatePaused.future) { + await _runCallbackAndResume(_mainIsolate!, true); + } + } + } finally { + _finished = true; } } IsolateGroupState _getGroup(IsolateRef isolateRef) => - isolateGroups[isolateRef.isolateGroupId!] ??= IsolateGroupState(); + _isolateGroups[isolateRef.isolateGroupId!] ??= IsolateGroupState(); void _onStart(IsolateRef isolateRef) { - if (_allExitedCompleter.isCompleted) return; - _getGroup(isolateRef).start(isolateRef.id!); + if (_finished) return; + final group = _getGroup(isolateRef); + group.start(isolateRef.id!); + if (_mainIsolate == null && _isMainIsolate(isolateRef)) { + _mainIsolate = isolateRef; + } else { + ++_numNonMainIsolates; + } } Future _onPause(IsolateRef isolateRef) async { - if (_allExitedCompleter.isCompleted) return; + if (_finished) return; final group = _getGroup(isolateRef); group.pause(isolateRef.id!); - try { - await _onIsolatePaused(isolateRef, group.noRunningIsolates); - } finally { - await _maybeResumeIsolate(isolateRef); + if (isolateRef.id! == _mainIsolate?.id) { + _mainIsolatePaused.complete(true); + } else { + await _runCallbackAndResume(isolateRef, group.noRunningIsolates); } } - Future _maybeResumeIsolate(IsolateRef isolateRef) async { - if (_mainIsolate == null && _isMainIsolate(isolateRef)) { - _mainIsolate = isolateRef; - // Pretend this isolate has exited so _allExitedCompleter can complete. - _onExit(isolateRef); - } else { + Future _runCallbackAndResume( + IsolateRef isolateRef, bool isLastIsolateInGroup) async { + if (isLastIsolateInGroup) { + _getGroup(isolateRef).collected = true; + } + try { + await _onIsolatePaused(isolateRef, isLastIsolateInGroup); + } finally { await _service.resume(isolateRef.id!); } } void _onExit(IsolateRef isolateRef) { - if (_allExitedCompleter.isCompleted) return; - _getGroup(isolateRef).exit(isolateRef.id!); - if (isolateGroups.values.every((group) => group.noLiveIsolates)) { - _allExitedCompleter.complete(); + if (_finished) return; + final group = _getGroup(isolateRef); + group.exit(isolateRef.id!); + if (group.noLiveIsolates && !group.collected) { + _log('ERROR: An isolate exited without pausing, causing ' + 'coverage data to be lost for group ${isolateRef.isolateGroupId!}.'); + } + if (isolateRef.id! == _mainIsolate?.id) { + if (!_mainIsolatePaused.isCompleted) { + // Main isolate exited without pausing. + _mainIsolatePaused.complete(false); + } + } else { + --_numNonMainIsolates; + if (_numNonMainIsolates == 0 && !_allNonMainIsolatesExited.isCompleted) { + _allNonMainIsolatesExited.complete(); + } } } @@ -115,18 +148,24 @@ Future listenToIsolateLifecycleEvents( final paused = >{}; Future onPause(IsolateRef isolateRef) async { - onStart(isolateRef); - await (paused[isolateRef.id!] ??= onIsolatePaused(isolateRef)); + try { + onStart(isolateRef); + } finally { + await (paused[isolateRef.id!] ??= onIsolatePaused(isolateRef)); + } } final exited = {}; Future onExit(IsolateRef isolateRef) async { onStart(isolateRef); if (exited.add(isolateRef.id!)) { - // Wait for in-progress pause callbacks. Otherwise prevent future pause - // callbacks from running. - await (paused[isolateRef.id!] ??= Future.value()); - onIsolateExited(isolateRef); + try { + // Wait for in-progress pause callbacks, and prevent future pause + // callbacks from running. + await (paused[isolateRef.id!] ??= Future.value()); + } finally { + onIsolateExited(isolateRef); + } } } @@ -173,6 +212,8 @@ class IsolateGroupState { @visibleForTesting final paused = {}; + bool collected = false; + bool get noRunningIsolates => running.isEmpty; bool get noLiveIsolates => running.isEmpty && paused.isEmpty; @@ -190,6 +231,9 @@ class IsolateGroupState { running.remove(id); paused.remove(id); } + + @override + String toString() => '{running: $running, paused: $paused}'; } /// Buffers VM service isolate [Event]s until [flush] is called. diff --git a/pkgs/coverage/test/isolate_paused_listener_test.dart b/pkgs/coverage/test/isolate_paused_listener_test.dart new file mode 100644 index 000000000..5e86bec91 --- /dev/null +++ b/pkgs/coverage/test/isolate_paused_listener_test.dart @@ -0,0 +1,741 @@ +// Copyright (c) 2024, 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:coverage/src/isolate_paused_listener.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'collect_coverage_mock_test.mocks.dart'; + +Event event( + String id, { + String? kind, + String? groupId, + String? name, +}) => + Event( + kind: kind, + isolate: IsolateRef( + isolateGroupId: groupId, + id: id, + name: name, + )); + +Isolate isolate( + String id, { + String? groupId, + String? name, + String? pauseKind, +}) => + Isolate( + isolateGroupId: groupId, + id: id, + name: name, + pauseEvent: pauseKind == null ? null : Event(kind: pauseKind), + ); + +(MockVmService, StreamController) createServiceAndEventStreams() { + final service = MockVmService(); + when(service.streamListen(any)).thenAnswer((_) async => Success()); + + // The VM service events we care about come in on 2 different streams, + // onIsolateEvent and onDebugEvent. We want to write tests that send sequences + // of events like [I1, D1, I2, D2, I3, D3], but since I and D go to separate + // streams, the listener may see them arrive like [I1, I2, I3, D1, D2, D3] or + // [D1, D2, D3, I1, I2, I3] or any other interleaving. So instead we send all + // the events through a single stream that gets split up. This emulates how + // the events work in reality, since they all come from a single web socket. + final allEvents = StreamController(); + final isolateEvents = StreamController(); + final debugEvents = StreamController(); + allEvents.stream.listen((Event e) { + if (e.kind == EventKind.kIsolateStart || + e.kind == EventKind.kIsolateStart) { + isolateEvents.add(e); + } else { + debugEvents.add(e); + } + }); + when(service.onIsolateEvent).thenAnswer((_) => isolateEvents.stream); + when(service.onDebugEvent).thenAnswer((_) => debugEvents.stream); + + return (service, allEvents); +} + +void main() { + group('IsolateEventBuffer', () { + test('buffers events', () async { + final received = []; + final eventBuffer = IsolateEventBuffer((Event event) async { + await Future.delayed(Duration.zero); + received.add(event.isolate!.id!); + }); + + await eventBuffer.add(event('a')); + await eventBuffer.add(event('b')); + await eventBuffer.add(event('c')); + expect(received, []); + + await eventBuffer.flush(); + expect(received, ['a', 'b', 'c']); + + await eventBuffer.flush(); + expect(received, ['a', 'b', 'c']); + + await eventBuffer.add(event('d')); + await eventBuffer.add(event('e')); + await eventBuffer.add(event('f')); + expect(received, ['a', 'b', 'c', 'd', 'e', 'f']); + + await eventBuffer.flush(); + expect(received, ['a', 'b', 'c', 'd', 'e', 'f']); + }); + + test('buffers events during flush', () async { + final received = []; + final pause = Completer(); + final eventBuffer = IsolateEventBuffer((Event event) async { + await pause.future; + received.add(event.isolate!.id!); + }); + + await eventBuffer.add(event('a')); + await eventBuffer.add(event('b')); + await eventBuffer.add(event('c')); + expect(received, []); + + final flushing = eventBuffer.flush(); + expect(received, []); + + await eventBuffer.add(event('d')); + await eventBuffer.add(event('e')); + await eventBuffer.add(event('f')); + expect(received, []); + + pause.complete(); + await flushing; + expect(received, ['a', 'b', 'c', 'd', 'e', 'f']); + }); + }); + + test('IsolateEventBuffer', () { + final group = IsolateGroupState(); + expect(group.running, isEmpty); + expect(group.paused, isEmpty); + expect(group.noRunningIsolates, isTrue); + expect(group.noLiveIsolates, isTrue); + + group.start('a'); + expect(group.running, unorderedEquals(['a'])); + expect(group.paused, isEmpty); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.start('a'); + expect(group.running, unorderedEquals(['a'])); + expect(group.paused, isEmpty); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.start('b'); + expect(group.running, unorderedEquals(['a', 'b'])); + expect(group.paused, isEmpty); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.pause('a'); + expect(group.running, unorderedEquals(['b'])); + expect(group.paused, unorderedEquals(['a'])); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.pause('a'); + expect(group.running, unorderedEquals(['b'])); + expect(group.paused, unorderedEquals(['a'])); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.pause('c'); + expect(group.running, unorderedEquals(['b'])); + expect(group.paused, unorderedEquals(['a', 'c'])); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.start('c'); + expect(group.running, unorderedEquals(['b', 'c'])); + expect(group.paused, unorderedEquals(['a'])); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.pause('c'); + expect(group.running, unorderedEquals(['b'])); + expect(group.paused, unorderedEquals(['a', 'c'])); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.exit('a'); + expect(group.running, unorderedEquals(['b'])); + expect(group.paused, unorderedEquals(['c'])); + expect(group.noRunningIsolates, isFalse); + expect(group.noLiveIsolates, isFalse); + + group.pause('b'); + expect(group.running, isEmpty); + expect(group.paused, unorderedEquals(['b', 'c'])); + expect(group.noRunningIsolates, isTrue); + expect(group.noLiveIsolates, isFalse); + + group.exit('b'); + expect(group.running, isEmpty); + expect(group.paused, unorderedEquals(['c'])); + expect(group.noRunningIsolates, isTrue); + expect(group.noLiveIsolates, isFalse); + + group.exit('c'); + expect(group.running, isEmpty); + expect(group.paused, isEmpty); + expect(group.noRunningIsolates, isTrue); + expect(group.noLiveIsolates, isTrue); + }); + + group('listenToIsolateLifecycleEvents', () { + late MockVmService service; + late StreamController allEvents; + late Completer> isolates; + late Future backfilled; + late Future testEnded; + + late List received; + Future? delayTheOnPauseCallback; + + void startEvent(String id) => + allEvents.add(event(id, kind: EventKind.kIsolateStart)); + void exitEvent(String id) => + allEvents.add(event(id, kind: EventKind.kIsolateExit)); + void pauseEvent(String id) => + allEvents.add(event(id, kind: EventKind.kPauseExit)); + void otherEvent(String id, String kind) => + allEvents.add(event(id, kind: kind)); + + Future backfill(List isos) async { + isolates.complete(isos); + await backfilled; + } + + // We end the test by sending an exit event with a specific ID. + const endTestEventId = 'END'; + Future endTest() { + exitEvent(endTestEventId); + return testEnded; + } + + setUp(() { + (service, allEvents) = createServiceAndEventStreams(); + + isolates = Completer>(); + when(service.getVM()) + .thenAnswer((_) async => VM(isolates: await isolates.future)); + when(service.getIsolate(any)).thenAnswer((invocation) async { + final id = invocation.positionalArguments[0]; + return (await isolates.future).firstWhere((iso) => iso.id == id); + }); + + received = []; + delayTheOnPauseCallback = null; + final testEnder = Completer(); + testEnded = testEnder.future; + backfilled = listenToIsolateLifecycleEvents( + service, + (iso) { + if (iso.id == endTestEventId) return; + received.add('Start ${iso.id}'); + }, + (iso) async { + received.add('Pause ${iso.id}'); + if (delayTheOnPauseCallback != null) { + await delayTheOnPauseCallback; + received.add('Pause done ${iso.id}'); + } + }, + (iso) { + if (iso.id == endTestEventId) { + testEnder.complete(); + } else { + received.add('Exit ${iso.id}'); + } + }, + ); + }); + + test('ordinary flows', () async { + // Events sent before backfill. + startEvent('A'); + startEvent('C'); + startEvent('B'); + pauseEvent('C'); + pauseEvent('A'); + startEvent('D'); + pauseEvent('D'); + exitEvent('A'); + + // Run backfill. + await backfill([ + isolate('B'), + isolate('C', pauseKind: EventKind.kPauseExit), + isolate('D'), + isolate('E'), + isolate('F', pauseKind: EventKind.kPauseExit), + ]); + + // All the backfill events happen before any of the real events. + expect(received, [ + // Backfill events. + 'Start B', + 'Start C', + 'Pause C', + 'Start D', + 'Start E', + 'Start F', + 'Pause F', + + // Real events from before backfill. + 'Start A', + 'Pause A', + 'Pause D', + 'Exit A', + ]); + + // Events sent after backfill. + received.clear(); + startEvent('G'); + exitEvent('C'); + exitEvent('B'); + exitEvent('G'); + exitEvent('D'); + exitEvent('E'); + exitEvent('F'); + + await endTest(); + expect(received, [ + 'Start G', + 'Exit C', + 'Exit B', + 'Exit G', + 'Exit D', + 'Exit E', + 'Exit F', + ]); + + verify(service.streamListen(EventStreams.kIsolate)).called(1); + verify(service.streamListen(EventStreams.kDebug)).called(1); + }); + + test('pause and exit events without start', () async { + await backfill([]); + + pauseEvent('A'); + exitEvent('B'); + + await endTest(); + expect(received, [ + 'Start A', + 'Pause A', + 'Start B', + 'Exit B', + ]); + }); + + test('pause event after exit is ignored', () async { + await backfill([]); + + exitEvent('A'); + pauseEvent('A'); + + await endTest(); + expect(received, [ + 'Start A', + 'Exit A', + ]); + }); + + test('event deduping', () async { + startEvent('A'); + startEvent('A'); + pauseEvent('A'); + pauseEvent('A'); + exitEvent('A'); + exitEvent('A'); + + pauseEvent('B'); + startEvent('B'); + + exitEvent('C'); + startEvent('C'); + + await backfill([]); + await endTest(); + expect(received, [ + 'Start A', + 'Pause A', + 'Exit A', + 'Start B', + 'Pause B', + 'Start C', + 'Exit C', + ]); + }); + + test('ignore other events', () async { + await backfill([]); + + startEvent('A'); + pauseEvent('A'); + otherEvent('A', EventKind.kResume); + exitEvent('A'); + + startEvent('B'); + otherEvent('B', EventKind.kPauseBreakpoint); + exitEvent('B'); + + otherEvent('C', EventKind.kInspect); + + await endTest(); + expect(received, [ + 'Start A', + 'Pause A', + 'Exit A', + 'Start B', + 'Exit B', + ]); + }); + + test('exit event during pause callback', () async { + final delayingTheOnPauseCallback = Completer(); + delayTheOnPauseCallback = delayingTheOnPauseCallback.future; + await backfill([]); + + startEvent('A'); + pauseEvent('A'); + exitEvent('A'); + + while (received.length < 2) { + await Future.delayed(Duration.zero); + } + + expect(received, [ + 'Start A', + 'Pause A', + ]); + + delayingTheOnPauseCallback.complete(); + await endTest(); + expect(received, [ + 'Start A', + 'Pause A', + 'Pause done A', + 'Exit A', + ]); + }); + + test('exit event during pause callback, event deduping', () async { + final delayingTheOnPauseCallback = Completer(); + delayTheOnPauseCallback = delayingTheOnPauseCallback.future; + await backfill([]); + + startEvent('A'); + pauseEvent('A'); + exitEvent('A'); + pauseEvent('A'); + pauseEvent('A'); + exitEvent('A'); + exitEvent('A'); + + while (received.length < 2) { + await Future.delayed(Duration.zero); + } + + expect(received, [ + 'Start A', + 'Pause A', + ]); + + delayingTheOnPauseCallback.complete(); + await endTest(); + expect(received, [ + 'Start A', + 'Pause A', + 'Pause done A', + 'Exit A', + ]); + }); + }); + + group('IsolatePausedListener', () { + late MockVmService service; + late StreamController allEvents; + late Future allIsolatesExited; + + late List received; + late bool stopped; + + void startEvent(String id, String groupId, [String? name]) => + allEvents.add(event( + id, + kind: EventKind.kIsolateStart, + groupId: groupId, + name: name ?? id, + )); + void exitEvent(String id, String groupId, [String? name]) => + allEvents.add(event( + id, + kind: EventKind.kIsolateExit, + groupId: groupId, + name: name ?? id, + )); + void pauseEvent(String id, String groupId, [String? name]) => + allEvents.add(event( + id, + kind: EventKind.kPauseExit, + groupId: groupId, + name: name ?? id, + )); + + Future endTest() async { + await allIsolatesExited; + stopped = true; + } + + setUp(() { + (service, allEvents) = createServiceAndEventStreams(); + + // Backfill was tested above, so this test does everything using events, + // for simplicity. No need to report any isolates. + when(service.getVM()).thenAnswer((_) async => VM()); + + received = []; + when(service.resume(any)).thenAnswer((invocation) async { + final id = invocation.positionalArguments[0]; + received.add('Resume $id'); + return Success(); + }); + + stopped = false; + allIsolatesExited = IsolatePausedListener( + service, + (iso, isLastIsolateInGroup) async { + expect(stopped, isFalse); + received.add('Pause ${iso.id}. Last in group ${iso.isolateGroupId}? ' + '${isLastIsolateInGroup ? 'Yes' : 'No'}'); + }, + (message) => received.add(message), + ).waitUntilAllExited(); + }); + + test('ordinary flows', () async { + startEvent('A', '1'); + startEvent('B', '1'); + pauseEvent('A', '1'); + startEvent('C', '1'); + pauseEvent('B', '1'); + exitEvent('A', '1'); + startEvent('D', '2'); + startEvent('E', '2'); + startEvent('F', '2'); + pauseEvent('C', '1'); + pauseEvent('F', '2'); + pauseEvent('E', '2'); + exitEvent('C', '1'); + exitEvent('E', '2'); + startEvent('G', '3'); + exitEvent('F', '2'); + startEvent('H', '3'); + startEvent('I', '3'); + pauseEvent('I', '3'); + exitEvent('I', '3'); + pauseEvent('H', '3'); + exitEvent('H', '3'); + pauseEvent('D', '2'); + pauseEvent('G', '3'); + exitEvent('D', '2'); + exitEvent('G', '3'); + exitEvent('B', '1'); + + await endTest(); + + // Events sent after waitUntilAllExited is finished do nothing. + startEvent('Z', '9'); + pauseEvent('Z', '9'); + exitEvent('Z', '9'); + + expect(received, [ + 'Pause A. Last in group 1? No', + 'Resume A', + 'Pause B. Last in group 1? No', + 'Resume B', + 'Pause C. Last in group 1? Yes', + 'Resume C', + 'Pause F. Last in group 2? No', + 'Resume F', + 'Pause E. Last in group 2? No', + 'Resume E', + 'Pause I. Last in group 3? No', + 'Resume I', + 'Pause H. Last in group 3? No', + 'Resume H', + 'Pause D. Last in group 2? Yes', + 'Resume D', + 'Pause G. Last in group 3? Yes', + 'Resume G', + ]); + }); + + test('exit without pausing', () async { + // If an isolate exits without pausing, this may mess up coverage + // collection (if it happens to be the last isolate in the group, that + // group won't be collected). The best we can do is log an error, and make + // sure not to wait forever for pause events that aren't coming. + startEvent('A', '1'); + startEvent('B', '1'); + exitEvent('A', '1'); + pauseEvent('B', '1'); + startEvent('C', '2'); + startEvent('D', '2'); + pauseEvent('D', '2'); + exitEvent('D', '2'); + exitEvent('C', '2'); + exitEvent('B', '1'); + + await endTest(); + + // B was paused correctly and was the last to exit isolate 1, so isolate 1 + // was collected ok. + expect(received, [ + 'Pause B. Last in group 1? Yes', + 'Resume B', + 'Pause D. Last in group 2? No', + 'Resume D', + 'ERROR: An isolate exited without pausing, causing coverage data to ' + 'be lost for group 2.', + ]); + }); + + test('main isolate resumed last', () async { + startEvent('A', '1', 'main'); + startEvent('B', '1', 'main'); // Second isolate named main, ignored. + pauseEvent('B', '1', 'main'); + pauseEvent('A', '1', 'main'); + startEvent('C', '2', 'main'); // Third isolate named main, ignored. + startEvent('D', '2'); + pauseEvent('C', '2'); + exitEvent('C', '2'); + pauseEvent('D', '2'); + exitEvent('D', '2'); + exitEvent('B', '1'); + + await endTest(); + + expect(received, [ + 'Pause B. Last in group 1? No', + 'Resume B', + 'Pause C. Last in group 2? No', + 'Resume C', + 'Pause D. Last in group 2? Yes', + 'Resume D', + 'Pause A. Last in group 1? Yes', + 'Resume A', + ]); + }); + + test('main isolate exits without pausing', () async { + startEvent('A', '1', 'main'); + startEvent('B', '1'); + pauseEvent('B', '1'); + exitEvent('A', '1', 'main'); + exitEvent('B', '1'); + + await endTest(); + + expect(received, [ + 'Pause B. Last in group 1? No', + 'Resume B', + 'ERROR: An isolate exited without pausing, causing coverage data to ' + 'be lost for group 1.', + ]); + }); + + test('all other isolates exit before main isolate pauses', () async { + startEvent('A', '1', 'main'); + startEvent('B', '1'); + pauseEvent('B', '1'); + exitEvent('B', '1'); + + await Future.delayed(Duration.zero); + + pauseEvent('A', '1', 'main'); + exitEvent('A', '1', 'main'); + + await endTest(); + + expect(received, [ + 'Pause B. Last in group 1? No', + 'Resume B', + 'Pause A. Last in group 1? Yes', + 'Resume A', + ]); + }); + + test('group reopened', () async { + // If an isolate is reported in a group after the group as believed to be + // closed, reopen the group. This double counts some coverage, but at + // least won't miss any. + + startEvent('Z', '9'); // Separate isolate to keep the system alive until + pauseEvent('Z', '9'); // the test is complete. + + startEvent('A', '1'); + startEvent('B', '1'); + pauseEvent('A', '1'); + pauseEvent('B', '1'); + exitEvent('B', '1'); + exitEvent('A', '1'); + + startEvent('D', '2'); + startEvent('E', '2'); + pauseEvent('E', '2'); + pauseEvent('D', '2'); + exitEvent('E', '2'); + exitEvent('D', '2'); + + startEvent('C', '1'); + pauseEvent('F', '2'); + pauseEvent('C', '1'); + exitEvent('C', '1'); + exitEvent('F', '2'); + + exitEvent('Z', '9'); + + await endTest(); + + expect(received, [ + 'Pause Z. Last in group 9? Yes', + 'Resume Z', + 'Pause A. Last in group 1? No', + 'Resume A', + 'Pause B. Last in group 1? Yes', + 'Resume B', + 'Pause E. Last in group 2? No', + 'Resume E', + 'Pause D. Last in group 2? Yes', + 'Resume D', + 'Pause F. Last in group 2? Yes', + 'Resume F', + 'Pause C. Last in group 1? Yes', + 'Resume C', + ]); + }); + }); +} From 65d57c07c5aaec60bb9bb676dcf800d04ab6ebf7 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 3 Oct 2024 17:14:44 +1000 Subject: [PATCH 17/19] fmt --- pkgs/coverage/lib/src/collect.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 4be9e37fd..76227bac9 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -177,11 +177,12 @@ Future> _getAllCoverage( if (waitPaused) { await IsolatePausedListener(service, - (IsolateRef isolateRef, bool isLastIsolateInGroup) async { + (IsolateRef isolateRef, bool isLastIsolateInGroup) async { if (isLastIsolateInGroup) { await collectIsolate(isolateRef); } - }, stderr.writeln).waitUntilAllExited(); + }, stderr.writeln) + .waitUntilAllExited(); } else { for (final isolateRef in await getAllIsolates(service)) { await collectIsolate(isolateRef); From f4670dd4f5292f3693d056be2030a54d8ef39d70 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 4 Oct 2024 11:55:32 +1000 Subject: [PATCH 18/19] Handle case where main is the only isolate --- pkgs/coverage/lib/src/isolate_paused_listener.dart | 14 +++++++++++--- .../test/isolate_paused_listener_test.dart | 14 +++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart index 3ac5ca98b..a89a88b8a 100644 --- a/pkgs/coverage/lib/src/isolate_paused_listener.dart +++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart @@ -74,6 +74,10 @@ class IsolatePausedListener { group.pause(isolateRef.id!); if (isolateRef.id! == _mainIsolate?.id) { _mainIsolatePaused.complete(true); + + // If the main isolate is the only isolate, then _allNonMainIsolatesExited + // will never be completed. So check that case here. + _checkCompleted(); } else { await _runCallbackAndResume(isolateRef, group.noRunningIsolates); } @@ -106,9 +110,13 @@ class IsolatePausedListener { } } else { --_numNonMainIsolates; - if (_numNonMainIsolates == 0 && !_allNonMainIsolatesExited.isCompleted) { - _allNonMainIsolatesExited.complete(); - } + _checkCompleted(); + } + } + + void _checkCompleted() { + if (_numNonMainIsolates == 0 && !_allNonMainIsolatesExited.isCompleted) { + _allNonMainIsolatesExited.complete(); } } diff --git a/pkgs/coverage/test/isolate_paused_listener_test.dart b/pkgs/coverage/test/isolate_paused_listener_test.dart index 5e86bec91..4fc7d4b12 100644 --- a/pkgs/coverage/test/isolate_paused_listener_test.dart +++ b/pkgs/coverage/test/isolate_paused_listener_test.dart @@ -627,8 +627,8 @@ void main() { startEvent('A', '1', 'main'); startEvent('B', '1', 'main'); // Second isolate named main, ignored. pauseEvent('B', '1', 'main'); - pauseEvent('A', '1', 'main'); startEvent('C', '2', 'main'); // Third isolate named main, ignored. + pauseEvent('A', '1', 'main'); startEvent('D', '2'); pauseEvent('C', '2'); exitEvent('C', '2'); @@ -667,6 +667,18 @@ void main() { ]); }); + test('main isolate is the only isolate', () async { + startEvent('A', '1', 'main'); + pauseEvent('A', '1', 'main'); + + await endTest(); + + expect(received, [ + 'Pause A. Last in group 1? Yes', + 'Resume A', + ]); + }); + test('all other isolates exit before main isolate pauses', () async { startEvent('A', '1', 'main'); startEvent('B', '1'); From 6a15be4ce67fdfb9cbc90cb5f396ef9e89ca6242 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 4 Oct 2024 12:08:55 +1000 Subject: [PATCH 19/19] Make meta dep less strict for flutter compatibility --- pkgs/coverage/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 4f5de816b..c7dc933b8 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: args: ^2.0.0 glob: ^2.1.2 logging: ^1.0.0 - meta: ^1.16.0 + meta: ^1.0.2 package_config: ^2.0.0 path: ^1.8.0 source_maps: ^0.10.10