| // 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 'package:file/file.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/android/android_device.dart'; |
| import 'package:flutter_tools/src/android/android_sdk.dart'; |
| import 'package:flutter_tools/src/application_package.dart'; |
| import 'package:flutter_tools/src/artifacts.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/base/user_messages.dart'; |
| import 'package:flutter_tools/src/build_info.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/commands/daemon.dart'; |
| import 'package:flutter_tools/src/commands/run.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/runner/flutter_command.dart'; |
| import 'package:flutter_tools/src/vmservice.dart'; |
| import 'package:test/fake.dart'; |
| import 'package:vm_service/vm_service.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/fake_devices.dart'; |
| import '../../src/fakes.dart'; |
| import '../../src/test_flutter_command_runner.dart'; |
| |
| void main() { |
| setUpAll(() { |
| Cache.disableLocking(); |
| }); |
| |
| group('run', () { |
| late TestDeviceManager testDeviceManager; |
| late FileSystem fileSystem; |
| |
| setUp(() { |
| testDeviceManager = TestDeviceManager(logger: BufferLogger.test()); |
| fileSystem = MemoryFileSystem.test(); |
| }); |
| |
| testUsingContext('fails when target not found', () async { |
| final RunCommand command = RunCommand(); |
| expect( |
| () => createTestCommandRunner(command).run(<String>['run', '-t', 'abc123', '--no-pub']), |
| throwsA(isA<ToolExit>().having((ToolExit error) => error.exitCode, 'exitCode', anyOf(isNull, 1))), |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => BufferLogger.test(), |
| }); |
| |
| testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async { |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.file('.packages').createSync(); |
| |
| final RunCommand command = RunCommand(); |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--use-application-binary=app/bar/faz', |
| '--fast-start', |
| '--no-pub', |
| '--show-test-device', |
| ]), |
| throwsA(isException.having( |
| (Exception exception) => exception.toString(), |
| 'toString', |
| isNot(contains('--fast-start is not supported with --use-application-binary')), |
| )), |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => BufferLogger.test(), |
| }); |
| |
| testUsingContext('Walks upward looking for a pubspec.yaml and succeeds if found', () async { |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.file('.packages') |
| .writeAsStringSync('\n'); |
| fileSystem.file('lib/main.dart') |
| .createSync(recursive: true); |
| fileSystem.currentDirectory = fileSystem.directory('a/b/c') |
| ..createSync(recursive: true); |
| |
| final RunCommand command = RunCommand(); |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| ]), |
| throwsToolExit(), |
| ); |
| final BufferLogger bufferLogger = globals.logger as BufferLogger; |
| expect( |
| bufferLogger.statusText, |
| containsIgnoringWhitespace('Changing current working directory to:'), |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => BufferLogger.test(), |
| }); |
| |
| testUsingContext('Walks upward looking for a pubspec.yaml and exits if missing', () async { |
| fileSystem.currentDirectory = fileSystem.directory('a/b/c') |
| ..createSync(recursive: true); |
| fileSystem.file('lib/main.dart') |
| .createSync(recursive: true); |
| |
| final RunCommand command = RunCommand(); |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| ]), |
| throwsToolExit(message: 'No pubspec.yaml file found'), |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => BufferLogger.test(), |
| }); |
| |
| group('run app', () { |
| late MemoryFileSystem fs; |
| late Artifacts artifacts; |
| late TestUsage usage; |
| late FakeAnsiTerminal fakeTerminal; |
| |
| setUpAll(() { |
| Cache.disableLocking(); |
| }); |
| |
| setUp(() { |
| fakeTerminal = FakeAnsiTerminal(); |
| artifacts = Artifacts.test(); |
| usage = TestUsage(); |
| fs = MemoryFileSystem.test(); |
| |
| fs.currentDirectory.childFile('pubspec.yaml') |
| .writeAsStringSync('name: flutter_app'); |
| fs.currentDirectory.childFile('.packages') |
| .writeAsStringSync('# Generated by pub on 2019-11-25 12:38:01.801784.'); |
| final Directory libDir = fs.currentDirectory.childDirectory('lib'); |
| libDir.createSync(); |
| final File mainFile = libDir.childFile('main.dart'); |
| mainFile.writeAsStringSync('void main() {}'); |
| }); |
| |
| testUsingContext('exits with a user message when no supported devices attached', () async { |
| final RunCommand command = RunCommand(); |
| testDeviceManager.devices = <Device>[]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| ]), |
| throwsA(isA<ToolExit>().having((ToolExit error) => error.message, 'message', isNull)), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace(userMessages.flutterNoSupportedDevices), |
| ); |
| }, overrides: <Type, Generator>{ |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| }); |
| |
| testUsingContext('exits and lists available devices when specified device not found', () async { |
| final RunCommand command = RunCommand(); |
| final FakeDevice device = FakeDevice(isLocalEmulator: true); |
| testDeviceManager |
| ..devices = <Device>[device] |
| ..specifiedDeviceId = 'invalid-device-id'; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '-d', |
| 'invalid-device-id', |
| '--no-pub', |
| '--no-hot', |
| ]), |
| throwsToolExit(), |
| ); |
| expect(testLogger.statusText, contains("No supported devices found with name or id matching 'invalid-device-id'")); |
| expect(testLogger.statusText, contains('The following devices were found:')); |
| expect(testLogger.statusText, contains('FakeDevice (mobile) • fake_device • ios • (simulator)')); |
| }, overrides: <Type, Generator>{ |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| }); |
| |
| testUsingContext('fails when targeted device is not Android with --device-user', () async { |
| final FakeDevice device = FakeDevice(isLocalEmulator: true); |
| |
| testDeviceManager.devices = <Device>[device]; |
| |
| final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates(); |
| await expectLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--device-user', |
| '10', |
| ]), throwsToolExit(message: '--device-user is only supported for Android. At least one Android device is required.')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| Stdio: () => FakeStdio(), |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| }); |
| |
| testUsingContext('succeeds when targeted device is an Android device with --device-user', () async { |
| final FakeDevice device = FakeDevice(isLocalEmulator: true, platformType: PlatformType.android); |
| |
| testDeviceManager.devices = <Device>[device]; |
| |
| final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates(); |
| await createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--device-user', |
| '10', |
| ]); |
| // Finishes normally without error. |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| Stdio: () => FakeStdio(), |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| }); |
| |
| testUsingContext('fails when v1 FlutterApplication is detected', () async { |
| fs.file('pubspec.yaml').createSync(); |
| fs.file('android/AndroidManifest.xml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.example.v1"> |
| <application |
| android:name="io.flutter.app.FlutterApplication"> |
| </application> |
| </manifest> |
| ''', flush: true); |
| fs.file('.packages').writeAsStringSync('\n'); |
| fs.file('lib/main.dart').createSync(recursive: true); |
| final AndroidDevice device = AndroidDevice('1234', |
| modelID: 'TestModel', |
| logger: testLogger, |
| platform: FakePlatform(), |
| androidSdk: FakeAndroidSdk(), |
| fileSystem: fs, |
| processManager: FakeProcessManager.any(), |
| ); |
| |
| testDeviceManager.devices = <Device>[device]; |
| |
| final RunCommand command = RunCommand(); |
| await expectLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--pub', |
| ]), throwsToolExit(message: 'Build failed due to use of deprecated Android v1 embedding.')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| Stdio: () => FakeStdio(), |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| }); |
| |
| testUsingContext('fails when v1 metadata is detected', () async { |
| fs.file('pubspec.yaml').createSync(); |
| fs.file('android/AndroidManifest.xml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.example.v1"> |
| <application > |
| <meta-data |
| android:name="flutterEmbedding" |
| android:value="1" /> |
| </application> |
| </manifest> |
| ''', flush: true); |
| fs.file('.packages').writeAsStringSync('\n'); |
| fs.file('lib/main.dart').createSync(recursive: true); |
| final AndroidDevice device = AndroidDevice('1234', |
| modelID: 'TestModel', |
| logger: testLogger, |
| platform: FakePlatform(), |
| androidSdk: FakeAndroidSdk(), |
| fileSystem: fs, |
| processManager: FakeProcessManager.any(), |
| ); |
| |
| testDeviceManager.devices = <Device>[device]; |
| |
| final RunCommand command = RunCommand(); |
| await expectLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--pub', |
| ]), throwsToolExit(message: 'Build failed due to use of deprecated Android v1 embedding.')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| Stdio: () => FakeStdio(), |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| }); |
| |
| testUsingContext('shows unsupported devices when no supported devices are found', () async { |
| final RunCommand command = RunCommand(); |
| final FakeDevice mockDevice = FakeDevice( |
| targetPlatform: TargetPlatform.android_arm, |
| isLocalEmulator: true, |
| sdkNameAndVersion: 'api-14', |
| isSupported: false, |
| ); |
| testDeviceManager.devices = <Device>[mockDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| ]), |
| throwsA(isA<ToolExit>().having((ToolExit error) => error.message, 'message', isNull)), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace(userMessages.flutterNoSupportedDevices), |
| ); |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace(userMessages.flutterFoundButUnsupportedDevices), |
| ); |
| expect( |
| testLogger.statusText, |
| containsIgnoringWhitespace( |
| userMessages.flutterMissPlatformProjects( |
| Device.devicesPlatformTypes(<Device>[mockDevice]), |
| ), |
| ), |
| ); |
| }, overrides: <Type, Generator>{ |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| }); |
| |
| testUsingContext('forwards --uninstall-only to DebuggingOptions', () async { |
| final RunCommand command = RunCommand(); |
| final FakeDevice mockDevice = FakeDevice( |
| sdkNameAndVersion: 'iOS 13', |
| )..startAppSuccess = false; |
| |
| testDeviceManager.devices = <Device>[mockDevice]; |
| |
| // Causes swift to be detected in the analytics. |
| fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true); |
| |
| await expectToolExitLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| '--uninstall-first', |
| ]), isNull); |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(false); |
| expect(options.uninstallFirst, isTrue); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => usage, |
| }); |
| |
| testUsingContext('passes device target platform to usage', () async { |
| final RunCommand command = RunCommand(); |
| final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13') |
| ..startAppSuccess = false; |
| |
| testDeviceManager.devices = <Device>[mockDevice]; |
| |
| // Causes swift to be detected in the analytics. |
| fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true); |
| |
| await expectToolExitLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| ]), isNull); |
| |
| expect(usage.commands, contains( |
| TestUsageCommand('run', parameters: CustomDimensions.fromMap(<String, String>{ |
| 'cd3': 'false', 'cd4': 'ios', 'cd22': 'iOS 13', |
| 'cd23': 'debug', 'cd18': 'false', 'cd15': 'swift', 'cd31': 'true', |
| 'cd56': 'false', |
| }) |
| ))); |
| }, overrides: <Type, Generator>{ |
| AnsiTerminal: () => fakeTerminal, |
| Artifacts: () => artifacts, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Stdio: () => FakeStdio(), |
| Usage: () => usage, |
| }); |
| |
| group('--machine', () { |
| testUsingContext('enables multidex by default', () async { |
| final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); |
| final FakeDevice device = FakeDevice(); |
| testDeviceManager.devices = <Device>[device]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--machine', |
| '-d', |
| device.id, |
| ]), |
| throwsToolExit(), |
| ); |
| expect(command.appDomain.multidexEnabled, isTrue); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => usage, |
| Stdio: () => FakeStdio(), |
| Logger: () => AppRunLogger(parent: BufferLogger.test()), |
| }); |
| |
| testUsingContext('can disable multidex with --no-multidex', () async { |
| final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); |
| final FakeDevice device = FakeDevice(); |
| testDeviceManager.devices = <Device>[device]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-multidex', |
| '--machine', |
| '-d', |
| device.id, |
| ]), |
| throwsToolExit(), |
| ); |
| expect(command.appDomain.multidexEnabled, isFalse); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => usage, |
| Stdio: () => FakeStdio(), |
| Logger: () => AppRunLogger(parent: BufferLogger.test()), |
| }); |
| |
| testUsingContext('can pass --device-user', () async { |
| final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); |
| final FakeDevice device = FakeDevice(platformType: PlatformType.android); |
| testDeviceManager.devices = <Device>[device]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--machine', |
| '--device-user', |
| '10', |
| '-d', |
| device.id, |
| ]), |
| throwsToolExit(), |
| ); |
| expect(command.appDomain.userIdentifier, '10'); |
| }, overrides: <Type, Generator>{ |
| Artifacts: () => artifacts, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| DeviceManager: () => testDeviceManager, |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => usage, |
| Stdio: () => FakeStdio(), |
| Logger: () => AppRunLogger(parent: BufferLogger.test()), |
| }); |
| }); |
| }); |
| |
| group('Fatal Logs', () { |
| late TestRunCommandWithFakeResidentRunner command; |
| late MemoryFileSystem fs; |
| |
| setUp(() { |
| command = TestRunCommandWithFakeResidentRunner() |
| ..fakeResidentRunner = FakeResidentRunner(); |
| fs = MemoryFileSystem.test(); |
| }); |
| |
| testUsingContext("doesn't fail if --fatal-warnings specified and no warnings occur", () async { |
| try { |
| await createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| '--${FlutterOptions.kFatalWarnings}', |
| ]); |
| } on Exception { |
| fail('Unexpected exception thrown'); |
| } |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext("doesn't fail if --fatal-warnings not specified", () async { |
| testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); |
| try { |
| await createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| ]); |
| } on Exception { |
| fail('Unexpected exception thrown'); |
| } |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('fails if --fatal-warnings specified and warnings emitted', () async { |
| testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); |
| await expectLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| '--${FlutterOptions.kFatalWarnings}', |
| ]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('fails if --fatal-warnings specified and errors emitted', () async { |
| testLogger.printError('Error: Danger Will Robinson!'); |
| await expectLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--no-hot', |
| '--${FlutterOptions.kFatalWarnings}', |
| ]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| testUsingContext('should only request artifacts corresponding to connected devices', () async { |
| testDeviceManager.devices = <Device>[FakeDevice(targetPlatform: TargetPlatform.android_arm)]; |
| |
| expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{ |
| DevelopmentArtifact.universal, |
| DevelopmentArtifact.androidGenSnapshot, |
| })); |
| |
| testDeviceManager.devices = <Device>[FakeDevice()]; |
| |
| expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{ |
| DevelopmentArtifact.universal, |
| DevelopmentArtifact.iOS, |
| })); |
| |
| testDeviceManager.devices = <Device>[ |
| FakeDevice(), |
| FakeDevice(targetPlatform: TargetPlatform.android_arm), |
| ]; |
| |
| expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{ |
| DevelopmentArtifact.universal, |
| DevelopmentArtifact.iOS, |
| DevelopmentArtifact.androidGenSnapshot, |
| })); |
| |
| testDeviceManager.devices = <Device>[ |
| FakeDevice(targetPlatform: TargetPlatform.web_javascript), |
| ]; |
| |
| expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{ |
| DevelopmentArtifact.universal, |
| DevelopmentArtifact.web, |
| })); |
| }, overrides: <Type, Generator>{ |
| DeviceManager: () => testDeviceManager, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| group('dart-defines and web-renderer options', () { |
| late List<String> dartDefines; |
| |
| setUp(() { |
| dartDefines = <String>[]; |
| }); |
| |
| test('auto web-renderer with no dart-defines', () { |
| dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'auto'); |
| expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']); |
| }); |
| |
| test('canvaskit web-renderer with no dart-defines', () { |
| dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'canvaskit'); |
| expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']); |
| }); |
| |
| test('html web-renderer with no dart-defines', () { |
| dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'html'); |
| expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']); |
| }); |
| |
| test('auto web-renderer with existing dart-defines', () { |
| dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false']; |
| dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'auto'); |
| expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']); |
| }); |
| |
| test('canvaskit web-renderer with no dart-defines', () { |
| dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false']; |
| dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'canvaskit'); |
| expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']); |
| }); |
| |
| test('html web-renderer with no dart-defines', () { |
| dartDefines = <String>['FLUTTER_WEB_USE_SKIA=true']; |
| dartDefines = FlutterCommand.updateDartDefines(dartDefines, 'html'); |
| expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']); |
| }); |
| }); |
| |
| group('terminal', () { |
| late FakeAnsiTerminal fakeTerminal; |
| |
| setUp(() { |
| fakeTerminal = FakeAnsiTerminal(); |
| }); |
| |
| testUsingContext('Flutter run sets terminal singleCharMode to false on exit', () async { |
| final FakeResidentRunner residentRunner = FakeResidentRunner(); |
| final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
| command.fakeResidentRunner = residentRunner; |
| |
| await createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| ]); |
| // The sync completer where we initially set `terminal.singleCharMode` to |
| // `true` does not execute in unit tests, so explicitly check the |
| // `setSingleCharModeHistory` that the finally block ran, setting this |
| // back to `false`. |
| expect(fakeTerminal.setSingleCharModeHistory, contains(false)); |
| }, overrides: <Type, Generator>{ |
| AnsiTerminal: () => fakeTerminal, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('Flutter run catches StdinException while setting terminal singleCharMode to false', () async { |
| fakeTerminal.hasStdin = false; |
| final FakeResidentRunner residentRunner = FakeResidentRunner(); |
| final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
| command.fakeResidentRunner = residentRunner; |
| |
| try { |
| await createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| ]); |
| } catch (err) { // ignore: avoid_catches_without_on_clauses |
| fail('Expected no error, got $err'); |
| } |
| expect(fakeTerminal.setSingleCharModeHistory, isEmpty); |
| }, overrides: <Type, Generator>{ |
| AnsiTerminal: () => fakeTerminal, |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| testUsingContext('Flutter run catches service has disappear errors and throws a tool exit', () async { |
| final FakeResidentRunner residentRunner = FakeResidentRunner(); |
| residentRunner.rpcError = RPCError('flutter._listViews', RPCErrorCodes.kServiceDisappeared, ''); |
| final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
| command.fakeResidentRunner = residentRunner; |
| |
| await expectToolExitLater(createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| ]), contains('Lost connection to device.')); |
| }, overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('Flutter run does not catch other RPC errors', () async { |
| final FakeResidentRunner residentRunner = FakeResidentRunner(); |
| residentRunner.rpcError = RPCError('flutter._listViews', RPCErrorCodes.kInvalidParams, ''); |
| final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
| command.fakeResidentRunner = residentRunner; |
| |
| await expectLater(() => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| ]), throwsA(isA<RPCError>())); |
| }, overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('Passes sksl bundle info the build options', () async { |
| final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner(); |
| |
| await expectLater(() => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| '--bundle-sksl-path=foo.json', |
| ]), throwsToolExit(message: 'No SkSL shader bundle found at foo.json')); |
| }, overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('Configures web connection options to use web sockets by default', () async { |
| final RunCommand command = RunCommand(); |
| await expectLater(() => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--no-pub', |
| ]), throwsToolExit()); |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(true); |
| |
| expect(options.webUseSseForDebugBackend, false); |
| expect(options.webUseSseForDebugProxy, false); |
| expect(options.webUseSseForInjectedClient, false); |
| }, overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('flags propagate to debugging options', () async { |
| final RunCommand command = RunCommand(); |
| await expectLater(() => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--start-paused', |
| '--disable-service-auth-codes', |
| '--use-test-fonts', |
| '--trace-skia', |
| '--trace-systrace', |
| '--verbose-system-logs', |
| '--null-assertions', |
| '--native-null-assertions', |
| '--enable-impeller', |
| '--trace-systrace', |
| '--enable-software-rendering', |
| '--skia-deterministic-rendering', |
| ]), throwsToolExit()); |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(false); |
| |
| expect(options.startPaused, true); |
| expect(options.disableServiceAuthCodes, true); |
| expect(options.useTestFonts, true); |
| expect(options.traceSkia, true); |
| expect(options.traceSystrace, true); |
| expect(options.verboseSystemLogs, true); |
| expect(options.nullAssertions, true); |
| expect(options.nativeNullAssertions, true); |
| expect(options.traceSystrace, true); |
| expect(options.enableImpeller, true); |
| expect(options.enableSoftwareRendering, true); |
| expect(options.skiaDeterministicRendering, true); |
| }, overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('fails when "--web-launch-url" is not supported', () async { |
| final RunCommand command = RunCommand(); |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'run', |
| '--web-launch-url=http://flutter.dev', |
| ]), |
| throwsA(isException.having( |
| (Exception exception) => exception.toString(), |
| 'toString', |
| isNot(contains('web-launch-url')), |
| )), |
| ); |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(true); |
| expect(options.webLaunchUrl, 'http://flutter.dev'); |
| |
| final RegExp pattern = RegExp(r'^((http)?:\/\/)[^\s]+'); |
| expect(pattern.hasMatch(options.webLaunchUrl!), true); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => BufferLogger.test(), |
| }); |
| } |
| |
| class TestDeviceManager extends DeviceManager { |
| TestDeviceManager({required this.logger}) : super(logger: logger); |
| List<Device> devices = <Device>[]; |
| |
| final Logger logger; |
| |
| @override |
| List<DeviceDiscovery> get deviceDiscoverers { |
| final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery(); |
| devices.forEach(discoverer.addDevice); |
| return <DeviceDiscovery>[discoverer]; |
| } |
| } |
| |
| class FakeAndroidSdk extends Fake implements AndroidSdk { |
| @override |
| String get adbPath => 'adb'; |
| } |
| |
| // 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 { |
| FakeDevice({ |
| bool isLocalEmulator = false, |
| TargetPlatform targetPlatform = TargetPlatform.ios, |
| String sdkNameAndVersion = '', |
| PlatformType platformType = PlatformType.ios, |
| bool isSupported = true, |
| }): _isLocalEmulator = isLocalEmulator, |
| _targetPlatform = targetPlatform, |
| _sdkNameAndVersion = sdkNameAndVersion, |
| _platformType = platformType, |
| _isSupported = isSupported; |
| |
| static const int kSuccess = 1; |
| static const int kFailure = -1; |
| final TargetPlatform _targetPlatform; |
| final bool _isLocalEmulator; |
| final String _sdkNameAndVersion; |
| final PlatformType _platformType; |
| final bool _isSupported; |
| |
| @override |
| Category get category => Category.mobile; |
| |
| @override |
| String get id => 'fake_device'; |
| |
| Never _throwToolExit(int code) => throwToolExit('FakeDevice tool exit', exitCode: code); |
| |
| @override |
| Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator); |
| |
| @override |
| bool supportsRuntimeMode(BuildMode mode) => true; |
| |
| @override |
| Future<bool> get supportsHardwareRendering async => true; |
| |
| @override |
| bool supportsHotReload = false; |
| |
| @override |
| bool get supportsHotRestart => true; |
| |
| @override |
| bool get supportsFastStart => false; |
| |
| bool supported = true; |
| |
| @override |
| bool isSupportedForProject(FlutterProject flutterProject) => _isSupported; |
| |
| @override |
| bool isSupported() => supported; |
| |
| @override |
| Future<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion); |
| |
| @override |
| Future<String> get targetPlatformDisplayName async => |
| getNameForTargetPlatform(await targetPlatform); |
| |
| @override |
| DeviceLogReader getLogReader({ |
| ApplicationPackage? app, |
| bool includePastLogs = false, |
| }) { |
| return FakeDeviceLogReader(); |
| } |
| |
| @override |
| String get name => 'FakeDevice'; |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => _targetPlatform; |
| |
| @override |
| PlatformType get platformType => _platformType; |
| |
| late bool startAppSuccess; |
| |
| @override |
| DevFSWriter? createDevFSWriter( |
| covariant ApplicationPackage? app, |
| String? userIdentifier, |
| ) { |
| return null; |
| } |
| |
| @override |
| Future<LaunchResult> startApp( |
| ApplicationPackage? package, { |
| String? mainPath, |
| String? route, |
| required DebuggingOptions debuggingOptions, |
| Map<String, Object?> platformArgs = const <String, Object?>{}, |
| bool prebuiltApplication = false, |
| bool usesTerminalUi = true, |
| bool ipv6 = false, |
| String? userIdentifier, |
| }) async { |
| if (startAppSuccess == false) { |
| return LaunchResult.failed(); |
| } |
| if (startAppSuccess == true) { |
| return LaunchResult.succeeded(); |
| } |
| final String dartFlags = debuggingOptions.dartFlags; |
| // In release mode, --dart-flags should be set to the empty string and |
| // provided flags should be dropped. In debug and profile modes, |
| // --dart-flags should not be empty. |
| if (debuggingOptions.buildInfo.isRelease) { |
| if (dartFlags.isNotEmpty) { |
| _throwToolExit(kFailure); |
| } |
| _throwToolExit(kSuccess); |
| } else { |
| if (dartFlags.isEmpty) { |
| _throwToolExit(kFailure); |
| } |
| _throwToolExit(kSuccess); |
| } |
| } |
| } |
| |
| class TestRunCommandWithFakeResidentRunner extends RunCommand { |
| late FakeResidentRunner fakeResidentRunner; |
| |
| @override |
| Future<ResidentRunner> createRunner({ |
| required bool hotMode, |
| required List<FlutterDevice> flutterDevices, |
| required String? applicationBinaryPath, |
| required FlutterProject flutterProject, |
| }) async { |
| return fakeResidentRunner; |
| } |
| |
| @override |
| // ignore: must_call_super |
| Future<void> validateCommand() async { |
| devices = <Device>[FakeDevice()..supportsHotReload = true]; |
| } |
| } |
| |
| class TestRunCommandThatOnlyValidates extends RunCommand { |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| return FlutterCommandResult.success(); |
| } |
| } |
| |
| class FakeResidentRunner extends Fake implements ResidentRunner { |
| RPCError? rpcError; |
| |
| @override |
| Future<int> run({ |
| Completer<DebugConnectionInfo>? connectionInfoCompleter, |
| Completer<void>? appStartedCompleter, |
| bool enableDevTools = false, |
| String? route, |
| }) async { |
| await null; |
| if (rpcError != null) { |
| throw rpcError!; |
| } |
| return 0; |
| } |
| } |
| |
| class DaemonCapturingRunCommand extends RunCommand { |
| late Daemon daemon; |
| late CapturingAppDomain appDomain; |
| |
| @override |
| Daemon createMachineDaemon() { |
| daemon = super.createMachineDaemon(); |
| appDomain = daemon.appDomain = CapturingAppDomain(daemon); |
| daemon.registerDomain(appDomain); |
| return daemon; |
| } |
| } |
| |
| class CapturingAppDomain extends AppDomain { |
| CapturingAppDomain(super.daemon); |
| |
| bool? multidexEnabled; |
| String? userIdentifier; |
| |
| @override |
| Future<AppInstance> startApp( |
| Device device, |
| String projectDirectory, |
| String target, |
| String? route, |
| DebuggingOptions options, |
| bool enableHotReload, { |
| File? applicationBinary, |
| required bool trackWidgetCreation, |
| String? projectRootPath, |
| String? packagesFilePath, |
| String? dillOutputPath, |
| bool ipv6 = false, |
| bool multidexEnabled = false, |
| String? isolateFilter, |
| bool machine = true, |
| String? userIdentifier, |
| }) async { |
| this.multidexEnabled = multidexEnabled; |
| this.userIdentifier = userIdentifier; |
| throwToolExit(''); |
| } |
| } |
| |
| class FakeAnsiTerminal extends Fake implements AnsiTerminal { |
| /// Setting to false will cause operations to Stdin to throw a [StdinException]. |
| bool hasStdin = true; |
| |
| @override |
| bool usesTerminalUi = false; |
| |
| /// A list of all the calls to the [singleCharMode] setter. |
| List<bool> setSingleCharModeHistory = <bool>[]; |
| |
| @override |
| set singleCharMode(bool value) { |
| if (!hasStdin) { |
| throw const StdinException('Error setting terminal line mode', OSError('The handle is invalid', 6)); |
| } |
| setSingleCharModeHistory.add(value); |
| } |
| |
| @override |
| bool get singleCharMode => setSingleCharModeHistory.last; |
| } |