Move tools tests into a general.shard directory in preparation to changing how we shard tools tests (#36108)
diff --git a/packages/flutter_tools/test/general.shard/commands/analyze_continuously_test.dart b/packages/flutter_tools/test/general.shard/commands/analyze_continuously_test.dart
new file mode 100644
index 0000000..05a49bf
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/analyze_continuously_test.dart
@@ -0,0 +1,105 @@
+// Copyright 2016 The Chromium 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:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/os.dart';
+import 'package:flutter_tools/src/dart/analysis.dart';
+import 'package:flutter_tools/src/dart/pub.dart';
+import 'package:flutter_tools/src/dart/sdk.dart';
+import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ AnalysisServer server;
+ Directory tempDir;
+
+ setUp(() {
+ FlutterCommandRunner.initFlutterRoot();
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_analysis_test.');
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ return server?.dispose();
+ });
+
+ group('analyze --watch', () {
+ testUsingContext('AnalysisServer success', () async {
+ _createSampleProject(tempDir);
+
+ await pubGet(context: PubContext.flutterTests, directory: tempDir.path);
+
+ server = AnalysisServer(dartSdkPath, <String>[tempDir.path]);
+
+ int errorCount = 0;
+ final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
+ server.onErrors.listen((FileAnalysisErrors errors) => errorCount += errors.errors.length);
+
+ await server.start();
+ await onDone;
+
+ expect(errorCount, 0);
+ }, overrides: <Type, Generator>{
+ OperatingSystemUtils: () => os,
+ });
+ });
+
+ testUsingContext('AnalysisServer errors', () async {
+ _createSampleProject(tempDir, brokenCode: true);
+
+ await pubGet(context: PubContext.flutterTests, directory: tempDir.path);
+
+ server = AnalysisServer(dartSdkPath, <String>[tempDir.path]);
+
+ int errorCount = 0;
+ final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
+ server.onErrors.listen((FileAnalysisErrors errors) {
+ errorCount += errors.errors.length;
+ });
+
+ await server.start();
+ await onDone;
+
+ expect(errorCount, greaterThan(0));
+ }, overrides: <Type, Generator>{
+ OperatingSystemUtils: () => os,
+ });
+
+ testUsingContext('Returns no errors when source is error-free', () async {
+ const String contents = "StringBuffer bar = StringBuffer('baz');";
+ tempDir.childFile('main.dart').writeAsStringSync(contents);
+ server = AnalysisServer(dartSdkPath, <String>[tempDir.path]);
+
+ int errorCount = 0;
+ final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
+ server.onErrors.listen((FileAnalysisErrors errors) {
+ errorCount += errors.errors.length;
+ });
+ await server.start();
+ await onDone;
+ expect(errorCount, 0);
+ }, overrides: <Type, Generator>{
+ OperatingSystemUtils: () => os,
+ });
+}
+
+void _createSampleProject(Directory directory, { bool brokenCode = false }) {
+ final File pubspecFile = fs.file(fs.path.join(directory.path, 'pubspec.yaml'));
+ pubspecFile.writeAsStringSync('''
+name: foo_project
+''');
+
+ final File dartFile = fs.file(fs.path.join(directory.path, 'lib', 'main.dart'));
+ dartFile.parent.createSync();
+ dartFile.writeAsStringSync('''
+void main() {
+ print('hello world');
+ ${brokenCode ? 'prints("hello world");' : ''}
+}
+''');
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/analyze_once_test.dart b/packages/flutter_tools/test/general.shard/commands/analyze_once_test.dart
new file mode 100644
index 0000000..d783d64
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/analyze_once_test.dart
@@ -0,0 +1,242 @@
+// Copyright 2017 The Chromium 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:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/analyze.dart';
+import 'package:flutter_tools/src/commands/create.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+/// Test case timeout for tests involving project analysis.
+const Timeout allowForSlowAnalyzeTests = Timeout.factor(5.0);
+
+final Generator _kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
+final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
+ Platform: _kNoColorTerminalPlatform,
+};
+
+void main() {
+ final String analyzerSeparator = platform.isWindows ? '-' : '•';
+
+ group('analyze once', () {
+ Directory tempDir;
+ String projectPath;
+ File libMain;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute;
+ projectPath = fs.path.join(tempDir.path, 'flutter_project');
+ libMain = fs.file(fs.path.join(projectPath, 'lib', 'main.dart'));
+ });
+
+ tearDownAll(() {
+ tryToDelete(tempDir);
+ });
+
+ // Create a project to be analyzed
+ testUsingContext('flutter create', () async {
+ await runCommand(
+ command: CreateCommand(),
+ arguments: <String>['--no-wrap', 'create', projectPath],
+ statusTextContains: <String>[
+ 'All done!',
+ 'Your application code is in ${fs.path.normalize(fs.path.join(fs.path.relative(projectPath), 'lib', 'main.dart'))}',
+ ],
+ );
+ expect(libMain.existsSync(), isTrue);
+ }, timeout: allowForRemotePubInvocation);
+
+ // Analyze in the current directory - no arguments
+ testUsingContext('working directory', () async {
+ await runCommand(
+ command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
+ arguments: <String>['analyze'],
+ statusTextContains: <String>['No issues found!'],
+ );
+ }, timeout: allowForSlowAnalyzeTests);
+
+ // Analyze a specific file outside the current directory
+ testUsingContext('passing one file throws', () async {
+ await runCommand(
+ command: AnalyzeCommand(),
+ arguments: <String>['analyze', libMain.path],
+ toolExit: true,
+ exitMessageContains: 'is not a directory',
+ );
+ });
+
+ // Analyze in the current directory - no arguments
+ testUsingContext('working directory with errors', () async {
+ // Break the code to produce the "The parameter 'onPressed' is required" hint
+ // that is upgraded to a warning in package:flutter/analysis_options_user.yaml
+ // to assert that we are using the default Flutter analysis options.
+ // Also insert a statement that should not trigger a lint here
+ // but will trigger a lint later on when an analysis_options.yaml is added.
+ String source = await libMain.readAsString();
+ source = source.replaceFirst(
+ 'onPressed: _incrementCounter,',
+ '// onPressed: _incrementCounter,',
+ );
+ source = source.replaceFirst(
+ '_counter++;',
+ '_counter++; throw "an error message";',
+ );
+ await libMain.writeAsString(source);
+
+ // Analyze in the current directory - no arguments
+ await runCommand(
+ command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
+ arguments: <String>['analyze'],
+ statusTextContains: <String>[
+ 'Analyzing',
+ 'warning $analyzerSeparator The parameter \'onPressed\' is required',
+ 'info $analyzerSeparator The method \'_incrementCounter\' isn\'t used',
+ ],
+ exitMessageContains: '2 issues found.',
+ toolExit: true,
+ );
+ }, timeout: allowForSlowAnalyzeTests, overrides: noColorTerminalOverride);
+
+ // Analyze in the current directory - no arguments
+ testUsingContext('working directory with local options', () async {
+ // Insert an analysis_options.yaml file in the project
+ // which will trigger a lint for broken code that was inserted earlier
+ final File optionsFile = fs.file(fs.path.join(projectPath, 'analysis_options.yaml'));
+ await optionsFile.writeAsString('''
+ include: package:flutter/analysis_options_user.yaml
+ linter:
+ rules:
+ - only_throw_errors
+ ''');
+
+ // Analyze in the current directory - no arguments
+ await runCommand(
+ command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
+ arguments: <String>['analyze'],
+ statusTextContains: <String>[
+ 'Analyzing',
+ 'warning $analyzerSeparator The parameter \'onPressed\' is required',
+ 'info $analyzerSeparator The method \'_incrementCounter\' isn\'t used',
+ 'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
+ ],
+ exitMessageContains: '3 issues found.',
+ toolExit: true,
+ );
+ }, timeout: allowForSlowAnalyzeTests, overrides: noColorTerminalOverride);
+
+ testUsingContext('no duplicate issues', () async {
+ final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
+
+ try {
+ final File foo = fs.file(fs.path.join(tempDir.path, 'foo.dart'));
+ foo.writeAsStringSync('''
+import 'bar.dart';
+
+void foo() => bar();
+''');
+
+ final File bar = fs.file(fs.path.join(tempDir.path, 'bar.dart'));
+ bar.writeAsStringSync('''
+import 'dart:async'; // unused
+
+void bar() {
+}
+''');
+
+ // Analyze in the current directory - no arguments
+ await runCommand(
+ command: AnalyzeCommand(workingDirectory: tempDir),
+ arguments: <String>['analyze'],
+ statusTextContains: <String>[
+ 'Analyzing',
+ ],
+ exitMessageContains: '1 issue found.',
+ toolExit: true,
+ );
+ } finally {
+ tryToDelete(tempDir);
+ }
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('returns no issues when source is error-free', () async {
+ const String contents = '''
+StringBuffer bar = StringBuffer('baz');
+''';
+ final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
+ tempDir.childFile('main.dart').writeAsStringSync(contents);
+ try {
+ await runCommand(
+ command: AnalyzeCommand(workingDirectory: fs.directory(tempDir)),
+ arguments: <String>['analyze'],
+ statusTextContains: <String>['No issues found!'],
+ );
+ } finally {
+ tryToDelete(tempDir);
+ }
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('returns no issues for todo comments', () async {
+ const String contents = '''
+// TODO(foobar):
+StringBuffer bar = StringBuffer('baz');
+''';
+ final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.');
+ tempDir.childFile('main.dart').writeAsStringSync(contents);
+ try {
+ await runCommand(
+ command: AnalyzeCommand(workingDirectory: fs.directory(tempDir)),
+ arguments: <String>['analyze'],
+ statusTextContains: <String>['No issues found!'],
+ );
+ } finally {
+ tryToDelete(tempDir);
+ }
+ }, overrides: noColorTerminalOverride);
+ });
+}
+
+void assertContains(String text, List<String> patterns) {
+ if (patterns == null) {
+ expect(text, isEmpty);
+ } else {
+ for (String pattern in patterns) {
+ expect(text, contains(pattern));
+ }
+ }
+}
+
+Future<void> runCommand({
+ FlutterCommand command,
+ List<String> arguments,
+ List<String> statusTextContains,
+ List<String> errorTextContains,
+ bool toolExit = false,
+ String exitMessageContains,
+}) async {
+ try {
+ arguments.insert(0, '--flutter-root=${Cache.flutterRoot}');
+ await createTestCommandRunner(command).run(arguments);
+ expect(toolExit, isFalse, reason: 'Expected ToolExit exception');
+ } on ToolExit catch (e) {
+ if (!toolExit) {
+ testLogger.clear();
+ rethrow;
+ }
+ if (exitMessageContains != null) {
+ expect(e.message, contains(exitMessageContains));
+ }
+ }
+ assertContains(testLogger.statusText, statusTextContains);
+ assertContains(testLogger.errorText, errorTextContains);
+
+ testLogger.clear();
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/analyze_test.dart b/packages/flutter_tools/test/general.shard/commands/analyze_test.dart
new file mode 100644
index 0000000..ee24615
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/analyze_test.dart
@@ -0,0 +1,53 @@
+// Copyright 2016 The Chromium 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 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/analyze_base.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+const String _kFlutterRoot = '/data/flutter';
+
+void main() {
+ FileSystem fs;
+ Directory tempDir;
+
+ setUp(() {
+ fs = MemoryFileSystem();
+ fs.directory(_kFlutterRoot).createSync(recursive: true);
+ Cache.flutterRoot = _kFlutterRoot;
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_analysis_test.');
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ });
+
+ group('analyze', () {
+ testUsingContext('inRepo', () {
+ // Absolute paths
+ expect(inRepo(<String>[tempDir.path]), isFalse);
+ expect(inRepo(<String>[fs.path.join(tempDir.path, 'foo')]), isFalse);
+ expect(inRepo(<String>[Cache.flutterRoot]), isTrue);
+ expect(inRepo(<String>[fs.path.join(Cache.flutterRoot, 'foo')]), isTrue);
+
+ // Relative paths
+ fs.currentDirectory = Cache.flutterRoot;
+ expect(inRepo(<String>['.']), isTrue);
+ expect(inRepo(<String>['foo']), isTrue);
+ fs.currentDirectory = tempDir.path;
+ expect(inRepo(<String>['.']), isFalse);
+ expect(inRepo(<String>['foo']), isFalse);
+
+ // Ensure no exceptions
+ inRepo(null);
+ inRepo(<String>[]);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+ });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/assemble_test.dart b/packages/flutter_tools/test/general.shard/commands/assemble_test.dart
new file mode 100644
index 0000000..e18ebb5
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/assemble_test.dart
@@ -0,0 +1,84 @@
+// Copyright 2019 The Chromium 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 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/build_system/build_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/assemble.dart';
+import 'package:flutter_tools/src/globals.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/testbed.dart';
+
+void main() {
+ group('Assemble', () {
+ Testbed testbed;
+ MockBuildSystem mockBuildSystem;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ mockBuildSystem = MockBuildSystem();
+ testbed = Testbed(overrides: <Type, Generator>{
+ BuildSystem: () => mockBuildSystem,
+ });
+ });
+
+ test('Can list the output directory relative to project root', () => testbed.run(() async {
+ final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+ await commandRunner.run(<String>['assemble', '--flutter-root=.', 'build-dir', '-dBuildMode=debug']);
+ final BufferLogger bufferLogger = logger;
+ final Environment environment = Environment(
+ defines: <String, String>{
+ 'BuildMode': 'debug'
+ }, projectDir: fs.currentDirectory,
+ buildDir: fs.directory(getBuildDirectory()),
+ );
+
+ expect(bufferLogger.statusText.trim(),
+ fs.path.relative(environment.buildDir.path, from: fs.currentDirectory.path));
+ }));
+
+ test('Can describe a target', () => testbed.run(() async {
+ when(mockBuildSystem.describe('foobar', any)).thenReturn(<Map<String, Object>>[
+ <String, Object>{'fizz': 'bar'},
+ ]);
+ final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+ await commandRunner.run(<String>['assemble', '--flutter-root=.', 'describe', 'foobar']);
+ final BufferLogger bufferLogger = logger;
+
+ expect(bufferLogger.statusText.trim(), '[{"fizz":"bar"}]');
+ }));
+
+ test('Can describe a target\'s inputs', () => testbed.run(() async {
+ when(mockBuildSystem.describe('foobar', any)).thenReturn(<Map<String, Object>>[
+ <String, Object>{'name': 'foobar', 'inputs': <String>['bar', 'baz']},
+ ]);
+ final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+ await commandRunner.run(<String>['assemble', '--flutter-root=.', 'inputs', 'foobar']);
+ final BufferLogger bufferLogger = logger;
+
+ expect(bufferLogger.statusText.trim(), 'bar\nbaz');
+ }));
+
+ test('Can run a build', () => testbed.run(() async {
+ when(mockBuildSystem.build('foobar', any, any)).thenAnswer((Invocation invocation) async {
+ return BuildResult(true, const <String, ExceptionMeasurement>{}, const <String, PerformanceMeasurement>{});
+ });
+ final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+ await commandRunner.run(<String>['assemble', 'run', 'foobar']);
+ final BufferLogger bufferLogger = logger;
+
+ expect(bufferLogger.statusText.trim(), 'build succeeded');
+ }));
+ });
+}
+
+class MockBuildSystem extends Mock implements BuildSystem {}
diff --git a/packages/flutter_tools/test/general.shard/commands/attach_test.dart b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
new file mode 100644
index 0000000..c708f1e
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
@@ -0,0 +1,644 @@
+// Copyright 2018 The Chromium 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/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/attach.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
+import 'package:flutter_tools/src/run_hot.dart';
+import 'package:meta/meta.dart';
+import 'package:mockito/mockito.dart';
+import 'package:multicast_dns/multicast_dns.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ group('attach', () {
+ StreamLogger logger;
+ FileSystem testFileSystem;
+
+ setUp(() {
+ Cache.disableLocking();
+ logger = StreamLogger();
+ testFileSystem = MemoryFileSystem(
+ style: platform.isWindows
+ ? FileSystemStyle.windows
+ : FileSystemStyle.posix,
+ );
+ testFileSystem.directory('lib').createSync();
+ testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync();
+ });
+
+ group('with one device and no specified target file', () {
+ const int devicePort = 499;
+ const int hostPort = 42;
+
+ MockDeviceLogReader mockLogReader;
+ MockPortForwarder portForwarder;
+ MockAndroidDevice device;
+
+ setUp(() {
+ mockLogReader = MockDeviceLogReader();
+ portForwarder = MockPortForwarder();
+ device = MockAndroidDevice();
+ when(device.getLogReader()).thenAnswer((_) {
+ // Now that the reader is used, start writing messages to it.
+ Timer.run(() {
+ mockLogReader.addLine('Foo');
+ mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
+ });
+
+ return mockLogReader;
+ });
+ when(device.portForwarder)
+ .thenReturn(portForwarder);
+ when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
+ .thenAnswer((_) async => hostPort);
+ when(portForwarder.forwardedPorts)
+ .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
+ when(portForwarder.unforward(any))
+ .thenAnswer((_) async => null);
+
+ // We cannot add the device to a device manager because that is
+ // only enabled by the context of each testUsingContext call.
+ //
+ // Instead each test will add the device to the device manager
+ // on its own.
+ });
+
+ tearDown(() {
+ mockLogReader.dispose();
+ });
+
+ testUsingContext('finds observatory port and forwards', () async {
+ testDeviceManager.addDevice(device);
+ final Completer<void> completer = Completer<void>();
+ final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+ if (message == '[stdout] Done.') {
+ // The "Done." message is output by the AttachCommand when it's done.
+ completer.complete();
+ }
+ });
+ final Future<void> task = createTestCommandRunner(AttachCommand()).run(<String>['attach']);
+ await completer.future;
+ verify(
+ portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')),
+ ).called(1);
+ await expectLoggerInterruptEndsTask(task, logger);
+ await loggerSubscription.cancel();
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ Logger: () => logger,
+ });
+
+ testUsingContext('accepts filesystem parameters', () async {
+ testDeviceManager.addDevice(device);
+
+ const String filesystemScheme = 'foo';
+ const String filesystemRoot = '/build-output/';
+ const String projectRoot = '/build-output/project-root';
+ const String outputDill = '/tmp/output.dill';
+
+ final MockHotRunner mockHotRunner = MockHotRunner();
+ when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
+ .thenAnswer((_) async => 0);
+
+ final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
+ when(
+ mockHotRunnerFactory.build(
+ any,
+ target: anyNamed('target'),
+ projectRootPath: anyNamed('projectRootPath'),
+ dillOutputPath: anyNamed('dillOutputPath'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ packagesFilePath: anyNamed('packagesFilePath'),
+ usesTerminalUi: anyNamed('usesTerminalUi'),
+ flutterProject: anyNamed('flutterProject'),
+ ipv6: false,
+ ),
+ ).thenReturn(mockHotRunner);
+
+ final AttachCommand command = AttachCommand(
+ hotRunnerFactory: mockHotRunnerFactory,
+ );
+ await createTestCommandRunner(command).run(<String>[
+ 'attach',
+ '--filesystem-scheme',
+ filesystemScheme,
+ '--filesystem-root',
+ filesystemRoot,
+ '--project-root',
+ projectRoot,
+ '--output-dill',
+ outputDill,
+ '-v', // enables verbose logging
+ ]);
+
+ // Validate the attach call built a mock runner with the right
+ // project root and output dill.
+ final VerificationResult verificationResult = verify(
+ mockHotRunnerFactory.build(
+ captureAny,
+ target: anyNamed('target'),
+ projectRootPath: projectRoot,
+ dillOutputPath: outputDill,
+ debuggingOptions: anyNamed('debuggingOptions'),
+ packagesFilePath: anyNamed('packagesFilePath'),
+ usesTerminalUi: anyNamed('usesTerminalUi'),
+ flutterProject: anyNamed('flutterProject'),
+ ipv6: false,
+ ),
+ )..called(1);
+
+ final List<FlutterDevice> flutterDevices = verificationResult.captured.first;
+ expect(flutterDevices, hasLength(1));
+
+ // Validate that the attach call built a flutter device with the right
+ // output dill, filesystem scheme, and filesystem root.
+ final FlutterDevice flutterDevice = flutterDevices.first;
+
+ expect(flutterDevice.dillOutputPath, outputDill);
+ expect(flutterDevice.fileSystemScheme, filesystemScheme);
+ expect(flutterDevice.fileSystemRoots, const <String>[filesystemRoot]);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+
+ testUsingContext('exits when ipv6 is specified and debug-port is not', () async {
+ testDeviceManager.addDevice(device);
+
+ final AttachCommand command = AttachCommand();
+ await expectLater(
+ createTestCommandRunner(command).run(<String>['attach', '--ipv6']),
+ throwsToolExit(
+ message: 'When the --debug-port or --debug-uri is unknown, this command determines '
+ 'the value of --ipv6 on its own.',
+ ),
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ },);
+
+ testUsingContext('exits when observatory-port is specified and debug-port is not', () async {
+ testDeviceManager.addDevice(device);
+
+ final AttachCommand command = AttachCommand();
+ await expectLater(
+ createTestCommandRunner(command).run(<String>['attach', '--observatory-port', '100']),
+ throwsToolExit(
+ message: 'When the --debug-port or --debug-uri is unknown, this command does not use '
+ 'the value of --observatory-port.',
+ ),
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ },);
+ });
+
+
+ testUsingContext('selects specified target', () async {
+ const int devicePort = 499;
+ const int hostPort = 42;
+ final MockDeviceLogReader mockLogReader = MockDeviceLogReader();
+ final MockPortForwarder portForwarder = MockPortForwarder();
+ final MockAndroidDevice device = MockAndroidDevice();
+ final MockHotRunner mockHotRunner = MockHotRunner();
+ final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
+ when(device.portForwarder)
+ .thenReturn(portForwarder);
+ when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
+ .thenAnswer((_) async => hostPort);
+ when(portForwarder.forwardedPorts)
+ .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
+ when(portForwarder.unforward(any))
+ .thenAnswer((_) async => null);
+ when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
+ .thenAnswer((_) async => 0);
+ when(mockHotRunnerFactory.build(
+ any,
+ target: anyNamed('target'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ packagesFilePath: anyNamed('packagesFilePath'),
+ usesTerminalUi: anyNamed('usesTerminalUi'),
+ flutterProject: anyNamed('flutterProject'),
+ ipv6: false,
+ )).thenReturn(mockHotRunner);
+
+ testDeviceManager.addDevice(device);
+ when(device.getLogReader())
+ .thenAnswer((_) {
+ // Now that the reader is used, start writing messages to it.
+ Timer.run(() {
+ mockLogReader.addLine('Foo');
+ mockLogReader.addLine(
+ 'Observatory listening on http://127.0.0.1:$devicePort');
+ });
+ return mockLogReader;
+ });
+ final File foo = fs.file('lib/foo.dart')
+ ..createSync();
+
+ // Delete the main.dart file to be sure that attach works without it.
+ fs.file(fs.path.join('lib', 'main.dart')).deleteSync();
+
+ final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory);
+ await createTestCommandRunner(command).run(<String>['attach', '-t', foo.path, '-v']);
+
+ verify(mockHotRunnerFactory.build(
+ any,
+ target: foo.path,
+ debuggingOptions: anyNamed('debuggingOptions'),
+ packagesFilePath: anyNamed('packagesFilePath'),
+ usesTerminalUi: anyNamed('usesTerminalUi'),
+ flutterProject: anyNamed('flutterProject'),
+ ipv6: false,
+ )).called(1);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+
+ group('forwarding to given port', () {
+ const int devicePort = 499;
+ const int hostPort = 42;
+ MockPortForwarder portForwarder;
+ MockAndroidDevice device;
+
+ setUp(() {
+ portForwarder = MockPortForwarder();
+ device = MockAndroidDevice();
+
+ when(device.portForwarder)
+ .thenReturn(portForwarder);
+ when(portForwarder.forward(devicePort))
+ .thenAnswer((_) async => hostPort);
+ when(portForwarder.forwardedPorts)
+ .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
+ when(portForwarder.unforward(any))
+ .thenAnswer((_) async => null);
+ });
+
+ testUsingContext('succeeds in ipv4 mode', () async {
+ testDeviceManager.addDevice(device);
+
+ final Completer<void> completer = Completer<void>();
+ final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+ if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') {
+ // Wait until resident_runner.dart tries to connect.
+ // There's nothing to connect _to_, so that's as far as we care to go.
+ completer.complete();
+ }
+ });
+ final Future<void> task = createTestCommandRunner(AttachCommand())
+ .run(<String>['attach', '--debug-port', '$devicePort']);
+ await completer.future;
+ verify(portForwarder.forward(devicePort)).called(1);
+
+ await expectLoggerInterruptEndsTask(task, logger);
+ await loggerSubscription.cancel();
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ Logger: () => logger,
+ });
+
+ testUsingContext('succeeds in ipv6 mode', () async {
+ testDeviceManager.addDevice(device);
+
+ final Completer<void> completer = Completer<void>();
+ final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+ if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') {
+ // Wait until resident_runner.dart tries to connect.
+ // There's nothing to connect _to_, so that's as far as we care to go.
+ completer.complete();
+ }
+ });
+ final Future<void> task = createTestCommandRunner(AttachCommand())
+ .run(<String>['attach', '--debug-port', '$devicePort', '--ipv6']);
+ await completer.future;
+ verify(portForwarder.forward(devicePort)).called(1);
+
+ await expectLoggerInterruptEndsTask(task, logger);
+ await loggerSubscription.cancel();
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ Logger: () => logger,
+ });
+
+ testUsingContext('skips in ipv4 mode with a provided observatory port', () async {
+ testDeviceManager.addDevice(device);
+
+ final Completer<void> completer = Completer<void>();
+ final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+ if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') {
+ // Wait until resident_runner.dart tries to connect.
+ // There's nothing to connect _to_, so that's as far as we care to go.
+ completer.complete();
+ }
+ });
+ final Future<void> task = createTestCommandRunner(AttachCommand()).run(
+ <String>[
+ 'attach',
+ '--debug-port',
+ '$devicePort',
+ '--observatory-port',
+ '$hostPort',
+ ],
+ );
+ await completer.future;
+ verifyNever(portForwarder.forward(devicePort));
+
+ await expectLoggerInterruptEndsTask(task, logger);
+ await loggerSubscription.cancel();
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ Logger: () => logger,
+ });
+
+ testUsingContext('skips in ipv6 mode with a provided observatory port', () async {
+ testDeviceManager.addDevice(device);
+
+ final Completer<void> completer = Completer<void>();
+ final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
+ if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') {
+ // Wait until resident_runner.dart tries to connect.
+ // There's nothing to connect _to_, so that's as far as we care to go.
+ completer.complete();
+ }
+ });
+ final Future<void> task = createTestCommandRunner(AttachCommand()).run(
+ <String>[
+ 'attach',
+ '--debug-port',
+ '$devicePort',
+ '--observatory-port',
+ '$hostPort',
+ '--ipv6',
+ ],
+ );
+ await completer.future;
+ verifyNever(portForwarder.forward(devicePort));
+
+ await expectLoggerInterruptEndsTask(task, logger);
+ await loggerSubscription.cancel();
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ Logger: () => logger,
+ });
+ });
+
+ testUsingContext('exits when no device connected', () async {
+ final AttachCommand command = AttachCommand();
+ await expectLater(
+ createTestCommandRunner(command).run(<String>['attach']),
+ throwsA(isInstanceOf<ToolExit>()),
+ );
+ expect(testLogger.statusText, contains('No supported devices connected'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+
+ testUsingContext('exits when multiple devices connected', () async {
+ Device aDeviceWithId(String id) {
+ final MockAndroidDevice device = MockAndroidDevice();
+ when(device.name).thenReturn('d$id');
+ when(device.id).thenReturn(id);
+ when(device.isLocalEmulator).thenAnswer((_) async => false);
+ when(device.sdkNameAndVersion).thenAnswer((_) async => 'Android 46');
+ return device;
+ }
+
+ final AttachCommand command = AttachCommand();
+ testDeviceManager.addDevice(aDeviceWithId('xx1'));
+ testDeviceManager.addDevice(aDeviceWithId('yy2'));
+ await expectLater(
+ createTestCommandRunner(command).run(<String>['attach']),
+ throwsA(isInstanceOf<ToolExit>()),
+ );
+ expect(testLogger.statusText, contains('More than one device'));
+ expect(testLogger.statusText, contains('xx1'));
+ expect(testLogger.statusText, contains('yy2'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+ });
+
+ group('mDNS Discovery', () {
+ final int year3000 = DateTime(3000).millisecondsSinceEpoch;
+
+ MDnsClient getMockClient(
+ List<PtrResourceRecord> ptrRecords,
+ Map<String, List<SrvResourceRecord>> srvResponse,
+ ) {
+ final MDnsClient client = MockMDnsClient();
+
+ when(client.lookup<PtrResourceRecord>(
+ ResourceRecordQuery.serverPointer(MDnsObservatoryDiscovery.dartObservatoryName),
+ )).thenAnswer((_) => Stream<PtrResourceRecord>.fromIterable(ptrRecords));
+
+ for (final MapEntry<String, List<SrvResourceRecord>> entry in srvResponse.entries) {
+ when(client.lookup<SrvResourceRecord>(
+ ResourceRecordQuery.service(entry.key),
+ )).thenAnswer((_) => Stream<SrvResourceRecord>.fromIterable(entry.value));
+ }
+ return client;
+ }
+
+ testUsingContext('No ports available', () async {
+ final MDnsClient client = getMockClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query())?.port;
+ expect(port, isNull);
+ });
+
+ testUsingContext('One port available, no appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query())?.port;
+ expect(port, 123);
+ });
+
+ testUsingContext('Multiple ports available, without appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ expect(() => portDiscovery.query(), throwsToolExit());
+ });
+
+ testUsingContext('Multiple ports available, with appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
+ expect(port, 321);
+ });
+
+ testUsingContext('Multiple ports available per process, with appId', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[
+ PtrResourceRecord('foo', year3000, domainName: 'bar'),
+ PtrResourceRecord('baz', year3000, domainName: 'fiz'),
+ ],
+ <String, List<SrvResourceRecord>>{
+ 'bar': <SrvResourceRecord>[
+ SrvResourceRecord('bar', year3000, port: 1234, weight: 1, priority: 1, target: 'appId'),
+ SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
+ ],
+ 'fiz': <SrvResourceRecord>[
+ SrvResourceRecord('fiz', year3000, port: 4321, weight: 1, priority: 1, target: 'local'),
+ SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
+ ],
+ },
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
+ expect(port, 1234);
+ });
+
+ testUsingContext('Query returns null', () async {
+ final MDnsClient client = getMockClient(
+ <PtrResourceRecord>[],
+ <String, List<SrvResourceRecord>>{},
+ );
+
+ final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
+ final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
+ expect(port, isNull);
+ });
+ });
+}
+
+class MockMDnsClient extends Mock implements MDnsClient {}
+
+class MockPortForwarder extends Mock implements DevicePortForwarder {}
+
+class MockHotRunner extends Mock implements HotRunner {}
+
+class MockHotRunnerFactory extends Mock implements HotRunnerFactory {}
+
+class StreamLogger extends Logger {
+ @override
+ bool get isVerbose => true;
+
+ @override
+ void printError(
+ String message, {
+ StackTrace stackTrace,
+ bool emphasis,
+ TerminalColor color,
+ int indent,
+ int hangingIndent,
+ bool wrap,
+ }) {
+ _log('[stderr] $message');
+ }
+
+ @override
+ void printStatus(
+ String message, {
+ bool emphasis,
+ TerminalColor color,
+ bool newline,
+ int indent,
+ int hangingIndent,
+ bool wrap,
+ }) {
+ _log('[stdout] $message');
+ }
+
+ @override
+ void printTrace(String message) {
+ _log('[verbose] $message');
+ }
+
+ @override
+ Status startProgress(
+ String message, {
+ @required Duration timeout,
+ String progressId,
+ bool multilineOutput = false,
+ int progressIndicatorPadding = kDefaultStatusPadding,
+ }) {
+ _log('[progress] $message');
+ return SilentStatus(timeout: timeout)..start();
+ }
+
+ bool _interrupt = false;
+
+ void interrupt() {
+ _interrupt = true;
+ }
+
+ final StreamController<String> _controller = StreamController<String>.broadcast();
+
+ void _log(String message) {
+ _controller.add(message);
+ if (_interrupt) {
+ _interrupt = false;
+ throw const LoggerInterrupted();
+ }
+ }
+
+ Stream<String> get stream => _controller.stream;
+}
+
+class LoggerInterrupted implements Exception {
+ const LoggerInterrupted();
+}
+
+Future<void> expectLoggerInterruptEndsTask(Future<void> task, StreamLogger logger) async {
+ logger.interrupt(); // an exception during the task should cause it to fail...
+ try {
+ await task;
+ expect(false, isTrue); // (shouldn't reach here)
+ } on ToolExit catch (error) {
+ expect(error.exitCode, 2); // ...with exit code 2.
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart b/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart
new file mode 100644
index 0000000..6c6c8ad
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart
@@ -0,0 +1,96 @@
+// Copyright 2019 The Chromium 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 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build_bundle.dart';
+import 'package:flutter_tools/src/bundle.dart';
+import 'package:flutter_tools/src/usage.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ Cache.disableLocking();
+
+ group('getUsage', () {
+ Directory tempDir;
+ MockBundleBuilder mockBundleBuilder;
+
+ setUp(() {
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
+
+ mockBundleBuilder = MockBundleBuilder();
+ when(
+ mockBundleBuilder.build(
+ platform: anyNamed('platform'),
+ buildMode: anyNamed('buildMode'),
+ mainPath: anyNamed('mainPath'),
+ manifestPath: anyNamed('manifestPath'),
+ applicationKernelFilePath: anyNamed('applicationKernelFilePath'),
+ depfilePath: anyNamed('depfilePath'),
+ privateKeyPath: anyNamed('privateKeyPath'),
+ assetDirPath: anyNamed('assetDirPath'),
+ packagesPath: anyNamed('packagesPath'),
+ precompiledSnapshot: anyNamed('precompiledSnapshot'),
+ reportLicensedPackages: anyNamed('reportLicensedPackages'),
+ trackWidgetCreation: anyNamed('trackWidgetCreation'),
+ extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
+ extraGenSnapshotOptions: anyNamed('extraGenSnapshotOptions'),
+ fileSystemRoots: anyNamed('fileSystemRoots'),
+ fileSystemScheme: anyNamed('fileSystemScheme'),
+ ),
+ ).thenAnswer((_) => Future<void>.value());
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ });
+
+ Future<BuildBundleCommand> runCommandIn(String projectPath, { List<String> arguments }) async {
+ final BuildBundleCommand command = BuildBundleCommand(bundleBuilder: mockBundleBuilder);
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ await runner.run(<String>[
+ 'bundle',
+ ...?arguments,
+ '--target=$projectPath/lib/main.dart',
+ ]);
+ return command;
+ }
+
+ testUsingContext('indicate that project is a module', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=module']);
+
+ final BuildBundleCommand command = await runCommandIn(projectPath);
+
+ expect(await command.usageValues,
+ containsPair(kCommandBuildBundleIsModule, 'true'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('indicate that project is not a module', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=app']);
+
+ final BuildBundleCommand command = await runCommandIn(projectPath);
+
+ expect(await command.usageValues,
+ containsPair(kCommandBuildBundleIsModule, 'false'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('indicate the target platform', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=app']);
+
+ final BuildBundleCommand command = await runCommandIn(projectPath);
+
+ expect(await command.usageValues,
+ containsPair(kCommandBuildBundleTargetPlatform, 'android-arm'));
+ }, timeout: allowForCreateFlutterProject);
+ });
+}
+
+class MockBundleBuilder extends Mock implements BundleBuilder {}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_fuchsia_test.dart b/packages/flutter_tools/test/general.shard/commands/build_fuchsia_test.dart
new file mode 100644
index 0000000..3734d04
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_fuchsia_test.dart
@@ -0,0 +1,237 @@
+// Copyright 2019 The Chromium 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 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:meta/meta.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ Cache.disableLocking();
+
+ MemoryFileSystem memoryFileSystem;
+ MockPlatform linuxPlatform;
+ MockPlatform windowsPlatform;
+ MockFuchsiaSdk fuchsiaSdk;
+ MockFuchsiaArtifacts fuchsiaArtifacts;
+ MockFuchsiaArtifacts fuchsiaArtifactsNoCompiler;
+
+ setUp(() {
+ memoryFileSystem = MemoryFileSystem();
+ linuxPlatform = MockPlatform();
+ windowsPlatform = MockPlatform();
+ fuchsiaSdk = MockFuchsiaSdk();
+ fuchsiaArtifacts = MockFuchsiaArtifacts();
+ fuchsiaArtifactsNoCompiler = MockFuchsiaArtifacts();
+
+ when(linuxPlatform.isLinux).thenReturn(true);
+ when(windowsPlatform.isWindows).thenReturn(true);
+ when(windowsPlatform.isLinux).thenReturn(false);
+ when(windowsPlatform.isMacOS).thenReturn(false);
+ when(fuchsiaArtifacts.kernelCompiler).thenReturn(MockFile());
+ when(fuchsiaArtifactsNoCompiler.kernelCompiler).thenReturn(null);
+ });
+
+ group('Fuchsia build fails gracefully when', () {
+ testUsingContext('there is no Fuchsia project',
+ () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ expect(
+ createTestCommandRunner(command)
+ .run(const <String>['build', 'fuchsia']),
+ throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => linuxPlatform,
+ FileSystem: () => memoryFileSystem,
+ FuchsiaArtifacts: () => fuchsiaArtifacts,
+ });
+
+ testUsingContext('there is no cmx file', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.directory('fuchsia').createSync(recursive: true);
+ fs.file('.packages').createSync();
+ fs.file('pubspec.yaml').createSync();
+
+ expect(
+ createTestCommandRunner(command)
+ .run(const <String>['build', 'fuchsia']),
+ throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => linuxPlatform,
+ FileSystem: () => memoryFileSystem,
+ FuchsiaArtifacts: () => fuchsiaArtifacts,
+ });
+
+ testUsingContext('on Windows platform', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ const String appName = 'app_name';
+ fs
+ .file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('{}');
+ fs.file('.packages').createSync();
+ final File pubspecFile = fs.file('pubspec.yaml')..createSync();
+ pubspecFile.writeAsStringSync('name: $appName');
+
+ expect(
+ createTestCommandRunner(command)
+ .run(const <String>['build', 'fuchsia']),
+ throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => windowsPlatform,
+ FileSystem: () => memoryFileSystem,
+ FuchsiaArtifacts: () => fuchsiaArtifacts,
+ });
+
+ testUsingContext('there is no Fuchsia kernel compiler', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ const String appName = 'app_name';
+ fs
+ .file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('{}');
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ final File pubspecFile = fs.file('pubspec.yaml')..createSync();
+ pubspecFile.writeAsStringSync('name: $appName');
+ expect(
+ createTestCommandRunner(command)
+ .run(const <String>['build', 'fuchsia']),
+ throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => linuxPlatform,
+ FileSystem: () => memoryFileSystem,
+ FuchsiaArtifacts: () => fuchsiaArtifactsNoCompiler,
+ });
+ });
+
+ testUsingContext('Fuchsia build parts fit together right', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ const String appName = 'app_name';
+ fs
+ .file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('{}');
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ final File pubspecFile = fs.file('pubspec.yaml')..createSync();
+ pubspecFile.writeAsStringSync('name: $appName');
+
+ await createTestCommandRunner(command)
+ .run(const <String>['build', 'fuchsia']);
+ final String farPath =
+ fs.path.join(getFuchsiaBuildDirectory(), 'pkg', 'app_name-0.far');
+ expect(fs.file(farPath).existsSync(), isTrue);
+ }, overrides: <Type, Generator>{
+ Platform: () => linuxPlatform,
+ FileSystem: () => memoryFileSystem,
+ FuchsiaSdk: () => fuchsiaSdk,
+ });
+}
+
+class MockPlatform extends Mock implements Platform {
+ @override
+ Map<String, String> environment = <String, String>{
+ 'FLUTTER_ROOT': '/',
+ };
+}
+
+class MockFuchsiaPM extends Mock implements FuchsiaPM {
+ String _appName;
+
+ @override
+ Future<bool> init(String buildPath, String appName) async {
+ if (!fs.directory(buildPath).existsSync()) {
+ return false;
+ }
+ fs
+ .file(fs.path.join(buildPath, 'meta', 'package'))
+ .createSync(recursive: true);
+ _appName = appName;
+ return true;
+ }
+
+ @override
+ Future<bool> genkey(String buildPath, String outKeyPath) async {
+ if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync()) {
+ return false;
+ }
+ fs.file(outKeyPath).createSync(recursive: true);
+ return true;
+ }
+
+ @override
+ Future<bool> build(
+ String buildPath, String keyPath, String manifestPath) async {
+ if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() ||
+ !fs.file(keyPath).existsSync() ||
+ !fs.file(manifestPath).existsSync()) {
+ return false;
+ }
+ fs.file(fs.path.join(buildPath, 'meta.far')).createSync(recursive: true);
+ return true;
+ }
+
+ @override
+ Future<bool> archive(
+ String buildPath, String keyPath, String manifestPath) async {
+ if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() ||
+ !fs.file(keyPath).existsSync() ||
+ !fs.file(manifestPath).existsSync()) {
+ return false;
+ }
+ if (_appName == null) {
+ return false;
+ }
+ fs
+ .file(fs.path.join(buildPath, '$_appName-0.far'))
+ .createSync(recursive: true);
+ return true;
+ }
+}
+
+class MockFuchsiaKernelCompiler extends Mock implements FuchsiaKernelCompiler {
+ @override
+ Future<void> build({
+ @required FuchsiaProject fuchsiaProject,
+ @required String target, // E.g., lib/main.dart
+ BuildInfo buildInfo = BuildInfo.debug,
+ }) async {
+ final String outDir = getFuchsiaBuildDirectory();
+ final String appName = fuchsiaProject.project.manifest.appName;
+ final String manifestPath = fs.path.join(outDir, '$appName.dilpmanifest');
+ fs.file(manifestPath).createSync(recursive: true);
+ }
+}
+
+class MockFuchsiaSdk extends Mock implements FuchsiaSdk {
+ @override
+ final FuchsiaPM fuchsiaPM = MockFuchsiaPM();
+
+ @override
+ final FuchsiaKernelCompiler fuchsiaKernelCompiler =
+ MockFuchsiaKernelCompiler();
+}
+
+class MockFile extends Mock implements File {}
+
+class MockFuchsiaArtifacts extends Mock implements FuchsiaArtifacts {}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_linux_test.dart b/packages/flutter_tools/test/general.shard/commands/build_linux_test.dart
new file mode 100644
index 0000000..638a20b
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_linux_test.dart
@@ -0,0 +1,125 @@
+// Copyright 2019 The Chromium 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 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/linux/makefile.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ MockProcessManager mockProcessManager;
+ MockProcess mockProcess;
+ MockPlatform linuxPlatform;
+ MockPlatform notLinuxPlatform;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ mockProcessManager = MockProcessManager();
+ mockProcess = MockProcess();
+ linuxPlatform = MockPlatform();
+ notLinuxPlatform = MockPlatform();
+ when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
+ return 0;
+ });
+ when(mockProcess.stderr).thenAnswer((Invocation invocation) {
+ return const Stream<List<int>>.empty();
+ });
+ when(mockProcess.stdout).thenAnswer((Invocation invocation) {
+ return const Stream<List<int>>.empty();
+ });
+ when(linuxPlatform.isLinux).thenReturn(true);
+ when(notLinuxPlatform.isLinux).thenReturn(false);
+ });
+
+ testUsingContext('Linux build fails when there is no linux project', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'linux']
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => linuxPlatform,
+ FileSystem: () => MemoryFileSystem(),
+ });
+
+ testUsingContext('Linux build fails on non-linux platform', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.file('linux/build.sh').createSync(recursive: true);
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'linux']
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => notLinuxPlatform,
+ FileSystem: () => MemoryFileSystem(),
+ });
+
+ testUsingContext('Linux build invokes make and writes temporary files', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.file('linux/build.sh').createSync(recursive: true);
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+ when(mockProcessManager.start(<String>[
+ 'make',
+ '-C',
+ '/linux',
+ ], runInShell: true)).thenAnswer((Invocation invocation) async {
+ return mockProcess;
+ });
+
+ await createTestCommandRunner(command).run(
+ const <String>['build', 'linux']
+ );
+ expect(fs.file('linux/flutter/generated_config').existsSync(), true);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => mockProcessManager,
+ Platform: () => linuxPlatform,
+ });
+
+ testUsingContext('linux can extract binary name from Makefile', () async {
+ fs.file('linux/Makefile')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(r'''
+# Comment
+SOMETHING_ELSE=FOO
+BINARY_NAME=fizz_bar
+''');
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+ final FlutterProject flutterProject = FlutterProject.current();
+
+ expect(makefileExecutableName(flutterProject.linux), 'fizz_bar');
+ }, overrides: <Type, Generator>{FileSystem: () => MemoryFileSystem()});
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockProcess extends Mock implements Process {}
+class MockPlatform extends Mock implements Platform {
+ @override
+ Map<String, String> environment = <String, String>{
+ 'FLUTTER_ROOT': '/',
+ };
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart b/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart
new file mode 100644
index 0000000..ceb07b8
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart
@@ -0,0 +1,117 @@
+// Copyright 2019 The Chromium 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 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ MockProcessManager mockProcessManager;
+ MemoryFileSystem memoryFilesystem;
+ MockProcess mockProcess;
+ MockPlatform macosPlatform;
+ MockPlatform notMacosPlatform;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ mockProcessManager = MockProcessManager();
+ memoryFilesystem = MemoryFileSystem();
+ mockProcess = MockProcess();
+ macosPlatform = MockPlatform();
+ notMacosPlatform = MockPlatform();
+ when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
+ return 0;
+ });
+ when(mockProcess.stderr).thenAnswer((Invocation invocation) {
+ return const Stream<List<int>>.empty();
+ });
+ when(mockProcess.stdout).thenAnswer((Invocation invocation) {
+ return const Stream<List<int>>.empty();
+ });
+ when(macosPlatform.isMacOS).thenReturn(true);
+ when(notMacosPlatform.isMacOS).thenReturn(false);
+ });
+
+ testUsingContext('macOS build fails when there is no macos project', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'macos']
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => macosPlatform,
+ });
+
+ testUsingContext('macOS build fails on non-macOS platform', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'macos']
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => notMacosPlatform,
+ FileSystem: () => memoryFilesystem,
+ });
+
+ testUsingContext('macOS build invokes build script', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.directory('macos').createSync();
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ final FlutterProject flutterProject = FlutterProject.fromDirectory(fs.currentDirectory);
+ final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory());
+
+ when(mockProcessManager.start(<String>[
+ '/usr/bin/env',
+ 'xcrun',
+ 'xcodebuild',
+ '-workspace', flutterProject.macos.xcodeWorkspace.path,
+ '-configuration', 'Debug',
+ '-scheme', 'Runner',
+ '-derivedDataPath', flutterBuildDir.absolute.path,
+ 'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
+ 'SYMROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}',
+ ], runInShell: true)).thenAnswer((Invocation invocation) async {
+ return mockProcess;
+ });
+
+ await createTestCommandRunner(command).run(
+ const <String>['build', 'macos']
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => memoryFilesystem,
+ ProcessManager: () => mockProcessManager,
+ Platform: () => macosPlatform,
+ });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockProcess extends Mock implements Process {}
+class MockPlatform extends Mock implements Platform {
+ @override
+ Map<String, String> environment = <String, String>{
+ 'FLUTTER_ROOT': '/',
+ };
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_web_test.dart b/packages/flutter_tools/test/general.shard/commands/build_web_test.dart
new file mode 100644
index 0000000..2adb818
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_web_test.dart
@@ -0,0 +1,97 @@
+// Copyright 2019 The Chromium 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 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
+import 'package:flutter_tools/src/resident_web_runner.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:flutter_tools/src/web/compile.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/testbed.dart';
+
+void main() {
+ MockWebCompilationProxy mockWebCompilationProxy;
+ Testbed testbed;
+ MockPlatform mockPlatform;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ mockWebCompilationProxy = MockWebCompilationProxy();
+ testbed = Testbed(setup: () {
+ fs.file('pubspec.yaml')
+ ..createSync()
+ ..writeAsStringSync('name: foo\n');
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('web', 'index.html')).createSync(recursive: true);
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ when(mockWebCompilationProxy.initialize(
+ projectDirectory: anyNamed('projectDirectory'),
+ release: anyNamed('release')
+ )).thenAnswer((Invocation invocation) {
+ final String path = fs.path.join('.dart_tool', 'build', 'flutter_web', 'foo', 'lib', 'main_web_entrypoint.dart.js');
+ fs.file(path).createSync(recursive: true);
+ fs.file('$path.map').createSync();
+ return Future<bool>.value(true);
+ });
+ }, overrides: <Type, Generator>{
+ WebCompilationProxy: () => mockWebCompilationProxy,
+ Platform: () => mockPlatform,
+ FlutterVersion: () => MockFlutterVersion(),
+ });
+ });
+
+ test('Refuses to build for web when missing index.html', () => testbed.run(() async {
+ fs.file(fs.path.join('web', 'index.html')).deleteSync();
+
+ expect(buildWeb(
+ FlutterProject.current(),
+ fs.path.join('lib', 'main.dart'),
+ BuildInfo.debug,
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }));
+
+ test('Refuses to build using runner when missing index.html', () => testbed.run(() async {
+ fs.file(fs.path.join('web', 'index.html')).deleteSync();
+
+ final ResidentWebRunner runner = ResidentWebRunner(
+ <FlutterDevice>[],
+ flutterProject: FlutterProject.current(),
+ ipv6: false,
+ debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
+ );
+ expect(await runner.run(), 1);
+ }));
+
+ test('Can build for web', () => testbed.run(() async {
+
+ await buildWeb(
+ FlutterProject.current(),
+ fs.path.join('lib', 'main.dart'),
+ BuildInfo.debug,
+ );
+ }));
+}
+
+class MockWebCompilationProxy extends Mock implements WebCompilationProxy {}
+class MockPlatform extends Mock implements Platform {
+ @override
+ Map<String, String> environment = <String, String>{
+ 'FLUTTER_ROOT': '/',
+ };
+}
+class MockFlutterVersion extends Mock implements FlutterVersion {
+ @override
+ bool get isMaster => true;
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_windows_test.dart b/packages/flutter_tools/test/general.shard/commands/build_windows_test.dart
new file mode 100644
index 0000000..301fa7d
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/build_windows_test.dart
@@ -0,0 +1,146 @@
+// Copyright 2019 The Chromium 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 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/windows/visual_studio.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+import 'package:xml/xml.dart' as xml;
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ MockProcessManager mockProcessManager;
+ MemoryFileSystem memoryFilesystem;
+ MockProcess mockProcess;
+ MockPlatform windowsPlatform;
+ MockPlatform notWindowsPlatform;
+ MockVisualStudio mockVisualStudio;
+ const String solutionPath = r'C:\windows\Runner.sln';
+ const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
+ const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ mockProcessManager = MockProcessManager();
+ memoryFilesystem = MemoryFileSystem(style: FileSystemStyle.windows);
+ mockProcess = MockProcess();
+ windowsPlatform = MockPlatform()
+ ..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\';
+ notWindowsPlatform = MockPlatform();
+ mockVisualStudio = MockVisualStudio();
+ when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
+ return 0;
+ });
+ when(mockProcess.stderr).thenAnswer((Invocation invocation) {
+ return const Stream<List<int>>.empty();
+ });
+ when(mockProcess.stdout).thenAnswer((Invocation invocation) {
+ return const Stream<List<int>>.empty();
+ });
+ when(windowsPlatform.isWindows).thenReturn(true);
+ when(notWindowsPlatform.isWindows).thenReturn(false);
+ });
+
+ testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.file(solutionPath).createSync(recursive: true);
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'windows']
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => windowsPlatform,
+ FileSystem: () => memoryFilesystem,
+ VisualStudio: () => mockVisualStudio,
+ });
+
+ testUsingContext('Windows build fails when there is no windows project', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'windows']
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => windowsPlatform,
+ FileSystem: () => memoryFilesystem,
+ VisualStudio: () => mockVisualStudio,
+ });
+
+ testUsingContext('Windows build fails on non windows platform', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.file(solutionPath).createSync(recursive: true);
+ when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'windows']
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }, overrides: <Type, Generator>{
+ Platform: () => notWindowsPlatform,
+ FileSystem: () => memoryFilesystem,
+ VisualStudio: () => mockVisualStudio,
+ });
+
+ testUsingContext('Windows build invokes msbuild and writes generated files', () async {
+ final BuildCommand command = BuildCommand();
+ applyMocksToCommand(command);
+ fs.file(solutionPath).createSync(recursive: true);
+ when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+
+ when(mockProcessManager.start(<String>[
+ r'C:\packages\flutter_tools\bin\vs_build.bat',
+ vcvarsPath,
+ fs.path.basename(solutionPath),
+ 'Release',
+ ], workingDirectory: fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
+ return mockProcess;
+ });
+
+ await createTestCommandRunner(command).run(
+ const <String>['build', 'windows']
+ );
+
+ // Spot-check important elements from the properties file.
+ final File propsFile = fs.file(r'C:\windows\flutter\Generated.props');
+ expect(propsFile.existsSync(), true);
+ final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync());
+ expect(props.findAllElements('PropertyGroup').first.getAttribute('Label'), 'UserMacros');
+ expect(props.findAllElements('ItemGroup').length, 1);
+ expect(props.findAllElements('FLUTTER_ROOT').first.text, r'C:\');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => memoryFilesystem,
+ ProcessManager: () => mockProcessManager,
+ Platform: () => windowsPlatform,
+ VisualStudio: () => mockVisualStudio,
+ });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockProcess extends Mock implements Process {}
+class MockPlatform extends Mock implements Platform {
+ @override
+ Map<String, String> environment = <String, String>{
+ 'FLUTTER_ROOT': r'C:\',
+ };
+}
+class MockVisualStudio extends Mock implements VisualStudio {}
diff --git a/packages/flutter_tools/test/general.shard/commands/clean_test.dart b/packages/flutter_tools/test/general.shard/commands/clean_test.dart
new file mode 100644
index 0000000..85926c7
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/clean_test.dart
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium 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 'package:flutter_tools/src/base/config.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/commands/clean.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+
+void main() {
+ final MockFileSystem mockFileSystem = MockFileSystem();
+ final MockDirectory currentDirectory = MockDirectory();
+ final MockDirectory exampleDirectory = MockDirectory();
+ final MockDirectory buildDirectory = MockDirectory();
+ final MockDirectory dartToolDirectory = MockDirectory();
+ final MockFile pubspec = MockFile();
+ final MockFile examplePubspec = MockFile();
+
+ when(mockFileSystem.currentDirectory).thenReturn(currentDirectory);
+ when(currentDirectory.childDirectory('example')).thenReturn(exampleDirectory);
+ when(currentDirectory.childFile('pubspec.yaml')).thenReturn(pubspec);
+ when(pubspec.path).thenReturn('/test/pubspec.yaml');
+ when(exampleDirectory.childFile('pubspec.yaml')).thenReturn(examplePubspec);
+ when(currentDirectory.childDirectory('.dart_tool')).thenReturn(dartToolDirectory);
+ when(examplePubspec.path).thenReturn('/test/example/pubspec.yaml');
+ when(mockFileSystem.isFileSync('/test/pubspec.yaml')).thenReturn(false);
+ when(mockFileSystem.isFileSync('/test/example/pubspec.yaml')).thenReturn(false);
+ when(mockFileSystem.directory('build')).thenReturn(buildDirectory);
+ when(mockFileSystem.path).thenReturn(fs.path);
+ when(buildDirectory.existsSync()).thenReturn(true);
+ when(dartToolDirectory.existsSync()).thenReturn(true);
+ group(CleanCommand, () {
+ testUsingContext('removes build and .dart_tool directories', () async {
+ await CleanCommand().runCommand();
+ verify(buildDirectory.deleteSync(recursive: true)).called(1);
+ verify(dartToolDirectory.deleteSync(recursive: true)).called(1);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => mockFileSystem,
+ Config: () => null,
+ });
+ });
+}
+
+class MockFileSystem extends Mock implements FileSystem {}
+class MockFile extends Mock implements File {}
+class MockDirectory extends Mock implements Directory {}
diff --git a/packages/flutter_tools/test/general.shard/commands/config_test.dart b/packages/flutter_tools/test/general.shard/commands/config_test.dart
new file mode 100644
index 0000000..b95b4ed
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/config_test.dart
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium 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:convert';
+
+import 'package:flutter_tools/src/android/android_sdk.dart';
+import 'package:flutter_tools/src/android/android_studio.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/commands/config.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ MockAndroidStudio mockAndroidStudio;
+ MockAndroidSdk mockAndroidSdk;
+
+ setUp(() {
+ mockAndroidStudio = MockAndroidStudio();
+ mockAndroidSdk = MockAndroidSdk();
+ });
+
+ group('config', () {
+ testUsingContext('machine flag', () async {
+ final BufferLogger logger = context.get<Logger>();
+ final ConfigCommand command = ConfigCommand();
+ await command.handleMachine();
+
+ expect(logger.statusText, isNotEmpty);
+ final dynamic jsonObject = json.decode(logger.statusText);
+ expect(jsonObject, isMap);
+
+ expect(jsonObject.containsKey('android-studio-dir'), true);
+ expect(jsonObject['android-studio-dir'], isNotNull);
+
+ expect(jsonObject.containsKey('android-sdk'), true);
+ expect(jsonObject['android-sdk'], isNotNull);
+ }, overrides: <Type, Generator>{
+ AndroidStudio: () => mockAndroidStudio,
+ AndroidSdk: () => mockAndroidSdk,
+ });
+ });
+}
+
+class MockAndroidStudio extends Mock implements AndroidStudio, Comparable<AndroidStudio> {
+ @override
+ String get directory => 'path/to/android/stdio';
+}
+
+class MockAndroidSdk extends Mock implements AndroidSdk {
+ @override
+ String get directory => 'path/to/android/sdk';
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/create_test.dart b/packages/flutter_tools/test/general.shard/commands/create_test.dart
new file mode 100644
index 0000000..7a8c087
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/create_test.dart
@@ -0,0 +1,1266 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This test performs too poorly to run with coverage enabled.
+@Tags(<String>['create', 'no_coverage'])
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/net.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/create.dart';
+import 'package:flutter_tools/src/dart/sdk.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/version.dart';
+
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+
+const String frameworkRevision = '12345678';
+const String frameworkChannel = 'omega';
+final Generator _kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
+final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
+ Platform: _kNoColorTerminalPlatform,
+};
+const String samplesIndexJson = '''[
+ { "id": "sample1" },
+ { "id": "sample2" }
+]''';
+
+void main() {
+ Directory tempDir;
+ Directory projectDir;
+ FlutterVersion mockFlutterVersion;
+ LoggingProcessManager loggingProcessManager;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ loggingProcessManager = LoggingProcessManager();
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_create_test.');
+ projectDir = tempDir.childDirectory('flutter_project');
+ mockFlutterVersion = MockFlutterVersion();
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ });
+
+ // Verify that we create a default project ('app') that is
+ // well-formed.
+ testUsingContext('can create a default project', () async {
+ await _createAndAnalyzeProject(
+ projectDir,
+ <String>[],
+ <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ 'flutter_project.iml',
+ 'ios/Flutter/AppFrameworkInfo.plist',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/GeneratedPluginRegistrant.h',
+ 'lib/main.dart',
+ ],
+ );
+ return _runFlutterTest(projectDir);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('can create a default project if empty directory exists', () async {
+ await projectDir.create(recursive: true);
+ await _createAndAnalyzeProject(
+ projectDir,
+ <String>[],
+ <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ 'flutter_project.iml',
+ 'ios/Flutter/AppFrameworkInfo.plist',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/GeneratedPluginRegistrant.h',
+ ],
+ );
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('creates a module project correctly', () async {
+ await _createAndAnalyzeProject(projectDir, <String>[
+ '--template=module',
+ ], <String>[
+ '.android/app/',
+ '.gitignore',
+ '.ios/Flutter',
+ '.metadata',
+ 'lib/main.dart',
+ 'pubspec.yaml',
+ 'README.md',
+ 'test/widget_test.dart',
+ ], unexpectedPaths: <String>[
+ 'android/',
+ 'ios/',
+ ]);
+ return _runFlutterTest(projectDir);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('cannot create a project if non-empty non-project directory exists with .metadata', () async {
+ await projectDir.absolute.childDirectory('blag').create(recursive: true);
+ await projectDir.absolute.childFile('.metadata').writeAsString('project_type: blag\n');
+ expect(
+ () async => await _createAndAnalyzeProject(projectDir, <String>[], <String>[], unexpectedPaths: <String>[
+ 'android/',
+ 'ios/',
+ '.android/',
+ '.ios/',
+ ]),
+ throwsToolExit(message: 'Sorry, unable to detect the type of project to recreate'));
+ }, timeout: allowForRemotePubInvocation, overrides: noColorTerminalOverride);
+
+ testUsingContext('Will create an app project if non-empty non-project directory exists without .metadata', () async {
+ await projectDir.absolute.childDirectory('blag').create(recursive: true);
+ await projectDir.absolute.childDirectory('.idea').create(recursive: true);
+ await _createAndAnalyzeProject(projectDir, <String>[], <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ 'flutter_project.iml',
+ 'ios/Flutter/AppFrameworkInfo.plist',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/GeneratedPluginRegistrant.h',
+ ], unexpectedPaths: <String>[
+ '.android/',
+ '.ios/',
+ ]);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('detects and recreates an app project correctly', () async {
+ await projectDir.absolute.childDirectory('lib').create(recursive: true);
+ await projectDir.absolute.childDirectory('ios').create(recursive: true);
+ await _createAndAnalyzeProject(projectDir, <String>[], <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ 'flutter_project.iml',
+ 'ios/Flutter/AppFrameworkInfo.plist',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/GeneratedPluginRegistrant.h',
+ ], unexpectedPaths: <String>[
+ '.android/',
+ '.ios/',
+ ]);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('detects and recreates a plugin project correctly', () async {
+ await projectDir.create(recursive: true);
+ await projectDir.absolute.childFile('.metadata').writeAsString('project_type: plugin\n');
+ return _createAndAnalyzeProject(
+ projectDir,
+ <String>[],
+ <String>[
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ 'example/ios/Runner/AppDelegate.h',
+ 'example/ios/Runner/AppDelegate.m',
+ 'example/ios/Runner/main.m',
+ 'example/lib/main.dart',
+ 'flutter_project.iml',
+ 'ios/Classes/FlutterProjectPlugin.h',
+ 'ios/Classes/FlutterProjectPlugin.m',
+ 'lib/flutter_project.dart',
+ ],
+ );
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('detects and recreates a package project correctly', () async {
+ await projectDir.create(recursive: true);
+ await projectDir.absolute.childFile('.metadata').writeAsString('project_type: package\n');
+ return _createAndAnalyzeProject(
+ projectDir,
+ <String>[],
+ <String>[
+ 'lib/flutter_project.dart',
+ 'test/flutter_project_test.dart',
+ ],
+ unexpectedPaths: <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ 'example/ios/Runner/AppDelegate.h',
+ 'example/ios/Runner/AppDelegate.m',
+ 'example/ios/Runner/main.m',
+ 'example/lib/main.dart',
+ 'ios/Classes/FlutterProjectPlugin.h',
+ 'ios/Classes/FlutterProjectPlugin.m',
+ 'ios/Runner/AppDelegate.h',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/main.m',
+ 'lib/main.dart',
+ 'test/widget_test.dart',
+ ],
+ );
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('kotlin/swift legacy app project', () async {
+ return _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=app', '--android-language=kotlin', '--ios-language=swift'],
+ <String>[
+ 'android/app/src/main/kotlin/com/example/flutter_project/MainActivity.kt',
+ 'ios/Runner/AppDelegate.swift',
+ 'ios/Runner/Runner-Bridging-Header.h',
+ 'lib/main.dart',
+ '.idea/libraries/KotlinJavaRuntime.xml',
+ ],
+ unexpectedPaths: <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'ios/Runner/AppDelegate.h',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/main.m',
+ ],
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can create a package project', () async {
+ await _createAndAnalyzeProject(
+ projectDir,
+ <String>['--template=package'],
+ <String>[
+ 'lib/flutter_project.dart',
+ 'test/flutter_project_test.dart',
+ ],
+ unexpectedPaths: <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ 'example/ios/Runner/AppDelegate.h',
+ 'example/ios/Runner/AppDelegate.m',
+ 'example/ios/Runner/main.m',
+ 'example/lib/main.dart',
+ 'ios/Classes/FlutterProjectPlugin.h',
+ 'ios/Classes/FlutterProjectPlugin.m',
+ 'ios/Runner/AppDelegate.h',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/main.m',
+ 'lib/main.dart',
+ 'test/widget_test.dart',
+ ],
+ );
+ return _runFlutterTest(projectDir);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('can create a plugin project', () async {
+ await _createAndAnalyzeProject(
+ projectDir,
+ <String>['--template=plugin'],
+ <String>[
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ 'example/ios/Runner/AppDelegate.h',
+ 'example/ios/Runner/AppDelegate.m',
+ 'example/ios/Runner/main.m',
+ 'example/lib/main.dart',
+ 'flutter_project.iml',
+ 'ios/Classes/FlutterProjectPlugin.h',
+ 'ios/Classes/FlutterProjectPlugin.m',
+ 'lib/flutter_project.dart',
+ ],
+ );
+ return _runFlutterTest(projectDir.childDirectory('example'));
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('kotlin/swift plugin project', () async {
+ return _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift'],
+ <String>[
+ 'android/src/main/kotlin/com/example/flutter_project/FlutterProjectPlugin.kt',
+ 'example/android/app/src/main/kotlin/com/example/flutter_project_example/MainActivity.kt',
+ 'example/ios/Runner/AppDelegate.swift',
+ 'example/ios/Runner/Runner-Bridging-Header.h',
+ 'example/lib/main.dart',
+ 'ios/Classes/FlutterProjectPlugin.h',
+ 'ios/Classes/FlutterProjectPlugin.m',
+ 'ios/Classes/SwiftFlutterProjectPlugin.swift',
+ 'lib/flutter_project.dart',
+ ],
+ unexpectedPaths: <String>[
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ 'example/ios/Runner/AppDelegate.h',
+ 'example/ios/Runner/AppDelegate.m',
+ 'example/ios/Runner/main.m',
+ ],
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('plugin project with custom org', () async {
+ return _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=plugin', '--org', 'com.bar.foo'],
+ <String>[
+ 'android/src/main/java/com/bar/foo/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
+ ],
+ unexpectedPaths: <String>[
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ ],
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('plugin project with valid custom project name', () async {
+ return _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=plugin', '--project-name', 'xyz'],
+ <String>[
+ 'android/src/main/java/com/example/xyz/XyzPlugin.java',
+ 'example/android/app/src/main/java/com/example/xyz_example/MainActivity.java',
+ ],
+ unexpectedPaths: <String>[
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ ],
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('plugin project with invalid custom project name', () async {
+ expect(
+ () => _createProject(projectDir,
+ <String>['--no-pub', '--template=plugin', '--project-name', 'xyz.xyz'],
+ <String>[],
+ ),
+ throwsToolExit(message: '"xyz.xyz" is not a valid Dart package name.'),
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('legacy app project with-driver-test', () async {
+ return _createAndAnalyzeProject(
+ projectDir,
+ <String>['--with-driver-test', '--template=app'],
+ <String>['lib/main.dart'],
+ );
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('module project with pub', () async {
+ return _createProject(projectDir, <String>[
+ '--template=module',
+ ], <String>[
+ '.android/build.gradle',
+ '.android/Flutter/build.gradle',
+ '.android/Flutter/src/main/AndroidManifest.xml',
+ '.android/Flutter/src/main/java/io/flutter/facade/Flutter.java',
+ '.android/Flutter/src/main/java/io/flutter/facade/FlutterFragment.java',
+ '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ '.android/gradle.properties',
+ '.android/gradle/wrapper/gradle-wrapper.jar',
+ '.android/gradle/wrapper/gradle-wrapper.properties',
+ '.android/gradlew',
+ '.android/gradlew.bat',
+ '.android/include_flutter.groovy',
+ '.android/local.properties',
+ '.android/settings.gradle',
+ '.gitignore',
+ '.metadata',
+ '.packages',
+ 'lib/main.dart',
+ 'pubspec.lock',
+ 'pubspec.yaml',
+ 'README.md',
+ 'test/widget_test.dart',
+ ], unexpectedPaths: <String>[
+ 'android/',
+ 'ios/',
+ ]);
+ }, timeout: allowForRemotePubInvocation);
+
+
+ testUsingContext('androidx app project', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--androidx', projectDir.path]);
+
+ void expectExists(String relPath) {
+ expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+ }
+
+ expectExists('android/gradle.properties');
+
+ final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+ expect(actualContents.contains('useAndroidX'), true);
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('non androidx app project', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--no-androidx', projectDir.path]);
+
+ void expectExists(String relPath) {
+ expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+ }
+
+ expectExists('android/gradle.properties');
+
+ final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+ expect(actualContents.contains('useAndroidX'), false);
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('androidx app module', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--template=module', '--no-pub', '--androidx', projectDir.path]);
+
+ final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+ expect(
+ project.usesAndroidX,
+ true,
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('non androidx app module', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--template=module', '--no-pub', '--no-androidx', projectDir.path]);
+
+ final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+ expect(
+ project.usesAndroidX,
+ false,
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('androidx plugin project', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=plugin', '--androidx', projectDir.path]);
+
+ void expectExists(String relPath) {
+ expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+ }
+
+ expectExists('android/gradle.properties');
+
+ final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+ expect(actualContents.contains('useAndroidX'), true);
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('non androidx plugin project', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=plugin', '--no-androidx', projectDir.path]);
+
+ void expectExists(String relPath) {
+ expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+ }
+
+ expectExists('android/gradle.properties');
+
+ final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
+
+ expect(actualContents.contains('useAndroidX'), false);
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('has correct content and formatting with module template', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--template=module', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
+
+ void expectExists(String relPath, [bool expectation = true]) {
+ expect(fs.isFileSync('${projectDir.path}/$relPath'), expectation);
+ }
+
+ expectExists('lib/main.dart');
+ expectExists('test/widget_test.dart');
+
+ final String actualContents = await fs.file(projectDir.path + '/test/widget_test.dart').readAsString();
+
+ expect(actualContents.contains('flutter_test.dart'), true);
+
+ for (FileSystemEntity file in projectDir.listSync(recursive: true)) {
+ if (file is File && file.path.endsWith('.dart')) {
+ final String original = file.readAsStringSync();
+
+ final Process process = await Process.start(
+ sdkBinaryName('dartfmt'),
+ <String>[file.path],
+ workingDirectory: projectDir.path,
+ );
+ final String formatted = await process.stdout.transform(utf8.decoder).join();
+
+ expect(original, formatted, reason: file.path);
+ }
+ }
+
+ await _runFlutterTest(projectDir, target: fs.path.join(projectDir.path, 'test', 'widget_test.dart'));
+
+ // Generated Xcode settings
+ final String xcodeConfigPath = fs.path.join('.ios', 'Flutter', 'Generated.xcconfig');
+ expectExists(xcodeConfigPath);
+ final File xcodeConfigFile = fs.file(fs.path.join(projectDir.path, xcodeConfigPath));
+ final String xcodeConfig = xcodeConfigFile.readAsStringSync();
+ expect(xcodeConfig, contains('FLUTTER_ROOT='));
+ expect(xcodeConfig, contains('FLUTTER_APPLICATION_PATH='));
+ expect(xcodeConfig, contains('FLUTTER_TARGET='));
+ // App identification
+ final String xcodeProjectPath = fs.path.join('.ios', 'Runner.xcodeproj', 'project.pbxproj');
+ expectExists(xcodeProjectPath);
+ final File xcodeProjectFile = fs.file(fs.path.join(projectDir.path, xcodeProjectPath));
+ final String xcodeProject = xcodeProjectFile.readAsStringSync();
+ expect(xcodeProject, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
+ // Xcode build system
+ final String xcodeWorkspaceSettingsPath = fs.path.join('.ios', 'Runner.xcworkspace', 'xcshareddata', 'WorkspaceSettings.xcsettings');
+ expectExists(xcodeWorkspaceSettingsPath, false);
+
+ final String versionPath = fs.path.join('.metadata');
+ expectExists(versionPath);
+ final String version = fs.file(fs.path.join(projectDir.path, versionPath)).readAsStringSync();
+ expect(version, contains('version:'));
+ expect(version, contains('revision: 12345678'));
+ expect(version, contains('channel: omega'));
+
+ // IntelliJ metadata
+ final String intelliJSdkMetadataPath = fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
+ expectExists(intelliJSdkMetadataPath);
+ final String sdkMetaContents = fs
+ .file(fs.path.join(
+ projectDir.path,
+ intelliJSdkMetadataPath,
+ ))
+ .readAsStringSync();
+ expect(sdkMetaContents, contains('<root url="file:/'));
+ expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
+ }, overrides: <Type, Generator>{
+ FlutterVersion: () => mockFlutterVersion,
+ Platform: _kNoColorTerminalPlatform,
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('has correct content and formatting with app template', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
+
+ void expectExists(String relPath) {
+ expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
+ }
+
+ expectExists('lib/main.dart');
+ expectExists('test/widget_test.dart');
+
+ for (FileSystemEntity file in projectDir.listSync(recursive: true)) {
+ if (file is File && file.path.endsWith('.dart')) {
+ final String original = file.readAsStringSync();
+
+ final Process process = await Process.start(
+ sdkBinaryName('dartfmt'),
+ <String>[file.path],
+ workingDirectory: projectDir.path,
+ );
+ final String formatted = await process.stdout.transform(utf8.decoder).join();
+
+ expect(original, formatted, reason: file.path);
+ }
+ }
+
+ await _runFlutterTest(projectDir, target: fs.path.join(projectDir.path, 'test', 'widget_test.dart'));
+
+ // Generated Xcode settings
+ final String xcodeConfigPath = fs.path.join('ios', 'Flutter', 'Generated.xcconfig');
+ expectExists(xcodeConfigPath);
+ final File xcodeConfigFile = fs.file(fs.path.join(projectDir.path, xcodeConfigPath));
+ final String xcodeConfig = xcodeConfigFile.readAsStringSync();
+ expect(xcodeConfig, contains('FLUTTER_ROOT='));
+ expect(xcodeConfig, contains('FLUTTER_APPLICATION_PATH='));
+ // App identification
+ final String xcodeProjectPath = fs.path.join('ios', 'Runner.xcodeproj', 'project.pbxproj');
+ expectExists(xcodeProjectPath);
+ final File xcodeProjectFile = fs.file(fs.path.join(projectDir.path, xcodeProjectPath));
+ final String xcodeProject = xcodeProjectFile.readAsStringSync();
+ expect(xcodeProject, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
+
+ final String versionPath = fs.path.join('.metadata');
+ expectExists(versionPath);
+ final String version = fs.file(fs.path.join(projectDir.path, versionPath)).readAsStringSync();
+ expect(version, contains('version:'));
+ expect(version, contains('revision: 12345678'));
+ expect(version, contains('channel: omega'));
+
+ // IntelliJ metadata
+ final String intelliJSdkMetadataPath = fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
+ expectExists(intelliJSdkMetadataPath);
+ final String sdkMetaContents = fs
+ .file(fs.path.join(
+ projectDir.path,
+ intelliJSdkMetadataPath,
+ ))
+ .readAsStringSync();
+ expect(sdkMetaContents, contains('<root url="file:/'));
+ expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
+ }, overrides: <Type, Generator>{
+ FlutterVersion: () => mockFlutterVersion,
+ Platform: _kNoColorTerminalPlatform,
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('has correct application id for android and bundle id for ios', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ String tmpProjectDir = fs.path.join(tempDir.path, 'hello_flutter');
+ await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.example', tmpProjectDir]);
+ FlutterProject project = FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+ expect(
+ project.ios.productBundleIdentifier,
+ 'com.example.helloFlutter',
+ );
+ expect(
+ project.android.applicationId,
+ 'com.example.hello_flutter',
+ );
+
+ tmpProjectDir = fs.path.join(tempDir.path, 'test_abc');
+ await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'abc^*.1#@', tmpProjectDir]);
+ project = FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+ expect(
+ project.ios.productBundleIdentifier,
+ 'abc.1.testAbc',
+ );
+ expect(
+ project.android.applicationId,
+ 'abc.u1.test_abc',
+ );
+
+ tmpProjectDir = fs.path.join(tempDir.path, 'flutter_project');
+ await runner.run(<String>['create', '--template=app', '--no-pub', '--org', '#+^%', tmpProjectDir]);
+ project = FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+ expect(
+ project.ios.productBundleIdentifier,
+ 'flutterProject.untitled',
+ );
+ expect(
+ project.android.applicationId,
+ 'flutter_project.untitled',
+ );
+ }, overrides: <Type, Generator>{
+ FlutterVersion: () => mockFlutterVersion,
+ Platform: _kNoColorTerminalPlatform,
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen default template over existing project', () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+ expect(metadata, contains('project_type: app\n'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen default template over existing app project with no metadta and detect the type', () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);
+
+ // Remove the .metadata to simulate an older instantiation that didn't generate those.
+ fs.file(fs.path.join(projectDir.path, '.metadata')).deleteSync();
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+ expect(metadata, contains('project_type: app\n'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen app template over existing app project and detect the type', () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+ expect(metadata, contains('project_type: app\n'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen template over existing module project and detect the type', () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=module', projectDir.path]);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+ expect(metadata, contains('project_type: module\n'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen default template over existing plugin project and detect the type', () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+ expect(metadata, contains('project_type: plugin'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen default template over existing package project and detect the type', () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=package', projectDir.path]);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
+ expect(metadata, contains('project_type: package'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen module .android/ folder, reusing custom org', () async {
+ await _createProject(
+ projectDir,
+ <String>['--template=module', '--org', 'com.bar.foo'],
+ <String>[],
+ );
+ projectDir.childDirectory('.android').deleteSync(recursive: true);
+ return _createProject(
+ projectDir,
+ <String>[],
+ <String>[
+ '.android/app/src/main/java/com/bar/foo/flutter_project/host/MainActivity.java',
+ ],
+ );
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('can re-gen module .ios/ folder, reusing custom org', () async {
+ await _createProject(
+ projectDir,
+ <String>['--template=module', '--org', 'com.bar.foo'],
+ <String>[],
+ );
+ projectDir.childDirectory('.ios').deleteSync(recursive: true);
+ await _createProject(projectDir, <String>[], <String>[]);
+ final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+ expect(
+ project.ios.productBundleIdentifier,
+ 'com.bar.foo.flutterProject',
+ );
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('can re-gen app android/ folder, reusing custom org', () async {
+ await _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
+ <String>[],
+ );
+ projectDir.childDirectory('android').deleteSync(recursive: true);
+ return _createProject(
+ projectDir,
+ <String>['--no-pub'],
+ <String>[
+ 'android/app/src/main/java/com/bar/foo/flutter_project/MainActivity.java',
+ ],
+ unexpectedPaths: <String>[
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ ],
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen app ios/ folder, reusing custom org', () async {
+ await _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
+ <String>[],
+ );
+ projectDir.childDirectory('ios').deleteSync(recursive: true);
+ await _createProject(projectDir, <String>['--no-pub'], <String>[]);
+ final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+ expect(
+ project.ios.productBundleIdentifier,
+ 'com.bar.foo.flutterProject',
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org', () async {
+ await _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=plugin', '--org', 'com.bar.foo'],
+ <String>[],
+ );
+ projectDir.childDirectory('example').deleteSync(recursive: true);
+ projectDir.childDirectory('ios').deleteSync(recursive: true);
+ await _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=plugin'],
+ <String>[
+ 'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
+ 'ios/Classes/FlutterProjectPlugin.h',
+ ],
+ unexpectedPaths: <String>[
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ ],
+ );
+ final FlutterProject project = FlutterProject.fromDirectory(projectDir);
+ expect(
+ project.example.ios.productBundleIdentifier,
+ 'com.bar.foo.flutterProjectExample',
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('fails to re-gen without specified org when org is ambiguous', () async {
+ await _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
+ <String>[],
+ );
+ fs.directory(fs.path.join(projectDir.path, 'ios')).deleteSync(recursive: true);
+ await _createProject(
+ projectDir,
+ <String>['--no-pub', '--template=app', '--org', 'com.bar.baz'],
+ <String>[],
+ );
+ expect(
+ () => _createProject(projectDir, <String>[], <String>[]),
+ throwsToolExit(message: 'Ambiguous organization'),
+ );
+ }, timeout: allowForCreateFlutterProject);
+
+ // Verify that we help the user correct an option ordering issue
+ testUsingContext('produces sensible error message', () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ expect(
+ runner.run(<String>['create', projectDir.path, '--pub']),
+ throwsToolExit(exitCode: 2, message: 'Try moving --pub'),
+ );
+ });
+
+ testUsingContext('fails when file exists where output directory should be', () async {
+ Cache.flutterRoot = '../..';
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ final File existingFile = fs.file(fs.path.join(projectDir.path, 'bad'));
+ if (!existingFile.existsSync()) {
+ existingFile.createSync(recursive: true);
+ }
+ expect(
+ runner.run(<String>['create', existingFile.path]),
+ throwsToolExit(message: 'existing file'),
+ );
+ });
+
+ testUsingContext('fails overwrite when file exists where output directory should be', () async {
+ Cache.flutterRoot = '../..';
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ final File existingFile = fs.file(fs.path.join(projectDir.path, 'bad'));
+ if (!existingFile.existsSync()) {
+ existingFile.createSync(recursive: true);
+ }
+ expect(
+ runner.run(<String>['create', '--overwrite', existingFile.path]),
+ throwsToolExit(message: 'existing file'),
+ );
+ });
+
+ testUsingContext('overwrites existing directory when requested', () async {
+ Cache.flutterRoot = '../..';
+ final Directory existingDirectory = fs.directory(fs.path.join(projectDir.path, 'bad'));
+ if (!existingDirectory.existsSync()) {
+ existingDirectory.createSync(recursive: true);
+ }
+ final File existingFile = fs.file(fs.path.join(existingDirectory.path, 'lib', 'main.dart'));
+ existingFile.createSync(recursive: true);
+ await _createProject(
+ fs.directory(existingDirectory.path),
+ <String>['--overwrite'],
+ <String>[
+ 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ 'lib/main.dart',
+ 'ios/Flutter/AppFrameworkInfo.plist',
+ 'ios/Runner/AppDelegate.m',
+ 'ios/Runner/GeneratedPluginRegistrant.h',
+ ],
+ );
+ });
+
+ testUsingContext('fails when invalid package name', () async {
+ Cache.flutterRoot = '../..';
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ expect(
+ runner.run(<String>['create', fs.path.join(projectDir.path, 'invalidName')]),
+ throwsToolExit(message: '"invalidName" is not a valid Dart package name.'),
+ );
+ });
+
+ testUsingContext(
+ 'invokes pub offline when requested',
+ () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--pub', '--offline', projectDir.path]);
+ expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
+ expect(loggingProcessManager.commands.first, contains('--offline'));
+ },
+ timeout: allowForCreateFlutterProject,
+ overrides: <Type, Generator>{
+ ProcessManager: () => loggingProcessManager,
+ },
+ );
+
+ testUsingContext(
+ 'invokes pub online when offline not requested',
+ () async {
+ Cache.flutterRoot = '../..';
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--pub', projectDir.path]);
+ expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]pub')));
+ expect(loggingProcessManager.commands.first, isNot(contains('--offline')));
+ },
+ timeout: allowForCreateFlutterProject,
+ overrides: <Type, Generator>{
+ ProcessManager: () => loggingProcessManager,
+ },
+ );
+
+ testUsingContext('can create a sample-based project', () async {
+ await _createAndAnalyzeProject(
+ projectDir,
+ <String>['--no-pub', '--sample=foo.bar.Baz'],
+ <String>[
+ 'lib/main.dart',
+ 'flutter_project.iml',
+ 'android/app/src/main/AndroidManifest.xml',
+ 'ios/Flutter/AppFrameworkInfo.plist',
+ ],
+ unexpectedPaths: <String>['test'],
+ );
+ expect(projectDir.childDirectory('lib').childFile('main.dart').readAsStringSync(),
+ contains('void main() {}'));
+ }, timeout: allowForRemotePubInvocation, overrides: <Type, Generator>{
+ HttpClientFactory: () => () => MockHttpClient(200, result: 'void main() {}'),
+ });
+
+ testUsingContext('can write samples index to disk', () async {
+ final String outputFile = fs.path.join(tempDir.path, 'flutter_samples.json');
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ final List<String> args = <String>[
+ 'create',
+ '--list-samples',
+ outputFile,
+ ];
+
+ await runner.run(args);
+ final File expectedFile = fs.file(outputFile);
+ expect(expectedFile.existsSync(), isTrue);
+ expect(expectedFile.readAsStringSync(), equals(samplesIndexJson));
+ }, overrides: <Type, Generator>{
+ HttpClientFactory: () =>
+ () => MockHttpClient(200, result: samplesIndexJson),
+ });
+ testUsingContext('provides an error to the user if samples json download fails', () async {
+ final String outputFile = fs.path.join(tempDir.path, 'flutter_samples.json');
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ final List<String> args = <String>[
+ 'create',
+ '--list-samples',
+ outputFile,
+ ];
+
+ await expectLater(runner.run(args), throwsToolExit(exitCode: 2, message: 'Failed to write samples'));
+ expect(fs.file(outputFile).existsSync(), isFalse);
+ }, overrides: <Type, Generator>{
+ HttpClientFactory: () =>
+ () => MockHttpClient(404, result: 'not found'),
+ });
+}
+
+
+Future<void> _createProject(
+ Directory dir,
+ List<String> createArgs,
+ List<String> expectedPaths, {
+ List<String> unexpectedPaths = const <String>[],
+}) async {
+ Cache.flutterRoot = '../../..';
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ await runner.run(<String>[
+ 'create',
+ ...createArgs,
+ dir.path,
+ ]);
+
+ bool pathExists(String path) {
+ final String fullPath = fs.path.join(dir.path, path);
+ return fs.typeSync(fullPath) != FileSystemEntityType.notFound;
+ }
+
+ final List<String> failures = <String>[];
+ for (String path in expectedPaths) {
+ if (!pathExists(path)) {
+ failures.add('Path "$path" does not exist.');
+ }
+ }
+ for (String path in unexpectedPaths) {
+ if (pathExists(path)) {
+ failures.add('Path "$path" exists when it shouldn\'t.');
+ }
+ }
+ expect(failures, isEmpty, reason: failures.join('\n'));
+}
+
+Future<void> _createAndAnalyzeProject(
+ Directory dir,
+ List<String> createArgs,
+ List<String> expectedPaths, {
+ List<String> unexpectedPaths = const <String>[],
+}) async {
+ await _createProject(dir, createArgs, expectedPaths, unexpectedPaths: unexpectedPaths);
+ await _analyzeProject(dir.path);
+}
+
+Future<void> _analyzeProject(String workingDir) async {
+ final String flutterToolsPath = fs.path.absolute(fs.path.join(
+ 'bin',
+ 'flutter_tools.dart',
+ ));
+
+ final List<String> args = <String>[
+ ...dartVmFlags,
+ flutterToolsPath,
+ 'analyze',
+ ];
+
+ final ProcessResult exec = await Process.run(
+ '$dartSdkPath/bin/dart',
+ args,
+ workingDirectory: workingDir,
+ );
+ if (exec.exitCode != 0) {
+ print(exec.stdout);
+ print(exec.stderr);
+ }
+ expect(exec.exitCode, 0);
+}
+
+Future<void> _runFlutterTest(Directory workingDir, { String target }) async {
+ final String flutterToolsPath = fs.path.absolute(fs.path.join(
+ 'bin',
+ 'flutter_tools.dart',
+ ));
+
+ // While flutter test does get packages, it doesn't write version
+ // files anymore.
+ await Process.run(
+ '$dartSdkPath/bin/dart',
+ <String>[
+ ...dartVmFlags,
+ flutterToolsPath,
+ 'packages',
+ 'get',
+ ],
+ workingDirectory: workingDir.path,
+ );
+
+ final List<String> args = <String>[
+ ...dartVmFlags,
+ flutterToolsPath,
+ 'test',
+ '--no-color',
+ if (target != null) target,
+ ];
+
+ final ProcessResult exec = await Process.run(
+ '$dartSdkPath/bin/dart',
+ args,
+ workingDirectory: workingDir.path,
+ );
+ if (exec.exitCode != 0) {
+ print(exec.stdout);
+ print(exec.stderr);
+ }
+ expect(exec.exitCode, 0);
+}
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
+
+/// A ProcessManager that invokes a real process manager, but keeps
+/// track of all commands sent to it.
+class LoggingProcessManager extends LocalProcessManager {
+ List<List<String>> commands = <List<String>>[];
+
+ @override
+ Future<Process> start(
+ List<dynamic> command, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ ProcessStartMode mode = ProcessStartMode.normal,
+ }) {
+ commands.add(command);
+ return super.start(
+ command,
+ workingDirectory: workingDirectory,
+ environment: environment,
+ includeParentEnvironment: includeParentEnvironment,
+ runInShell: runInShell,
+ mode: mode,
+ );
+ }
+}
+
+class MockHttpClient implements HttpClient {
+ MockHttpClient(this.statusCode, {this.result});
+
+ final int statusCode;
+ final String result;
+
+ @override
+ Future<HttpClientRequest> getUrl(Uri url) async {
+ return MockHttpClientRequest(statusCode, result: result);
+ }
+
+ @override
+ dynamic noSuchMethod(Invocation invocation) {
+ throw 'io.HttpClient - $invocation';
+ }
+}
+
+class MockHttpClientRequest implements HttpClientRequest {
+ MockHttpClientRequest(this.statusCode, {this.result});
+
+ final int statusCode;
+ final String result;
+
+ @override
+ Future<HttpClientResponse> close() async {
+ return MockHttpClientResponse(statusCode, result: result);
+ }
+
+ @override
+ dynamic noSuchMethod(Invocation invocation) {
+ throw 'io.HttpClientRequest - $invocation';
+ }
+}
+
+class MockHttpClientResponse implements HttpClientResponse {
+ MockHttpClientResponse(this.statusCode, {this.result});
+
+ @override
+ final int statusCode;
+
+ final String result;
+
+ @override
+ String get reasonPhrase => '<reason phrase>';
+
+ @override
+ HttpClientResponseCompressionState get compressionState {
+ return HttpClientResponseCompressionState.decompressed;
+ }
+
+ @override
+ StreamSubscription<Uint8List> listen(
+ void onData(Uint8List event), {
+ Function onError,
+ void onDone(),
+ bool cancelOnError,
+ }) {
+ return Stream<Uint8List>.fromIterable(<Uint8List>[Uint8List.fromList(result.codeUnits)])
+ .listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+ }
+
+ @override
+ Future<dynamic> forEach(void Function(Uint8List element) action) {
+ action(Uint8List.fromList(result.codeUnits));
+ return Future<void>.value();
+ }
+
+ @override
+ dynamic noSuchMethod(Invocation invocation) {
+ throw 'io.HttpClientResponse - $invocation';
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart b/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart
new file mode 100644
index 0000000..735cee5
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart
@@ -0,0 +1,104 @@
+// Copyright 2015 The Chromium 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 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/create.dart';
+import 'package:flutter_tools/src/doctor.dart';
+import 'package:flutter_tools/src/usage.dart';
+
+import '../../src/common.dart';
+import '../../src/testbed.dart';
+
+
+void main() {
+ group('usageValues', () {
+ Testbed testbed;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ testbed = Testbed(setup: () {
+ final List<String> paths = <String>[
+ fs.path.join('flutter', 'packages', 'flutter', 'pubspec.yaml'),
+ fs.path.join('flutter', 'packages', 'flutter_driver', 'pubspec.yaml'),
+ fs.path.join('flutter', 'packages', 'flutter_test', 'pubspec.yaml'),
+ fs.path.join('flutter', 'bin', 'cache', 'artifacts', 'gradle_wrapper', 'wrapper'),
+ fs.path.join('usr', 'local', 'bin', 'adb'),
+ fs.path.join('Android', 'platform-tools', 'foo'),
+ ];
+ for (String path in paths) {
+ fs.file(path).createSync(recursive: true);
+ }
+ }, overrides: <Type, Generator>{
+ DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
+ });
+ });
+
+ test('set template type as usage value', () => testbed.run(() async {
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=module', 'testy']);
+ expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'module'));
+
+ await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
+ expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'app'));
+
+ await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=package', 'testy']);
+ expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'package'));
+
+ await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=plugin', 'testy']);
+ expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'plugin'));
+ }));
+
+ test('set iOS host language type as usage value', () => testbed.run(() async {
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
+ expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'objc'));
+
+ await runner.run(<String>[
+ 'create',
+ '--flutter-root=flutter',
+ '--no-pub',
+ '--template=app',
+ '--ios-language=swift',
+ 'testy',
+ ]);
+ expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'swift'));
+
+ }));
+
+ test('set Android host language type as usage value', () => testbed.run(() async {
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
+ expect(await command.usageValues, containsPair(kCommandCreateAndroidLanguage, 'java'));
+
+ await runner.run(<String>[
+ 'create',
+ '--flutter-root=flutter',
+ '--no-pub',
+ '--template=app',
+ '--android-language=kotlin',
+ 'testy',
+ ]);
+ expect(await command.usageValues, containsPair(kCommandCreateAndroidLanguage, 'kotlin'));
+ }));
+ });
+}
+
+class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
+ @override
+ List<DoctorValidator> get validators => <DoctorValidator>[];
+
+ @override
+ List<Workflow> get workflows => <Workflow>[];
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/daemon_test.dart b/packages/flutter_tools/test/general.shard/commands/daemon_test.dart
new file mode 100644
index 0000000..54aebdb
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/daemon_test.dart
@@ -0,0 +1,323 @@
+// Copyright 2015 The Chromium 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:flutter_tools/src/android/android_workflow.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/commands/daemon.dart';
+import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
+import 'package:flutter_tools/src/globals.dart';
+import 'package:flutter_tools/src/ios/ios_workflow.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ Daemon daemon;
+ NotifyingLogger notifyingLogger;
+
+ group('daemon', () {
+ setUp(() {
+ notifyingLogger = NotifyingLogger();
+ });
+
+ tearDown(() {
+ if (daemon != null)
+ return daemon.shutdown();
+ notifyingLogger.dispose();
+ });
+
+ testUsingContext('daemon.version command should succeed', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ );
+ commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.version'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ expect(response['result'], isNotEmpty);
+ expect(response['result'] is String, true);
+ await responses.close();
+ await commands.close();
+ });
+
+ testUsingContext('printError should send daemon.logMessage event', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ );
+ printError('daemon.logMessage test');
+ final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
+ return map['event'] == 'daemon.logMessage' && map['params']['level'] == 'error';
+ });
+ expect(response['id'], isNull);
+ expect(response['event'], 'daemon.logMessage');
+ final Map<String, String> logMessage = response['params'].cast<String, String>();
+ expect(logMessage['level'], 'error');
+ expect(logMessage['message'], 'daemon.logMessage test');
+ await responses.close();
+ await commands.close();
+ }, overrides: <Type, Generator>{
+ Logger: () => notifyingLogger,
+ });
+
+ testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async {
+ final StringBuffer buffer = StringBuffer();
+
+ await runZoned<Future<void>>(() async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ logToStdout: true,
+ );
+ printStatus('daemon.logMessage test');
+ // Service the event loop.
+ await Future<void>.value();
+ }, zoneSpecification: ZoneSpecification(print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
+ buffer.writeln(line);
+ }));
+
+ expect(buffer.toString().trim(), 'daemon.logMessage test');
+ }, overrides: <Type, Generator>{
+ Logger: () => notifyingLogger,
+ });
+
+ testUsingContext('daemon.shutdown command should stop daemon', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ );
+ commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.shutdown'});
+ return daemon.onExit.then<void>((int code) async {
+ await commands.close();
+ expect(code, 0);
+ });
+ });
+
+ testUsingContext('app.restart without an appId should report an error', () async {
+ final DaemonCommand command = DaemonCommand();
+ applyMocksToCommand(command);
+
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ daemonCommand: command,
+ notifyingLogger: notifyingLogger,
+ );
+
+ commands.add(<String, dynamic>{'id': 0, 'method': 'app.restart'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ expect(response['error'], contains('appId is required'));
+ await responses.close();
+ await commands.close();
+ });
+
+ testUsingContext('ext.flutter.debugPaint via service extension without an appId should report an error', () async {
+ final DaemonCommand command = DaemonCommand();
+ applyMocksToCommand(command);
+
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ daemonCommand: command,
+ notifyingLogger: notifyingLogger,
+ );
+
+ commands.add(<String, dynamic>{
+ 'id': 0,
+ 'method': 'app.callServiceExtension',
+ 'params': <String, String>{
+ 'methodName': 'ext.flutter.debugPaint',
+ },
+ });
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ expect(response['error'], contains('appId is required'));
+ await responses.close();
+ await commands.close();
+ });
+
+ testUsingContext('app.stop without appId should report an error', () async {
+ final DaemonCommand command = DaemonCommand();
+ applyMocksToCommand(command);
+
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ daemonCommand: command,
+ notifyingLogger: notifyingLogger,
+ );
+
+ commands.add(<String, dynamic>{'id': 0, 'method': 'app.stop'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ expect(response['error'], contains('appId is required'));
+ await responses.close();
+ await commands.close();
+ });
+
+ testUsingContext('device.getDevices should respond with list', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ );
+ commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ expect(response['result'], isList);
+ await responses.close();
+ await commands.close();
+ });
+
+ testUsingContext('device.getDevices reports available devices', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ );
+ final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
+ daemon.deviceDomain.addDeviceDiscoverer(discoverer);
+ discoverer.addDevice(MockAndroidDevice());
+ commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ final dynamic result = response['result'];
+ expect(result, isList);
+ expect(result, isNotEmpty);
+ await responses.close();
+ await commands.close();
+ });
+
+ testUsingContext('should send device.added event when device is discovered', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ );
+
+ final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
+ daemon.deviceDomain.addDeviceDiscoverer(discoverer);
+ discoverer.addDevice(MockAndroidDevice());
+
+ return await responses.stream.skipWhile(_isConnectedEvent).first.then<void>((Map<String, dynamic> response) async {
+ expect(response['event'], 'device.added');
+ expect(response['params'], isMap);
+
+ final Map<String, dynamic> params = response['params'];
+ expect(params['platform'], isNotEmpty); // the mock device has a platform of 'android-arm'
+
+ await responses.close();
+ await commands.close();
+ });
+ }, overrides: <Type, Generator>{
+ AndroidWorkflow: () => MockAndroidWorkflow(),
+ IOSWorkflow: () => MockIOSWorkflow(),
+ FuchsiaWorkflow: () => MockFuchsiaWorkflow(),
+ });
+
+ testUsingContext('emulator.launch without an emulatorId should report an error', () async {
+ final DaemonCommand command = DaemonCommand();
+ applyMocksToCommand(command);
+
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ daemonCommand: command,
+ notifyingLogger: notifyingLogger,
+ );
+
+ commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ expect(response['error'], contains('emulatorId is required'));
+ await responses.close();
+ await commands.close();
+ });
+
+ testUsingContext('emulator.getEmulators should respond with list', () async {
+ final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
+ final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
+ daemon = Daemon(
+ commands.stream,
+ responses.add,
+ notifyingLogger: notifyingLogger,
+ );
+ commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.getEmulators'});
+ final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
+ expect(response['id'], 0);
+ expect(response['result'], isList);
+ await responses.close();
+ await commands.close();
+ });
+ });
+
+ group('daemon serialization', () {
+ test('OperationResult', () {
+ expect(
+ jsonEncodeObject(OperationResult.ok),
+ '{"code":0,"message":""}',
+ );
+ expect(
+ jsonEncodeObject(OperationResult(1, 'foo')),
+ '{"code":1,"message":"foo"}',
+ );
+ });
+ });
+}
+
+bool _notEvent(Map<String, dynamic> map) => map['event'] == null;
+
+bool _isConnectedEvent(Map<String, dynamic> map) => map['event'] == 'daemon.connected';
+
+class MockFuchsiaWorkflow extends FuchsiaWorkflow {
+ MockFuchsiaWorkflow({ this.canListDevices = true });
+
+ @override
+ final bool canListDevices;
+}
+
+class MockAndroidWorkflow extends AndroidWorkflow {
+ MockAndroidWorkflow({ this.canListDevices = true });
+
+ @override
+ final bool canListDevices;
+}
+
+class MockIOSWorkflow extends IOSWorkflow {
+ MockIOSWorkflow({ this.canListDevices =true });
+
+ @override
+ final bool canListDevices;
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/devices_test.dart b/packages/flutter_tools/test/general.shard/commands/devices_test.dart
new file mode 100644
index 0000000..01bbb9a
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/devices_test.dart
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:flutter_tools/src/android/android_sdk.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/devices.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ group('devices', () {
+ setUpAll(() {
+ Cache.disableLocking();
+ // TODO(jonahwilliams): adjust the individual tests so they do not
+ // depend on the host environment.
+ debugDisableWebAndDesktop = true;
+ });
+
+ testUsingContext('returns 0 when called', () async {
+ final DevicesCommand command = DevicesCommand();
+ await createTestCommandRunner(command).run(<String>['devices']);
+ });
+
+ testUsingContext('no error when no connected devices', () async {
+ final DevicesCommand command = DevicesCommand();
+ await createTestCommandRunner(command).run(<String>['devices']);
+ expect(testLogger.statusText, contains('No devices detected'));
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => null,
+ DeviceManager: () => DeviceManager(),
+ ProcessManager: () => MockProcessManager(),
+ });
+ });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {
+ @override
+ Future<ProcessResult> run(
+ List<dynamic> command, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ Encoding stdoutEncoding = systemEncoding,
+ Encoding stderrEncoding = systemEncoding,
+ }) async {
+ return ProcessResult(0, 0, '', '');
+ }
+
+ @override
+ ProcessResult runSync(
+ List<dynamic> command, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ Encoding stdoutEncoding = systemEncoding,
+ Encoding stderrEncoding = systemEncoding,
+ }) {
+ return ProcessResult(0, 0, '', '');
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/doctor_test.dart b/packages/flutter_tools/test/general.shard/commands/doctor_test.dart
new file mode 100644
index 0000000..5aa2a89
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/doctor_test.dart
@@ -0,0 +1,831 @@
+// Copyright 2015 The Chromium 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:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.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/doctor.dart';
+import 'package:flutter_tools/src/globals.dart';
+import 'package:flutter_tools/src/proxy_validator.dart';
+import 'package:flutter_tools/src/vscode/vscode.dart';
+import 'package:flutter_tools/src/vscode/vscode_validator.dart';
+import 'package:flutter_tools/src/usage.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+final Generator _kNoColorOutputPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
+final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
+ Platform: _kNoColorOutputPlatform,
+};
+
+void main() {
+ MockProcessManager mockProcessManager;
+
+ setUp(() {
+ mockProcessManager = MockProcessManager();
+ });
+
+ group('doctor', () {
+ testUsingContext('intellij validator', () async {
+ const String installPath = '/path/to/intelliJ';
+ final ValidationResult result = await IntelliJValidatorTestTarget('Test', installPath).validate();
+ expect(result.type, ValidationType.partial);
+ expect(result.statusInfo, 'version test.test.test');
+ expect(result.messages, hasLength(4));
+
+ ValidationMessage message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('IntelliJ '));
+ expect(message.message, 'IntelliJ at $installPath');
+
+ message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('Dart '));
+ expect(message.message, 'Dart plugin version 162.2485');
+
+ message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+ expect(message.message, contains('Flutter plugin version 0.1.3'));
+ expect(message.message, contains('recommended minimum version'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('vs code validator when both installed', () async {
+ final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension.validate();
+ expect(result.type, ValidationType.installed);
+ expect(result.statusInfo, 'version 1.2.3');
+ expect(result.messages, hasLength(2));
+
+ ValidationMessage message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
+ expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
+
+ message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+ expect(message.message, 'Flutter extension version 4.5.6');
+ expect(message.isError, isFalse);
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('vs code validator when 64bit installed', () async {
+ expect(VsCodeValidatorTestTargets.installedWithExtension64bit.title, 'VS Code, 64-bit edition');
+ final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension64bit.validate();
+ expect(result.type, ValidationType.installed);
+ expect(result.statusInfo, 'version 1.2.3');
+ expect(result.messages, hasLength(2));
+
+ ValidationMessage message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
+ expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
+
+ message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+ expect(message.message, 'Flutter extension version 4.5.6');
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('vs code validator when extension missing', () async {
+ final ValidationResult result = await VsCodeValidatorTestTargets.installedWithoutExtension.validate();
+ expect(result.type, ValidationType.partial);
+ expect(result.statusInfo, 'version 1.2.3');
+ expect(result.messages, hasLength(2));
+
+ ValidationMessage message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
+ expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
+
+ message = result.messages
+ .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter '));
+ expect(message.message, startsWith('Flutter extension not installed'));
+ expect(message.isError, isTrue);
+ }, overrides: noColorTerminalOverride);
+ });
+
+ group('proxy validator', () {
+ testUsingContext('does not show if HTTP_PROXY is not set', () {
+ expect(ProxyValidator.shouldShow, isFalse);
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform()..environment = <String, String>{},
+ });
+
+ testUsingContext('does not show if HTTP_PROXY is only whitespace', () {
+ expect(ProxyValidator.shouldShow, isFalse);
+ }, overrides: <Type, Generator>{
+ Platform: () =>
+ FakePlatform()..environment = <String, String>{'HTTP_PROXY': ' '},
+ });
+
+ testUsingContext('shows when HTTP_PROXY is set', () {
+ expect(ProxyValidator.shouldShow, isTrue);
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform()
+ ..environment = <String, String>{'HTTP_PROXY': 'fakeproxy.local'},
+ });
+
+ testUsingContext('shows when http_proxy is set', () {
+ expect(ProxyValidator.shouldShow, isTrue);
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform()
+ ..environment = <String, String>{'http_proxy': 'fakeproxy.local'},
+ });
+
+ testUsingContext('reports success when NO_PROXY is configured correctly', () async {
+ final ValidationResult results = await ProxyValidator().validate();
+ final List<ValidationMessage> issues = results.messages
+ .where((ValidationMessage msg) => msg.isError || msg.isHint)
+ .toList();
+ expect(issues, hasLength(0));
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform()
+ ..environment = <String, String>{
+ 'HTTP_PROXY': 'fakeproxy.local',
+ 'NO_PROXY': 'localhost,127.0.0.1',
+ },
+ });
+
+ testUsingContext('reports success when no_proxy is configured correctly', () async {
+ final ValidationResult results = await ProxyValidator().validate();
+ final List<ValidationMessage> issues = results.messages
+ .where((ValidationMessage msg) => msg.isError || msg.isHint)
+ .toList();
+ expect(issues, hasLength(0));
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform()
+ ..environment = <String, String>{
+ 'http_proxy': 'fakeproxy.local',
+ 'no_proxy': 'localhost,127.0.0.1',
+ },
+ });
+
+ testUsingContext('reports issues when NO_PROXY is missing localhost', () async {
+ final ValidationResult results = await ProxyValidator().validate();
+ final List<ValidationMessage> issues = results.messages
+ .where((ValidationMessage msg) => msg.isError || msg.isHint)
+ .toList();
+ expect(issues, isNot(hasLength(0)));
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform()
+ ..environment = <String, String>{
+ 'HTTP_PROXY': 'fakeproxy.local',
+ 'NO_PROXY': '127.0.0.1',
+ },
+ });
+
+ testUsingContext('reports issues when NO_PROXY is missing 127.0.0.1', () async {
+ final ValidationResult results = await ProxyValidator().validate();
+ final List<ValidationMessage> issues = results.messages
+ .where((ValidationMessage msg) => msg.isError || msg.isHint)
+ .toList();
+ expect(issues, isNot(hasLength(0)));
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform()
+ ..environment = <String, String>{
+ 'HTTP_PROXY': 'fakeproxy.local',
+ 'NO_PROXY': 'localhost',
+ },
+ });
+ });
+
+ group('doctor with overridden validators', () {
+ testUsingContext('validate non-verbose output format for run without issues', () async {
+ expect(await doctor.diagnose(verbose: false), isTrue);
+ expect(testLogger.statusText, equals(
+ 'Doctor summary (to see all details, run flutter doctor -v):\n'
+ '[✓] Passing Validator (with statusInfo)\n'
+ '[✓] Another Passing Validator (with statusInfo)\n'
+ '[✓] Providing validators is fun (with statusInfo)\n'
+ '\n'
+ '• No issues found!\n'
+ ));
+ }, overrides: <Type, Generator>{
+ DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
+ Platform: _kNoColorOutputPlatform,
+ });
+ });
+
+ group('doctor usage params', () {
+ Usage mockUsage;
+
+ setUp(() {
+ mockUsage = MockUsage();
+ when(mockUsage.isFirstRun).thenReturn(true);
+ });
+
+ testUsingContext('contains installed', () async {
+ await doctor.diagnose(verbose: false);
+
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured,
+ <dynamic>['installed', 'installed', 'installed'],
+ );
+ }, overrides: <Type, Generator>{
+ DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
+ Platform: _kNoColorOutputPlatform,
+ Usage: () => mockUsage,
+ });
+
+ testUsingContext('contains installed and partial', () async {
+ await FakePassingDoctor().diagnose(verbose: false);
+
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured,
+ <dynamic>['installed', 'installed'],
+ );
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithHintsOnly', captureAny)).captured,
+ <dynamic>['partial'],
+ );
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithErrors', captureAny)).captured,
+ <dynamic>['partial'],
+ );
+ }, overrides: <Type, Generator>{
+ Platform: _kNoColorOutputPlatform,
+ Usage: () => mockUsage,
+ });
+
+ testUsingContext('contains installed, missing and partial', () async {
+ await FakeDoctor().diagnose(verbose: false);
+
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.PassingValidator', captureAny)).captured,
+ <dynamic>['installed'],
+ );
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.MissingValidator', captureAny)).captured,
+ <dynamic>['missing'],
+ );
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.NotAvailableValidator', captureAny)).captured,
+ <dynamic>['notAvailable'],
+ );
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithHintsOnly', captureAny)).captured,
+ <dynamic>['partial'],
+ );
+ expect(
+ verify(mockUsage.sendEvent('doctorResult.PartialValidatorWithErrors', captureAny)).captured,
+ <dynamic>['partial'],
+ );
+ }, overrides: <Type, Generator>{
+ Platform: _kNoColorOutputPlatform,
+ Usage: () => mockUsage,
+ });
+ });
+
+ group('doctor with fake validators', () {
+ testUsingContext('validate non-verbose output format for run without issues', () async {
+ expect(await FakeQuietDoctor().diagnose(verbose: false), isTrue);
+ expect(testLogger.statusText, equals(
+ 'Doctor summary (to see all details, run flutter doctor -v):\n'
+ '[✓] Passing Validator (with statusInfo)\n'
+ '[✓] Another Passing Validator (with statusInfo)\n'
+ '[✓] Validators are fun (with statusInfo)\n'
+ '[✓] Four score and seven validators ago (with statusInfo)\n'
+ '\n'
+ '• No issues found!\n'
+ ));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate non-verbose output format when only one category fails', () async {
+ expect(await FakeSinglePassingDoctor().diagnose(verbose: false), isTrue);
+ expect(testLogger.statusText, equals(
+ 'Doctor summary (to see all details, run flutter doctor -v):\n'
+ '[!] Partial Validator with only a Hint\n'
+ ' ! There is a hint here\n'
+ '\n'
+ '! Doctor found issues in 1 category.\n'
+ ));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate non-verbose output format for a passing run', () async {
+ expect(await FakePassingDoctor().diagnose(verbose: false), isTrue);
+ expect(testLogger.statusText, equals(
+ 'Doctor summary (to see all details, run flutter doctor -v):\n'
+ '[✓] Passing Validator (with statusInfo)\n'
+ '[!] Partial Validator with only a Hint\n'
+ ' ! There is a hint here\n'
+ '[!] Partial Validator with Errors\n'
+ ' ✗ An error message indicating partial installation\n'
+ ' ! Maybe a hint will help the user\n'
+ '[✓] Another Passing Validator (with statusInfo)\n'
+ '\n'
+ '! Doctor found issues in 2 categories.\n'
+ ));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate non-verbose output format', () async {
+ expect(await FakeDoctor().diagnose(verbose: false), isFalse);
+ expect(testLogger.statusText, equals(
+ 'Doctor summary (to see all details, run flutter doctor -v):\n'
+ '[✓] Passing Validator (with statusInfo)\n'
+ '[✗] Missing Validator\n'
+ ' ✗ A useful error message\n'
+ ' ! A hint message\n'
+ '[!] Not Available Validator\n'
+ ' ✗ A useful error message\n'
+ ' ! A hint message\n'
+ '[!] Partial Validator with only a Hint\n'
+ ' ! There is a hint here\n'
+ '[!] Partial Validator with Errors\n'
+ ' ✗ An error message indicating partial installation\n'
+ ' ! Maybe a hint will help the user\n'
+ '\n'
+ '! Doctor found issues in 4 categories.\n'
+ ));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate verbose output format', () async {
+ expect(await FakeDoctor().diagnose(verbose: true), isFalse);
+ expect(testLogger.statusText, equals(
+ '[✓] Passing Validator (with statusInfo)\n'
+ ' • A helpful message\n'
+ ' • A second, somewhat longer helpful message\n'
+ '\n'
+ '[✗] Missing Validator\n'
+ ' ✗ A useful error message\n'
+ ' • A message that is not an error\n'
+ ' ! A hint message\n'
+ '\n'
+ '[!] Not Available Validator\n'
+ ' ✗ A useful error message\n'
+ ' • A message that is not an error\n'
+ ' ! A hint message\n'
+ '\n'
+ '[!] Partial Validator with only a Hint\n'
+ ' ! There is a hint here\n'
+ ' • But there is no error\n'
+ '\n'
+ '[!] Partial Validator with Errors\n'
+ ' ✗ An error message indicating partial installation\n'
+ ' ! Maybe a hint will help the user\n'
+ ' • An extra message with some verbose details\n'
+ '\n'
+ '! Doctor found issues in 4 categories.\n'
+ ));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('gen_snapshot does not work', () async {
+ when(mockProcessManager.runSync(
+ <String>[artifacts.getArtifactPath(Artifact.genSnapshot)],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenReturn(ProcessResult(101, 1, '', ''));
+
+ expect(await FlutterValidatorDoctor().diagnose(verbose: false), isTrue);
+ final List<String> statusLines = testLogger.statusText.split('\n');
+ for (String msg in userMessages.flutterBinariesDoNotRun.split('\n')) {
+ expect(statusLines, contains(contains(msg)));
+ }
+ if (platform.isLinux) {
+ for (String msg in userMessages.flutterBinariesLinuxRepairCommands.split('\n')) {
+ expect(statusLines, contains(contains(msg)));
+ }
+ }
+ }, overrides: <Type, Generator>{
+ OutputPreferences: () => OutputPreferences(wrapText: false),
+ ProcessManager: () => mockProcessManager,
+ Platform: _kNoColorOutputPlatform,
+ });
+ });
+
+ testUsingContext('validate non-verbose output wrapping', () async {
+ expect(await FakeDoctor().diagnose(verbose: false), isFalse);
+ expect(testLogger.statusText, equals(
+ 'Doctor summary (to see all\n'
+ 'details, run flutter doctor\n'
+ '-v):\n'
+ '[✓] Passing Validator (with\n'
+ ' statusInfo)\n'
+ '[✗] Missing Validator\n'
+ ' ✗ A useful error message\n'
+ ' ! A hint message\n'
+ '[!] Not Available Validator\n'
+ ' ✗ A useful error message\n'
+ ' ! A hint message\n'
+ '[!] Partial Validator with\n'
+ ' only a Hint\n'
+ ' ! There is a hint here\n'
+ '[!] Partial Validator with\n'
+ ' Errors\n'
+ ' ✗ An error message\n'
+ ' indicating partial\n'
+ ' installation\n'
+ ' ! Maybe a hint will help\n'
+ ' the user\n'
+ '\n'
+ '! Doctor found issues in 4\n'
+ ' categories.\n'
+ ''
+ ));
+ }, overrides: <Type, Generator>{
+ OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 30),
+ Platform: _kNoColorOutputPlatform,
+ });
+
+ testUsingContext('validate verbose output wrapping', () async {
+ expect(await FakeDoctor().diagnose(verbose: true), isFalse);
+ expect(testLogger.statusText, equals(
+ '[✓] Passing Validator (with\n'
+ ' statusInfo)\n'
+ ' • A helpful message\n'
+ ' • A second, somewhat\n'
+ ' longer helpful message\n'
+ '\n'
+ '[✗] Missing Validator\n'
+ ' ✗ A useful error message\n'
+ ' • A message that is not an\n'
+ ' error\n'
+ ' ! A hint message\n'
+ '\n'
+ '[!] Not Available Validator\n'
+ ' ✗ A useful error message\n'
+ ' • A message that is not an\n'
+ ' error\n'
+ ' ! A hint message\n'
+ '\n'
+ '[!] Partial Validator with\n'
+ ' only a Hint\n'
+ ' ! There is a hint here\n'
+ ' • But there is no error\n'
+ '\n'
+ '[!] Partial Validator with\n'
+ ' Errors\n'
+ ' ✗ An error message\n'
+ ' indicating partial\n'
+ ' installation\n'
+ ' ! Maybe a hint will help\n'
+ ' the user\n'
+ ' • An extra message with\n'
+ ' some verbose details\n'
+ '\n'
+ '! Doctor found issues in 4\n'
+ ' categories.\n'
+ ''
+ ));
+ }, overrides: <Type, Generator>{
+ OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 30),
+ Platform: _kNoColorOutputPlatform,
+ });
+
+
+ group('doctor with grouped validators', () {
+ testUsingContext('validate diagnose combines validator output', () async {
+ expect(await FakeGroupedDoctor().diagnose(), isTrue);
+ expect(testLogger.statusText, equals(
+ '[✓] Category 1\n'
+ ' • A helpful message\n'
+ ' • A helpful message\n'
+ '\n'
+ '[!] Category 2\n'
+ ' • A helpful message\n'
+ ' ✗ A useful error message\n'
+ '\n'
+ '! Doctor found issues in 1 category.\n'
+ ));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate merging assigns statusInfo and title', () async {
+ // There are two subvalidators. Only the second contains statusInfo.
+ expect(await FakeGroupedDoctorWithStatus().diagnose(), isTrue);
+ expect(testLogger.statusText, equals(
+ '[✓] First validator title (A status message)\n'
+ ' • A helpful message\n'
+ ' • A different message\n'
+ '\n'
+ '• No issues found!\n'
+ ));
+ }, overrides: noColorTerminalOverride);
+ });
+
+
+ group('grouped validator merging results', () {
+ final PassingGroupedValidator installed = PassingGroupedValidator('Category');
+ final PartialGroupedValidator partial = PartialGroupedValidator('Category');
+ final MissingGroupedValidator missing = MissingGroupedValidator('Category');
+
+ testUsingContext('validate installed + installed = installed', () async {
+ expect(await FakeSmallGroupDoctor(installed, installed).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[✓]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate installed + partial = partial', () async {
+ expect(await FakeSmallGroupDoctor(installed, partial).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[!]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate installed + missing = partial', () async {
+ expect(await FakeSmallGroupDoctor(installed, missing).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[!]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate partial + installed = partial', () async {
+ expect(await FakeSmallGroupDoctor(partial, installed).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[!]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate partial + partial = partial', () async {
+ expect(await FakeSmallGroupDoctor(partial, partial).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[!]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate partial + missing = partial', () async {
+ expect(await FakeSmallGroupDoctor(partial, missing).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[!]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate missing + installed = partial', () async {
+ expect(await FakeSmallGroupDoctor(missing, installed).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[!]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate missing + partial = partial', () async {
+ expect(await FakeSmallGroupDoctor(missing, partial).diagnose(), isTrue);
+ expect(testLogger.statusText, startsWith('[!]'));
+ }, overrides: noColorTerminalOverride);
+
+ testUsingContext('validate missing + missing = missing', () async {
+ expect(await FakeSmallGroupDoctor(missing, missing).diagnose(), isFalse);
+ expect(testLogger.statusText, startsWith('[✗]'));
+ }, overrides: noColorTerminalOverride);
+ });
+}
+
+class MockUsage extends Mock implements Usage {}
+
+class IntelliJValidatorTestTarget extends IntelliJValidator {
+ IntelliJValidatorTestTarget(String title, String installPath) : super(title, installPath);
+
+ @override
+ String get pluginsPath => fs.path.join('test', 'data', 'intellij', 'plugins');
+
+ @override
+ String get version => 'test.test.test';
+}
+
+class PassingValidator extends DoctorValidator {
+ PassingValidator(String name) : super(name);
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage('A helpful message'));
+ messages.add(ValidationMessage('A second, somewhat longer helpful message'));
+ return ValidationResult(ValidationType.installed, messages, statusInfo: 'with statusInfo');
+ }
+}
+
+class MissingValidator extends DoctorValidator {
+ MissingValidator() : super('Missing Validator');
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage.error('A useful error message'));
+ messages.add(ValidationMessage('A message that is not an error'));
+ messages.add(ValidationMessage.hint('A hint message'));
+ return ValidationResult(ValidationType.missing, messages);
+ }
+}
+
+class NotAvailableValidator extends DoctorValidator {
+ NotAvailableValidator() : super('Not Available Validator');
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage.error('A useful error message'));
+ messages.add(ValidationMessage('A message that is not an error'));
+ messages.add(ValidationMessage.hint('A hint message'));
+ return ValidationResult(ValidationType.notAvailable, messages);
+ }
+}
+
+class PartialValidatorWithErrors extends DoctorValidator {
+ PartialValidatorWithErrors() : super('Partial Validator with Errors');
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage.error('An error message indicating partial installation'));
+ messages.add(ValidationMessage.hint('Maybe a hint will help the user'));
+ messages.add(ValidationMessage('An extra message with some verbose details'));
+ return ValidationResult(ValidationType.partial, messages);
+ }
+}
+
+class PartialValidatorWithHintsOnly extends DoctorValidator {
+ PartialValidatorWithHintsOnly() : super('Partial Validator with only a Hint');
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage.hint('There is a hint here'));
+ messages.add(ValidationMessage('But there is no error'));
+ return ValidationResult(ValidationType.partial, messages);
+ }
+}
+
+/// A doctor that fails with a missing [ValidationResult].
+class FakeDoctor extends Doctor {
+ List<DoctorValidator> _validators;
+
+ @override
+ List<DoctorValidator> get validators {
+ if (_validators == null) {
+ _validators = <DoctorValidator>[];
+ _validators.add(PassingValidator('Passing Validator'));
+ _validators.add(MissingValidator());
+ _validators.add(NotAvailableValidator());
+ _validators.add(PartialValidatorWithHintsOnly());
+ _validators.add(PartialValidatorWithErrors());
+ }
+ return _validators;
+ }
+}
+
+/// A doctor that should pass, but still has issues in some categories.
+class FakePassingDoctor extends Doctor {
+ List<DoctorValidator> _validators;
+ @override
+ List<DoctorValidator> get validators {
+ if (_validators == null) {
+ _validators = <DoctorValidator>[];
+ _validators.add(PassingValidator('Passing Validator'));
+ _validators.add(PartialValidatorWithHintsOnly());
+ _validators.add(PartialValidatorWithErrors());
+ _validators.add(PassingValidator('Another Passing Validator'));
+ }
+ return _validators;
+ }
+}
+
+/// A doctor that should pass, but still has 1 issue to test the singular of
+/// categories.
+class FakeSinglePassingDoctor extends Doctor {
+ List<DoctorValidator> _validators;
+ @override
+ List<DoctorValidator> get validators {
+ if (_validators == null) {
+ _validators = <DoctorValidator>[];
+ _validators.add(PartialValidatorWithHintsOnly());
+ }
+ return _validators;
+ }
+}
+
+/// A doctor that passes and has no issues anywhere.
+class FakeQuietDoctor extends Doctor {
+ List<DoctorValidator> _validators;
+ @override
+ List<DoctorValidator> get validators {
+ if (_validators == null) {
+ _validators = <DoctorValidator>[];
+ _validators.add(PassingValidator('Passing Validator'));
+ _validators.add(PassingValidator('Another Passing Validator'));
+ _validators.add(PassingValidator('Validators are fun'));
+ _validators.add(PassingValidator('Four score and seven validators ago'));
+ }
+ return _validators;
+ }
+}
+
+/// A DoctorValidatorsProvider that overrides the default validators without
+/// overriding the doctor.
+class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
+ @override
+ List<DoctorValidator> get validators {
+ return <DoctorValidator>[
+ PassingValidator('Passing Validator'),
+ PassingValidator('Another Passing Validator'),
+ PassingValidator('Providing validators is fun'),
+ ];
+ }
+
+ @override
+ List<Workflow> get workflows => <Workflow>[];
+}
+
+class PassingGroupedValidator extends DoctorValidator {
+ PassingGroupedValidator(String name) : super(name);
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage('A helpful message'));
+ return ValidationResult(ValidationType.installed, messages);
+ }
+}
+
+class MissingGroupedValidator extends DoctorValidator {
+ MissingGroupedValidator(String name) : super(name);
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage.error('A useful error message'));
+ return ValidationResult(ValidationType.missing, messages);
+ }
+}
+
+class PartialGroupedValidator extends DoctorValidator {
+ PartialGroupedValidator(String name) : super(name);
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage.error('An error message for partial installation'));
+ return ValidationResult(ValidationType.partial, messages);
+ }
+}
+
+class PassingGroupedValidatorWithStatus extends DoctorValidator {
+ PassingGroupedValidatorWithStatus(String name) : super(name);
+
+ @override
+ Future<ValidationResult> validate() async {
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ messages.add(ValidationMessage('A different message'));
+ return ValidationResult(ValidationType.installed, messages, statusInfo: 'A status message');
+ }
+}
+
+/// A doctor that has two groups of two validators each.
+class FakeGroupedDoctor extends Doctor {
+ List<DoctorValidator> _validators;
+ @override
+ List<DoctorValidator> get validators {
+ if (_validators == null) {
+ _validators = <DoctorValidator>[];
+ _validators.add(GroupedValidator(<DoctorValidator>[
+ PassingGroupedValidator('Category 1'),
+ PassingGroupedValidator('Category 1'),
+ ]));
+ _validators.add(GroupedValidator(<DoctorValidator>[
+ PassingGroupedValidator('Category 2'),
+ MissingGroupedValidator('Category 2'),
+ ]));
+ }
+ return _validators;
+ }
+}
+
+class FakeGroupedDoctorWithStatus extends Doctor {
+ List<DoctorValidator> _validators;
+ @override
+ List<DoctorValidator> get validators {
+ _validators ??= <DoctorValidator>[
+ GroupedValidator(<DoctorValidator>[
+ PassingGroupedValidator('First validator title'),
+ PassingGroupedValidatorWithStatus('Second validator title'),
+ ])];
+ return _validators;
+ }
+}
+
+class FlutterValidatorDoctor extends Doctor {
+ List<DoctorValidator> _validators;
+ @override
+ List<DoctorValidator> get validators {
+ _validators ??= <DoctorValidator>[FlutterValidator()];
+ return _validators;
+ }
+}
+
+/// A doctor that takes any two validators. Used to check behavior when
+/// merging ValidationTypes (installed, missing, partial).
+class FakeSmallGroupDoctor extends Doctor {
+ FakeSmallGroupDoctor(DoctorValidator val1, DoctorValidator val2) {
+ _validators = <DoctorValidator>[GroupedValidator(<DoctorValidator>[val1, val2])];
+ }
+
+ List<DoctorValidator> _validators;
+
+ @override
+ List<DoctorValidator> get validators => _validators;
+}
+
+class VsCodeValidatorTestTargets extends VsCodeValidator {
+ VsCodeValidatorTestTargets._(String installDirectory, String extensionDirectory, {String edition})
+ : super(VsCode.fromDirectory(installDirectory, extensionDirectory, edition: edition));
+
+ static VsCodeValidatorTestTargets get installedWithExtension =>
+ VsCodeValidatorTestTargets._(validInstall, validExtensions);
+
+ static VsCodeValidatorTestTargets get installedWithExtension64bit =>
+ VsCodeValidatorTestTargets._(validInstall, validExtensions, edition: '64-bit edition');
+
+ static VsCodeValidatorTestTargets get installedWithoutExtension =>
+ VsCodeValidatorTestTargets._(validInstall, missingExtensions);
+
+ static final String validInstall = fs.path.join('test', 'data', 'vscode', 'application');
+ static final String validExtensions = fs.path.join('test', 'data', 'vscode', 'extensions');
+ static final String missingExtensions = fs.path.join('test', 'data', 'vscode', 'notExtensions');
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
diff --git a/packages/flutter_tools/test/general.shard/commands/drive_test.dart b/packages/flutter_tools/test/general.shard/commands/drive_test.dart
new file mode 100644
index 0000000..f6e39ed
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/drive_test.dart
@@ -0,0 +1,429 @@
+// Copyright 2016 The Chromium 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/memory.dart';
+import 'package:flutter_tools/src/android/android_device.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/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/drive.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ group('drive', () {
+ DriveCommand command;
+ Device mockDevice;
+ MemoryFileSystem fs;
+ Directory tempDir;
+
+ void withMockDevice([ Device mock ]) {
+ mockDevice = mock ?? MockDevice();
+ targetDeviceFinder = () async => mockDevice;
+ testDeviceManager.addDevice(mockDevice);
+ }
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ command = DriveCommand();
+ applyMocksToCommand(command);
+ fs = MemoryFileSystem();
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_drive_test.');
+ fs.currentDirectory = tempDir;
+ fs.directory('test').createSync();
+ fs.directory('test_driver').createSync();
+ fs.file('pubspec.yaml')..createSync();
+ fs.file('.packages').createSync();
+ setExitFunctionForTests();
+ targetDeviceFinder = () {
+ throw 'Unexpected call to targetDeviceFinder';
+ };
+ appStarter = (DriveCommand command) {
+ throw 'Unexpected call to appStarter';
+ };
+ testRunner = (List<String> testArgs, String observatoryUri) {
+ throw 'Unexpected call to testRunner';
+ };
+ appStopper = (DriveCommand command) {
+ throw 'Unexpected call to appStopper';
+ };
+ });
+
+ tearDown(() {
+ command = null;
+ restoreExitFunction();
+ restoreAppStarter();
+ restoreAppStopper();
+ restoreTestRunner();
+ restoreTargetDeviceFinder();
+ tryToDelete(tempDir);
+ });
+
+ testUsingContext('returns 1 when test file is not found', () async {
+ withMockDevice();
+
+ final String testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+ final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+ fs.file(testApp).createSync(recursive: true);
+
+ final List<String> args = <String>[
+ 'drive',
+ '--target=$testApp',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode ?? 1, 1);
+ expect(e.message, contains('Test file not found: $testFile'));
+ }
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('returns 1 when app fails to run', () async {
+ withMockDevice();
+ appStarter = expectAsync1((DriveCommand command) async => null);
+
+ final String testApp = fs.path.join(tempDir.path, 'test_driver', 'e2e.dart');
+ final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+ final MemoryFileSystem memFs = fs;
+ await memFs.file(testApp).writeAsString('main() { }');
+ await memFs.file(testFile).writeAsString('main() { }');
+
+ final List<String> args = <String>[
+ 'drive',
+ '--target=$testApp',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode, 1);
+ expect(e.message, contains('Application failed to start. Will not run test. Quitting.'));
+ }
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('returns 1 when app file is outside package', () async {
+ final String appFile = fs.path.join(tempDir.dirname, 'other_app', 'app.dart');
+ fs.file(appFile).createSync(recursive: true);
+ final List<String> args = <String>[
+ '--no-wrap',
+ 'drive',
+ '--target=$appFile',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode ?? 1, 1);
+ expect(testLogger.errorText, contains(
+ 'Application file $appFile is outside the package directory ${tempDir.path}',
+ ));
+ }
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('returns 1 when app file is in the root dir', () async {
+ final String appFile = fs.path.join(tempDir.path, 'main.dart');
+ fs.file(appFile).createSync(recursive: true);
+ final List<String> args = <String>[
+ '--no-wrap',
+ 'drive',
+ '--target=$appFile',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode ?? 1, 1);
+ expect(testLogger.errorText, contains(
+ 'Application file main.dart must reside in one of the '
+ 'sub-directories of the package structure, not in the root directory.',
+ ));
+ }
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('returns 0 when test ends successfully', () async {
+ withMockDevice();
+
+ final String testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+ final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+ appStarter = expectAsync1((DriveCommand command) async {
+ return LaunchResult.succeeded();
+ });
+ testRunner = expectAsync2((List<String> testArgs, String observatoryUri) async {
+ expect(testArgs, <String>[testFile]);
+ return null;
+ });
+ appStopper = expectAsync1((DriveCommand command) async {
+ return true;
+ });
+
+ final MemoryFileSystem memFs = fs;
+ await memFs.file(testApp).writeAsString('main() {}');
+ await memFs.file(testFile).writeAsString('main() {}');
+
+ final List<String> args = <String>[
+ 'drive',
+ '--target=$testApp',
+ ];
+ await createTestCommandRunner(command).run(args);
+ expect(testLogger.errorText, isEmpty);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('returns exitCode set by test runner', () async {
+ withMockDevice();
+
+ final String testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+ final String testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+ appStarter = expectAsync1((DriveCommand command) async {
+ return LaunchResult.succeeded();
+ });
+ testRunner = (List<String> testArgs, String observatoryUri) async {
+ throwToolExit(null, exitCode: 123);
+ };
+ appStopper = expectAsync1((DriveCommand command) async {
+ return true;
+ });
+
+ final MemoryFileSystem memFs = fs;
+ await memFs.file(testApp).writeAsString('main() {}');
+ await memFs.file(testFile).writeAsString('main() {}');
+
+ final List<String> args = <String>[
+ 'drive',
+ '--target=$testApp',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode ?? 1, 123);
+ expect(e.message, isNull);
+ }
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ group('findTargetDevice', () {
+ testUsingContext('uses specified device', () async {
+ testDeviceManager.specifiedDeviceId = '123';
+ withMockDevice();
+ when(mockDevice.name).thenReturn('specified-device');
+ when(mockDevice.id).thenReturn('123');
+
+ final Device device = await findTargetDevice();
+ expect(device.name, 'specified-device');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+ });
+
+ void findTargetDeviceOnOperatingSystem(String operatingSystem) {
+ Platform platform() => FakePlatform(operatingSystem: operatingSystem);
+
+ testUsingContext('returns null if no devices found', () async {
+ expect(await findTargetDevice(), isNull);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ Platform: platform,
+ });
+
+ testUsingContext('uses existing Android device', () async {
+ mockDevice = MockAndroidDevice();
+ when(mockDevice.name).thenReturn('mock-android-device');
+ withMockDevice(mockDevice);
+
+ final Device device = await findTargetDevice();
+ expect(device.name, 'mock-android-device');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ Platform: platform,
+ });
+ }
+
+ group('findTargetDevice on Linux', () {
+ findTargetDeviceOnOperatingSystem('linux');
+ });
+
+ group('findTargetDevice on Windows', () {
+ findTargetDeviceOnOperatingSystem('windows');
+ });
+
+ group('findTargetDevice on macOS', () {
+ findTargetDeviceOnOperatingSystem('macos');
+
+ Platform macOsPlatform() => FakePlatform(operatingSystem: 'macos');
+
+ testUsingContext('uses existing simulator', () async {
+ withMockDevice();
+ when(mockDevice.name).thenReturn('mock-simulator');
+ when(mockDevice.isLocalEmulator)
+ .thenAnswer((Invocation invocation) => Future<bool>.value(true));
+
+ final Device device = await findTargetDevice();
+ expect(device.name, 'mock-simulator');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ Platform: macOsPlatform,
+ });
+ });
+
+ group('build arguments', () {
+ String testApp, testFile;
+
+ setUp(() {
+ restoreAppStarter();
+ });
+
+ Future<void> appStarterSetup() async {
+ withMockDevice();
+
+ final MockDeviceLogReader mockDeviceLogReader = MockDeviceLogReader();
+ when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader);
+ final MockLaunchResult mockLaunchResult = MockLaunchResult();
+ when(mockLaunchResult.started).thenReturn(true);
+ when(mockDevice.startApp(
+ null,
+ mainPath: anyNamed('mainPath'),
+ route: anyNamed('route'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ platformArgs: anyNamed('platformArgs'),
+ prebuiltApplication: anyNamed('prebuiltApplication'),
+ usesTerminalUi: false,
+ )).thenAnswer((_) => Future<LaunchResult>.value(mockLaunchResult));
+ when(mockDevice.isAppInstalled(any)).thenAnswer((_) => Future<bool>.value(false));
+
+ testApp = fs.path.join(tempDir.path, 'test', 'e2e.dart');
+ testFile = fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
+
+ testRunner = (List<String> testArgs, String observatoryUri) async {
+ throwToolExit(null, exitCode: 123);
+ };
+ appStopper = expectAsync1(
+ (DriveCommand command) async {
+ return true;
+ },
+ count: 2,
+ );
+
+ final MemoryFileSystem memFs = fs;
+ await memFs.file(testApp).writeAsString('main() {}');
+ await memFs.file(testFile).writeAsString('main() {}');
+ }
+
+ testUsingContext('does not use pre-built app if no build arg provided', () async {
+ await appStarterSetup();
+
+ final List<String> args = <String>[
+ 'drive',
+ '--target=$testApp',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ } on ToolExit catch (e) {
+ expect(e.exitCode, 123);
+ expect(e.message, null);
+ }
+ verify(mockDevice.startApp(
+ null,
+ mainPath: anyNamed('mainPath'),
+ route: anyNamed('route'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ platformArgs: anyNamed('platformArgs'),
+ prebuiltApplication: false,
+ usesTerminalUi: false,
+ ));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('does not use pre-built app if --build arg provided', () async {
+ await appStarterSetup();
+
+ final List<String> args = <String>[
+ 'drive',
+ '--build',
+ '--target=$testApp',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ } on ToolExit catch (e) {
+ expect(e.exitCode, 123);
+ expect(e.message, null);
+ }
+ verify(mockDevice.startApp(
+ null,
+ mainPath: anyNamed('mainPath'),
+ route: anyNamed('route'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ platformArgs: anyNamed('platformArgs'),
+ prebuiltApplication: false,
+ usesTerminalUi: false,
+ ));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('uses prebuilt app if --no-build arg provided', () async {
+ await appStarterSetup();
+
+ final List<String> args = <String>[
+ 'drive',
+ '--no-build',
+ '--target=$testApp',
+ ];
+ try {
+ await createTestCommandRunner(command).run(args);
+ } on ToolExit catch (e) {
+ expect(e.exitCode, 123);
+ expect(e.message, null);
+ }
+ verify(mockDevice.startApp(
+ null,
+ mainPath: anyNamed('mainPath'),
+ route: anyNamed('route'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ platformArgs: anyNamed('platformArgs'),
+ prebuiltApplication: true,
+ usesTerminalUi: false,
+ ));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+ });
+ });
+}
+
+class MockDevice extends Mock implements Device {
+ MockDevice() {
+ when(isSupported()).thenReturn(true);
+ }
+}
+
+class MockAndroidDevice extends Mock implements AndroidDevice { }
+
+class MockLaunchResult extends Mock implements LaunchResult { }
diff --git a/packages/flutter_tools/test/general.shard/commands/format_test.dart b/packages/flutter_tools/test/general.shard/commands/format_test.dart
new file mode 100644
index 0000000..1817bbe
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/format_test.dart
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium 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 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/format.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ group('format', () {
+ Directory tempDir;
+
+ setUp(() {
+ Cache.disableLocking();
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_format_test.');
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ });
+
+ testUsingContext('a file', () async {
+ final String projectPath = await createProject(tempDir);
+
+ final File srcFile = fs.file(fs.path.join(projectPath, 'lib', 'main.dart'));
+ final String original = srcFile.readAsStringSync();
+ srcFile.writeAsStringSync(original.replaceFirst('main()', 'main( )'));
+
+ final FormatCommand command = FormatCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ await runner.run(<String>['format', srcFile.path]);
+
+ final String formatted = srcFile.readAsStringSync();
+ expect(formatted, original);
+ });
+
+ testUsingContext('dry-run', () async {
+ final String projectPath = await createProject(tempDir);
+
+ final File srcFile = fs.file(
+ fs.path.join(projectPath, 'lib', 'main.dart'));
+ final String nonFormatted = srcFile.readAsStringSync().replaceFirst(
+ 'main()', 'main( )');
+ srcFile.writeAsStringSync(nonFormatted);
+
+ final FormatCommand command = FormatCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ await runner.run(<String>['format', '--dry-run', srcFile.path]);
+
+ final String shouldNotFormatted = srcFile.readAsStringSync();
+ expect(shouldNotFormatted, nonFormatted);
+ });
+
+ testUsingContext('dry-run with set-exit-if-changed', () async {
+ final String projectPath = await createProject(tempDir);
+
+ final File srcFile = fs.file(
+ fs.path.join(projectPath, 'lib', 'main.dart'));
+ final String nonFormatted = srcFile.readAsStringSync().replaceFirst(
+ 'main()', 'main( )');
+ srcFile.writeAsStringSync(nonFormatted);
+
+ final FormatCommand command = FormatCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ expect(runner.run(<String>[
+ 'format', '--dry-run', '--set-exit-if-changed', srcFile.path,
+ ]), throwsException);
+
+ final String shouldNotFormatted = srcFile.readAsStringSync();
+ expect(shouldNotFormatted, nonFormatted);
+ });
+ });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/ide_config_test.dart b/packages/flutter_tools/test/general.shard/commands/ide_config_test.dart
new file mode 100644
index 0000000..dd6defa
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/ide_config_test.dart
@@ -0,0 +1,324 @@
+// Copyright 2017 The Chromium 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:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/template.dart';
+import 'package:flutter_tools/src/commands/ide_config.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ group('ide_config', () {
+ Directory tempDir;
+ Directory templateDir;
+ Directory intellijDir;
+ Directory toolsDir;
+
+ Map<String, String> _getFilesystemContents([ Directory root ]) {
+ final String tempPath = tempDir.absolute.path;
+ final List<String> paths =
+ (root ?? tempDir).listSync(recursive: true).map((FileSystemEntity entity) {
+ final String relativePath = fs.path.relative(entity.path, from: tempPath);
+ return relativePath;
+ }).toList();
+ final Map<String, String> contents = <String, String>{};
+ for (String path in paths) {
+ final String absPath = fs.path.join(tempPath, path);
+ if (fs.isDirectorySync(absPath)) {
+ contents[path] = 'dir';
+ } else if (fs.isFileSync(absPath)) {
+ contents[path] = fs.file(absPath).readAsStringSync();
+ }
+ }
+ return contents;
+ }
+
+ Map<String, String> _getManifest(Directory base, String marker, { bool isTemplate = false }) {
+ final String basePath = fs.path.relative(base.path, from: tempDir.absolute.path);
+ final String suffix = isTemplate ? Template.copyTemplateExtension : '';
+ return <String, String>{
+ fs.path.join(basePath, '.idea'): 'dir',
+ fs.path.join(basePath, '.idea', 'modules.xml$suffix'): 'modules $marker',
+ fs.path.join(basePath, '.idea', 'vcs.xml$suffix'): 'vcs $marker',
+ fs.path.join(basePath, '.idea', '.name$suffix'):
+ 'codeStyleSettings $marker',
+ fs.path.join(basePath, '.idea', 'runConfigurations'): 'dir',
+ fs.path.join(basePath, '.idea', 'runConfigurations', 'hello_world.xml$suffix'):
+ 'hello_world $marker',
+ fs.path.join(basePath, 'flutter.iml$suffix'): 'flutter $marker',
+ fs.path.join(basePath, 'packages', 'new', 'deep.iml$suffix'): 'deep $marker',
+ };
+ }
+
+ void _populateDir(Map<String, String> manifest) {
+ for (String key in manifest.keys) {
+ if (manifest[key] == 'dir') {
+ tempDir.childDirectory(key)..createSync(recursive: true);
+ }
+ }
+ for (String key in manifest.keys) {
+ if (manifest[key] != 'dir') {
+ tempDir.childFile(key)
+ ..createSync(recursive: true)
+ ..writeAsStringSync(manifest[key]);
+ }
+ }
+ }
+
+ bool _fileOrDirectoryExists(String path) {
+ final String absPath = fs.path.join(tempDir.absolute.path, path);
+ return fs.file(absPath).existsSync() || fs.directory(absPath).existsSync();
+ }
+
+ Future<void> _updateIdeConfig({
+ Directory dir,
+ List<String> args = const <String>[],
+ Map<String, String> expectedContents = const <String, String>{},
+ List<String> unexpectedPaths = const <String>[],
+ }) async {
+ dir ??= tempDir;
+ final IdeConfigCommand command = IdeConfigCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ await runner.run(<String>[
+ '--flutter-root=${tempDir.absolute.path}',
+ 'ide-config',
+ ...args,
+ ]);
+
+ for (String path in expectedContents.keys) {
+ final String absPath = fs.path.join(tempDir.absolute.path, path);
+ expect(_fileOrDirectoryExists(fs.path.join(dir.path, path)), true,
+ reason: "$path doesn't exist");
+ if (fs.file(absPath).existsSync()) {
+ expect(fs.file(absPath).readAsStringSync(), equals(expectedContents[path]),
+ reason: "$path contents don't match");
+ }
+ }
+ for (String path in unexpectedPaths) {
+ expect(_fileOrDirectoryExists(fs.path.join(dir.path, path)), false, reason: '$path exists');
+ }
+ }
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() {
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_ide_config_test.');
+ final Directory packagesDir = tempDir.childDirectory('packages')..createSync(recursive: true);
+ toolsDir = packagesDir.childDirectory('flutter_tools')..createSync();
+ templateDir = toolsDir.childDirectory('ide_templates')..createSync();
+ intellijDir = templateDir.childDirectory('intellij')..createSync();
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ });
+
+ testUsingContext("doesn't touch existing files without --overwrite", () async {
+ final Map<String, String> templateManifest = _getManifest(
+ intellijDir,
+ 'template',
+ isTemplate: true,
+ );
+ final Map<String, String> flutterManifest = _getManifest(
+ tempDir,
+ 'existing',
+ );
+ _populateDir(templateManifest);
+ _populateDir(flutterManifest);
+ final Map<String, String> expectedContents = _getFilesystemContents();
+ return _updateIdeConfig(
+ expectedContents: expectedContents,
+ );
+ }, timeout: const Timeout.factor(2.0));
+
+ testUsingContext('creates non-existent files', () async {
+ final Map<String, String> templateManifest = _getManifest(
+ intellijDir,
+ 'template',
+ isTemplate: true,
+ );
+ final Map<String, String> flutterManifest = _getManifest(
+ tempDir,
+ 'template',
+ );
+ _populateDir(templateManifest);
+ final Map<String, String> expectedContents = <String, String>{
+ ...templateManifest,
+ ...flutterManifest,
+ };
+ return _updateIdeConfig(
+ expectedContents: expectedContents,
+ );
+ }, timeout: const Timeout.factor(2.0));
+
+ testUsingContext('overwrites existing files with --overwrite', () async {
+ final Map<String, String> templateManifest = _getManifest(
+ intellijDir,
+ 'template',
+ isTemplate: true,
+ );
+ final Map<String, String> flutterManifest = _getManifest(
+ tempDir,
+ 'existing',
+ );
+ _populateDir(templateManifest);
+ _populateDir(flutterManifest);
+ final Map<String, String> overwrittenManifest = _getManifest(
+ tempDir,
+ 'template',
+ );
+ final Map<String, String> expectedContents = <String, String>{
+ ...templateManifest,
+ ...overwrittenManifest,
+ };
+ return _updateIdeConfig(
+ args: <String>['--overwrite'],
+ expectedContents: expectedContents,
+ );
+ }, timeout: const Timeout.factor(2.0));
+
+ testUsingContext('only adds new templates without --overwrite', () async {
+ final Map<String, String> templateManifest = _getManifest(
+ intellijDir,
+ 'template',
+ isTemplate: true,
+ );
+ final String flutterIml = fs.path.join(
+ 'packages',
+ 'flutter_tools',
+ 'ide_templates',
+ 'intellij',
+ 'flutter.iml${Template.copyTemplateExtension}',
+ );
+ templateManifest.remove(flutterIml);
+ _populateDir(templateManifest);
+ templateManifest[flutterIml] = 'flutter existing';
+ final Map<String, String> flutterManifest = _getManifest(
+ tempDir,
+ 'existing',
+ );
+ _populateDir(flutterManifest);
+ final Map<String, String> expectedContents = <String, String>{
+ ...flutterManifest,
+ ...templateManifest,
+ };
+ return _updateIdeConfig(
+ args: <String>['--update-templates'],
+ expectedContents: expectedContents,
+ );
+ }, timeout: const Timeout.factor(2.0));
+
+ testUsingContext('update all templates with --overwrite', () async {
+ final Map<String, String> templateManifest = _getManifest(
+ intellijDir,
+ 'template',
+ isTemplate: true,
+ );
+ _populateDir(templateManifest);
+ final Map<String, String> flutterManifest = _getManifest(
+ tempDir,
+ 'existing',
+ );
+ _populateDir(flutterManifest);
+ final Map<String, String> updatedTemplates = _getManifest(
+ intellijDir,
+ 'existing',
+ isTemplate: true,
+ );
+ final Map<String, String> expectedContents = <String, String>{
+ ...flutterManifest,
+ ...updatedTemplates,
+ };
+ return _updateIdeConfig(
+ args: <String>['--update-templates', '--overwrite'],
+ expectedContents: expectedContents,
+ );
+ }, timeout: const Timeout.factor(2.0));
+
+ testUsingContext('removes deleted imls with --overwrite', () async {
+ final Map<String, String> templateManifest = _getManifest(
+ intellijDir,
+ 'template',
+ isTemplate: true,
+ );
+ _populateDir(templateManifest);
+ final Map<String, String> flutterManifest = _getManifest(
+ tempDir,
+ 'existing',
+ );
+ flutterManifest.remove('flutter.iml');
+ _populateDir(flutterManifest);
+ final Map<String, String> updatedTemplates = _getManifest(
+ intellijDir,
+ 'existing',
+ isTemplate: true,
+ );
+ final String flutterIml = fs.path.join(
+ 'packages',
+ 'flutter_tools',
+ 'ide_templates',
+ 'intellij',
+ 'flutter.iml${Template.copyTemplateExtension}',
+ );
+ updatedTemplates.remove(flutterIml);
+ final Map<String, String> expectedContents = <String, String>{
+ ...flutterManifest,
+ ...updatedTemplates,
+ };
+ return _updateIdeConfig(
+ args: <String>['--update-templates', '--overwrite'],
+ expectedContents: expectedContents,
+ );
+ }, timeout: const Timeout.factor(2.0));
+
+ testUsingContext('removes deleted imls with --overwrite, including empty parent dirs', () async {
+ final Map<String, String> templateManifest = _getManifest(
+ intellijDir,
+ 'template',
+ isTemplate: true,
+ );
+ _populateDir(templateManifest);
+ final Map<String, String> flutterManifest = _getManifest(
+ tempDir,
+ 'existing',
+ );
+ flutterManifest.remove(fs.path.join('packages', 'new', 'deep.iml'));
+ _populateDir(flutterManifest);
+ final Map<String, String> updatedTemplates = _getManifest(
+ intellijDir,
+ 'existing',
+ isTemplate: true,
+ );
+ String deepIml = fs.path.join(
+ 'packages',
+ 'flutter_tools',
+ 'ide_templates',
+ 'intellij');
+ // Remove the all the dir entries too.
+ updatedTemplates.remove(deepIml);
+ deepIml = fs.path.join(deepIml, 'packages');
+ updatedTemplates.remove(deepIml);
+ deepIml = fs.path.join(deepIml, 'new');
+ updatedTemplates.remove(deepIml);
+ deepIml = fs.path.join(deepIml, 'deep.iml');
+ updatedTemplates.remove(deepIml);
+ final Map<String, String> expectedContents = <String, String>{
+ ...flutterManifest,
+ ...updatedTemplates,
+ };
+ return _updateIdeConfig(
+ args: <String>['--update-templates', '--overwrite'],
+ expectedContents: expectedContents,
+ );
+ }, timeout: const Timeout.factor(2.0));
+
+ });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/install_test.dart b/packages/flutter_tools/test/general.shard/commands/install_test.dart
new file mode 100644
index 0000000..2883259
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/install_test.dart
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium 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 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/install.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ group('install', () {
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ testUsingContext('returns 0 when Android is connected and ready for an install', () async {
+ final InstallCommand command = InstallCommand();
+ applyMocksToCommand(command);
+
+ final MockAndroidDevice device = MockAndroidDevice();
+ when(device.isAppInstalled(any)).thenAnswer((_) async => false);
+ when(device.installApp(any)).thenAnswer((_) async => true);
+ testDeviceManager.addDevice(device);
+
+ await createTestCommandRunner(command).run(<String>['install']);
+ }, overrides: <Type, Generator>{
+ Cache: () => MockCache(),
+ });
+
+ testUsingContext('returns 0 when iOS is connected and ready for an install', () async {
+ final InstallCommand command = InstallCommand();
+ applyMocksToCommand(command);
+
+ final MockIOSDevice device = MockIOSDevice();
+ when(device.isAppInstalled(any)).thenAnswer((_) async => false);
+ when(device.installApp(any)).thenAnswer((_) async => true);
+ testDeviceManager.addDevice(device);
+
+ await createTestCommandRunner(command).run(<String>['install']);
+ }, overrides: <Type, Generator>{
+ Cache: () => MockCache(),
+ });
+ });
+}
+
+class MockCache extends Mock implements Cache {}
diff --git a/packages/flutter_tools/test/general.shard/commands/packages_test.dart b/packages/flutter_tools/test/general.shard/commands/packages_test.dart
new file mode 100644
index 0000000..8e0269b
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/packages_test.dart
@@ -0,0 +1,448 @@
+// Copyright 2016 The Chromium 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:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart' hide IOSink;
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/utils.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/packages.dart';
+import 'package:flutter_tools/src/usage.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart' show MockProcessManager, MockStdio, PromptingProcess;
+
+class AlwaysTrueBotDetector implements BotDetector {
+ const AlwaysTrueBotDetector();
+
+ @override
+ bool get isRunningOnBot => true;
+}
+
+
+class AlwaysFalseBotDetector implements BotDetector {
+ const AlwaysFalseBotDetector();
+
+ @override
+ bool get isRunningOnBot => false;
+}
+
+
+void main() {
+ Cache.disableLocking();
+ group('packages get/upgrade', () {
+ Directory tempDir;
+
+ setUp(() {
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ });
+
+ Future<String> createProjectWithPlugin(String plugin, { List<String> arguments }) async {
+ final String projectPath = await createProject(tempDir, arguments: arguments);
+ final File pubspec = fs.file(fs.path.join(projectPath, 'pubspec.yaml'));
+ String content = await pubspec.readAsString();
+ content = content.replaceFirst(
+ '\ndependencies:\n',
+ '\ndependencies:\n $plugin:\n',
+ );
+ await pubspec.writeAsString(content, flush: true);
+ return projectPath;
+ }
+
+ Future<PackagesCommand> runCommandIn(String projectPath, String verb, { List<String> args }) async {
+ final PackagesCommand command = PackagesCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+ await runner.run(<String>[
+ 'packages',
+ verb,
+ ...?args,
+ projectPath,
+ ]);
+ return command;
+ }
+
+ void expectExists(String projectPath, String relPath) {
+ expect(
+ fs.isFileSync(fs.path.join(projectPath, relPath)),
+ true,
+ reason: '$projectPath/$relPath should exist, but does not',
+ );
+ }
+
+ void expectContains(String projectPath, String relPath, String substring) {
+ expectExists(projectPath, relPath);
+ expect(
+ fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+ contains(substring),
+ reason: '$projectPath/$relPath has unexpected content',
+ );
+ }
+
+ void expectNotExists(String projectPath, String relPath) {
+ expect(
+ fs.isFileSync(fs.path.join(projectPath, relPath)),
+ false,
+ reason: '$projectPath/$relPath should not exist, but does',
+ );
+ }
+
+ void expectNotContains(String projectPath, String relPath, String substring) {
+ expectExists(projectPath, relPath);
+ expect(
+ fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+ isNot(contains(substring)),
+ reason: '$projectPath/$relPath has unexpected content',
+ );
+ }
+
+ const List<String> pubOutput = <String>[
+ '.packages',
+ 'pubspec.lock',
+ ];
+
+ const List<String> pluginRegistrants = <String>[
+ 'ios/Runner/GeneratedPluginRegistrant.h',
+ 'ios/Runner/GeneratedPluginRegistrant.m',
+ 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ ];
+
+ const List<String> modulePluginRegistrants = <String>[
+ '.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.h',
+ '.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.m',
+ '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ ];
+
+ const List<String> pluginWitnesses = <String>[
+ '.flutter-plugins',
+ 'ios/Podfile',
+ ];
+
+ const List<String> modulePluginWitnesses = <String>[
+ '.flutter-plugins',
+ '.ios/Podfile',
+ ];
+
+ const Map<String, String> pluginContentWitnesses = <String, String>{
+ 'ios/Flutter/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
+ 'ios/Flutter/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
+ };
+
+ const Map<String, String> modulePluginContentWitnesses = <String, String>{
+ '.ios/Config/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
+ '.ios/Config/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
+ };
+
+ void expectDependenciesResolved(String projectPath) {
+ for (String output in pubOutput) {
+ expectExists(projectPath, output);
+ }
+ }
+
+ void expectZeroPluginsInjected(String projectPath) {
+ for (final String registrant in modulePluginRegistrants) {
+ expectExists(projectPath, registrant);
+ }
+ for (final String witness in pluginWitnesses) {
+ expectNotExists(projectPath, witness);
+ }
+ modulePluginContentWitnesses.forEach((String witness, String content) {
+ expectNotContains(projectPath, witness, content);
+ });
+ }
+
+ void expectPluginInjected(String projectPath) {
+ for (final String registrant in pluginRegistrants) {
+ expectExists(projectPath, registrant);
+ }
+ for (final String witness in pluginWitnesses) {
+ expectExists(projectPath, witness);
+ }
+ pluginContentWitnesses.forEach((String witness, String content) {
+ expectContains(projectPath, witness, content);
+ });
+ }
+
+ void expectModulePluginInjected(String projectPath) {
+ for (final String registrant in modulePluginRegistrants) {
+ expectExists(projectPath, registrant);
+ }
+ for (final String witness in modulePluginWitnesses) {
+ expectExists(projectPath, witness);
+ }
+ modulePluginContentWitnesses.forEach((String witness, String content) {
+ expectContains(projectPath, witness, content);
+ });
+ }
+
+ void removeGeneratedFiles(String projectPath) {
+ final Iterable<String> allFiles = <List<String>>[
+ pubOutput,
+ modulePluginRegistrants,
+ pluginWitnesses,
+ ].expand<String>((List<String> list) => list);
+ for (String path in allFiles) {
+ final File file = fs.file(fs.path.join(projectPath, path));
+ if (file.existsSync())
+ file.deleteSync();
+ }
+ }
+
+ testUsingContext('get fetches packages', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=module']);
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'get');
+
+ expectDependenciesResolved(projectPath);
+ expectZeroPluginsInjected(projectPath);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('get --offline fetches packages', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=module']);
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'get', args: <String>['--offline']);
+
+ expectDependenciesResolved(projectPath);
+ expectZeroPluginsInjected(projectPath);
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('set the number of plugins as usage value', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=module']);
+ removeGeneratedFiles(projectPath);
+
+ final PackagesCommand command = await runCommandIn(projectPath, 'get');
+ final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+ expect(await getCommand.usageValues, containsPair(kCommandPackagesNumberPlugins, '0'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('indicate that the project is not a module in usage value', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub']);
+ removeGeneratedFiles(projectPath);
+
+ final PackagesCommand command = await runCommandIn(projectPath, 'get');
+ final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+ expect(await getCommand.usageValues, containsPair(kCommandPackagesProjectModule, 'false'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('indicate that the project is a module in usage value', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=module']);
+ removeGeneratedFiles(projectPath);
+
+ final PackagesCommand command = await runCommandIn(projectPath, 'get');
+ final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+ expect(await getCommand.usageValues, containsPair(kCommandPackagesProjectModule, 'true'));
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('upgrade fetches packages', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub', '--template=module']);
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'upgrade');
+
+ expectDependenciesResolved(projectPath);
+ expectZeroPluginsInjected(projectPath);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('get fetches packages and injects plugin', () async {
+ final String projectPath = await createProjectWithPlugin('path_provider',
+ arguments: <String>['--no-pub', '--template=module']);
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'get');
+
+ expectDependenciesResolved(projectPath);
+ expectModulePluginInjected(projectPath);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('get fetches packages and injects plugin in plugin project', () async {
+ final String projectPath = await createProject(
+ tempDir,
+ arguments: <String>['--template=plugin', '--no-pub'],
+ );
+ final String exampleProjectPath = fs.path.join(projectPath, 'example');
+ removeGeneratedFiles(projectPath);
+ removeGeneratedFiles(exampleProjectPath);
+
+ await runCommandIn(projectPath, 'get');
+
+ expectDependenciesResolved(projectPath);
+
+ await runCommandIn(exampleProjectPath, 'get');
+
+ expectDependenciesResolved(exampleProjectPath);
+ expectPluginInjected(exampleProjectPath);
+ }, timeout: allowForRemotePubInvocation);
+ });
+
+ group('packages test/pub', () {
+ MockProcessManager mockProcessManager;
+ MockStdio mockStdio;
+
+ setUp(() {
+ mockProcessManager = MockProcessManager();
+ mockStdio = MockStdio();
+ });
+
+ testUsingContext('test without bot', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'test']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(3));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], 'run');
+ expect(commands[2], 'test');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysFalseBotDetector(),
+ });
+
+ testUsingContext('test with bot', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'test']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(4));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], '--trace');
+ expect(commands[2], 'run');
+ expect(commands[3], 'test');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysTrueBotDetector(),
+ });
+
+ testUsingContext('run', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', '--verbose', 'pub', 'run', '--foo', 'bar']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(4));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], 'run');
+ expect(commands[2], '--foo');
+ expect(commands[3], 'bar');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ });
+
+ testUsingContext('pub publish', () async {
+ final PromptingProcess process = PromptingProcess();
+ mockProcessManager.processFactory = (List<String> commands) => process;
+ final Future<void> runPackages = createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'pub', 'publish']);
+ final Future<void> runPrompt = process.showPrompt('Proceed (y/n)? ', <String>['hello', 'world']);
+ final Future<void> simulateUserInput = Future<void>(() {
+ mockStdio.simulateStdin('y');
+ });
+ await Future.wait<void>(<Future<void>>[runPackages, runPrompt, simulateUserInput]);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(2));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], 'publish');
+ final List<String> stdout = mockStdio.writtenToStdout;
+ expect(stdout, hasLength(4));
+ expect(stdout.sublist(0, 2), contains('Proceed (y/n)? '));
+ expect(stdout.sublist(0, 2), contains('y\n'));
+ expect(stdout[2], 'hello\n');
+ expect(stdout[3], 'world\n');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ });
+
+ testUsingContext('publish', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'publish']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(3));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], '--trace');
+ expect(commands[2], 'publish');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysTrueBotDetector(),
+ });
+
+ testUsingContext('deps', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'deps']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(3));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], '--trace');
+ expect(commands[2], 'deps');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysTrueBotDetector(),
+ });
+
+ testUsingContext('cache', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'cache']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(3));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], '--trace');
+ expect(commands[2], 'cache');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysTrueBotDetector(),
+ });
+
+ testUsingContext('version', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'version']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(3));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], '--trace');
+ expect(commands[2], 'version');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysTrueBotDetector(),
+ });
+
+ testUsingContext('uploader', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'uploader']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(3));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], '--trace');
+ expect(commands[2], 'uploader');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysTrueBotDetector(),
+ });
+
+ testUsingContext('global', () async {
+ await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'global', 'list']);
+ final List<String> commands = mockProcessManager.commands;
+ expect(commands, hasLength(4));
+ expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
+ expect(commands[1], '--trace');
+ expect(commands[2], 'global');
+ expect(commands[3], 'list');
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Stdio: () => mockStdio,
+ BotDetector: () => const AlwaysTrueBotDetector(),
+ });
+ });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/precache_test.dart b/packages/flutter_tools/test/general.shard/commands/precache_test.dart
new file mode 100644
index 0000000..0308da6
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/precache_test.dart
@@ -0,0 +1,87 @@
+// Copyright 2019 The Chromium 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 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/precache.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ group('precache', () {
+ final MockCache cache = MockCache();
+ Set<DevelopmentArtifact> artifacts;
+
+ when(cache.isUpToDate()).thenReturn(false);
+ when(cache.updateAll(any)).thenAnswer((Invocation invocation) {
+ artifacts = invocation.positionalArguments.first;
+ return Future<void>.value(null);
+ });
+
+ testUsingContext('Adds artifact flags to requested artifacts', () async {
+ final PrecacheCommand command = PrecacheCommand();
+ applyMocksToCommand(command);
+ await createTestCommandRunner(command).run(
+ const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia']
+ );
+ expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.iOS,
+ DevelopmentArtifact.android,
+ DevelopmentArtifact.web,
+ DevelopmentArtifact.macOS,
+ DevelopmentArtifact.linux,
+ DevelopmentArtifact.windows,
+ DevelopmentArtifact.fuchsia,
+ }));
+ }, overrides: <Type, Generator>{
+ Cache: () => cache,
+ });
+
+ final MockFlutterVersion flutterVersion = MockFlutterVersion();
+ when(flutterVersion.isMaster).thenReturn(false);
+
+ testUsingContext('Adds artifact flags to requested artifacts on stable', () async {
+ // Release lock between test cases.
+ Cache.releaseLockEarly();
+ final PrecacheCommand command = PrecacheCommand();
+ applyMocksToCommand(command);
+ await createTestCommandRunner(command).run(
+ const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia']
+ );
+ expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.iOS,
+ DevelopmentArtifact.android,
+ }));
+ }, overrides: <Type, Generator>{
+ Cache: () => cache,
+ FlutterVersion: () => flutterVersion,
+ });
+
+ testUsingContext('Downloads artifacts when --force is provided', () async {
+ when(cache.isUpToDate()).thenReturn(true);
+ // Release lock between test cases.
+ Cache.releaseLockEarly();
+ final PrecacheCommand command = PrecacheCommand();
+ applyMocksToCommand(command);
+ await createTestCommandRunner(command).run(const <String>['precache', '--force']);
+ expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.iOS,
+ DevelopmentArtifact.android,
+ }));
+ }, overrides: <Type, Generator>{
+ Cache: () => cache,
+ FlutterVersion: () => flutterVersion,
+ });
+ });
+}
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
+class MockCache extends Mock implements Cache {}
diff --git a/packages/flutter_tools/test/general.shard/commands/run_test.dart b/packages/flutter_tools/test/general.shard/commands/run_test.dart
new file mode 100644
index 0000000..b743a36
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/run_test.dart
@@ -0,0 +1,262 @@
+// Copyright 2016 The Chromium 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 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/application_package.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/run.dart';
+import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ group('run', () {
+ MockApplicationPackageFactory mockApplicationPackageFactory;
+ MockDeviceManager mockDeviceManager;
+ MockFlutterVersion mockStableFlutterVersion;
+ MockFlutterVersion mockUnstableFlutterVersion;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ mockApplicationPackageFactory = MockApplicationPackageFactory();
+ mockDeviceManager = MockDeviceManager();
+ mockStableFlutterVersion = MockFlutterVersion(isStable: true);
+ mockUnstableFlutterVersion = MockFlutterVersion(isStable: false);
+ });
+
+ testUsingContext('fails when target not found', () async {
+ final RunCommand command = RunCommand();
+ applyMocksToCommand(command);
+ try {
+ await createTestCommandRunner(command).run(<String>['run', '-t', 'abc123']);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode ?? 1, 1);
+ }
+ });
+
+
+ group('dart-flags option', () {
+ setUpAll(() {
+ when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+ return Stream<Device>.fromIterable(<Device>[
+ FakeDevice(),
+ ]);
+ });
+ });
+
+ RunCommand command;
+ List<String> args;
+ setUp(() {
+ command = TestRunCommand();
+ args = <String> [
+ 'run',
+ '--dart-flags', '"--observe"',
+ '--no-hot',
+ ];
+ });
+
+ testUsingContext('is not available on stable channel', () async {
+ // Stable branch.
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ // ignore: unused_catch_clause
+ } on UsageException catch(e) {
+ // Not available while on stable branch.
+ }
+ }, overrides: <Type, Generator>{
+ DeviceManager: () => mockDeviceManager,
+ FlutterVersion: () => mockStableFlutterVersion,
+ });
+
+ testUsingContext('is populated in debug mode', () async {
+ // FakeDevice.startApp checks that --dart-flags doesn't get dropped and
+ // throws ToolExit with FakeDevice.kSuccess if the flag is populated.
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode, FakeDevice.kSuccess);
+ }
+ }, overrides: <Type, Generator>{
+ ApplicationPackageFactory: () => mockApplicationPackageFactory,
+ DeviceManager: () => mockDeviceManager,
+ FlutterVersion: () => mockUnstableFlutterVersion,
+ });
+
+ testUsingContext('is populated in profile mode', () async {
+ args.add('--profile');
+
+ // FakeDevice.startApp checks that --dart-flags doesn't get dropped and
+ // throws ToolExit with FakeDevice.kSuccess if the flag is populated.
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode, FakeDevice.kSuccess);
+ }
+ }, overrides: <Type, Generator>{
+ ApplicationPackageFactory: () => mockApplicationPackageFactory,
+ DeviceManager: () => mockDeviceManager,
+ FlutterVersion: () => mockUnstableFlutterVersion,
+ });
+
+ testUsingContext('is not populated in release mode', () async {
+ args.add('--release');
+
+ // FakeDevice.startApp checks that --dart-flags *does* get dropped and
+ // throws ToolExit with FakeDevice.kSuccess if the flag is set to the
+ // empty string.
+ try {
+ await createTestCommandRunner(command).run(args);
+ fail('Expect exception');
+ } on ToolExit catch (e) {
+ expect(e.exitCode, FakeDevice.kSuccess);
+ }
+ }, overrides: <Type, Generator>{
+ ApplicationPackageFactory: () => mockApplicationPackageFactory,
+ DeviceManager: () => mockDeviceManager,
+ FlutterVersion: () => mockUnstableFlutterVersion,
+ });
+ });
+
+ testUsingContext('should only request artifacts corresponding to connected devices', () async {
+ when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+ return Stream<Device>.fromIterable(<Device>[
+ MockDevice(TargetPlatform.android_arm),
+ ]);
+ });
+
+ expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.android,
+ }));
+
+ when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+ return Stream<Device>.fromIterable(<Device>[
+ MockDevice(TargetPlatform.ios),
+ ]);
+ });
+
+ expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.iOS,
+ }));
+
+ when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+ return Stream<Device>.fromIterable(<Device>[
+ MockDevice(TargetPlatform.ios),
+ MockDevice(TargetPlatform.android_arm),
+ ]);
+ });
+
+ expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.iOS,
+ DevelopmentArtifact.android,
+ }));
+
+ when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+ return Stream<Device>.fromIterable(<Device>[
+ MockDevice(TargetPlatform.web_javascript),
+ ]);
+ });
+
+ expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.web,
+ }));
+ }, overrides: <Type, Generator>{
+ DeviceManager: () => mockDeviceManager,
+ });
+ });
+}
+
+class MockDeviceManager extends Mock implements DeviceManager {}
+class MockDevice extends Mock implements Device {
+ MockDevice(this._targetPlatform);
+
+ final TargetPlatform _targetPlatform;
+
+ @override
+ Future<TargetPlatform> get targetPlatform async => _targetPlatform;
+}
+
+class TestRunCommand extends RunCommand {
+ @override
+ // ignore: must_call_super
+ Future<void> validateCommand() async {
+ devices = await deviceManager.getDevices().toList();
+ }
+}
+
+class MockStableFlutterVersion extends MockFlutterVersion {
+ @override
+ bool get isMaster => false;
+}
+
+class FakeDevice extends Fake implements Device {
+ static const int kSuccess = 1;
+ static const int kFailure = -1;
+ final TargetPlatform _targetPlatform = TargetPlatform.ios;
+
+ void _throwToolExit(int code) => throwToolExit(null, exitCode: code);
+
+ @override
+ Future<bool> get isLocalEmulator => Future<bool>.value(false);
+
+ @override
+ bool get supportsHotReload => false;
+
+ @override
+ Future<String> get sdkNameAndVersion => Future<String>.value('');
+
+ @override
+ DeviceLogReader getLogReader({ ApplicationPackage app }) {
+ return MockDeviceLogReader();
+ }
+
+ @override
+ String get name => 'FakeDevice';
+
+ @override
+ Future<TargetPlatform> get targetPlatform async => _targetPlatform;
+
+ @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,
+ }) async {
+ 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);
+ }
+ return null;
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/shell_completion_test.dart b/packages/flutter_tools/test/general.shard/commands/shell_completion_test.dart
new file mode 100644
index 0000000..00b4aac
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/shell_completion_test.dart
@@ -0,0 +1,91 @@
+// Copyright 2018 The Chromium 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 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/shell_completion.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart';
+
+void main() {
+ group('shell_completion', () {
+ MockStdio mockStdio;
+
+ setUp(() {
+ Cache.disableLocking();
+ mockStdio = MockStdio();
+ });
+
+ testUsingContext('generates bash initialization script to stdout', () async {
+ final ShellCompletionCommand command = ShellCompletionCommand();
+ await createTestCommandRunner(command).run(<String>['bash-completion']);
+ expect(mockStdio.writtenToStdout.length, equals(1));
+ expect(mockStdio.writtenToStdout.first, contains('__flutter_completion'));
+ }, overrides: <Type, Generator>{
+ Stdio: () => mockStdio,
+ });
+
+ testUsingContext('generates bash initialization script to stdout with arg', () async {
+ final ShellCompletionCommand command = ShellCompletionCommand();
+ await createTestCommandRunner(command).run(<String>['bash-completion', '-']);
+ expect(mockStdio.writtenToStdout.length, equals(1));
+ expect(mockStdio.writtenToStdout.first, contains('__flutter_completion'));
+ }, overrides: <Type, Generator>{
+ Stdio: () => mockStdio,
+ });
+
+ testUsingContext('generates bash initialization script to output file', () async {
+ final ShellCompletionCommand command = ShellCompletionCommand();
+ const String outputFile = 'bash-setup.sh';
+ await createTestCommandRunner(command).run(
+ <String>['bash-completion', outputFile],
+ );
+ expect(fs.isFileSync(outputFile), isTrue);
+ expect(fs.file(outputFile).readAsStringSync(), contains('__flutter_completion'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ Stdio: () => mockStdio,
+ });
+
+ testUsingContext("won't overwrite existing output file ", () async {
+ final ShellCompletionCommand command = ShellCompletionCommand();
+ const String outputFile = 'bash-setup.sh';
+ fs.file(outputFile).createSync();
+ try {
+ await createTestCommandRunner(command).run(
+ <String>['bash-completion', outputFile],
+ );
+ fail('Expect ToolExit exception');
+ } on ToolExit catch (error) {
+ expect(error.exitCode ?? 1, 1);
+ expect(error.message, contains('Use --overwrite'));
+ }
+ expect(fs.isFileSync(outputFile), isTrue);
+ expect(fs.file(outputFile).readAsStringSync(), isEmpty);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ Stdio: () => mockStdio,
+ });
+
+ testUsingContext('will overwrite existing output file if given --overwrite', () async {
+ final ShellCompletionCommand command = ShellCompletionCommand();
+ const String outputFile = 'bash-setup.sh';
+ fs.file(outputFile).createSync();
+ await createTestCommandRunner(command).run(
+ <String>['bash-completion', '--overwrite', outputFile],
+ );
+ expect(fs.isFileSync(outputFile), isTrue);
+ expect(fs.file(outputFile).readAsStringSync(), contains('__flutter_completion'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ Stdio: () => mockStdio,
+ });
+ });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/test_test.dart b/packages/flutter_tools/test/general.shard/commands/test_test.dart
new file mode 100644
index 0000000..348ce99
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/test_test.dart
@@ -0,0 +1,221 @@
+// Copyright 2015 The Chromium 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:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/dart/sdk.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+// This test depends on some files in ///dev/automated_tests/flutter_test/*
+
+Future<void> _testExclusionLock;
+
+void main() {
+ group('flutter test should', () {
+
+ final String automatedTestsDirectory = fs.path.join('..', '..', 'dev', 'automated_tests');
+ final String flutterTestDirectory = fs.path.join(automatedTestsDirectory, 'flutter_test');
+
+ testUsingContext('not have extraneous error messages', () async {
+ Cache.flutterRoot = '../..';
+ return _testFile('trivial_widget', automatedTestsDirectory, flutterTestDirectory, exitCode: isZero);
+ }, skip: io.Platform.isLinux); // Flutter on Linux sometimes has problems with font resolution (#7224)
+
+ testUsingContext('report nice errors for exceptions thrown within testWidgets()', () async {
+ Cache.flutterRoot = '../..';
+ return _testFile('exception_handling', automatedTestsDirectory, flutterTestDirectory);
+ }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+ testUsingContext('report a nice error when a guarded function was called without await', () async {
+ Cache.flutterRoot = '../..';
+ return _testFile('test_async_utils_guarded', automatedTestsDirectory, flutterTestDirectory);
+ }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+ testUsingContext('report a nice error when an async function was called without await', () async {
+ Cache.flutterRoot = '../..';
+ return _testFile('test_async_utils_unguarded', automatedTestsDirectory, flutterTestDirectory);
+ }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+ testUsingContext('report a nice error when a Ticker is left running', () async {
+ Cache.flutterRoot = '../..';
+ return _testFile('ticker', automatedTestsDirectory, flutterTestDirectory);
+ }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+ testUsingContext('report a nice error when a pubspec.yaml is missing a flutter_test dependency', () async {
+ final String missingDependencyTests = fs.path.join('..', '..', 'dev', 'missing_dependency_tests');
+ Cache.flutterRoot = '../..';
+ return _testFile('trivial', missingDependencyTests, missingDependencyTests);
+ }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+ testUsingContext('report which user created widget caused the error', () async {
+ Cache.flutterRoot = '../..';
+ return _testFile('print_user_created_ancestor', automatedTestsDirectory, flutterTestDirectory,
+ extraArguments: const <String>['--track-widget-creation']);
+ }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+ testUsingContext('report which user created widget caused the error - no flag', () async {
+ Cache.flutterRoot = '../..';
+ return _testFile('print_user_created_ancestor_no_flag', automatedTestsDirectory, flutterTestDirectory);
+ }, skip: io.Platform.isWindows); // TODO(chunhtai): Dart on Windows has trouble with unicode characters in output (#35425).
+
+ testUsingContext('run a test when its name matches a regexp', () async {
+ Cache.flutterRoot = '../..';
+ final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
+ extraArguments: const <String>['--name', 'inc.*de']);
+ if (!result.stdout.contains('+1: All tests passed'))
+ fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
+ expect(result.exitCode, 0);
+ });
+
+ testUsingContext('run a test when its name contains a string', () async {
+ Cache.flutterRoot = '../..';
+ final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
+ extraArguments: const <String>['--plain-name', 'include']);
+ if (!result.stdout.contains('+1: All tests passed'))
+ fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
+ expect(result.exitCode, 0);
+ });
+
+ testUsingContext('test runs to completion', () async {
+ Cache.flutterRoot = '../..';
+ final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory,
+ extraArguments: const <String>['--verbose']);
+ if ((!result.stdout.contains('+1: All tests passed')) ||
+ (!result.stdout.contains('test 0: starting shell process')) ||
+ (!result.stdout.contains('test 0: deleting temporary directory')) ||
+ (!result.stdout.contains('test 0: finished')) ||
+ (!result.stdout.contains('test package returned with exit code 0')))
+ fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
+ if (result.stderr.isNotEmpty)
+ fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n');
+ expect(result.exitCode, 0);
+ });
+
+ });
+}
+
+Future<void> _testFile(
+ String testName,
+ String workingDirectory,
+ String testDirectory, {
+ Matcher exitCode,
+ List<String> extraArguments = const <String>[],
+ }) async {
+ exitCode ??= isNonZero;
+ final String fullTestExpectation = fs.path.join(testDirectory, '${testName}_expectation.txt');
+ final File expectationFile = fs.file(fullTestExpectation);
+ if (!expectationFile.existsSync())
+ fail('missing expectation file: $expectationFile');
+
+ while (_testExclusionLock != null)
+ await _testExclusionLock;
+
+ final ProcessResult exec = await _runFlutterTest(
+ testName,
+ workingDirectory,
+ testDirectory,
+ extraArguments: extraArguments,
+ );
+
+ expect(exec.exitCode, exitCode);
+ final List<String> output = exec.stdout.split('\n');
+ if (output.first == 'Waiting for another flutter command to release the startup lock...')
+ output.removeAt(0);
+ if (output.first.startsWith('Running "flutter pub get" in'))
+ output.removeAt(0);
+ output.add('<<stderr>>');
+ output.addAll(exec.stderr.split('\n'));
+ final List<String> expectations = fs.file(fullTestExpectation).readAsLinesSync();
+ bool allowSkip = false;
+ int expectationLineNumber = 0;
+ int outputLineNumber = 0;
+ bool haveSeenStdErrMarker = false;
+ while (expectationLineNumber < expectations.length) {
+ expect(
+ output,
+ hasLength(greaterThan(outputLineNumber)),
+ reason: 'Failure in $testName to compare to $fullTestExpectation',
+ );
+ final String expectationLine = expectations[expectationLineNumber];
+ String outputLine = output[outputLineNumber];
+ if (expectationLine == '<<skip until matching line>>') {
+ allowSkip = true;
+ expectationLineNumber += 1;
+ continue;
+ }
+ if (allowSkip) {
+ if (!RegExp(expectationLine).hasMatch(outputLine)) {
+ outputLineNumber += 1;
+ continue;
+ }
+ allowSkip = false;
+ }
+ if (expectationLine == '<<stderr>>') {
+ expect(haveSeenStdErrMarker, isFalse);
+ haveSeenStdErrMarker = true;
+ }
+ if (!RegExp(expectationLine).hasMatch(outputLine) && outputLineNumber + 1 < output.length) {
+ // Check if the RegExp can match the next two lines in the output so
+ // that it is possible to write expectations that still hold even if a
+ // line is wrapped slightly differently due to for example a file name
+ // being longer on one platform than another.
+ final String mergedLines = '$outputLine\n${output[outputLineNumber+1]}';
+ if (RegExp(expectationLine).hasMatch(mergedLines)) {
+ outputLineNumber += 1;
+ outputLine = mergedLines;
+ }
+ }
+
+ expect(outputLine, matches(expectationLine), reason: 'Full output:\n- - - -----8<----- - - -\n${output.join("\n")}\n- - - -----8<----- - - -');
+ expectationLineNumber += 1;
+ outputLineNumber += 1;
+ }
+ expect(allowSkip, isFalse);
+ if (!haveSeenStdErrMarker)
+ expect(exec.stderr, '');
+}
+
+Future<ProcessResult> _runFlutterTest(
+ String testName,
+ String workingDirectory,
+ String testDirectory, {
+ List<String> extraArguments = const <String>[],
+}) async {
+
+ final String testFilePath = fs.path.join(testDirectory, '${testName}_test.dart');
+ final File testFile = fs.file(testFilePath);
+ if (!testFile.existsSync())
+ fail('missing test file: $testFile');
+
+ final List<String> args = <String>[
+ ...dartVmFlags,
+ fs.path.absolute(fs.path.join('bin', 'flutter_tools.dart')),
+ 'test',
+ '--no-color',
+ ...extraArguments,
+ testFilePath
+ ];
+
+ while (_testExclusionLock != null)
+ await _testExclusionLock;
+
+ final Completer<void> testExclusionCompleter = Completer<void>();
+ _testExclusionLock = testExclusionCompleter.future;
+ try {
+ return await Process.run(
+ fs.path.join(dartSdkPath, 'bin', 'dart'),
+ args,
+ workingDirectory: workingDirectory,
+ );
+ } finally {
+ _testExclusionLock = null;
+ testExclusionCompleter.complete();
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/update_packages_test.dart b/packages/flutter_tools/test/general.shard/commands/update_packages_test.dart
new file mode 100644
index 0000000..6097ba0
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/update_packages_test.dart
@@ -0,0 +1,18 @@
+// Copyright 2015 The Chromium 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 'package:flutter_tools/src/commands/update_packages.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ group('UpdatePackagesCommand', () {
+ // Marking it as experimental breaks bots tests and packaging scripts on stable branches.
+ testUsingContext('is not marked as experimental', () async {
+ final UpdatePackagesCommand command = UpdatePackagesCommand();
+ expect(command.isExperimental, isFalse);
+ });
+ });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
new file mode 100644
index 0000000..ac9879d
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
@@ -0,0 +1,192 @@
+// Copyright 2016 The Chromium 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 '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/os.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/upgrade.dart';
+import 'package:flutter_tools/src/runner/flutter_command.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+void main() {
+ group('UpgradeCommandRunner', () {
+ FakeUpgradeCommandRunner fakeCommandRunner;
+ UpgradeCommandRunner realCommandRunner;
+ MockProcessManager processManager;
+ final MockFlutterVersion flutterVersion = MockFlutterVersion();
+ const GitTagVersion gitTagVersion = GitTagVersion(1, 2, 3, 4, 5, 'asd');
+ when(flutterVersion.channel).thenReturn('dev');
+
+ setUp(() {
+ fakeCommandRunner = FakeUpgradeCommandRunner();
+ realCommandRunner = UpgradeCommandRunner();
+ processManager = MockProcessManager();
+ fakeCommandRunner.willHaveUncomittedChanges = false;
+ });
+
+ test('throws on unknown tag, official branch, noforce', () async {
+ final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+ false,
+ const GitTagVersion.unknown(),
+ flutterVersion,
+ );
+ expect(result, throwsA(isInstanceOf<ToolExit>()));
+ });
+
+ test('does not throw on unknown tag, official branch, force', () async {
+ final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+ true,
+ const GitTagVersion.unknown(),
+ flutterVersion,
+ );
+ expect(await result, null);
+ });
+
+ test('throws tool exit with uncommitted changes', () async {
+ fakeCommandRunner.willHaveUncomittedChanges = true;
+ final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+ false,
+ gitTagVersion,
+ flutterVersion,
+ );
+ expect(result, throwsA(isA<ToolExit>()));
+ });
+
+ test('does not throw tool exit with uncommitted changes and force', () async {
+ fakeCommandRunner.willHaveUncomittedChanges = true;
+ final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+ true,
+ gitTagVersion,
+ flutterVersion,
+ );
+ expect(await result, null);
+ });
+
+ test('Doesn\'t throw on known tag, dev branch, no force', () async {
+ final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+ false,
+ gitTagVersion,
+ flutterVersion,
+ );
+ expect(await result, null);
+ });
+
+ testUsingContext('verifyUpstreamConfigured', () async {
+ when(processManager.run(
+ <String>['git', 'rev-parse', '@{u}'],
+ environment:anyNamed('environment'),
+ workingDirectory: anyNamed('workingDirectory'))
+ ).thenAnswer((Invocation invocation) async {
+ return FakeProcessResult()
+ ..exitCode = 0;
+ });
+ await realCommandRunner.verifyUpstreamConfigured();
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => processManager,
+ });
+ });
+
+ group('matchesGitLine', () {
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ bool _match(String line) => UpgradeCommandRunner.matchesGitLine(line);
+
+ test('regex match', () {
+ expect(_match(' .../flutter_gallery/lib/demo/buttons_demo.dart | 10 +--'), true);
+ expect(_match(' dev/benchmarks/complex_layout/lib/main.dart | 24 +-'), true);
+
+ expect(_match(' rename {packages/flutter/doc => dev/docs}/styles.html (92%)'), true);
+ expect(_match(' delete mode 100644 doc/index.html'), true);
+ expect(_match(' create mode 100644 examples/flutter_gallery/lib/gallery/demo.dart'), true);
+
+ expect(_match('Fast-forward'), true);
+ });
+
+ test('regex doesn\'t match', () {
+ expect(_match('Updating 79cfe1e..5046107'), false);
+ expect(_match('229 files changed, 6179 insertions(+), 3065 deletions(-)'), false);
+ });
+
+ group('findProjectRoot', () {
+ Directory tempDir;
+
+ setUp(() async {
+ tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_upgrade_test.');
+ });
+
+ tearDown(() {
+ tryToDelete(tempDir);
+ });
+
+ testUsingContext('in project', () async {
+ final String projectPath = await createProject(tempDir);
+ expect(findProjectRoot(projectPath), projectPath);
+ expect(findProjectRoot(fs.path.join(projectPath, 'lib')), projectPath);
+
+ final String hello = fs.path.join(Cache.flutterRoot, 'examples', 'hello_world');
+ expect(findProjectRoot(hello), hello);
+ expect(findProjectRoot(fs.path.join(hello, 'lib')), hello);
+ });
+
+ testUsingContext('outside project', () async {
+ final String projectPath = await createProject(tempDir);
+ expect(findProjectRoot(fs.directory(projectPath).parent.path), null);
+ expect(findProjectRoot(Cache.flutterRoot), null);
+ });
+ });
+ });
+}
+
+class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
+ bool willHaveUncomittedChanges = false;
+
+ @override
+ Future<void> verifyUpstreamConfigured() async {}
+
+ @override
+ Future<bool> hasUncomittedChanges() async => willHaveUncomittedChanges;
+
+ @override
+ Future<void> resetChanges(GitTagVersion gitTagVersion) async {}
+
+ @override
+ Future<void> upgradeChannel(FlutterVersion flutterVersion) async {}
+
+ @override
+ Future<void> attemptFastForward() async {}
+
+ @override
+ Future<void> precacheArtifacts() async {}
+
+ @override
+ Future<void> updatePackages(FlutterVersion flutterVersion) async {}
+
+ @override
+ Future<void> runDoctor() async {}
+}
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
+class MockProcessManager extends Mock implements ProcessManager {}
+class FakeProcessResult implements ProcessResult {
+ @override
+ int exitCode;
+
+ @override
+ int pid = 0;
+
+ @override
+ String stderr = '';
+
+ @override
+ String stdout = '';
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/version_test.dart b/packages/flutter_tools/test/general.shard/commands/version_test.dart
new file mode 100644
index 0000000..1e48004
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/commands/version_test.dart
@@ -0,0 +1,123 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/version.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/mocks.dart' show MockProcess;
+
+void main() {
+ group('version', () {
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ testUsingContext('version ls', () async {
+ final VersionCommand command = VersionCommand();
+ await createTestCommandRunner(command).run(<String>['version']);
+ expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\n' ''));
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => MockProcessManager(),
+ });
+
+ testUsingContext('version switch', () async {
+ const String version = '10.0.0';
+ final VersionCommand command = VersionCommand();
+ final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', version]);
+ await Future.wait<void>(<Future<void>>[runCommand]);
+ expect(testLogger.statusText, contains('Switching Flutter to version $version'));
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => MockProcessManager(),
+ });
+
+ testUsingContext('switch to not supported version without force', () async {
+ const String version = '1.1.5';
+ final VersionCommand command = VersionCommand();
+ final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', version]);
+ await Future.wait<void>(<Future<void>>[runCommand]);
+ expect(testLogger.errorText, contains('Version command is not supported in'));
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => MockProcessManager(),
+ });
+
+ testUsingContext('switch to not supported version with force', () async {
+ const String version = '1.1.5';
+ final VersionCommand command = VersionCommand();
+ final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', '--force', version]);
+ await Future.wait<void>(<Future<void>>[runCommand]);
+ expect(testLogger.statusText, contains('Switching Flutter to version $version with force'));
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => MockProcessManager(),
+ });
+ });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {
+ String version = '';
+
+ @override
+ Future<ProcessResult> run(
+ List<dynamic> command, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ Encoding stdoutEncoding = systemEncoding,
+ Encoding stderrEncoding = systemEncoding,
+ }) async {
+ if (command[0] == 'git' && command[1] == 'tag') {
+ return ProcessResult(0, 0, 'v10.0.0\r\nv20.0.0', '');
+ }
+ if (command[0] == 'git' && command[1] == 'checkout') {
+ version = command[2];
+ }
+ return ProcessResult(0, 0, '', '');
+ }
+
+ @override
+ ProcessResult runSync(
+ List<dynamic> command, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ Encoding stdoutEncoding = systemEncoding,
+ Encoding stderrEncoding = systemEncoding,
+ }) {
+ final String commandStr = command.join(' ');
+ if (commandStr == 'git log -n 1 --pretty=format:%H') {
+ return ProcessResult(0, 0, '000000000000000000000', '');
+ }
+ if (commandStr ==
+ 'git describe --match v*.*.* --first-parent --long --tags') {
+ if (version.isNotEmpty) {
+ return ProcessResult(0, 0, '$version-0-g00000000', '');
+ }
+ }
+ return ProcessResult(0, 0, '', '');
+ }
+
+ @override
+ Future<Process> start(
+ List<dynamic> command, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ ProcessStartMode mode = ProcessStartMode.normal,
+ }) {
+ final Completer<Process> completer = Completer<Process>();
+ completer.complete(MockProcess());
+ return completer.future;
+ }
+}