| // 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:io' as io; |
| |
| import 'package:fake_async/fake_async.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/application_package.dart'; |
| import 'package:flutter_tools/src/base/async_guard.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/process.dart'; |
| import 'package:flutter_tools/src/base/signals.dart'; |
| import 'package:flutter_tools/src/base/terminal.dart'; |
| import 'package:flutter_tools/src/build_info.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/commands/drive.dart'; |
| import 'package:flutter_tools/src/dart/pub.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/drive/drive_service.dart'; |
| import 'package:flutter_tools/src/ios/application_package.dart'; |
| import 'package:flutter_tools/src/ios/devices.dart'; |
| import 'package:flutter_tools/src/project.dart'; |
| import 'package:flutter_tools/src/web/web_device.dart'; |
| import 'package:package_config/package_config.dart'; |
| import 'package:test/fake.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/fakes.dart'; |
| import '../../src/test_flutter_command_runner.dart'; |
| |
| void main() { |
| late FileSystem fileSystem; |
| late BufferLogger logger; |
| late Platform platform; |
| late OutputPreferences outputPreferences; |
| late FakeDeviceManager fakeDeviceManager; |
| late FakeSignals signals; |
| |
| DelegatingToolContext createToolContext({Signals? signalsOverride}) => DelegatingToolContext( |
| fs: fileSystem, |
| logger: logger, |
| outputPreferences: outputPreferences, |
| platform: platform, |
| signals: signalsOverride ?? signals, |
| ); |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| logger = BufferLogger.test(); |
| platform = FakePlatform(); |
| outputPreferences = OutputPreferences.test(); |
| fakeDeviceManager = FakeDeviceManager(); |
| signals = FakeSignals(); |
| }); |
| |
| setUpAll(() { |
| Cache.disableLocking(); |
| }); |
| |
| tearDownAll(() { |
| Cache.enableLocking(); |
| }); |
| |
| testUsingContext( |
| 'web drive runs without launching Chrome directly', |
| () async { |
| final capturingDriverService = CapturingDriverService(); |
| final command = DriveCommand( |
| toolContext: createToolContext(), |
| flutterDriverFactory: CapturingFlutterDriverFactory(capturingDriverService), |
| ); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.file('web/index.html').createSync(recursive: true); |
| |
| fakeDeviceManager.attachedDevices = <Device>[FakeChromiumDriveDevice()]; |
| |
| await createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| 'chrome', |
| '--browser-name=chrome', |
| '--chrome-binary=/tmp/custom-chrome', |
| '--web-define=FOO=bar', |
| ]); |
| |
| expect(capturingDriverService.platformArgs, containsPair('no-launch-chrome', true)); |
| expect(capturingDriverService.platformArgs, isNot(contains('--no-launch-chrome'))); |
| expect(capturingDriverService.webDefines, <String, String>{'FOO': 'bar'}); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'fails if the specified --target is not found', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| await expectLater( |
| () => |
| createTestCommandRunner(command) |
| .run(<String>['drive', '--no-pub', '--target', 'lib/app.dart']), |
| throwsToolExit(message: 'Target file "lib/app.dart" not found'), |
| ); |
| |
| expect(logger.errorText, isEmpty); |
| expect(logger.statusText, isEmpty); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.empty(), |
| }, |
| ); |
| |
| testUsingContext( |
| 'fails if the default --target is not found', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| fileSystem.file('lib/app.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/app_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>['drive', '--no-pub']), |
| throwsToolExit(message: 'Target file "lib/main.dart" not found'), |
| ); |
| |
| expect(logger.errorText, isEmpty); |
| expect(logger.statusText, isEmpty); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.empty(), |
| }, |
| ); |
| |
| testUsingContext( |
| 'fails with an informative error message if --target looks like --driver', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| await expectLater( |
| () => |
| createTestCommandRunner(command) |
| .run(<String>['drive', '--no-pub', '--target', 'test_driver/main_test.dart']), |
| throwsToolExit(message: 'Test file not found: /test_driver/main_test_test.dart'), |
| ); |
| |
| expect( |
| logger.errorText, |
| contains('The file path passed to --target should be an app entrypoint'), |
| ); |
| expect(logger.statusText, isEmpty); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.empty(), |
| }, |
| ); |
| |
| testUsingContext( |
| 'warns if screenshot is not supported but continues test', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.directory('drive_screenshots').createSync(); |
| |
| final Device screenshotDevice = ThrowingScreenshotDevice()..supportsScreenshot = false; |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| screenshotDevice.id, |
| '--screenshot', |
| 'drive_screenshots', |
| ]), |
| throwsToolExit(message: 'cannot start app'), |
| ); |
| |
| expect(logger.errorText, contains('Screenshot not supported for FakeDevice')); |
| expect(logger.statusText, isEmpty); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'does not register screenshot signal handler if --screenshot not provided', |
| () async { |
| final command = DriveCommand( |
| toolContext: createToolContext(), |
| flutterDriverFactory: FailingFakeFlutterDriverFactory(), |
| signalsToHandle: const <ProcessSignal>{ProcessSignal.sigusr1}, |
| ); |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.directory('drive_screenshots').createSync(); |
| |
| final Device screenshotDevice = ScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| screenshotDevice.id, |
| '--use-existing-app', |
| 'http://localhost:8181', |
| '--keep-app-running', |
| ]), |
| throwsToolExit(), |
| ); |
| expect(logger.statusText, isNot(contains('Screenshot written to '))); |
| expect(signals.addedSignals, isNot(contains(ProcessSignal.sigusr1))); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'takes screenshot and rethrows on drive exception', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.directory('drive_screenshots').createSync(); |
| |
| final Device screenshotDevice = ThrowingScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| screenshotDevice.id, |
| '--screenshot', |
| 'drive_screenshots', |
| ]), |
| throwsToolExit(message: 'cannot start app'), |
| ); |
| |
| expect(logger.statusText, contains('Screenshot written to drive_screenshots/drive_01.png')); |
| expect(logger.statusText, isNot(contains('drive_02.png'))); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'takes screenshot on drive test failure', |
| () async { |
| final command = DriveCommand( |
| toolContext: createToolContext(), |
| flutterDriverFactory: FailingFakeFlutterDriverFactory(), |
| ); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.directory('drive_screenshots').createSync(); |
| |
| final Device screenshotDevice = ScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| screenshotDevice.id, |
| '--use-existing-app', |
| 'http://localhost:8181', |
| '--keep-app-running', |
| '--screenshot', |
| 'drive_screenshots', |
| ]), |
| throwsToolExit(), |
| ); |
| |
| // Takes the screenshot before the application would be killed (if --keep-app-running not passed). |
| expect( |
| logger.statusText, |
| contains( |
| 'Screenshot written to drive_screenshots/drive_01.png\n' |
| 'Leaving the application running.', |
| ), |
| ); |
| expect(logger.statusText, isNot(contains('drive_02.png'))); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'drive --screenshot errors but does not fail if screenshot fails', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.file('drive_screenshots').createSync(); |
| |
| final Device screenshotDevice = ThrowingScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| screenshotDevice.id, |
| '--screenshot', |
| 'drive_screenshots', |
| ]), |
| throwsToolExit(message: 'cannot start app'), |
| ); |
| |
| expect(logger.statusText, isEmpty); |
| expect( |
| logger.errorText, |
| contains('Error taking screenshot: FileSystemException: Not a directory'), |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'drive --timeout takes screenshot and tool exits after timeout', |
| () async { |
| final command = DriveCommand( |
| toolContext: createToolContext(signalsOverride: Signals.test()), |
| flutterDriverFactory: FakeFlutterDriverFactory(), |
| ); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.directory('drive_screenshots').createSync(); |
| |
| final screenshotDevice = ScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| expect(screenshotDevice.screenshots, isEmpty); |
| var caughtToolExit = false; |
| FakeAsync().run<void>((FakeAsync time) { |
| // Because the tool exit will be thrown asynchronously by a [Timer], |
| // use [asyncGuard] to catch it |
| asyncGuard<void>( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| screenshotDevice.id, |
| '--use-existing-app', |
| 'http://localhost:8181', |
| '--screenshot', |
| 'drive_screenshots', |
| '--timeout', |
| '300', // 5 minutes |
| ]), |
| onError: (Object error) { |
| expect(error, isA<ToolExit>()); |
| expect((error as ToolExit).message, contains('Timed out after 300 seconds')); |
| caughtToolExit = true; |
| }, |
| ); |
| time.elapse(const Duration(seconds: 299)); |
| expect(screenshotDevice.screenshots, isEmpty); |
| time.elapse(const Duration(seconds: 2)); |
| expect( |
| screenshotDevice.screenshots, |
| contains( |
| isA<File>().having((File file) => file.path, 'path', 'drive_screenshots/drive_01.png'), |
| ), |
| ); |
| }); |
| expect(caughtToolExit, isTrue); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'drive --screenshot takes screenshot if sent a registered signal', |
| () async { |
| final signal = FakeProcessSignal(); |
| final signalUnderTest = ProcessSignal(signal); |
| final command = DriveCommand( |
| toolContext: createToolContext(signalsOverride: Signals.test()), |
| flutterDriverFactory: FakeFlutterDriverFactory( |
| onStartTest: () { |
| signal.controller.add(signal); |
| return Completer<int>().future; |
| }, |
| ), |
| signalsToHandle: <ProcessSignal>{signalUnderTest}, |
| ); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.directory('drive_screenshots').createSync(); |
| |
| final screenshotDevice = ScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| expect(screenshotDevice.screenshots, isEmpty); |
| |
| // This command will never complete. In reality, a real signal would have |
| // shut down the Dart process. |
| unawaited( |
| createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--no-pub', |
| '-d', |
| screenshotDevice.id, |
| '--use-existing-app', |
| 'http://localhost:8181', |
| '--screenshot', |
| 'drive_screenshots', |
| ]), |
| ); |
| |
| await screenshotDevice.firstScreenshot; |
| expect( |
| screenshotDevice.screenshots, |
| contains( |
| isA<File>().having((File file) => file.path, 'path', 'drive_screenshots/drive_01.png'), |
| ), |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| 'shouldRunPub is true unless user specifies --no-pub', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| try { |
| await createTestCommandRunner(command).run(const <String>['drive', '--no-pub']); |
| } on Exception { |
| // Expected to throw |
| } |
| |
| expect(command.shouldRunPub, false); |
| |
| try { |
| await createTestCommandRunner(command).run(const <String>['drive']); |
| } on Exception { |
| // Expected to throw |
| } |
| |
| expect(command.shouldRunPub, true); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| }, |
| ); |
| |
| testUsingContext( |
| 'flags propagate to debugging options', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '--start-paused', |
| '--disable-service-auth-codes', |
| '--trace-skia', |
| '--trace-systrace', |
| '--trace-to-file=path/to/trace.binpb', |
| '--verbose-system-logs', |
| '--native-null-assertions', |
| '--enable-impeller', |
| '--enable-flutter-gpu', |
| '--trace-systrace', |
| '--profile-microtasks', |
| '--profile-startup', |
| '--enable-software-rendering', |
| '--skia-deterministic-rendering', |
| '--enable-embedder-api', |
| '--ci', |
| '--debug-logs-dir=path/to/logs', |
| ]), |
| throwsToolExit(), |
| ); |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(); |
| |
| expect(options.startPaused, true); |
| expect(options.disableServiceAuthCodes, true); |
| expect(options.traceSkia, true); |
| expect(options.traceSystrace, true); |
| expect(options.traceToFile, 'path/to/trace.binpb'); |
| expect(options.profileMicrotasks, true); |
| expect(options.profileStartup, true); |
| expect(options.verboseSystemLogs, true); |
| expect(options.nativeNullAssertions, true); |
| expect(options.enableImpeller, ImpellerStatus.enabled); |
| expect(options.enableFlutterGpu, true); |
| expect(options.traceSystrace, true); |
| expect(options.enableSoftwareRendering, true); |
| expect(options.skiaDeterministicRendering, true); |
| expect(options.usingCISystem, true); |
| expect(options.debugLogsDirectoryPath, 'path/to/logs'); |
| }, |
| overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| }, |
| ); |
| |
| testUsingContext( |
| 'Port publication not disabled for wireless device', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| final Device wirelessDevice = FakeIosDevice() |
| ..connectionInterface = DeviceConnectionInterface.wireless; |
| fakeDeviceManager.wirelessDevices = <Device>[wirelessDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>['drive']), |
| throwsToolExit(), |
| ); |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(); |
| expect(options.disablePortPublication, false); |
| }, |
| overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => fakeDeviceManager, |
| Pub: () => FakePub(), |
| }, |
| ); |
| |
| testUsingContext( |
| 'Port publication is disabled for wired device', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>['drive']), |
| throwsToolExit(), |
| ); |
| |
| final Device usbDevice = FakeIosDevice() |
| ..connectionInterface = DeviceConnectionInterface.attached; |
| fakeDeviceManager.attachedDevices = <Device>[usbDevice]; |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(); |
| expect(options.disablePortPublication, true); |
| }, |
| overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => fakeDeviceManager, |
| Pub: () => FakePub(), |
| }, |
| ); |
| |
| testUsingContext( |
| 'Port publication does not default to enabled for wireless device if flag manually added', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| final Device wirelessDevice = FakeIosDevice() |
| ..connectionInterface = DeviceConnectionInterface.wireless; |
| fakeDeviceManager.wirelessDevices = <Device>[wirelessDevice]; |
| |
| await expectLater( |
| () => createTestCommandRunner(command).run(<String>['drive', '--no-publish-port']), |
| throwsToolExit(), |
| ); |
| |
| final DebuggingOptions options = await command.createDebuggingOptions(); |
| expect(options.disablePortPublication, true); |
| }, |
| overrides: <Type, Generator>{ |
| Cache: () => Cache.test(processManager: FakeProcessManager.any()), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => fakeDeviceManager, |
| Pub: () => FakePub(), |
| }, |
| ); |
| |
| testUsingContext( |
| '--use-existing-app keeps the app running', |
| () async { |
| var wasStopped = false; |
| |
| final signal = FakeProcessSignal(); |
| final signalUnderTest = ProcessSignal(signal); |
| final command = DriveCommand( |
| toolContext: createToolContext(), |
| flutterDriverFactory: FakeFlutterDriverFactory( |
| onStartTest: () async { |
| signal.controller.add(signal); |
| return 0; |
| }, |
| onStop: () { |
| wasStopped = true; |
| }, |
| ), |
| signalsToHandle: <ProcessSignal>{signalUnderTest}, |
| ); |
| |
| final Device screenshotDevice = ThrowingScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| final Future<void> runningCommand = createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '-d', |
| screenshotDevice.id, |
| '--no-pub', |
| '--use-existing-app', |
| 'http://localhost:8181', |
| ]); |
| |
| signal.controller.add(io.ProcessSignal.sigint); |
| await runningCommand; |
| expect( |
| wasStopped, |
| false, |
| reason: 'Using --use-existing-app without --no-keep-app-running does not stop the app', |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext( |
| '--no-keep-app-running always stops the app running', |
| () async { |
| var wasStopped = false; |
| |
| final signal = FakeProcessSignal(); |
| final signalUnderTest = ProcessSignal(signal); |
| final command = DriveCommand( |
| toolContext: createToolContext(), |
| flutterDriverFactory: FakeFlutterDriverFactory( |
| onStartTest: () async { |
| signal.controller.add(signal); |
| return 0; |
| }, |
| onStop: () { |
| wasStopped = true; |
| }, |
| ), |
| signalsToHandle: <ProcessSignal>{signalUnderTest}, |
| ); |
| |
| final Device screenshotDevice = ThrowingScreenshotDevice(); |
| fakeDeviceManager.attachedDevices = <Device>[screenshotDevice]; |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| final Future<void> runningCommand = createTestCommandRunner(command).run(<String>[ |
| 'drive', |
| '-d', |
| screenshotDevice.id, |
| '--no-pub', |
| '--no-keep-app-running', |
| '--use-existing-app', |
| 'http://localhost:8181', |
| ]); |
| |
| signal.controller.add(io.ProcessSignal.sigint); |
| await runningCommand; |
| expect( |
| wasStopped, |
| true, |
| reason: 'Using --use-existing-app with --no-keep-app-running stops the app', |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| |
| testUsingContext('flutter drive --help explains how to use the command', () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| await createTestCommandRunner(command).run(<String>['drive', '--help']); |
| |
| expect( |
| logger.statusText, |
| stringContainsInOrder(<String>['flutter drive', '--target', '--driver']), |
| ); |
| }, overrides: <Type, Generator>{Logger: () => logger}); |
| |
| testUsingContext( |
| 'flutter drive fails if driver test imports package:flutter_test', |
| () async { |
| final command = DriveCommand(toolContext: createToolContext()); |
| |
| fileSystem.file('lib/main.dart').createSync(recursive: true); |
| final File driverTest = fileSystem.file('test_driver/main_test.dart') |
| ..createSync(recursive: true); |
| driverTest.writeAsStringSync(''' |
| import 'package:flutter_test/flutter_test.dart'; |
| void main() {} |
| '''); |
| fileSystem.file('pubspec.yaml').createSync(); |
| |
| // Create a mock package_config.json |
| fileSystem.file('.dart_tool/package_config.json') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| { |
| "configVersion": 2, |
| "packages": [ |
| { |
| "name": "my_package", |
| "rootUri": "../", |
| "packageUriRoot": "lib/" |
| } |
| ] |
| } |
| '''); |
| |
| fakeDeviceManager.attachedDevices = <Device>[FakeChromiumDriveDevice()]; |
| |
| expect( |
| () => createTestCommandRunner(command).run(<String>['drive', '--no-pub', '-d', 'chrome']), |
| throwsToolExit( |
| message: 'flutter_driver test "/test_driver/main_test.dart" has invalid imports:', |
| ), |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: () => FakePub(), |
| DeviceManager: () => fakeDeviceManager, |
| }, |
| ); |
| } |
| |
| class ThrowingScreenshotDevice extends ScreenshotDevice { |
| @override |
| Future<LaunchResult> startApp( |
| ApplicationPackage? package, { |
| String? mainPath, |
| String? route, |
| DebuggingOptions? debuggingOptions, |
| Map<String, dynamic>? platformArgs, |
| bool prebuiltApplication = false, |
| bool usesTerminalUi = true, |
| bool ipv6 = false, |
| String? userIdentifier, |
| }) async { |
| throwToolExit('cannot start app'); |
| } |
| } |
| |
| class ScreenshotDevice extends Fake implements Device { |
| final screenshots = <File>[]; |
| |
| final _firstScreenshotCompleter = Completer<void>(); |
| |
| /// A Future that completes when [takeScreenshot] is called the first time. |
| Future<void> get firstScreenshot => _firstScreenshotCompleter.future; |
| |
| @override |
| final name = 'FakeDevice'; |
| |
| @override |
| String get displayName => name; |
| |
| @override |
| final Category category = Category.mobile; |
| |
| @override |
| final id = 'fake_device'; |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.android; |
| |
| @override |
| bool supportsScreenshot = true; |
| |
| @override |
| bool get isConnected => true; |
| |
| @override |
| Future<LaunchResult> startApp( |
| ApplicationPackage? package, { |
| String? mainPath, |
| String? route, |
| DebuggingOptions? debuggingOptions, |
| Map<String, dynamic>? platformArgs, |
| bool prebuiltApplication = false, |
| bool usesTerminalUi = true, |
| bool ipv6 = false, |
| String? userIdentifier, |
| }) async => LaunchResult.succeeded(); |
| |
| @override |
| Future<void> takeScreenshot(File outputFile) async { |
| if (!_firstScreenshotCompleter.isCompleted) { |
| _firstScreenshotCompleter.complete(); |
| } |
| screenshots.add(outputFile); |
| } |
| } |
| |
| class FakePub extends Fake implements Pub { |
| @override |
| Future<void> get({ |
| PubContext? context, |
| required FlutterProject project, |
| bool upgrade = false, |
| bool offline = false, |
| String? flutterRootOverride, |
| bool checkUpToDate = false, |
| bool shouldSkipThirdPartyGenerator = true, |
| bool enforceLockfile = false, |
| PubOutputMode outputMode = PubOutputMode.all, |
| }) async {} |
| } |
| |
| /// A [FlutterDriverFactory] that creates a [FakeDriverService]. |
| class FakeFlutterDriverFactory extends Fake implements FlutterDriverFactory { |
| FakeFlutterDriverFactory({this.onStartTest, this.onStop}); |
| |
| final Future<int> Function()? onStartTest; |
| final void Function()? onStop; |
| |
| @override |
| DriverService createDriverService(bool web) { |
| return FakeDriverService(onStartTest: onStartTest, onStop: onStop); |
| } |
| } |
| |
| class CapturingFlutterDriverFactory extends Fake implements FlutterDriverFactory { |
| CapturingFlutterDriverFactory(this.driverService); |
| |
| final CapturingDriverService driverService; |
| |
| @override |
| DriverService createDriverService(bool web) => driverService; |
| } |
| |
| /// A [DriverService] that will return a Future from [startTest] that will never complete. |
| /// |
| /// This is to simulate when the test will take a long time, but a signal is |
| /// expected to interrupt the process. |
| class FakeDriverService extends Fake implements DriverService { |
| FakeDriverService({this.onStartTest, this.onStop}); |
| |
| final Future<int> Function()? onStartTest; |
| final void Function()? onStop; |
| |
| @override |
| Future<void> reuseApplication( |
| Uri vmServiceUri, |
| Device device, |
| DebuggingOptions debuggingOptions, |
| ) async {} |
| |
| @override |
| Future<int> startTest( |
| String testFile, |
| List<String> arguments, |
| PackageConfig packageConfig, { |
| bool? headless, |
| String? chromeBinary, |
| String? browserName, |
| bool? androidEmulator, |
| int? driverPort, |
| List<String>? webBrowserFlags, |
| List<String>? browserDimension, |
| String? profileMemory, |
| }) async { |
| final Future<int> result = onStartTest?.call() ?? Completer<int>().future; |
| return result; |
| } |
| |
| @override |
| Future<void> stop({String? userIdentifier}) async { |
| return onStop?.call(); |
| } |
| } |
| |
| class CapturingDriverService extends Fake implements DriverService { |
| Map<String, Object>? platformArgs; |
| Map<String, String>? webDefines; |
| |
| @override |
| Future<void> start( |
| BuildInfo buildInfo, |
| Device device, |
| DebuggingOptions debuggingOptions, { |
| File? applicationBinary, |
| String? route, |
| String? userIdentifier, |
| String? mainPath, |
| Map<String, Object> platformArgs = const <String, Object>{}, |
| Map<String, String> webDefines = const <String, String>{}, |
| }) async { |
| this.platformArgs = platformArgs; |
| this.webDefines = webDefines; |
| } |
| |
| @override |
| Future<void> reuseApplication( |
| Uri vmServiceUri, |
| Device device, |
| DebuggingOptions debuggingOptions, |
| ) async {} |
| |
| @override |
| Future<int> startTest( |
| String testFile, |
| List<String> arguments, |
| PackageConfig packageConfig, { |
| bool? headless, |
| String? chromeBinary, |
| String? browserName, |
| bool? androidEmulator, |
| int? driverPort, |
| List<String>? webBrowserFlags, |
| List<String>? browserDimension, |
| String? profileMemory, |
| }) async => 0; |
| |
| @override |
| Future<void> stop({String? userIdentifier}) async {} |
| } |
| |
| class FailingFakeFlutterDriverFactory extends Fake implements FlutterDriverFactory { |
| @override |
| DriverService createDriverService(bool web) => FailingFakeDriverService(); |
| } |
| |
| class FailingFakeDriverService extends Fake implements DriverService { |
| @override |
| Future<void> reuseApplication( |
| Uri vmServiceUri, |
| Device device, |
| DebuggingOptions debuggingOptions, |
| ) async {} |
| |
| @override |
| Future<int> startTest( |
| String testFile, |
| List<String> arguments, |
| PackageConfig packageConfig, { |
| bool? headless, |
| String? chromeBinary, |
| String? browserName, |
| bool? androidEmulator, |
| int? driverPort, |
| List<String>? webBrowserFlags, |
| List<String>? browserDimension, |
| String? profileMemory, |
| }) async => 1; |
| } |
| |
| class FakeProcessSignal extends Fake implements io.ProcessSignal { |
| final controller = StreamController<io.ProcessSignal>(); |
| |
| @override |
| Stream<io.ProcessSignal> watch() => controller.stream; |
| } |
| |
| class FakeIosDevice extends Fake implements IOSDevice { |
| @override |
| DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached; |
| |
| @override |
| bool get isWirelesslyConnected => connectionInterface == DeviceConnectionInterface.wireless; |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios; |
| |
| @override |
| Future<LaunchResult> startApp( |
| IOSApp? package, { |
| String? mainPath, |
| String? route, |
| required DebuggingOptions debuggingOptions, |
| Map<String, Object?> platformArgs = const <String, Object?>{}, |
| bool prebuiltApplication = false, |
| String? userIdentifier, |
| Duration? discoveryTimeout, |
| ShutdownHooks? shutdownHooks, |
| }) async => LaunchResult.failed(); |
| } |
| |
| class FakeChromiumDriveDevice extends Fake implements ChromiumDevice { |
| @override |
| final String id = 'chrome'; |
| |
| @override |
| String get name => 'Chrome'; |
| |
| @override |
| String get displayName => name; |
| |
| @override |
| bool get isConnected => true; |
| |
| @override |
| bool get supportsScreenshot => false; |
| |
| @override |
| Category get category => Category.web; |
| |
| @override |
| PlatformType get platformType => PlatformType.web; |
| |
| @override |
| Future<bool> get isLocalEmulator async => false; |
| |
| @override |
| Future<String> get sdkNameAndVersion async => 'Google Chrome 0.0'; |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript; |
| |
| @override |
| DeviceLogReader getLogReader({ApplicationPackage? app, bool includePastLogs = false}) { |
| return NoOpDeviceLogReader(app?.name); |
| } |
| |
| @override |
| bool supportsRuntimeMode(BuildMode buildMode) => true; |
| } |
| |
| class FakeSignals extends Fake implements Signals { |
| List<SignalHandler> addedHandlers = <SignalHandler>[]; |
| final addedSignals = <ProcessSignal>[]; |
| |
| @override |
| Object addHandler(ProcessSignal signal, SignalHandler handler) { |
| addedHandlers.add(handler); |
| addedSignals.add(signal); |
| return const Object(); |
| } |
| |
| @override |
| Future<bool> removeHandler(ProcessSignal signal, Object token) async => true; |
| } |