// Copyright 2014 The Flutter 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 'dart:io'; import 'package:dwds/dwds.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/targets/scene_importer.dart'; import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/isolated/devfs_web.dart'; import 'package:flutter_tools/src/isolated/resident_web_runner.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/resident_devtools_handler.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/web/chrome.dart'; import 'package:flutter_tools/src/web/web_device.dart'; import 'package:package_config/package_config.dart'; import 'package:package_config/package_config_types.dart'; import 'package:test/fake.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; import '../src/common.dart'; import '../src/context.dart'; import '../src/fake_process_manager.dart'; import '../src/fake_vm_services.dart'; const List<VmServiceExpectation> kAttachLogExpectations = <VmServiceExpectation>[ FakeVmServiceRequest( method: 'streamListen', args: <String, Object>{ 'streamId': 'Stdout', }, ), FakeVmServiceRequest( method: 'streamListen', args: <String, Object>{ 'streamId': 'Stderr', }, ), ]; const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectation>[ FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ 'streamId': 'Isolate', }), FakeVmServiceRequest(method: 'registerService', args: <String, Object>{ 'service': kReloadSourcesServiceName, 'alias': kFlutterToolAlias, }), FakeVmServiceRequest(method: 'registerService', args: <String, Object>{ 'service': kFlutterVersionServiceName, 'alias': kFlutterToolAlias, }), FakeVmServiceRequest(method: 'registerService', args: <String, Object>{ 'service': kFlutterMemoryInfoServiceName, 'alias': kFlutterToolAlias, }), FakeVmServiceRequest( method: 'streamListen', args: <String, Object>{ 'streamId': 'Extension', }, ), ]; const List<VmServiceExpectation> kAttachExpectations = <VmServiceExpectation>[ ...kAttachLogExpectations, ...kAttachIsolateExpectations, ]; void main() { late FakeDebugConnection debugConnection; late FakeChromeDevice chromeDevice; late FakeAppConnection appConnection; late FakeFlutterDevice flutterDevice; late FakeWebDevFS webDevFS; late FakeResidentCompiler residentCompiler; late FakeChromeConnection chromeConnection; late FakeChromeTab chromeTab; late FakeWebServerDevice webServerDevice; late FakeDevice mockDevice; late FakeVmServiceHost fakeVmServiceHost; late FileSystem fileSystem; late ProcessManager processManager; late TestUsage testUsage; setUp(() { testUsage = TestUsage(); fileSystem = MemoryFileSystem.test(); processManager = FakeProcessManager.any(); debugConnection = FakeDebugConnection(); mockDevice = FakeDevice(); appConnection = FakeAppConnection(); webDevFS = FakeWebDevFS(); residentCompiler = FakeResidentCompiler(); chromeDevice = FakeChromeDevice(); chromeConnection = FakeChromeConnection(); chromeTab = FakeChromeTab('index.html'); webServerDevice = FakeWebServerDevice(); flutterDevice = FakeFlutterDevice() .._devFS = webDevFS ..device = mockDevice ..generator = residentCompiler; fileSystem.file('.packages').writeAsStringSync('\n'); }); void setupMocks() { fileSystem.file('pubspec.yaml').createSync(); fileSystem.file('lib/main.dart').createSync(recursive: true); fileSystem.file('web/index.html').createSync(recursive: true); webDevFS.report = UpdateFSReport(success: true); debugConnection.fakeVmServiceHost = () => fakeVmServiceHost; webDevFS.result = ConnectionResult( appConnection, debugConnection, debugConnection.vmService, ); debugConnection.uri = 'ws://127.0.0.1/abcd/'; chromeConnection.tabs.add(chromeTab); } testUsingContext( 'runner with web server device does not support debugging without --start-paused', () { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); flutterDevice.device = WebServerDevice( logger: BufferLogger.test(), ); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final ResidentRunner profileResidentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, fileSystem: fileSystem, logger: BufferLogger.test(), usage: globals.flutterUsage, systemClock: globals.systemClock, ); expect(profileResidentWebRunner.debuggingEnabled, false); flutterDevice.device = chromeDevice; expect(residentWebRunner.debuggingEnabled, true); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext( 'runner with web server device supports debugging with --start-paused', () { fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); flutterDevice.device = WebServerDevice( logger: BufferLogger.test(), ); final ResidentRunner profileResidentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true), ipv6: true, fileSystem: fileSystem, logger: BufferLogger.test(), usage: globals.flutterUsage, systemClock: globals.systemClock, ); expect(profileResidentWebRunner.uri, webDevFS.baseUri); expect(profileResidentWebRunner.debuggingEnabled, true); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('profile does not supportsServiceProtocol', () { final ResidentRunner residentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, fileSystem: fileSystem, logger: BufferLogger.test(), usage: globals.flutterUsage, systemClock: globals.systemClock, ); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); flutterDevice.device = chromeDevice; final ResidentRunner profileResidentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile), ipv6: true, fileSystem: fileSystem, logger: BufferLogger.test(), usage: globals.flutterUsage, systemClock: globals.systemClock, ); expect(profileResidentWebRunner.supportsServiceProtocol, false); expect(residentWebRunner.supportsServiceProtocol, true); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Can successfully run and connect to vmservice', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future; expect(appConnection.ranMain, true); expect(logger.statusText, contains('Debug service listening on ws://127.0.0.1/abcd/')); expect(debugConnectionInfo.wsUri.toString(), 'ws://127.0.0.1/abcd/'); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('WebRunner copies compiled app.dill to cache during startup', () async { final DebuggingOptions debuggingOptions = DebuggingOptions.enabled( const BuildInfo(BuildMode.debug, null, treeShakeIcons: false), ); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, debuggingOptions: debuggingOptions); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); residentWebRunner.artifactDirectory .childFile('app.dill') .writeAsStringSync('ABC'); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; expect( await fileSystem .file(fileSystem.path.join('build', 'cache.dill')) .readAsString(), 'ABC'); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext( 'WebRunner copies compiled app.dill to cache during startup with track-widget-creation', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); residentWebRunner.artifactDirectory .childFile('app.dill') .writeAsStringSync('ABC'); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; expect( await fileSystem .file(fileSystem.path.join('build', 'cache.dill.track.dill')) .readAsString(), 'ABC'); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); // Regression test for https://github.com/flutter/flutter/issues/60613 testUsingContext( 'ResidentWebRunner calls appFailedToStart if initial compilation fails', () async { fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fileSystem .file(globals.fs.path.join('lib', 'main.dart')) .createSync(recursive: true); webDevFS.report = UpdateFSReport(); expect(await residentWebRunner.run(), 1); // Completing this future ensures that the daemon can exit correctly. expect(await residentWebRunner.waitForAppToFinish(), 1); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext( 'Can successfully run without an index.html including status warning', () async { final BufferLogger logger = BufferLogger.test(); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); fileSystem.directory('web').deleteSync(recursive: true); final ResidentWebRunner residentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: false, fileSystem: fileSystem, logger: logger, usage: globals.flutterUsage, systemClock: globals.systemClock, ); expect(await residentWebRunner.run(), 0); expect(logger.statusText, contains('This application is not configured to build on the web')); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Can successfully run and disconnect with --no-resident', () async { fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); final ResidentRunner residentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: false, fileSystem: fileSystem, logger: BufferLogger.test(), usage: globals.flutterUsage, systemClock: globals.systemClock, ); expect(await residentWebRunner.run(), 0); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Listens to stdout and stderr streams before running main', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachLogExpectations, FakeVmServiceStreamResponse( streamId: 'Stdout', event: vm_service.Event( timestamp: 0, kind: vm_service.EventStreams.kStdout, bytes: base64.encode(utf8.encode('THIS MESSAGE IS IMPORTANT'))), ), FakeVmServiceStreamResponse( streamId: 'Stderr', event: vm_service.Event( timestamp: 0, kind: vm_service.EventStreams.kStderr, bytes: base64.encode(utf8.encode('SO IS THIS'))), ), ...kAttachIsolateExpectations, ]); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; expect(logger.statusText, contains('THIS MESSAGE IS IMPORTANT')); expect(logger.statusText, contains('SO IS THIS')); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Listens to extension events with structured errors', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: testLogger); final List<VmServiceExpectation> requests = <VmServiceExpectation>[ ...kAttachExpectations, // This is the first error message of a session. FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Flutter.Error', extensionData: vm_service.ExtensionData.parse(<String, Object?>{ 'errorsSinceReload': 0, 'renderedErrorText': 'first', }), kind: vm_service.EventStreams.kExtension, ), ), // This is the second error message of a session. FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Flutter.Error', extensionData: vm_service.ExtensionData.parse(<String, Object?>{ 'errorsSinceReload': 1, 'renderedErrorText': 'second', }), kind: vm_service.EventStreams.kExtension, ), ), // This is not Flutter.Error kind data, so it should not be logged, even though it has a renderedErrorText field. FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Other', extensionData: vm_service.ExtensionData.parse(<String, Object?>{ 'errorsSinceReload': 2, 'renderedErrorText': 'not an error', }), kind: vm_service.EventStreams.kExtension, ), ), // This is the third error message of a session. FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Flutter.Error', extensionData: vm_service.ExtensionData.parse(<String, Object?>{ 'errorsSinceReload': 2, 'renderedErrorText': 'third', }), kind: vm_service.EventStreams.kExtension, ), ), // This is bogus error data. FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Flutter.Error', extensionData: vm_service.ExtensionData.parse(<String, Object?>{ 'other': 'bad stuff', }), kind: vm_service.EventStreams.kExtension, ), ), // Empty error text should not break anything. FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Flutter.Error', extensionData: vm_service.ExtensionData.parse(<String, Object?>{ 'test': 'data', 'renderedErrorText': '', }), kind: vm_service.EventStreams.kExtension, ), ), // Messages without errorsSinceReload should act as if errorsSinceReload: 0 FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Flutter.Error', extensionData: vm_service.ExtensionData.parse(<String, Object?>{ 'test': 'data', 'renderedErrorText': 'error text', }), kind: vm_service.EventStreams.kExtension, ), ), // When adding things here, make sure the last one is supposed to output something // to the statusLog, otherwise you won't be able to distinguish the absence of output // due to it passing the test from absence due to it not running the test. ]; // We use requests below, so make a copy of it here (FakeVmServiceHost will // clear its copy internally, which would affect our pumping below). fakeVmServiceHost = FakeVmServiceHost(requests: requests.toList()); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; assert(requests.length > 5, 'requests was modified'); for (final Object _ in requests) { // pump the task queue once for each message await null; } expect(testLogger.statusText, 'Launching lib/main.dart on FakeDevice in debug mode...\n' 'Waiting for connection from debug service on FakeDevice...\n' 'Debug service listening on ws://127.0.0.1/abcd/\n' '\n' 'first\n' '\n' 'second\n' 'third\n' '\n' '\n' // the empty message '\n' '\n' 'error text\n' '\n' ); expect(testLogger.errorText, 'Received an invalid Flutter.Error message from app: {other: bad stuff}\n' ); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Does not run main with --start-paused', () async { final ResidentRunner residentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true), ipv6: true, fileSystem: fileSystem, logger: BufferLogger.test(), usage: globals.flutterUsage, systemClock: globals.systemClock, ); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; expect(appConnection.ranMain, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Can hot reload after attaching', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner( flutterDevice, logger: logger, systemClock: SystemClock.fixed(DateTime(2001)), ); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: kHotRestartServiceName, jsonResponse: <String, Object>{ 'type': 'Success', }), const FakeVmServiceRequest( method: 'streamListen', args: <String, Object>{ 'streamId': 'Isolate', }, ), ]); setupMocks(); final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher(); final FakeProcess process = FakeProcess(); final Chromium chrome = Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher, process: process, logger: logger); chromiumLauncher.setInstance(chrome); flutterDevice.device = GoogleChromeDevice( fileSystem: fileSystem, chromiumLauncher: chromiumLauncher, logger: BufferLogger.test(), platform: FakePlatform(), processManager: FakeProcessManager.any(), ); webDevFS.report = UpdateFSReport(success: true); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future; expect(debugConnectionInfo, isNotNull); final OperationResult result = await residentWebRunner.restart(); expect(logger.statusText, contains('Restarted application in')); expect(result.code, 0); expect(webDevFS.mainUri.toString(), contains('entrypoint.dart')); // ensure that analytics are sent. expect(testUsage.events, <TestUsageEvent>[ TestUsageEvent('hot', 'restart', parameters: CustomDimensions.fromMap(<String, String>{ 'cd27': 'web-javascript', 'cd28': '', 'cd29': 'false', 'cd30': 'true', 'cd13': '0', 'cd48': 'false' })), ]); expect(testUsage.timings, const <TestTimingEvent>[ TestTimingEvent('hot', 'web-incremental-restart', Duration.zero), ]); }, overrides: <Type, Generator>{ Usage: () => testUsage, FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Can hot restart after attaching', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner( flutterDevice, logger: logger, systemClock: SystemClock.fixed(DateTime(2001)), ); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: kHotRestartServiceName, jsonResponse: <String, Object>{ 'type': 'Success', }), ]); setupMocks(); final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher(); final FakeProcess process = FakeProcess(); final Chromium chrome = Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher, process: process, logger: logger); chromiumLauncher.setInstance(chrome); flutterDevice.device = GoogleChromeDevice( fileSystem: fileSystem, chromiumLauncher: chromiumLauncher, logger: BufferLogger.test(), platform: FakePlatform(), processManager: FakeProcessManager.any(), ); webDevFS.report = UpdateFSReport(success: true); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; final OperationResult result = await residentWebRunner.restart(fullRestart: true); // Ensure that generated entrypoint is generated correctly. expect(webDevFS.mainUri, isNotNull); final String entrypointContents = fileSystem.file(webDevFS.mainUri).readAsStringSync(); expect(entrypointContents, contains('// Flutter web bootstrap script')); expect(entrypointContents, contains("import 'dart:ui_web' as ui_web;")); expect(entrypointContents, contains('await ui_web.bootstrapEngine(')); expect(logger.statusText, contains('Restarted application in')); expect(result.code, 0); // ensure that analytics are sent. expect(testUsage.events, <TestUsageEvent>[ TestUsageEvent('hot', 'restart', parameters: CustomDimensions.fromMap(<String, String>{ 'cd27': 'web-javascript', 'cd28': '', 'cd29': 'false', 'cd30': 'true', 'cd13': '0', 'cd48': 'false' })), ]); expect(testUsage.timings, const <TestTimingEvent>[ TestTimingEvent('hot', 'web-incremental-restart', Duration.zero), ]); }, overrides: <Type, Generator>{ Usage: () => testUsage, FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Can hot restart after attaching with web-server device', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner( flutterDevice, logger: logger, systemClock: SystemClock.fixed(DateTime(2001)), ); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations); setupMocks(); flutterDevice.device = webServerDevice; webDevFS.report = UpdateFSReport(success: true); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; final OperationResult result = await residentWebRunner.restart(fullRestart: true); expect(logger.statusText, contains('Restarted application in')); expect(result.code, 0); // web-server device does not send restart analytics expect(testUsage.events, isEmpty); expect(testUsage.timings, isEmpty); }, overrides: <Type, Generator>{ Usage: () => testUsage, FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('web resident runner is debuggable', () { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); expect(residentWebRunner.debuggingEnabled, true); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Exits when initial compile fails', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); webDevFS.report = UpdateFSReport(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); expect(await residentWebRunner.run(), 1); expect(testUsage.events, isEmpty); expect(testUsage.timings, isEmpty); }, overrides: <Type, Generator>{ Usage: () => testUsage, FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext( 'Faithfully displays stdout messages with leading/trailing spaces', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachLogExpectations, FakeVmServiceStreamResponse( streamId: 'Stdout', event: vm_service.Event( timestamp: 0, kind: vm_service.EventStreams.kStdout, bytes: base64.encode( utf8.encode( ' This is a message with 4 leading and trailing spaces '), ), ), ), ...kAttachIsolateExpectations, ]); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; expect( logger.statusText, contains( ' This is a message with 4 leading and trailing spaces ')); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Fails on compilation errors in hot restart', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; webDevFS.report = UpdateFSReport(); final OperationResult result = await residentWebRunner.restart(fullRestart: true); expect(result.code, 1); expect(result.message, contains('Failed to recompile application.')); expect(testUsage.events, isEmpty); expect(testUsage.timings, isEmpty); }, overrides: <Type, Generator>{ Usage: () => testUsage, FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext( 'Fails non-fatally on vmservice response error for hot restart', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: kHotRestartServiceName, jsonResponse: <String, Object>{ 'type': 'Failed', }, ), ]); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; final OperationResult result = await residentWebRunner.restart(); expect(result.code, 0); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Fails fatally on Vm Service error response', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: kHotRestartServiceName, // Failed response, errorCode: RPCErrorCodes.kInternalError, ), ]); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; final OperationResult result = await residentWebRunner.restart(); expect(result.code, 1); expect(result.message, contains(RPCErrorCodes.kInternalError.toString())); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('printHelp without details shows hot restart help message', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); residentWebRunner.printHelp(details: false); expect(logger.statusText, contains('To hot restart changes')); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('cleanup of resources is safe to call multiple times', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); mockDevice.dds = DartDevelopmentService(); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, ]); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.exit(); await residentWebRunner.exit(); expect(debugConnection.didClose, false); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('cleans up Chrome if tab is closed', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, ]); setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); final Future<int?> result = residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, ); await connectionInfoCompleter.future; debugConnection.completer.complete(); await result; expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Prints target and device name on run', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, ]); setupMocks(); mockDevice.name = 'Chromez'; final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; expect( logger.statusText, contains( 'Launching ${fileSystem.path.join('lib', 'main.dart')} on ' 'Chromez in debug mode', )); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Sends launched app.webLaunchUrl event for Chrome device', () async { final BufferLogger logger = BufferLogger.test(); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachLogExpectations, ...kAttachIsolateExpectations, ]); setupMocks(); final FakeChromeConnection chromeConnection = FakeChromeConnection(); final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher(); final FakeProcess process = FakeProcess(); final Chromium chrome = Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher, process: process, logger: logger); chromiumLauncher.setInstance(chrome); flutterDevice.device = GoogleChromeDevice( fileSystem: fileSystem, chromiumLauncher: chromiumLauncher, logger: logger, platform: FakePlatform(), processManager: FakeProcessManager.any(), ); webDevFS.baseUri = Uri.parse('http://localhost:8765/app/'); final FakeChromeTab chromeTab = FakeChromeTab('index.html'); chromeConnection.tabs.add(chromeTab); final ResidentWebRunner runner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, fileSystem: fileSystem, logger: logger, usage: globals.flutterUsage, systemClock: globals.systemClock, ); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(runner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; // Ensure we got the URL and that it was already launched. expect( logger.eventText, contains(json.encode( <String, Object>{ 'name': 'app.webLaunchUrl', 'args': <String, Object>{ 'url': 'http://localhost:8765/app/', 'launched': true, }, }, ))); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext( 'Sends unlaunched app.webLaunchUrl event for Web Server device', () async { final BufferLogger logger = BufferLogger.test(); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); flutterDevice.device = WebServerDevice( logger: logger, ); webDevFS.baseUri = Uri.parse('http://localhost:8765/app/'); final ResidentWebRunner runner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, fileSystem: fileSystem, logger: logger, usage: globals.flutterUsage, systemClock: globals.systemClock, ); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(runner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; // Ensure we got the URL and that it was not already launched. expect( logger.eventText, contains(json.encode( <String, Object>{ 'name': 'app.webLaunchUrl', 'args': <String, Object>{ 'url': 'http://localhost:8765/app/', 'launched': false, }, }, ))); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('ResidentWebRunner generates files when l10n.yaml exists', () async { fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); setupMocks(); final ResidentRunner residentWebRunner = ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: false, fileSystem: fileSystem, logger: BufferLogger.test(), usage: globals.flutterUsage, systemClock: globals.systemClock, ); // Create necessary files. globals.fs.file(globals.fs.path.join('lib', 'main.dart')) .createSync(recursive: true); globals.fs.file(globals.fs.path.join('lib', 'l10n', 'app_en.arb')) ..createSync(recursive: true) ..writeAsStringSync(''' { "helloWorld": "Hello, World!", "@helloWorld": { "description": "Sample description" } }'''); globals.fs.file('l10n.yaml').createSync(); globals.fs.file('pubspec.yaml').writeAsStringSync(''' flutter: generate: true '''); globals.fs.directory('.dart_tool') .childFile('package_config.json') ..createSync(recursive: true) ..writeAsStringSync(''' { "configVersion": 2, "packages": [ { "name": "path_provider_linux", "rootUri": "../../../path_provider_linux", "packageUri": "lib/", "languageVersion": "2.12" } ] } '''); expect(await residentWebRunner.run(), 0); final File generatedLocalizationsFile = globals.fs.directory('.dart_tool') .childDirectory('flutter_gen') .childDirectory('gen_l10n') .childFile('app_localizations.dart'); expect(generatedLocalizationsFile.existsSync(), isTrue); // Completing this future ensures that the daemon can exit correctly. expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); // While this file should be ignored on web, generating it here will cause a // perf regression in hot restart. testUsingContext('Does not generate dart_plugin_registrant.dart', () async { // Create necessary files for [DartPluginRegistrantTarget] final File packageConfig = globals.fs.directory('.dart_tool').childFile('package_config.json'); packageConfig.createSync(recursive: true); packageConfig.writeAsStringSync(''' { "configVersion": 2, "packages": [ { "name": "path_provider_linux", "rootUri": "../../../path_provider_linux", "packageUri": "lib/", "languageVersion": "2.12" } ] } '''); // Start with a dart_plugin_registrant.dart file. globals.fs .directory('.dart_tool') .childDirectory('flutter_build') .childFile('dart_plugin_registrant.dart') .createSync(recursive: true); final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); await residentWebRunner.runSourceGenerators(); // dart_plugin_registrant.dart should be untouched, indicating that its // generation didn't run. If it had run, the file would have been removed as // there are no plugins in the project. expect(project.dartPluginRegistrant.existsSync(), true); expect(project.dartPluginRegistrant.readAsStringSync(), ''); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Successfully turns WebSocketException into ToolExit', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); webDevFS.exception = const WebSocketException(); await expectLater(residentWebRunner.run, throwsToolExit()); expect(logger.errorText, contains('WebSocketException')); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Successfully turns AppConnectionException into ToolExit', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); webDevFS.exception = AppConnectionException(''); await expectLater(residentWebRunner.run, throwsToolExit()); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Successfully turns ChromeDebugError into ToolExit', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); webDevFS.exception = ChromeDebugException(<String, Object?>{ 'text': 'error', }); await expectLater(residentWebRunner.run, throwsToolExit()); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Rethrows unknown Exception type from dwds', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); webDevFS.exception = Exception(); await expectLater(residentWebRunner.run, throwsException); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('Rethrows unknown Error type from dwds tooling', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); setupMocks(); webDevFS.exception = StateError(''); await expectLater(residentWebRunner.run, throwsStateError); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); testUsingContext('throws when port is an integer outside the valid TCP range', () async { final BufferLogger logger = BufferLogger.test(); DebuggingOptions debuggingOptions = DebuggingOptions.enabled(BuildInfo.debug, port: '65536'); ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger, debuggingOptions: debuggingOptions); await expectToolExitLater(residentWebRunner.run(), matches('Invalid port: 65536.*')); debuggingOptions = DebuggingOptions.enabled(BuildInfo.debug, port: '-1'); residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger, debuggingOptions: debuggingOptions); await expectToolExitLater(residentWebRunner.run(), matches('Invalid port: -1.*')); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, }); } ResidentRunner setUpResidentRunner( FlutterDevice flutterDevice, { Logger? logger, SystemClock? systemClock, DebuggingOptions? debuggingOptions, }) { return ResidentWebRunner( flutterDevice, flutterProject: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), debuggingOptions: debuggingOptions ?? DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, usage: globals.flutterUsage, systemClock: systemClock ?? SystemClock.fixed(DateTime.now()), fileSystem: globals.fs, logger: logger ?? BufferLogger.test(), devtoolsHandler: createNoOpHandler, ); } // Unfortunately Device, despite not being immutable, has an `operator ==`. // Until we fix that, we have to also ignore related lints here. // ignore: avoid_implementing_value_types class FakeWebServerDevice extends FakeDevice implements WebServerDevice {} // Unfortunately Device, despite not being immutable, has an `operator ==`. // Until we fix that, we have to also ignore related lints here. // ignore: avoid_implementing_value_types class FakeDevice extends Fake implements Device { @override String name = 'FakeDevice'; int count = 0; @override Future<String> get sdkNameAndVersion async => 'SDK Name and Version'; @override late DartDevelopmentService dds; @override Future<LaunchResult> startApp( ApplicationPackage? package, { String? mainPath, String? route, DebuggingOptions? debuggingOptions, Map<String, dynamic>? platformArgs, bool prebuiltApplication = false, bool ipv6 = false, String? userIdentifier, }) async { return LaunchResult.succeeded(); } @override Future<bool> stopApp( ApplicationPackage? app, { String? userIdentifier, }) async { if (count > 0) { throw StateError('stopApp called more than once.'); } count += 1; return true; } } class FakeDebugConnection extends Fake implements DebugConnection { late FakeVmServiceHost Function() fakeVmServiceHost; @override vm_service.VmService get vmService => fakeVmServiceHost.call().vmService.service; @override late String uri; final Completer<void> completer = Completer<void>(); bool didClose = false; @override Future<void> get onDone => completer.future; @override Future<void> close() async { didClose = true; } } class FakeAppConnection extends Fake implements AppConnection { bool ranMain = false; @override void runMain() { ranMain = true; } } // Unfortunately Device, despite not being immutable, has an `operator ==`. // Until we fix that, we have to also ignore related lints here. // ignore: avoid_implementing_value_types class FakeChromeDevice extends Fake implements ChromiumDevice {} class FakeWipDebugger extends Fake implements WipDebugger {} class FakeResidentCompiler extends Fake implements ResidentCompiler { @override Future<CompilerOutput> recompile( Uri mainUri, List<Uri>? invalidatedFiles, { required String outputPath, required PackageConfig packageConfig, required FileSystem fs, String? projectRootPath, bool suppressErrors = false, bool checkDartPluginRegistry = false, File? dartPluginRegistrant, Uri? nativeAssetsYaml, }) async { return const CompilerOutput('foo.dill', 0, <Uri>[]); } @override void accept() {} @override void reset() {} @override Future<CompilerOutput> reject() async { return const CompilerOutput('foo.dill', 0, <Uri>[]); } @override void addFileSystemRoot(String root) {} } class FakeWebDevFS extends Fake implements WebDevFS { Object? exception; ConnectionResult? result; late UpdateFSReport report; Uri? mainUri; @override List<Uri> sources = <Uri>[]; @override Uri baseUri = Uri.parse('http://localhost:12345'); @override DateTime? lastCompiled = DateTime.now(); @override PackageConfig? lastPackageConfig = PackageConfig.empty; @override Future<Uri> create() async { return baseUri; } @override Future<UpdateFSReport> update({ required Uri mainUri, required ResidentCompiler generator, required bool trackWidgetCreation, required String pathToReload, required List<Uri> invalidatedFiles, required PackageConfig packageConfig, required String dillOutputPath, required DevelopmentShaderCompiler shaderCompiler, DevelopmentSceneImporter? sceneImporter, DevFSWriter? devFSWriter, String? target, AssetBundle? bundle, DateTime? firstBuildTime, bool bundleFirstUpload = false, bool fullRestart = false, String? projectRootPath, File? dartPluginRegistrant, }) async { this.mainUri = mainUri; return report; } @override Future<ConnectionResult?> connect(bool useDebugExtension, {VmServiceFactory vmServiceFactory = createVmServiceDelegate}) async { if (exception != null) { assert(exception is Exception || exception is Error); // ignore: only_throw_errors, exception is either Error or Exception here. throw exception!; } return result; } } class FakeChromeConnection extends Fake implements ChromeConnection { final List<ChromeTab> tabs = <ChromeTab>[]; @override Future<ChromeTab> getTab(bool Function(ChromeTab tab) accept, {Duration? retryFor}) async { return tabs.firstWhere(accept); } @override Future<List<ChromeTab>> getTabs({Duration? retryFor}) async { return tabs; } } class FakeChromeTab extends Fake implements ChromeTab { FakeChromeTab(this.url); @override final String url; final FakeWipConnection connection = FakeWipConnection(); @override Future<WipConnection> connect({Function? onError}) async { return connection; } } class FakeWipConnection extends Fake implements WipConnection { @override final WipDebugger debugger = FakeWipDebugger(); } /// A test implementation of the [ChromiumLauncher] that launches a fixed instance. class TestChromiumLauncher implements ChromiumLauncher { TestChromiumLauncher(); bool _hasInstance = false; void setInstance(Chromium chromium) { _hasInstance = true; currentCompleter.complete(chromium); } @override Completer<Chromium> currentCompleter = Completer<Chromium>(); @override bool canFindExecutable() { return true; } @override Future<Chromium> get connectedInstance => currentCompleter.future; @override String findExecutable() { return 'chrome'; } @override bool get hasChromeInstance => _hasInstance; @override Future<Chromium> launch( String url, { bool headless = false, int? debugPort, bool skipCheck = false, Directory? cacheDir, List<String> webBrowserFlags = const <String>[], }) async { return currentCompleter.future; } @override Future<Chromium> connect(Chromium chrome, bool skipCheck) { return currentCompleter.future; } } class FakeFlutterDevice extends Fake implements FlutterDevice { Uri? testUri; UpdateFSReport report = UpdateFSReport( success: true, invalidatedSourcesCount: 1, ); Exception? reportError; @override ResidentCompiler? generator; @override Stream<Uri?> get vmServiceUris => Stream<Uri?>.value(testUri); @override DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler(); @override FlutterVmService? vmService; DevFS? _devFS; @override DevFS? get devFS => _devFS; @override set devFS(DevFS? value) {} @override Device? device; @override Future<void> stopEchoingDeviceLog() async {} @override Future<void> initLogReader() async {} @override Future<Uri?> setupDevFS(String fsName, Directory rootDirectory) async { return testUri; } @override Future<void> exitApps( {Duration timeoutDelay = const Duration(seconds: 10)}) async {} @override Future<void> connect({ ReloadSources? reloadSources, Restart? restart, CompileExpression? compileExpression, GetSkSLMethod? getSkSLMethod, FlutterProject? flutterProject, PrintStructuredErrorLogMethod? printStructuredErrorLogMethod, int? hostVmServicePort, int? ddsPort, bool disableServiceAuthCodes = false, bool enableDds = true, bool cacheStartupProfile = false, required bool allowExistingDdsInstance, bool? ipv6 = false, }) async {} @override Future<UpdateFSReport> updateDevFS({ Uri? mainUri, String? target, AssetBundle? bundle, DateTime? firstBuildTime, bool bundleFirstUpload = false, bool bundleDirty = false, bool fullRestart = false, String? projectRootPath, String? pathToReload, String? dillOutputPath, List<Uri>? invalidatedFiles, PackageConfig? packageConfig, File? dartPluginRegistrant, }) async { if (reportError != null) { throw reportError!; } return report; } @override Future<void> updateReloadStatus(bool wasReloadSuccessful) async {} } class FakeShaderCompiler implements DevelopmentShaderCompiler { const FakeShaderCompiler(); @override void configureCompiler( TargetPlatform? platform, { required ImpellerStatus impellerStatus, }) { } @override Future<DevFSContent> recompileShader(DevFSContent inputShader) { throw UnimplementedError(); } }