mirror of
https://github.com/flutter/packages.git
synced 2025-05-25 16:58:01 +08:00

Changes to some files (e.g., CI scripts) have the potential to cause failures in any packages, without changes to those packages themselves. This updates the --run-on-changed-packages to consider all packages as changed if any of those files are changed, to avoid issues where a change that changes both some repo-level files and some package-specific files only run presubmit tests on the packages that are directly changed, causing post-submit-only failures. Fixes https://github.com/flutter/flutter/issues/82965
547 lines
19 KiB
Dart
547 lines
19 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:async';
|
|
import 'dart:convert';
|
|
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.dart';
|
|
import 'package:git/git.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/testing.dart';
|
|
import 'package:mockito/annotations.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:pub_semver/pub_semver.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'common_test.mocks.dart';
|
|
import 'util.dart';
|
|
|
|
@GenerateMocks(<Type>[GitDir])
|
|
void main() {
|
|
late RecordingProcessRunner processRunner;
|
|
late CommandRunner<void> runner;
|
|
late FileSystem fileSystem;
|
|
late Directory packagesDir;
|
|
late Directory thirdPartyPackagesDir;
|
|
late List<String> plugins;
|
|
late List<List<String>?> gitDirCommands;
|
|
late String gitDiffResponse;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem();
|
|
packagesDir = fileSystem.currentDirectory.childDirectory('packages');
|
|
thirdPartyPackagesDir = packagesDir.parent
|
|
.childDirectory('third_party')
|
|
.childDirectory('packages');
|
|
|
|
gitDirCommands = <List<String>?>[];
|
|
gitDiffResponse = '';
|
|
final MockGitDir gitDir = MockGitDir();
|
|
when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
|
|
.thenAnswer((Invocation invocation) {
|
|
gitDirCommands.add(invocation.positionalArguments[0] as List<String>?);
|
|
final MockProcessResult mockProcessResult = MockProcessResult();
|
|
if (invocation.positionalArguments[0][0] == 'diff') {
|
|
when<String?>(mockProcessResult.stdout as String?)
|
|
.thenReturn(gitDiffResponse);
|
|
}
|
|
return Future<ProcessResult>.value(mockProcessResult);
|
|
});
|
|
initializeFakePackages(parentDir: packagesDir.parent);
|
|
processRunner = RecordingProcessRunner();
|
|
plugins = <String>[];
|
|
final SamplePluginCommand samplePluginCommand = SamplePluginCommand(
|
|
plugins,
|
|
packagesDir,
|
|
fileSystem,
|
|
processRunner: processRunner,
|
|
gitDir: gitDir,
|
|
);
|
|
runner =
|
|
CommandRunner<void>('common_command', 'Test for common functionality');
|
|
runner.addCommand(samplePluginCommand);
|
|
});
|
|
|
|
test('all plugins from file system', () async {
|
|
final Directory plugin1 =
|
|
createFakePlugin('plugin1', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(<String>['sample']);
|
|
expect(plugins, unorderedEquals(<String>[plugin1.path, plugin2.path]));
|
|
});
|
|
|
|
test('all plugins includes third_party/packages', () async {
|
|
final Directory plugin1 =
|
|
createFakePlugin('plugin1', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
final Directory plugin3 =
|
|
createFakePlugin('plugin3', packagesDirectory: thirdPartyPackagesDir);
|
|
await runner.run(<String>['sample']);
|
|
expect(plugins,
|
|
unorderedEquals(<String>[plugin1.path, plugin2.path, plugin3.path]));
|
|
});
|
|
|
|
test('exclude plugins when plugins flag is specified', () async {
|
|
createFakePlugin('plugin1', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--plugins=plugin1,plugin2', '--exclude=plugin1']);
|
|
expect(plugins, unorderedEquals(<String>[plugin2.path]));
|
|
});
|
|
|
|
test('exclude plugins when plugins flag isn\'t specified', () async {
|
|
createFakePlugin('plugin1', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(<String>['sample', '--exclude=plugin1,plugin2']);
|
|
expect(plugins, unorderedEquals(<String>[]));
|
|
});
|
|
|
|
test('exclude federated plugins when plugins flag is specified', () async {
|
|
createFakePlugin('plugin1',
|
|
parentDirectoryName: 'federated', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(<String>[
|
|
'sample',
|
|
'--plugins=federated/plugin1,plugin2',
|
|
'--exclude=federated/plugin1'
|
|
]);
|
|
expect(plugins, unorderedEquals(<String>[plugin2.path]));
|
|
});
|
|
|
|
test('exclude entire federated plugins when plugins flag is specified',
|
|
() async {
|
|
createFakePlugin('plugin1',
|
|
parentDirectoryName: 'federated', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(<String>[
|
|
'sample',
|
|
'--plugins=federated/plugin1,plugin2',
|
|
'--exclude=federated'
|
|
]);
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[plugin1.path, plugin2.path]));
|
|
});
|
|
|
|
test('Only changed plugin should be tested.', () async {
|
|
gitDiffResponse = 'packages/plugin1/plugin1.dart';
|
|
final Directory plugin1 =
|
|
createFakePlugin('plugin1', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin3', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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',
|
|
parentDirectoryName: 'plugin1', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin3', packagesDirectory: packagesDir);
|
|
await runner.run(
|
|
<String>['sample', '--base-sha=master', '--run-on-changed-packages']);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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',
|
|
parentDirectoryName: 'plugin1', packagesDirectory: packagesDir);
|
|
final Directory plugin2 =
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin3', packagesDirectory: packagesDir);
|
|
await runner.run(<String>[
|
|
'sample',
|
|
'--plugins=plugin1,plugin2',
|
|
'--base-sha=master',
|
|
'--run-on-changed-packages'
|
|
]);
|
|
|
|
expect(plugins, unorderedEquals(<String>[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',
|
|
parentDirectoryName: 'plugin1', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin2', packagesDirectory: packagesDir);
|
|
createFakePlugin('plugin3', packagesDirectory: packagesDir);
|
|
await runner.run(<String>[
|
|
'sample',
|
|
'--exclude=plugin2,plugin3',
|
|
'--base-sha=master',
|
|
'--run-on-changed-packages'
|
|
]);
|
|
|
|
expect(plugins, unorderedEquals(<String>[plugin1.path]));
|
|
});
|
|
});
|
|
|
|
group('$GitVersionFinder', () {
|
|
late List<List<String>?> gitDirCommands;
|
|
late String gitDiffResponse;
|
|
String? mergeBaseResponse;
|
|
late MockGitDir gitDir;
|
|
|
|
setUp(() {
|
|
gitDirCommands = <List<String>?>[];
|
|
gitDiffResponse = '';
|
|
gitDir = MockGitDir();
|
|
when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
|
|
.thenAnswer((Invocation invocation) {
|
|
gitDirCommands.add(invocation.positionalArguments[0] as List<String>?);
|
|
final MockProcessResult mockProcessResult = MockProcessResult();
|
|
if (invocation.positionalArguments[0][0] == 'diff') {
|
|
when<String?>(mockProcessResult.stdout as String?)
|
|
.thenReturn(gitDiffResponse);
|
|
} else if (invocation.positionalArguments[0][0] == 'merge-base') {
|
|
when<String?>(mockProcessResult.stdout as String?)
|
|
.thenReturn(mergeBaseResponse);
|
|
}
|
|
return Future<ProcessResult>.value(mockProcessResult);
|
|
});
|
|
initializeFakePackages();
|
|
processRunner = RecordingProcessRunner();
|
|
});
|
|
|
|
tearDown(() {
|
|
cleanupPackages();
|
|
});
|
|
|
|
test('No git diff should result no files changed', () async {
|
|
final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha');
|
|
final List<String> changedFiles = await finder.getChangedFiles();
|
|
|
|
expect(changedFiles, isEmpty);
|
|
});
|
|
|
|
test('get correct files changed based on git diff', () async {
|
|
gitDiffResponse = '''
|
|
file1/file1.cc
|
|
file2/file2.cc
|
|
''';
|
|
final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha');
|
|
final List<String> changedFiles = await finder.getChangedFiles();
|
|
|
|
expect(
|
|
changedFiles, equals(<String>['file1/file1.cc', 'file2/file2.cc']));
|
|
});
|
|
|
|
test('get correct pubspec change based on git diff', () async {
|
|
gitDiffResponse = '''
|
|
file1/pubspec.yaml
|
|
file2/file2.cc
|
|
''';
|
|
final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha');
|
|
final List<String> changedFiles = await finder.getChangedPubSpecs();
|
|
|
|
expect(changedFiles, equals(<String>['file1/pubspec.yaml']));
|
|
});
|
|
|
|
test('use correct base sha if not specified', () async {
|
|
mergeBaseResponse = 'shaqwiueroaaidf12312jnadf123nd';
|
|
gitDiffResponse = '''
|
|
file1/pubspec.yaml
|
|
file2/file2.cc
|
|
''';
|
|
|
|
final GitVersionFinder finder = GitVersionFinder(gitDir, null);
|
|
await finder.getChangedFiles();
|
|
verify(gitDir.runCommand(
|
|
<String>['diff', '--name-only', mergeBaseResponse!, 'HEAD']));
|
|
});
|
|
|
|
test('use correct base sha if specified', () async {
|
|
const String customBaseSha = 'aklsjdcaskf12312';
|
|
gitDiffResponse = '''
|
|
file1/pubspec.yaml
|
|
file2/file2.cc
|
|
''';
|
|
final GitVersionFinder finder = GitVersionFinder(gitDir, customBaseSha);
|
|
await finder.getChangedFiles();
|
|
verify(gitDir
|
|
.runCommand(<String>['diff', '--name-only', customBaseSha, 'HEAD']));
|
|
});
|
|
});
|
|
|
|
group('$PubVersionFinder', () {
|
|
test('Package does not exist.', () async {
|
|
final MockClient mockClient = MockClient((http.Request request) async {
|
|
return http.Response('', 404);
|
|
});
|
|
final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient);
|
|
final PubVersionFinderResponse response =
|
|
await finder.getPackageVersion(package: 'some_package');
|
|
|
|
expect(response.versions, isNull);
|
|
expect(response.result, PubVersionFinderResult.noPackageFound);
|
|
expect(response.httpResponse!.statusCode, 404);
|
|
expect(response.httpResponse!.body, '');
|
|
});
|
|
|
|
test('HTTP error when getting versions from pub', () async {
|
|
final MockClient mockClient = MockClient((http.Request request) async {
|
|
return http.Response('', 400);
|
|
});
|
|
final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient);
|
|
final PubVersionFinderResponse response =
|
|
await finder.getPackageVersion(package: 'some_package');
|
|
|
|
expect(response.versions, isNull);
|
|
expect(response.result, PubVersionFinderResult.fail);
|
|
expect(response.httpResponse!.statusCode, 400);
|
|
expect(response.httpResponse!.body, '');
|
|
});
|
|
|
|
test('Get a correct list of versions when http response is OK.', () async {
|
|
const Map<String, dynamic> httpResponse = <String, dynamic>{
|
|
'name': 'some_package',
|
|
'versions': <String>[
|
|
'0.0.1',
|
|
'0.0.2',
|
|
'0.0.2+2',
|
|
'0.1.1',
|
|
'0.0.1+1',
|
|
'0.1.0',
|
|
'0.2.0',
|
|
'0.1.0+1',
|
|
'0.0.2+1',
|
|
'2.0.0',
|
|
'1.2.0',
|
|
'1.0.0',
|
|
],
|
|
};
|
|
final MockClient mockClient = MockClient((http.Request request) async {
|
|
return http.Response(json.encode(httpResponse), 200);
|
|
});
|
|
final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient);
|
|
final PubVersionFinderResponse response =
|
|
await finder.getPackageVersion(package: 'some_package');
|
|
|
|
expect(response.versions, <Version>[
|
|
Version.parse('2.0.0'),
|
|
Version.parse('1.2.0'),
|
|
Version.parse('1.0.0'),
|
|
Version.parse('0.2.0'),
|
|
Version.parse('0.1.1'),
|
|
Version.parse('0.1.0+1'),
|
|
Version.parse('0.1.0'),
|
|
Version.parse('0.0.2+2'),
|
|
Version.parse('0.0.2+1'),
|
|
Version.parse('0.0.2'),
|
|
Version.parse('0.0.1+1'),
|
|
Version.parse('0.0.1'),
|
|
]);
|
|
expect(response.result, PubVersionFinderResult.success);
|
|
expect(response.httpResponse!.statusCode, 200);
|
|
expect(response.httpResponse!.body, json.encode(httpResponse));
|
|
});
|
|
});
|
|
}
|
|
|
|
class SamplePluginCommand extends PluginCommand {
|
|
SamplePluginCommand(
|
|
this._plugins,
|
|
Directory packagesDir,
|
|
FileSystem fileSystem, {
|
|
ProcessRunner processRunner = const ProcessRunner(),
|
|
GitDir? gitDir,
|
|
}) : super(packagesDir, fileSystem,
|
|
processRunner: processRunner, gitDir: gitDir);
|
|
|
|
final List<String> _plugins;
|
|
|
|
@override
|
|
final String name = 'sample';
|
|
|
|
@override
|
|
final String description = 'sample command';
|
|
|
|
@override
|
|
Future<void> run() async {
|
|
await for (final Directory package in getPlugins()) {
|
|
_plugins.add(package.path);
|
|
}
|
|
}
|
|
}
|
|
|
|
class MockProcessResult extends Mock implements ProcessResult {}
|