blob: b6ff0f1ba25839ee358436fa781ac67f526aac27 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. 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:convert';
import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_shared/devtools_shared.dart';
import 'package:flutter/foundation.dart';
import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';
import 'generated_mocks_factories.dart';
class FakeIsolateManager extends Fake implements IsolateManager {
@override
ValueListenable<IsolateRef?> get selectedIsolate => _selectedIsolate;
final _selectedIsolate = ValueNotifier(
IsolateRef.parse({
'id': 'fake_isolate_id',
'name': 'selected-isolate',
}),
);
@override
ValueListenable<IsolateRef?> get mainIsolate => _mainIsolate;
final _mainIsolate =
ValueNotifier(IsolateRef.parse({'id': 'fake_main_isolate_id'}));
@override
ValueNotifier<List<IsolateRef>> get isolates {
final value = _selectedIsolate.value;
_isolates ??= ValueNotifier([if (value != null) value]);
return _isolates!;
}
@override
IsolateState? get mainIsolateDebuggerState {
return MockIsolateState();
}
ValueNotifier<List<IsolateRef>>? _isolates;
@override
IsolateState isolateDebuggerState(IsolateRef? isolate) {
final state = MockIsolateState();
final mockIsolate = MockIsolate();
when(mockIsolate.libraries).thenReturn([]);
when(state.isolateNow).thenReturn(mockIsolate);
return state;
}
}
class FakeInspectorService extends Fake implements InspectorService {
final pubRootDirectories = <String>{};
@override
ObjectGroup createObjectGroup(String debugName) {
return ObjectGroup(debugName, this);
}
@override
Future<bool> isWidgetTreeReady() async {
return false;
}
@override
Future<List<String>> inferPubRootDirectoryIfNeeded() async {
return ['/some/directory'];
}
@override
Future<List<String>?> getPubRootDirectories() {
return Future.value(pubRootDirectories.toList());
}
@override
Future<void> addPubRootDirectories(List<String> rootDirectories) {
pubRootDirectories.addAll(rootDirectories);
return Future<void>.value();
}
@override
Future<void> removePubRootDirectories(List<String> rootDirectories) {
pubRootDirectories.removeAll(rootDirectories);
return Future<void>.value();
}
@override
bool get useDaemonApi => true;
@override
final Set<InspectorServiceClient> clients = {};
@override
void addClient(InspectorServiceClient client) {
clients.add(client);
}
@override
void removeClient(InspectorServiceClient client) {
clients.remove(client);
}
}
class MockInspectorTreeController extends Mock
implements InspectorTreeController {}
class TestInspectorController extends Fake implements InspectorController {
InspectorService service = FakeInspectorService();
@override
ValueListenable<InspectorTreeNode?> get selectedNode => _selectedNode;
final ValueNotifier<InspectorTreeNode?> _selectedNode = ValueNotifier(null);
@override
void setSelectedNode(InspectorTreeNode? newSelection) {
_selectedNode.value = newSelection;
}
@override
InspectorService get inspectorService => service;
}
class FakeVM extends Fake implements VM {
FakeVM();
@override
Map<String, dynamic>? json = {
'_FAKE_VM': true,
'_currentRSS': 0,
};
}
class MockIsolateState extends Mock implements IsolateState {
@override
ValueListenable<bool> get isPaused => ValueNotifier<bool>(false);
}
class MockIsolate extends Mock implements Isolate {}
class MockObj extends Mock implements Obj {}
class MockCpuSamples extends Mock implements CpuSamples {}
// TODO(kenz): make it easier to mock a connected app by adding a constructor
// that will override the public getters on the class (e.g. isFlutterAppNow,
// isProfileBuildNow, etc.). Do this after devtools_app is migrated to null
// safety so that we can use null-safety here.
// TODO(polinach): delete this class.
// See https://github.com/flutter/devtools/issues/4029.
class MockConnectedAppLegacy extends Mock implements ConnectedApp {}
class FakeConnectedApp extends Mock implements ConnectedApp {}
class MockBannerMessagesController extends Mock
implements BannerMessagesController {}
class MockLoggingController extends Mock
with SearchControllerMixin<LogData>, FilterControllerMixin<LogData>
implements LoggingController {
@override
final selectedLog = ValueNotifier<LogData?>(null);
@override
List<LogData> matchesForSearch(
String search, {
bool searchPreviousMatches = false,
}) =>
[];
@override
List<LogData> data = <LogData>[];
}
class MockMemoryController extends Mock implements MemoryController {}
class MockFlutterMemoryController extends Mock implements MemoryController {}
class MockProfilerScreenController extends Mock
implements ProfilerScreenController {}
class MockStorage extends Mock implements Storage {}
class TestCodeViewController extends CodeViewController {
@override
ProgramExplorerController get programExplorerController =>
_explorerController;
final _explorerController = createMockProgramExplorerControllerWithDefaults();
}
// TODO(polinach): delete this class.
// See https://github.com/flutter/devtools/issues/4029.
class MockDebuggerControllerLegacy extends Mock implements DebuggerController {
MockDebuggerControllerLegacy();
factory MockDebuggerControllerLegacy.withDefaults() {
final debuggerController = MockDebuggerControllerLegacy();
when(debuggerController.resuming).thenReturn(ValueNotifier(false));
when(debuggerController.isSystemIsolate).thenReturn(false);
when(debuggerController.selectedBreakpoint).thenReturn(ValueNotifier(null));
when(debuggerController.stackFramesWithLocation)
.thenReturn(ValueNotifier([]));
when(debuggerController.selectedStackFrame).thenReturn(ValueNotifier(null));
when(debuggerController.exceptionPauseMode)
.thenReturn(ValueNotifier('Unhandled'));
return debuggerController;
}
}
class MockScriptManagerLegacy extends Mock implements ScriptManager {}
// TODO(polinach): delete this class.
// See https://github.com/flutter/devtools/issues/4029.
class MockProgramExplorerControllerLegacy extends Mock
implements ProgramExplorerController {
MockProgramExplorerControllerLegacy();
factory MockProgramExplorerControllerLegacy.withDefaults() {
final controller = MockProgramExplorerControllerLegacy();
when(controller.initialized).thenReturn(ValueNotifier(true));
when(controller.rootObjectNodes).thenReturn(ValueNotifier([]));
when(controller.outlineNodes).thenReturn(ValueNotifier([]));
when(controller.outlineSelection).thenReturn(ValueNotifier(null));
when(controller.isLoadingOutline).thenReturn(ValueNotifier(false));
return controller;
}
}
class MockVM extends Mock implements VM {}
void mockWebVm(VM vm) {
when(vm.targetCPU).thenReturn('Web');
when(vm.architectureBits).thenReturn(-1);
when(vm.operatingSystem).thenReturn('macos');
}
void mockConnectedApp(
ConnectedApp connectedApp, {
required bool isFlutterApp,
required isProfileBuild,
required isWebApp,
}) {
assert(!(!isFlutterApp && isProfileBuild));
// Dart VM.
when(connectedApp.isRunningOnDartVM).thenReturn(!isWebApp);
// Flutter app.
when(connectedApp.isFlutterAppNow).thenReturn(isFlutterApp);
when(connectedApp.isFlutterApp).thenAnswer((_) => Future.value(isFlutterApp));
when(connectedApp.isFlutterNativeAppNow)
.thenReturn(isFlutterApp && !isWebApp);
if (isFlutterApp) {
when(connectedApp.flutterVersionNow).thenReturn(
FlutterVersion.parse({
'type': 'Success',
'frameworkVersion': '2.10.0',
'channel': 'unknown',
'repositoryUrl': 'unknown source',
'frameworkRevision': '74432fa91c8ffbc555ffc2701309e8729380a012',
'frameworkCommitDate': '2020-05-14 13:05:34 -0700',
'engineRevision': 'ae2222f47e788070c09020311b573542b9706a78',
'dartSdkVersion': '2.9.0 (build 2.9.0-8.0.dev d6fed1f624)',
'frameworkRevisionShort': '74432fa91c',
'engineRevisionShort': 'ae2222f47e',
}),
);
} else {
when(connectedApp.flutterVersionNow).thenReturn(null);
}
// Flutter web app.
when(connectedApp.isFlutterWebAppNow).thenReturn(isFlutterApp && isWebApp);
// Web app.
when(connectedApp.isDartWebApp).thenAnswer((_) => Future.value(isWebApp));
when(connectedApp.isDartWebAppNow).thenReturn(isWebApp);
// CLI app.
final isCliApp = !isFlutterApp && !isWebApp;
when(connectedApp.isDartCliApp).thenAnswer((_) => Future.value(isCliApp));
when(connectedApp.isDartCliAppNow).thenReturn(isCliApp);
// Run mode.
when(connectedApp.isProfileBuild)
.thenAnswer((_) => Future.value(isProfileBuild));
when(connectedApp.isProfileBuildNow).thenReturn(isProfileBuild);
when(connectedApp.isDebugFlutterAppNow)
.thenReturn(isFlutterApp && !isProfileBuild);
// Initialized.
when(connectedApp.connectedAppInitialized).thenReturn(true);
when(connectedApp.initialized).thenReturn(Completer()..complete(true));
}
void mockFlutterVersion(
ConnectedApp connectedApp,
SemanticVersion version,
) {
when(connectedApp.flutterVersionNow).thenReturn(
FlutterVersion.parse({
'frameworkVersion': '$version',
}),
);
when(connectedApp.connectedAppInitialized).thenReturn(true);
}
// ignore: prefer_single_quotes
final Grammar mockGrammar = Grammar.fromJson(
jsonDecode(
'''
{
"name": "Dart",
"fileTypes": [
"dart"
],
"scopeName": "source.dart",
"patterns": [],
"repository": {}
}
''',
),
);
final Script? mockScript = Script.parse(
jsonDecode(
"""
{
"type": "Script",
"class": {
"type": "@Class",
"fixedId": true,
"id": "classes/11",
"name": "Script",
"library": {
"type": "@Instance",
"_vmType": "null",
"class": {
"type": "@Class",
"fixedId": true,
"id": "classes/148",
"name": "Null",
"location": {
"type": "SourceLocation",
"script": {
"type": "@Script",
"fixedId": true,
"id": "libraries/@0150898/scripts/dart%3Acore%2Fnull.dart/0",
"uri": "dart:core/null.dart",
"_kind": "kernel"
},
"tokenPos": 925,
"endTokenPos": 1165
},
"library": {
"type": "@Library",
"fixedId": true,
"id": "libraries/@0150898",
"name": "dart.core",
"uri": "dart:core"
}
},
"kind": "Null",
"fixedId": true,
"id": "objects/null",
"valueAsString": "null"
}
},
"size": 80,
"fixedId": true,
"id": "libraries/@783137924/scripts/package%3Agallery%2Fmain.dart/17b557e5bc3",
"uri": "package:gallery/main.dart",
"_kind": "kernel",
"_loadTime": 1629226949571,
"library": {
"type": "@Library",
"fixedId": true,
"id": "libraries/@783137924",
"name": "",
"uri": "package:gallery/main.dart"
},
"lineOffset": 0,
"columnOffset": 0,
"source": "// Copyright 2019 The Flutter team. All rights reserved.\\n// Use of this source code is governed by a BSD-style license that can be\\n// found in the LICENSE file.\\n\\nimport 'package:flutter/foundation.dart';\\nimport 'package:flutter/material.dart';\\nimport 'package:flutter/scheduler.dart' show timeDilation;\\nimport 'package:flutter_gen/gen_l10n/gallery_localizations.dart';\\nimport 'package:flutter_localized_locales/flutter_localized_locales.dart';\\nimport 'package:gallery/constants.dart';\\nimport 'package:gallery/data/gallery_options.dart';\\nimport 'package:gallery/pages/backdrop.dart';\\nimport 'package:gallery/pages/splash.dart';\\nimport 'package:gallery/routes.dart';\\nimport 'package:gallery/themes/gallery_theme_data.dart';\\nimport 'package:google_fonts/google_fonts.dart';\\n\\nexport 'package:gallery/data/demos.dart' show pumpDeferredLibraries;\\n\\nvoid main() {\\n GoogleFonts.config.allowRuntimeFetching = false;\\n runApp(const GalleryApp());\\n}\\n\\nclass GalleryApp extends StatelessWidget {\\n const GalleryApp({\\n Key key,\\n this.initialRoute,\\n this.isTestMode = false,\\n }) : super(key: key);\\n\\n final bool isTestMode;\\n final String initialRoute;\\n\\n @override\\n Widget build(BuildContext context) {\\n return ModelBinding(\\n initialModel: GalleryOptions(\\n themeMode: ThemeMode.system,\\n textScaleFactor: systemTextScaleFactorOption,\\n customTextDirection: CustomTextDirection.localeBased,\\n locale: null,\\n timeDilation: timeDilation,\\n platform: defaultTargetPlatform,\\n isTestMode: isTestMode,\\n ),\\n child: Builder(\\n builder: (context) {\\n return MaterialApp(\\n // By default on desktop, scrollbars are applied by the\\n // ScrollBehavior. This overrides that. All vertical scrollables in\\n // the gallery need to be audited before enabling this feature,\\n // see https://github.com/flutter/gallery/issues/523\\n scrollBehavior:\\n const MaterialScrollBehavior().copyWith(scrollbars: false),\\n restorationScopeId: 'rootGallery',\\n title: 'Flutter Gallery',\\n debugShowCheckedModeBanner: false,\\n themeMode: GalleryOptions.of(context).themeMode,\\n theme: GalleryThemeData.lightThemeData.copyWith(\\n platform: GalleryOptions.of(context).platform,\\n ),\\n darkTheme: GalleryThemeData.darkThemeData.copyWith(\\n platform: GalleryOptions.of(context).platform,\\n ),\\n localizationsDelegates: const [\\n ...GalleryLocalizations.localizationsDelegates,\\n LocaleNamesLocalizationsDelegate()\\n ],\\n initialRoute: initialRoute,\\n supportedLocales: GalleryLocalizations.supportedLocales,\\n locale: GalleryOptions.of(context).locale,\\n localeResolutionCallback: (locale, supportedLocales) {\\n deviceLocale = locale;\\n return locale;\\n },\\n onGenerateRoute: RouteConfiguration.onGenerateRoute,\\n );\\n },\\n ),\\n );\\n }\\n}\\n\\nclass RootPage extends StatelessWidget {\\n const RootPage({\\n Key key,\\n }) : super(key: key);\\n\\n @override\\n Widget build(BuildContext context) {\\n return const ApplyTextOptions(\\n child: SplashPage(\\n child: Backdrop(),\\n ),\\n );\\n }\\n}\\n",
"tokenPosTable": [
[
20,
842,
1,
847,
6,
851,
10,
854,
13
],
[
21,
870,
15,
877,
22
],
[
22,
909,
3,
922,
16
],
[
23,
937,
1
],
[
25,
940,
1
],
[
26,
985,
3,
991,
9,
1001,
19
],
[
27,
1012,
9
],
[
28,
1026,
10
],
[
29,
1049,
10,
1062,
23
],
[
30,
1076,
8,
1087,
19,
1091,
23
],
[
32,
1107,
14,
1117,
24
],
[
33,
1134,
16,
1146,
28
],
[
35,
1151,
3,
1152,
4
],
[
36,
1170,
10,
1175,
15,
1189,
29,
1198,
38
],
[
37,
1204,
5,
1211,
12
],
[
38,
1245,
21
],
[
39,
1290,
30
],
[
40,
1323,
26
],
[
41,
1401,
50
],
[
43,
1458,
23
],
[
44,
1490,
19
],
[
45,
1533,
21
],
[
47,
1567,
14
],
[
48,
1593,
18,
1594,
19,
1603,
28
],
[
49,
1615,
11,
1622,
18
],
[
55,
1974,
23,
1999,
48
],
[
59,
2198,
39,
2201,
42,
2210,
51
],
[
60,
2257,
37,
2272,
52
],
[
61,
2321,
40,
2324,
43,
2333,
52
],
[
63,
2398,
41,
2412,
55
],
[
64,
2461,
40,
2464,
43,
2473,
52
],
[
66,
2534,
37
],
[
70,
2694,
27
],
[
71,
2759,
52
],
[
72,
2812,
36,
2815,
39,
2824,
48
],
[
73,
2870,
39,
2871,
40,
2879,
48,
2897,
66
],
[
74,
2913,
15,
2928,
30
],
[
75,
2950,
15,
2957,
22
],
[
76,
2977,
13,
2978,
14
],
[
77,
3028,
49
],
[
79,
3066,
9,
3067,
10
],
[
82,
3087,
3
],
[
83,
3089,
1
],
[
85,
3092,
1
],
[
86,
3135,
3,
3141,
9,
3149,
17
],
[
87,
3160,
9
],
[
88,
3172,
8,
3183,
19,
3187,
23
],
[
90,
3192,
3,
3193,
4
],
[
91,
3211,
10,
3216,
15,
3230,
29,
3239,
38
],
[
92,
3245,
5,
3258,
18
],
[
97,
3346,
3
],
[
98,
3348,
1
]
]
}
""",
),
);
final mockScriptRef = ScriptRef(
uri:
'libraries/@783137924/scripts/package%3Agallery%2Fmain.dart/17b557e5bc3"',
id: 'test-script-long-lines',
);
final mockSyntaxHighlighter = SyntaxHighlighter.withGrammar(
grammar: mockGrammar,
source: mockScript!.source,
);
const coverageHitLines = <int>{
1,
3,
4,
7,
};
const coverageMissLines = <int>{
2,
5,
};
const executableLines = <int>{
...coverageHitLines,
...coverageMissLines,
};
const profilerEntries = <int, ProfileReportEntry>{
1: ProfileReportEntry(
sampleCount: 5,
line: 1,
inclusive: 2,
exclusive: 2,
),
3: ProfileReportEntry(
sampleCount: 5,
line: 3,
inclusive: 1,
exclusive: 1,
),
4: ProfileReportEntry(
sampleCount: 5,
line: 4,
inclusive: 1,
exclusive: 1,
),
7: ProfileReportEntry(
sampleCount: 5,
line: 7,
inclusive: 1,
exclusive: 1,
),
};
final mockParsedScript = ParsedScript(
script: mockScript!,
highlighter: mockSyntaxHighlighter,
executableLines: executableLines,
sourceReport: ProcessedSourceReport(
coverageHitLines: coverageHitLines,
coverageMissedLines: coverageMissLines,
profilerEntries: profilerEntries,
),
);
final mockScriptRefs = [
ScriptRef(uri: 'zoo:animals/cats/meow.dart', id: 'fake/id/1'),
ScriptRef(uri: 'zoo:animals/cats/purr.dart', id: 'fake/id/2'),
ScriptRef(uri: 'zoo:animals/dogs/bark.dart', id: 'fake/id/3'),
ScriptRef(uri: 'zoo:animals/dogs/growl.dart', id: 'fake/id/4'),
ScriptRef(uri: 'zoo:animals/insects/caterpillar.dart', id: 'fake/id/5'),
ScriptRef(uri: 'zoo:animals/insects/cicada.dart', id: 'fake/id/6'),
ScriptRef(uri: 'kitchen:food/catering/party.dart', id: 'fake/id/7'),
ScriptRef(uri: 'kitchen:food/carton/milk.dart', id: 'fake/id/8'),
ScriptRef(uri: 'kitchen:food/milk/carton.dart', id: 'fake/id/9'),
ScriptRef(uri: 'travel:adventure/cave_tours_europe.dart', id: 'fake/id/10'),
ScriptRef(uri: 'travel:canada/banff.dart', id: 'fake/id/11'),
];