-
Notifications
You must be signed in to change notification settings - Fork 388
Fix auto select main isolate #9756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
062845e
fcd42b9
45fa504
9c1cf10
8775ece
0f74d38
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -166,20 +166,26 @@ final class IsolateManager with DisposerMixin { | |
| !event.isolate!.isSystemIsolate!) { | ||
| await _registerIsolate(event.isolate!); | ||
| _isolateCreatedController.add(event.isolate); | ||
| // TODO(jacobr): we assume the first isolate started is the main isolate | ||
| // but that may not always be a safe assumption. | ||
| if (_mainIsolate.value == null) { | ||
|
|
||
| // Recompute whenever a new isolate starts so test connections can move | ||
| // from the runner isolate to the user test-suite isolate when available. | ||
| final previousMain = _mainIsolate.value; | ||
| final computedMain = await _computeMainIsolate(); | ||
| if (computedMain != null) { | ||
| _mainIsolate.value = computedMain; | ||
| } else if (_mainIsolate.value == null) { | ||
| _mainIsolate.value = event.isolate; | ||
| if (_shouldReselectMainIsolate) { | ||
| // Assume the main isolate has come back up after a hot restart, so | ||
| // select it. | ||
| _shouldReselectMainIsolate = false; | ||
| _setSelectedIsolate(event.isolate); | ||
| } | ||
| } | ||
|
|
||
| if (_selectedIsolate.value == null) { | ||
| _setSelectedIsolate(event.isolate); | ||
| if (_mainIsolate.value != null && | ||
| (_shouldReselectMainIsolate || | ||
| _selectedIsolate.value == null || | ||
| _selectedIsolate.value == previousMain)) { | ||
| // If the previous main exited and returned (hot restart), or if the | ||
| // selected isolate was previously the main isolate, update selection to | ||
| // the newly computed main isolate. | ||
| _shouldReselectMainIsolate = false; | ||
| _setSelectedIsolate(_mainIsolate.value); | ||
| } | ||
| } else if (event.kind == EventKind.kServiceExtensionAdded) { | ||
| // Check to see if there is a new isolate. | ||
|
|
@@ -223,25 +229,107 @@ final class IsolateManager with DisposerMixin { | |
|
|
||
| final service = _service; | ||
| for (final isolateState in _isolateStates.values) { | ||
| if (_selectedIsolate.value == null) { | ||
| final isolate = await isolateState.isolate; | ||
| if (service != _service) return null; | ||
| for (final extensionName in isolate?.extensionRPCs ?? <String>[]) { | ||
| if (extensions.isFlutterExtension(extensionName)) { | ||
| return isolateState.isolateRef; | ||
| } | ||
| final isolate = await isolateState.isolate; | ||
| if (service != _service) return null; | ||
| for (final extensionName in isolate?.extensionRPCs ?? <String>[]) { | ||
| if (extensions.isFlutterExtension(extensionName)) { | ||
| return isolateState.isolateRef; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| final ref = _isolateStates.keys.firstWhereOrNull((IsolateRef ref) { | ||
| // 'foo.dart:main()' | ||
| return ref.name!.contains(':main('); | ||
| return ref.name?.contains(':main(') ?? false; | ||
| }); | ||
|
|
||
| if (ref == null) { | ||
| final rootLibraryTestSuiteRef = | ||
| await _findTestSuiteByRootLibrary(service); | ||
| if (rootLibraryTestSuiteRef != null) return rootLibraryTestSuiteRef; | ||
|
|
||
| // When connecting to a test run, the test package (package:test_core) | ||
| // spawns each test suite in a separate isolate with a debug name | ||
| // prefixed with 'test_suite:'. DevTools should connect to this isolate | ||
| // rather than the test runner isolate ('main'), since the test suite | ||
| // isolate is where user code actually runs. | ||
| // See: https://github.com/flutter/devtools/issues/9747 | ||
| final testSuiteRef = _isolateStates.keys.firstWhereOrNull( | ||
| (IsolateRef ref) => ref.name?.startsWith('test_suite:') ?? false, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this isolate not marked as a system isolate? Ideally it just wouldn't appear in the isolates list.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the actual test, so it's the thing we do want to find. I don't know if we can make the root isolate (test runner) be not marked as a system isolate. But that is the one we would want to ignore if any probably.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, that makes sense. If we mark the main isolate as a system isolate, I'm not sure we'll need this change. We already do it for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we can achieve the same result that way it would definitely be preferable to matching based on debug name and other heuristics |
||
| ); | ||
| if (testSuiteRef != null) return testSuiteRef; | ||
| } | ||
|
|
||
| return ref ?? _isolateStates.keys.first; | ||
| } | ||
|
|
||
| Future<IsolateRef?> _findTestSuiteByRootLibrary(VmService? service) async { | ||
| String? projectRootUri; | ||
| if (service != null) { | ||
| try { | ||
| final vm = await service.getVM(); | ||
| if (service != _service) return null; | ||
| // `rootUri` is not a typed field on `VM` in the current vm_service | ||
| // version, so read it from the raw JSON response. | ||
| projectRootUri = vm.json?['rootUri'] as String?; | ||
| } catch (_) { | ||
| // If getVM() fails, proceed without project-root scoping. | ||
| } | ||
| } | ||
|
|
||
| for (final isolateState in _isolateStates.values) { | ||
| final isolate = await isolateState.isolate; | ||
| if (service != _service) return null; | ||
|
|
||
| final rootLibraryUri = isolate?.rootLib?.uri; | ||
| if (rootLibraryUri == null) continue; | ||
|
|
||
| if (_isDartTestRunnerRootLibrary(rootLibraryUri)) continue; | ||
|
|
||
| if (_isLikelyUserTestRootLibrary( | ||
| rootLibraryUri, | ||
| projectRootUri: projectRootUri, | ||
| )) { | ||
| return isolateState.isolateRef; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| /// Returns true if [uri] looks like the root library of a dart:test runner | ||
| /// isolate (as opposed to the user's test suite isolate). | ||
| /// | ||
| /// Note: 'dart_test.kernel', 'package:test_core/', and 'package:test_api/' | ||
| /// are implementation details of package:test that DevTools depends on. | ||
| /// Changing these in package:test will break this logic. | ||
| bool _isDartTestRunnerRootLibrary(String uri) { | ||
| return uri.contains('dart_test.kernel') || | ||
rishika0212 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| uri.startsWith('package:test_core/') || | ||
| uri.startsWith('package:test_api/'); | ||
| } | ||
|
|
||
| /// Returns true if [uri] looks like the root library of a user test isolate. | ||
| /// | ||
| /// When [projectRootUri] is provided, `file://` URIs must be under it to | ||
| /// avoid false positives from other packages in the workspace that happen to | ||
| /// contain '/test/' or end with '_test.dart'. | ||
| bool _isLikelyUserTestRootLibrary(String uri, {String? projectRootUri}) { | ||
| if (uri.startsWith('dart:')) return false; | ||
|
|
||
| // Scope file:// URIs to the current project to avoid matching test files | ||
| // from other packages in a workspace. | ||
| if (projectRootUri != null && | ||
| uri.startsWith('file:') && | ||
| !uri.startsWith(projectRootUri)) { | ||
| return false; | ||
| } | ||
|
|
||
| return uri.endsWith('_test.dart') || | ||
| uri.contains('/test/') || | ||
| uri.contains('\\test\\'); | ||
| } | ||
|
|
||
| void _setSelectedIsolate(IsolateRef? ref) { | ||
| _selectedIsolate.value = ref; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.