// 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 'dart:io'; import 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/plugin_command.dart'; import 'package:flutter_plugin_tools/src/common/process_runner.dart'; import 'package:git/git.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import '../util.dart'; import 'plugin_command_test.mocks.dart'; @GenerateMocks([GitDir]) void main() { late RecordingProcessRunner processRunner; late CommandRunner runner; late FileSystem fileSystem; late Directory packagesDir; late Directory thirdPartyPackagesDir; late List plugins; late List?> gitDirCommands; late String gitDiffResponse; setUp(() { fileSystem = MemoryFileSystem(); packagesDir = createPackagesDirectory(fileSystem: fileSystem); thirdPartyPackagesDir = packagesDir.parent .childDirectory('third_party') .childDirectory('packages'); gitDirCommands = ?>[]; gitDiffResponse = ''; final MockGitDir gitDir = MockGitDir(); when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) .thenAnswer((Invocation invocation) { gitDirCommands.add(invocation.positionalArguments[0] as List?); final MockProcessResult mockProcessResult = MockProcessResult(); if (invocation.positionalArguments[0][0] == 'diff') { when(mockProcessResult.stdout as String?) .thenReturn(gitDiffResponse); } return Future.value(mockProcessResult); }); processRunner = RecordingProcessRunner(); plugins = []; final SamplePluginCommand samplePluginCommand = SamplePluginCommand( plugins, packagesDir, processRunner: processRunner, gitDir: gitDir, ); runner = CommandRunner('common_command', 'Test for common functionality'); runner.addCommand(samplePluginCommand); }); group('plugin iteration', () { test('all plugins from file system', () async { final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run(['sample']); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('all plugins includes third_party/packages', () async { final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); final Directory plugin3 = createFakePlugin('plugin3', thirdPartyPackagesDir); await runner.run(['sample']); expect(plugins, unorderedEquals([plugin1.path, plugin2.path, plugin3.path])); }); test('exclude plugins when plugins flag is specified', () async { createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run( ['sample', '--plugins=plugin1,plugin2', '--exclude=plugin1']); expect(plugins, unorderedEquals([plugin2.path])); }); test('exclude plugins when plugins flag isn\'t specified', () async { createFakePlugin('plugin1', packagesDir); createFakePlugin('plugin2', packagesDir); await runner.run(['sample', '--exclude=plugin1,plugin2']); expect(plugins, unorderedEquals([])); }); test('exclude federated plugins when plugins flag is specified', () async { createFakePlugin('plugin1', packagesDir, parentDirectoryName: 'federated'); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--plugins=federated/plugin1,plugin2', '--exclude=federated/plugin1' ]); expect(plugins, unorderedEquals([plugin2.path])); }); test('exclude entire federated plugins when plugins flag is specified', () async { createFakePlugin('plugin1', packagesDir, parentDirectoryName: 'federated'); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--plugins=federated/plugin1,plugin2', '--exclude=federated' ]); expect(plugins, unorderedEquals([plugin2.path])); }); group('test run-on-changed-packages', () { test('all plugins should be tested if there are no changes.', () async { final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test( 'all plugins should be tested if there are no plugin related changes.', () async { gitDiffResponse = 'AUTHORS'; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('all plugins should be tested if .cirrus.yml changes.', () async { gitDiffResponse = ''' .cirrus.yml packages/plugin1/CHANGELOG '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('all plugins should be tested if .ci.yaml changes', () async { gitDiffResponse = ''' .ci.yaml packages/plugin1/CHANGELOG '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('all plugins should be tested if anything in .ci/ changes', () async { gitDiffResponse = ''' .ci/Dockerfile packages/plugin1/CHANGELOG '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('all plugins should be tested if anything in script changes.', () async { gitDiffResponse = ''' script/tool_runner.sh packages/plugin1/CHANGELOG '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('all plugins should be tested if the root analysis options change.', () async { gitDiffResponse = ''' analysis_options.yaml packages/plugin1/CHANGELOG '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('all plugins should be tested if formatting options change.', () async { gitDiffResponse = ''' .clang-format packages/plugin1/CHANGELOG '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('Only changed plugin should be tested.', () async { gitDiffResponse = 'packages/plugin1/plugin1.dart'; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path])); }); test('multiple files in one plugin should also test the plugin', () async { gitDiffResponse = ''' packages/plugin1/plugin1.dart packages/plugin1/ios/plugin1.m '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); createFakePlugin('plugin2', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path])); }); test('multiple plugins changed should test all the changed plugins', () async { gitDiffResponse = ''' packages/plugin1/plugin1.dart packages/plugin2/ios/plugin2.m '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); createFakePlugin('plugin3', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test( 'multiple plugins inside the same plugin group changed should output the plugin group name', () async { gitDiffResponse = ''' packages/plugin1/plugin1/plugin1.dart packages/plugin1/plugin1_platform_interface/plugin1_platform_interface.dart packages/plugin1/plugin1_web/plugin1_web.dart '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir, parentDirectoryName: 'plugin1'); createFakePlugin('plugin2', packagesDir); createFakePlugin('plugin3', packagesDir); await runner.run([ 'sample', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path])); }); test('--plugins flag overrides the behavior of --run-on-changed-packages', () async { gitDiffResponse = ''' packages/plugin1/plugin1.dart packages/plugin2/ios/plugin2.m packages/plugin3/plugin3.dart '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir, parentDirectoryName: 'plugin1'); final Directory plugin2 = createFakePlugin('plugin2', packagesDir); createFakePlugin('plugin3', packagesDir); await runner.run([ 'sample', '--plugins=plugin1,plugin2', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path, plugin2.path])); }); test('--exclude flag works with --run-on-changed-packages', () async { gitDiffResponse = ''' packages/plugin1/plugin1.dart packages/plugin2/ios/plugin2.m packages/plugin3/plugin3.dart '''; final Directory plugin1 = createFakePlugin('plugin1', packagesDir, parentDirectoryName: 'plugin1'); createFakePlugin('plugin2', packagesDir); createFakePlugin('plugin3', packagesDir); await runner.run([ 'sample', '--exclude=plugin2,plugin3', '--base-sha=master', '--run-on-changed-packages' ]); expect(plugins, unorderedEquals([plugin1.path])); }); }); }); } class SamplePluginCommand extends PluginCommand { SamplePluginCommand( this._plugins, Directory packagesDir, { ProcessRunner processRunner = const ProcessRunner(), GitDir? gitDir, }) : super(packagesDir, processRunner: processRunner, gitDir: gitDir); final List _plugins; @override final String name = 'sample'; @override final String description = 'sample command'; @override Future run() async { await for (final Directory package in getPlugins()) { _plugins.add(package.path); } } } class MockProcessResult extends Mock implements ProcessResult {}