// 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/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_runner/devfs_web.dart'; import 'package:flutter_tools/src/build_runner/resident_web_runner.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/dart/pub.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/project.dart'; import 'package:flutter_tools/src/reporting/reporting.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:mockito/mockito.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; import '../src/common.dart'; import '../src/context.dart'; import '../src/testbed.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: 'streamListen', args: <String, Object>{ 'streamId': 'Extension', }, ), FakeVmServiceRequest( method: 'registerService', args: <String, Object>{ 'service': 'reloadSources', 'alias': 'FlutterTools', } ) ]; const List<VmServiceExpectation> kAttachExpectations = <VmServiceExpectation>[ ...kAttachLogExpectations, ...kAttachIsolateExpectations, ]; void main() { MockDebugConnection mockDebugConnection; MockChromeDevice mockChromeDevice; MockAppConnection mockAppConnection; MockFlutterDevice mockFlutterDevice; MockWebDevFS mockWebDevFS; MockResidentCompiler mockResidentCompiler; MockChrome mockChrome; MockChromeConnection mockChromeConnection; MockChromeTab mockChromeTab; MockWipConnection mockWipConnection; MockWipDebugger mockWipDebugger; MockWebServerDevice mockWebServerDevice; MockDevice mockDevice; FakeVmServiceHost fakeVmServiceHost; FileSystem fileSystem; ProcessManager processManager; setUp(() { fileSystem = MemoryFileSystem.test(); processManager = FakeProcessManager.any(); mockDebugConnection = MockDebugConnection(); mockDevice = MockDevice(); mockAppConnection = MockAppConnection(); mockFlutterDevice = MockFlutterDevice(); mockWebDevFS = MockWebDevFS(); mockResidentCompiler = MockResidentCompiler(); mockChrome = MockChrome(); mockChromeConnection = MockChromeConnection(); mockChromeTab = MockChromeTab(); mockWipConnection = MockWipConnection(); mockWipDebugger = MockWipDebugger(); mockWebServerDevice = MockWebServerDevice(); when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS); when(mockFlutterDevice.device).thenReturn(mockDevice); when(mockWebDevFS.connect(any)).thenAnswer((Invocation invocation) async { return ConnectionResult(mockAppConnection, mockDebugConnection); }); 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); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), bundle: anyNamed('bundle'), firstBuildTime: anyNamed('firstBuildTime'), bundleFirstUpload: anyNamed('bundleFirstUpload'), generator: anyNamed('generator'), fullRestart: anyNamed('fullRestart'), dillOutputPath: anyNamed('dillOutputPath'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), trackWidgetCreation: anyNamed('trackWidgetCreation'), packageConfig: anyNamed('packageConfig'), )).thenAnswer((Invocation _) async { return UpdateFSReport(success: true, syncedBytes: 0); }); when(mockDebugConnection.vmService).thenAnswer((Invocation invocation) { return fakeVmServiceHost.vmService; }); when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) { return Completer<void>().future; }); when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/'); when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS); when(mockWebDevFS.sources).thenReturn(<Uri>[]); when(mockWebDevFS.baseUri).thenReturn(Uri.parse('http://localhost:12345')); when(mockFlutterDevice.generator).thenReturn(mockResidentCompiler); when(mockChrome.chromeConnection).thenReturn(mockChromeConnection); when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async { return mockChromeTab; }); when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async { return mockWipConnection; }); when(mockWipConnection.debugger).thenReturn(mockWipDebugger); } testUsingContext('runner with web server device does not support debugging without --start-paused', () { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); when(mockFlutterDevice.device).thenReturn(WebServerDevice( logger: BufferLogger.test(), )); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: true, urlTunneller: null, ) as ResidentWebRunner; expect(profileResidentWebRunner.debuggingEnabled, false); when(mockFlutterDevice.device).thenReturn(MockChromeDevice()); expect(residentWebRunner.debuggingEnabled, true); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('runner with web server device supports debugging with --start-paused', () { fileSystem.file('.packages') ..createSync(recursive: true) ..writeAsStringSync('\n'); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockFlutterDevice.device).thenReturn(WebServerDevice( logger: BufferLogger.test(), )); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true), ipv6: true, stayResident: true, urlTunneller: null, ); expect(profileResidentWebRunner.uri, mockWebDevFS.baseUri); expect(profileResidentWebRunner.debuggingEnabled, true); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('profile does not supportsServiceProtocol', () { fileSystem.file('.packages') ..createSync(recursive: true) ..writeAsStringSync('\n'); final ResidentRunner residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: true, urlTunneller: null, ) as ResidentWebRunner; fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); when(mockFlutterDevice.device).thenReturn(mockChromeDevice); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile), ipv6: true, stayResident: true, urlTunneller: null, ); expect(profileResidentWebRunner.supportsServiceProtocol, false); expect(residentWebRunner.supportsServiceProtocol, true); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Exits on run if target file does not exist', () async { fileSystem.file('.packages') ..createSync(recursive: true) ..writeAsStringSync('\n'); final ResidentRunner residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: true, urlTunneller: null, ) as ResidentWebRunner; fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fileSystem.file('pubspec.yaml').createSync(); fileSystem.file(fileSystem.path.join('web', 'index.html')) .createSync(recursive: true); expect(await residentWebRunner.run(), 1); final String absoluteMain = fileSystem.path.absolute(fileSystem.path.join('lib', 'main.dart')); expect(testLogger.errorText, contains('Tried to run $absoluteMain, but that file does not exist.')); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Can successfully run and connect to vmservice', () async { fileSystem.file('.packages') ..createSync(recursive: true) ..writeAsStringSync('\n'); final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final BufferLogger bufferLogger = delegateLogger.delegate as BufferLogger; final MockStatus status = MockStatus(); delegateLogger.status = status; final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future; verify(mockAppConnection.runMain()).called(1); verify(status.stop()).called(1); verify(pub.get( context: PubContext.pubGet, directory: anyNamed('directory'), generateSyntheticPackage: false, )).called(1); expect(bufferLogger.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>{ Logger: () => DelegateLogger(BufferLogger.test()), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('WebRunner copies compiled app.dill to cache during startup', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); 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, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); // Regression test for https://github.com/flutter/flutter/issues/60613 testUsingContext('ResidentWebRunner calls appFailedToStart if initial compilation fails', () async { _setupMocks(); final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fileSystem.file(globals.fs.path.join('lib', 'main.dart')) .createSync(recursive: true); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), bundle: anyNamed('bundle'), firstBuildTime: anyNamed('firstBuildTime'), bundleFirstUpload: anyNamed('bundleFirstUpload'), generator: anyNamed('generator'), fullRestart: anyNamed('fullRestart'), dillOutputPath: anyNamed('dillOutputPath'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), trackWidgetCreation: anyNamed('trackWidgetCreation'), packageConfig: anyNamed('packageConfig'), )).thenAnswer((Invocation _) async { return UpdateFSReport(success: false, syncedBytes: 0); }); 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, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Can successfully run without an index.html including status warning', () async { fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); fileSystem.file(fileSystem.path.join('web', 'index.html')) .deleteSync(); final ResidentWebRunner residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: false, urlTunneller: null, ) as ResidentWebRunner; expect(await residentWebRunner.run(), 0); expect(testLogger.statusText, contains('This application is not configured to build on the web')); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Can successfully run and disconnect with --no-resident', () async { fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final ResidentRunner residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: false, urlTunneller: null, ) as ResidentWebRunner; expect(await residentWebRunner.run(), 0); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Listens to stdout and stderr streams before running main', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); 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(testLogger.statusText, contains('THIS MESSAGE IS IMPORTANT')); expect(testLogger.statusText, contains('SO IS THIS')); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Listens to extension events with structured errors', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); final Map<String, String> extensionData = <String, String>{ 'test': 'data', 'renderedErrorText': 'error text', }; final Map<String, String> emptyExtensionData = <String, String>{ 'test': 'data', 'renderedErrorText': '', }; final Map<String, String> nonStructuredErrorData = <String, String>{ 'other': 'other stuff', }; fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Flutter.Error', extensionData: vm_service.ExtensionData.parse(extensionData), 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(emptyExtensionData), kind: vm_service.EventStreams.kExtension, ), ), // This is not Flutter.Error kind data, so it should not be logged. FakeVmServiceStreamResponse( streamId: 'Extension', event: vm_service.Event( timestamp: 0, extensionKind: 'Other', extensionData: vm_service.ExtensionData.parse(nonStructuredErrorData), kind: vm_service.EventStreams.kExtension, ), ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; // Need these to run events, otherwise expect statements below run before // structured errors are processed. await null; await null; await null; expect(testLogger.statusText, contains('\nerror text')); expect(testLogger.statusText, isNot(contains('other stuff'))); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Does not run main with --start-paused', () async { final ResidentRunner residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true), ipv6: true, stayResident: true, urlTunneller: null, ) as ResidentWebRunner; fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; verifyNever(mockAppConnection.runMain()); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Can hot reload after attaching', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'hotRestart', jsonResponse: <String, Object>{ 'type': 'Success', } ), ]); _setupMocks(); final ChromiumLauncher chromiumLauncher = MockChromeLauncher(); when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir'))) .thenAnswer((Invocation invocation) async { return mockChrome; }); when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async { return mockChrome; }); when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice( fileSystem: fileSystem, chromiumLauncher: chromiumLauncher, logger: globals.logger, platform: FakePlatform(operatingSystem: 'linux'), processManager: FakeProcessManager.any(), )); when(chromiumLauncher.canFindExecutable()).thenReturn(true); chromiumLauncher.testLaunchChromium(mockChrome); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), bundle: anyNamed('bundle'), firstBuildTime: anyNamed('firstBuildTime'), bundleFirstUpload: anyNamed('bundleFirstUpload'), generator: anyNamed('generator'), fullRestart: anyNamed('fullRestart'), dillOutputPath: anyNamed('dillOutputPath'), trackWidgetCreation: anyNamed('trackWidgetCreation'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), packageConfig: anyNamed('packageConfig'), )).thenAnswer((Invocation invocation) async { // Generated entrypoint file in temp dir. expect(invocation.namedArguments[#mainUri].toString(), contains('entrypoint.dart')); return 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(fullRestart: false); expect(testLogger.statusText, contains('Restarted application in')); expect(result.code, 0); verify(mockResidentCompiler.accept()).called(2); // ensure that analytics are sent. final Map<String, String> config = verify(globals.flutterUsage.sendEvent('hot', 'restart', parameters: captureAnyNamed('parameters'))).captured.first as Map<String, String>; expect(config, allOf(<Matcher>[ containsPair('cd27', 'web-javascript'), containsPair('cd28', ''), containsPair('cd29', 'false'), containsPair('cd30', 'true'), ])); verify(globals.flutterUsage.sendTiming('hot', 'web-incremental-restart', any)).called(1); }, overrides: <Type, Generator>{ Usage: () => MockFlutterUsage(), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Can hot restart after attaching', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'hotRestart', jsonResponse: <String, Object>{ 'type': 'Success', } ), ]); _setupMocks(); final ChromiumLauncher chromiumLauncher = MockChromeLauncher(); when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir'))) .thenAnswer((Invocation invocation) async { return mockChrome; }); when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async { return mockChrome; }); when(chromiumLauncher.canFindExecutable()).thenReturn(true); when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice( fileSystem: fileSystem, chromiumLauncher: chromiumLauncher, logger: globals.logger, platform: FakePlatform(operatingSystem: 'linux'), processManager: FakeProcessManager.any(), )); chromiumLauncher.testLaunchChromium(mockChrome); Uri entrypointFileUri; when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), bundle: anyNamed('bundle'), firstBuildTime: anyNamed('firstBuildTime'), bundleFirstUpload: anyNamed('bundleFirstUpload'), generator: anyNamed('generator'), fullRestart: anyNamed('fullRestart'), dillOutputPath: anyNamed('dillOutputPath'), trackWidgetCreation: anyNamed('trackWidgetCreation'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), packageConfig: anyNamed('packageConfig'), )).thenAnswer((Invocation invocation) async { entrypointFileUri = invocation.namedArguments[#mainUri] as Uri; return 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(entrypointFileUri, isNotNull); final String entrypointContents = fileSystem.file(entrypointFileUri).readAsStringSync(); expect(entrypointContents, contains('// Flutter web bootstrap script')); expect(entrypointContents, contains("import 'dart:ui' as ui;")); expect(entrypointContents, contains('await ui.webOnlyInitializePlatform();')); expect(testLogger.statusText, contains('Restarted application in')); expect(result.code, 0); verify(mockResidentCompiler.accept()).called(2); // ensure that analytics are sent. final Map<String, String> config = verify(globals.flutterUsage.sendEvent('hot', 'restart', parameters: captureAnyNamed('parameters'))).captured.first as Map<String, String>; expect(config, allOf(<Matcher>[ containsPair('cd27', 'web-javascript'), containsPair('cd28', ''), containsPair('cd29', 'false'), containsPair('cd30', 'true'), ])); verify(globals.flutterUsage.sendTiming('hot', 'web-incremental-restart', any)).called(1); }, overrides: <Type, Generator>{ Usage: () => MockFlutterUsage(), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Can hot restart after attaching with web-server device', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests :kAttachExpectations); _setupMocks(); when(mockFlutterDevice.device).thenReturn(mockWebServerDevice); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), bundle: anyNamed('bundle'), firstBuildTime: anyNamed('firstBuildTime'), bundleFirstUpload: anyNamed('bundleFirstUpload'), generator: anyNamed('generator'), fullRestart: anyNamed('fullRestart'), dillOutputPath: anyNamed('dillOutputPath'), trackWidgetCreation: anyNamed('trackWidgetCreation'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), packageConfig: anyNamed('packageConfig'), )).thenAnswer((Invocation invocation) async { return 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(testLogger.statusText, contains('Restarted application in')); expect(result.code, 0); verify(mockResidentCompiler.accept()).called(2); // ensure that analytics are sent. verifyNever(globals.flutterUsage.sendTiming('hot', 'web-incremental-restart', any)); }, overrides: <Type, Generator>{ Usage: () => MockFlutterUsage(), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('web resident runner is debuggable', () { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); expect(residentWebRunner.debuggingEnabled, true); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('web resident runner can toggle CanvasKit', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final WebAssetServer webAssetServer = WebAssetServer(null, null, null, null, null, null); when(mockWebDevFS.webAssetServer).thenReturn(webAssetServer); expect(residentWebRunner.supportsCanvasKit, true); expect(webAssetServer.canvasKitRendering, false); final bool toggleResult = await residentWebRunner.toggleCanvaskit(); expect(webAssetServer.canvasKitRendering, true); expect(toggleResult, true); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Exits when initial compile fails', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), bundle: anyNamed('bundle'), firstBuildTime: anyNamed('firstBuildTime'), bundleFirstUpload: anyNamed('bundleFirstUpload'), generator: anyNamed('generator'), fullRestart: anyNamed('fullRestart'), dillOutputPath: anyNamed('dillOutputPath'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), packageConfig: anyNamed('packageConfig'), trackWidgetCreation: anyNamed('trackWidgetCreation'), )).thenAnswer((Invocation _) async { return UpdateFSReport(success: false, syncedBytes: 0); }); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); expect(await residentWebRunner.run(), 1); verifyNever(globals.flutterUsage.sendTiming('hot', 'web-restart', any)); }, overrides: <Type, Generator>{ Usage: () => MockFlutterUsage(), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Faithfully displays stdout messages with leading/trailing spaces', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); 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(testLogger.statusText, contains(' This is a message with 4 leading and trailing spaces ')); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Fails on compilation errors in hot restart', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), bundle: anyNamed('bundle'), firstBuildTime: anyNamed('firstBuildTime'), bundleFirstUpload: anyNamed('bundleFirstUpload'), generator: anyNamed('generator'), fullRestart: anyNamed('fullRestart'), dillOutputPath: anyNamed('dillOutputPath'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), packageConfig: anyNamed('packageConfig'), trackWidgetCreation: anyNamed('trackWidgetCreation'), )).thenAnswer((Invocation _) async { return UpdateFSReport(success: false, syncedBytes: 0); }); final OperationResult result = await residentWebRunner.restart(fullRestart: true); expect(result.code, 1); expect(result.message, contains('Failed to recompile application.')); verifyNever(globals.flutterUsage.sendTiming('hot', 'web-restart', any)); }, overrides: <Type, Generator>{ Usage: () => MockFlutterUsage(), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Fails non-fatally on vmservice response error for hot restart', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'hotRestart', 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(fullRestart: false); expect(result.code, 0); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Fails fatally on Vm Service error response', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'hotRestart', // 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(fullRestart: false); expect(result.code, 1); expect(result.message, contains(RPCErrorCodes.kInternalError.toString())); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('printHelp without details has web warning', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); residentWebRunner.printHelp(details: false); expect(testLogger.statusText, contains('Warning')); expect(testLogger.statusText, contains('https://flutter.dev/web')); expect(testLogger.statusText, isNot(contains('https://flutter.dev/web.'))); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugDumpApp', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.debugDumpApp', args: <String, Object>{ 'isolateId': null, }, ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugDumpApp(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugDumpLayerTree', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.debugDumpLayerTree', args: <String, Object>{ 'isolateId': null, }, ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugDumpLayerTree(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugDumpRenderTree', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.debugDumpRenderTree', args: <String, Object>{ 'isolateId': null, }, ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugDumpRenderTree(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugDumpSemanticsTreeInTraversalOrder', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder', args: <String, Object>{ 'isolateId': null, }, ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugDumpSemanticsTreeInTraversalOrder(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugDumpSemanticsTreeInInverseHitTestOrder', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', args: <String, Object>{ 'isolateId': null, }, ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugDumpSemanticsTreeInInverseHitTestOrder(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugToggleDebugPaintSizeEnabled', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.debugPaint', args: <String, Object>{ 'isolateId': null, }, jsonResponse: <String, Object>{ 'enabled': 'false' }, ), const FakeVmServiceRequest( method: 'ext.flutter.debugPaint', args: <String, Object>{ 'isolateId': null, 'enabled': 'true', }, jsonResponse: <String, Object>{ 'value': 'true' }, ) ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugToggleDebugPaintSizeEnabled(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugTogglePerformanceOverlayOverride', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.showPerformanceOverlay', args: <String, Object>{ 'isolateId': null, }, jsonResponse: <String, Object>{ 'enabled': 'false' }, ), const FakeVmServiceRequest( method: 'ext.flutter.showPerformanceOverlay', args: <String, Object>{ 'isolateId': null, 'enabled': 'true', }, jsonResponse: <String, Object>{ 'enabled': 'true' }, ) ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugTogglePerformanceOverlayOverride(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugToggleInvertOversizedImagesOverride', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.invertOversizedImages', args: <String, Object>{ 'isolateId': null, }, jsonResponse: <String, Object>{ 'enabled': 'false' }, ), const FakeVmServiceRequest( method: 'ext.flutter.invertOversizedImages', args: <String, Object>{ 'isolateId': null, 'enabled': 'true', }, jsonResponse: <String, Object>{ 'enabled': 'true' }, ) ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugToggleInvertOversizedImages(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugToggleWidgetInspector', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.inspector.show', args: <String, Object>{ 'isolateId': null, }, jsonResponse: <String, Object>{ 'enabled': 'false' }, ), const FakeVmServiceRequest( method: 'ext.flutter.inspector.show', args: <String, Object>{ 'isolateId': null, 'enabled': 'true', }, jsonResponse: <String, Object>{ 'enabled': 'true' }, ) ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugToggleWidgetInspector(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugToggleProfileWidgetBuilds', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.profileWidgetBuilds', args: <String, Object>{ 'isolateId': null, }, jsonResponse: <String, Object>{ 'enabled': 'false' }, ), const FakeVmServiceRequest( method: 'ext.flutter.profileWidgetBuilds', args: <String, Object>{ 'isolateId': null, 'enabled': 'true', }, jsonResponse: <String, Object>{ 'enabled': 'true' }, ) ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugToggleProfileWidgetBuilds(); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugTogglePlatform', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.platformOverride', args: <String, Object>{ 'isolateId': null, }, jsonResponse: <String, Object>{ 'value': 'iOS' }, ), const FakeVmServiceRequest( method: 'ext.flutter.platformOverride', args: <String, Object>{ 'isolateId': null, 'value': 'fuchsia', }, jsonResponse: <String, Object>{ 'value': 'fuchsia' }, ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugTogglePlatform(); expect(testLogger.statusText, contains('Switched operating system to fuchsia')); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('debugToggleBrightness', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, const FakeVmServiceRequest( method: 'ext.flutter.brightnessOverride', args: <String, Object>{ 'isolateId': null, }, jsonResponse: <String, Object>{ 'value': 'Brightness.light' }, ), const FakeVmServiceRequest( method: 'ext.flutter.brightnessOverride', args: <String, Object>{ 'isolateId': null, 'value': 'Brightness.dark', }, jsonResponse: <String, Object>{ 'value': 'Brightness.dark' }, ), ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.debugToggleBrightness(); expect(testLogger.statusText, contains('Changed brightness to Brightness.dark.')); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('cleanup of resources is safe to call multiple times', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, ]); _setupMocks(); bool debugClosed = false; when(mockDevice.stopApp(any, userIdentifier: anyNamed('userIdentifier'))).thenAnswer((Invocation invocation) async { if (debugClosed) { throw StateError('debug connection closed twice'); } debugClosed = true; return true; }); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; await residentWebRunner.exit(); await residentWebRunner.exit(); verifyNever(mockDebugConnection.close()); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('cleans up Chrome if tab is closed', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, ]); _setupMocks(); final Completer<void> onDone = Completer<void>(); when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) { return onDone.future; }); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); final Future<int> result = residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, ); await connectionInfoCompleter.future; onDone.complete(); await result; expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Prints target and device name on run', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachExpectations, ]); _setupMocks(); when(mockDevice.name).thenReturn('Chromez'); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; expect(testLogger.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, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Sends launched app.webLaunchUrl event for Chrome device', () async { fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ ...kAttachLogExpectations, ...kAttachIsolateExpectations, ]); _setupMocks(); final ChromiumLauncher chromiumLauncher = MockChromeLauncher(); when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir'))) .thenAnswer((Invocation invocation) async { return mockChrome; }); when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async { return mockChrome; }); when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice( fileSystem: fileSystem, chromiumLauncher: chromiumLauncher, logger: globals.logger, platform: FakePlatform(operatingSystem: 'linux'), processManager: FakeProcessManager.any(), )); when(chromiumLauncher.canFindExecutable()).thenReturn(true); when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { return Uri.parse('http://localhost:8765/app/'); }); final MockChrome chrome = MockChrome(); final MockChromeConnection mockChromeConnection = MockChromeConnection(); final MockChromeTab mockChromeTab = MockChromeTab(); final MockWipConnection mockWipConnection = MockWipConnection(); when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async { return mockChromeTab; }); when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async { return mockWipConnection; }); when(chrome.chromeConnection).thenReturn(mockChromeConnection); chromiumLauncher.testLaunchChromium(chrome); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); delegateLogger.status = mockStatus; final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: true, urlTunneller: null, ) as ResidentWebRunner; 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((delegateLogger.delegate as BufferLogger).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>{ Logger: () => DelegateLogger(BufferLogger.test()), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Sends unlaunched app.webLaunchUrl event for Web Server device', () async { fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockFlutterDevice.device).thenReturn(WebServerDevice( logger: globals.logger, )); when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { return Uri.parse('http://localhost:8765/app/'); }); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); delegateLogger.status = mockStatus; final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: true, urlTunneller: null, ) as ResidentWebRunner; 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((delegateLogger.delegate as BufferLogger).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>{ Logger: () => DelegateLogger(BufferLogger.test()), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Successfully turns WebSocketException into ToolExit', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockWebDevFS.connect(any)) .thenThrow(const WebSocketException()); await expectLater(residentWebRunner.run, throwsToolExit()); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Successfully turns AppConnectionException into ToolExit', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockWebDevFS.connect(any)) .thenThrow(AppConnectionException('')); await expectLater(residentWebRunner.run, throwsToolExit()); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Successfully turns ChromeDebugError into ToolExit', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockWebDevFS.connect(any)) .thenThrow(ChromeDebugException(<String, dynamic>{})); await expectLater(residentWebRunner.run, throwsToolExit()); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Rethrows unknown Exception type from dwds', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockWebDevFS.connect(any)).thenThrow(Exception()); await expectLater(residentWebRunner.run, throwsException); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); testUsingContext('Rethrows unknown Error type from dwds tooling', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); delegateLogger.status = mockStatus; when(mockWebDevFS.connect(any)).thenThrow(StateError('')); await expectLater(residentWebRunner.run, throwsStateError); verify(mockStatus.stop()).called(1); expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ Logger: () => DelegateLogger(BufferLogger( terminal: AnsiTerminal( stdio: null, platform: const LocalPlatform(), ), outputPreferences: OutputPreferences.test(), )), FileSystem: () => fileSystem, ProcessManager: () => processManager, Pub: () => MockPub(), Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}), }); } ResidentRunner setUpResidentRunner(FlutterDevice flutterDevice) { return DwdsWebRunnerFactory().createWebRunner( flutterDevice, flutterProject: FlutterProject.current(), debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), ipv6: true, stayResident: true, urlTunneller: null, ) as ResidentWebRunner; } class MockChromeLauncher extends Mock implements ChromiumLauncher {} class MockFlutterUsage extends Mock implements Usage {} class MockChromeDevice extends Mock implements ChromiumDevice {} class MockDebugConnection extends Mock implements DebugConnection {} class MockAppConnection extends Mock implements AppConnection {} class MockVmService extends Mock implements VmService {} class MockStatus extends Mock implements Status {} class MockFlutterDevice extends Mock implements FlutterDevice {} class MockWebDevFS extends Mock implements WebDevFS {} class MockResidentCompiler extends Mock implements ResidentCompiler {} class MockChrome extends Mock implements Chromium {} class MockChromeConnection extends Mock implements ChromeConnection {} class MockChromeTab extends Mock implements ChromeTab {} class MockWipConnection extends Mock implements WipConnection {} class MockWipDebugger extends Mock implements WipDebugger {} class MockWebServerDevice extends Mock implements WebServerDevice {} class MockDevice extends Mock implements Device {} class MockPub extends Mock implements Pub {}