| // 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:args/command_runner.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/base/common.dart'; |
| import 'package:flutter_tools/src/base/error_handling_io.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/signals.dart'; |
| import 'package:flutter_tools/src/base/terminal.dart'; |
| import 'package:flutter_tools/src/base/time.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/dart/pub.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| import 'package:flutter_tools/src/pre_run_validator.dart'; |
| import 'package:flutter_tools/src/project.dart'; |
| import 'package:flutter_tools/src/reporting/reporting.dart'; |
| import 'package:flutter_tools/src/runner/flutter_command.dart'; |
| import 'package:test/fake.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/fake_devices.dart'; |
| import '../../src/test_flutter_command_runner.dart'; |
| import 'utils.dart'; |
| |
| void main() { |
| group('Flutter Command', () { |
| late FakeCache cache; |
| late TestUsage usage; |
| late FakeClock clock; |
| late FakeProcessInfo processInfo; |
| late MemoryFileSystem fileSystem; |
| late FakeProcessManager processManager; |
| late PreRunValidator preRunValidator; |
| |
| setUpAll(() { |
| Cache.flutterRoot = '/path/to/sdk/flutter'; |
| }); |
| |
| setUp(() { |
| Cache.disableLocking(); |
| cache = FakeCache(); |
| usage = TestUsage(); |
| clock = FakeClock(); |
| processInfo = FakeProcessInfo(); |
| processInfo.maxRss = 10; |
| fileSystem = MemoryFileSystem.test(); |
| processManager = FakeProcessManager.empty(); |
| preRunValidator = PreRunValidator(fileSystem: fileSystem); |
| }); |
| |
| tearDown(() { |
| Cache.enableLocking(); |
| }); |
| |
| testUsingContext('help text contains global options', () { |
| final FakeDeprecatedCommand fake = FakeDeprecatedCommand(); |
| createTestCommandRunner(fake); |
| expect(fake.usage, contains('Global options:\n')); |
| }); |
| |
| testUsingContext('honors shouldUpdateCache false', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| await flutterCommand.run(); |
| |
| expect(cache.artifacts, isEmpty); |
| expect(flutterCommand.deprecated, isFalse); |
| expect(flutterCommand.hidden, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| Cache: () => cache, |
| }); |
| |
| testUsingContext('honors shouldUpdateCache true', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(shouldUpdateCache: true); |
| await flutterCommand.run(); |
| // First call for universal, second for the rest |
| expect( |
| cache.artifacts, |
| <Set<DevelopmentArtifact>>[ |
| <DevelopmentArtifact>{DevelopmentArtifact.universal}, |
| <DevelopmentArtifact>{}, |
| ], |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| Cache: () => cache, |
| }); |
| |
| testUsingContext("throws toolExit if flutter_tools source dir doesn't exist", () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| await expectToolExitLater( |
| flutterCommand.run(), |
| contains('Flutter SDK installation appears corrupted'), |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| Cache: () => cache, |
| FileSystem: () => fileSystem, |
| PreRunValidator: () => preRunValidator, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('deprecated command should warn', () async { |
| final FakeDeprecatedCommand flutterCommand = FakeDeprecatedCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(flutterCommand); |
| await runner.run(<String>['deprecated']); |
| |
| expect(testLogger.warningText, |
| contains('The "deprecated" command is deprecated and will be removed in ' |
| 'a future version of Flutter.')); |
| expect(flutterCommand.usage, |
| contains('Deprecated. This command will be removed in a future version ' |
| 'of Flutter.')); |
| expect(flutterCommand.deprecated, isTrue); |
| expect(flutterCommand.hidden, isTrue); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('uses the error handling file system', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand( |
| commandFunction: () async { |
| expect(globals.fs, isA<ErrorHandlingFileSystem>()); |
| return const FlutterCommandResult(ExitStatus.success); |
| } |
| ); |
| await flutterCommand.run(); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('finds the target file with default values', () async { |
| globals.fs.file('lib/main.dart').createSync(recursive: true); |
| final FakeTargetCommand fakeTargetCommand = FakeTargetCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(fakeTargetCommand); |
| await runner.run(<String>['test']); |
| |
| expect(fakeTargetCommand.cachedTargetFile, 'lib/main.dart'); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('finds the target file with specified value', () async { |
| globals.fs.file('lib/foo.dart').createSync(recursive: true); |
| final FakeTargetCommand fakeTargetCommand = FakeTargetCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(fakeTargetCommand); |
| await runner.run(<String>['test', '-t', 'lib/foo.dart']); |
| |
| expect(fakeTargetCommand.cachedTargetFile, 'lib/foo.dart'); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('throws tool exit if specified file does not exist', () async { |
| final FakeTargetCommand fakeTargetCommand = FakeTargetCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(fakeTargetCommand); |
| |
| expect(() async => runner.run(<String>['test', '-t', 'lib/foo.dart']), throwsToolExit()); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| void testUsingCommandContext(String testName, dynamic Function() testBody) { |
| testUsingContext(testName, testBody, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessInfo: () => processInfo, |
| ProcessManager: () => processManager, |
| SystemClock: () => clock, |
| Usage: () => usage, |
| }); |
| } |
| |
| testUsingCommandContext('reports command that results in success', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand( |
| commandFunction: () async { |
| return const FlutterCommandResult(ExitStatus.success); |
| } |
| ); |
| await flutterCommand.run(); |
| |
| expect(usage.events, <TestUsageEvent>[ |
| const TestUsageEvent( |
| 'tool-command-result', |
| 'dummy', |
| label: 'success', |
| ), |
| const TestUsageEvent( |
| 'tool-command-max-rss', |
| 'dummy', |
| label: 'success', |
| value: 10, |
| ), |
| ]); |
| }); |
| |
| testUsingCommandContext('reports command that results in warning', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand( |
| commandFunction: () async { |
| return const FlutterCommandResult(ExitStatus.warning); |
| } |
| ); |
| await flutterCommand.run(); |
| |
| expect(usage.events, <TestUsageEvent>[ |
| const TestUsageEvent( |
| 'tool-command-result', |
| 'dummy', |
| label: 'warning', |
| ), |
| const TestUsageEvent( |
| 'tool-command-max-rss', |
| 'dummy', |
| label: 'warning', |
| value: 10, |
| ), |
| ]); |
| }); |
| |
| testUsingCommandContext('reports command that results in error', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand( |
| commandFunction: () async { |
| throwToolExit('fail'); |
| }, |
| ); |
| await expectLater( |
| () => flutterCommand.run(), |
| throwsToolExit(), |
| ); |
| expect(usage.events, <TestUsageEvent>[ |
| const TestUsageEvent( |
| 'tool-command-result', |
| 'dummy', |
| label: 'fail', |
| ), |
| const TestUsageEvent( |
| 'tool-command-max-rss', |
| 'dummy', |
| label: 'fail', |
| value: 10, |
| ), |
| ]); |
| }); |
| |
| test('FlutterCommandResult.success()', () async { |
| expect(FlutterCommandResult.success().exitStatus, ExitStatus.success); |
| }); |
| |
| test('FlutterCommandResult.warning()', () async { |
| expect(FlutterCommandResult.warning().exitStatus, ExitStatus.warning); |
| }); |
| |
| testUsingContext('devToolsServerAddress returns parsed uri', () async { |
| final DummyFlutterCommand command = DummyFlutterCommand()..addDevToolsOptions(verboseHelp: false); |
| await createTestCommandRunner(command).run(<String>[ |
| 'dummy', |
| '--${FlutterCommand.kDevToolsServerAddress}', |
| 'http://127.0.0.1:9105', |
| ]); |
| expect(command.devToolsServerAddress.toString(), equals('http://127.0.0.1:9105')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('devToolsServerAddress returns null for bad input', () async { |
| final DummyFlutterCommand command = DummyFlutterCommand()..addDevToolsOptions(verboseHelp: false); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>[ |
| 'dummy', |
| '--${FlutterCommand.kDevToolsServerAddress}', |
| 'hello-world', |
| ]); |
| expect(command.devToolsServerAddress, isNull); |
| |
| await runner.run(<String>[ |
| 'dummy', |
| '--${FlutterCommand.kDevToolsServerAddress}', |
| '', |
| ]); |
| expect(command.devToolsServerAddress, isNull); |
| |
| await runner.run(<String>[ |
| 'dummy', |
| '--${FlutterCommand.kDevToolsServerAddress}', |
| '9101', |
| ]); |
| expect(command.devToolsServerAddress, isNull); |
| |
| await runner.run(<String>[ |
| 'dummy', |
| '--${FlutterCommand.kDevToolsServerAddress}', |
| '127.0.0.1:9101', |
| ]); |
| expect(command.devToolsServerAddress, isNull); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| group('signals tests', () { |
| late FakeIoProcessSignal mockSignal; |
| late ProcessSignal signalUnderTest; |
| late StreamController<io.ProcessSignal> signalController; |
| |
| setUp(() { |
| mockSignal = FakeIoProcessSignal(); |
| signalUnderTest = ProcessSignal(mockSignal); |
| signalController = StreamController<io.ProcessSignal>(); |
| mockSignal.stream = signalController.stream; |
| }); |
| |
| testUsingContext('reports command that is killed', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final Completer<void> completer = Completer<void>(); |
| setExitFunctionForTests((int exitCode) { |
| expect(exitCode, 0); |
| restoreExitFunction(); |
| completer.complete(); |
| }); |
| |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand( |
| commandFunction: () async { |
| final Completer<void> c = Completer<void>(); |
| await c.future; |
| throw UnsupportedError('Unreachable'); |
| } |
| ); |
| |
| unawaited(flutterCommand.run()); |
| signalController.add(mockSignal); |
| await completer.future; |
| |
| expect(usage.events, <TestUsageEvent>[ |
| const TestUsageEvent( |
| 'tool-command-result', |
| 'dummy', |
| label: 'killed', |
| ), |
| const TestUsageEvent( |
| 'tool-command-max-rss', |
| 'dummy', |
| label: 'killed', |
| value: 10, |
| ), |
| ]); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| ProcessInfo: () => processInfo, |
| Signals: () => FakeSignals( |
| subForSigTerm: signalUnderTest, |
| exitSignals: <ProcessSignal>[signalUnderTest], |
| ), |
| SystemClock: () => clock, |
| Usage: () => usage, |
| }); |
| |
| testUsingContext('command release lock on kill signal', () async { |
| clock.times = <int>[1000, 2000]; |
| final Completer<void> completer = Completer<void>(); |
| setExitFunctionForTests((int exitCode) { |
| expect(exitCode, 0); |
| restoreExitFunction(); |
| completer.complete(); |
| }); |
| final Completer<void> checkLockCompleter = Completer<void>(); |
| final DummyFlutterCommand flutterCommand = |
| DummyFlutterCommand(commandFunction: () async { |
| await globals.cache.lock(); |
| checkLockCompleter.complete(); |
| final Completer<void> c = Completer<void>(); |
| await c.future; |
| throw UnsupportedError('Unreachable'); |
| }); |
| |
| unawaited(flutterCommand.run()); |
| await checkLockCompleter.future; |
| |
| globals.cache.checkLockAcquired(); |
| |
| signalController.add(mockSignal); |
| await completer.future; |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| ProcessInfo: () => processInfo, |
| Signals: () => FakeSignals( |
| subForSigTerm: signalUnderTest, |
| exitSignals: <ProcessSignal>[signalUnderTest], |
| ), |
| Usage: () => usage, |
| }); |
| }); |
| |
| testUsingCommandContext('report execution timing by default', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| await flutterCommand.run(); |
| |
| expect(usage.timings, contains( |
| const TestTimingEvent( |
| 'flutter', |
| 'dummy', |
| Duration(milliseconds: 1000), |
| label: 'fail', |
| ))); |
| }); |
| |
| testUsingCommandContext('no timing report without usagePath', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final DummyFlutterCommand flutterCommand = |
| DummyFlutterCommand(noUsagePath: true); |
| await flutterCommand.run(); |
| |
| expect(usage.timings, isEmpty); |
| }); |
| |
| testUsingCommandContext('report additional FlutterCommandResult data', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final FlutterCommandResult commandResult = FlutterCommandResult( |
| ExitStatus.success, |
| // nulls should be cleaned up. |
| timingLabelParts: <String?> ['blah1', 'blah2', null, 'blah3'], |
| endTimeOverride: DateTime.fromMillisecondsSinceEpoch(1500), |
| ); |
| |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand( |
| commandFunction: () async => commandResult |
| ); |
| await flutterCommand.run(); |
| |
| expect(usage.timings, contains( |
| const TestTimingEvent( |
| 'flutter', |
| 'dummy', |
| Duration(milliseconds: 500), |
| label: 'success-blah1-blah2-blah3', |
| ))); |
| }); |
| |
| testUsingCommandContext('report failed execution timing too', () async { |
| // Crash if called a third time which is unexpected. |
| clock.times = <int>[1000, 2000]; |
| |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand( |
| commandFunction: () async { |
| throwToolExit('fail'); |
| }, |
| ); |
| |
| await expectLater( |
| () => flutterCommand.run(), |
| throwsToolExit(), |
| ); |
| expect(usage.timings, contains( |
| const TestTimingEvent( |
| 'flutter', |
| 'dummy', |
| Duration(milliseconds: 1000), |
| label: 'fail', |
| ), |
| )); |
| }); |
| |
| testUsingContext('reports null safety analytics when reportNullSafety is true', () async { |
| globals.fs.file('lib/main.dart') |
| ..createSync(recursive: true) |
| ..writeAsStringSync('// @dart=2.12'); |
| globals.fs.file('pubspec.yaml') |
| .writeAsStringSync('name: example\n'); |
| globals.fs.file('.dart_tool/package_config.json') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(r''' |
| { |
| "configVersion": 2, |
| "packages": [ |
| { |
| "name": "example", |
| "rootUri": "../", |
| "packageUri": "lib/", |
| "languageVersion": "2.12" |
| } |
| ], |
| "generated": "2020-12-02T19:30:53.862346Z", |
| "generator": "pub", |
| "generatorVersion": "2.12.0-76.0.dev" |
| } |
| '''); |
| final FakeReportingNullSafetyCommand command = FakeReportingNullSafetyCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| |
| await runner.run(<String>['test']); |
| |
| expect(usage.events, containsAll(<TestUsageEvent>[ |
| const TestUsageEvent( |
| NullSafetyAnalysisEvent.kNullSafetyCategory, |
| 'runtime-mode', |
| label: 'NullSafetyMode.sound', |
| ), |
| TestUsageEvent( |
| NullSafetyAnalysisEvent.kNullSafetyCategory, |
| 'stats', |
| parameters: CustomDimensions.fromMap(<String, String>{ |
| 'cd49': '1', 'cd50': '1', |
| }), |
| ), |
| const TestUsageEvent( |
| NullSafetyAnalysisEvent.kNullSafetyCategory, |
| 'language-version', |
| label: '2.12', |
| ), |
| ])); |
| }, overrides: <Type, Generator>{ |
| Pub: () => FakePub(), |
| Usage: () => usage, |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('use packagesPath to generate BuildInfo', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(packagesPath: 'foo'); |
| final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug); |
| expect(buildInfo.packagesPath, 'foo'); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('use fileSystemScheme to generate BuildInfo', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(fileSystemScheme: 'foo'); |
| final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug); |
| expect(buildInfo.fileSystemScheme, 'foo'); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('use fileSystemRoots to generate BuildInfo', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(fileSystemRoots: <String>['foo', 'bar']); |
| final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug); |
| expect(buildInfo.fileSystemRoots, <String>['foo', 'bar']); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('includes initializeFromDill in BuildInfo', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand()..usesInitializeFromDillOption(hide: false); |
| final CommandRunner<void> runner = createTestCommandRunner(flutterCommand); |
| await runner.run(<String>['dummy', '--initialize-from-dill=/foo/bar.dill']); |
| final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug); |
| expect(buildInfo.initializeFromDill, '/foo/bar.dill'); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('includes assumeInitializeFromDillUpToDate in BuildInfo', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand()..usesInitializeFromDillOption(hide: false); |
| final CommandRunner<void> runner = createTestCommandRunner(flutterCommand); |
| await runner.run(<String>['dummy', '--assume-initialize-from-dill-up-to-date']); |
| final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug); |
| expect(buildInfo.assumeInitializeFromDillUpToDate, isTrue); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('unsets assumeInitializeFromDillUpToDate in BuildInfo when disabled', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand()..usesInitializeFromDillOption(hide: false); |
| final CommandRunner<void> runner = createTestCommandRunner(flutterCommand); |
| await runner.run(<String>['dummy', '--no-assume-initialize-from-dill-up-to-date']); |
| final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug); |
| expect(buildInfo.assumeInitializeFromDillUpToDate, isFalse); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('dds options', () async { |
| final FakeDdsCommand ddsCommand = FakeDdsCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(ddsCommand); |
| await runner.run(<String>['test', '--dds-port=1']); |
| expect(ddsCommand.enableDds, isTrue); |
| expect(ddsCommand.ddsPort, 1); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('dds options --dds', () async { |
| final FakeDdsCommand ddsCommand = FakeDdsCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(ddsCommand); |
| await runner.run(<String>['test', '--dds']); |
| expect(ddsCommand.enableDds, isTrue); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('dds options --no-dds', () async { |
| final FakeDdsCommand ddsCommand = FakeDdsCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(ddsCommand); |
| await runner.run(<String>['test', '--no-dds']); |
| expect(ddsCommand.enableDds, isFalse); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('dds options --disable-dds', () async { |
| final FakeDdsCommand ddsCommand = FakeDdsCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(ddsCommand); |
| await runner.run(<String>['test', '--disable-dds']); |
| expect(ddsCommand.enableDds, isFalse); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('dds options --no-disable-dds', () async { |
| final FakeDdsCommand ddsCommand = FakeDdsCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(ddsCommand); |
| await runner.run(<String>['test', '--no-disable-dds']); |
| expect(ddsCommand.enableDds, isTrue); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| testUsingContext('dds options --dds --disable-dds', () async { |
| final FakeDdsCommand ddsCommand = FakeDdsCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(ddsCommand); |
| await runner.run(<String>['test', '--dds', '--disable-dds']); |
| expect(() => ddsCommand.enableDds, throwsToolExit()); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => processManager, |
| }); |
| |
| group('findAllTargetDevices', () { |
| final FakeDevice device1 = FakeDevice('device1', 'device1'); |
| final FakeDevice device2 = FakeDevice('device2', 'device2'); |
| group('when specified device id', () { |
| testUsingContext('returns device when device is found', () async { |
| testDeviceManager.specifiedDeviceId = 'device-id'; |
| testDeviceManager.addDevice(device1); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, <Device>[device1]); |
| }); |
| |
| testUsingContext('show error when no device found', () async { |
| testDeviceManager.specifiedDeviceId = 'device-id'; |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, null); |
| expect(testLogger.statusText, contains(UserMessages().flutterNoMatchingDevice('device-id'))); |
| }); |
| |
| testUsingContext('show error when multiple devices found', () async { |
| testDeviceManager.specifiedDeviceId = 'device-id'; |
| testDeviceManager.addDevice(device1); |
| testDeviceManager.addDevice(device2); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, null); |
| expect(testLogger.statusText, contains(UserMessages().flutterFoundSpecifiedDevices(2, 'device-id'))); |
| }); |
| }); |
| |
| group('when specified all', () { |
| testUsingContext('can return one device', () async { |
| testDeviceManager.specifiedDeviceId = 'all'; |
| testDeviceManager.addDevice(device1); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, <Device>[device1]); |
| }); |
| |
| testUsingContext('can return multiple devices', () async { |
| testDeviceManager.specifiedDeviceId = 'all'; |
| testDeviceManager.addDevice(device1); |
| testDeviceManager.addDevice(device2); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, <Device>[device1, device2]); |
| }); |
| |
| testUsingContext('show error when no device found', () async { |
| testDeviceManager.specifiedDeviceId = 'all'; |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, null); |
| expect(testLogger.statusText, contains(UserMessages().flutterNoDevicesFound)); |
| }); |
| }); |
| |
| group('when device not specified', () { |
| testUsingContext('returns one device when only one device connected', () async { |
| testDeviceManager.addDevice(device1); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, <Device>[device1]); |
| }); |
| |
| testUsingContext('show error when no device found', () async { |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, null); |
| expect(testLogger.statusText, contains(UserMessages().flutterNoSupportedDevices)); |
| }); |
| |
| testUsingContext('show error when multiple devices found and not connected to terminal', () async { |
| testDeviceManager.addDevice(device1); |
| testDeviceManager.addDevice(device2); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| expect(devices, null); |
| expect(testLogger.statusText, contains(UserMessages().flutterSpecifyDeviceWithAllOption)); |
| }, overrides: <Type, Generator>{ |
| AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false), |
| }); |
| |
| // Prompt to choose device when multiple devices found and connected to terminal |
| group('show prompt', () { |
| late FakeTerminal terminal; |
| setUp(() { |
| terminal = FakeTerminal(); |
| }); |
| |
| testUsingContext('choose first device', () async { |
| testDeviceManager.addDevice(device1); |
| testDeviceManager.addDevice(device2); |
| terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1'); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| |
| expect(devices, <Device>[device1]); |
| }, overrides: <Type, Generator>{ |
| AnsiTerminal: () => terminal, |
| }); |
| |
| testUsingContext('choose second device', () async { |
| testDeviceManager.addDevice(device1); |
| testDeviceManager.addDevice(device2); |
| terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '2'); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| final List<Device>? devices = await flutterCommand.findAllTargetDevices(); |
| |
| expect(devices, <Device>[device2]); |
| }, overrides: <Type, Generator>{ |
| AnsiTerminal: () => terminal, |
| }); |
| |
| testUsingContext('exits without choosing device', () async { |
| testDeviceManager.addDevice(device1); |
| testDeviceManager.addDevice(device2); |
| terminal.setPrompt(<String>['1', '2', 'q', 'Q'], 'q'); |
| final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); |
| |
| await expectLater( |
| flutterCommand.findAllTargetDevices(), |
| throwsToolExit(), |
| ); |
| }, overrides: <Type, Generator>{ |
| AnsiTerminal: () => terminal, |
| }); |
| }); |
| }); |
| }); |
| }); |
| } |
| |
| class FakeDeprecatedCommand extends FlutterCommand { |
| @override |
| String get description => 'A fake command'; |
| |
| @override |
| String get name => 'deprecated'; |
| |
| @override |
| bool get deprecated => true; |
| |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| return FlutterCommandResult.success(); |
| } |
| } |
| |
| class FakeTargetCommand extends FlutterCommand { |
| FakeTargetCommand() { |
| usesTargetOption(); |
| } |
| |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| cachedTargetFile = targetFile; |
| return FlutterCommandResult.success(); |
| } |
| |
| String? cachedTargetFile; |
| |
| @override |
| String get description => ''; |
| |
| @override |
| String get name => 'test'; |
| } |
| |
| class FakeReportingNullSafetyCommand extends FlutterCommand { |
| FakeReportingNullSafetyCommand() { |
| argParser.addFlag('debug'); |
| argParser.addFlag('release'); |
| argParser.addFlag('jit-release'); |
| argParser.addFlag('profile'); |
| } |
| |
| @override |
| String get description => 'test'; |
| |
| @override |
| String get name => 'test'; |
| |
| @override |
| bool get shouldRunPub => true; |
| |
| @override |
| bool get reportNullSafety => true; |
| |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| return FlutterCommandResult.success(); |
| } |
| } |
| |
| class FakeDdsCommand extends FlutterCommand { |
| FakeDdsCommand() { |
| addDdsOptions(verboseHelp: false); |
| } |
| |
| @override |
| String get description => 'test'; |
| |
| @override |
| String get name => 'test'; |
| |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| return FlutterCommandResult.success(); |
| } |
| } |
| |
| class FakeProcessInfo extends Fake implements ProcessInfo { |
| @override |
| int maxRss = 0; |
| } |
| |
| class FakeIoProcessSignal extends Fake implements io.ProcessSignal { |
| late Stream<io.ProcessSignal> stream; |
| |
| @override |
| Stream<io.ProcessSignal> watch() => stream; |
| } |
| |
| class FakeCache extends Fake implements Cache { |
| List<Set<DevelopmentArtifact>> artifacts = <Set<DevelopmentArtifact>>[]; |
| |
| @override |
| Future<void> updateAll(Set<DevelopmentArtifact> requiredArtifacts, {bool offline = false}) async { |
| artifacts.add(requiredArtifacts.toSet()); |
| } |
| |
| @override |
| void releaseLock() { } |
| } |
| |
| class FakeSignals implements Signals { |
| FakeSignals({ |
| required this.subForSigTerm, |
| required List<ProcessSignal> exitSignals, |
| }) : delegate = Signals.test(exitSignals: exitSignals); |
| |
| final ProcessSignal subForSigTerm; |
| final Signals delegate; |
| |
| @override |
| Object addHandler(ProcessSignal signal, SignalHandler handler) { |
| if (signal == ProcessSignal.sigterm) { |
| return delegate.addHandler(subForSigTerm, handler); |
| } |
| return delegate.addHandler(signal, handler); |
| } |
| |
| @override |
| Future<bool> removeHandler(ProcessSignal signal, Object token) => |
| delegate.removeHandler(signal, token); |
| |
| @override |
| Stream<Object> get errors => delegate.errors; |
| } |
| |
| class FakeClock extends Fake implements SystemClock { |
| List<int> times = <int>[]; |
| |
| @override |
| DateTime now() { |
| return DateTime.fromMillisecondsSinceEpoch(times.removeAt(0)); |
| } |
| } |
| |
| class FakePub extends Fake implements Pub { |
| @override |
| Future<void> get({ |
| required PubContext context, |
| required FlutterProject project, |
| bool upgrade = false, |
| bool offline = false, |
| String? flutterRootOverride, |
| bool checkUpToDate = false, |
| bool shouldSkipThirdPartyGenerator = true, |
| bool printProgress = true, |
| }) async { } |
| } |
| |
| class FakeTerminal extends Fake implements AnsiTerminal { |
| FakeTerminal({this.stdinHasTerminal = true}); |
| |
| @override |
| final bool stdinHasTerminal; |
| |
| @override |
| bool usesTerminalUi = true; |
| |
| void setPrompt(List<String> characters, String result) { |
| _nextPrompt = characters; |
| _nextResult = result; |
| } |
| |
| List<String>? _nextPrompt; |
| late String _nextResult; |
| |
| @override |
| Future<String> promptForCharInput( |
| List<String> acceptedCharacters, { |
| Logger? logger, |
| String? prompt, |
| int? defaultChoiceIndex, |
| bool displayAcceptedCharacters = true, |
| }) async { |
| expect(acceptedCharacters, _nextPrompt); |
| return _nextResult; |
| } |
| } |