| // 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:convert'; |
| import 'dart:io' as 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/version_check_command.dart'; |
| import 'package:http/http.dart' as http; |
| import 'package:http/testing.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:pub_semver/pub_semver.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'common/package_command_test.mocks.dart'; |
| import 'mocks.dart'; |
| import 'util.dart'; |
| |
| void testAllowedVersion( |
| String mainVersion, |
| String headVersion, { |
| bool allowed = true, |
| NextVersionType? nextVersionType, |
| }) { |
| final Version main = Version.parse(mainVersion); |
| final Version head = Version.parse(headVersion); |
| final Map<Version, NextVersionType> allowedVersions = |
| getAllowedNextVersions(main, newVersion: head); |
| if (allowed) { |
| expect(allowedVersions, contains(head)); |
| if (nextVersionType != null) { |
| expect(allowedVersions[head], equals(nextVersionType)); |
| } |
| } else { |
| expect(allowedVersions, isNot(contains(head))); |
| } |
| } |
| |
| class MockProcessResult extends Mock implements io.ProcessResult {} |
| |
| void main() { |
| const String indentation = ' '; |
| group('VersionCheckCommand', () { |
| late FileSystem fileSystem; |
| late MockPlatform mockPlatform; |
| late Directory packagesDir; |
| late CommandRunner<void> runner; |
| late RecordingProcessRunner processRunner; |
| late MockGitDir gitDir; |
| // Ignored if mockHttpResponse is set. |
| int mockHttpStatus; |
| Map<String, dynamic>? mockHttpResponse; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem(); |
| mockPlatform = MockPlatform(); |
| packagesDir = createPackagesDirectory(fileSystem: fileSystem); |
| |
| gitDir = MockGitDir(); |
| when(gitDir.path).thenReturn(packagesDir.parent.path); |
| when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) |
| .thenAnswer((Invocation invocation) { |
| final List<String> arguments = |
| invocation.positionalArguments[0]! as List<String>; |
| // Route git calls through the process runner, to make mock output |
| // consistent with other processes. 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); |
| }); |
| |
| // Default to simulating the plugin never having been published. |
| mockHttpStatus = 404; |
| mockHttpResponse = null; |
| final MockClient mockClient = MockClient((http.Request request) async { |
| return http.Response(json.encode(mockHttpResponse), |
| mockHttpResponse == null ? mockHttpStatus : 200); |
| }); |
| |
| processRunner = RecordingProcessRunner(); |
| final VersionCheckCommand command = VersionCheckCommand(packagesDir, |
| processRunner: processRunner, |
| platform: mockPlatform, |
| gitDir: gitDir, |
| httpClient: mockClient); |
| |
| runner = CommandRunner<void>( |
| 'version_check_command', 'Test for $VersionCheckCommand'); |
| runner.addCommand(command); |
| }); |
| |
| test('allows valid version', () async { |
| createFakePlugin('plugin', packagesDir, version: '2.0.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('1.0.0 -> 2.0.0'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('denies invalid version', () async { |
| createFakePlugin('plugin', packagesDir, version: '0.2.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 0.0.1')), |
| ]; |
| Error? commandError; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Incorrectly updated version.'), |
| ])); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('uses merge-base without explicit base-sha', () async { |
| createFakePlugin('plugin', packagesDir, version: '2.0.0'); |
| processRunner.mockProcessesForExecutable['git-merge-base'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'abc123')), |
| FakeProcessInfo(MockProcess(stdout: 'abc123')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| final List<String> output = |
| await runCapturingPrint(runner, <String>['version-check']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('1.0.0 -> 2.0.0'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-merge-base', |
| <String>['--fork-point', 'FETCH_HEAD', 'HEAD'], null), |
| ProcessCall('git-show', |
| <String>['abc123:packages/plugin/pubspec.yaml'], null), |
| ])); |
| }); |
| |
| test('allows valid version for new package.', () async { |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| final List<String> output = |
| await runCapturingPrint(runner, <String>['version-check']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('Unable to find previous version at git base.'), |
| ]), |
| ); |
| }); |
| |
| test('allows likely reverts.', () async { |
| createFakePlugin('plugin', packagesDir, version: '0.6.1'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 0.6.2')), |
| ]; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('New version is lower than previous version. ' |
| 'This is assumed to be a revert.'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('denies lower version that could not be a simple revert', () async { |
| createFakePlugin('plugin', packagesDir, version: '0.5.1'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 0.6.2')), |
| ]; |
| |
| Error? commandError; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Incorrectly updated version.'), |
| ])); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('allows minor changes to platform interfaces', () async { |
| createFakePlugin('plugin_platform_interface', packagesDir, |
| version: '1.1.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('1.0.0 -> 1.1.0'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', |
| <String>[ |
| 'main:packages/plugin_platform_interface/pubspec.yaml' |
| ], |
| null) |
| ])); |
| }); |
| |
| test('disallows breaking changes to platform interfaces by default', |
| () async { |
| createFakePlugin('plugin_platform_interface', packagesDir, |
| version: '2.0.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| Error? commandError; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains( |
| ' Breaking changes to platform interfaces are not allowed ' |
| 'without explicit justification.\n' |
| ' See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages ' |
| 'for more information.'), |
| ])); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', |
| <String>[ |
| 'main:packages/plugin_platform_interface/pubspec.yaml' |
| ], |
| null) |
| ])); |
| }); |
| |
| test('allows breaking changes to platform interfaces with override label', |
| () async { |
| createFakePlugin('plugin_platform_interface', packagesDir, |
| version: '2.0.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| |
| final List<String> output = await runCapturingPrint(runner, <String>[ |
| 'version-check', |
| '--base-sha=main', |
| '--pr-labels=some label,override: allow breaking change,another-label' |
| ]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Allowing breaking change to plugin_platform_interface ' |
| 'due to the "override: allow breaking change" label.'), |
| contains('Ran for 1 package(s) (1 with warnings)'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', |
| <String>[ |
| 'main:packages/plugin_platform_interface/pubspec.yaml' |
| ], |
| null) |
| ])); |
| }); |
| |
| test('allows breaking changes to platform interfaces with bypass flag', |
| () async { |
| createFakePlugin('plugin_platform_interface', packagesDir, |
| version: '2.0.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| final List<String> output = await runCapturingPrint(runner, <String>[ |
| 'version-check', |
| '--base-sha=main', |
| '--ignore-platform-interface-breaks' |
| ]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Allowing breaking change to plugin_platform_interface due ' |
| 'to --ignore-platform-interface-breaks'), |
| contains('Ran for 1 package(s) (1 with warnings)'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall( |
| 'git-show', |
| <String>[ |
| 'main:packages/plugin_platform_interface/pubspec.yaml' |
| ], |
| null) |
| ])); |
| }); |
| |
| test('Allow empty lines in front of the first version in CHANGELOG', |
| () async { |
| const String version = '1.0.1'; |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: version); |
| const String changelog = ''' |
| |
| ## $version |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test('Throws if versions in changelog and pubspec do not match', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.1'); |
| const String changelog = ''' |
| ## 1.0.2 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| Error? commandError; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main', '--against-pub'], |
| errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Versions in CHANGELOG.md and pubspec.yaml do not match.'), |
| ]), |
| ); |
| }); |
| |
| test('Success if CHANGELOG and pubspec versions match', () async { |
| const String version = '1.0.1'; |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: version); |
| |
| const String changelog = ''' |
| ## $version |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test( |
| 'Fail if pubspec version only matches an older version listed in CHANGELOG', |
| () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.1 |
| * Some changes. |
| ## 1.0.0 |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| bool hasError = false; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main', '--against-pub'], |
| errorHandler: (Error e) { |
| expect(e, isA<ToolExit>()); |
| hasError = true; |
| }); |
| expect(hasError, isTrue); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Versions in CHANGELOG.md and pubspec.yaml do not match.'), |
| ]), |
| ); |
| }); |
| |
| test('Allow NEXT as a placeholder for gathering CHANGELOG entries', |
| () async { |
| const String version = '1.0.0'; |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: version); |
| |
| const String changelog = ''' |
| ## NEXT |
| * Some changes that won't be published until the next time there's a release. |
| ## $version |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('Found NEXT; validating next version in the CHANGELOG.'), |
| ]), |
| ); |
| }); |
| |
| test('Fail if NEXT appears after a version', () async { |
| const String version = '1.0.1'; |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: version); |
| |
| const String changelog = ''' |
| ## $version |
| * Some changes. |
| ## NEXT |
| * Some changes that should have been folded in 1.0.1. |
| ## 1.0.0 |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| bool hasError = false; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main', '--against-pub'], |
| errorHandler: (Error e) { |
| expect(e, isA<ToolExit>()); |
| hasError = true; |
| }); |
| expect(hasError, isTrue); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('When bumping the version for release, the NEXT section ' |
| "should be incorporated into the new version's release notes.") |
| ]), |
| ); |
| }); |
| |
| test('Fail if NEXT is left in the CHANGELOG when adding a version bump', |
| () async { |
| const String version = '1.0.1'; |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: version); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| |
| const String changelog = ''' |
| ## NEXT |
| * Some changes that should have been folded in 1.0.1. |
| ## $version |
| * Some changes. |
| ## 1.0.0 |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| |
| bool hasError = false; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| expect(e, isA<ToolExit>()); |
| hasError = true; |
| }); |
| expect(hasError, isTrue); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('When bumping the version for release, the NEXT section ' |
| "should be incorporated into the new version's release notes."), |
| contains('plugin:\n' |
| ' CHANGELOG.md failed validation.'), |
| ]), |
| ); |
| }); |
| |
| test('fails if the version increases without replacing NEXT', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.1'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| |
| const String changelog = ''' |
| ## NEXT |
| * Some changes that should be listed as part of 1.0.1. |
| ## 1.0.0 |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| |
| bool hasError = false; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| expect(e, isA<ToolExit>()); |
| hasError = true; |
| }); |
| expect(hasError, isTrue); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('When bumping the version for release, the NEXT section ' |
| "should be incorporated into the new version's release notes.") |
| ]), |
| ); |
| }); |
| |
| test('allows NEXT for a revert', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## NEXT |
| * Some changes that should be listed as part of 1.0.1. |
| ## 1.0.0 |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.1')), |
| ]; |
| |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('New version is lower than previous version. ' |
| 'This is assumed to be a revert.'), |
| ]), |
| ); |
| }); |
| |
| // This handles imports of a package with a NEXT section. |
| test('allows NEXT for a new package', () async { |
| final RepositoryPackage plugin = |
| createFakePackage('a_package', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## NEXT |
| * Some changes that should be listed in the next release. |
| ## 1.0.0 |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Unable to find previous version at git base'), |
| contains('Found NEXT; validating next version in the CHANGELOG'), |
| ]), |
| ); |
| }); |
| |
| test( |
| 'fails gracefully if the version headers are not found due to using the wrong style', |
| () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## NEXT |
| * Some changes for a later release. |
| # 1.0.0 |
| * Some other changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| |
| Error? commandError; |
| final List<String> output = await runCapturingPrint(runner, <String>[ |
| 'version-check', |
| '--base-sha=main', |
| ], errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Unable to find a version in CHANGELOG.md'), |
| contains('The current version should be on a line starting with ' |
| '"## ", either on the first non-empty line or after a "## NEXT" ' |
| 'section.'), |
| ]), |
| ); |
| }); |
| |
| test('fails gracefully if the version is unparseable', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## Alpha |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| |
| Error? commandError; |
| final List<String> output = await runCapturingPrint(runner, <String>[ |
| 'version-check', |
| '--base-sha=main', |
| ], errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('"Alpha" could not be parsed as a version.'), |
| ]), |
| ); |
| }); |
| |
| group('missing change detection', () { |
| Future<List<String>> runWithMissingChangeDetection(List<String> extraArgs, |
| {void Function(Error error)? errorHandler}) async { |
| return runCapturingPrint( |
| runner, |
| <String>[ |
| 'version-check', |
| '--base-sha=main', |
| '--check-for-missing-changes', |
| ...extraArgs, |
| ], |
| errorHandler: errorHandler); |
| } |
| |
| test('passes for unchanged packages', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: '')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test( |
| 'fails if a version change is missing from a change that does not ' |
| 'pass the exemption check', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/lib/plugin.dart |
| ''')), |
| ]; |
| |
| Error? commandError; |
| final List<String> output = await runWithMissingChangeDetection( |
| <String>[], errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('No version change found'), |
| contains('plugin:\n' |
| ' Missing version change'), |
| ]), |
| ); |
| }); |
| |
| test('passes version change requirement when version changes', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.1'); |
| |
| const String changelog = ''' |
| ## 1.0.1 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/lib/plugin.dart |
| packages/plugin/CHANGELOG.md |
| packages/plugin/pubspec.yaml |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test('version change check ignores files outside the package', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin_a/lib/plugin.dart |
| tool/plugin/lib/plugin.dart |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test('allows missing version change for exempt changes', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/example/android/.pluginToolsConfig.yaml |
| packages/plugin/example/android/lint-baseline.xml |
| packages/plugin/example/android/src/androidTest/foo/bar/FooTest.java |
| packages/plugin/example/ios/RunnerTests/Foo.m |
| packages/plugin/example/ios/RunnerUITests/info.plist |
| packages/plugin/analysis_options.yaml |
| packages/plugin/CHANGELOG.md |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test('allows missing version change with override label', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/lib/plugin.dart |
| packages/plugin/CHANGELOG.md |
| packages/plugin/pubspec.yaml |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[ |
| '--pr-labels=some label,override: no versioning needed,another-label' |
| ]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Ignoring lack of version change due to the ' |
| '"override: no versioning needed" label.'), |
| ]), |
| ); |
| }); |
| |
| test('fails if a CHANGELOG change is missing', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/example/lib/foo.dart |
| ''')), |
| ]; |
| |
| Error? commandError; |
| final List<String> output = await runWithMissingChangeDetection( |
| <String>[], errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('No CHANGELOG change found'), |
| contains('plugin:\n' |
| ' Missing CHANGELOG change'), |
| ]), |
| ); |
| }); |
| |
| test('passes CHANGELOG check when the CHANGELOG is changed', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/example/lib/foo.dart |
| packages/plugin/CHANGELOG.md |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test('fails CHANGELOG check if only another package CHANGELOG chages', |
| () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/example/lib/foo.dart |
| packages/another_plugin/CHANGELOG.md |
| ''')), |
| ]; |
| |
| Error? commandError; |
| final List<String> output = await runWithMissingChangeDetection( |
| <String>[], errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('No CHANGELOG change found'), |
| ]), |
| ); |
| }); |
| |
| test('allows missing CHANGELOG change with justification', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/example/lib/foo.dart |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[ |
| '--pr-labels=some label,override: no changelog needed,another-label' |
| ]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Ignoring lack of CHANGELOG update due to the ' |
| '"override: no changelog needed" label.'), |
| ]), |
| ); |
| }); |
| |
| // This test ensures that Dependabot Gradle changes to test-only files |
| // aren't flagged by the version check. |
| test( |
| 'allows missing CHANGELOG and version change for test-only Gradle changes', |
| () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| // File list. |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/android/build.gradle |
| ''')), |
| // build.gradle diff |
| FakeProcessInfo(MockProcess(stdout: ''' |
| - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' |
| - testImplementation 'junit:junit:4.10.0' |
| + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' |
| + testImplementation 'junit:junit:4.13.2' |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test( |
| 'allows missing CHANGELOG and version change for dev-only-file changes', |
| () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| // File list. |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/tool/run_tests.dart |
| packages/plugin/run_tests.sh |
| ''')), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test( |
| 'allows missing CHANGELOG and version change for dev-only line-level ' |
| 'changes in production files', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| // File list. |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/lib/plugin.dart |
| ''')), |
| // Dart file diff. |
| FakeProcessInfo(MockProcess(stdout: ''' |
| + // TODO(someone): Fix this. |
| + // ignore: some_lint |
| '''), <String>['main', 'HEAD', '--', 'packages/plugin/lib/plugin.dart']), |
| ]; |
| |
| final List<String> output = |
| await runWithMissingChangeDetection(<String>[]); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| ]), |
| ); |
| }); |
| |
| test('documentation comments are not exempt', () async { |
| final RepositoryPackage plugin = |
| createFakePlugin('plugin', packagesDir, version: '1.0.0'); |
| |
| const String changelog = ''' |
| ## 1.0.0 |
| * Some changes. |
| '''; |
| plugin.changelogFile.writeAsStringSync(changelog); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| processRunner.mockProcessesForExecutable['git-diff'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: ''' |
| packages/plugin/lib/plugin.dart |
| ''')), |
| // Dart file diff. |
| FakeProcessInfo(MockProcess(stdout: ''' |
| + /// Important new information for API clients! |
| '''), <String>['main', 'HEAD', '--', 'packages/plugin/lib/plugin.dart']), |
| ]; |
| |
| Error? commandError; |
| final List<String> output = await runWithMissingChangeDetection( |
| <String>[], errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('No version change found'), |
| contains('plugin:\n' |
| ' Missing version change'), |
| ]), |
| ); |
| }); |
| }); |
| |
| test('allows valid against pub', () async { |
| mockHttpResponse = <String, dynamic>{ |
| 'name': 'some_package', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.0.2', |
| '1.0.0', |
| ], |
| }; |
| |
| createFakePlugin('plugin', packagesDir, version: '2.0.0'); |
| final List<String> output = await runCapturingPrint(runner, |
| <String>['version-check', '--base-sha=main', '--against-pub']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('plugin: Current largest version on pub: 1.0.0'), |
| ]), |
| ); |
| }); |
| |
| test('denies invalid against pub', () async { |
| mockHttpResponse = <String, dynamic>{ |
| 'name': 'some_package', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.0.2', |
| ], |
| }; |
| |
| createFakePlugin('plugin', packagesDir, version: '2.0.0'); |
| |
| bool hasError = false; |
| final List<String> result = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main', '--against-pub'], |
| errorHandler: (Error e) { |
| expect(e, isA<ToolExit>()); |
| hasError = true; |
| }); |
| expect(hasError, isTrue); |
| |
| expect( |
| result, |
| containsAllInOrder(<Matcher>[ |
| contains(''' |
| ${indentation}Incorrectly updated version. |
| ${indentation}HEAD: 2.0.0, pub: 0.0.2. |
| ${indentation}Allowed versions: {1.0.0: NextVersionType.BREAKING_MAJOR, 0.1.0: NextVersionType.MINOR, 0.0.3: NextVersionType.PATCH}''') |
| ]), |
| ); |
| }); |
| |
| test( |
| 'throw and print error message if http request failed when checking against pub', |
| () async { |
| mockHttpStatus = 400; |
| |
| createFakePlugin('plugin', packagesDir, version: '2.0.0'); |
| bool hasError = false; |
| final List<String> result = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main', '--against-pub'], |
| errorHandler: (Error e) { |
| expect(e, isA<ToolExit>()); |
| hasError = true; |
| }); |
| expect(hasError, isTrue); |
| |
| expect( |
| result, |
| containsAllInOrder(<Matcher>[ |
| contains(''' |
| ${indentation}Error fetching version on pub for plugin. |
| ${indentation}HTTP Status 400 |
| ${indentation}HTTP response: null |
| ''') |
| ]), |
| ); |
| }); |
| |
| test('when checking against pub, allow any version if http status is 404.', |
| () async { |
| mockHttpStatus = 404; |
| |
| createFakePlugin('plugin', packagesDir, version: '2.0.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| final List<String> result = await runCapturingPrint(runner, |
| <String>['version-check', '--base-sha=main', '--against-pub']); |
| |
| expect( |
| result, |
| containsAllInOrder(<Matcher>[ |
| contains('Unable to find previous version on pub server.'), |
| ]), |
| ); |
| }); |
| |
| group('prelease versions', () { |
| test( |
| 'allow an otherwise-valid transition that also adds a pre-release component', |
| () async { |
| createFakePlugin('plugin', packagesDir, version: '2.0.0-dev'); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), |
| ]; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('1.0.0 -> 2.0.0-dev'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-show', |
| <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('allow releasing a pre-release', () async { |
| createFakePlugin('plugin', packagesDir, version: '1.2.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.2.0-dev')), |
| ]; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('1.2.0-dev -> 1.2.0'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-show', |
| <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| // Allow abandoning a pre-release version in favor of a different version |
| // change type. |
| test( |
| 'allow an otherwise-valid transition that also removes a pre-release component', |
| () async { |
| createFakePlugin('plugin', packagesDir, version: '2.0.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.2.0-dev')), |
| ]; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('1.2.0-dev -> 2.0.0'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-show', |
| <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('allow changing only the pre-release version', () async { |
| createFakePlugin('plugin', packagesDir, version: '1.2.0-dev.2'); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 1.2.0-dev.1')), |
| ]; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main']); |
| |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Running for plugin'), |
| contains('1.2.0-dev.1 -> 1.2.0-dev.2'), |
| ]), |
| ); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-show', |
| <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('denies invalid version change that also adds a pre-release', |
| () async { |
| createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 0.0.1')), |
| ]; |
| Error? commandError; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Incorrectly updated version.'), |
| ])); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-show', |
| <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('denies invalid version change that also removes a pre-release', |
| () async { |
| createFakePlugin('plugin', packagesDir, version: '0.2.0'); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 0.0.1-dev')), |
| ]; |
| Error? commandError; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Incorrectly updated version.'), |
| ])); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-show', |
| <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| |
| test('denies invalid version change between pre-releases', () async { |
| createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); |
| processRunner.mockProcessesForExecutable['git-show'] = |
| <FakeProcessInfo>[ |
| FakeProcessInfo(MockProcess(stdout: 'version: 0.0.1-dev')), |
| ]; |
| Error? commandError; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['version-check', '--base-sha=main'], |
| errorHandler: (Error e) { |
| commandError = e; |
| }); |
| |
| expect(commandError, isA<ToolExit>()); |
| expect( |
| output, |
| containsAllInOrder(<Matcher>[ |
| contains('Incorrectly updated version.'), |
| ])); |
| expect( |
| processRunner.recordedCalls, |
| containsAllInOrder(const <ProcessCall>[ |
| ProcessCall('git-show', |
| <String>['main:packages/plugin/pubspec.yaml'], null) |
| ])); |
| }); |
| }); |
| }); |
| |
| group('Pre 1.0', () { |
| test('nextVersion allows patch version', () { |
| testAllowedVersion('0.12.0', '0.12.0+1', |
| nextVersionType: NextVersionType.PATCH); |
| testAllowedVersion('0.12.0+4', '0.12.0+5', |
| nextVersionType: NextVersionType.PATCH); |
| }); |
| |
| test('nextVersion does not allow jumping patch', () { |
| testAllowedVersion('0.12.0', '0.12.0+2', allowed: false); |
| testAllowedVersion('0.12.0+2', '0.12.0+4', allowed: false); |
| }); |
| |
| test('nextVersion does not allow going back', () { |
| testAllowedVersion('0.12.0', '0.11.0', allowed: false); |
| testAllowedVersion('0.12.0+2', '0.12.0+1', allowed: false); |
| testAllowedVersion('0.12.0+1', '0.12.0', allowed: false); |
| }); |
| |
| test('nextVersion allows minor version', () { |
| testAllowedVersion('0.12.0', '0.12.1', |
| nextVersionType: NextVersionType.MINOR); |
| testAllowedVersion('0.12.0+4', '0.12.1', |
| nextVersionType: NextVersionType.MINOR); |
| }); |
| |
| test('nextVersion does not allow jumping minor', () { |
| testAllowedVersion('0.12.0', '0.12.2', allowed: false); |
| testAllowedVersion('0.12.0+2', '0.12.3', allowed: false); |
| }); |
| }); |
| |
| group('Releasing 1.0', () { |
| test('nextVersion allows releasing 1.0', () { |
| testAllowedVersion('0.12.0', '1.0.0', |
| nextVersionType: NextVersionType.BREAKING_MAJOR); |
| testAllowedVersion('0.12.0+4', '1.0.0', |
| nextVersionType: NextVersionType.BREAKING_MAJOR); |
| }); |
| |
| test('nextVersion does not allow jumping major', () { |
| testAllowedVersion('0.12.0', '2.0.0', allowed: false); |
| testAllowedVersion('0.12.0+4', '2.0.0', allowed: false); |
| }); |
| |
| test('nextVersion does not allow un-releasing', () { |
| testAllowedVersion('1.0.0', '0.12.0+4', allowed: false); |
| testAllowedVersion('1.0.0', '0.12.0', allowed: false); |
| }); |
| }); |
| |
| group('Post 1.0', () { |
| test('nextVersion allows patch jumps', () { |
| testAllowedVersion('1.0.1', '1.0.2', |
| nextVersionType: NextVersionType.PATCH); |
| testAllowedVersion('1.0.0', '1.0.1', |
| nextVersionType: NextVersionType.PATCH); |
| }); |
| |
| test('nextVersion does not allow build jumps', () { |
| testAllowedVersion('1.0.1', '1.0.1+1', allowed: false); |
| testAllowedVersion('1.0.0+5', '1.0.0+6', allowed: false); |
| }); |
| |
| test('nextVersion does not allow skipping patches', () { |
| testAllowedVersion('1.0.1', '1.0.3', allowed: false); |
| testAllowedVersion('1.0.0', '1.0.6', allowed: false); |
| }); |
| |
| test('nextVersion allows minor version jumps', () { |
| testAllowedVersion('1.0.1', '1.1.0', |
| nextVersionType: NextVersionType.MINOR); |
| testAllowedVersion('1.0.0', '1.1.0', |
| nextVersionType: NextVersionType.MINOR); |
| }); |
| |
| test('nextVersion does not allow skipping minor versions', () { |
| testAllowedVersion('1.0.1', '1.2.0', allowed: false); |
| testAllowedVersion('1.1.0', '1.3.0', allowed: false); |
| }); |
| |
| test('nextVersion allows breaking changes', () { |
| testAllowedVersion('1.0.1', '2.0.0', |
| nextVersionType: NextVersionType.BREAKING_MAJOR); |
| testAllowedVersion('1.0.0', '2.0.0', |
| nextVersionType: NextVersionType.BREAKING_MAJOR); |
| }); |
| |
| test('nextVersion does not allow skipping major versions', () { |
| testAllowedVersion('1.0.1', '3.0.0', allowed: false); |
| testAllowedVersion('1.1.0', '2.3.0', allowed: false); |
| }); |
| }); |
| } |