Files
packages/script/tool/test/update_excerpts_command_test.dart
stuartmorgan fa2e8a0528 [tool] Support code excerpts for any .md file (#4212)
Updates `update-excerpts` to support any top-level .md file (other than CHANGELOG.md), rather than just README.md. This is useful for supplemental content, such as migration guides linked from the main README file.

Also makes some small improvements to the error messaging:
- The list of incorrect files is now relative to, and restricted to, the package. This makes the error message simpler, and ensures that changed files in other packages don't get listed.
- Adds a link to the relevant wiki docs, since this has been a source of confusion for newer contributors.
2023-06-15 13:57:35 +00:00

420 lines
13 KiB
Dart

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '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_excerpts_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 RecordingProcessRunner processRunner;
late CommandRunner<void> runner;
setUp(() {
fileSystem = MemoryFileSystem();
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
final MockGitDir gitDir = MockGitDir();
when(gitDir.path).thenReturn(packagesDir.parent.path);
processRunner = RecordingProcessRunner();
final UpdateExcerptsCommand command = UpdateExcerptsCommand(
packagesDir,
processRunner: processRunner,
platform: MockPlatform(),
gitDir: gitDir,
);
runner = CommandRunner<void>(
'update_excerpts_command', 'Test for update_excerpts_command');
runner.addCommand(command);
});
test('runs pub get before running scripts', () async {
final RepositoryPackage package = createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
final Directory example = getExampleDir(package);
await runCapturingPrint(runner, <String>['update-excerpts']);
expect(
processRunner.recordedCalls,
containsAll(<ProcessCall>[
ProcessCall('dart', const <String>['pub', 'get'], example.path),
ProcessCall(
'dart',
const <String>[
'run',
'build_runner',
'build',
'--config',
'excerpt',
'--output',
'excerpts',
'--delete-conflicting-outputs',
],
example.path),
]));
});
test('runs when config is present', () async {
final RepositoryPackage package = createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
final Directory example = getExampleDir(package);
final List<String> output =
await runCapturingPrint(runner, <String>['update-excerpts']);
expect(
processRunner.recordedCalls,
containsAll(<ProcessCall>[
ProcessCall(
'dart',
const <String>[
'run',
'build_runner',
'build',
'--config',
'excerpt',
'--output',
'excerpts',
'--delete-conflicting-outputs',
],
example.path),
ProcessCall(
'dart',
const <String>[
'run',
'code_excerpt_updater',
'--write-in-place',
'--yaml',
'--no-escape-ng-interpolation',
'../README.md',
],
example.path),
]));
expect(
output,
containsAllInOrder(<Matcher>[
contains('Ran for 1 package(s)'),
]));
});
test('updates example readme when config is present', () async {
final RepositoryPackage package = createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath, 'example/README.md']);
final Directory example = getExampleDir(package);
final List<String> output =
await runCapturingPrint(runner, <String>['update-excerpts']);
expect(
processRunner.recordedCalls,
containsAll(<ProcessCall>[
ProcessCall(
'dart',
const <String>[
'run',
'build_runner',
'build',
'--config',
'excerpt',
'--output',
'excerpts',
'--delete-conflicting-outputs',
],
example.path),
ProcessCall(
'dart',
const <String>[
'run',
'code_excerpt_updater',
'--write-in-place',
'--yaml',
'--no-escape-ng-interpolation',
'README.md',
],
example.path),
]));
expect(
output,
containsAllInOrder(<Matcher>[
contains('Ran for 1 package(s)'),
]));
});
test('includes all top-level .md files', () async {
const String otherMdFileName = 'another_file.md';
final RepositoryPackage package = createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath, otherMdFileName]);
final Directory example = getExampleDir(package);
final List<String> output =
await runCapturingPrint(runner, <String>['update-excerpts']);
expect(
processRunner.recordedCalls,
containsAll(<ProcessCall>[
ProcessCall(
'dart',
const <String>[
'run',
'build_runner',
'build',
'--config',
'excerpt',
'--output',
'excerpts',
'--delete-conflicting-outputs',
],
example.path),
ProcessCall(
'dart',
const <String>[
'run',
'code_excerpt_updater',
'--write-in-place',
'--yaml',
'--no-escape-ng-interpolation',
'../README.md',
'../$otherMdFileName',
],
example.path),
]));
expect(
output,
containsAllInOrder(<Matcher>[
contains('Ran for 1 package(s)'),
]));
});
test('skips when no config is present', () async {
createFakePlugin('a_package', packagesDir);
final List<String> output =
await runCapturingPrint(runner, <String>['update-excerpts']);
expect(processRunner.recordedCalls, isEmpty);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Skipped 1 package(s)'),
]));
});
test('restores pubspec even if running the script fails', () async {
final RepositoryPackage package = createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1), <String>['pub', 'get'])
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts'], errorHandler: (Error e) {
commandError = e;
});
// Check that it's definitely a failure in a step between making the changes
// and restoring the original.
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The following packages had errors:'),
contains('a_package:\n'
' Unable to get script dependencies')
]));
final String examplePubspecContent =
package.getExamples().first.pubspecFile.readAsStringSync();
expect(examplePubspecContent, isNot(contains('code_excerpter')));
expect(examplePubspecContent, isNot(contains('code_excerpt_updater')));
});
test('fails if pub get fails', () async {
createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1), <String>['pub', 'get'])
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The following packages had errors:'),
contains('a_package:\n'
' Unable to get script dependencies')
]));
});
test('fails if extraction fails', () async {
createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(), <String>['pub', 'get']),
FakeProcessInfo(MockProcess(exitCode: 1), <String>['run', 'build_runner'])
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The following packages had errors:'),
contains('a_package:\n'
' Unable to extract excerpts')
]));
});
test('fails if injection fails', () async {
createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(), <String>['pub', 'get']),
FakeProcessInfo(MockProcess(), <String>['run', 'build_runner']),
FakeProcessInfo(
MockProcess(exitCode: 1), <String>['run', 'code_excerpt_updater']),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The following packages had errors:'),
contains('a_package:\n'
' Unable to inject excerpts')
]));
});
test('fails if example injection fails', () async {
createFakePlugin('a_package', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath, 'example/README.md']);
processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(), <String>['pub', 'get']),
FakeProcessInfo(MockProcess(), <String>['run', 'build_runner']),
FakeProcessInfo(MockProcess(), <String>['run', 'code_excerpt_updater']),
FakeProcessInfo(
MockProcess(exitCode: 1), <String>['run', 'code_excerpt_updater']),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The following packages had errors:'),
contains('a_package:\n'
' Unable to inject example excerpts')
]));
});
test('fails if READMEs are changed with --fail-on-change', () async {
createFakePlugin('a_plugin', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
const String changedFilePath = 'README.md';
processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFilePath)),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts', '--fail-on-change'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'One or more .md files are out of sync with their source excerpts'),
contains('Snippets are out of sync in the following files: '
'$changedFilePath'),
]));
});
test('passes if unrelated files are changed with --fail-on-change', () async {
createFakePlugin('a_plugin', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt';
processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFilePath)),
];
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts', '--fail-on-change']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Ran for 1 package(s)'),
]));
});
test('fails if git ls-files fails', () async {
createFakePlugin('a_plugin', packagesDir,
extraFiles: <String>[kReadmeExcerptConfigPath]);
processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1))
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['update-excerpts', '--fail-on-change'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Unable to determine local file state'),
]));
});
}