Skip to content

Commit f6199b9

Browse files
committed
Auto-select test suite isolate for paused dart tests
1 parent ce5d8ef commit f6199b9

2 files changed

Lines changed: 88 additions & 22 deletions

File tree

packages/devtools_app_shared/lib/src/service/isolate_manager.dart

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -166,23 +166,25 @@ final class IsolateManager with DisposerMixin {
166166
!event.isolate!.isSystemIsolate!) {
167167
await _registerIsolate(event.isolate!);
168168
_isolateCreatedController.add(event.isolate);
169-
// TODO(jacobr): we assume the first isolate started is the main isolate
170-
// but that may not always be a safe assumption.
171-
// TODO(https://github.com/flutter/devtools/issues/9747): Detect main
172-
// isolate using root library information for test connections here too,
173-
// not just in _computeMainIsolate().
174-
if (_mainIsolate.value == null) {
169+
170+
// Recompute whenever a new isolate starts so test connections can move
171+
// from the runner isolate to the user test-suite isolate when available.
172+
final previousMain = _mainIsolate.value;
173+
final computedMain = await _computeMainIsolate();
174+
if (computedMain != null) {
175+
_mainIsolate.value = computedMain;
176+
} else if (_mainIsolate.value == null) {
175177
_mainIsolate.value = event.isolate;
176-
if (_shouldReselectMainIsolate) {
177-
// Assume the main isolate has come back up after a hot restart, so
178-
// select it.
179-
_shouldReselectMainIsolate = false;
180-
_setSelectedIsolate(event.isolate);
181-
}
182178
}
183179

184-
if (_selectedIsolate.value == null) {
185-
_setSelectedIsolate(event.isolate);
180+
if (_mainIsolate.value != null &&
181+
(_shouldReselectMainIsolate ||
182+
_selectedIsolate.value == null ||
183+
_selectedIsolate.value == previousMain)) {
184+
// If the previous main exited and returned (hot restart) or we were
185+
// following the previous main, follow the newly computed main isolate.
186+
_shouldReselectMainIsolate = false;
187+
_setSelectedIsolate(_mainIsolate.value);
186188
}
187189
} else if (event.kind == EventKind.kServiceExtensionAdded) {
188190
// Check to see if there is a new isolate.
@@ -226,13 +228,11 @@ final class IsolateManager with DisposerMixin {
226228

227229
final service = _service;
228230
for (final isolateState in _isolateStates.values) {
229-
if (_selectedIsolate.value == null) {
230-
final isolate = await isolateState.isolate;
231-
if (service != _service) return null;
232-
for (final extensionName in isolate?.extensionRPCs ?? <String>[]) {
233-
if (extensions.isFlutterExtension(extensionName)) {
234-
return isolateState.isolateRef;
235-
}
231+
final isolate = await isolateState.isolate;
232+
if (service != _service) return null;
233+
for (final extensionName in isolate?.extensionRPCs ?? <String>[]) {
234+
if (extensions.isFlutterExtension(extensionName)) {
235+
return isolateState.isolateRef;
236236
}
237237
}
238238
}

packages/devtools_app_shared/test/service/isolate_manager_test.dart

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ class _FakeVmService extends Fake implements VmService {
1313
/// Map of isolate id -> Isolate to return from getIsolate().
1414
final Map<String, Isolate> isolates;
1515

16+
final _isolateEventController = StreamController<Event>.broadcast();
17+
1618
_FakeVmService(this.isolates);
1719

1820
@override
19-
Stream<Event> get onIsolateEvent => const Stream.empty();
21+
Stream<Event> get onIsolateEvent => _isolateEventController.stream;
2022

2123
@override
2224
Stream<Event> get onDebugEvent => const Stream.empty();
@@ -34,6 +36,26 @@ class _FakeVmService extends Fake implements VmService {
3436
@override
3537
Future<Success> resume(String isolateId, {String? step, int? frameIndex}) =>
3638
Future.value(Success());
39+
40+
Future<void> emitIsolateStart(IsolateRef isolateRef) async {
41+
_isolateEventController.add(
42+
Event.parse({
43+
'type': 'Event',
44+
'kind': EventKind.kIsolateStart,
45+
'isolate': {
46+
'type': '@Isolate',
47+
'id': isolateRef.id,
48+
'name': isolateRef.name,
49+
'isSystemIsolate': isolateRef.isSystemIsolate,
50+
},
51+
})!,
52+
);
53+
await Future<void>.delayed(Duration.zero);
54+
}
55+
56+
Future<void> dispose() async {
57+
await _isolateEventController.close();
58+
}
3759
}
3860

3961
/// Creates a minimal runnable [Isolate] for a given [IsolateRef].
@@ -63,13 +85,18 @@ IsolateRef _makeRef(String name, String id) {
6385
void main() {
6486
group('IsolateManager._computeMainIsolate', () {
6587
late IsolateManager manager;
88+
final fakeServices = <_FakeVmService>[];
6689

6790
setUp(() {
6891
manager = IsolateManager();
6992
});
7093

7194
tearDown(() {
7295
manager.handleVmServiceClosed();
96+
for (final fakeService in fakeServices) {
97+
fakeService.dispose();
98+
}
99+
fakeServices.clear();
73100
});
74101

75102
test(
@@ -91,6 +118,7 @@ void main() {
91118
'isolates/2': _makeIsolate(testSuiteRef),
92119
'isolates/3': _makeIsolate(vmServiceRef),
93120
});
121+
fakeServices.add(fakeService);
94122

95123
manager.vmServiceOpened(fakeService);
96124
await manager.init([testRunnerRef, testSuiteRef, vmServiceRef]);
@@ -118,6 +146,7 @@ void main() {
118146
'isolates/1': _makeIsolate(mainRef),
119147
'isolates/2': _makeIsolate(vmServiceRef),
120148
});
149+
fakeServices.add(fakeService);
121150

122151
manager.vmServiceOpened(fakeService);
123152
await manager.init([mainRef, vmServiceRef]);
@@ -135,6 +164,7 @@ void main() {
135164
final fakeService = _FakeVmService({
136165
'isolates/1': _makeIsolate(scriptRef),
137166
});
167+
fakeServices.add(fakeService);
138168

139169
manager.vmServiceOpened(fakeService);
140170
await manager.init([scriptRef]);
@@ -166,6 +196,7 @@ void main() {
166196
rootLibraryUri: 'dart:developer',
167197
),
168198
});
199+
fakeServices.add(fakeService);
169200

170201
manager.vmServiceOpened(fakeService);
171202
await manager.init([testRunnerRef, userTestRef, vmServiceRef]);
@@ -182,5 +213,40 @@ void main() {
182213
);
183214
},
184215
);
216+
217+
test(
218+
'promotes main isolate from test runner to test suite on isolate start',
219+
() async {
220+
final testRunnerRef = _makeRef('main', 'isolates/1');
221+
final testSuiteRef = _makeRef(
222+
'test_suite:file:///tmp/dart_test.kernel.dill',
223+
'isolates/2',
224+
);
225+
226+
final fakeService = _FakeVmService({
227+
'isolates/1': _makeIsolate(testRunnerRef),
228+
'isolates/2': _makeIsolate(testSuiteRef),
229+
});
230+
fakeServices.add(fakeService);
231+
232+
manager.vmServiceOpened(fakeService);
233+
await manager.init(const []);
234+
235+
await fakeService.emitIsolateStart(testRunnerRef);
236+
expect(manager.selectedIsolate.value?.name, equals('main'));
237+
expect(manager.mainIsolate.value?.name, equals('main'));
238+
239+
await fakeService.emitIsolateStart(testSuiteRef);
240+
expect(
241+
manager.selectedIsolate.value?.name,
242+
equals('test_suite:file:///tmp/dart_test.kernel.dill'),
243+
reason: 'Should switch selection to test_suite isolate once it starts',
244+
);
245+
expect(
246+
manager.mainIsolate.value?.name,
247+
equals('test_suite:file:///tmp/dart_test.kernel.dill'),
248+
);
249+
},
250+
);
185251
});
186252
}

0 commit comments

Comments
 (0)