| // 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/package_command.dart'; | |
| import 'package:git/git.dart'; | |
| import 'package:mockito/annotations.dart'; | |
| import 'package:platform/platform.dart'; | |
| import 'package:test/test.dart'; | |
| import '../mocks.dart'; | |
| import '../util.dart'; | |
| @GenerateMocks(<Type>[GitDir]) | |
| void main() { | |
| late GitDir gitDir; | |
| late RecordingProcessRunner processRunner; | |
| late RecordingProcessRunner gitProcessRunner; | |
| late SamplePackageCommand command; | |
| late CommandRunner<void> runner; | |
| late NativePlatform mockPlatform; | |
| late Directory packagesDir; | |
| late Directory thirdPartyPackagesDir; | |
| SamplePackageCommand configureCommand({bool includeSubpackages = false}) { | |
| final command = SamplePackageCommand( | |
| packagesDir, | |
| processRunner: processRunner, | |
| platform: mockPlatform, | |
| gitDir: gitDir, | |
| includeSubpackages: includeSubpackages, | |
| ); | |
| return command; | |
| } | |
| setUp(() { | |
| mockPlatform = createMockPlatform(); | |
| (:packagesDir, :processRunner, :gitProcessRunner, :gitDir) = configureBaseCommandMocks( | |
| platform: mockPlatform, | |
| ); | |
| thirdPartyPackagesDir = packagesDir.parent | |
| .childDirectory('third_party') | |
| .childDirectory('packages'); | |
| command = configureCommand(); | |
| runner = CommandRunner<void>('common_command', 'Test for common functionality'); | |
| runner.addCommand(command); | |
| }); | |
| group('plugin iteration', () { | |
| test('all plugins from file system', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample']); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| }); | |
| test('includes both plugins and packages', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| final RepositoryPackage package3 = createFakePackage('package3', packagesDir); | |
| final RepositoryPackage package4 = createFakePackage('package4', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample']); | |
| expect( | |
| command.plugins, | |
| unorderedEquals(<String>[plugin1.path, plugin2.path, package3.path, package4.path]), | |
| ); | |
| }); | |
| test('includes packages without source', () async { | |
| final RepositoryPackage package = createFakePackage('package', packagesDir); | |
| package.libDirectory.deleteSync(recursive: true); | |
| await runCapturingPrint(runner, <String>['sample']); | |
| expect(command.plugins, unorderedEquals(<String>[package.path])); | |
| }); | |
| test('all plugins includes third_party/packages', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| final RepositoryPackage plugin3 = createFakePlugin('plugin3', thirdPartyPackagesDir); | |
| await runCapturingPrint(runner, <String>['sample']); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path, plugin3.path])); | |
| }); | |
| test('--packages limits packages', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| createFakePackage('package3', packagesDir); | |
| final RepositoryPackage package4 = createFakePackage('package4', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--packages=plugin1,package4']); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, package4.path])); | |
| }); | |
| test('--plugins acts as an alias to --packages', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| createFakePackage('package3', packagesDir); | |
| final RepositoryPackage package4 = createFakePackage('package4', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--plugins=plugin1,package4']); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, package4.path])); | |
| }); | |
| test('exclude packages when packages flag is specified', () async { | |
| createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages=plugin1,plugin2', | |
| '--exclude=plugin1', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin2.path])); | |
| }); | |
| test("exclude packages when packages flag isn't specified", () async { | |
| createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--exclude=plugin1,plugin2']); | |
| expect(command.plugins, unorderedEquals(<String>[])); | |
| }); | |
| test('exclude federated plugins when packages flag is specified', () async { | |
| createFakePlugin('plugin1', packagesDir.childDirectory('federated')); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages=federated/plugin1,plugin2', | |
| '--exclude=federated/plugin1', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin2.path])); | |
| }); | |
| test('exclude entire federated plugins when packages flag is specified', () async { | |
| createFakePlugin('plugin1', packagesDir.childDirectory('federated')); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages=federated/plugin1,plugin2', | |
| '--exclude=federated', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin2.path])); | |
| }); | |
| test('exclude accepts config files', () async { | |
| createFakePlugin('plugin1', packagesDir); | |
| final File configFile = packagesDir.childFile('exclude.yaml'); | |
| configFile.writeAsStringSync('- plugin1'); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages=plugin1', | |
| '--exclude=${configFile.path}', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[])); | |
| }); | |
| test('exclude accepts empty config files', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final File configFile1 = packagesDir.childFile('exclude1.yaml'); | |
| configFile1.createSync(); | |
| final File configFile2 = packagesDir.childFile('exclude2.yaml'); | |
| configFile2.writeAsStringSync('\n'); | |
| final File configFile3 = packagesDir.childFile('exclude3.yaml'); | |
| configFile3.writeAsStringSync('# - plugin1'); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages=plugin1', | |
| '--exclude=${configFile1.path},${configFile2.path},${configFile3.path}', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('filter-packages-to accepts config files', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| final File configFile = packagesDir.childFile('exclude.yaml'); | |
| configFile.writeAsStringSync('- plugin1'); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages=plugin1,plugin2', | |
| '--filter-packages-to=${configFile.path}', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('explicitly specifying the plugin (group) name of a federated plugin ' | |
| 'should include all plugins in the group', () async { | |
| final Directory pluginGroup = packagesDir.childDirectory('plugin1'); | |
| final RepositoryPackage appFacingPackage = createFakePlugin('plugin1', pluginGroup); | |
| final RepositoryPackage platformInterfacePackage = createFakePlugin( | |
| 'plugin1_platform_interface', | |
| pluginGroup, | |
| ); | |
| final RepositoryPackage implementationPackage = createFakePlugin('plugin1_web', pluginGroup); | |
| await runCapturingPrint(runner, <String>['sample', '--packages=plugin1']); | |
| expect( | |
| command.plugins, | |
| unorderedEquals(<String>[ | |
| appFacingPackage.path, | |
| platformInterfacePackage.path, | |
| implementationPackage.path, | |
| ]), | |
| ); | |
| }); | |
| test('specifying the app-facing package of a federated plugin with ' | |
| '--exact-match-only should only include only that package', () async { | |
| final Directory pluginGroup = packagesDir.childDirectory('plugin1'); | |
| final RepositoryPackage appFacingPackage = createFakePlugin('plugin1', pluginGroup); | |
| createFakePlugin('plugin1_platform_interface', pluginGroup); | |
| createFakePlugin('plugin1_web', pluginGroup); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages=plugin1', | |
| '--exact-match-only', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[appFacingPackage.path])); | |
| }); | |
| test('specifying the app-facing package of a federated plugin using its ' | |
| 'fully qualified name should include only that package', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1/plugin1.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final Directory pluginGroup = packagesDir.childDirectory('plugin1'); | |
| final RepositoryPackage appFacingPackage = createFakePlugin('plugin1', pluginGroup); | |
| createFakePlugin('plugin1_platform_interface', pluginGroup); | |
| createFakePlugin('plugin1_web', pluginGroup); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--packages=plugin1/plugin1', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[appFacingPackage.path])); | |
| }); | |
| test('specifying a package of a federated plugin by its name should ' | |
| 'include only that package', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1/plugin1.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final Directory pluginGroup = packagesDir.childDirectory('plugin1'); | |
| createFakePlugin('plugin1', pluginGroup); | |
| final RepositoryPackage platformInterfacePackage = createFakePlugin( | |
| 'plugin1_platform_interface', | |
| pluginGroup, | |
| ); | |
| createFakePlugin('plugin1_web', pluginGroup); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--packages=plugin1_platform_interface', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[platformInterfacePackage.path])); | |
| }); | |
| test('returns subpackages after the enclosing package', () async { | |
| final SamplePackageCommand localCommand = configureCommand(includeSubpackages: true); | |
| final localRunner = CommandRunner<void>('common_command', 'subpackage testing'); | |
| localRunner.addCommand(localCommand); | |
| final RepositoryPackage package = createFakePackage('apackage', packagesDir); | |
| await runCapturingPrint(localRunner, <String>['sample']); | |
| expect( | |
| localCommand.plugins, | |
| containsAllInOrder(<String>[package.path, getExampleDir(package).path]), | |
| ); | |
| }); | |
| group('conflicting package selection', () { | |
| test('does not allow --packages with --run-on-changed-packages', () async { | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--run-on-changed-packages', '--packages=plugin1'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[contains('Only one of the package selection arguments')]), | |
| ); | |
| }); | |
| test('does not allow --packages with --packages-for-branch', () async { | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--packages-for-branch', '--packages=plugin1'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[contains('Only one of the package selection arguments')]), | |
| ); | |
| }); | |
| test('does not allow --packages with --current-package', () async { | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--current-package', '--packages=plugin1'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[contains('Only one of the package selection arguments')]), | |
| ); | |
| }); | |
| test('does not allow --run-on-changed-packages with --packages-for-branch', () async { | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--packages-for-branch', '--run-on-changed-packages'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[contains('Only one of the package selection arguments')]), | |
| ); | |
| }); | |
| test('does not allow --run-on-dirty-packages with --run-on-staged-packages', () async { | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--run-on-dirty-packages', '--run-on-staged-packages'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[contains('Only one of the package selection arguments')]), | |
| ); | |
| }); | |
| }); | |
| group('current-package', () { | |
| test('throws when run from outside of the packages directory', () async { | |
| packagesDir.fileSystem.currentDirectory = packagesDir.parent; | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--current-package'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains( | |
| '--current-package can only be used within a repository ' | |
| 'package or package group', | |
| ), | |
| ]), | |
| ); | |
| }); | |
| test('throws when run directly in the packages directory', () async { | |
| packagesDir.fileSystem.currentDirectory = packagesDir; | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--current-package'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains( | |
| '--current-package can only be used within a repository ' | |
| 'package or package group', | |
| ), | |
| ]), | |
| ); | |
| }); | |
| test('runs on a package when run from the package directory', () async { | |
| final RepositoryPackage package = createFakePlugin('a_package', packagesDir); | |
| createFakePlugin('another_package', packagesDir); | |
| packagesDir.fileSystem.currentDirectory = package.directory; | |
| await runCapturingPrint(runner, <String>['sample', '--current-package']); | |
| expect(command.plugins, unorderedEquals(<String>[package.path])); | |
| }); | |
| test('runs on a package when run from the third_party/packages directory', () async { | |
| final RepositoryPackage package = createFakePlugin('a_package', thirdPartyPackagesDir); | |
| createFakePlugin('another_package', thirdPartyPackagesDir); | |
| packagesDir.fileSystem.currentDirectory = package.directory; | |
| await runCapturingPrint(runner, <String>['sample', '--current-package']); | |
| expect(command.plugins, unorderedEquals(<String>[package.path])); | |
| }); | |
| test('runs only app-facing package of a federated plugin', () async { | |
| const pluginName = 'foo'; | |
| final Directory groupDir = packagesDir.childDirectory(pluginName); | |
| final RepositoryPackage package = createFakePlugin(pluginName, groupDir); | |
| createFakePlugin('${pluginName}_someplatform', groupDir); | |
| createFakePackage('${pluginName}_platform_interface', groupDir); | |
| packagesDir.fileSystem.currentDirectory = package.directory; | |
| await runCapturingPrint(runner, <String>['sample', '--current-package']); | |
| expect(command.plugins, unorderedEquals(<String>[package.path])); | |
| }); | |
| test('runs on a package when run from a package example directory', () async { | |
| final RepositoryPackage package = createFakePlugin( | |
| 'a_package', | |
| packagesDir, | |
| examples: <String>['a', 'b', 'c'], | |
| ); | |
| createFakePlugin('another_package', packagesDir); | |
| packagesDir.fileSystem.currentDirectory = package.getExamples().first.directory; | |
| await runCapturingPrint(runner, <String>['sample', '--current-package']); | |
| expect(command.plugins, unorderedEquals(<String>[package.path])); | |
| }); | |
| test('runs on a package group when run from the group directory', () async { | |
| final Directory pluginGroup = packagesDir.childDirectory('a_plugin'); | |
| final RepositoryPackage plugin1 = createFakePlugin('a_plugin_foo', pluginGroup); | |
| final RepositoryPackage plugin2 = createFakePlugin('a_plugin_bar', pluginGroup); | |
| createFakePlugin('unrelated_plugin', packagesDir); | |
| packagesDir.fileSystem.currentDirectory = pluginGroup; | |
| await runCapturingPrint(runner, <String>['sample', '--current-package']); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| }); | |
| }); | |
| group('test run-on-changed-packages', () { | |
| test('all plugins should be tested if there are no changes.', () async { | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| }); | |
| test('all plugins should be tested if there are no plugin related changes.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'AUTHORS')), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| }); | |
| test('all plugins should be tested if .ci.yaml changes', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| .ci.yaml | |
| packages/plugin1/CHANGELOG | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains( | |
| 'Running for all packages, since a file has changed ' | |
| 'that could affect the entire repository.', | |
| ), | |
| ]), | |
| ); | |
| }); | |
| test('all plugins should be tested if anything in .ci/ changes', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| .ci/Dockerfile | |
| packages/plugin1/CHANGELOG | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains( | |
| 'Running for all packages, since a file has changed ' | |
| 'that could affect the entire repository.', | |
| ), | |
| ]), | |
| ); | |
| }); | |
| test('all plugins should be tested if anything in script/ changes.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| script/tool/bin/flutter_plugin_tools.dart | |
| packages/plugin1/CHANGELOG | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains( | |
| 'Running for all packages, since a file has changed ' | |
| 'that could affect the entire repository.', | |
| ), | |
| ]), | |
| ); | |
| }); | |
| test('all plugins should be tested if the root analysis options change.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| analysis_options.yaml | |
| packages/plugin1/CHANGELOG | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains( | |
| 'Running for all packages, since a file has changed ' | |
| 'that could affect the entire repository.', | |
| ), | |
| ]), | |
| ); | |
| }); | |
| test('all plugins should be tested if formatting options change.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| .clang-format | |
| packages/plugin1/CHANGELOG | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains( | |
| 'Running for all packages, since a file has changed ' | |
| 'that could affect the entire repository.', | |
| ), | |
| ]), | |
| ); | |
| }); | |
| test('Only changed plugin should be tested.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/plugin1/plugin1.dart')), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('Running for all packages that have diffs relative to "main"'), | |
| ]), | |
| ); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('multiple files in one plugin should also test the plugin', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1.dart | |
| packages/plugin1/ios/plugin1.m | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('multiple plugins changed should test all the changed plugins', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1.dart | |
| packages/plugin2/ios/plugin2.m | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir); | |
| createFakePlugin('plugin3', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path, plugin2.path])); | |
| }); | |
| test( | |
| 'multiple plugins inside the same plugin group changed should output the plugin group name', | |
| () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1/plugin1.dart | |
| packages/plugin1/plugin1_platform_interface/plugin1_platform_interface.dart | |
| packages/plugin1/plugin1_web/plugin1_web.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin( | |
| 'plugin1', | |
| packagesDir.childDirectory('plugin1'), | |
| ); | |
| createFakePlugin('plugin2', packagesDir); | |
| createFakePlugin('plugin3', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }, | |
| ); | |
| test('changing one plugin in a federated group should only include that plugin', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1/plugin1.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin( | |
| 'plugin1', | |
| packagesDir.childDirectory('plugin1'), | |
| ); | |
| createFakePlugin('plugin1_platform_interface', packagesDir.childDirectory('plugin1')); | |
| createFakePlugin('plugin1_web', packagesDir.childDirectory('plugin1')); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('honors --exclude flag', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1.dart | |
| packages/plugin2/ios/plugin2.m | |
| packages/plugin3/plugin3.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin( | |
| 'plugin1', | |
| packagesDir.childDirectory('plugin1'), | |
| ); | |
| createFakePlugin('plugin2', packagesDir); | |
| createFakePlugin('plugin3', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--exclude=plugin2,plugin3', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('honors --filter-packages-to flag', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/plugin1/plugin1.dart | |
| packages/plugin2/ios/plugin2.m | |
| packages/plugin3/plugin3.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin( | |
| 'plugin1', | |
| packagesDir.childDirectory('plugin1'), | |
| ); | |
| createFakePlugin('plugin2', packagesDir); | |
| createFakePlugin('plugin3', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--filter-packages-to=plugin1', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('honors --filter-packages-to flag when a file is changed that makes ' | |
| 'all packages potentially changed', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| .ci.yaml | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin( | |
| 'plugin1', | |
| packagesDir.childDirectory('plugin1'), | |
| ); | |
| createFakePlugin('plugin2', packagesDir); | |
| createFakePlugin('plugin3', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--filter-packages-to=plugin1', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| test('--filter-packages-to handles federated plugin groups', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/a_plugin/a_plugin/lib/foo.dart | |
| packages/a_plugin/a_plugin_impl/lib/foo.dart | |
| packages/a_plugin/a_plugin_platform_interface/lib/foo.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final Directory groupDir = packagesDir.childDirectory('a_plugin'); | |
| final RepositoryPackage plugin1 = createFakePlugin('a_plugin', groupDir); | |
| final RepositoryPackage plugin2 = createFakePlugin('a_plugin_impl', groupDir); | |
| final RepositoryPackage plugin3 = createFakePlugin('a_plugin_platform_interface', groupDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--filter-packages-to=a_plugin', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect( | |
| command.plugins, | |
| unorderedEquals(<String>[plugin1.path, plugin2.path, plugin3.path]), | |
| ); | |
| }); | |
| test('--filter-packages-to and --exclude work together', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| .ci.yaml | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin( | |
| 'plugin1', | |
| packagesDir.childDirectory('plugin1'), | |
| ); | |
| createFakePlugin('plugin2', packagesDir); | |
| createFakePlugin('plugin3', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--filter-packages-to=plugin1,plugin2', | |
| '--exclude=plugin2', | |
| '--base-sha=main', | |
| '--run-on-changed-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| }); | |
| }); | |
| group('test run-on-dirty-packages', () { | |
| test('no packages should be tested if there are no changes.', () async { | |
| createFakePackage('a_package', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--run-on-dirty-packages']); | |
| expect(command.plugins, unorderedEquals(<String>[])); | |
| }); | |
| test('no packages should be tested if there are no plugin related changes.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'AUTHORS')), | |
| ]; | |
| createFakePackage('a_package', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--run-on-dirty-packages']); | |
| expect(command.plugins, unorderedEquals(<String>[])); | |
| }); | |
| test('no packages should be tested even if special repo files change.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| .ci.yaml | |
| .ci/Dockerfile | |
| .clang-format | |
| analysis_options.yaml | |
| script/tool/bin/flutter_plugin_tools.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| createFakePackage('a_package', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--run-on-dirty-packages']); | |
| expect(command.plugins, unorderedEquals(<String>[])); | |
| }); | |
| test('Only changed packages should be tested.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/a_package/lib/a_package.dart')), | |
| ]; | |
| final RepositoryPackage packageA = createFakePackage('a_package', packagesDir); | |
| createFakePlugin('b_package', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--run-on-dirty-packages', | |
| ]); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('Running for all packages that have uncommitted changes'), | |
| ]), | |
| ); | |
| expect(command.plugins, unorderedEquals(<String>[packageA.path])); | |
| }); | |
| test('multiple packages changed should test all the changed packages', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/a_package/lib/a_package.dart | |
| packages/b_package/lib/src/foo.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage packageA = createFakePackage('a_package', packagesDir); | |
| final RepositoryPackage packageB = createFakePackage('b_package', packagesDir); | |
| createFakePackage('c_package', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--run-on-dirty-packages']); | |
| expect(command.plugins, unorderedEquals(<String>[packageA.path, packageB.path])); | |
| }); | |
| test('honors --exclude flag', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/a_package/lib/a_package.dart | |
| packages/b_package/lib/src/foo.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage packageA = createFakePackage('a_package', packagesDir); | |
| createFakePackage('b_package', packagesDir); | |
| createFakePackage('c_package', packagesDir); | |
| await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--exclude=b_package', | |
| '--run-on-dirty-packages', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[packageA.path])); | |
| }); | |
| }); | |
| group('test run-on-staged-packages', () { | |
| test('no packages should be tested if there are no changes.', () async { | |
| createFakePackage('a_package', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--run-on-staged-packages']); | |
| expect(command.plugins, unorderedEquals(<String>[])); | |
| }); | |
| test('Only changed packages should be tested.', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/a_package/lib/a_package.dart')), | |
| ]; | |
| final RepositoryPackage packageA = createFakePackage('a_package', packagesDir); | |
| createFakePlugin('b_package', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--run-on-staged-packages', | |
| ]); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('Running for all packages that have staged changes'), | |
| ]), | |
| ); | |
| expect(command.plugins, unorderedEquals(<String>[packageA.path])); | |
| }); | |
| test('multiple packages changed should test all the changed packages', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| packages/a_package/lib/a_package.dart | |
| packages/b_package/lib/src/foo.dart | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final RepositoryPackage packageA = createFakePackage('a_package', packagesDir); | |
| final RepositoryPackage packageB = createFakePackage('b_package', packagesDir); | |
| createFakePackage('c_package', packagesDir); | |
| await runCapturingPrint(runner, <String>['sample', '--run-on-staged-packages']); | |
| expect(command.plugins, unorderedEquals(<String>[packageA.path, packageB.path])); | |
| }); | |
| }); | |
| }); | |
| group('--packages-for-branch', () { | |
| test('only tests changed packages relative to the merge base on a branch', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/plugin1/plugin1.dart')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-rev-parse'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'a-branch')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-merge-base'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(exitCode: 1), <String>['--is-ancestor']), | |
| FakeProcessInfo(MockProcess(stdout: 'abc123'), <String>[ | |
| '--fork-point', | |
| ]), // finding merge base | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages-for-branch', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('--packages-for-branch: running on branch "a-branch"'), | |
| contains('Running for all packages that have diffs relative to "abc123"'), | |
| ]), | |
| ); | |
| // Ensure that it's diffing against the merge-base. | |
| expect( | |
| gitProcessRunner.recordedCalls, | |
| contains( | |
| const ProcessCall('git-diff', <String>['-z', '--name-only', 'abc123', 'HEAD'], null), | |
| ), | |
| ); | |
| }); | |
| test('only tests changed packages relative to the previous commit on main', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/plugin1/plugin1.dart')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-rev-parse'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'main')), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages-for-branch', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('--packages-for-branch: running on default branch.'), | |
| contains('--packages-for-branch: using parent commit as the diff base'), | |
| contains('Running for all packages that have diffs relative to "HEAD~"'), | |
| ]), | |
| ); | |
| // Ensure that it's diffing against the prior commit. | |
| expect( | |
| gitProcessRunner.recordedCalls, | |
| contains( | |
| const ProcessCall('git-diff', <String>['-z', '--name-only', 'HEAD~', 'HEAD'], null), | |
| ), | |
| ); | |
| }); | |
| test('only tests changed packages relative to the previous commit if ' | |
| 'running on a specific hash from main', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/plugin1/plugin1.dart')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-rev-parse'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'HEAD')), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages-for-branch', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('--packages-for-branch: running on a commit from default branch.'), | |
| contains('--packages-for-branch: using parent commit as the diff base'), | |
| contains('Running for all packages that have diffs relative to "HEAD~"'), | |
| ]), | |
| ); | |
| // Ensure that it's diffing against the prior commit. | |
| expect( | |
| gitProcessRunner.recordedCalls, | |
| contains( | |
| const ProcessCall('git-diff', <String>['-z', '--name-only', 'HEAD~', 'HEAD'], null), | |
| ), | |
| ); | |
| }); | |
| test('only tests changed packages relative to the previous commit if ' | |
| 'running on a specific hash from origin/main', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/plugin1/plugin1.dart')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-rev-parse'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'HEAD')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-merge-base'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(exitCode: 128), <String>[ | |
| '--is-ancestor', | |
| 'HEAD', | |
| 'main', | |
| ]), // Fail with a non-1 exit code for 'main' | |
| FakeProcessInfo(MockProcess(), <String>[ | |
| '--is-ancestor', | |
| 'HEAD', | |
| 'origin/main', | |
| ]), // Succeed for the variant. | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages-for-branch', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('--packages-for-branch: running on a commit from default branch.'), | |
| contains('--packages-for-branch: using parent commit as the diff base'), | |
| contains('Running for all packages that have diffs relative to "HEAD~"'), | |
| ]), | |
| ); | |
| // Ensure that it's diffing against the prior commit. | |
| expect( | |
| gitProcessRunner.recordedCalls, | |
| contains( | |
| const ProcessCall('git-diff', <String>['-z', '--name-only', 'HEAD~', 'HEAD'], null), | |
| ), | |
| ); | |
| }); | |
| test('only tests changed packages relative to the previous commit on master', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/plugin1/plugin1.dart')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-rev-parse'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'master')), | |
| ]; | |
| final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); | |
| createFakePlugin('plugin2', packagesDir); | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'sample', | |
| '--packages-for-branch', | |
| ]); | |
| expect(command.plugins, unorderedEquals(<String>[plugin1.path])); | |
| expect( | |
| output, | |
| containsAllInOrder(<Matcher>[ | |
| contains('--packages-for-branch: running on default branch.'), | |
| contains('--packages-for-branch: using parent commit as the diff base'), | |
| contains('Running for all packages that have diffs relative to "HEAD~"'), | |
| ]), | |
| ); | |
| // Ensure that it's diffing against the prior commit. | |
| expect( | |
| gitProcessRunner.recordedCalls, | |
| contains( | |
| const ProcessCall('git-diff', <String>['-z', '--name-only', 'HEAD~', 'HEAD'], null), | |
| ), | |
| ); | |
| }); | |
| test('throws if getting the branch fails', () async { | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(stdout: 'packages/plugin1/plugin1.dart')), | |
| ]; | |
| gitProcessRunner.mockProcessesForExecutable['git-rev-parse'] = <FakeProcessInfo>[ | |
| FakeProcessInfo(MockProcess(exitCode: 1)), | |
| ]; | |
| Error? commandError; | |
| final List<String> output = await runCapturingPrint( | |
| runner, | |
| <String>['sample', '--packages-for-branch'], | |
| errorHandler: (Error e) { | |
| commandError = e; | |
| }, | |
| ); | |
| expect(commandError, isA<ToolExit>()); | |
| expect(output, containsAllInOrder(<Matcher>[contains('Unable to determine branch')])); | |
| }); | |
| }); | |
| group('sharding', () { | |
| test('distributes evenly when evenly divisible', () async { | |
| final expectedShards = <List<RepositoryPackage>>[ | |
| <RepositoryPackage>[ | |
| createFakePackage('package1', packagesDir), | |
| createFakePackage('package2', packagesDir), | |
| createFakePackage('package3', packagesDir), | |
| ], | |
| <RepositoryPackage>[ | |
| createFakePackage('package4', packagesDir), | |
| createFakePackage('package5', packagesDir), | |
| createFakePackage('package6', packagesDir), | |
| ], | |
| <RepositoryPackage>[ | |
| createFakePackage('package7', packagesDir), | |
| createFakePackage('package8', packagesDir), | |
| createFakePackage('package9', packagesDir), | |
| ], | |
| ]; | |
| for (var i = 0; i < expectedShards.length; ++i) { | |
| final SamplePackageCommand localCommand = configureCommand(); | |
| final localRunner = CommandRunner<void>('common_command', 'Shard testing'); | |
| localRunner.addCommand(localCommand); | |
| await runCapturingPrint(localRunner, <String>[ | |
| 'sample', | |
| '--shardIndex=$i', | |
| '--shardCount=3', | |
| ]); | |
| expect( | |
| localCommand.plugins, | |
| unorderedEquals( | |
| expectedShards[i].map((RepositoryPackage package) => package.path).toList(), | |
| ), | |
| ); | |
| } | |
| }); | |
| test('distributes as evenly as possible when not evenly divisible', () async { | |
| final expectedShards = <List<RepositoryPackage>>[ | |
| <RepositoryPackage>[ | |
| createFakePackage('package1', packagesDir), | |
| createFakePackage('package2', packagesDir), | |
| createFakePackage('package3', packagesDir), | |
| ], | |
| <RepositoryPackage>[ | |
| createFakePackage('package4', packagesDir), | |
| createFakePackage('package5', packagesDir), | |
| createFakePackage('package6', packagesDir), | |
| ], | |
| <RepositoryPackage>[ | |
| createFakePackage('package7', packagesDir), | |
| createFakePackage('package8', packagesDir), | |
| ], | |
| ]; | |
| for (var i = 0; i < expectedShards.length; ++i) { | |
| final SamplePackageCommand localCommand = configureCommand(); | |
| final localRunner = CommandRunner<void>('common_command', 'Shard testing'); | |
| localRunner.addCommand(localCommand); | |
| await runCapturingPrint(localRunner, <String>[ | |
| 'sample', | |
| '--shardIndex=$i', | |
| '--shardCount=3', | |
| ]); | |
| expect( | |
| localCommand.plugins, | |
| unorderedEquals( | |
| expectedShards[i].map((RepositoryPackage package) => package.path).toList(), | |
| ), | |
| ); | |
| } | |
| }); | |
| // In CI (which is the use case for sharding) we often want to run muliple | |
| // commands on the same set of packages, but the exclusion lists for those | |
| // commands may be different. In those cases we still want all the commands | |
| // to operate on a consistent set of plugins. | |
| // | |
| // E.g., some commands require running build-examples in a previous step; | |
| // excluding some plugins from the later step shouldn't change what's tested | |
| // in each shard, as it may no longer align with what was built. | |
| test('counts excluded plugins when sharding', () async { | |
| final expectedShards = <List<RepositoryPackage>>[ | |
| <RepositoryPackage>[ | |
| createFakePackage('package1', packagesDir), | |
| createFakePackage('package2', packagesDir), | |
| createFakePackage('package3', packagesDir), | |
| ], | |
| <RepositoryPackage>[ | |
| createFakePackage('package4', packagesDir), | |
| createFakePackage('package5', packagesDir), | |
| createFakePackage('package6', packagesDir), | |
| ], | |
| <RepositoryPackage>[createFakePackage('package7', packagesDir)], | |
| ]; | |
| // These would be in the last shard, but are excluded. | |
| createFakePackage('package8', packagesDir); | |
| createFakePackage('package9', packagesDir); | |
| for (var i = 0; i < expectedShards.length; ++i) { | |
| final SamplePackageCommand localCommand = configureCommand(); | |
| final localRunner = CommandRunner<void>('common_command', 'Shard testing'); | |
| localRunner.addCommand(localCommand); | |
| await runCapturingPrint(localRunner, <String>[ | |
| 'sample', | |
| '--shardIndex=$i', | |
| '--shardCount=3', | |
| '--exclude=package8,package9', | |
| ]); | |
| expect( | |
| localCommand.plugins, | |
| unorderedEquals( | |
| expectedShards[i].map((RepositoryPackage package) => package.path).toList(), | |
| ), | |
| ); | |
| } | |
| }); | |
| }); | |
| } | |
| class SamplePackageCommand extends PackageCommand { | |
| SamplePackageCommand( | |
| super.packagesDir, { | |
| super.processRunner, | |
| super.platform, | |
| super.gitDir, | |
| this.includeSubpackages = false, | |
| }); | |
| final List<String> plugins = <String>[]; | |
| final bool includeSubpackages; | |
| @override | |
| final String name = 'sample'; | |
| @override | |
| final String description = 'sample command'; | |
| @override | |
| Future<void> run() async { | |
| final Stream<PackageEnumerationEntry> packages = includeSubpackages | |
| ? getTargetPackagesAndSubpackages() | |
| : getTargetPackages(); | |
| await for (final PackageEnumerationEntry entry in packages) { | |
| plugins.add(entry.package.path); | |
| } | |
| } | |
| } |