// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; import 'package:flutter_plugin_tools/src/fetch_deps_command.dart'; import 'package:test/test.dart'; import 'mocks.dart'; import 'util.dart'; void main() { group('FetchDepsCommand', () { FileSystem fileSystem; late Directory packagesDir; late CommandRunner<void> runner; late MockPlatform mockPlatform; late RecordingProcessRunner processRunner; setUp(() { fileSystem = MemoryFileSystem(); packagesDir = createPackagesDirectory(fileSystem: fileSystem); mockPlatform = MockPlatform(); processRunner = RecordingProcessRunner(); final FetchDepsCommand command = FetchDepsCommand( packagesDir, processRunner: processRunner, platform: mockPlatform, ); runner = CommandRunner<void>('fetch_deps_test', 'Test for $FetchDepsCommand'); runner.addCommand(command); }); group('dart', () { test('runs pub get', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformIOS: const PlatformDetails(PlatformSupport.inline) }); final List<String> output = await runCapturingPrint(runner, <String>['fetch-deps']); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ ProcessCall( 'flutter', const <String>['pub', 'get'], plugin.directory.path, ), ]), ); expect( output, containsAllInOrder(<Matcher>[ contains('Running for plugin1'), contains('No issues found!'), ])); }); test('fails if pub get fails', () async { createFakePlugin('plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformIOS: const PlatformDetails(PlatformSupport.inline) }); processRunner .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = <FakeProcessInfo>[ FakeProcessInfo(MockProcess(exitCode: 1)), ]; Error? commandError; final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA<ToolExit>()); expect( output, containsAllInOrder( <Matcher>[ contains('Failed to "pub get"'), ], )); }); test('skips unsupported packages when any platforms are passed', () async { final RepositoryPackage packageWithBoth = createFakePackage( 'supports_both', packagesDir, extraFiles: <String>[ 'example/linux/placeholder', 'example/windows/placeholder' ]); final RepositoryPackage packageWithOne = createFakePackage( 'supports_one', packagesDir, extraFiles: <String>['example/linux/placeholder']); createFakePackage('supports_neither', packagesDir); await runCapturingPrint(runner, <String>[ 'fetch-deps', '--linux', '--windows', '--supporting-target-platforms-only' ]); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ ProcessCall( 'dart', const <String>['pub', 'get'], packageWithBoth.path, ), ProcessCall( 'dart', const <String>['pub', 'get'], packageWithOne.path, ), ]), ); }); }); group('android', () { test('runs pub get before gradlew dependencies', () async { final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, extraFiles: <String>[ 'example/android/gradlew', ], platformSupport: <String, PlatformDetails>{ platformAndroid: const PlatformDetails(PlatformSupport.inline) }); final Directory androidDir = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--android']); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ ProcessCall( 'flutter', const <String>['pub', 'get'], plugin.directory.path, ), ProcessCall( androidDir.childFile('gradlew').path, const <String>['plugin1:dependencies'], androidDir.path, ), ]), ); expect( output, containsAllInOrder(<Matcher>[ contains('Running for plugin1'), contains('No issues found!'), ])); }); test('runs gradlew dependencies', () async { final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, extraFiles: <String>[ 'example/android/gradlew', ], platformSupport: <String, PlatformDetails>{ platformAndroid: const PlatformDetails(PlatformSupport.inline) }); final Directory androidDir = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--android']); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ ProcessCall( androidDir.childFile('gradlew').path, const <String>['plugin1:dependencies'], androidDir.path, ), ]), ); expect( output, containsAllInOrder(<Matcher>[ contains('Running for plugin1'), contains('No issues found!'), ])); }); test('runs on all examples', () async { final List<String> examples = <String>['example1', 'example2']; final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, examples: examples, extraFiles: <String>[ 'example/example1/android/gradlew', 'example/example2/android/gradlew', ], platformSupport: <String, PlatformDetails>{ platformAndroid: const PlatformDetails(PlatformSupport.inline) }); final Iterable<Directory> exampleAndroidDirs = plugin.getExamples().map( (RepositoryPackage example) => example.platformDirectory(FlutterPlatform.android)); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--android']); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ for (final Directory directory in exampleAndroidDirs) ProcessCall( directory.childFile('gradlew').path, const <String>['plugin1:dependencies'], directory.path, ), ]), ); expect( output, containsAllInOrder(<Matcher>[ contains('Running for plugin1'), contains('No issues found!'), ])); }); test('runs --config-only build if gradlew is missing', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformAndroid: const PlatformDetails(PlatformSupport.inline) }); final Directory androidDir = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--android']); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ ProcessCall( getFlutterCommand(mockPlatform), const <String>['build', 'apk', '--config-only'], plugin.getExamples().first.directory.path, ), ProcessCall( androidDir.childFile('gradlew').path, const <String>['plugin1:dependencies'], androidDir.path, ), ]), ); expect( output, containsAllInOrder(<Matcher>[ contains('Running for plugin1'), contains('No issues found!'), ])); }); test('fails if gradlew generation fails', () async { createFakePlugin('plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformAndroid: const PlatformDetails(PlatformSupport.inline) }); processRunner .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = <FakeProcessInfo>[ FakeProcessInfo(MockProcess(exitCode: 1)), ]; Error? commandError; final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--android'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA<ToolExit>()); expect( output, containsAllInOrder( <Matcher>[ contains('Unable to configure Gradle project'), ], )); }); test('fails if dependency download finds issues', () async { final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, extraFiles: <String>[ 'example/android/gradlew', ], platformSupport: <String, PlatformDetails>{ platformAndroid: const PlatformDetails(PlatformSupport.inline) }); final String gradlewPath = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android) .childFile('gradlew') .path; processRunner.mockProcessesForExecutable[gradlewPath] = <FakeProcessInfo>[ FakeProcessInfo(MockProcess(exitCode: 1)), ]; Error? commandError; final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--android'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA<ToolExit>()); expect( output, containsAllInOrder( <Matcher>[ contains('The following packages had errors:'), ], )); }); test('skips non-Android plugins', () async { createFakePlugin('plugin1', packagesDir); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--android']); expect( output, containsAllInOrder( <Matcher>[ contains('Package does not have native Android dependencies.') ], )); }); test('skips non-inline plugins', () async { createFakePlugin('plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformAndroid: const PlatformDetails(PlatformSupport.federated) }); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--android']); expect( output, containsAllInOrder( <Matcher>[ contains('Package does not have native Android dependencies.') ], )); }); }); group('ios', () { test('runs on all examples', () async { final List<String> examples = <String>['example1', 'example2']; final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, examples: examples, platformSupport: <String, PlatformDetails>{ platformIOS: const PlatformDetails(PlatformSupport.inline) }); final Iterable<Directory> exampleDirs = plugin .getExamples() .map((RepositoryPackage example) => example.directory); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--ios']); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ const ProcessCall( 'flutter', <String>['precache', '--ios'], null, ), for (final Directory directory in exampleDirs) ProcessCall( 'flutter', const <String>['build', 'ios', '--config-only'], directory.path, ), ]), ); expect( output, containsAllInOrder(<Matcher>[ contains('Running for plugin1'), contains('No issues found!'), ])); }); test('fails if flutter build --config-only fails', () async { createFakePlugin('plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformIOS: const PlatformDetails(PlatformSupport.inline) }); processRunner .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = <FakeProcessInfo>[ FakeProcessInfo(MockProcess(), <String>['precache']), FakeProcessInfo(MockProcess(exitCode: 1), <String>['build', 'ios', '--config-only']), ]; Error? commandError; final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--ios'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA<ToolExit>()); expect( output, containsAllInOrder( <Matcher>[ contains('The following packages had errors:'), ], )); }); test('skips non-iOS plugins', () async { createFakePlugin('plugin1', packagesDir); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--ios']); expect( output, containsAllInOrder( <Matcher>[ contains('Package does not have native iOS dependencies.') ], )); }); test('skips non-inline plugins', () async { createFakePlugin('plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformIOS: const PlatformDetails(PlatformSupport.federated) }); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--ios']); expect( output, containsAllInOrder( <Matcher>[ contains('Package does not have native iOS dependencies.') ], )); }); }); group('macos', () { test('runs on all examples', () async { final List<String> examples = <String>['example1', 'example2']; final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, examples: examples, platformSupport: <String, PlatformDetails>{ platformMacOS: const PlatformDetails(PlatformSupport.inline) }); final Iterable<Directory> exampleDirs = plugin .getExamples() .map((RepositoryPackage example) => example.directory); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--macos']); expect( processRunner.recordedCalls, orderedEquals(<ProcessCall>[ const ProcessCall( 'flutter', <String>['precache', '--macos'], null, ), for (final Directory directory in exampleDirs) ProcessCall( 'flutter', const <String>['build', 'macos', '--config-only'], directory.path, ), ]), ); expect( output, containsAllInOrder(<Matcher>[ contains('Running for plugin1'), contains('No issues found!'), ])); }); test('fails if flutter build --config-only fails', () async { createFakePlugin('plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformMacOS: const PlatformDetails(PlatformSupport.inline) }); processRunner .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = <FakeProcessInfo>[ FakeProcessInfo(MockProcess(), <String>['precache']), FakeProcessInfo(MockProcess(exitCode: 1), <String>['build', 'macos', '--config-only']), ]; Error? commandError; final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--macos'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA<ToolExit>()); expect( output, containsAllInOrder( <Matcher>[ contains('The following packages had errors:'), ], )); }); test('skips non-macOS plugins', () async { createFakePlugin('plugin1', packagesDir); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--macos']); expect( output, containsAllInOrder( <Matcher>[ contains('Package does not have native macOS dependencies.') ], )); }); test('skips non-inline plugins', () async { createFakePlugin('plugin1', packagesDir, platformSupport: <String, PlatformDetails>{ platformMacOS: const PlatformDetails(PlatformSupport.federated) }); final List<String> output = await runCapturingPrint( runner, <String>['fetch-deps', '--no-dart', '--macos']); expect( output, containsAllInOrder( <Matcher>[ contains('Package does not have native macOS dependencies.') ], )); }); }); }); }