Unverified Commit 03aa059c authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Do not abort at first error when tests fail. (#108936)

parent 274a9349
This diff is collapsed.
...@@ -149,8 +149,8 @@ Future<Command> startCommand(String executable, List<String> arguments, { ...@@ -149,8 +149,8 @@ Future<Command> startCommand(String executable, List<String> arguments, {
/// Runs the `executable` and waits until the process exits. /// Runs the `executable` and waits until the process exits.
/// ///
/// If the process exits with a non-zero exit code, exits this process with /// If the process exits with a non-zero exit code and `expectNonZeroExit` is
/// exit code 1, unless `expectNonZeroExit` is set to true. /// false, calls foundError (which does not terminate execution!).
/// ///
/// `outputListener` is called for every line of standard output from the /// `outputListener` is called for every line of standard output from the
/// process, and is given the [Process] object. This can be used to interrupt /// process, and is given the [Process] object. This can be used to interrupt
...@@ -195,7 +195,7 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, { ...@@ -195,7 +195,7 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
io.stdout.writeln(result.flattenedStderr); io.stdout.writeln(result.flattenedStderr);
break; break;
} }
exitWithError(<String>[ foundError(<String>[
if (failureMessage != null) if (failureMessage != null)
failureMessage failureMessage
else else
...@@ -203,8 +203,9 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, { ...@@ -203,8 +203,9 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
'${bold}Command: $green$commandDescription$reset', '${bold}Command: $green$commandDescription$reset',
'${bold}Relative working directory: $cyan$relativeWorkingDir$reset', '${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
]); ]);
} } else {
print('$clock ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset'); print('$clock ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
}
return result; return result;
} }
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:core' hide print;
import 'dart:io'; import 'dart:io' hide exit;
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
...@@ -43,6 +43,10 @@ Future<void> main() async { ...@@ -43,6 +43,10 @@ Future<void> main() async {
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort); await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent); await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false); await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
if (hasError) {
print('One or more tests failed.');
reportErrorsAndExit();
}
} }
Future<void> _setAppVersion(int version) async { Future<void> _setAppVersion(int version) async {
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:core' hide print;
import 'dart:io' hide exit;
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:typed_data'; import 'dart:typed_data';
...@@ -170,7 +171,6 @@ String get shuffleSeed { ...@@ -170,7 +171,6 @@ String get shuffleSeed {
/// bin/cache/dart-sdk/bin/dart dev/bots/test.dart --local-engine=host_debug_unopt /// bin/cache/dart-sdk/bin/dart dev/bots/test.dart --local-engine=host_debug_unopt
Future<void> main(List<String> args) async { Future<void> main(List<String> args) async {
print('$clock STARTING ANALYSIS'); print('$clock STARTING ANALYSIS');
try {
flutterTestArgs.addAll(args); flutterTestArgs.addAll(args);
final Set<String> removeArgs = <String>{}; final Set<String> removeArgs = <String>{};
for (final String arg in args) { for (final String arg in args) {
...@@ -214,8 +214,9 @@ Future<void> main(List<String> args) async { ...@@ -214,8 +214,9 @@ Future<void> main(List<String> args) async {
'skp_generator': _runSkpGeneratorTests, 'skp_generator': _runSkpGeneratorTests,
kTestHarnessShardName: _runTestHarnessTests, // Used for testing this script. kTestHarnessShardName: _runTestHarnessTests, // Used for testing this script.
}); });
} on ExitException catch (error) { if (hasError) {
error.apply(); print('$clock ${bold}Test failed.$reset');
reportErrorsAndExit();
} }
print('$clock ${bold}Test successful.$reset'); print('$clock ${bold}Test successful.$reset');
} }
...@@ -241,9 +242,7 @@ Future<void> _validateEngineHash() async { ...@@ -241,9 +242,7 @@ Future<void> _validateEngineHash() async {
return line.startsWith('Flutter Engine Version:'); return line.startsWith('Flutter Engine Version:');
}); });
if (!actualVersion.contains(expectedVersion)) { if (!actualVersion.contains(expectedVersion)) {
print('${red}Expected "Flutter Engine Version: $expectedVersion", ' foundError(<String>['${red}Expected "Flutter Engine Version: $expectedVersion", but found "$actualVersion".']);
'but found "$actualVersion".');
exit(1);
} }
} }
...@@ -332,7 +331,7 @@ Future<void> _runTestHarnessTests() async { ...@@ -332,7 +331,7 @@ Future<void> _runTestHarnessTests() async {
// Verify that we correctly generated the version file. // Verify that we correctly generated the version file.
final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version'))); final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version')));
if (versionError != null) { if (versionError != null) {
exitWithError(<String>[versionError]); foundError(<String>[versionError]);
} }
} }
...@@ -652,9 +651,10 @@ Future<void> _flutterBuild( ...@@ -652,9 +651,10 @@ Future<void> _flutterBuild(
); );
final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json')); final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
if (!_allTargetsCached(file)) { if (!_allTargetsCached(file)) {
print('${red}Not all build targets cached after second run.$reset'); foundError(<String>[
print('The target performance data was: ${file.readAsStringSync().replaceAll('},', '},\n')}'); '${red}Not all build targets cached after second run.$reset',
exit(1); 'The target performance data was: ${file.readAsStringSync().replaceAll('},', '},\n')}',
]);
} }
} }
} }
...@@ -840,8 +840,7 @@ Future<void> _runFrameworkTests() async { ...@@ -840,8 +840,7 @@ Future<void> _runFrameworkTests() async {
}, },
)); ));
if (results.isNotEmpty) { if (results.isNotEmpty) {
print(results.join('\n')); foundError(results);
exit(1);
} }
} }
...@@ -941,20 +940,24 @@ Future<void> _runFrameworkTests() async { ...@@ -941,20 +940,24 @@ Future<void> _runFrameworkTests() async {
Future<void> _runFrameworkCoverage() async { Future<void> _runFrameworkCoverage() async {
final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info')); final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info'));
if (!coverageFile.existsSync()) { if (!coverageFile.existsSync()) {
print('${red}Coverage file not found.$reset'); foundError(<String>[
print('Expected to find: $cyan${coverageFile.absolute}$reset'); '${red}Coverage file not found.$reset',
print('This file is normally obtained by running `${green}flutter update-packages$reset`.'); 'Expected to find: $cyan${coverageFile.absolute}$reset',
exit(1); 'This file is normally obtained by running `${green}flutter update-packages$reset`.',
]);
return;
} }
coverageFile.deleteSync(); coverageFile.deleteSync();
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'), await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'),
options: const <String>['--coverage'], options: const <String>['--coverage'],
); );
if (!coverageFile.existsSync()) { if (!coverageFile.existsSync()) {
print('${red}Coverage file not found.$reset'); foundError(<String>[
print('Expected to find: $cyan${coverageFile.absolute}$reset'); '${red}Coverage file not found.$reset',
print('This file should have been generated by the `${green}flutter test --coverage$reset` script, but was not.'); 'Expected to find: $cyan${coverageFile.absolute}$reset',
exit(1); 'This file should have been generated by the `${green}flutter test --coverage$reset` script, but was not.',
]);
return;
} }
} }
...@@ -1496,12 +1499,11 @@ Future<void> _runWebStackTraceTest(String buildMode, String entrypoint) async { ...@@ -1496,12 +1499,11 @@ Future<void> _runWebStackTraceTest(String buildMode, String entrypoint) async {
browserDebugPort: browserDebugPort, browserDebugPort: browserDebugPort,
); );
if (result.contains('--- TEST SUCCEEDED ---')) { if (!result.contains('--- TEST SUCCEEDED ---')) {
print('${green}Web stack trace integration test passed.$reset'); foundError(<String>[
} else { result,
print(result); '${red}Web stack trace integration test failed.$reset',
print('${red}Web stack trace integration test failed.$reset'); ]);
exit(1);
} }
} }
...@@ -1545,12 +1547,11 @@ Future<void> _runWebReleaseTest(String target, { ...@@ -1545,12 +1547,11 @@ Future<void> _runWebReleaseTest(String target, {
browserDebugPort: browserDebugPort, browserDebugPort: browserDebugPort,
); );
if (result.contains('--- TEST SUCCEEDED ---')) { if (!result.contains('--- TEST SUCCEEDED ---')) {
print('${green}Web release mode test passed.$reset'); foundError(<String>[
} else { result,
print(result); '${red}Web release mode test failed.$reset',
print('${red}Web release mode test failed.$reset'); ]);
exit(1);
} }
} }
...@@ -1599,13 +1600,12 @@ Future<void> _runWebDebugTest(String target, { ...@@ -1599,13 +1600,12 @@ Future<void> _runWebDebugTest(String target, {
environment: environment, environment: environment,
); );
if (success) { if (!success) {
print('${green}Web stack trace integration test passed.$reset'); foundError(<String>[
} else { result.flattenedStdout!,
print(result.flattenedStdout!); result.flattenedStderr!,
print(result.flattenedStderr!); '${red}Web stack trace integration test failed.$reset',
print('${red}Web stack trace integration test failed.$reset'); ]);
exit(1);
} }
} }
...@@ -1652,9 +1652,11 @@ Future<void> _dartRunTest(String workingDirectory, { ...@@ -1652,9 +1652,11 @@ Future<void> _dartRunTest(String workingDirectory, {
if (cpuVariable != null) { if (cpuVariable != null) {
cpus = int.tryParse(cpuVariable, radix: 10); cpus = int.tryParse(cpuVariable, radix: 10);
if (cpus == null) { if (cpus == null) {
print('${red}The CPU environment variable, if set, must be set to the integer number of available cores.$reset'); foundError(<String>[
print('Actual value: "$cpuVariable"'); '${red}The CPU environment variable, if set, must be set to the integer number of available cores.$reset',
exit(1); 'Actual value: "$cpuVariable"',
]);
return;
} }
} else { } else {
cpus = 2; // Don't default to 1, otherwise we won't catch race conditions. cpus = 2; // Don't default to 1, otherwise we won't catch race conditions.
...@@ -1762,13 +1764,14 @@ Future<void> _runFlutterTest(String workingDirectory, { ...@@ -1762,13 +1764,14 @@ Future<void> _runFlutterTest(String workingDirectory, {
if (script != null) { if (script != null) {
final String fullScriptPath = path.join(workingDirectory, script); final String fullScriptPath = path.join(workingDirectory, script);
if (!FileSystemEntity.isFileSync(fullScriptPath)) { if (!FileSystemEntity.isFileSync(fullScriptPath)) {
print('${red}Could not find test$reset: $green$fullScriptPath$reset'); foundError(<String>[
print('Working directory: $cyan$workingDirectory$reset'); '${red}Could not find test$reset: $green$fullScriptPath$reset',
print('Script: $green$script$reset'); 'Working directory: $cyan$workingDirectory$reset',
if (!printOutput) { 'Script: $green$script$reset',
print('This is one of the tests that does not normally print output.'); if (!printOutput)
} 'This is one of the tests that does not normally print output.',
exit(1); ]);
return;
} }
args.add(script); args.add(script);
} }
...@@ -1792,7 +1795,7 @@ Future<void> _runFlutterTest(String workingDirectory, { ...@@ -1792,7 +1795,7 @@ Future<void> _runFlutterTest(String workingDirectory, {
if (outputChecker != null) { if (outputChecker != null) {
final String? message = outputChecker(result); final String? message = outputChecker(result);
if (message != null) { if (message != null) {
exitWithError(<String>[message]); foundError(<String>[message]);
} }
} }
return; return;
...@@ -1906,15 +1909,19 @@ List<T> _selectIndexOfTotalSubshard<T>(List<T> tests, {String subshardKey = kSub ...@@ -1906,15 +1909,19 @@ List<T> _selectIndexOfTotalSubshard<T>(List<T> tests, {String subshardKey = kSub
final RegExp pattern = RegExp(r'^(\d+)_(\d+)$'); final RegExp pattern = RegExp(r'^(\d+)_(\d+)$');
final Match? match = pattern.firstMatch(subshardName); final Match? match = pattern.firstMatch(subshardName);
if (match == null || match.groupCount != 2) { if (match == null || match.groupCount != 2) {
print('${red}Invalid subshard name "$subshardName". Expected format "[int]_[int]" ex. "1_3"'); foundError(<String>[
exit(1); '${red}Invalid subshard name "$subshardName". Expected format "[int]_[int]" ex. "1_3"',
]);
return <T>[];
} }
// One-indexed. // One-indexed.
final int index = int.parse(match!.group(1)!); final int index = int.parse(match.group(1)!);
final int total = int.parse(match.group(2)!); final int total = int.parse(match.group(2)!);
if (index > total) { if (index > total) {
print('${red}Invalid subshard name "$subshardName". Index number must be greater or equal to total.'); foundError(<String>[
exit(1); '${red}Invalid subshard name "$subshardName". Index number must be greater or equal to total.',
]);
return <T>[];
} }
final int testsPerShard = (tests.length / total).ceil(); final int testsPerShard = (tests.length / total).ceil();
...@@ -1965,9 +1972,11 @@ Future<void> _runFromList(Map<String, ShardRunner> items, String key, String nam ...@@ -1965,9 +1972,11 @@ Future<void> _runFromList(Map<String, ShardRunner> items, String key, String nam
} }
} else { } else {
if (!items.containsKey(item)) { if (!items.containsKey(item)) {
print('${red}Invalid $name: $item$reset'); foundError(<String>[
print('The available ${name}s are: ${items.keys.join(", ")}'); '${red}Invalid $name: $item$reset',
exit(1); 'The available ${name}s are: ${items.keys.join(", ")}',
]);
return;
} }
print('$bold$key=$item$reset'); print('$bold$key=$item$reset');
await items[item]!(); await items[item]!();
......
...@@ -12,21 +12,22 @@ import 'common.dart'; ...@@ -12,21 +12,22 @@ import 'common.dart';
typedef AsyncVoidCallback = Future<void> Function(); typedef AsyncVoidCallback = Future<void> Function();
Future<String> capture(AsyncVoidCallback callback, { int exitCode = 0 }) async { Future<String> capture(AsyncVoidCallback callback, { bool shouldHaveErrors = false }) async {
final StringBuffer buffer = StringBuffer(); final StringBuffer buffer = StringBuffer();
final PrintCallback oldPrint = print; final PrintCallback oldPrint = print;
try { try {
print = (Object line) { print = (Object line) {
buffer.writeln(line); buffer.writeln(line);
}; };
try {
await callback(); await callback();
expect(exitCode, 0); expect(
} on ExitException catch (error) { hasError,
expect(error.exitCode, exitCode); shouldHaveErrors,
} reason: buffer.isEmpty ? '(No output to report.)' : hasError ? 'Unexpected errors:\n$buffer' : 'Unexpected success:\n$buffer',
);
} finally { } finally {
print = oldPrint; print = oldPrint;
resetErrorStatus();
} }
if (stdout.supportsAnsiEscapes) { if (stdout.supportsAnsiEscapes) {
// Remove ANSI escapes when this test is running on a terminal. // Remove ANSI escapes when this test is running on a terminal.
...@@ -42,7 +43,7 @@ void main() { ...@@ -42,7 +43,7 @@ void main() {
final String dartPath = path.canonicalize(path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', dartName)); final String dartPath = path.canonicalize(path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', dartName));
test('analyze.dart - verifyDeprecations', () async { test('analyze.dart - verifyDeprecations', () async {
final String result = await capture(() => verifyDeprecations(testRootPath, minimumMatches: 2), exitCode: 1); final String result = await capture(() => verifyDeprecations(testRootPath, minimumMatches: 2), shouldHaveErrors: true);
final String lines = <String>[ final String lines = <String>[
'test/analyze-test-input/root/packages/foo/deprecation.dart:12: Deprecation notice does not match required pattern.', 'test/analyze-test-input/root/packages/foo/deprecation.dart:12: Deprecation notice does not match required pattern.',
'test/analyze-test-input/root/packages/foo/deprecation.dart:18: Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: STYLE_GUIDE_URL', 'test/analyze-test-input/root/packages/foo/deprecation.dart:18: Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: STYLE_GUIDE_URL',
...@@ -73,7 +74,7 @@ void main() { ...@@ -73,7 +74,7 @@ void main() {
}); });
test('analyze.dart - verifyGoldenTags', () async { test('analyze.dart - verifyGoldenTags', () async {
final String result = await capture(() => verifyGoldenTags(testRootPath, minimumMatches: 6), exitCode: 1); final String result = await capture(() => verifyGoldenTags(testRootPath, minimumMatches: 6), shouldHaveErrors: true);
const String noTag = "Files containing golden tests must be tagged using @Tags(<String>['reduced-test-set']) " const String noTag = "Files containing golden tests must be tagged using @Tags(<String>['reduced-test-set']) "
'at the top of the file before import statements.'; 'at the top of the file before import statements.';
const String missingTag = "Files containing golden tests must be tagged with 'reduced-test-set'."; const String missingTag = "Files containing golden tests must be tagged with 'reduced-test-set'.";
...@@ -117,7 +118,7 @@ void main() { ...@@ -117,7 +118,7 @@ void main() {
}); });
test('analyze.dart - verifyNoMissingLicense', () async { test('analyze.dart - verifyNoMissingLicense', () async {
final String result = await capture(() => verifyNoMissingLicense(testRootPath, checkMinimums: false), exitCode: 1); final String result = await capture(() => verifyNoMissingLicense(testRootPath, checkMinimums: false), shouldHaveErrors: true);
final String file = 'test/analyze-test-input/root/packages/foo/foo.dart' final String file = 'test/analyze-test-input/root/packages/foo/foo.dart'
.replaceAll('/', Platform.isWindows ? r'\' : '/'); .replaceAll('/', Platform.isWindows ? r'\' : '/');
expect(result, expect(result,
...@@ -137,7 +138,7 @@ void main() { ...@@ -137,7 +138,7 @@ void main() {
}); });
test('analyze.dart - verifyNoTrailingSpaces', () async { test('analyze.dart - verifyNoTrailingSpaces', () async {
final String result = await capture(() => verifyNoTrailingSpaces(testRootPath, minimumMatches: 2), exitCode: 1); final String result = await capture(() => verifyNoTrailingSpaces(testRootPath, minimumMatches: 2), shouldHaveErrors: true);
final String lines = <String>[ final String lines = <String>[
'test/analyze-test-input/root/packages/foo/spaces.txt:5: trailing U+0020 space character', 'test/analyze-test-input/root/packages/foo/spaces.txt:5: trailing U+0020 space character',
'test/analyze-test-input/root/packages/foo/spaces.txt:9: trailing blank line', 'test/analyze-test-input/root/packages/foo/spaces.txt:9: trailing blank line',
...@@ -155,7 +156,7 @@ void main() { ...@@ -155,7 +156,7 @@ void main() {
final String result = await capture(() => verifyNoBinaries( final String result = await capture(() => verifyNoBinaries(
testRootPath, testRootPath,
legacyBinaries: <Hash256>{const Hash256(0x39A050CD69434936, 0, 0, 0)}, legacyBinaries: <Hash256>{const Hash256(0x39A050CD69434936, 0, 0, 0)},
), exitCode: Platform.isWindows ? 0 : 1); ), shouldHaveErrors: !Platform.isWindows);
if (!Platform.isWindows) { if (!Platform.isWindows) {
expect(result, expect(result,
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'
...@@ -173,7 +174,7 @@ void main() { ...@@ -173,7 +174,7 @@ void main() {
}); });
test('analyze.dart - verifyInternationalizations - comparison fails', () async { test('analyze.dart - verifyInternationalizations - comparison fails', () async {
final String result = await capture(() => verifyInternationalizations(testRootPath, dartPath), exitCode: 1); final String result = await capture(() => verifyInternationalizations(testRootPath, dartPath), shouldHaveErrors: true);
final String genLocalizationsScript = path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart'); final String genLocalizationsScript = path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart');
expect(result, expect(result,
contains('$dartName $genLocalizationsScript --cupertino')); contains('$dartName $genLocalizationsScript --cupertino'));
...@@ -201,7 +202,7 @@ void main() { ...@@ -201,7 +202,7 @@ void main() {
final String result = await capture(() => verifyNullInitializedDebugExpensiveFields( final String result = await capture(() => verifyNullInitializedDebugExpensiveFields(
testRootPath, testRootPath,
minimumMatches: 1, minimumMatches: 1,
), exitCode: 1); ), shouldHaveErrors: true);
expect(result, contains('L15')); expect(result, contains('L15'));
expect(result, isNot(contains('L12'))); expect(result, isNot(contains('L12')));
......
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
import 'dart:core' as core_internals show print; import 'dart:core' as core_internals show print;
import 'dart:core' hide print; import 'dart:core' hide print;
import 'dart:io' as io_internals show exit; import 'dart:io' as system show exit;
import 'dart:io' hide exit; import 'dart:io' hide exit;
import 'package:meta/meta.dart';
final bool hasColor = stdout.supportsAnsiEscapes; final bool hasColor = stdout.supportsAnsiEscapes;
final String bold = hasColor ? '\x1B[1m' : ''; // used for shard titles final String bold = hasColor ? '\x1B[1m' : ''; // used for shard titles
...@@ -16,36 +18,41 @@ final String yellow = hasColor ? '\x1B[33m' : ''; // used for skips ...@@ -16,36 +18,41 @@ final String yellow = hasColor ? '\x1B[33m' : ''; // used for skips
final String cyan = hasColor ? '\x1B[36m' : ''; // used for paths final String cyan = hasColor ? '\x1B[36m' : ''; // used for paths
final String reverse = hasColor ? '\x1B[7m' : ''; // used for clocks final String reverse = hasColor ? '\x1B[7m' : ''; // used for clocks
final String reset = hasColor ? '\x1B[0m' : ''; final String reset = hasColor ? '\x1B[0m' : '';
final String redLine = '$red━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$reset';
class ExitException implements Exception { typedef PrintCallback = void Function(Object line);
ExitException(this.exitCode);
final int exitCode; // Allow print() to be overridden, for tests.
PrintCallback print = core_internals.print;
void apply() { bool get hasError => _hasError;
io_internals.exit(exitCode); bool _hasError = false;
}
}
// We actually reimplement exit() so that it uses exceptions rather Iterable<String> get errorMessages => _errorMessages;
// than truly immediately terminating the application, so that we can List<String> _errorMessages = <String>[];
// test the exit code in unit tests (see test/analyze_test.dart).
void exit(int exitCode) {
throw ExitException(exitCode);
}
void exitWithError(List<String> messages) { void foundError(List<String> messages) {
final String redLine = '$red━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$reset'; assert(messages.isNotEmpty);
print(redLine); print(redLine);
messages.forEach(print); messages.forEach(print);
print(redLine); print(redLine);
exit(1); _errorMessages.addAll(messages);
_hasError = true;
} }
typedef PrintCallback = void Function(Object line); @visibleForTesting
void resetErrorStatus() {
_hasError = false;
_errorMessages.clear();
}
// Allow print() to be overridden, for tests. Never reportErrorsAndExit() {
PrintCallback print = core_internals.print; print(redLine);
print('For your convenience, the error messages reported above are repeated here:');
_errorMessages.forEach(print);
print(redLine);
system.exit(1);
}
String get clock { String get clock {
final DateTime now = DateTime.now(); final DateTime now = DateTime.now();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment