// Copyright 2013 The Flutter Authors // 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: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:git/git.dart'; import 'package:test/test.dart'; import 'mocks.dart'; import 'util.dart'; void main() { group('FetchDepsCommand', () { late Directory packagesDir; late CommandRunner runner; late MockPlatform mockPlatform; late RecordingProcessRunner processRunner; setUp(() { mockPlatform = MockPlatform(); final GitDir gitDir; (:packagesDir, :processRunner, gitProcessRunner: _, :gitDir) = configureBaseCommandMocks(platform: mockPlatform); final command = FetchDepsCommand( packagesDir, processRunner: processRunner, platform: mockPlatform, gitDir: gitDir, ); runner = CommandRunner( 'fetch_deps_test', 'Test for $FetchDepsCommand', ); runner.addCommand(command); }); group('dart', () { test('runs pub get', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', ]); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall('flutter', const [ 'pub', 'get', ], plugin.directory.path), ]), ); expect( output, containsAllInOrder([ contains('Running for plugin1'), contains('No issues found!'), ]), ); }); test('runs pub get in non-example sub-packages', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); final RepositoryPackage subpackage = createFakePackage( 'subpackage', plugin.directory.childDirectory('extras'), ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', ]); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall('flutter', const [ 'pub', 'get', ], plugin.directory.path), ProcessCall('dart', const [ 'pub', 'get', ], subpackage.directory.path), ]), ); expect( output, containsAllInOrder([ contains('Running for plugin1'), contains('No issues found!'), ]), ); }); test('fails if pub get fails', () async { createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); processRunner.mockProcessesForExecutable[getFlutterCommand( mockPlatform, )] = [ FakeProcessInfo(MockProcess(exitCode: 1)), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['fetch-deps'], errorHandler: (Error e) { commandError = e; }, ); expect(commandError, isA()); expect( output, containsAllInOrder([contains('Failed to "pub get"')]), ); }); test( 'skips unsupported packages when any platforms are passed', () async { final RepositoryPackage packageWithBoth = createFakePackage( 'supports_both', packagesDir, extraFiles: [ 'example/linux/placeholder', 'example/windows/placeholder', ], ); final RepositoryPackage packageWithOne = createFakePackage( 'supports_one', packagesDir, extraFiles: ['example/linux/placeholder'], ); createFakePackage('supports_neither', packagesDir); await runCapturingPrint(runner, [ 'fetch-deps', '--linux', '--windows', '--supporting-target-platforms-only', ]); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall('dart', const [ 'pub', 'get', ], packageWithBoth.path), ProcessCall('dart', const [ 'pub', 'get', ], packageWithOne.path), ]), ); }, ); }); group('android', () { test('runs pub get before gradlew dependencies', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, extraFiles: ['example/android/gradlew'], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); final Directory androidDir = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--android', ]); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall('flutter', const [ 'pub', 'get', ], plugin.directory.path), ProcessCall(androidDir.childFile('gradlew').path, const [ 'plugin1:dependencies', ], androidDir.path), ]), ); expect( output, containsAllInOrder([ contains('Running for plugin1'), contains('No issues found!'), ]), ); }); test('runs gradlew dependencies', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, extraFiles: ['example/android/gradlew'], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); final Directory androidDir = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--android', ]); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall(androidDir.childFile('gradlew').path, const [ 'plugin1:dependencies', ], androidDir.path), ]), ); expect( output, containsAllInOrder([ contains('Running for plugin1'), contains('No issues found!'), ]), ); }); test('runs on all examples', () async { final examples = ['example1', 'example2']; final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, examples: examples, extraFiles: [ 'example/example1/android/gradlew', 'example/example2/android/gradlew', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); final Iterable exampleAndroidDirs = plugin.getExamples().map( (RepositoryPackage example) => example.platformDirectory(FlutterPlatform.android), ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--android', ]); expect( processRunner.recordedCalls, orderedEquals([ for (final Directory directory in exampleAndroidDirs) ProcessCall(directory.childFile('gradlew').path, const [ 'plugin1:dependencies', ], directory.path), ]), ); expect( output, containsAllInOrder([ 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: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); final Directory androidDir = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--android', ]); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const ['build', 'apk', '--config-only'], plugin.getExamples().first.directory.path, ), ProcessCall(androidDir.childFile('gradlew').path, const [ 'plugin1:dependencies', ], androidDir.path), ]), ); expect( output, containsAllInOrder([ contains('Running for plugin1'), contains('No issues found!'), ]), ); }); test('fails if gradlew generation fails', () async { createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); processRunner.mockProcessesForExecutable[getFlutterCommand( mockPlatform, )] = [ FakeProcessInfo(MockProcess(exitCode: 1)), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['fetch-deps', '--no-dart', '--android'], errorHandler: (Error e) { commandError = e; }, ); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Unable to configure Gradle project'), ]), ); }); test('fails if dependency download finds issues', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, extraFiles: ['example/android/gradlew'], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); final String gradlewPath = plugin .getExamples() .first .platformDirectory(FlutterPlatform.android) .childFile('gradlew') .path; processRunner.mockProcessesForExecutable[gradlewPath] = [FakeProcessInfo(MockProcess(exitCode: 1))]; Error? commandError; final List output = await runCapturingPrint( runner, ['fetch-deps', '--no-dart', '--android'], errorHandler: (Error e) { commandError = e; }, ); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), ]), ); }); test('skips non-Android plugins', () async { createFakePlugin('plugin1', packagesDir); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--android', ]); expect( output, containsAllInOrder([ contains('Package does not have native Android dependencies.'), ]), ); }); test('skips non-inline plugins', () async { createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.federated), }, ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--android', ]); expect( output, containsAllInOrder([ contains('Package does not have native Android dependencies.'), ]), ); }); }); group('ios', () { test('runs on all examples', () async { final examples = ['example1', 'example2']; final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, examples: examples, platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); final Iterable exampleDirs = plugin.getExamples().map( (RepositoryPackage example) => example.directory, ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--ios', ]); expect( processRunner.recordedCalls, orderedEquals([ const ProcessCall('flutter', ['precache', '--ios'], null), const ProcessCall('pod', ['repo', 'update'], null), for (final Directory directory in exampleDirs) ProcessCall('flutter', const [ 'build', 'ios', '--config-only', ], directory.path), ]), ); expect( output, containsAllInOrder([ contains('Running for plugin1'), contains('No issues found!'), ]), ); }); test('fails if flutter build --config-only fails', () async { createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); processRunner.mockProcessesForExecutable[getFlutterCommand( mockPlatform, )] = [ FakeProcessInfo(MockProcess(), ['precache']), FakeProcessInfo(MockProcess(), ['repo', 'update']), FakeProcessInfo(MockProcess(exitCode: 1), [ 'build', 'ios', '--config-only', ]), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['fetch-deps', '--no-dart', '--ios'], errorHandler: (Error e) { commandError = e; }, ); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), ]), ); }); test('skips non-iOS plugins', () async { createFakePlugin('plugin1', packagesDir); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--ios', ]); expect( output, containsAllInOrder([ contains('Package does not have native iOS dependencies.'), ]), ); }); test('skips non-inline plugins', () async { createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.federated), }, ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--ios', ]); expect( output, containsAllInOrder([ contains('Package does not have native iOS dependencies.'), ]), ); }); }); group('macos', () { test('runs on all examples', () async { final examples = ['example1', 'example2']; final RepositoryPackage plugin = createFakePlugin( 'plugin1', packagesDir, examples: examples, platformSupport: { platformMacOS: const PlatformDetails(PlatformSupport.inline), }, ); final Iterable exampleDirs = plugin.getExamples().map( (RepositoryPackage example) => example.directory, ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--macos', ]); expect( processRunner.recordedCalls, orderedEquals([ const ProcessCall('flutter', ['precache', '--macos'], null), const ProcessCall('pod', ['repo', 'update'], null), for (final Directory directory in exampleDirs) ProcessCall('flutter', const [ 'build', 'macos', '--config-only', ], directory.path), ]), ); expect( output, containsAllInOrder([ contains('Running for plugin1'), contains('No issues found!'), ]), ); }); test('fails if flutter build --config-only fails', () async { createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformMacOS: const PlatformDetails(PlatformSupport.inline), }, ); processRunner.mockProcessesForExecutable[getFlutterCommand( mockPlatform, )] = [ FakeProcessInfo(MockProcess(), ['precache']), FakeProcessInfo(MockProcess(), ['repo', 'update']), FakeProcessInfo(MockProcess(exitCode: 1), [ 'build', 'macos', '--config-only', ]), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['fetch-deps', '--no-dart', '--macos'], errorHandler: (Error e) { commandError = e; }, ); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), ]), ); }); test('skips non-macOS plugins', () async { createFakePlugin('plugin1', packagesDir); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--macos', ]); expect( output, containsAllInOrder([ contains('Package does not have native macOS dependencies.'), ]), ); }); test('skips non-inline plugins', () async { createFakePlugin( 'plugin1', packagesDir, platformSupport: { platformMacOS: const PlatformDetails(PlatformSupport.federated), }, ); final List output = await runCapturingPrint(runner, [ 'fetch-deps', '--no-dart', '--macos', ]); expect( output, containsAllInOrder([ contains('Package does not have native macOS dependencies.'), ]), ); }); }); }); }