blob: cfec93823ff04a516fc50fcd9071678ce39c8ae8 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' 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/update_release_info_command.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'common/package_command_test.mocks.dart';
import 'mocks.dart';
import 'util.dart';
void main() {
late FileSystem fileSystem;
late Directory packagesDir;
late MockGitDir gitDir;
late RecordingProcessRunner processRunner;
late CommandRunner<void> runner;
setUp(() {
fileSystem = MemoryFileSystem();
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
processRunner = RecordingProcessRunner();
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 a 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);
});
final UpdateReleaseInfoCommand command = UpdateReleaseInfoCommand(
packagesDir,
gitDir: gitDir,
);
runner = CommandRunner<void>(
'update_release_info_command', 'Test for update_release_info_command');
runner.addCommand(command);
});
group('flags', () {
test('fails if --changelog is missing', () async {
Exception? commandError;
await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=next',
], exceptionHandler: (Exception e) {
commandError = e;
});
expect(commandError, isA<UsageException>());
});
test('fails if --changelog is blank', () async {
Exception? commandError;
await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=next',
'--changelog',
'',
], exceptionHandler: (Exception e) {
commandError = e;
});
expect(commandError, isA<UsageException>());
});
test('fails if --version is missing', () async {
Exception? commandError;
await runCapturingPrint(
runner, <String>['update-release-info', '--changelog', ''],
exceptionHandler: (Exception e) {
commandError = e;
});
expect(commandError, isA<UsageException>());
});
test('fails if --version is an unknown value', () async {
Exception? commandError;
await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=foo',
'--changelog',
'',
], exceptionHandler: (Exception e) {
commandError = e;
});
expect(commandError, isA<UsageException>());
});
});
group('changelog', () {
test('adds new NEXT section', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## 1.0.0
* Previous changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=next',
'--changelog',
'A change.'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## NEXT
* A change.
$originalChangelog''';
expect(
output,
containsAllInOrder(<Matcher>[
contains(' Added a NEXT section.'),
]),
);
expect(newChangelog, expectedChangeLog);
});
test('adds to existing NEXT section', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## NEXT
* Already-pending changes.
## 1.0.0
* Old changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=next',
'--changelog',
'A change.'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## NEXT
* A change.
* Already-pending changes.
## 1.0.0
* Old changes.
''';
expect(output,
containsAllInOrder(<Matcher>[contains(' Updated NEXT section.')]));
expect(newChangelog, expectedChangeLog);
});
test('adds new version section', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## 1.0.0
* Previous changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=bugfix',
'--changelog',
'A change.'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## 1.0.1
* A change.
$originalChangelog''';
expect(
output,
containsAllInOrder(<Matcher>[
contains(' Added a 1.0.1 section.'),
]),
);
expect(newChangelog, expectedChangeLog);
});
test('converts existing NEXT section to version section', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## NEXT
* Already-pending changes.
## 1.0.0
* Old changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=bugfix',
'--changelog',
'A change.'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## 1.0.1
* A change.
* Already-pending changes.
## 1.0.0
* Old changes.
''';
expect(output,
containsAllInOrder(<Matcher>[contains(' Updated NEXT section.')]));
expect(newChangelog, expectedChangeLog);
});
test('treats multiple lines as multiple list items', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## 1.0.0
* Previous changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=bugfix',
'--changelog',
'First change.\nSecond change.'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## 1.0.1
* First change.
* Second change.
$originalChangelog''';
expect(newChangelog, expectedChangeLog);
});
test('adds a period to any lines missing it, and removes whitespace',
() async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## 1.0.0
* Previous changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=bugfix',
'--changelog',
'First change \nSecond change'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## 1.0.1
* First change.
* Second change.
$originalChangelog''';
expect(newChangelog, expectedChangeLog);
});
test('handles non-standard changelog format', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
# 1.0.0
* A version with the wrong heading format.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=next',
'--changelog',
'A change.'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## NEXT
* A change.
$originalChangelog''';
expect(output,
containsAllInOrder(<Matcher>[contains(' Added a NEXT section.')]));
expect(newChangelog, expectedChangeLog);
});
test('adds to existing NEXT section using - list style', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## NEXT
- Already-pending changes.
## 1.0.0
- Previous changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=next',
'--changelog',
'A change.'
]);
final String newChangelog = package.changelogFile.readAsStringSync();
const String expectedChangeLog = '''
## NEXT
- A change.
- Already-pending changes.
## 1.0.0
- Previous changes.
''';
expect(output,
containsAllInOrder(<Matcher>[contains(' Updated NEXT section.')]));
expect(newChangelog, expectedChangeLog);
});
test('skips for "minimal" when there are no changes at all', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.1');
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
MockProcess(stdout: '''
packages/different_package/lib/foo.dart
'''),
];
final String originalChangelog = package.changelogFile.readAsStringSync();
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minimal',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '1.0.1');
expect(package.changelogFile.readAsStringSync(), originalChangelog);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No changes to package'),
contains('Skipped 1 package')
]));
});
test('skips for "minimal" when there are only test changes', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.1');
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
MockProcess(stdout: '''
packages/a_package/test/a_test.dart
packages/a_package/example/integration_test/another_test.dart
'''),
];
final String originalChangelog = package.changelogFile.readAsStringSync();
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minimal',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '1.0.1');
expect(package.changelogFile.readAsStringSync(), originalChangelog);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No non-exempt changes to package'),
contains('Skipped 1 package')
]));
});
test('fails if CHANGELOG.md is missing', () async {
createFakePackage('a_package', packagesDir, includeCommonFiles: false);
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minor',
'--changelog',
'A change.',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(output,
containsAllInOrder(<Matcher>[contains(' Missing CHANGELOG.md.')]));
});
test('fails if CHANGELOG.md has unexpected NEXT block format', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
const String originalChangelog = '''
## NEXT
Some free-form text that isn't a list.
## 1.0.0
- Previous changes.
''';
package.changelogFile.writeAsStringSync(originalChangelog);
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minor',
'--changelog',
'A change.',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(' Existing NEXT section has unrecognized format.')
]));
});
});
group('pubspec', () {
test('does not change for --next', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.0');
await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=next',
'--changelog',
'A change.'
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '1.0.0');
});
test('updates bugfix version for pre-1.0 without existing build number',
() async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '0.1.0');
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=bugfix',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '0.1.0+1');
expect(
output,
containsAllInOrder(
<Matcher>[contains(' Incremented version to 0.1.0+1')]));
});
test('updates bugfix version for pre-1.0 with existing build number',
() async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '0.1.0+2');
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=bugfix',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '0.1.0+3');
expect(
output,
containsAllInOrder(
<Matcher>[contains(' Incremented version to 0.1.0+3')]));
});
test('updates bugfix version for post-1.0', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.1');
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=bugfix',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '1.0.2');
expect(
output,
containsAllInOrder(
<Matcher>[contains(' Incremented version to 1.0.2')]));
});
test('updates minor version for pre-1.0', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '0.1.0+2');
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minor',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '0.1.1');
expect(
output,
containsAllInOrder(
<Matcher>[contains(' Incremented version to 0.1.1')]));
});
test('updates minor version for post-1.0', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.1');
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minor',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '1.1.0');
expect(
output,
containsAllInOrder(
<Matcher>[contains(' Incremented version to 1.1.0')]));
});
test('updates bugfix version for "minimal" with publish-worthy changes',
() async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.1');
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
MockProcess(stdout: '''
packages/a_package/lib/plugin.dart
'''),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minimal',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '1.0.2');
expect(
output,
containsAllInOrder(
<Matcher>[contains(' Incremented version to 1.0.2')]));
});
test('no version change for "minimal" with non-publish-worthy changes',
() async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir, version: '1.0.1');
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
MockProcess(stdout: '''
packages/a_package/test/plugin_test.dart
'''),
];
await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minimal',
'--changelog',
'A change.',
]);
final String version = package.parsePubspec().version?.toString() ?? '';
expect(version, '1.0.1');
});
test('fails if there is no version in pubspec', () async {
createFakePackage('a_package', packagesDir, version: null);
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'update-release-info',
'--version=minor',
'--changelog',
'A change.',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(
<Matcher>[contains('Could not determine current version.')]));
});
});
}