Files
packages/script/tool/test/common/plugin_command_test.dart
stuartmorgan e8b4147fcc Re-sync analysis_options.yaml with flutter/flutter (#5695)
The analysis options have gotten behind; this re-syncs to the current state of flutter/flutter. For options that are non-trivial to enable, either because they are non-trivial to fix, or touch a very large number of files, they are locally disabled with clear "LOCAL CHANGE" markers so that it's obvious where we are out of sync. For options that are simple to resolve, they are enabled in the PR.

Part of https://github.com/flutter/flutter/issues/76229
2022-05-11 11:48:47 -04:00

1004 lines
35 KiB
Dart

// 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/core.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:platform/platform.dart';
import 'package:test/test.dart';
import '../mocks.dart';
import '../util.dart';
import 'plugin_command_test.mocks.dart';
@GenerateMocks(<Type>[GitDir])
void main() {
late RecordingProcessRunner processRunner;
late SamplePluginCommand command;
late CommandRunner<void> runner;
late FileSystem fileSystem;
late MockPlatform mockPlatform;
late Directory packagesDir;
late Directory thirdPartyPackagesDir;
setUp(() {
fileSystem = MemoryFileSystem();
mockPlatform = MockPlatform();
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
thirdPartyPackagesDir = packagesDir.parent
.childDirectory('third_party')
.childDirectory('packages');
final MockGitDir gitDir = MockGitDir();
when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
.thenAnswer((Invocation invocation) {
final List<String> arguments =
invocation.positionalArguments[0]! as List<String>;
// Attach the first argument to the command to make targeting the mock
// results easier.
final String gitCommand = arguments.removeAt(0);
return processRunner.run('git-$gitCommand', arguments);
});
processRunner = RecordingProcessRunner();
command = SamplePluginCommand(
packagesDir,
processRunner: processRunner,
platform: mockPlatform,
gitDir: gitDir,
);
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(
'explicitly specifying the plugin (group) name of a federated plugin '
'should include all plugins in the group', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
packages/plugin1/plugin1/plugin1.dart
'''),
];
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', '--base-sha=main', '--packages=plugin1']);
expect(
command.plugins,
unorderedEquals(<String>[
appFacingPackage.path,
platformInterfacePackage.path,
implementationPackage.path
]));
});
test(
'specifying the app-facing package of a federated plugin using its '
'fully qualified name should include only that package', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 SamplePluginCommand localCommand = SamplePluginCommand(
packagesDir,
processRunner: processRunner,
platform: mockPlatform,
gitDir: MockGitDir(),
includeSubpackages: true,
);
final CommandRunner<void> 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 --packages, --run-on-changed-packages, or '
'--packages-for-branch can be provided.')
]));
});
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 --packages, --run-on-changed-packages, or '
'--packages-for-branch can be provided.')
]));
});
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',
'--packages=plugin1',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Only one of --packages, --run-on-changed-packages, or '
'--packages-for-branch can be provided.')
]));
});
});
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 .cirrus.yml changes.', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
.cirrus.yml
packages/plugin1/CHANGELOG
'''),
];
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
.ci.yaml
packages/plugin1/CHANGELOG
'''),
];
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 anything in .ci/ changes',
() async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
.ci/Dockerfile
packages/plugin1/CHANGELOG
'''),
];
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 anything in script changes.',
() async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
script/tool_runner.sh
packages/plugin1/CHANGELOG
'''),
];
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 the root analysis options change.',
() async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
analysis_options.yaml
packages/plugin1/CHANGELOG
'''),
];
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 formatting options change.',
() async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
.clang-format
packages/plugin1/CHANGELOG
'''),
];
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('Only changed plugin should be tested.', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 changed relative to "main"'),
]));
expect(command.plugins, unorderedEquals(<String>[plugin1.path]));
});
test('multiple files in one plugin should also test the plugin',
() async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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('--exclude flag works with --run-on-changed-packages', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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]));
});
});
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: '''
.cirrus.yml
.ci.yaml
.ci/Dockerfile
.clang-format
analysis_options.yaml
script/tool_runner.sh
'''),
];
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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 {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
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('--packages-for-branch', () {
test('only tests changed packages on a branch', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: 'packages/plugin1/plugin1.dart'),
];
processRunner.mockProcessesForExecutable['git-rev-parse'] = <Process>[
MockProcess(stdout: 'a-branch'),
];
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 changed packages'),
]));
});
test('tests all packages on main', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: 'packages/plugin1/plugin1.dart'),
];
processRunner.mockProcessesForExecutable['git-rev-parse'] = <Process>[
MockProcess(stdout: 'main'),
];
final RepositoryPackage plugin1 =
createFakePlugin('plugin1', packagesDir);
final RepositoryPackage plugin2 =
createFakePlugin('plugin2', packagesDir);
final List<String> output = await runCapturingPrint(
runner, <String>['sample', '--packages-for-branch']);
expect(command.plugins,
unorderedEquals(<String>[plugin1.path, plugin2.path]));
expect(
output,
containsAllInOrder(<Matcher>[
contains('--packages-for-branch: running on all packages'),
]));
});
test('tests all packages on master', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: 'packages/plugin1/plugin1.dart'),
];
processRunner.mockProcessesForExecutable['git-rev-parse'] = <Process>[
MockProcess(stdout: 'master'),
];
final RepositoryPackage plugin1 =
createFakePlugin('plugin1', packagesDir);
final RepositoryPackage plugin2 =
createFakePlugin('plugin2', packagesDir);
final List<String> output = await runCapturingPrint(
runner, <String>['sample', '--packages-for-branch']);
expect(command.plugins,
unorderedEquals(<String>[plugin1.path, plugin2.path]));
expect(
output,
containsAllInOrder(<Matcher>[
contains('--packages-for-branch: running on all packages'),
]));
});
test('throws if getting the branch fails', () async {
processRunner.mockProcessesForExecutable['git-diff'] = <Process>[
MockProcess(stdout: 'packages/plugin1/plugin1.dart'),
];
processRunner.mockProcessesForExecutable['git-rev-parse'] = <Process>[
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('Unabled to determine branch'),
]));
});
});
group('sharding', () {
test('distributes evenly when evenly divisible', () async {
final List<List<RepositoryPackage>> 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 (int i = 0; i < expectedShards.length; ++i) {
final SamplePluginCommand localCommand = SamplePluginCommand(
packagesDir,
processRunner: processRunner,
platform: mockPlatform,
gitDir: MockGitDir(),
);
final CommandRunner<void> 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 List<List<RepositoryPackage>> 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 (int i = 0; i < expectedShards.length; ++i) {
final SamplePluginCommand localCommand = SamplePluginCommand(
packagesDir,
processRunner: processRunner,
platform: mockPlatform,
gitDir: MockGitDir(),
);
final CommandRunner<void> 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 List<List<RepositoryPackage>> 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 (int i = 0; i < expectedShards.length; ++i) {
final SamplePluginCommand localCommand = SamplePluginCommand(
packagesDir,
processRunner: processRunner,
platform: mockPlatform,
gitDir: MockGitDir(),
);
final CommandRunner<void> 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 SamplePluginCommand extends PluginCommand {
SamplePluginCommand(
Directory packagesDir, {
ProcessRunner processRunner = const ProcessRunner(),
Platform platform = const LocalPlatform(),
GitDir? gitDir,
this.includeSubpackages = false,
}) : super(packagesDir,
processRunner: processRunner, platform: platform, gitDir: gitDir);
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);
}
}
}