Only cache pub root directories set by the user (#6897)
diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_screen.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_screen.dart index 14f0def..8564620 100644 --- a/packages/devtools_app/lib/src/screens/inspector/inspector_screen.dart +++ b/packages/devtools_app/lib/src/screens/inspector/inspector_screen.dart
@@ -126,7 +126,7 @@ searchPreventClose = false; } }); - addAutoDisposeListener(preferences.inspector.customPubRootDirectories, () { + addAutoDisposeListener(preferences.inspector.pubRootDirectories, () { if (serviceConnection.serviceManager.hasConnection && controller.firstInspectorTreeLoadCompleted) { _refreshInspector(); @@ -579,16 +579,19 @@ child: EditableList( gaScreen: gac.inspector, gaRefreshSelection: gac.refreshPubRoots, - entries: preferences.inspector.customPubRootDirectories, + entries: preferences.inspector.pubRootDirectories, textFieldLabel: 'Enter a new package directory', - isRefreshing: - preferences.inspector.isRefreshingCustomPubRootDirectories, - onEntryAdded: (p0) => - unawaited(preferences.inspector.addPubRootDirectories([p0])), + isRefreshing: preferences.inspector.isRefreshingPubRootDirectories, + onEntryAdded: (p0) => unawaited( + preferences.inspector.addPubRootDirectories( + [p0], + shouldCache: true, + ), + ), onEntryRemoved: (p0) => unawaited(preferences.inspector.removePubRootDirectories([p0])), onRefreshTriggered: () => - unawaited(preferences.inspector.loadCustomPubRootDirectories()), + unawaited(preferences.inspector.loadPubRootDirectories()), ), ); },
diff --git a/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart b/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart index 52b8cfd..1c328b0 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart
@@ -21,6 +21,7 @@ import '../console/primitives/simple_items.dart'; import '../globals.dart'; +import '../utils.dart'; import 'diagnostics_node.dart'; import 'generic_instance_reference.dart'; import 'object_group_api.dart'; @@ -29,8 +30,6 @@ const _inspectorLibraryUri = 'package:flutter/src/widgets/widget_inspector.dart'; -const _google3PathSegment = 'google3'; -const _thirdPartyPathSegment = 'third_party'; abstract class InspectorServiceBase extends DisposableController with AutoDisposeControllerMixin { @@ -355,8 +354,8 @@ final libIndex = parts.lastIndexOf('lib'); final path = libIndex > 0 ? parts.sublist(0, libIndex) : parts; // Special case handling of bazel packages. - if (_isGoogle3Path(path)) { - var packageParts = _stripGoogle3(path); + if (isGoogle3Path(path)) { + var packageParts = stripGoogle3(path); // A well formed third_party dart package should be in a directory of // the form // third_party/dart/packageName (package:packageName) @@ -479,88 +478,6 @@ return response as Map<String, dynamic>; } - /// As we aren't running from an IDE, we don't know exactly what the pub root - /// directories are for the current project so we make a best guess if needed - /// based on the root directory of the first non artificial widget in the - /// tree. - Future<List<String>> inferPubRootDirectoryIfNeeded() async { - final group = createObjectGroup('temp'); - List<String> directories = await group.getPubRootDirectories(); - if (directories.isEmpty) { - final directory = await inferPubRootDirectoryIfNeededHelper(); - if (directory != null) { - directories = [directory]; - } - } - - await _onRootDirectoriesChanged(directories); - return directories; - } - - Future<String?> inferPubRootDirectoryIfNeededHelper() async { - final path = await serviceConnection.rootLibraryForMainIsolate(); - if (path == null) { - return null; - } - // TODO(jacobr): Once https://github.com/flutter/flutter/issues/26615 is - // fixed we will be able to use package: paths. Temporarily all tools - // tracking widget locations will need to support both path formats. - // TODO(jacobr): use the list of loaded scripts to determine the appropriate - // package root directory given that the root script of this project is in - // this directory rather than guessing based on url structure. - final parts = path.split('/'); - String? pubRootDirectory; - // For google3, we grab the top-level directory in the google3 directory - // (e.g. /education), or the top-level directory in third_party (e.g. - // /third_party/dart): - if (_isGoogle3Path(parts)) { - pubRootDirectory = _pubRootDirectoryForGoogle3(parts); - } else { - final parts = path.split('/'); - - for (int i = parts.length - 1; i >= 0; i--) { - final part = parts[i]; - if (part == 'lib' || part == 'web') { - pubRootDirectory = parts.sublist(0, i).join('/'); - break; - } - - if (part == 'packages') { - pubRootDirectory = parts.sublist(0, i + 1).join('/'); - break; - } - } - } - pubRootDirectory ??= (parts..removeLast()).join('/'); - - await _addPubRootDirectories([pubRootDirectory]); - return pubRootDirectory; - } - - bool _isGoogle3Path(List<String> pathParts) => - pathParts.contains(_google3PathSegment); - - List<String> _stripGoogle3(List<String> pathParts) { - final google3Index = pathParts.lastIndexOf(_google3PathSegment); - if (google3Index != -1 && google3Index + 1 < pathParts.length) { - return pathParts.sublist(google3Index + 1); - } - return pathParts; - } - - String? _pubRootDirectoryForGoogle3(List<String> pathParts) { - final strippedParts = _stripGoogle3(pathParts); - if (strippedParts.isEmpty) return null; - - final topLevelDirectory = strippedParts.first; - if (topLevelDirectory == _thirdPartyPathSegment && - strippedParts.length >= 2) { - return '/${strippedParts.sublist(0, 2).join('/')}'; - } else { - return '/${strippedParts.first}'; - } - } - RemoteDiagnosticsNode? _currentSelection; InspectorObjectGroupManager get _selectionGroups {
diff --git a/packages/devtools_app/lib/src/shared/preferences.dart b/packages/devtools_app/lib/src/shared/preferences.dart index c7d718c..45acca3 100644 --- a/packages/devtools_app/lib/src/shared/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences.dart
@@ -17,6 +17,9 @@ import 'constants.dart'; import 'diagnostics/inspector_service.dart'; import 'globals.dart'; +import 'utils.dart'; + +const _thirdPartyPathSegment = 'third_party'; /// A controller for global application preferences. class PreferencesController extends DisposableController @@ -142,16 +145,15 @@ class InspectorPreferencesController extends DisposableController with AutoDisposeControllerMixin { ValueListenable<bool> get hoverEvalModeEnabled => _hoverEvalMode; - ListValueNotifier<String> get customPubRootDirectories => - _customPubRootDirectories; - ValueListenable<bool> get isRefreshingCustomPubRootDirectories => - _customPubRootDirectoriesAreBusy; + ListValueNotifier<String> get pubRootDirectories => _pubRootDirectories; + ValueListenable<bool> get isRefreshingPubRootDirectories => + _pubRootDirectoriesAreBusy; InspectorServiceBase? get _inspectorService => serviceConnection.inspectorService; final _hoverEvalMode = ValueNotifier<bool>(false); - final _customPubRootDirectories = ListValueNotifier<String>([]); - final _customPubRootDirectoriesAreBusy = ValueNotifier<bool>(false); + final _pubRootDirectories = ListValueNotifier<String>([]); + final _pubRootDirectoriesAreBusy = ValueNotifier<bool>(false); final _busyCounter = ValueNotifier<int>(0); static const _hoverEvalModeStorageId = 'inspector.hoverEvalMode'; static const _customPubRootDirectoriesStoragePrefix = @@ -174,7 +176,7 @@ Future<void> init() async { await _initHoverEvalMode(); // TODO(jacobr): consider initializing this first as it is not blocking. - _initCustomPubRootDirectories(); + _initPubRootDirectories(); } Future<void> _initHoverEvalMode() async { @@ -197,19 +199,19 @@ setHoverEvalMode(hoverEvalModeEnabledValue == 'true'); } - void _initCustomPubRootDirectories() { + void _initPubRootDirectories() { addAutoDisposeListener( serviceConnection.serviceManager.connectedState, () async { if (serviceConnection.serviceManager.connectedState.value.connected) { - await _handleConnectionToNewService(); + await handleConnectionToNewService(); } else { _handleConnectionClosed(); } }, ); addAutoDisposeListener(_busyCounter, () { - _customPubRootDirectoriesAreBusy.value = _busyCounter.value != 0; + _pubRootDirectoriesAreBusy.value = _busyCounter.value != 0; }); addAutoDisposeListener( serviceConnection.serviceManager.isolateManager.mainIsolate, @@ -223,13 +225,13 @@ if (debuggerState?.isPaused.value == false) { // the isolate is already unpaused, we can try to load // the directories - unawaited(preferences.inspector.loadCustomPubRootDirectories()); + unawaited(preferences.inspector.loadPubRootDirectories()); } else { late Function() pausedListener; pausedListener = () { if (debuggerState?.isPaused.value == false) { - unawaited(preferences.inspector.loadCustomPubRootDirectories()); + unawaited(preferences.inspector.loadPubRootDirectories()); debuggerState?.isPaused.removeListener(pausedListener); } @@ -245,41 +247,125 @@ void _handleConnectionClosed() { _mainScriptDir = null; - _customPubRootDirectories.clear(); + _pubRootDirectories.clear(); } - Future<void> _handleConnectionToNewService() async { + @visibleForTesting + Future<void> handleConnectionToNewService() async { await _updateMainScriptRef(); await _updateHoverEvalMode(); + await loadPubRootDirectories(); + } - final localInspectorService = _inspectorService; - if (localInspectorService is InspectorService) { - _customPubRootDirectories.clear(); - await loadCustomPubRootDirectories(); + Future<void> loadPubRootDirectories() async { + await _pubRootDirectoryBusyTracker(() async { + await addPubRootDirectories(await _determinePubRootDirectories()); + await _refreshPubRootDirectoriesFromService(); + }); + } - if (_customPubRootDirectories.value.isEmpty) { - // If there are no pub root directories set on the first connection - // then try inferring them. - await _customPubRootDirectoryBusyTracker(() async { - await localInspectorService.inferPubRootDirectoryIfNeeded(); - await loadCustomPubRootDirectories(); - }); + Future<List<String>> _determinePubRootDirectories() async { + final cachedDirectories = await readCachedPubRootDirectories(); + final inferredDirectory = await _inferPubRootDirectory(); + + if (inferredDirectory == null) return cachedDirectories; + return {inferredDirectory, ...cachedDirectories}.toList(); + } + + @visibleForTesting + Future<List<String>> readCachedPubRootDirectories() async { + final cachedDirectoriesJson = + await storage.getValue(_customPubRootStorageId()); + if (cachedDirectoriesJson == null) return <String>[]; + + return List<String>.from( + jsonDecode(cachedDirectoriesJson), + ); + } + + /// As we aren't running from an IDE, we don't know exactly what the pub root + /// directories are for the current project so we make a best guess based on + /// the root library for the main isolate. + Future<String?> _inferPubRootDirectory() async { + final path = await serviceConnection.rootLibraryForMainIsolate(); + if (path == null) { + return null; + } + // TODO(jacobr): Once https://github.com/flutter/flutter/issues/26615 is + // fixed we will be able to use package: paths. Temporarily all tools + // tracking widget locations will need to support both path formats. + // TODO(jacobr): use the list of loaded scripts to determine the appropriate + // package root directory given that the root script of this project is in + // this directory rather than guessing based on url structure. + final parts = path.split('/'); + String? pubRootDirectory; + // For google3, we grab the top-level directory in the google3 directory + // (e.g. /education), or the top-level directory in third_party (e.g. + // /third_party/dart): + if (isGoogle3Path(parts)) { + pubRootDirectory = _pubRootDirectoryForGoogle3(parts); + } else { + final parts = path.split('/'); + + for (int i = parts.length - 1; i >= 0; i--) { + final part = parts[i]; + if (part == 'lib' || part == 'web') { + pubRootDirectory = parts.sublist(0, i).join('/'); + break; + } + + if (part == 'packages') { + pubRootDirectory = parts.sublist(0, i + 1).join('/'); + break; + } } } + pubRootDirectory ??= (parts..removeLast()).join('/'); + return pubRootDirectory; + } + + String? _pubRootDirectoryForGoogle3(List<String> pathParts) { + final strippedParts = stripGoogle3(pathParts); + if (strippedParts.isEmpty) return null; + + final topLevelDirectory = strippedParts.first; + if (topLevelDirectory == _thirdPartyPathSegment && + strippedParts.length >= 2) { + return '/${strippedParts.sublist(0, 2).join('/')}'; + } else { + return '/${strippedParts.first}'; + } } - void _persistCustomPubRootDirectoriesToStorage() { - unawaited( - storage.setValue( - _customPubRootStorageId(), - jsonEncode(_customPubRootDirectories.value), - ), + Future<void> _cachePubRootDirectories( + List<String> pubRootDirectories, + ) async { + final cachedDirectories = await readCachedPubRootDirectories(); + await storage.setValue( + _customPubRootStorageId(), + jsonEncode([ + ...cachedDirectories, + ...pubRootDirectories, + ]), + ); + } + + Future<void> _uncachePubRootDirectories( + List<String> pubRootDirectories, + ) async { + final directoriesToCache = (await readCachedPubRootDirectories()) + .where((dir) => !pubRootDirectories.contains(dir)) + .toList(); + await storage.setValue( + _customPubRootStorageId(), + jsonEncode(directoriesToCache), ); } Future<void> addPubRootDirectories( - List<String> pubRootDirectories, - ) async { + List<String> pubRootDirectories, { + bool shouldCache = false, + }) async { // TODO(https://github.com/flutter/devtools/issues/4380): // Add validation to EditableList Input. // Directories of just / will break the inspector tree local package checks. @@ -288,11 +374,14 @@ ); if (!serviceConnection.serviceManager.hasConnection) return; - await _customPubRootDirectoryBusyTracker(() async { + await _pubRootDirectoryBusyTracker(() async { final localInspectorService = _inspectorService; if (localInspectorService is! InspectorService) return; await localInspectorService.addPubRootDirectories(pubRootDirectories); + if (shouldCache) { + await _cachePubRootDirectories(pubRootDirectories); + } await _refreshPubRootDirectoriesFromService(); }); } @@ -301,17 +390,18 @@ List<String> pubRootDirectories, ) async { if (!serviceConnection.serviceManager.hasConnection) return; - await _customPubRootDirectoryBusyTracker(() async { + await _pubRootDirectoryBusyTracker(() async { final localInspectorService = _inspectorService; if (localInspectorService is! InspectorService) return; await localInspectorService.removePubRootDirectories(pubRootDirectories); + await _uncachePubRootDirectories(pubRootDirectories); await _refreshPubRootDirectoriesFromService(); }); } Future<void> _refreshPubRootDirectoriesFromService() async { - await _customPubRootDirectoryBusyTracker(() async { + await _pubRootDirectoryBusyTracker(() async { final localInspectorService = _inspectorService; if (localInspectorService is! InspectorService) return; @@ -319,14 +409,13 @@ await localInspectorService.getPubRootDirectories(); if (freshPubRootDirectories != null) { final newSet = Set<String>.of(freshPubRootDirectories); - final oldSet = Set<String>.of(_customPubRootDirectories.value); + final oldSet = Set<String>.of(_pubRootDirectories.value); final directoriesToAdd = newSet.difference(oldSet); final directoriesToRemove = oldSet.difference(newSet); - _customPubRootDirectories.removeAll(directoriesToRemove); - _customPubRootDirectories.addAll(directoriesToAdd); - - _persistCustomPubRootDirectoriesToStorage(); + _pubRootDirectories + ..removeAll(directoriesToRemove) + ..addAll(directoriesToAdd); } }); } @@ -337,25 +426,7 @@ return '${_customPubRootDirectoriesStoragePrefix}_$packageId'; } - Future<void> loadCustomPubRootDirectories() async { - if (!serviceConnection.serviceManager.hasConnection) return; - - await _customPubRootDirectoryBusyTracker(() async { - final storedCustomPubRootDirectories = - await storage.getValue(_customPubRootStorageId()); - - if (storedCustomPubRootDirectories != null) { - await addPubRootDirectories( - List<String>.from( - jsonDecode(storedCustomPubRootDirectories), - ), - ); - } - await _refreshPubRootDirectoriesFromService(); - }); - } - - Future<void> _customPubRootDirectoryBusyTracker( + Future<void> _pubRootDirectoryBusyTracker( Future<void> Function() callback, ) async { try {
diff --git a/packages/devtools_app/lib/src/shared/utils.dart b/packages/devtools_app/lib/src/shared/utils.dart index 29cd8d3..853b864 100644 --- a/packages/devtools_app/lib/src/shared/utils.dart +++ b/packages/devtools_app/lib/src/shared/utils.dart
@@ -214,3 +214,16 @@ final queryParameters = loadQueryParams(); return queryParameters[key]; } + +const _google3PathSegment = 'google3'; + +bool isGoogle3Path(List<String> pathParts) => + pathParts.contains(_google3PathSegment); + +List<String> stripGoogle3(List<String> pathParts) { + final google3Index = pathParts.lastIndexOf(_google3PathSegment); + if (google3Index != -1 && google3Index + 1 < pathParts.length) { + return pathParts.sublist(google3Index + 1); + } + return pathParts; +}
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index eb4a204..59ced0c 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -14,7 +14,7 @@ ## Inspector updates -TODO: Remove this section if there are not any general updates. +* Only cache pub root directories added by the user - [6897](https://github.com/flutter/devtools/pull/6897) ## Performance updates
diff --git a/packages/devtools_app/test/inspector/inspector_integration_test.dart b/packages/devtools_app/test/inspector/inspector_integration_test.dart index eafed2f..bfea308 100644 --- a/packages/devtools_app/test/inspector/inspector_integration_test.dart +++ b/packages/devtools_app/test/inspector/inspector_integration_test.dart
@@ -37,10 +37,6 @@ isAlive: null, ); } - - if (service is InspectorService) { - await service.inferPubRootDirectoryIfNeeded(); - } }; setUp(() async {
diff --git a/packages/devtools_app/test/inspector/inspector_service_test.dart b/packages/devtools_app/test/inspector/inspector_service_test.dart index 0579a9e..7faccfd 100644 --- a/packages/devtools_app/test/inspector/inspector_service_test.dart +++ b/packages/devtools_app/test/inspector/inspector_service_test.dart
@@ -17,10 +17,6 @@ import '../test_infra/flutter_test_environment.dart'; import '../test_infra/matchers/matchers.dart'; -// TODO(elliette): Add testing that project directories can be inferred from -// google3-paths. This will require mocking the main isolate so that we can -// change the root library during testing instead of using the -// LiveTestWidgetsFlutterBinding. void main() { initializeLiveTestWidgetsFlutterBindingWithAssets(); @@ -35,9 +31,6 @@ setGlobal(IdeTheme, IdeTheme()); inspectorService = InspectorService(); - if (env.runConfig.trackWidgetCreation) { - await inspectorService!.inferPubRootDirectoryIfNeeded(); - } }; env.beforeEveryTearDown = () async { @@ -86,24 +79,6 @@ await env.tearDownEnvironment(force: true); }); - // TODO(elliette): Figure out why this didn't catch - // https://github.com/flutter/devtools/issues/6841 and fix so that it - // catches future regressions. - test('can be inferred', () async { - await env.setupEnvironment(); - final inspectorServiceLocal = inspectorService!; - - final group = inspectorServiceLocal.createObjectGroup('test-group'); - // These tests are moot if widget creation is not tracked. - expect(await inspectorServiceLocal.isWidgetCreationTracked(), isTrue); - await inspectorServiceLocal.addPubRootDirectories([]); - final List<String> rootDirectories = - await inspectorServiceLocal.inferPubRootDirectoryIfNeeded(); - expect(rootDirectories.length, 1); - expect(rootDirectories.first, endsWith('/fixtures/flutter_app')); - await group.dispose(); - }); - test('can be added and removed', () async { await env.setupEnvironment(); final inspectorServiceLocal = inspectorService!; @@ -143,16 +118,17 @@ () async { await env.setupEnvironment(); final inspectorServiceLocal = inspectorService!; - final group = inspectorServiceLocal.createObjectGroup('test-group'); // These tests are moot if widget creation is not tracked. expect( await inspectorServiceLocal.isWidgetCreationTracked(), isTrue, ); - await inspectorServiceLocal.addPubRootDirectories([]); + final rootLibrary = + await serviceConnection.rootLibraryForMainIsolate(); + await inspectorServiceLocal.addPubRootDirectories([rootLibrary!]); final List<String> rootDirectories = - await inspectorServiceLocal.inferPubRootDirectoryIfNeeded(); + await inspectorServiceLocal.getPubRootDirectories() ?? []; expect(rootDirectories.length, 1); expect(rootDirectories.first, endsWith('/fixtures/flutter_app')); final originalRootDirectories = rootDirectories.toList(); @@ -269,8 +245,7 @@ expect(await inspectorServiceLocal.isWidgetCreationTracked(), isTrue); await inspectorServiceLocal.addPubRootDirectories([]); final originalRootDirectories = - (await inspectorServiceLocal.inferPubRootDirectoryIfNeeded()) - .toList(); + await inspectorServiceLocal.getPubRootDirectories(); try { await inspectorServiceLocal.addPubRootDirectories( ['/usr/me/clients/google3/foo/bar/baz/lib/src/bla'], @@ -379,7 +354,7 @@ } finally { // Restore. await inspectorServiceLocal - .addPubRootDirectories(originalRootDirectories); + .addPubRootDirectories(originalRootDirectories ?? []); await group.dispose(); }
diff --git a/packages/devtools_app/test/shared/preferences_controller_test.dart b/packages/devtools_app/test/shared/preferences_controller_test.dart index 6e4d211..b7dc518 100644 --- a/packages/devtools_app/test/shared/preferences_controller_test.dart +++ b/packages/devtools_app/test/shared/preferences_controller_test.dart
@@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; + import 'package:devtools_app/devtools_app.dart'; import 'package:devtools_app_shared/utils.dart'; import 'package:devtools_test/devtools_test.dart'; @@ -75,16 +77,26 @@ }); }); + // TODO(https://github.com/flutter/devtools/issues/4342): Add more tests. group('$InspectorPreferencesController', () { + late InspectorPreferencesController controller; + late FlutterTestStorage storage; + + void updateMainIsolateRootLibrary(String? rootLibrary) { + setGlobal( + ServiceConnectionManager, + FakeServiceConnectionManager( + rootLibrary: rootLibrary, + ), + ); + } + + setUp(() { + setGlobal(Storage, storage = FlutterTestStorage()); + controller = InspectorPreferencesController(); + }); + group('hoverEvalMode', () { - late InspectorPreferencesController controller; - late FlutterTestStorage storage; - - setUp(() { - setGlobal(Storage, storage = FlutterTestStorage()); - controller = InspectorPreferencesController(); - }); - test('default value equals inspector service default value', () async { await controller.init(); expect( @@ -113,8 +125,180 @@ newHoverModeValue.toString(), ); }); - // TODO(https://github.com/flutter/devtools/issues/4342): make inspector - // preferences testable, then test it + }); + + group( + 'infers the pub root directory based on the main isolate\'s root library', + () { + final rootLibToExpectedPubRoot = { + 'test_dir/fake_app/lib/main.dart': 'test_dir/fake_app', + 'my_user/google3/dart_apps/test_app/lib/main.dart': '/dart_apps', + 'my_user/google3/third_party/dart/dart_apps/test_app/lib/main.dart': + '/third_party/dart', + }; + + for (final MapEntry( + key: rootLib, + value: expectedPubRoot, + ) in rootLibToExpectedPubRoot.entries) { + test( + '$rootLib -> $expectedPubRoot', + () async { + updateMainIsolateRootLibrary(rootLib); + await controller.handleConnectionToNewService(); + final directories = controller.pubRootDirectories.value; + + expect(directories, equals([expectedPubRoot])); + }, + ); + } + }, + ); + + group('Caching custom pub root directories', () { + final customPubRootDirectories = [ + 'test_dir/fake_app/custom_dir1', + 'test_dir/fake_app/custom_dir2', + ]; + + setUp(() async { + updateMainIsolateRootLibrary('test_dir/fake_app/lib/main.dart'); + await controller.handleConnectionToNewService(); + await controller.addPubRootDirectories( + customPubRootDirectories, + shouldCache: true, + ); + }); + + test( + 'fetches custom pub root directories from the local cache', + () { + final directories = controller.pubRootDirectories.value; + + expect( + directories, + containsAll(customPubRootDirectories), + ); + }, + ); + + test( + 'custom pub root directories are cached across multiple connections', + () async { + var directories = controller.pubRootDirectories.value; + var cachedDirectories = + await controller.readCachedPubRootDirectories(); + + expect( + directories, + containsAll(customPubRootDirectories), + ); + expect( + cachedDirectories, + containsAll(customPubRootDirectories), + ); + + await controller.handleConnectionToNewService(); + directories = controller.pubRootDirectories.value; + cachedDirectories = await controller.readCachedPubRootDirectories(); + + expect( + directories, + containsAll(customPubRootDirectories), + ); + expect(cachedDirectories, containsAll(customPubRootDirectories)); + }, + ); + + test( + 'adding more directories to cache doesn\'t overwrite pre-existing values', + () async { + await controller.addPubRootDirectories( + ['test_dir/fake_app/custom_dir3'], + shouldCache: true, + ); + + final cachedDirectories = + await controller.readCachedPubRootDirectories(); + + expect( + cachedDirectories, + containsAll([ + ...customPubRootDirectories, + 'test_dir/fake_app/custom_dir3', + ]), + ); + }, + ); + + test( + 'removing directories from cache removes the correct values', + () async { + const notRemoved = 'test_dir/fake_app/custom_dir1'; + const removed = 'test_dir/fake_app/custom_dir2'; + var cachedDirectories = + await controller.readCachedPubRootDirectories(); + + expect(cachedDirectories, containsAll([notRemoved, removed])); + + await controller.removePubRootDirectories([removed]); + cachedDirectories = await controller.readCachedPubRootDirectories(); + + expect( + cachedDirectories, + isNot(contains(removed)), + ); + expect( + cachedDirectories, + contains(notRemoved), + ); + }, + ); + + test( + 'directories includes inferred directory as well', + () { + final directories = controller.pubRootDirectories.value; + + expect( + directories, + contains('test_dir/fake_app'), + ); + }, + ); + + test( + 'does not save inferred directory to local cache', + () async { + final cachedDirectoriesJson = await storage + .getValue('inspector.customPubRootDirectories_myPackage'); + final cachedDirectories = List<String>.from( + jsonDecode(cachedDirectoriesJson!), + ); + + expect(cachedDirectories, isNot(contains('test_dir/fake_app'))); + }, + ); + + test( + 'directories added with "no caching" specified are not cached', + () async { + await controller.addPubRootDirectories( + ['test_dir/fake_app/do_not_cache_dir'], + ); + + final cachedDirectoriesJson = await storage + .getValue('inspector.customPubRootDirectories_myPackage'); + final cachedDirectories = List<String>.from( + jsonDecode(cachedDirectoriesJson!), + ); + + expect( + cachedDirectories, + isNot(contains('test_dir/fake_app/do_not_cache_dir')), + ); + }, + ); }); });
diff --git a/packages/devtools_test/lib/src/mocks/fake_isolate_manager.dart b/packages/devtools_test/lib/src/mocks/fake_isolate_manager.dart index 65654c4..10644ed 100644 --- a/packages/devtools_test/lib/src/mocks/fake_isolate_manager.dart +++ b/packages/devtools_test/lib/src/mocks/fake_isolate_manager.dart
@@ -14,6 +14,12 @@ import 'generated.mocks.dart'; base class FakeIsolateManager extends Fake with TestIsolateManager { + FakeIsolateManager({ + this.rootLibrary = 'package:my_app/main.dart', + }); + + final String? rootLibrary; + @override ValueListenable<IsolateRef?> get selectedIsolate => _selectedIsolate; final _selectedIsolate = ValueNotifier( @@ -51,7 +57,7 @@ IsolateState isolateState(IsolateRef? isolate) { final state = MockIsolateState(); final mockIsolate = MockIsolate(); - final rootLib = LibraryRef(id: '0', uri: 'package:my_app/main.dart'); + final rootLib = LibraryRef(id: '0', uri: rootLibrary); when(mockIsolate.libraries).thenReturn( [ rootLib,
diff --git a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart index 8493b47..0e7855f 100644 --- a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart +++ b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart
@@ -29,6 +29,7 @@ bool hasService = true, List<String> availableServices = const [], List<String> availableLibraries = const [], + String? rootLibrary, }) { _serviceManager = FakeServiceManager( service: service, @@ -37,6 +38,7 @@ availableLibraries: availableLibraries, availableServices: availableServices, onVmServiceOpened: resolvedUriManager.vmServiceOpened, + rootLibrary: rootLibrary, ); for (var screenId in screenIds) { when(errorBadgeManager.erroredItemsForPage(screenId)).thenReturn( @@ -92,6 +94,13 @@ }) { return Future.value(); } + + @override + Future<String?> rootLibraryForMainIsolate() { + final fakeIsolateManager = + _serviceManager.isolateManager as FakeIsolateManager; + return Future.value(fakeIsolateManager.rootLibrary); + } } // ignore: subtype_of_sealed_class, fake for testing. @@ -105,8 +114,10 @@ this.availableLibraries = const [], this.onVmServiceOpened, Map<String, Response>? serviceExtensionResponses, - }) : serviceExtensionResponses = - serviceExtensionResponses ?? _defaultServiceExtensionResponses { + String? rootLibrary, + }) : serviceExtensionResponses = + serviceExtensionResponses ?? _defaultServiceExtensionResponses, + _isolateManager = FakeIsolateManager(rootLibrary: rootLibrary) { this.service = service ?? createFakeService(); mockConnectedApp( connectedApp!, @@ -153,6 +164,8 @@ final Map<String, Response> serviceExtensionResponses; + final IsolateManager _isolateManager; + static final _defaultServiceExtensionResponses = <String, Response>{ isImpellerEnabled: Response.parse({'enabled': false})!, }; @@ -177,7 +190,7 @@ bool connectedAppInitialized; @override - final IsolateManager isolateManager = FakeIsolateManager(); + IsolateManager get isolateManager => _isolateManager; @override final FakeServiceExtensionManager serviceExtensionManager =
diff --git a/packages/devtools_test/lib/src/mocks/mocks.dart b/packages/devtools_test/lib/src/mocks/mocks.dart index 36c373e..6b6bcf0 100644 --- a/packages/devtools_test/lib/src/mocks/mocks.dart +++ b/packages/devtools_test/lib/src/mocks/mocks.dart
@@ -28,11 +28,6 @@ } @override - Future<List<String>> inferPubRootDirectoryIfNeeded() async { - return ['/some/directory']; - } - - @override Future<List<String>?> getPubRootDirectories() { return Future.value(pubRootDirectories.toList()); }