[tool] Improve check version ci so that it enforces the version in CHANGELOG and pubspec matches. (#3678)
diff --git a/.cirrus.yml b/.cirrus.yml
index 8d992d5..8f61fab 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -6,10 +6,13 @@
env:
INTEGRATION_TEST_PATH: "./packages/integration_test"
CHANNEL: "master" # Default to master when not explicitly set by a task.
+ PLUGIN_TOOLS: "dart run ./script/tool/lib/src/main.dart"
setup_script:
- flutter channel $CHANNEL
- flutter upgrade
- git fetch origin master # To set FETCH_HEAD for "git merge-base" to work
+ - cd script/tool
+ - pub get
# Light-workload Linux tasks.
@@ -24,10 +27,14 @@
- name: plugin_tools_tests
script:
- cd script/tool
- - pub get
- CIRRUS_BUILD_ID=null pub run test
- name: publishable
script:
+ - if [[ "$CIRRUS_BRANCH" == "master" ]]; then
+ - $PLUGIN_TOOLS version-check
+ - else
+ - $PLUGIN_TOOLS version-check --run-on-changed-packages
+ - fi
- ./script/check_publish.sh
- name: format
format_script: ./script/incremental_build.sh format --fail-on-change
diff --git a/script/incremental_build.sh b/script/incremental_build.sh
index 826229b..38a4d2d 100755
--- a/script/incremental_build.sh
+++ b/script/incremental_build.sh
@@ -49,8 +49,5 @@
else
echo running "${ACTIONS[@]}"
(cd "$REPO_DIR" && plugin_tools "${ACTIONS[@]}" --plugins="$CHANGED_PACKAGES" --exclude="$ALL_EXCLUDED" ${PLUGIN_SHARDING[@]})
- echo "Running version check for changed packages"
- # TODO(egarciad): Enable this check once in master.
- # (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools version-check --base_sha="$(get_branch_base_sha)")
fi
fi
diff --git a/script/tool/lib/src/common.dart b/script/tool/lib/src/common.dart
index 08622df..ce4f378 100644
--- a/script/tool/lib/src/common.dart
+++ b/script/tool/lib/src/common.dart
@@ -7,8 +7,12 @@
import 'dart:math';
import 'package:args/command_runner.dart';
+import 'package:colorize/colorize.dart';
import 'package:file/file.dart';
+import 'package:git/git.dart';
+import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
+import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
typedef void Print(Object object);
@@ -140,6 +144,13 @@
return pluginSupportsPlatform(kLinux, entity, fileSystem);
}
+/// Throws a [ToolExit] with `exitCode` and log the `errorMessage` in red.
+void printErrorAndExit({@required String errorMessage, int exitCode = 1}) {
+ final Colorize redError = Colorize(errorMessage)..red();
+ print(redError);
+ throw ToolExit(exitCode);
+}
+
/// Error thrown when a command needs to exit with a non-zero exit code.
class ToolExit extends Error {
ToolExit(this.exitCode);
@@ -152,6 +163,7 @@
this.packagesDir,
this.fileSystem, {
this.processRunner = const ProcessRunner(),
+ this.gitDir,
}) {
argParser.addMultiOption(
_pluginsArg,
@@ -179,12 +191,23 @@
help: 'Exclude packages from this command.',
defaultsTo: <String>[],
);
+ argParser.addFlag(_runOnChangedPackagesArg,
+ help: 'Run the command on changed packages/plugins.\n'
+ 'If the $_pluginsArg is specified, this flag is ignored.\n'
+ 'The packages excluded with $_excludeArg is also excluded even if changed.\n'
+ 'See $_kBaseSha if a custom base is needed to determine the diff.');
+ argParser.addOption(_kBaseSha,
+ help: 'The base sha used to determine git diff. \n'
+ 'This is useful when $_runOnChangedPackagesArg is specified.\n'
+ 'If not specified, merge-base is used as base sha.');
}
static const String _pluginsArg = 'plugins';
static const String _shardIndexArg = 'shardIndex';
static const String _shardCountArg = 'shardCount';
static const String _excludeArg = 'exclude';
+ static const String _runOnChangedPackagesArg = 'run-on-changed-packages';
+ static const String _kBaseSha = 'base-sha';
/// The directory containing the plugin packages.
final Directory packagesDir;
@@ -199,6 +222,11 @@
/// This can be overridden for testing.
final ProcessRunner processRunner;
+ /// The git directory to use. By default it uses the parent directory.
+ ///
+ /// This can be mocked for testing.
+ final GitDir gitDir;
+
int _shardIndex;
int _shardCount;
@@ -273,9 +301,13 @@
/// "client library" package, which declares the API for the plugin, as
/// well as one or more platform-specific implementations.
Stream<Directory> _getAllPlugins() async* {
- final Set<String> plugins = Set<String>.from(argResults[_pluginsArg]);
+ Set<String> plugins = Set<String>.from(argResults[_pluginsArg]);
final Set<String> excludedPlugins =
Set<String>.from(argResults[_excludeArg]);
+ final bool runOnChangedPackages = argResults[_runOnChangedPackagesArg];
+ if (plugins.isEmpty && runOnChangedPackages) {
+ plugins = await _getChangedPackages();
+ }
await for (FileSystemEntity entity
in packagesDir.list(followLinks: false)) {
@@ -363,6 +395,50 @@
(FileSystemEntity entity) => isFlutterPackage(entity, fileSystem))
.cast<Directory>();
}
+
+ /// Retrieve an instance of [GitVersionFinder] based on `_kBaseSha` and [gitDir].
+ ///
+ /// Throws tool exit if [gitDir] nor root directory is a git directory.
+ Future<GitVersionFinder> retrieveVersionFinder() async {
+ final String rootDir = packagesDir.parent.absolute.path;
+ String baseSha = argResults[_kBaseSha];
+
+ GitDir baseGitDir = gitDir;
+ if (baseGitDir == null) {
+ if (!await GitDir.isGitDir(rootDir)) {
+ printErrorAndExit(
+ errorMessage: '$rootDir is not a valid Git repository.',
+ exitCode: 2);
+ }
+ baseGitDir = await GitDir.fromExisting(rootDir);
+ }
+
+ final GitVersionFinder gitVersionFinder =
+ GitVersionFinder(baseGitDir, baseSha);
+ return gitVersionFinder;
+ }
+
+ Future<Set<String>> _getChangedPackages() async {
+ final GitVersionFinder gitVersionFinder = await retrieveVersionFinder();
+
+ final List<String> allChangedFiles =
+ await gitVersionFinder.getChangedFiles();
+ final Set<String> packages = <String>{};
+ allChangedFiles.forEach((String path) {
+ final List<String> pathComponents = path.split('/');
+ final int packagesIndex =
+ pathComponents.indexWhere((String element) => element == 'packages');
+ if (packagesIndex != -1) {
+ packages.add(pathComponents[packagesIndex + 1]);
+ }
+ });
+ if (packages.isNotEmpty) {
+ final String changedPackages = packages.join(',');
+ print(changedPackages);
+ }
+ print('No changed packages.');
+ return packages;
+ }
}
/// A class used to run processes.
@@ -466,3 +542,68 @@
return 'ERROR: Unable to execute "$executable ${args.join(' ')}"$workdir.';
}
}
+
+/// Finding diffs based on `baseGitDir` and `baseSha`.
+class GitVersionFinder {
+ /// Constructor
+ GitVersionFinder(this.baseGitDir, this.baseSha);
+
+ /// The top level directory of the git repo.
+ ///
+ /// That is where the .git/ folder exists.
+ final GitDir baseGitDir;
+
+ /// The base sha used to get diff.
+ final String baseSha;
+
+ static bool _isPubspec(String file) {
+ return file.trim().endsWith('pubspec.yaml');
+ }
+
+ /// Get a list of all the pubspec.yaml file that is changed.
+ Future<List<String>> getChangedPubSpecs() async {
+ return (await getChangedFiles()).where(_isPubspec).toList();
+ }
+
+ /// Get a list of all the changed files.
+ Future<List<String>> getChangedFiles() async {
+ final String baseSha = await _getBaseSha();
+ final io.ProcessResult changedFilesCommand = await baseGitDir
+ .runCommand(<String>['diff', '--name-only', '$baseSha', 'HEAD']);
+ print('Determine diff with base sha: $baseSha');
+ final String changedFilesStdout = changedFilesCommand.stdout.toString() ?? '';
+ if (changedFilesStdout.isEmpty) {
+ return <String>[];
+ }
+ final List<String> changedFiles = changedFilesStdout
+ .split('\n')
+ ..removeWhere((element) => element.isEmpty);
+ return changedFiles.toList();
+ }
+
+ /// Get the package version specified in the pubspec file in `pubspecPath` and at the revision of `gitRef`.
+ Future<Version> getPackageVersion(String pubspecPath, String gitRef) async {
+ final io.ProcessResult gitShow =
+ await baseGitDir.runCommand(<String>['show', '$gitRef:$pubspecPath']);
+ final String fileContent = gitShow.stdout;
+ final String versionString = loadYaml(fileContent)['version'];
+ return versionString == null ? null : Version.parse(versionString);
+ }
+
+ Future<String> _getBaseSha() async {
+ if (baseSha != null && baseSha.isNotEmpty) {
+ return baseSha;
+ }
+
+ io.ProcessResult baseShaFromMergeBase = await baseGitDir.runCommand(
+ <String>['merge-base', '--fork-point', 'FETCH_HEAD', 'HEAD'],
+ throwOnError: false);
+ if (baseShaFromMergeBase == null ||
+ baseShaFromMergeBase.stderr != null ||
+ baseShaFromMergeBase.stdout == null) {
+ baseShaFromMergeBase = await baseGitDir
+ .runCommand(<String>['merge-base', 'FETCH_HEAD', 'HEAD']);
+ }
+ return (baseShaFromMergeBase.stdout as String).trim();
+ }
+}
diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart
index 2c6b92b..111239f 100644
--- a/script/tool/lib/src/version_check_command.dart
+++ b/script/tool/lib/src/version_check_command.dart
@@ -6,43 +6,14 @@
import 'dart:io' as io;
import 'package:meta/meta.dart';
-import 'package:colorize/colorize.dart';
import 'package:file/file.dart';
import 'package:git/git.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
-import 'package:yaml/yaml.dart';
import 'common.dart';
-const String _kBaseSha = 'base_sha';
-
-class GitVersionFinder {
- GitVersionFinder(this.baseGitDir, this.baseSha);
-
- final GitDir baseGitDir;
- final String baseSha;
-
- static bool isPubspec(String file) {
- return file.trim().endsWith('pubspec.yaml');
- }
-
- Future<List<String>> getChangedPubSpecs() async {
- final io.ProcessResult changedFilesCommand = await baseGitDir
- .runCommand(<String>['diff', '--name-only', '$baseSha', 'HEAD']);
- final List<String> changedFiles =
- changedFilesCommand.stdout.toString().split('\n');
- return changedFiles.where(isPubspec).toList();
- }
-
- Future<Version> getPackageVersion(String pubspecPath, String gitRef) async {
- final io.ProcessResult gitShow =
- await baseGitDir.runCommand(<String>['show', '$gitRef:$pubspecPath']);
- final String fileContent = gitShow.stdout;
- final String versionString = loadYaml(fileContent)['version'];
- return versionString == null ? null : Version.parse(versionString);
- }
-}
+const String _kBaseSha = 'base-sha';
enum NextVersionType {
BREAKING_MAJOR,
@@ -128,46 +99,28 @@
Directory packagesDir,
FileSystem fileSystem, {
ProcessRunner processRunner = const ProcessRunner(),
- this.gitDir,
- }) : super(packagesDir, fileSystem, processRunner: processRunner) {
- argParser.addOption(_kBaseSha);
- }
-
- /// The git directory to use. By default it uses the parent directory.
- ///
- /// This can be mocked for testing.
- final GitDir gitDir;
+ GitDir gitDir,
+ }) : super(packagesDir, fileSystem,
+ processRunner: processRunner, gitDir: gitDir);
@override
final String name = 'version-check';
@override
final String description =
- 'Checks if the versions of the plugins have been incremented per pub specification.\n\n'
+ 'Checks if the versions of the plugins have been incremented per pub specification.\n'
+ 'Also checks if the latest version in CHANGELOG matches the version in pubspec.\n\n'
'This command requires "pub" and "flutter" to be in your path.';
@override
Future<Null> run() async {
checkSharding();
-
- final String rootDir = packagesDir.parent.absolute.path;
- final String baseSha = argResults[_kBaseSha];
-
- GitDir baseGitDir = gitDir;
- if (baseGitDir == null) {
- if (!await GitDir.isGitDir(rootDir)) {
- print('$rootDir is not a valid Git repository.');
- throw ToolExit(2);
- }
- baseGitDir = await GitDir.fromExisting(rootDir);
- }
-
- final GitVersionFinder gitVersionFinder =
- GitVersionFinder(baseGitDir, baseSha);
+ final GitVersionFinder gitVersionFinder = await retrieveVersionFinder();
final List<String> changedPubspecs =
await gitVersionFinder.getChangedPubSpecs();
+ final String baseSha = argResults[_kBaseSha];
for (final String pubspecPath in changedPubspecs) {
try {
final File pubspecFile = fileSystem.file(pubspecPath);
@@ -194,9 +147,7 @@
final String error = '$pubspecPath incorrectly updated version.\n'
'HEAD: $headVersion, master: $masterVersion.\n'
'Allowed versions: $allowedNextVersions';
- final Colorize redError = Colorize(error)..red();
- print(redError);
- throw ToolExit(1);
+ printErrorAndExit(errorMessage: error);
}
bool isPlatformInterface = pubspec.name.endsWith("_platform_interface");
@@ -205,9 +156,7 @@
NextVersionType.BREAKING_MAJOR) {
final String error = '$pubspecPath breaking change detected.\n'
'Breaking changes to platform interfaces are strongly discouraged.\n';
- final Colorize redError = Colorize(error)..red();
- print(redError);
- throw ToolExit(1);
+ printErrorAndExit(errorMessage: error);
}
} on io.ProcessException {
print('Unable to find pubspec in master for $pubspecPath.'
@@ -215,6 +164,74 @@
}
}
+ await for (Directory plugin in getPlugins()) {
+ await _checkVersionsMatch(plugin);
+ }
+
print('No version check errors found!');
}
+
+ Future<void> _checkVersionsMatch(Directory plugin) async {
+ // get version from pubspec
+ final String packageName = plugin.basename;
+ print('-----------------------------------------');
+ print(
+ 'Checking the first version listed in CHANGELOG.MD matches the version in pubspec.yaml for $packageName.');
+
+ final Pubspec pubspec = _tryParsePubspec(plugin);
+ if (pubspec == null) {
+ final String error = 'Cannot parse version from pubspec.yaml';
+ printErrorAndExit(errorMessage: error);
+ }
+ final Version fromPubspec = pubspec.version;
+
+ // get first version from CHANGELOG
+ final File changelog = plugin.childFile('CHANGELOG.md');
+ final List<String> lines = changelog.readAsLinesSync();
+ String firstLineWithText;
+ final Iterator iterator = lines.iterator;
+ while (iterator.moveNext()) {
+ if ((iterator.current as String).trim().isNotEmpty) {
+ firstLineWithText = iterator.current;
+ break;
+ }
+ }
+ // Remove all leading mark down syntax from the version line.
+ final String versionString = firstLineWithText.split(' ').last;
+ Version fromChangeLog = Version.parse(versionString);
+ if (fromChangeLog == null) {
+ final String error =
+ 'Cannot find version on the first line of ${plugin.path}/CHANGELOG.md';
+ printErrorAndExit(errorMessage: error);
+ }
+
+ if (fromPubspec != fromChangeLog) {
+ final String error = '''
+versions for $packageName in CHANGELOG.md and pubspec.yaml do not match.
+The version in pubspec.yaml is $fromPubspec.
+The first version listed in CHANGELOG.md is $fromChangeLog.
+''';
+ printErrorAndExit(errorMessage: error);
+ }
+ print('${packageName} passed version check');
+ }
+
+ Pubspec _tryParsePubspec(Directory package) {
+ final File pubspecFile = package.childFile('pubspec.yaml');
+
+ try {
+ Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
+ if (pubspec == null) {
+ final String error =
+ 'Failed to parse `pubspec.yaml` at ${pubspecFile.path}';
+ printErrorAndExit(errorMessage: error);
+ }
+ return pubspec;
+ } on Exception catch (exception) {
+ final String error =
+ 'Failed to parse `pubspec.yaml` at ${pubspecFile.path}: $exception}';
+ printErrorAndExit(errorMessage: error);
+ }
+ return null;
+ }
}
diff --git a/script/tool/test/common_test.dart b/script/tool/test/common_test.dart
index b3504c2..0fb3ce7 100644
--- a/script/tool/test/common_test.dart
+++ b/script/tool/test/common_test.dart
@@ -1,6 +1,10 @@
+import 'dart:io';
+
import 'package:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:flutter_plugin_tools/src/common.dart';
+import 'package:git/git.dart';
+import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'util.dart';
@@ -9,8 +13,21 @@
RecordingProcessRunner processRunner;
CommandRunner runner;
List<String> plugins;
+ List<List<String>> gitDirCommands;
+ String gitDiffResponse;
setUp(() {
+ gitDirCommands = <List<String>>[];
+ gitDiffResponse = '';
+ final MockGitDir gitDir = MockGitDir();
+ when(gitDir.runCommand(any)).thenAnswer((Invocation invocation) {
+ gitDirCommands.add(invocation.positionalArguments[0]);
+ final MockProcessResult mockProcessResult = MockProcessResult();
+ if (invocation.positionalArguments[0][0] == 'diff') {
+ when<String>(mockProcessResult.stdout).thenReturn(gitDiffResponse);
+ }
+ return Future<ProcessResult>.value(mockProcessResult);
+ });
initializeFakePackages();
processRunner = RecordingProcessRunner();
plugins = [];
@@ -19,6 +36,7 @@
mockPackagesDir,
mockFileSystem,
processRunner: processRunner,
+ gitDir: gitDir,
);
runner =
CommandRunner<Null>('common_command', 'Test for common functionality');
@@ -73,6 +91,207 @@
]);
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');
+ final Directory plugin2 = createFakePlugin('plugin2');
+ 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 = ".cirrus";
+ final Directory plugin1 = createFakePlugin('plugin1');
+ final Directory plugin2 = createFakePlugin('plugin2');
+ 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');
+ createFakePlugin('plugin2');
+ 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');
+ createFakePlugin('plugin2');
+ 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');
+ final Directory plugin2 = createFakePlugin('plugin2');
+ createFakePlugin('plugin3');
+ 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');
+ createFakePlugin('plugin2');
+ createFakePlugin('plugin3');
+ 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');
+ final Directory plugin2 = createFakePlugin('plugin2');
+ createFakePlugin('plugin3');
+ 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');
+ createFakePlugin('plugin2');
+ createFakePlugin('plugin3');
+ await runner.run(<String>[
+ 'sample',
+ '--exclude=plugin2,plugin3',
+ '--base-sha=master',
+ '--run-on-changed-packages'
+ ]);
+
+ expect(plugins, unorderedEquals(<String>[plugin1.path]));
+ });
+ });
+
+ group('$GitVersionFinder', () {
+ List<List<String>> gitDirCommands;
+ String gitDiffResponse;
+ String mergeBaseResponse;
+ MockGitDir gitDir;
+
+ setUp(() {
+ gitDirCommands = <List<String>>[];
+ gitDiffResponse = '';
+ gitDir = MockGitDir();
+ when(gitDir.runCommand(any)).thenAnswer((Invocation invocation) {
+ gitDirCommands.add(invocation.positionalArguments[0]);
+ final MockProcessResult mockProcessResult = MockProcessResult();
+ if (invocation.positionalArguments[0][0] == 'diff') {
+ when<String>(mockProcessResult.stdout).thenReturn(gitDiffResponse);
+ } else if (invocation.positionalArguments[0][0] == 'merge-base') {
+ when<String>(mockProcessResult.stdout).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');
+ 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');
+ 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');
+ 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(['diff', '--name-only', mergeBaseResponse, 'HEAD']));
+ });
+
+ test('use correct base sha if specified', () async {
+ final String customBaseSha = 'aklsjdcaskf12312';
+ gitDiffResponse = '''
+file1/pubspec.yaml
+file2/file2.cc
+''';
+ final GitVersionFinder finder = GitVersionFinder(gitDir, customBaseSha);
+ await finder.getChangedFiles();
+ verify(gitDir.runCommand(['diff', '--name-only', customBaseSha, 'HEAD']));
+ });
+ });
}
class SamplePluginCommand extends PluginCommand {
@@ -81,7 +300,9 @@
Directory packagesDir,
FileSystem fileSystem, {
ProcessRunner processRunner = const ProcessRunner(),
- }) : super(packagesDir, fileSystem, processRunner: processRunner);
+ GitDir gitDir,
+ }) : super(packagesDir, fileSystem,
+ processRunner: processRunner, gitDir: gitDir);
List<String> plugins_;
@@ -98,3 +319,7 @@
}
}
}
+
+class MockGitDir extends Mock implements GitDir {}
+
+class MockProcessResult extends Mock implements ProcessResult {}
diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart
index ec0000d..1538d9b 100644
--- a/script/tool/test/util.dart
+++ b/script/tool/test/util.dart
@@ -37,6 +37,8 @@
bool isLinuxPlugin = false,
bool isMacOsPlugin = false,
bool isWindowsPlugin = false,
+ bool includeChangeLog = false,
+ bool includeVersion = false,
String parentDirectoryName = '',
}) {
assert(!(withSingleExample && withExamples.isNotEmpty),
@@ -57,7 +59,14 @@
isLinuxPlugin: isLinuxPlugin,
isMacOsPlugin: isMacOsPlugin,
isWindowsPlugin: isWindowsPlugin,
+ includeVersion: includeVersion,
);
+ if (includeChangeLog) {
+ createFakeCHANGELOG(pluginDirectory, '''
+## 0.0.1
+ * Some changes.
+ ''');
+ }
if (withSingleExample) {
final Directory exampleDir = pluginDirectory.childDirectory('example')
@@ -85,6 +94,11 @@
return pluginDirectory;
}
+void createFakeCHANGELOG(Directory parent, String texts) {
+ parent.childFile('CHANGELOG.md').createSync();
+ parent.childFile('CHANGELOG.md').writeAsStringSync(texts);
+}
+
/// Creates a `pubspec.yaml` file with a flutter dependency.
void createFakePubspec(
Directory parent, {
@@ -97,6 +111,7 @@
bool isLinuxPlugin = false,
bool isMacOsPlugin = false,
bool isWindowsPlugin = false,
+ String version = '0.0.1',
}) {
parent.childFile('pubspec.yaml').createSync();
String yaml = '''
@@ -152,8 +167,8 @@
}
if (includeVersion) {
yaml += '''
-version: 0.0.1
-publish_to: none # Hardcoded safeguard to prevent this from somehow being published by a broken test.
+version: $version
+publish_to: http://no_pub_server.com # Hardcoded safeguard to prevent this from somehow being published by a broken test.
''';
}
parent.childFile('pubspec.yaml').writeAsStringSync(yaml);
diff --git a/script/tool/test/version_check_test.dart b/script/tool/test/version_check_test.dart
index b9ace38..ac0d378 100644
--- a/script/tool/test/version_check_test.dart
+++ b/script/tool/test/version_check_test.dart
@@ -2,6 +2,7 @@
import 'dart:io';
import 'package:args/command_runner.dart';
+import 'package:flutter_plugin_tools/src/common.dart';
import 'package:git/git.dart';
import 'package:mockito/mockito.dart';
import "package:test/test.dart";
@@ -74,18 +75,18 @@
});
test('allows valid version', () async {
- createFakePlugin('plugin');
+ createFakePlugin('plugin', includeChangeLog: true, includeVersion: true);
gitDiffResponse = "packages/plugin/pubspec.yaml";
gitShowResponses = <String, String>{
'master:packages/plugin/pubspec.yaml': 'version: 1.0.0',
'HEAD:packages/plugin/pubspec.yaml': 'version: 2.0.0',
};
final List<String> output = await runCapturingPrint(
- runner, <String>['version-check', '--base_sha=master']);
+ runner, <String>['version-check', '--base-sha=master']);
expect(
output,
- orderedEquals(<String>[
+ containsAllInOrder(<String>[
'No version check errors found!',
]),
);
@@ -99,14 +100,14 @@
});
test('denies invalid version', () async {
- createFakePlugin('plugin');
+ createFakePlugin('plugin', includeChangeLog: true, includeVersion: true);
gitDiffResponse = "packages/plugin/pubspec.yaml";
gitShowResponses = <String, String>{
'master:packages/plugin/pubspec.yaml': 'version: 0.0.1',
'HEAD:packages/plugin/pubspec.yaml': 'version: 0.2.0',
};
final Future<List<String>> result = runCapturingPrint(
- runner, <String>['version-check', '--base_sha=master']);
+ runner, <String>['version-check', '--base-sha=master']);
await expectLater(
result,
@@ -122,7 +123,7 @@
});
test('gracefully handles missing pubspec.yaml', () async {
- createFakePlugin('plugin');
+ createFakePlugin('plugin', includeChangeLog: true, includeVersion: true);
gitDiffResponse = "packages/plugin/pubspec.yaml";
mockFileSystem.currentDirectory
.childDirectory('packages')
@@ -130,11 +131,12 @@
.childFile('pubspec.yaml')
.deleteSync();
final List<String> output = await runCapturingPrint(
- runner, <String>['version-check', '--base_sha=master']);
+ runner, <String>['version-check', '--base-sha=master']);
expect(
output,
orderedEquals(<String>[
+ 'Determine diff with base sha: master',
'No version check errors found!',
]),
);
@@ -144,7 +146,8 @@
});
test('allows minor changes to platform interfaces', () async {
- createFakePlugin('plugin_platform_interface');
+ createFakePlugin('plugin_platform_interface',
+ includeChangeLog: true, includeVersion: true);
gitDiffResponse = "packages/plugin_platform_interface/pubspec.yaml";
gitShowResponses = <String, String>{
'master:packages/plugin_platform_interface/pubspec.yaml':
@@ -153,10 +156,10 @@
'version: 1.1.0',
};
final List<String> output = await runCapturingPrint(
- runner, <String>['version-check', '--base_sha=master']);
+ runner, <String>['version-check', '--base-sha=master']);
expect(
output,
- orderedEquals(<String>[
+ containsAllInOrder(<String>[
'No version check errors found!',
]),
);
@@ -172,7 +175,8 @@
});
test('disallows breaking changes to platform interfaces', () async {
- createFakePlugin('plugin_platform_interface');
+ createFakePlugin('plugin_platform_interface',
+ includeChangeLog: true, includeVersion: true);
gitDiffResponse = "packages/plugin_platform_interface/pubspec.yaml";
gitShowResponses = <String, String>{
'master:packages/plugin_platform_interface/pubspec.yaml':
@@ -181,7 +185,7 @@
'version: 2.0.0',
};
final Future<List<String>> output = runCapturingPrint(
- runner, <String>['version-check', '--base_sha=master']);
+ runner, <String>['version-check', '--base-sha=master']);
await expectLater(
output,
throwsA(const TypeMatcher<Error>()),
@@ -196,6 +200,138 @@
expect(gitDirCommands[2].join(' '),
equals('show HEAD:packages/plugin_platform_interface/pubspec.yaml'));
});
+
+ test('Allow empty lines in front of the first version in CHANGELOG',
+ () async {
+ createFakePlugin('plugin', includeChangeLog: true, includeVersion: true);
+
+ final Directory pluginDirectory =
+ mockPackagesDir.childDirectory('plugin');
+
+ createFakePubspec(pluginDirectory,
+ isFlutter: true, includeVersion: true, version: '1.0.1');
+ String changelog = '''
+
+
+
+## 1.0.1
+
+* Some changes.
+''';
+ createFakeCHANGELOG(pluginDirectory, changelog);
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['version-check', '--base-sha=master']);
+ await expect(
+ output,
+ containsAllInOrder([
+ 'Checking the first version listed in CHANGELOG.MD matches the version in pubspec.yaml for plugin.',
+ 'plugin passed version check',
+ 'No version check errors found!'
+ ]),
+ );
+ });
+
+ test('Throws if versions in changelog and pubspec do not match', () async {
+ createFakePlugin('plugin', includeChangeLog: true, includeVersion: true);
+
+ final Directory pluginDirectory =
+ mockPackagesDir.childDirectory('plugin');
+
+ createFakePubspec(pluginDirectory,
+ isFlutter: true, includeVersion: true, version: '1.0.1');
+ String changelog = '''
+## 1.0.2
+
+* Some changes.
+''';
+ createFakeCHANGELOG(pluginDirectory, changelog);
+ final Future<List<String>> output = runCapturingPrint(
+ runner, <String>['version-check', '--base-sha=master']);
+ await expectLater(
+ output,
+ throwsA(const TypeMatcher<Error>()),
+ );
+ try {
+ List<String> outputValue = await output;
+ await expectLater(
+ outputValue,
+ containsAllInOrder([
+ '''
+ versions for plugin in CHANGELOG.md and pubspec.yaml do not match.
+ The version in pubspec.yaml is 1.0.1.
+ The first version listed in CHANGELOG.md is 1.0.2.
+ ''',
+ ]),
+ );
+ } on ToolExit catch (_) {}
+ });
+
+ test('Success if CHANGELOG and pubspec versions match', () async {
+ createFakePlugin('plugin', includeChangeLog: true, includeVersion: true);
+
+ final Directory pluginDirectory =
+ mockPackagesDir.childDirectory('plugin');
+
+ createFakePubspec(pluginDirectory,
+ isFlutter: true, includeVersion: true, version: '1.0.1');
+ String changelog = '''
+## 1.0.1
+
+* Some changes.
+''';
+ createFakeCHANGELOG(pluginDirectory, changelog);
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['version-check', '--base-sha=master']);
+ await expect(
+ output,
+ containsAllInOrder([
+ 'Checking the first version listed in CHANGELOG.MD matches the version in pubspec.yaml for plugin.',
+ 'plugin passed version check',
+ 'No version check errors found!'
+ ]),
+ );
+ });
+
+ test(
+ 'Fail if pubspec version only matches an older version listed in CHANGELOG',
+ () async {
+ createFakePlugin('plugin', includeChangeLog: true, includeVersion: true);
+
+ final Directory pluginDirectory =
+ mockPackagesDir.childDirectory('plugin');
+
+ createFakePubspec(pluginDirectory,
+ isFlutter: true, includeVersion: true, version: '1.0.0');
+ String changelog = '''
+## 1.0.1
+
+* Some changes.
+
+## 1.0.0
+
+* Some other changes.
+''';
+ createFakeCHANGELOG(pluginDirectory, changelog);
+ Future<List<String>> output = runCapturingPrint(
+ runner, <String>['version-check', '--base-sha=master']);
+ await expectLater(
+ output,
+ throwsA(const TypeMatcher<Error>()),
+ );
+ try {
+ List<String> outputValue = await output;
+ await expectLater(
+ outputValue,
+ containsAllInOrder([
+ '''
+ versions for plugin in CHANGELOG.md and pubspec.yaml do not match.
+ The version in pubspec.yaml is 1.0.0.
+ The first version listed in CHANGELOG.md is 1.0.1.
+ ''',
+ ]),
+ );
+ } on ToolExit catch (_) {}
+ });
});
group("Pre 1.0", () {