mirror of
https://github.com/flutter/packages.git
synced 2026-03-13 10:40:37 +08:00
Makes the analysis options changes described in
https://github.com/flutter/flutter/issues/178827:
- Adding
[omit_obvious_local_variable_types](https://dart.dev/tools/linter-rules/omit_obvious_local_variable_types)
- Adding
[specify_nonobvious_local_variable_types](https://dart.dev/tools/linter-rules/specify_nonobvious_local_variable_types)
- Adding
[specify_nonobvious_property_types](https://dart.dev/tools/linter-rules/specify_nonobvious_property_types)
- Adding
[type_annotate_public_apis](https://dart.dev/tools/linter-rules/type_annotate_public_apis)
- Removing
[always_specify_types](https://dart.dev/tools/linter-rules/always_specify_types)
After those changes, makes the following repo-wide changes:
- `dart fix --apply` in all packages and in script/tool/
- `dart format` in all packages and in script/tool/
- `update-excerpts` repo tooling command to update excerpts based on the
changes to their sources
Also updates the min Flutter/Dart SDK version to 3.35/3.9 for the
following packages, to avoid `analyze` failures in the `N-2` legacy
analysis run due to what appears to be a 3.9 change in what the Dart
analyzer continues to be an obvious local type in loop iterations:
- go_router
- google_fonts
- google_identity_services_web
- google_maps_flutter_web
- local_auth_platform_interface
- metrics_center
- multicast_dns
- pigeon
- rfw
- shared_preferences
- two_dimensional_scrollables
- vector_graphics_compiler
- mustache_template
- path_parsing
Because this is causing a significant amount of format churn already, I
took this opportunity to update the repository tooling to a min Dart SDK
of 3.8 (the N-2 stable version, so the earliest version we need the
tooling to support) to pick up the new format style, so the amount of
automated formatter change is higher in script/tool/ than in the
packages.
This does contain two manual changes (other than the repo tooling min
version):
-
d700b45c7d
changes `dynamic` to `Object?` in a few places where `dynamic` caused
analyzer warnings under the new rule set.
- Updates the repo tooling to ignore `.dart_tool/` when looking for
unexpected local `analysis_options.yaml` files, to fix issues running
the repo tool's `analyze` command locally based on recent changes in
`dart` behavior.
This does not include any CHANGELOG or version updates; even though we
normally version any changes to production code, mass automated changes
like this aren't worth the churn of releasing. This includes changes to
lib/example/main.dart and to README.md excerpts; while the style changes
will be user-visible on pub.dev, it's fine for those changes to wait for
the next release of each package.
Part of https://github.com/flutter/flutter/issues/178827
1214 lines
36 KiB
Dart
1214 lines
36 KiB
Dart
// Copyright 2013 The Flutter Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:args/command_runner.dart';
|
|
import 'package:file/file.dart';
|
|
import 'package:flutter_plugin_tools/src/common/core.dart';
|
|
import 'package:flutter_plugin_tools/src/common/output_utils.dart';
|
|
import 'package:flutter_plugin_tools/src/common/package_looping_command.dart';
|
|
import 'package:git/git.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import '../mocks.dart';
|
|
import '../util.dart';
|
|
|
|
// Constants for colorized output start and end.
|
|
const String _startElapsedTimeColor = '\x1B[90m';
|
|
const String _startErrorColor = '\x1B[31m';
|
|
const String _startHeadingColor = '\x1B[36m';
|
|
const String _startSkipColor = '\x1B[90m';
|
|
const String _startSkipWithWarningColor = '\x1B[93m';
|
|
const String _startSuccessColor = '\x1B[32m';
|
|
const String _startWarningColor = '\x1B[33m';
|
|
const String _endColor = '\x1B[0m';
|
|
|
|
// The filename within a package containing warnings to log during runForPackage.
|
|
enum _ResultFileType {
|
|
/// A file containing errors to return.
|
|
errors,
|
|
|
|
/// A file containing warnings that should be logged.
|
|
warns,
|
|
|
|
/// A file indicating that the package should be skipped, and why.
|
|
skips,
|
|
|
|
/// A file indicating that the package should throw.
|
|
throws,
|
|
}
|
|
|
|
// The filename within a package containing errors to return from runForPackage.
|
|
const String _errorFile = 'errors';
|
|
// The filename within a package indicating that it should be skipped.
|
|
const String _skipFile = 'skip';
|
|
// The filename within a package containing warnings to log during runForPackage.
|
|
const String _warningFile = 'warnings';
|
|
// The filename within a package indicating that it should throw.
|
|
const String _throwFile = 'throw';
|
|
|
|
/// Writes a file to [package] to control the behavior of
|
|
/// [TestPackageLoopingCommand] for that package.
|
|
void _addResultFile(
|
|
RepositoryPackage package,
|
|
_ResultFileType type, {
|
|
String? contents,
|
|
}) {
|
|
final File file = package.directory.childFile(_filenameForType(type));
|
|
file.createSync();
|
|
if (contents != null) {
|
|
file.writeAsStringSync(contents);
|
|
}
|
|
}
|
|
|
|
String _filenameForType(_ResultFileType type) {
|
|
switch (type) {
|
|
case _ResultFileType.errors:
|
|
return _errorFile;
|
|
case _ResultFileType.warns:
|
|
return _warningFile;
|
|
case _ResultFileType.skips:
|
|
return _skipFile;
|
|
case _ResultFileType.throws:
|
|
return _throwFile;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
late MockPlatform mockPlatform;
|
|
late Directory packagesDir;
|
|
late Directory thirdPartyPackagesDir;
|
|
late GitDir gitDir;
|
|
late RecordingProcessRunner gitProcessRunner;
|
|
|
|
setUp(() {
|
|
mockPlatform = MockPlatform();
|
|
(:packagesDir, processRunner: _, :gitProcessRunner, :gitDir) =
|
|
configureBaseCommandMocks(platform: mockPlatform);
|
|
// Correct color handling is part of the behavior being tested here.
|
|
useColorForOutput = true;
|
|
thirdPartyPackagesDir = packagesDir.parent
|
|
.childDirectory('third_party')
|
|
.childDirectory('packages');
|
|
});
|
|
|
|
tearDown(() {
|
|
// Restore the default behavior.
|
|
useColorForOutput = io.stdout.supportsAnsiEscapes;
|
|
});
|
|
|
|
/// Creates a TestPackageLoopingCommand with the given configuration.
|
|
TestPackageLoopingCommand createTestCommand({
|
|
bool hasLongOutput = true,
|
|
PackageLoopingType packageLoopingType = PackageLoopingType.topLevelOnly,
|
|
bool failsDuringInit = false,
|
|
bool warnsDuringInit = false,
|
|
bool captureOutput = false,
|
|
String? customFailureListHeader,
|
|
String? customFailureListFooter,
|
|
}) {
|
|
return TestPackageLoopingCommand(
|
|
packagesDir,
|
|
platform: mockPlatform,
|
|
hasLongOutput: hasLongOutput,
|
|
packageLoopingType: packageLoopingType,
|
|
failsDuringInit: failsDuringInit,
|
|
warnsDuringInit: warnsDuringInit,
|
|
customFailureListHeader: customFailureListHeader,
|
|
customFailureListFooter: customFailureListFooter,
|
|
captureOutput: captureOutput,
|
|
gitDir: gitDir,
|
|
);
|
|
}
|
|
|
|
/// Runs [command] with the given [arguments], and returns its output.
|
|
Future<List<String>> runCommand(
|
|
TestPackageLoopingCommand command, {
|
|
List<String> arguments = const <String>[],
|
|
void Function(Error error)? errorHandler,
|
|
}) async {
|
|
late CommandRunner<void> runner;
|
|
runner = CommandRunner<void>(
|
|
'test_package_looping_command',
|
|
'Test for base package looping functionality',
|
|
);
|
|
runner.addCommand(command);
|
|
return runCapturingPrint(runner, <String>[
|
|
command.name,
|
|
...arguments,
|
|
], errorHandler: errorHandler);
|
|
}
|
|
|
|
group('tool exit', () {
|
|
test('is handled during initializeRun', () async {
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
failsDuringInit: true,
|
|
);
|
|
|
|
expect(() => runCommand(command), throwsA(isA<ToolExit>()));
|
|
});
|
|
|
|
test('does not stop looping on error', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
final RepositoryPackage failingPackage = createFakePlugin(
|
|
'package_b',
|
|
packagesDir,
|
|
);
|
|
createFakePackage('package_c', packagesDir);
|
|
_addResultFile(failingPackage, _ResultFileType.errors);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
Error? commandError;
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
},
|
|
);
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
'${_startHeadingColor}Running for package_b...$_endColor',
|
|
'${_startHeadingColor}Running for package_c...$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('does not stop looping on exceptions', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
final RepositoryPackage failingPackage = createFakePlugin(
|
|
'package_b',
|
|
packagesDir,
|
|
);
|
|
createFakePackage('package_c', packagesDir);
|
|
_addResultFile(failingPackage, _ResultFileType.throws);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
Error? commandError;
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
},
|
|
);
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
'${_startHeadingColor}Running for package_b...$_endColor',
|
|
'${_startHeadingColor}Running for package_c...$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('file filtering', () {
|
|
test('runs command if the changed files list is empty', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
|
|
gitProcessRunner.mockProcessesForExecutable['git-diff'] =
|
|
<FakeProcessInfo>[FakeProcessInfo(MockProcess(stdout: ''))];
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('runs command if any files are not ignored', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
|
|
gitProcessRunner.mockProcessesForExecutable['git-diff'] =
|
|
<FakeProcessInfo>[
|
|
FakeProcessInfo(
|
|
MockProcess(
|
|
stdout: '''
|
|
skip/a
|
|
other
|
|
skip/b
|
|
''',
|
|
),
|
|
),
|
|
];
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('skips commands if all files should be ignored', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
|
|
gitProcessRunner.mockProcessesForExecutable['git-diff'] =
|
|
<FakeProcessInfo>[
|
|
FakeProcessInfo(
|
|
MockProcess(
|
|
stdout: '''
|
|
skip/a
|
|
skip/b
|
|
''',
|
|
),
|
|
),
|
|
];
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
isNot(containsAllInOrder(<Matcher>[contains('Running for package_a')])),
|
|
);
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startSkipColor}SKIPPING ALL PACKAGES: No changed files affect this command$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('package iteration', () {
|
|
test('includes plugins and packages', () async {
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
);
|
|
final RepositoryPackage package = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand();
|
|
await runCommand(command);
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[plugin.path, package.path]),
|
|
);
|
|
});
|
|
|
|
test('includes third_party/packages', () async {
|
|
final RepositoryPackage package1 = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
final RepositoryPackage package2 = createFakePackage(
|
|
'another_package',
|
|
thirdPartyPackagesDir,
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand();
|
|
await runCommand(command);
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[package1.path, package2.path]),
|
|
);
|
|
});
|
|
|
|
test('includes all subpackages when requested', () async {
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
examples: <String>['example1', 'example2'],
|
|
);
|
|
final RepositoryPackage package = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
final RepositoryPackage subPackage = createFakePackage(
|
|
'sub_package',
|
|
package.directory,
|
|
examples: <String>[],
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
packageLoopingType: PackageLoopingType.includeAllSubpackages,
|
|
);
|
|
await runCommand(command);
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[
|
|
plugin.path,
|
|
getExampleDir(plugin).childDirectory('example1').path,
|
|
getExampleDir(plugin).childDirectory('example2').path,
|
|
package.path,
|
|
getExampleDir(package).path,
|
|
subPackage.path,
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('includes examples when requested', () async {
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
examples: <String>['example1', 'example2'],
|
|
);
|
|
final RepositoryPackage package = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
final RepositoryPackage subPackage = createFakePackage(
|
|
'sub_package',
|
|
package.directory,
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
packageLoopingType: PackageLoopingType.includeExamples,
|
|
);
|
|
await runCommand(command);
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[
|
|
plugin.path,
|
|
getExampleDir(plugin).childDirectory('example1').path,
|
|
getExampleDir(plugin).childDirectory('example2').path,
|
|
package.path,
|
|
getExampleDir(package).path,
|
|
]),
|
|
);
|
|
expect(command.checkedPackages, isNot(contains(subPackage.path)));
|
|
});
|
|
|
|
test('excludes subpackages when main package is excluded', () async {
|
|
final RepositoryPackage excluded = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
examples: <String>['example1', 'example2'],
|
|
);
|
|
final RepositoryPackage included = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
final RepositoryPackage subpackage = createFakePackage(
|
|
'sub_package',
|
|
excluded.directory,
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
packageLoopingType: PackageLoopingType.includeAllSubpackages,
|
|
);
|
|
await runCommand(command, arguments: <String>['--exclude=a_plugin']);
|
|
|
|
final Iterable<RepositoryPackage> examples = excluded.getExamples();
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[included.path, getExampleDir(included).path]),
|
|
);
|
|
expect(command.checkedPackages, isNot(contains(excluded.path)));
|
|
expect(examples.length, 2);
|
|
for (final example in examples) {
|
|
expect(command.checkedPackages, isNot(contains(example.path)));
|
|
}
|
|
expect(command.checkedPackages, isNot(contains(subpackage.path)));
|
|
});
|
|
|
|
test('excludes examples when main package is excluded', () async {
|
|
final RepositoryPackage excluded = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
examples: <String>['example1', 'example2'],
|
|
);
|
|
final RepositoryPackage included = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
packageLoopingType: PackageLoopingType.includeExamples,
|
|
);
|
|
await runCommand(command, arguments: <String>['--exclude=a_plugin']);
|
|
|
|
final Iterable<RepositoryPackage> examples = excluded.getExamples();
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[included.path, getExampleDir(included).path]),
|
|
);
|
|
expect(command.checkedPackages, isNot(contains(excluded.path)));
|
|
expect(examples.length, 2);
|
|
for (final example in examples) {
|
|
expect(command.checkedPackages, isNot(contains(example.path)));
|
|
}
|
|
});
|
|
|
|
test('skips unsupported Flutter versions when requested', () async {
|
|
final RepositoryPackage excluded = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
flutterConstraint: '>=2.10.0',
|
|
);
|
|
final RepositoryPackage included = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
packageLoopingType: PackageLoopingType.includeAllSubpackages,
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
arguments: <String>['--skip-if-not-supporting-flutter-version=2.5.0'],
|
|
);
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[included.path, getExampleDir(included).path]),
|
|
);
|
|
expect(command.checkedPackages, isNot(contains(excluded.path)));
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for a_package...$_endColor',
|
|
'${_startHeadingColor}Running for a_plugin...$_endColor',
|
|
'$_startSkipColor SKIPPING: Does not support Flutter 2.5.0$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('skips unsupported Dart versions when requested', () async {
|
|
final RepositoryPackage excluded = createFakePackage(
|
|
'excluded_package',
|
|
packagesDir,
|
|
dartConstraint: '>=2.18.0 <4.0.0',
|
|
);
|
|
final RepositoryPackage included = createFakePackage(
|
|
'a_package',
|
|
packagesDir,
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
packageLoopingType: PackageLoopingType.includeAllSubpackages,
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
arguments: <String>[
|
|
'--skip-if-not-supporting-flutter-version=3.0.0', // Flutter 3.0.0 -> Dart 2.17.0
|
|
],
|
|
);
|
|
|
|
expect(
|
|
command.checkedPackages,
|
|
unorderedEquals(<String>[included.path, getExampleDir(included).path]),
|
|
);
|
|
expect(command.checkedPackages, isNot(contains(excluded.path)));
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for a_package...$_endColor',
|
|
'${_startHeadingColor}Running for excluded_package...$_endColor',
|
|
'$_startSkipColor SKIPPING: Does not support Dart 2.17.0$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('output', () {
|
|
test('has the expected package headers for long-form output', () async {
|
|
createFakePlugin('package_a', packagesDir);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand();
|
|
final List<String> output = await runCommand(command);
|
|
|
|
const separator =
|
|
'============================================================';
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'$_startHeadingColor\n$separator\n|| Running for package_a\n$separator\n$_endColor',
|
|
'$_startHeadingColor\n$separator\n|| Running for package_b\n$separator\n$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('has the expected package headers for short-form output', () async {
|
|
createFakePlugin('package_a', packagesDir);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
'${_startHeadingColor}Running for package_b...$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('prints timing info in long-form output when requested', () async {
|
|
createFakePlugin('package_a', packagesDir);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand();
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
arguments: <String>['--log-timing'],
|
|
);
|
|
|
|
const separator =
|
|
'============================================================';
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'$_startHeadingColor\n$separator\n|| Running for package_a [@0:00]\n$separator\n$_endColor',
|
|
'$_startElapsedTimeColor\n[package_a completed in 0m 0s]$_endColor',
|
|
'$_startHeadingColor\n$separator\n|| Running for package_b [@0:00]\n$separator\n$_endColor',
|
|
'$_startElapsedTimeColor\n[package_b completed in 0m 0s]$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('prints timing info in short-form output when requested', () async {
|
|
createFakePlugin('package_a', packagesDir);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
arguments: <String>['--log-timing'],
|
|
);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'$_startHeadingColor[0:00] Running for package_a...$_endColor',
|
|
'$_startHeadingColor[0:00] Running for package_b...$_endColor',
|
|
]),
|
|
);
|
|
// Short-form output should not include elapsed time.
|
|
expect(output, isNot(contains('[package_a completed in 0m 0s]')));
|
|
});
|
|
|
|
test('shows the success message when nothing fails', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'\n',
|
|
'${_startSuccessColor}No issues found!$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test(
|
|
'shows failure summaries when something fails without extra details',
|
|
() async {
|
|
createFakePackage('package_a', packagesDir);
|
|
final RepositoryPackage failingPackage1 = createFakePlugin(
|
|
'package_b',
|
|
packagesDir,
|
|
);
|
|
createFakePackage('package_c', packagesDir);
|
|
final RepositoryPackage failingPackage2 = createFakePlugin(
|
|
'package_d',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(failingPackage1, _ResultFileType.errors);
|
|
_addResultFile(failingPackage2, _ResultFileType.errors);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
Error? commandError;
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
},
|
|
);
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'\n',
|
|
'${_startErrorColor}The following packages had errors:$_endColor',
|
|
'$_startErrorColor package_b$_endColor',
|
|
'$_startErrorColor package_d$_endColor',
|
|
'${_startErrorColor}See above for full details.$_endColor',
|
|
]),
|
|
);
|
|
},
|
|
);
|
|
|
|
test('uses custom summary header and footer if provided', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
final RepositoryPackage failingPackage1 = createFakePlugin(
|
|
'package_b',
|
|
packagesDir,
|
|
);
|
|
createFakePackage('package_c', packagesDir);
|
|
final RepositoryPackage failingPackage2 = createFakePlugin(
|
|
'package_d',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(failingPackage1, _ResultFileType.errors);
|
|
_addResultFile(failingPackage2, _ResultFileType.errors);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
customFailureListHeader: 'This is a custom header',
|
|
customFailureListFooter: 'And a custom footer!',
|
|
);
|
|
Error? commandError;
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
},
|
|
);
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'\n',
|
|
'${_startErrorColor}This is a custom header$_endColor',
|
|
'$_startErrorColor package_b$_endColor',
|
|
'$_startErrorColor package_d$_endColor',
|
|
'${_startErrorColor}And a custom footer!$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test(
|
|
'shows failure summaries when something fails with extra details',
|
|
() async {
|
|
createFakePackage('package_a', packagesDir);
|
|
final RepositoryPackage failingPackage1 = createFakePlugin(
|
|
'package_b',
|
|
packagesDir,
|
|
);
|
|
createFakePackage('package_c', packagesDir);
|
|
final RepositoryPackage failingPackage2 = createFakePlugin(
|
|
'package_d',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
failingPackage1,
|
|
_ResultFileType.errors,
|
|
contents: 'just one detail',
|
|
);
|
|
_addResultFile(
|
|
failingPackage2,
|
|
_ResultFileType.errors,
|
|
contents: 'first detail\nsecond detail',
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
Error? commandError;
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
},
|
|
);
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'\n',
|
|
'${_startErrorColor}The following packages had errors:$_endColor',
|
|
'$_startErrorColor package_b:\n just one detail$_endColor',
|
|
'$_startErrorColor package_d:\n first detail\n second detail$_endColor',
|
|
'${_startErrorColor}See above for full details.$_endColor',
|
|
]),
|
|
);
|
|
},
|
|
);
|
|
|
|
test('is captured, not printed, when requested', () async {
|
|
createFakePlugin('package_a', packagesDir);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
captureOutput: true,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(output, isEmpty);
|
|
|
|
// None of the output should be colorized when captured.
|
|
const separator =
|
|
'============================================================';
|
|
expect(
|
|
command.capturedOutput,
|
|
containsAllInOrder(<String>[
|
|
'\n$separator\n|| Running for package_a\n$separator\n',
|
|
'\n$separator\n|| Running for package_b\n$separator\n',
|
|
'No issues found!',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('logs skips', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
final RepositoryPackage skipPackage = createFakePackage(
|
|
'package_b',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
skipPackage,
|
|
_ResultFileType.skips,
|
|
contents: 'For a reason',
|
|
);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
'${_startHeadingColor}Running for package_b...$_endColor',
|
|
'$_startSkipColor SKIPPING: For a reason$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('logs exclusions', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
arguments: <String>['--exclude=package_b'],
|
|
);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
'${_startSkipColor}Not running for package_b; excluded$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('logs warnings', () async {
|
|
final RepositoryPackage warnPackage = createFakePackage(
|
|
'package_a',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
warnPackage,
|
|
_ResultFileType.warns,
|
|
contents: 'Warning 1\nWarning 2',
|
|
);
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
'${_startWarningColor}Warning 1$_endColor',
|
|
'${_startWarningColor}Warning 2$_endColor',
|
|
'${_startHeadingColor}Running for package_b...$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('logs unhandled exceptions as errors', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
final RepositoryPackage failingPackage = createFakePlugin(
|
|
'package_b',
|
|
packagesDir,
|
|
);
|
|
createFakePackage('package_c', packagesDir);
|
|
_addResultFile(failingPackage, _ResultFileType.throws);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
Error? commandError;
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
},
|
|
);
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startErrorColor}Exception: Uh-oh$_endColor',
|
|
'${_startErrorColor}The following packages had errors:$_endColor',
|
|
'$_startErrorColor package_b:\n Unhandled exception$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('prints run summary on success', () async {
|
|
final RepositoryPackage warnPackage1 = createFakePackage(
|
|
'package_a',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
warnPackage1,
|
|
_ResultFileType.warns,
|
|
contents: 'Warning 1\nWarning 2',
|
|
);
|
|
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final RepositoryPackage skipPackage = createFakePackage(
|
|
'package_c',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
skipPackage,
|
|
_ResultFileType.skips,
|
|
contents: 'For a reason',
|
|
);
|
|
|
|
final RepositoryPackage skipAndWarnPackage = createFakePackage(
|
|
'package_d',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
skipAndWarnPackage,
|
|
_ResultFileType.warns,
|
|
contents: 'Warning',
|
|
);
|
|
_addResultFile(
|
|
skipAndWarnPackage,
|
|
_ResultFileType.skips,
|
|
contents: 'See warning',
|
|
);
|
|
|
|
final RepositoryPackage warnPackage2 = createFakePackage(
|
|
'package_e',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
warnPackage2,
|
|
_ResultFileType.warns,
|
|
contents: 'Warning 1\nWarning 2',
|
|
);
|
|
|
|
createFakePackage('package_f', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'------------------------------------------------------------',
|
|
'Ran for 4 package(s) (2 with warnings)',
|
|
'Skipped 2 package(s) (1 with warnings)',
|
|
'\n',
|
|
'${_startSuccessColor}No issues found!$_endColor',
|
|
]),
|
|
);
|
|
// The long-form summary should not be printed for short-form commands.
|
|
expect(output, isNot(contains('Run summary:')));
|
|
expect(output, isNot(contains(contains('package a - ran'))));
|
|
});
|
|
|
|
test('counts exclusions as skips in run summary', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
);
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
arguments: <String>['--exclude=package_a'],
|
|
);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'------------------------------------------------------------',
|
|
'Skipped 1 package(s)',
|
|
'\n',
|
|
'${_startSuccessColor}No issues found!$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('prints long-form run summary for long-output commands', () async {
|
|
final RepositoryPackage warnPackage1 = createFakePackage(
|
|
'package_a',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
warnPackage1,
|
|
_ResultFileType.warns,
|
|
contents: 'Warning 1\nWarning 2',
|
|
);
|
|
|
|
createFakePackage('package_b', packagesDir);
|
|
|
|
final RepositoryPackage skipPackage = createFakePackage(
|
|
'package_c',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
skipPackage,
|
|
_ResultFileType.skips,
|
|
contents: 'For a reason',
|
|
);
|
|
|
|
final RepositoryPackage skipAndWarnPackage = createFakePackage(
|
|
'package_d',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
skipAndWarnPackage,
|
|
_ResultFileType.warns,
|
|
contents: 'Warning',
|
|
);
|
|
_addResultFile(
|
|
skipAndWarnPackage,
|
|
_ResultFileType.skips,
|
|
contents: 'See warning',
|
|
);
|
|
|
|
final RepositoryPackage warnPackage2 = createFakePackage(
|
|
'package_e',
|
|
packagesDir,
|
|
);
|
|
_addResultFile(
|
|
warnPackage2,
|
|
_ResultFileType.warns,
|
|
contents: 'Warning 1\nWarning 2',
|
|
);
|
|
|
|
createFakePackage('package_f', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand();
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'------------------------------------------------------------',
|
|
'Run overview:',
|
|
' package_a - ${_startWarningColor}ran (with warning)$_endColor',
|
|
' package_b - ${_startSuccessColor}ran$_endColor',
|
|
' package_c - ${_startSkipColor}skipped$_endColor',
|
|
' package_d - ${_startSkipWithWarningColor}skipped (with warning)$_endColor',
|
|
' package_e - ${_startWarningColor}ran (with warning)$_endColor',
|
|
' package_f - ${_startSuccessColor}ran$_endColor',
|
|
'',
|
|
'Ran for 4 package(s) (2 with warnings)',
|
|
'Skipped 2 package(s) (1 with warnings)',
|
|
'\n',
|
|
'${_startSuccessColor}No issues found!$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('prints exclusions as skips in long-form run summary', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand();
|
|
final List<String> output = await runCommand(
|
|
command,
|
|
arguments: <String>['--exclude=package_a'],
|
|
);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
' package_a - ${_startSkipColor}excluded$_endColor',
|
|
'',
|
|
'Skipped 1 package(s)',
|
|
'\n',
|
|
'${_startSuccessColor}No issues found!$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('handles warnings outside of runForPackage', () async {
|
|
createFakePackage('package_a', packagesDir);
|
|
|
|
final TestPackageLoopingCommand command = createTestCommand(
|
|
hasLongOutput: false,
|
|
warnsDuringInit: true,
|
|
);
|
|
final List<String> output = await runCommand(command);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'${_startWarningColor}Warning during initializeRun$_endColor',
|
|
'${_startHeadingColor}Running for package_a...$_endColor',
|
|
'${_startWarningColor}Warning during completeRun$_endColor',
|
|
'------------------------------------------------------------',
|
|
'Ran for 1 package(s)',
|
|
'2 warnings not associated with a package',
|
|
'\n',
|
|
'${_startSuccessColor}No issues found!$_endColor',
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
class TestPackageLoopingCommand extends PackageLoopingCommand {
|
|
TestPackageLoopingCommand(
|
|
super.packagesDir, {
|
|
required super.platform,
|
|
this.hasLongOutput = true,
|
|
this.packageLoopingType = PackageLoopingType.topLevelOnly,
|
|
this.customFailureListHeader,
|
|
this.customFailureListFooter,
|
|
this.failsDuringInit = false,
|
|
this.warnsDuringInit = false,
|
|
this.captureOutput = false,
|
|
super.processRunner,
|
|
super.gitDir,
|
|
});
|
|
|
|
final List<String> checkedPackages = <String>[];
|
|
final List<String> capturedOutput = <String>[];
|
|
|
|
final String? customFailureListHeader;
|
|
final String? customFailureListFooter;
|
|
|
|
final bool failsDuringInit;
|
|
final bool warnsDuringInit;
|
|
|
|
@override
|
|
bool hasLongOutput;
|
|
|
|
@override
|
|
PackageLoopingType packageLoopingType;
|
|
|
|
@override
|
|
String get failureListHeader =>
|
|
customFailureListHeader ?? super.failureListHeader;
|
|
|
|
@override
|
|
String get failureListFooter =>
|
|
customFailureListFooter ?? super.failureListFooter;
|
|
|
|
@override
|
|
bool captureOutput;
|
|
|
|
@override
|
|
final String name = 'loop-test';
|
|
|
|
@override
|
|
final String description = 'sample package looping command';
|
|
|
|
@override
|
|
bool shouldIgnoreFile(String path) {
|
|
return path.startsWith('skip/');
|
|
}
|
|
|
|
@override
|
|
Future<void> initializeRun() async {
|
|
if (warnsDuringInit) {
|
|
logWarning('Warning during initializeRun');
|
|
}
|
|
if (failsDuringInit) {
|
|
throw ToolExit(2);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<PackageResult> runForPackage(RepositoryPackage package) async {
|
|
checkedPackages.add(package.path);
|
|
final File warningFile = package.directory.childFile(_warningFile);
|
|
if (warningFile.existsSync()) {
|
|
final List<String> warnings = warningFile.readAsLinesSync();
|
|
warnings.forEach(logWarning);
|
|
}
|
|
final File skipFile = package.directory.childFile(_skipFile);
|
|
if (skipFile.existsSync()) {
|
|
return PackageResult.skip(skipFile.readAsStringSync());
|
|
}
|
|
final File errorFile = package.directory.childFile(_errorFile);
|
|
if (errorFile.existsSync()) {
|
|
return PackageResult.fail(errorFile.readAsLinesSync());
|
|
}
|
|
final File throwFile = package.directory.childFile(_throwFile);
|
|
if (throwFile.existsSync()) {
|
|
throw Exception('Uh-oh');
|
|
}
|
|
return PackageResult.success();
|
|
}
|
|
|
|
@override
|
|
Future<void> completeRun() async {
|
|
if (warnsDuringInit) {
|
|
logWarning('Warning during completeRun');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> handleCapturedOutput(List<String> output) async {
|
|
capturedOutput.addAll(output);
|
|
}
|
|
}
|