diff --git a/script/tool/lib/src/publish_plugin_command.dart b/script/tool/lib/src/publish_plugin_command.dart index 5a75ce6af89f..be9e6d300125 100644 --- a/script/tool/lib/src/publish_plugin_command.dart +++ b/script/tool/lib/src/publish_plugin_command.dart @@ -355,11 +355,9 @@ Safe to ignore if the package is deleted in this commit. final String tag = _getTag(packageDir); print('Tagging release $tag...'); if (!getBoolArg(_dryRunFlag)) { - final io.ProcessResult result = await processRunner.run( - 'git', + final io.ProcessResult result = await (await gitDir).runCommand( ['tag', tag], - workingDir: packageDir, - logOnError: true, + throwOnError: false, ); if (result.exitCode != 0) { return false; @@ -400,11 +398,9 @@ Safe to ignore if the package is deleted in this commit. } Future _checkGitStatus(Directory packageDir) async { - final io.ProcessResult statusResult = await processRunner.run( - 'git', + final io.ProcessResult statusResult = await (await gitDir).runCommand( ['status', '--porcelain', '--ignored', packageDir.absolute.path], - workingDir: packageDir, - logOnError: true, + throwOnError: false, ); if (statusResult.exitCode != 0) { return false; @@ -421,11 +417,9 @@ Safe to ignore if the package is deleted in this commit. } Future _verifyRemote(String remote) async { - final io.ProcessResult getRemoteUrlResult = await processRunner.run( - 'git', + final io.ProcessResult getRemoteUrlResult = await (await gitDir).runCommand( ['remote', 'get-url', remote], - workingDir: packagesDir, - logOnError: true, + throwOnError: false, ); if (getRemoteUrlResult.exitCode != 0) { return null; @@ -494,11 +488,9 @@ Safe to ignore if the package is deleted in this commit. } } if (!getBoolArg(_dryRunFlag)) { - final io.ProcessResult result = await processRunner.run( - 'git', + final io.ProcessResult result = await (await gitDir).runCommand( ['push', remote.name, tag], - workingDir: packagesDir, - logOnError: true, + throwOnError: false, ); if (result.exitCode != 0) { return false; diff --git a/script/tool/test/publish_plugin_command_test.dart b/script/tool/test/publish_plugin_command_test.dart index 576d3a4c88c8..40018b6edb61 100644 --- a/script/tool/test/publish_plugin_command_test.dart +++ b/script/tool/test/publish_plugin_command_test.dart @@ -8,34 +8,31 @@ import 'dart:io' as io; import 'package:args/command_runner.dart'; import 'package:file/file.dart'; -import 'package:file/local.dart'; +import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/common/process_runner.dart'; import 'package:flutter_plugin_tools/src/publish_plugin_command.dart'; -import 'package:git/git.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:test/test.dart'; +import 'common/plugin_command_test.mocks.dart'; import 'mocks.dart'; import 'util.dart'; void main() { const String testPluginName = 'foo'; - late Directory testRoot; late Directory packagesDir; late Directory pluginDir; - late GitDir gitDir; + late MockGitDir gitDir; late TestProcessRunner processRunner; + late RecordingProcessRunner gitProcessRunner; late CommandRunner commandRunner; late MockStdin mockStdin; - // This test uses a local file system instead of an in memory one throughout - // so that git actually works. In setup we initialize a mono repo of plugins - // with one package and commit everything to Git. - const FileSystem fileSystem = LocalFileSystem(); + late FileSystem fileSystem; void _createMockCredentialFile() { final String credentialPath = PublishPluginCommand.getCredentialPath(); @@ -45,20 +42,26 @@ void main() { } setUp(() async { - testRoot = fileSystem.systemTempDirectory - .createTempSync('publish_plugin_command_test-'); - // The temp directory can have symbolic links, which won't match git output; - // use a fully resolved version to avoid potential path comparison issues. - testRoot = fileSystem.directory(testRoot.resolveSymbolicLinksSync()); - packagesDir = createPackagesDirectory(parentDir: testRoot); + fileSystem = MemoryFileSystem(); + packagesDir = createPackagesDirectory(fileSystem: fileSystem); + // TODO(stuartmorgan): Move this from setup to individual tests. pluginDir = createFakePlugin(testPluginName, packagesDir, examples: []); assert(pluginDir != null && pluginDir.existsSync()); - io.Process.runSync('git', ['init'], - workingDirectory: testRoot.path); - gitDir = await GitDir.fromExisting(testRoot.path); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Initial commit']); + + gitProcessRunner = RecordingProcessRunner(); + gitDir = MockGitDir(); + when(gitDir.path).thenReturn(packagesDir.parent.path); + when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) + .thenAnswer((Invocation invocation) { + final List arguments = + invocation.positionalArguments[0]! as List; + // Attach the first argument to the command to make targeting the mock + // results easier. + final String gitCommand = arguments.removeAt(0); + return gitProcessRunner.run('git-$gitCommand', arguments); + }); + processRunner = TestProcessRunner(); mockStdin = MockStdin(); commandRunner = CommandRunner('tester', '') @@ -66,10 +69,6 @@ void main() { processRunner: processRunner, stdinput: mockStdin, gitDir: gitDir)); }); - tearDown(() { - testRoot.deleteSync(recursive: true); - }); - group('Initial validation', () { test('requires a package flag', () async { Error? commandError; @@ -79,7 +78,11 @@ void main() { }); expect(commandError, isA()); - expect(output.last, contains('Must specify a package to publish.')); + expect( + output, + containsAllInOrder([ + contains('Must specify a package to publish.'), + ])); }); test('requires an existing flag', () async { @@ -91,11 +94,14 @@ void main() { }); expect(commandError, isA()); - expect(output.last, contains('iamerror does not exist')); + expect(output, + containsAllInOrder([contains('iamerror does not exist')])); }); test('refuses to proceed with dirty files', () async { - pluginDir.childFile('tmp').createSync(); + gitProcessRunner.mockProcessesForExecutable['git-status'] = [ + MockProcess(stdout: '?? ${pluginDir.childFile('tmp').path}\n') + ]; Error? commandError; final List output = await runCapturingPrint( @@ -114,7 +120,7 @@ void main() { containsAllInOrder([ contains('There are files in the package directory that haven\'t ' 'been saved in git. Refusing to publish these files:\n\n' - '?? packages/foo/tmp\n\n' + '?? /packages/foo/tmp\n\n' 'If the directory should be clean, you can run `git clean -xdf && ' 'git reset --hard HEAD` to wipe all local changes.'), contains('Failed, see above for details.'), @@ -122,20 +128,32 @@ void main() { }); test('fails immediately if the remote doesn\'t exist', () async { + gitProcessRunner.mockProcessesForExecutable['git-remote'] = [ + MockProcess(exitCode: 1), + ]; + Error? commandError; - await runCapturingPrint(commandRunner, [ - 'publish-plugin', - '--package', - testPluginName - ], errorHandler: (Error e) { + final List output = await runCapturingPrint(commandRunner, + ['publish-plugin', '--package', testPluginName], + errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); - expect(processRunner.results.last.stderr, contains('No such remote')); + expect( + output, + containsAllInOrder([ + contains( + 'Unable to find URL for remote upstream; cannot push tags'), + ])); }); test("doesn't validate the remote if it's not pushing tags", () async { + // Checking the remote should fail. + gitProcessRunner.mockProcessesForExecutable['git-remote'] = [ + MockProcess(exitCode: 1), + ]; + // Immediately return 0 when running `pub publish`. processRunner.mockPublishCompleteCode = 0; @@ -148,17 +166,18 @@ void main() { '--no-tag-release' ]); - expect(output.last, 'Done!'); + expect( + output, + containsAllInOrder([ + contains('Running `pub publish ` in /packages/$testPluginName...'), + contains('Package published!'), + contains('Released [$testPluginName] successfully.'), + ])); }); test('can publish non-flutter package', () async { const String packageName = 'a_package'; createFakePackage(packageName, packagesDir); - io.Process.runSync('git', ['init'], - workingDirectory: testRoot.path); - gitDir = await GitDir.fromExisting(testRoot.path); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Initial commit']); // Immediately return 0 when running `pub publish`. processRunner.mockPublishCompleteCode = 0; @@ -171,7 +190,15 @@ void main() { '--no-tag-release' ]); - expect(output.last, 'Done!'); + expect( + output, + containsAllInOrder( + [ + contains('Running `pub publish ` in /packages/a_package...'), + contains('Package published!'), + ], + ), + ); }); }); @@ -190,8 +217,12 @@ void main() { '--no-tag-release' ]); - expect(output, contains('Foo')); - expect(output, contains('Bar')); + expect( + output, + containsAllInOrder([ + contains('Foo'), + contains('Bar'), + ])); }); test('forwards input from the user to `pub publish`', () async { @@ -288,7 +319,10 @@ void main() { '--no-tag-release', ]); - expect(processRunner.pushTagsArgs, isEmpty); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); expect( output, containsAllInOrder([ @@ -310,10 +344,10 @@ void main() { '--no-push-tags', ]); - final String? tag = (await gitDir - .runCommand(['show-ref', '$testPluginName-v0.0.1'])) - .stdout as String?; - expect(tag, isNotEmpty); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-tag', ['$testPluginName-v0.0.1'], null))); }); test('only if publishing succeeded', () async { @@ -336,20 +370,14 @@ void main() { containsAllInOrder([ contains('Publish foo failed.'), ])); - final String? tag = (await gitDir.runCommand( - ['show-ref', '$testPluginName-v0.0.1'], - throwOnError: false)) - .stdout as String?; - expect(tag, isEmpty); + expect( + gitProcessRunner.recordedCalls, + isNot(contains( + const ProcessCall('git-tag', ['foo-v0.0.1'], null)))); }); }); group('Pushes tags', () { - setUp(() async { - await gitDir.runCommand( - ['remote', 'add', 'upstream', 'http://localhost:8000']); - }); - test('requires user confirmation', () async { processRunner.mockPublishCompleteCode = 0; mockStdin.readLineOutput = 'help'; @@ -369,7 +397,6 @@ void main() { }); test('to upstream by default', () async { - await gitDir.runCommand(['tag', 'garbage']); processRunner.mockPublishCompleteCode = 0; mockStdin.readLineOutput = 'y'; @@ -380,15 +407,19 @@ void main() { testPluginName, ]); - expect(processRunner.pushTagsArgs.isNotEmpty, isTrue); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], '$testPluginName-v0.0.1'); - expect(output.last, 'Done!'); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall('git-push', + ['upstream', '$testPluginName-v0.0.1'], null))); + expect( + output, + containsAllInOrder([ + contains('Released [$testPluginName] successfully.'), + ])); }); test('does not ask for user input if the --skip-confirmation flag is on', () async { - await gitDir.runCommand(['tag', 'garbage']); processRunner.mockPublishCompleteCode = 0; _createMockCredentialFile(); @@ -400,14 +431,18 @@ void main() { testPluginName, ]); - expect(processRunner.pushTagsArgs.isNotEmpty, isTrue); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], '$testPluginName-v0.0.1'); - expect(output.last, 'Done!'); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall('git-push', + ['upstream', '$testPluginName-v0.0.1'], null))); + expect( + output, + containsAllInOrder([ + contains('Released [$testPluginName] successfully.'), + ])); }); test('to upstream by default, dry run', () async { - await gitDir.runCommand(['tag', 'garbage']); // Immediately return 1 when running `pub publish`. If dry-run does not work, test should throw. processRunner.mockPublishCompleteCode = 1; mockStdin.readLineOutput = 'y'; @@ -415,7 +450,10 @@ void main() { final List output = await runCapturingPrint(commandRunner, ['publish-plugin', '--package', testPluginName, '--dry-run']); - expect(processRunner.pushTagsArgs, isEmpty); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); expect( output, containsAllInOrder([ @@ -428,8 +466,6 @@ void main() { }); test('to different remotes based on a flag', () async { - await gitDir.runCommand( - ['remote', 'add', 'origin', 'http://localhost:8001']); processRunner.mockPublishCompleteCode = 0; mockStdin.readLineOutput = 'y'; @@ -442,10 +478,15 @@ void main() { 'origin', ]); - expect(processRunner.pushTagsArgs.isNotEmpty, isTrue); - expect(processRunner.pushTagsArgs[1], 'origin'); - expect(processRunner.pushTagsArgs[2], '$testPluginName-v0.0.1'); - expect(output.last, 'Done!'); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['origin', '$testPluginName-v0.0.1'], null))); + expect( + output, + containsAllInOrder([ + contains('Released [$testPluginName] successfully.'), + ])); }); test('only if tagging and pushing to remotes are both enabled', () async { @@ -459,20 +500,21 @@ void main() { '--no-tag-release', ]); - expect(processRunner.pushTagsArgs.isEmpty, isTrue); - expect(output.last, 'Done!'); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); + expect( + output, + containsAllInOrder([ + contains('Running `pub publish ` in /packages/$testPluginName...'), + contains('Package published!'), + contains('Released [$testPluginName] successfully.'), + ])); }); }); group('Auto release (all-changed flag)', () { - setUp(() async { - io.Process.runSync('git', ['init'], - workingDirectory: testRoot.path); - gitDir = await GitDir.fromExisting(testRoot.path); - await gitDir.runCommand( - ['remote', 'add', 'upstream', 'http://localhost:8000']); - }); - test('can release newly created plugins', () async { const Map httpResponsePlugin1 = { 'name': 'plugin1', @@ -511,8 +553,11 @@ void main() { 'plugin2', packagesDir.childDirectory('plugin2'), ); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('pubspec.yaml').path}\n' + '${pluginDir2.childFile('pubspec.yaml').path}\n') + ]; // Immediately return 0 when running `pub publish`. processRunner.mockPublishCompleteCode = 0; mockStdin.readLineOutput = 'y'; @@ -530,13 +575,14 @@ void main() { 'Packages released: plugin1, plugin2', 'Done!' ])); - expect(processRunner.pushTagsArgs, isNotEmpty); - expect(processRunner.pushTagsArgs[0], 'push'); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], 'plugin1-v0.0.1'); - expect(processRunner.pushTagsArgs[3], 'push'); - expect(processRunner.pushTagsArgs[4], 'upstream'); - expect(processRunner.pushTagsArgs[5], 'plugin2-v0.0.1'); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'plugin1-v0.0.1'], null))); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'plugin2-v0.0.1'], null))); }); test('can release newly created plugins, while there are existing plugins', @@ -578,11 +624,24 @@ void main() { ); commandRunner.addCommand(command); - // Prepare an exiting plugin and tag it + // The existing plugin. createFakePlugin('plugin0', packagesDir); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); - await gitDir.runCommand(['tag', 'plugin0-v0.0.1']); + // Non-federated + final Directory pluginDir1 = createFakePlugin('plugin1', packagesDir); + // federated + final Directory pluginDir2 = + createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); + + // Git results for plugin0 having been released already, and plugin1 and + // plugin2 being new. + gitProcessRunner.mockProcessesForExecutable['git-tag'] = [ + MockProcess(stdout: 'plugin0-v0.0.1\n') + ]; + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('pubspec.yaml').path}\n' + '${pluginDir2.childFile('pubspec.yaml').path}\n') + ]; // Immediately return 0 when running `pub publish`. processRunner.mockPublishCompleteCode = 0; @@ -591,19 +650,6 @@ void main() { final List output = await runCapturingPrint(commandRunner, ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); - processRunner.pushTagsArgs.clear(); - - // Non-federated - final Directory pluginDir1 = createFakePlugin('plugin1', packagesDir); - // federated - final Directory pluginDir2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); - - output.addAll(await runCapturingPrint(commandRunner, - ['publish-plugin', '--all-changed', '--base-sha=HEAD~'])); - expect( output, containsAllInOrder([ @@ -614,13 +660,14 @@ void main() { 'Packages released: plugin1, plugin2', 'Done!' ])); - expect(processRunner.pushTagsArgs, isNotEmpty); - expect(processRunner.pushTagsArgs[0], 'push'); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], 'plugin1-v0.0.1'); - expect(processRunner.pushTagsArgs[3], 'push'); - expect(processRunner.pushTagsArgs[4], 'upstream'); - expect(processRunner.pushTagsArgs[5], 'plugin2-v0.0.1'); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'plugin1-v0.0.1'], null))); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'plugin2-v0.0.1'], null))); }); test('can release newly created plugins, dry run', () async { @@ -658,10 +705,12 @@ void main() { // federated final Directory pluginDir2 = createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); - // Immediately return 1 when running `pub publish`. If dry-run does not work, test should throw. - processRunner.mockPublishCompleteCode = 1; + + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('pubspec.yaml').path}\n' + '${pluginDir2.childFile('pubspec.yaml').path}\n') + ]; mockStdin.readLineOutput = 'y'; final List output = await runCapturingPrint( @@ -687,18 +736,21 @@ void main() { 'Packages released: plugin1, plugin2', 'Done!' ])); - expect(processRunner.pushTagsArgs, isEmpty); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); }); test('version change triggers releases.', () async { const Map httpResponsePlugin1 = { 'name': 'plugin1', - 'versions': [], + 'versions': ['0.0.1'], }; const Map httpResponsePlugin2 = { 'name': 'plugin2', - 'versions': [], + 'versions': ['0.0.1'], }; final MockClient mockClient = MockClient((http.Request request) async { @@ -722,57 +774,23 @@ void main() { commandRunner.addCommand(command); // Non-federated - final Directory pluginDir1 = createFakePlugin('plugin1', packagesDir); + final Directory pluginDir1 = + createFakePlugin('plugin1', packagesDir, version: '0.0.2'); // federated - final Directory pluginDir2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); + final Directory pluginDir2 = createFakePlugin( + 'plugin2', packagesDir.childDirectory('plugin2'), + version: '0.0.2'); + + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('pubspec.yaml').path}\n' + '${pluginDir2.childFile('pubspec.yaml').path}\n') + ]; + // Immediately return 0 when running `pub publish`. processRunner.mockPublishCompleteCode = 0; mockStdin.readLineOutput = 'y'; - final List output = await runCapturingPrint(commandRunner, - ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - 'Checking local repo...', - 'Local repo is ready!', - 'Running `pub publish ` in ${pluginDir1.path}...\n', - 'Running `pub publish ` in ${pluginDir2.path}...\n', - 'Packages released: plugin1, plugin2', - 'Done!' - ])); - expect(processRunner.pushTagsArgs, isNotEmpty); - expect(processRunner.pushTagsArgs[0], 'push'); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], 'plugin1-v0.0.1'); - expect(processRunner.pushTagsArgs[3], 'push'); - expect(processRunner.pushTagsArgs[4], 'upstream'); - expect(processRunner.pushTagsArgs[5], 'plugin2-v0.0.1'); - - processRunner.pushTagsArgs.clear(); - - final List plugin1Pubspec = - pluginDir1.childFile('pubspec.yaml').readAsLinesSync(); - plugin1Pubspec[plugin1Pubspec.indexWhere( - (String element) => element.contains('version:'))] = 'version: 0.0.2'; - pluginDir1 - .childFile('pubspec.yaml') - .writeAsStringSync(plugin1Pubspec.join('\n')); - final List plugin2Pubspec = - pluginDir2.childFile('pubspec.yaml').readAsLinesSync(); - plugin2Pubspec[plugin2Pubspec.indexWhere( - (String element) => element.contains('version:'))] = 'version: 0.0.2'; - pluginDir2 - .childFile('pubspec.yaml') - .writeAsStringSync(plugin2Pubspec.join('\n')); - await gitDir.runCommand(['add', '-A']); - await gitDir - .runCommand(['commit', '-m', 'Update versions to 0.0.2']); - final List output2 = await runCapturingPrint(commandRunner, ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); expect( @@ -785,14 +803,14 @@ void main() { 'Packages released: plugin1, plugin2', 'Done!' ])); - - expect(processRunner.pushTagsArgs, isNotEmpty); - expect(processRunner.pushTagsArgs[0], 'push'); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], 'plugin1-v0.0.2'); - expect(processRunner.pushTagsArgs[3], 'push'); - expect(processRunner.pushTagsArgs[4], 'upstream'); - expect(processRunner.pushTagsArgs[5], 'plugin2-v0.0.2'); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'plugin1-v0.0.2'], null))); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'plugin2-v0.0.2'], null))); }); test( @@ -800,12 +818,12 @@ void main() { () async { const Map httpResponsePlugin1 = { 'name': 'plugin1', - 'versions': [], + 'versions': ['0.0.1'], }; const Map httpResponsePlugin2 = { 'name': 'plugin2', - 'versions': [], + 'versions': ['0.0.1'], }; final MockClient mockClient = MockClient((http.Request request) async { @@ -829,55 +847,23 @@ void main() { commandRunner.addCommand(command); // Non-federated - final Directory pluginDir1 = createFakePlugin('plugin1', packagesDir); + final Directory pluginDir1 = + createFakePlugin('plugin1', packagesDir, version: '0.0.2'); // federated final Directory pluginDir2 = createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); + pluginDir2.deleteSync(recursive: true); + + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('pubspec.yaml').path}\n' + '${pluginDir2.childFile('pubspec.yaml').path}\n') + ]; + // Immediately return 0 when running `pub publish`. processRunner.mockPublishCompleteCode = 0; mockStdin.readLineOutput = 'y'; - final List output = await runCapturingPrint(commandRunner, - ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); - expect( - output, - containsAllInOrder([ - 'Checking local repo...', - 'Local repo is ready!', - 'Running `pub publish ` in ${pluginDir1.path}...\n', - 'Running `pub publish ` in ${pluginDir2.path}...\n', - 'Packages released: plugin1, plugin2', - 'Done!' - ])); - expect(processRunner.pushTagsArgs, isNotEmpty); - expect(processRunner.pushTagsArgs[0], 'push'); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], 'plugin1-v0.0.1'); - expect(processRunner.pushTagsArgs[3], 'push'); - expect(processRunner.pushTagsArgs[4], 'upstream'); - expect(processRunner.pushTagsArgs[5], 'plugin2-v0.0.1'); - - processRunner.pushTagsArgs.clear(); - - final List plugin1Pubspec = - pluginDir1.childFile('pubspec.yaml').readAsLinesSync(); - plugin1Pubspec[plugin1Pubspec.indexWhere( - (String element) => element.contains('version:'))] = 'version: 0.0.2'; - pluginDir1 - .childFile('pubspec.yaml') - .writeAsStringSync(plugin1Pubspec.join('\n')); - - pluginDir2.deleteSync(recursive: true); - - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand([ - 'commit', - '-m', - 'Update plugin1 versions to 0.0.2, delete plugin2' - ]); - final List output2 = await runCapturingPrint(commandRunner, ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); expect( @@ -890,15 +876,13 @@ void main() { 'Packages released: plugin1', 'Done!' ])); - - expect(processRunner.pushTagsArgs, isNotEmpty); - expect(processRunner.pushTagsArgs.length, 3); - expect(processRunner.pushTagsArgs[0], 'push'); - expect(processRunner.pushTagsArgs[1], 'upstream'); - expect(processRunner.pushTagsArgs[2], 'plugin1-v0.0.2'); + expect( + gitProcessRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'plugin1-v0.0.2'], null))); }); - test('Exiting versions do not trigger release, also prints out message.', + test('Existing versions do not trigger release, also prints out message.', () async { const Map httpResponsePlugin1 = { 'name': 'plugin1', @@ -931,17 +915,23 @@ void main() { commandRunner.addCommand(command); // Non-federated - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); + final Directory pluginDir1 = + createFakePlugin('plugin1', packagesDir, version: '0.0.2'); // federated - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2'), + final Directory pluginDir2 = createFakePlugin( + 'plugin2', packagesDir.childDirectory('plugin2'), version: '0.0.2'); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); - await gitDir.runCommand(['tag', 'plugin1-v0.0.2']); - await gitDir.runCommand(['tag', 'plugin2-v0.0.2']); - // Immediately return 0 when running `pub publish`. - processRunner.mockPublishCompleteCode = 0; - mockStdin.readLineOutput = 'y'; + + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('pubspec.yaml').path}\n' + '${pluginDir2.childFile('pubspec.yaml').path}\n') + ]; + gitProcessRunner.mockProcessesForExecutable['git-tag'] = [ + MockProcess( + stdout: 'plugin1-v0.0.2\n' + 'plugin2-v0.0.2\n') + ]; final List output = await runCapturingPrint(commandRunner, ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); @@ -958,11 +948,14 @@ void main() { 'Done!' ])); - expect(processRunner.pushTagsArgs, isEmpty); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); }); test( - 'Exiting versions do not trigger release, but fail if the tags do not exist.', + 'Existing versions do not trigger release, but fail if the tags do not exist.', () async { const Map httpResponsePlugin1 = { 'name': 'plugin1', @@ -995,27 +988,41 @@ void main() { commandRunner.addCommand(command); // Non-federated - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); + final Directory pluginDir1 = + createFakePlugin('plugin1', packagesDir, version: '0.0.2'); // federated - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2'), + final Directory pluginDir2 = createFakePlugin( + 'plugin2', packagesDir.childDirectory('plugin2'), version: '0.0.2'); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); - // Immediately return 0 when running `pub publish`. - processRunner.mockPublishCompleteCode = 0; - mockStdin.readLineOutput = 'y'; + + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('pubspec.yaml').path}\n' + '${pluginDir2.childFile('pubspec.yaml').path}\n') + ]; Error? commandError; - await runCapturingPrint(commandRunner, [ - 'publish-plugin', - '--all-changed', - '--base-sha=HEAD~' - ], errorHandler: (Error e) { + final List output = await runCapturingPrint(commandRunner, + ['publish-plugin', '--all-changed', '--base-sha=HEAD~'], + errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); - expect(processRunner.pushTagsArgs, isEmpty); + expect( + output, + containsAllInOrder([ + contains('The version 0.0.2 of plugin1 has already been published'), + contains( + 'However, the git release tag for this version (plugin1-v0.0.2) is not found.'), + contains('The version 0.0.2 of plugin2 has already been published'), + contains( + 'However, the git release tag for this version (plugin2-v0.0.2) is not found.'), + ])); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); }); test('No version change does not release any plugins', () async { @@ -1025,20 +1032,11 @@ void main() { final Directory pluginDir2 = createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - io.Process.runSync('git', ['init'], - workingDirectory: testRoot.path); - gitDir = await GitDir.fromExisting(testRoot.path); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); - - pluginDir1.childFile('plugin1.dart').createSync(); - pluginDir2.childFile('plugin2.dart').createSync(); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add dart files']); - - // Immediately return 0 when running `pub publish`. - processRunner.mockPublishCompleteCode = 0; - mockStdin.readLineOutput = 'y'; + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess( + stdout: '${pluginDir1.childFile('plugin1.dart').path}\n' + '${pluginDir2.childFile('plugin2.dart').path}\n') + ]; final List output = await runCapturingPrint(commandRunner, ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); @@ -1051,7 +1049,10 @@ void main() { 'No version updates in this commit.', 'Done!' ])); - expect(processRunner.pushTagsArgs, isEmpty); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); }); test('Do not release flutter_plugin_tools', () async { @@ -1080,11 +1081,9 @@ void main() { final Directory flutterPluginTools = createFakePlugin('flutter_plugin_tools', packagesDir); - await gitDir.runCommand(['add', '-A']); - await gitDir.runCommand(['commit', '-m', 'Add plugins']); - // Immediately return 0 when running `pub publish`. - processRunner.mockPublishCompleteCode = 0; - mockStdin.readLineOutput = 'y'; + gitProcessRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess(stdout: flutterPluginTools.childFile('pubspec.yaml').path) + ]; final List output = await runCapturingPrint(commandRunner, ['publish-plugin', '--all-changed', '--base-sha=HEAD~']); @@ -1101,19 +1100,18 @@ void main() { 'Running `pub publish ` in ${flutterPluginTools.path}...\n', ), isFalse); - expect(processRunner.pushTagsArgs, isEmpty); - processRunner.pushTagsArgs.clear(); + expect( + gitProcessRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); }); }); } class TestProcessRunner extends ProcessRunner { - final List results = []; // Most recent returned publish process. late MockProcess mockPublishProcess; final List mockPublishArgs = []; - final MockProcessResult mockPushTagsResult = MockProcessResult(); - final List pushTagsArgs = []; String? mockPublishStdout; String? mockPublishStderr; @@ -1129,15 +1127,8 @@ class TestProcessRunner extends ProcessRunner { Encoding stdoutEncoding = io.systemEncoding, Encoding stderrEncoding = io.systemEncoding, }) async { - // Don't ever really push tags. - if (executable == 'git' && args.isNotEmpty && args[0] == 'push') { - pushTagsArgs.addAll(args); - return mockPushTagsResult; - } - final io.ProcessResult result = io.Process.runSync(executable, args, workingDirectory: workingDir?.path); - results.add(result); if (result.exitCode != 0) { throw ToolExit(result.exitCode); }