From 52ae102f182afaa0524d0d01d21b2d86d15a11dc Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 10 Nov 2021 16:13:04 -0800 Subject: [PATCH] Adds tool warning log level and command line options to fail on warning/error output (#92031) --- dev/bots/test.dart | 16 +- dev/conductor/core/lib/src/codesign.dart | 83 ++++--- dev/conductor/core/lib/src/stdio.dart | 23 +- dev/devicelab/lib/command/test.dart | 1 + .../bin/fuchsia_asset_builder.dart | 4 +- packages/flutter_tools/lib/runner.dart | 2 +- .../flutter_tools/lib/src/base/logger.dart | 218 ++++++++++++++++-- .../flutter_tools/lib/src/build_info.dart | 2 +- .../lib/src/build_system/targets/web.dart | 2 +- .../flutter_tools/lib/src/bundle_builder.dart | 2 +- packages/flutter_tools/lib/src/cache.dart | 21 +- .../flutter_tools/lib/src/commands/build.dart | 3 +- .../lib/src/commands/build_aar.dart | 2 +- .../lib/src/commands/build_apk.dart | 2 +- .../lib/src/commands/build_appbundle.dart | 4 +- .../lib/src/commands/build_bundle.dart | 5 +- .../lib/src/commands/build_fuchsia.dart | 4 +- .../lib/src/commands/build_ios.dart | 8 +- .../lib/src/commands/build_ios_framework.dart | 3 +- .../lib/src/commands/build_linux.dart | 3 +- .../lib/src/commands/build_macos.dart | 4 +- .../lib/src/commands/build_web.dart | 2 +- .../lib/src/commands/build_windows.dart | 4 +- .../lib/src/commands/build_winuwp.dart | 4 +- .../lib/src/commands/create.dart | 2 +- .../lib/src/commands/daemon.dart | 14 +- .../lib/src/commands/devices.dart | 2 +- .../lib/src/commands/install.dart | 2 +- .../flutter_tools/lib/src/commands/run.dart | 1 + .../flutter_tools/lib/src/commands/test.dart | 7 +- .../lib/src/commands/update_packages.dart | 6 +- .../lib/src/flutter_manifest.dart | 6 +- .../lib/src/flutter_plugins.dart | 8 +- .../lib/src/fuchsia/fuchsia_build.dart | 4 + packages/flutter_tools/lib/src/globals.dart | 24 ++ .../lib/src/globals_null_migrated.dart | 0 .../lib/src/macos/cocoapods.dart | 16 +- .../flutter_tools/lib/src/macos/xcdevice.dart | 2 +- .../flutter_tools/lib/src/mdns_discovery.dart | 2 +- packages/flutter_tools/lib/src/project.dart | 17 -- .../lib/src/resident_runner.dart | 8 +- packages/flutter_tools/lib/src/run_cold.dart | 4 +- .../lib/src/runner/flutter_command.dart | 27 ++- packages/flutter_tools/lib/src/version.dart | 4 +- .../commands.shard/hermetic/attach_test.dart | 14 ++ .../hermetic/build_linux_test.dart | 4 + .../commands.shard/hermetic/build_test.dart | 122 +++++++++- .../commands.shard/hermetic/daemon_test.dart | 23 ++ .../commands.shard/hermetic/run_test.dart | 97 +++++++- .../commands.shard/hermetic/test_test.dart | 55 +++++ .../test/general.shard/asset_bundle_test.dart | 2 +- .../test/general.shard/cache_test.dart | 6 +- .../general.shard/commands/build_test.dart | 2 + .../general.shard/macos/cocoapods_test.dart | 4 +- .../test/general.shard/plugins_test.dart | 8 +- .../test/general.shard/project_test.dart | 2 +- .../runner/flutter_command_test.dart | 2 +- .../general.shard/update_packages_test.dart | 2 +- .../test/integration.shard/cache_test.dart | 154 +++++++++---- .../test/integration.shard/test_test.dart | 60 ++--- 60 files changed, 873 insertions(+), 262 deletions(-) create mode 100644 packages/flutter_tools/lib/src/globals_null_migrated.dart diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 5191aabb18..7ecbdd10f7 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -170,6 +170,7 @@ Future main(List args) async { try { flutterTestArgs.addAll(args); final Set removeArgs = {}; + bool runSmokeTests = true; for (final String arg in args) { if (arg.startsWith('--local-engine=')) { localEngineEnv['FLUTTER_LOCAL_ENGINE'] = arg.substring('--local-engine='.length); @@ -181,12 +182,18 @@ Future main(List args) async { _shuffleSeed = arg.substring('--test-randomize-ordering-seed='.length); removeArgs.add(arg); } + if (arg == '--no-smoke-tests') { + runSmokeTests = false; + removeArgs.add(arg); + } } flutterTestArgs.removeWhere((String arg) => removeArgs.contains(arg)); if (Platform.environment.containsKey(CIRRUS_TASK_NAME)) print('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}'); print('═' * 80); - await _runSmokeTests(); + if (runSmokeTests) { + await _runSmokeTests(); + } print('═' * 80); await selectShard({ 'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests, @@ -474,7 +481,7 @@ Future _runBuildTests() async { Future _runExampleProjectBuildTests(Directory exampleDirectory, [File? mainFile]) async { // Only verify caching with flutter gallery. final bool verifyCaching = exampleDirectory.path.contains('flutter_gallery'); - final String examplePath = exampleDirectory.path; + final String examplePath = path.relative(exampleDirectory.path, from: Directory.current.path); final bool hasNullSafety = File(path.join(examplePath, 'null_safety')).existsSync(); final List additionalArgs = [ if (hasNullSafety) '--no-sound-null-safety', @@ -786,7 +793,8 @@ Future _runFrameworkTests() async { await _pubRunTest(path.join(flutterRoot, 'dev', 'bots')); await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), ensurePrecompiledTool: false); // See https://github.com/flutter/flutter/issues/86209 await _pubRunTest(path.join(flutterRoot, 'dev', 'conductor', 'core'), forceSingleCore: true); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing')); + // TODO(gspencergoog): Remove the exception for fatalWarnings once https://github.com/flutter/flutter/pull/91127 has landed. + await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), fatalWarnings: false); await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes')); @@ -1621,6 +1629,7 @@ Future _runFlutterTest(String workingDirectory, { Map? environment, List tests = const [], bool shuffleTests = true, + bool fatalWarnings = true, }) async { assert(!printOutput || outputChecker == null, 'Output either can be printed or checked but not both'); @@ -1634,6 +1643,7 @@ Future _runFlutterTest(String workingDirectory, { final List args = [ 'test', if (shuffleTests) '--test-randomize-ordering-seed=$shuffleSeed', + if (fatalWarnings) '--fatal-warnings', ...options, ...tags, ...flutterTestArgs, diff --git a/dev/conductor/core/lib/src/codesign.dart b/dev/conductor/core/lib/src/codesign.dart index 7622551b33..8fa00992c9 100644 --- a/dev/conductor/core/lib/src/codesign.dart +++ b/dev/conductor/core/lib/src/codesign.dart @@ -43,15 +43,13 @@ class CodesignCommand extends Command { } argParser.addFlag( kVerify, - help: - 'Only verify expected binaries exist and are codesigned with entitlements.', + help: 'Only verify expected binaries exist and are codesigned with entitlements.', ); argParser.addFlag( kSignatures, defaultsTo: true, - help: - 'When off, this command will only verify the existence of binaries, and not their\n' - 'signatures or entitlements. Must be used with --verify flag.', + help: 'When off, this command will only verify the existence of binaries, and not their\n' + 'signatures or entitlements. Must be used with --verify flag.', ); argParser.addOption( kUpstream, @@ -92,24 +90,25 @@ class CodesignCommand extends Command { Future run() async { if (!platform.isMacOS) { throw ConductorException( - 'Error! Expected operating system "macos", actual operating system is: ' - '"${platform.operatingSystem}"'); + 'Error! Expected operating system "macos", actual operating system is: ' + '"${platform.operatingSystem}"', + ); } if (argResults!['verify'] as bool != true) { throw ConductorException( - 'Sorry, but codesigning is not implemented yet. Please pass the ' - '--$kVerify flag to verify signatures.'); + 'Sorry, but codesigning is not implemented yet. Please pass the ' + '--$kVerify flag to verify signatures.', + ); } String revision; if (argResults!.wasParsed(kRevision)) { - stdio.printError( - 'Warning! When providing an arbitrary revision, the contents of the cache may not'); - stdio.printError( - 'match the expected binaries in the conductor tool. It is preferred to check out'); - stdio.printError( - 'the desired revision and run that version of the conductor.\n'); + stdio.printWarning( + 'Warning! When providing an arbitrary revision, the contents of the cache may not ' + 'match the expected binaries in the conductor tool. It is preferred to check out ' + 'the desired revision and run that version of the conductor.\n', + ); revision = argResults![kRevision] as String; } else { revision = ((await processManager.run( @@ -225,13 +224,11 @@ class CodesignCommand extends Command { ) .toList(); stdio.printError( - 'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n'); - stdio.printError( - 'If this commit is removing binaries from the cache, this test should be fixed by'); - stdio.printError( - 'removing the relevant entry from either the `binariesWithEntitlements` or'); - stdio.printError( - '`binariesWithoutEntitlements` getters in dev/tools/lib/codesign.dart.'); + 'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n\n' + 'If this commit is removing binaries from the cache, this test should be fixed by\n' + 'removing the relevant entry from either the "binariesWithEntitlements" or\n' + '"binariesWithoutEntitlements" getters in dev/tools/lib/codesign.dart.', + ); throw ConductorException('Did not find all expected binaries!'); } @@ -271,9 +268,10 @@ class CodesignCommand extends Command { if (codeSignResult.exitCode != 0) { unsignedBinaries.add(binaryPath); stdio.printError( - 'File "$binaryPath" does not appear to be codesigned.\n' - 'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n' - '${codeSignResult.stderr}\n'); + 'File "$binaryPath" does not appear to be codesigned.\n' + 'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n' + '${codeSignResult.stderr}\n', + ); continue; } if (verifyEntitlements) { @@ -291,42 +289,39 @@ class CodesignCommand extends Command { } if (wrongEntitlementBinaries.isNotEmpty) { - stdio.printError( - 'Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:'); + stdio.printError('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:'); wrongEntitlementBinaries.forEach(stdio.printError); } if (unexpectedBinaries.isNotEmpty) { - stdio.printError( - 'Found ${unexpectedBinaries.length} unexpected binaries in the cache:'); + stdio.printError('Found ${unexpectedBinaries.length} unexpected binaries in the cache:'); unexpectedBinaries.forEach(print); } // Finally, exit on any invalid state if (unsignedBinaries.isNotEmpty) { - throw ConductorException( - 'Test failed because unsigned binaries detected.'); + throw ConductorException('Test failed because unsigned binaries detected.'); } if (wrongEntitlementBinaries.isNotEmpty) { throw ConductorException( - 'Test failed because files found with the wrong entitlements:\n' - '${wrongEntitlementBinaries.join('\n')}'); + 'Test failed because files found with the wrong entitlements:\n' + '${wrongEntitlementBinaries.join('\n')}', + ); } if (unexpectedBinaries.isNotEmpty) { - throw ConductorException( - 'Test failed because unexpected binaries found in the cache.'); + throw ConductorException('Test failed because unexpected binaries found in the cache.'); } final String? desiredRevision = argResults![kRevision] as String?; if (desiredRevision == null) { - stdio.printStatus( - 'Verified that binaries are codesigned and have expected entitlements.'); + stdio.printStatus('Verified that binaries are codesigned and have expected entitlements.'); } else { stdio.printStatus( - 'Verified that binaries for commit $desiredRevision are codesigned and have ' - 'expected entitlements.'); + 'Verified that binaries for commit $desiredRevision are codesigned and have ' + 'expected entitlements.', + ); } } @@ -387,8 +382,9 @@ class CodesignCommand extends Command { if (entitlementResult.exitCode != 0) { stdio.printError( - 'The `codesign --entitlements` command failed with exit code ${entitlementResult.exitCode}:\n' - '${entitlementResult.stderr}\n'); + 'The `codesign --entitlements` command failed with exit code ${entitlementResult.exitCode}:\n' + '${entitlementResult.stderr}\n', + ); return false; } @@ -399,8 +395,9 @@ class CodesignCommand extends Command { (await binariesWithEntitlements).contains(binaryPath); if (output.contains(entitlement) != entitlementExpected) { stdio.printError( - 'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} ' - 'entitlement $entitlement.'); + 'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} ' + 'entitlement $entitlement.', + ); passes = false; } } diff --git a/dev/conductor/core/lib/src/stdio.dart b/dev/conductor/core/lib/src/stdio.dart index 90765115f0..11276d048e 100644 --- a/dev/conductor/core/lib/src/stdio.dart +++ b/dev/conductor/core/lib/src/stdio.dart @@ -9,25 +9,44 @@ import 'package:meta/meta.dart'; abstract class Stdio { final List logs = []; - /// Error/warning messages printed to STDERR. + /// Error messages printed to STDERR. + /// + /// Display an error `message` to the user on stderr. Print errors if the code + /// fails in some way. Errors are typically followed shortly by exiting the + /// app with a non-zero exit status. @mustCallSuper void printError(String message) { logs.add('[error] $message'); } + /// Warning messages printed to STDERR. + /// + /// Display a warning `message` to the user on stderr. Print warnings if there + /// is important information to convey to the user that is not fatal. + @mustCallSuper + void printWarning(String message) { + logs.add('[warning] $message'); + } + /// Ordinary STDOUT messages. + /// + /// Displays normal output on stdout. This should be used for things like + /// progress messages, success messages, or just normal command output. @mustCallSuper void printStatus(String message) { logs.add('[status] $message'); } /// Debug messages that are only printed in verbose mode. + /// + /// Use this for verbose tracing output. Users can turn this output on in order + /// to help diagnose issues. @mustCallSuper void printTrace(String message) { logs.add('[trace] $message'); } - /// Write string to STDOUT without trailing newline. + /// Write the `message` string to STDOUT without a trailing newline. @mustCallSuper void write(String message) { logs.add('[write] $message'); diff --git a/dev/devicelab/lib/command/test.dart b/dev/devicelab/lib/command/test.dart index 62c947bab7..437b80d94c 100644 --- a/dev/devicelab/lib/command/test.dart +++ b/dev/devicelab/lib/command/test.dart @@ -49,6 +49,7 @@ class TestCommand extends Command { 'task, will write test results to the file.'); argParser.addFlag( 'silent', + help: 'Suppresses standard output and only print standard error output.', ); } diff --git a/packages/flutter_tools/bin/fuchsia_asset_builder.dart b/packages/flutter_tools/bin/fuchsia_asset_builder.dart index ce20d9b41d..d787a42850 100644 --- a/packages/flutter_tools/bin/fuchsia_asset_builder.dart +++ b/packages/flutter_tools/bin/fuchsia_asset_builder.dart @@ -6,6 +6,7 @@ import 'package:args/args.dart'; import 'package:flutter_tools/src/asset.dart' hide defaultManifestPath; +import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/file_system.dart' as libfs; import 'package:flutter_tools/src/base/io.dart'; @@ -70,8 +71,7 @@ Future run(List args) async { ); if (assets == null) { - print('Unable to find assets.'); - exit(1); + throwToolExit('Unable to find assets.', exitCode: 1); } final List> calls = >[]; diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index f5435aca6e..b6bde825b8 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -39,7 +39,7 @@ Future run( // Remove the verbose option; for help and doctor, users don't need to see // verbose logs. args = List.of(args); - args.removeWhere((String option) => option == '-v' || option == '--verbose'); + args.removeWhere((String option) => option == '-vv' || option == '-v' || option == '--verbose'); } return runInContext(() async { diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart index 4c605b0ee3..916efe1dbd 100644 --- a/packages/flutter_tools/lib/src/base/logger.dart +++ b/packages/flutter_tools/lib/src/base/logger.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import '../convert.dart'; +import 'common.dart'; import 'io.dart'; import 'terminal.dart' show Terminal, TerminalColor, OutputPreferences; import 'utils.dart'; @@ -28,22 +29,41 @@ class StopwatchFactory { typedef VoidCallback = void Function(); abstract class Logger { + /// Whether or not this logger should print [printTrace] messages. bool get isVerbose => false; + /// If true, silences the logger output. bool quiet = false; + /// If true, this logger supports color output. bool get supportsColor; + /// If true, this logger is connected to a terminal. bool get hasTerminal; + /// If true, then [printError] has been called at least once for this logger + /// since the last time it was set to false. + bool hadErrorOutput = false; + + /// If true, then [printWarning] has been called at least once for this logger + /// since the last time it was reset to false. + bool hadWarningOutput = false; + + /// Causes [checkForFatalLogs] to call [throwToolExit] when it is called if + /// [hadWarningOutput] is true. + bool fatalWarnings = false; + + /// Returns the terminal attached to this logger. Terminal get terminal; OutputPreferences get _outputPreferences; /// Display an error `message` to the user. Commands should use this if they - /// fail in some way. + /// fail in some way. Errors are typically followed shortly by a call to + /// [throwToolExit] to terminate the run. /// - /// The `message` argument is printed to the stderr in red by default. + /// The `message` argument is printed to the stderr in [TerminalColor.red] by + /// default. /// /// The `stackTrace` argument is the stack trace that will be printed if /// supplied. @@ -74,10 +94,41 @@ abstract class Logger { bool? wrap, }); + /// Display a warning `message` to the user. Commands should use this if they + /// important information to convey to the user that is not fatal. + /// + /// The `message` argument is printed to the stderr in [TerminalColor.cyan] by + /// default. + /// + /// The `emphasis` argument will cause the output message be printed in bold text. + /// + /// The `color` argument will print the message in the supplied color instead + /// of the default of cyan. Colors will not be printed if the output terminal + /// doesn't support them. + /// + /// The `indent` argument specifies the number of spaces to indent the overall + /// message. If wrapping is enabled in [outputPreferences], then the wrapped + /// lines will be indented as well. + /// + /// If `hangingIndent` is specified, then any wrapped lines will be indented + /// by this much more than the first line, if wrapping is enabled in + /// [outputPreferences]. + /// + /// If `wrap` is specified, then it overrides the + /// `outputPreferences.wrapText` setting. + void printWarning( + String message, { + bool? emphasis, + TerminalColor? color, + int? indent, + int? hangingIndent, + bool? wrap, + }); + /// Display normal output of the command. This should be used for things like /// progress messages, success messages, or just normal command output. /// - /// The `message` argument is printed to the stderr in red by default. + /// The `message` argument is printed to the stdout. /// /// The `stackTrace` argument is the stack trace that will be printed if /// supplied. @@ -144,9 +195,21 @@ abstract class Logger { /// Clears all output. void clear(); + + /// If [fatalWarnings] is set, causes the logger to check if + /// [hadWarningOutput] is true, and then to call [throwToolExit] if so. + /// + /// The [fatalWarnings] flag can be set from the command line with the + /// "--fatal-warnings" option on commands that support it. + void checkForFatalLogs() { + if (fatalWarnings && (hadWarningOutput || hadErrorOutput)) { + throwToolExit('Logger received ${hadErrorOutput ? 'error' : 'warning'} output ' + 'during the run, and "--fatal-warnings" is enabled.'); + } + } } -/// A [Logger] that forwards all methods to another one. +/// A [Logger] that forwards all methods to another logger. /// /// Classes can derive from this to add functionality to an existing [Logger]. class DelegatingLogger implements Logger { @@ -174,6 +237,24 @@ class DelegatingLogger implements Logger { @override bool get isVerbose => _delegate.isVerbose; + @override + bool get hadErrorOutput => _delegate.hadErrorOutput; + + @override + set hadErrorOutput(bool value) => _delegate.hadErrorOutput = value; + + @override + bool get hadWarningOutput => _delegate.hadWarningOutput; + + @override + set hadWarningOutput(bool value) => _delegate.hadWarningOutput = value; + + @override + bool get fatalWarnings => _delegate.fatalWarnings; + + @override + set fatalWarnings(bool value) => _delegate.fatalWarnings = value; + @override void printError(String message, { StackTrace? stackTrace, @@ -194,6 +275,24 @@ class DelegatingLogger implements Logger { ); } + @override + void printWarning(String message, { + bool? emphasis, + TerminalColor? color, + int? indent, + int? hangingIndent, + bool? wrap, + }) { + _delegate.printWarning( + message, + emphasis: emphasis, + color: color, + indent: indent, + hangingIndent: hangingIndent, + wrap: wrap, + ); + } + @override void printStatus(String message, { bool? emphasis, @@ -244,6 +343,9 @@ class DelegatingLogger implements Logger { @override void clear() => _delegate.clear(); + + @override + void checkForFatalLogs() => _delegate.checkForFatalLogs(); } /// If [logger] is a [DelegatingLogger], walks the delegate chain and returns @@ -303,6 +405,7 @@ class StdoutLogger extends Logger { int? hangingIndent, bool? wrap, }) { + hadErrorOutput = true; _status?.pause(); message = wrapText(message, indent: indent, @@ -321,6 +424,31 @@ class StdoutLogger extends Logger { _status?.resume(); } + @override + void printWarning( + String message, { + bool? emphasis, + TerminalColor? color, + int? indent, + int? hangingIndent, + bool? wrap, + }) { + hadWarningOutput = true; + _status?.pause(); + message = wrapText(message, + indent: indent, + hangingIndent: hangingIndent, + shouldWrap: wrap ?? _outputPreferences.wrapText, + columnWidth: _outputPreferences.wrapColumn, + ); + if (emphasis == true) { + message = terminal.bolden(message); + } + message = terminal.color(message, color ?? TerminalColor.cyan); + writeToStdErr('$message\n'); + _status?.resume(); + } + @override void printStatus( String message, { @@ -482,7 +610,6 @@ class BufferLogger extends Logger { _outputPreferences = outputPreferences ?? OutputPreferences.test(), _stopwatchFactory = const StopwatchFactory(); - @override final OutputPreferences _outputPreferences; @@ -498,11 +625,13 @@ class BufferLogger extends Logger { bool get supportsColor => terminal.supportsColor; final StringBuffer _error = StringBuffer(); + final StringBuffer _warning = StringBuffer(); final StringBuffer _status = StringBuffer(); final StringBuffer _trace = StringBuffer(); final StringBuffer _events = StringBuffer(); String get errorText => _error.toString(); + String get warningText => _warning.toString(); String get statusText => _status.toString(); String get traceText => _trace.toString(); String get eventText => _events.toString(); @@ -520,6 +649,7 @@ class BufferLogger extends Logger { int? hangingIndent, bool? wrap, }) { + hadErrorOutput = true; _error.writeln(terminal.color( wrapText(message, indent: indent, @@ -531,6 +661,27 @@ class BufferLogger extends Logger { )); } + @override + void printWarning( + String message, { + bool? emphasis, + TerminalColor? color, + int? indent, + int? hangingIndent, + bool? wrap, + }) { + hadWarningOutput = true; + _warning.writeln(terminal.color( + wrapText(message, + indent: indent, + hangingIndent: hangingIndent, + shouldWrap: wrap ?? _outputPreferences.wrapText, + columnWidth: _outputPreferences.wrapColumn, + ), + color ?? TerminalColor.cyan, + )); + } + @override void printStatus( String message, { @@ -625,6 +776,7 @@ class VerboseLogger extends DelegatingLogger { int? hangingIndent, bool? wrap, }) { + hadErrorOutput = true; _emit( _LogType.error, wrapText(message, @@ -637,6 +789,29 @@ class VerboseLogger extends DelegatingLogger { ); } + @override + void printWarning( + String message, { + StackTrace? stackTrace, + bool? emphasis, + TerminalColor? color, + int? indent, + int? hangingIndent, + bool? wrap, + }) { + hadWarningOutput = true; + _emit( + _LogType.warning, + wrapText(message, + indent: indent, + hangingIndent: hangingIndent, + shouldWrap: wrap ?? _outputPreferences.wrapText, + columnWidth: _outputPreferences.wrapColumn, + ), + stackTrace, + ); + } + @override void printStatus( String message, { @@ -707,15 +882,25 @@ class VerboseLogger extends DelegatingLogger { final String indent = ''.padLeft(prefix.length); final String indentMessage = message.replaceAll('\n', '\n$indent'); - if (type == _LogType.error) { - super.printError(prefix + terminal.bolden(indentMessage)); - if (stackTrace != null) { - super.printError(indent + stackTrace.toString().replaceAll('\n', '\n$indent')); - } - } else if (type == _LogType.status) { - super.printStatus(prefix + terminal.bolden(indentMessage)); - } else { - super.printStatus(prefix + indentMessage); + switch (type) { + case _LogType.error: + super.printError(prefix + terminal.bolden(indentMessage)); + if (stackTrace != null) { + super.printError(indent + stackTrace.toString().replaceAll('\n', '\n$indent')); + } + break; + case _LogType.warning: + super.printWarning(prefix + terminal.bolden(indentMessage)); + break; + case _LogType.status: + super.printStatus(prefix + terminal.bolden(indentMessage)); + break; + case _LogType.trace: + // This seems wrong, since there is a 'printTrace' to call on the + // superclass, but it's actually the entire point of this logger: to + // make things more verbose than they normally would be. + super.printStatus(prefix + indentMessage); + break; } } @@ -736,6 +921,7 @@ class PrefixedErrorLogger extends DelegatingLogger { int? hangingIndent, bool? wrap, }) { + hadErrorOutput = true; if (message.trim().isNotEmpty == true) { message = 'ERROR: $message'; } @@ -751,7 +937,7 @@ class PrefixedErrorLogger extends DelegatingLogger { } } -enum _LogType { error, status, trace } +enum _LogType { error, warning, status, trace } typedef SlowWarningCallback = String Function(); @@ -820,7 +1006,7 @@ abstract class Status { } } -/// A [SilentStatus] shows nothing. +/// A [Status] that shows nothing. class SilentStatus extends Status { SilentStatus({ required Stopwatch stopwatch, diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 06e429acf6..e8d5a7551e 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -751,7 +751,7 @@ HostPlatform getCurrentHostPlatform() { return HostPlatform.windows_x64; } - globals.printError('Unsupported host platform, defaulting to Linux'); + globals.printWarning('Unsupported host platform, defaulting to Linux'); return HostPlatform.linux_x64; } diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index 563d9a405e..d28459c4cf 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -266,7 +266,7 @@ class Dart2JSTarget extends Target { final File dart2jsDeps = environment.buildDir .childFile('app.dill.deps'); if (!dart2jsDeps.existsSync()) { - globals.printError('Warning: dart2js did not produced expected deps list at ' + globals.printWarning('Warning: dart2js did not produced expected deps list at ' '${dart2jsDeps.path}'); return; } diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart index 0c13cba45c..f18a090c5f 100644 --- a/packages/flutter_tools/lib/src/bundle_builder.dart +++ b/packages/flutter_tools/lib/src/bundle_builder.dart @@ -140,7 +140,7 @@ Future writeBundle( try { bundleDir.deleteSync(recursive: true); } on FileSystemException catch (err) { - loggerOverride.printError( + loggerOverride.printWarning( 'Failed to clean up asset directory ${bundleDir.path}: $err\n' 'To clean build artifacts, use the command "flutter clean".' ); diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index 254c68eb53..82c8c2c6c9 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -164,6 +164,7 @@ class Cache { late final ArtifactUpdater _artifactUpdater = _createUpdater(); + @visibleForTesting @protected void registerArtifact(ArtifactSet artifactSet) { _artifacts.add(artifactSet); @@ -320,13 +321,17 @@ class Cache { } on FileSystemException { if (!printed) { _logger.printTrace('Waiting to be able to obtain lock of Flutter binary artifacts directory: ${_lock!.path}'); - // This needs to go to stderr to avoid cluttering up stdout if a parent - // process is collecting stdout. It's not really an "error" though, - // so print it in grey. - _logger.printError( + // This needs to go to stderr to avoid cluttering up stdout if a + // parent process is collecting stdout (e.g. when calling "flutter + // version --machine"). It's not really a "warning" though, so print it + // in grey. Also, make sure that it isn't counted as a warning for + // Logger.warningsAreFatal. + final bool oldWarnings = _logger.hadWarningOutput; + _logger.printWarning( 'Waiting for another flutter command to release the startup lock...', color: TerminalColor.grey, ); + _logger.hadWarningOutput = oldWarnings; printed = true; } await Future.delayed(const Duration(milliseconds: 50)); @@ -509,7 +514,7 @@ class Cache { ErrorHandlingFileSystem.deleteIfExists(file); } } on FileSystemException catch (err) { - _logger.printError('Failed to delete some stamp files: $err'); + _logger.printWarning('Failed to delete some stamp files: $err'); } } @@ -703,7 +708,7 @@ abstract class CachedArtifact extends ArtifactSet { await updateInner(artifactUpdater, fileSystem, operatingSystemUtils); try { if (version == null) { - logger.printError( + logger.printWarning( 'No known version for the artifact name "$name". ' 'Flutter can continue, but the artifact may be re-downloaded on ' 'subsequent invocations until the problem is resolved.', @@ -712,7 +717,7 @@ abstract class CachedArtifact extends ArtifactSet { cache.setStampFor(stampName, version!); } } on FileSystemException catch (err) { - logger.printError( + logger.printWarning( 'The new artifact "$name" was downloaded, but Flutter failed to update ' 'its stamp file, receiving the error "$err". ' 'Flutter can continue, but the artifact may be re-downloaded on ' @@ -1103,7 +1108,7 @@ class ArtifactUpdater { try { file.deleteSync(); } on FileSystemException catch (e) { - _logger.printError('Failed to delete "${file.path}". Please delete manually. $e'); + _logger.printWarning('Failed to delete "${file.path}". Please delete manually. $e'); continue; } for (Directory directory = file.parent; directory.absolute.path != _tempStorage.absolute.path; directory = directory.parent) { diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index 25ac316ebb..af0105097b 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart @@ -65,8 +65,9 @@ class BuildCommand extends FlutterCommand { } abstract class BuildSubCommand extends FlutterCommand { - BuildSubCommand() { + BuildSubCommand({@required bool verboseHelp}) { requiresPubspecYaml(); + usesFatalWarningsOption(verboseHelp: verboseHelp); } @override diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart index aa26185837..96755ba7cd 100644 --- a/packages/flutter_tools/lib/src/commands/build_aar.dart +++ b/packages/flutter_tools/lib/src/commands/build_aar.dart @@ -20,7 +20,7 @@ import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; class BuildAarCommand extends BuildSubCommand { - BuildAarCommand({ @required bool verboseHelp }) { + BuildAarCommand({ @required bool verboseHelp }) : super(verboseHelp: verboseHelp) { argParser ..addFlag( 'debug', diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index 9c619dd511..4a03b0d787 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -16,7 +16,7 @@ import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; class BuildApkCommand extends BuildSubCommand { - BuildApkCommand({bool verboseHelp = false}) { + BuildApkCommand({bool verboseHelp = false}) : super(verboseHelp: verboseHelp) { addTreeShakeIconsFlag(); usesTargetOption(); addBuildModeFlags(verboseHelp: verboseHelp); diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart index 3cf4211305..0cd619c7bf 100644 --- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart @@ -19,7 +19,9 @@ import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; class BuildAppBundleCommand extends BuildSubCommand { - BuildAppBundleCommand({bool verboseHelp = false}) { + BuildAppBundleCommand({ + bool verboseHelp = false, + }) : super(verboseHelp: verboseHelp) { addTreeShakeIconsFlag(); usesTargetOption(); addBuildModeFlags(verboseHelp: verboseHelp); diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart index 44ec592d3e..56465a8535 100644 --- a/packages/flutter_tools/lib/src/commands/build_bundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart @@ -16,7 +16,10 @@ import '../runner/flutter_command.dart'; import 'build.dart'; class BuildBundleCommand extends BuildSubCommand { - BuildBundleCommand({bool verboseHelp = false, this.bundleBuilder}) { + BuildBundleCommand({ + bool verboseHelp = false, + this.bundleBuilder, + }) : super(verboseHelp: verboseHelp) { usesTargetOption(); usesFilesystemOptions(hide: !verboseHelp); usesBuildNumberOption(); diff --git a/packages/flutter_tools/lib/src/commands/build_fuchsia.dart b/packages/flutter_tools/lib/src/commands/build_fuchsia.dart index e983386ae2..290339c1e3 100644 --- a/packages/flutter_tools/lib/src/commands/build_fuchsia.dart +++ b/packages/flutter_tools/lib/src/commands/build_fuchsia.dart @@ -19,7 +19,9 @@ import 'build.dart'; /// A command to build a Fuchsia target. class BuildFuchsiaCommand extends BuildSubCommand { - BuildFuchsiaCommand({ @required bool verboseHelp }) { + BuildFuchsiaCommand({ + @required bool verboseHelp, + }) : super(verboseHelp: verboseHelp) { addTreeShakeIconsFlag(); usesTargetOption(); usesDartDefineOption(); diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index 25d33490e7..b1ec5d429b 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -129,7 +129,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { // xcarchive failed or not at expected location. if (xcarchiveResult.exitStatus != ExitStatus.success) { - globals.logger.printStatus('Skipping IPA'); + globals.printStatus('Skipping IPA'); return xcarchiveResult; } @@ -176,14 +176,16 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { throwToolExit('Encountered error while building IPA:\n$errorMessage'); } - globals.logger.printStatus('Built IPA to $outputPath.'); + globals.printStatus('Built IPA to $outputPath.'); return FlutterCommandResult.success(); } } abstract class _BuildIOSSubCommand extends BuildSubCommand { - _BuildIOSSubCommand({ @required bool verboseHelp }) { + _BuildIOSSubCommand({ + @required bool verboseHelp + }) : super(verboseHelp: verboseHelp) { addTreeShakeIconsFlag(); addSplitDebugInfoOption(); addBuildModeFlags(verboseHelp: verboseHelp); diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart index b4fb207b48..0132dc582b 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -39,7 +39,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { }) : _flutterVersion = flutterVersion, _buildSystem = buildSystem, _injectedCache = cache, - _injectedPlatform = platform { + _injectedPlatform = platform, + super(verboseHelp: verboseHelp) { addTreeShakeIconsFlag(); usesTargetOption(); usesFlavorOption(); diff --git a/packages/flutter_tools/lib/src/commands/build_linux.dart b/packages/flutter_tools/lib/src/commands/build_linux.dart index 70fdf054dd..ce462fd74e 100644 --- a/packages/flutter_tools/lib/src/commands/build_linux.dart +++ b/packages/flutter_tools/lib/src/commands/build_linux.dart @@ -23,7 +23,8 @@ class BuildLinuxCommand extends BuildSubCommand { BuildLinuxCommand({ @required OperatingSystemUtils operatingSystemUtils, bool verboseHelp = false, - }) : _operatingSystemUtils = operatingSystemUtils { + }) : _operatingSystemUtils = operatingSystemUtils, + super(verboseHelp: verboseHelp) { addCommonDesktopBuildOptions(verboseHelp: verboseHelp); final String defaultTargetPlatform = (_operatingSystemUtils.hostPlatform == HostPlatform.linux_arm64) ? diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart index e387c0e480..84bc5fb0a1 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos.dart @@ -19,7 +19,9 @@ import 'build.dart'; /// A command to build a macOS desktop target through a build shell script. class BuildMacosCommand extends BuildSubCommand { - BuildMacosCommand({ @required bool verboseHelp }) { + BuildMacosCommand({ + @required bool verboseHelp, + }) : super(verboseHelp: verboseHelp) { addCommonDesktopBuildOptions(verboseHelp: verboseHelp); usesBuildNumberOption(); usesBuildNameOption(); diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 0bf0c75746..4c63d91e92 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -20,7 +20,7 @@ import 'build.dart'; class BuildWebCommand extends BuildSubCommand { BuildWebCommand({ @required bool verboseHelp, - }) { + }) : super(verboseHelp: verboseHelp) { addTreeShakeIconsFlag(enabledByDefault: false); usesTargetOption(); usesPubOption(); diff --git a/packages/flutter_tools/lib/src/commands/build_windows.dart b/packages/flutter_tools/lib/src/commands/build_windows.dart index 91f58fa3ab..611899f8de 100644 --- a/packages/flutter_tools/lib/src/commands/build_windows.dart +++ b/packages/flutter_tools/lib/src/commands/build_windows.dart @@ -20,7 +20,9 @@ import 'build.dart'; /// A command to build a windows desktop target through a build shell script. class BuildWindowsCommand extends BuildSubCommand { - BuildWindowsCommand({ bool verboseHelp = false }) { + BuildWindowsCommand({ + bool verboseHelp = false, + }) : super(verboseHelp: verboseHelp) { addCommonDesktopBuildOptions(verboseHelp: verboseHelp); } diff --git a/packages/flutter_tools/lib/src/commands/build_winuwp.dart b/packages/flutter_tools/lib/src/commands/build_winuwp.dart index 84741c5066..f8761c447f 100644 --- a/packages/flutter_tools/lib/src/commands/build_winuwp.dart +++ b/packages/flutter_tools/lib/src/commands/build_winuwp.dart @@ -19,7 +19,9 @@ import 'build.dart'; /// A command to build a Windows UWP desktop target. class BuildWindowsUwpCommand extends BuildSubCommand { - BuildWindowsUwpCommand({ bool verboseHelp = false }) { + BuildWindowsUwpCommand({ + bool verboseHelp = false, + }) : super(verboseHelp: verboseHelp) { addCommonDesktopBuildOptions(verboseHelp: verboseHelp); } diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index b625d7fe0a..27fbd080e2 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -228,7 +228,7 @@ class CreateCommand extends CreateBase { validateProjectDir(overwrite: overwrite); if (boolArg('with-driver-test')) { - globals.printError( + globals.printWarning( 'The "--with-driver-test" argument has been deprecated and will no longer add a flutter ' 'driver template. Instead, learn how to use package:integration_test by ' 'visiting https://pub.dev/packages/integration_test .' diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 252f73031f..f6a375e6a6 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -309,7 +309,7 @@ class DaemonDomain extends Domain { // capture the print output for testing. // ignore: avoid_print print(message.message); - } else if (message.level == 'error') { + } else if (message.level == 'error' || message.level == 'warning') { globals.stdio.stderrWrite('${message.message}\n'); if (message.stackTrace != null) { globals.stdio.stderrWrite( @@ -1011,6 +1011,18 @@ class NotifyingLogger extends DelegatingLogger { _sendMessage(LogMessage('error', message, stackTrace)); } + @override + void printWarning( + String message, { + bool emphasis = false, + TerminalColor color, + int indent, + int hangingIndent, + bool wrap, + }) { + _sendMessage(LogMessage('warning', message)); + } + @override void printStatus( String message, { diff --git a/packages/flutter_tools/lib/src/commands/devices.dart b/packages/flutter_tools/lib/src/commands/devices.dart index 2c197271f1..391cf06b18 100644 --- a/packages/flutter_tools/lib/src/commands/devices.dart +++ b/packages/flutter_tools/lib/src/commands/devices.dart @@ -48,7 +48,7 @@ class DevicesCommand extends FlutterCommand { @override Future validateCommand() { if (argResults?['timeout'] != null) { - globals.printError('${globals.logger.terminal.warningMark} The "--timeout" argument is deprecated; use "--${FlutterOptions.kDeviceTimeout}" instead.'); + globals.printWarning('${globals.logger.terminal.warningMark} The "--timeout" argument is deprecated; use "--${FlutterOptions.kDeviceTimeout}" instead.'); } return super.validateCommand(); } diff --git a/packages/flutter_tools/lib/src/commands/install.dart b/packages/flutter_tools/lib/src/commands/install.dart index 37cb4e0a71..554d57ebd4 100644 --- a/packages/flutter_tools/lib/src/commands/install.dart +++ b/packages/flutter_tools/lib/src/commands/install.dart @@ -98,7 +98,7 @@ Future installApp( if (uninstall && await device.isAppInstalled(package, userIdentifier: userIdentifier)) { globals.printStatus('Uninstalling old version...'); if (!await device.uninstallApp(package, userIdentifier: userIdentifier)) { - globals.printError('Warning: uninstalling old version failed'); + globals.printWarning('Warning: uninstalling old version failed'); } } } on ProcessException catch (e) { diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index d75bdee478..695541256a 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -153,6 +153,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment addDdsOptions(verboseHelp: verboseHelp); addDevToolsOptions(verboseHelp: verboseHelp); addAndroidSpecificBuildOptions(hide: !verboseHelp); + usesFatalWarningsOption(verboseHelp: verboseHelp); } bool get traceStartup => boolArg('trace-startup'); diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 75df8948fa..8d3b8eb0f9 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -216,7 +216,8 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { 'or as the string "none" to disable the timeout entirely.', defaultsTo: '30s', ); - addDdsOptions(verboseHelp: verboseHelp); + addDdsOptions(verboseHelp: verboseHelp); + usesFatalWarningsOption(verboseHelp: verboseHelp); } /// The interface for starting and configuring the tester. @@ -283,7 +284,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { // correct [requiredArtifacts] can be identified before [run] takes place. _isIntegrationTest = _shouldRunAsIntegrationTests(globals.fs.currentDirectory.absolute.path, _testFiles); - globals.logger.printTrace( + globals.printTrace( 'Found ${_testFiles.length} files which will be executed as ' '${_isIntegrationTest ? 'Integration' : 'Widget'} Tests.', ); @@ -338,7 +339,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { } if (_isIntegrationTest) { if (argResults.wasParsed('concurrency')) { - globals.logger.printStatus( + globals.printStatus( '-j/--concurrency was parsed but will be ignored, this option is not ' 'supported when running Integration Tests.', ); diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index db605dc8ea..a3b8fc1832 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -190,7 +190,7 @@ class UpdatePackagesCommand extends FlutterCommand { if (pubspec.checksum.value == null) { // If the checksum is invalid or missing, we can just ask them run to run // upgrade again to compute it. - globals.printError( + globals.printWarning( 'Warning: pubspec in ${directory.path} has out of date dependencies. ' 'Please run "flutter update-packages --force-upgrade" to update them correctly.' ); @@ -207,7 +207,7 @@ class UpdatePackagesCommand extends FlutterCommand { if (checksum != pubspec.checksum.value) { // If the checksum doesn't match, they may have added or removed some dependencies. // we need to run update-packages to recapture the transitive deps. - globals.printError( + globals.printWarning( 'Warning: pubspec in ${directory.path} has updated or new dependencies. ' 'Please run "flutter update-packages --force-upgrade" to update them correctly ' '(checksum ${pubspec.checksum.value} != $checksum).' @@ -1501,7 +1501,7 @@ Directory createTemporaryFlutterSdk( ..createSync(recursive: true); final PubspecYaml pubspecYaml = pubspecsByName[flutterPackage]; if (pubspecYaml == null) { - logger.printError( + logger.printWarning( "Unexpected package '$flutterPackage' found in packages directory", ); continue; diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index 89d3066c3b..4f4dcb0905 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart @@ -339,11 +339,11 @@ class FlutterManifest { final YamlList? fontFiles = fontFamily['fonts'] as YamlList?; final String? familyName = fontFamily['family'] as String?; if (familyName == null) { - _logger.printError('Warning: Missing family name for font.', emphasis: true); + _logger.printWarning('Warning: Missing family name for font.', emphasis: true); continue; } if (fontFiles == null) { - _logger.printError('Warning: No fonts specified for font $familyName', emphasis: true); + _logger.printWarning('Warning: No fonts specified for font $familyName', emphasis: true); continue; } @@ -351,7 +351,7 @@ class FlutterManifest { for (final Map fontFile in fontFiles.cast>()) { final String? asset = fontFile['asset'] as String?; if (asset == null) { - _logger.printError('Warning: Missing asset in fonts for $familyName', emphasis: true); + _logger.printWarning('Warning: Missing asset in fonts for $familyName', emphasis: true); continue; } diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index f711472f9f..65a845e7bf 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -393,7 +393,7 @@ Future _writeAndroidPluginRegistrant(FlutterProject project, List } } if (pluginsUsingV1.length > 1) { - globals.printError( + globals.printWarning( 'The plugins `${pluginsUsingV1.join(', ')}` use a deprecated version of the Android embedding.\n' 'To avoid unexpected runtime failures, or future build failures, try to see if these plugins ' 'support the Android V2 embedding. Otherwise, consider removing them since a future release ' @@ -402,7 +402,7 @@ Future _writeAndroidPluginRegistrant(FlutterProject project, List 'https://flutter.dev/go/android-plugin-migration.' ); } else if (pluginsUsingV1.isNotEmpty) { - globals.printError( + globals.printWarning( 'The plugin `${pluginsUsingV1.first}` uses a deprecated version of the Android embedding.\n' 'To avoid unexpected runtime failures, or future build failures, try to see if this plugin ' 'supports the Android V2 embedding. Otherwise, consider removing it since a future release ' @@ -414,7 +414,7 @@ Future _writeAndroidPluginRegistrant(FlutterProject project, List templateContent = _androidPluginRegistryTemplateNewEmbedding; break; case AndroidEmbeddingVersion.v1: - globals.printError( + globals.printWarning( 'This app is using a deprecated version of the Android embedding.\n' 'To avoid unexpected runtime failures, or future build failures, try to migrate this ' 'app to the V2 embedding.\n' @@ -1301,7 +1301,7 @@ Future generateMainDartWithPluginRegistrant( newMainDart.deleteSync(); } } on FileSystemException catch (error) { - globals.printError( + globals.printWarning( 'Unable to remove ${newMainDart.path}, received error: $error.\n' 'You might need to run flutter clean.' ); diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart index 3834bff306..080e197478 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart @@ -124,6 +124,10 @@ Future _buildAssets( assetDirPath: assetDir, ); + if (assets == null) { + throwToolExit('Unable to find assets.', exitCode: 1); + } + final Map assetEntries = Map.of(assets.entries); await writeBundle(globals.fs.directory(assetDir), assetEntries); diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index 34ed293a13..187db6d3b5 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -150,6 +150,30 @@ void printError( ); } +/// Display a warning level message to the user. Commands should use this if they +/// have important warnings to convey that aren't fatal. +/// +/// Set [emphasis] to true to make the output bold if it's supported. +/// Set [color] to a [TerminalColor] to color the output, if the logger +/// supports it. The [color] defaults to [TerminalColor.cyan]. +void printWarning( + String message, { + bool? emphasis, + TerminalColor? color, + int? indent, + int? hangingIndent, + bool? wrap, + }) { + logger.printWarning( + message, + emphasis: emphasis ?? false, + color: color, + indent: indent, + hangingIndent: hangingIndent, + wrap: wrap, + ); +} + /// Display normal output of the command. This should be used for things like /// progress messages, success messages, or just normal command output. /// diff --git a/packages/flutter_tools/lib/src/globals_null_migrated.dart b/packages/flutter_tools/lib/src/globals_null_migrated.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index 7a0a689501..3ee17c97e9 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -176,7 +176,7 @@ class CocoaPods { final CocoaPodsStatus installation = await evaluateCocoaPodsInstallation; switch (installation) { case CocoaPodsStatus.notInstalled: - _logger.printError( + _logger.printWarning( 'Warning: CocoaPods not installed. Skipping pod install.\n' '$noCocoaPodsConsequence\n' 'To install $cocoaPodsInstallInstructions\n', @@ -184,7 +184,7 @@ class CocoaPods { ); return false; case CocoaPodsStatus.brokenInstall: - _logger.printError( + _logger.printWarning( 'Warning: CocoaPods is installed but broken. Skipping pod install.\n' '$brokenCocoaPodsConsequence\n' 'To re-install $cocoaPodsInstallInstructions\n', @@ -192,7 +192,7 @@ class CocoaPods { ); return false; case CocoaPodsStatus.unknownVersion: - _logger.printError( + _logger.printWarning( 'Warning: Unknown CocoaPods version installed.\n' '$unknownCocoaPodsConsequence\n' 'To upgrade $cocoaPodsInstallInstructions\n', @@ -200,7 +200,7 @@ class CocoaPods { ); break; case CocoaPodsStatus.belowMinimumVersion: - _logger.printError( + _logger.printWarning( 'Warning: CocoaPods minimum required version $cocoaPodsMinimumVersion or greater not installed. Skipping pod install.\n' '$noCocoaPodsConsequence\n' 'To upgrade $cocoaPodsInstallInstructions\n', @@ -208,7 +208,7 @@ class CocoaPods { ); return false; case CocoaPodsStatus.belowRecommendedVersion: - _logger.printError( + _logger.printWarning( 'Warning: CocoaPods recommended version $cocoaPodsRecommendedVersion or greater not installed.\n' 'Pods handling may fail on some projects involving plugins.\n' 'To upgrade $cocoaPodsInstallInstructions\n', @@ -406,15 +406,15 @@ class CocoaPods { // plugin_pods = parse_KV_file('../.flutter-plugins') if (xcodeProject.podfile.existsSync() && xcodeProject.podfile.readAsStringSync().contains(".flutter-plugins'")) { - const String error = 'Warning: Podfile is out of date\n' + const String warning = 'Warning: Podfile is out of date\n' '$outOfDatePluginsPodfileConsequence\n' 'To regenerate the Podfile, run:\n'; if (isIos) { - throwToolExit('$error\n$podfileIosMigrationInstructions\n'); + throwToolExit('$warning\n$podfileIosMigrationInstructions\n'); } else { // The old macOS Podfile will work until `.flutter-plugins` is removed. // Warn instead of exit. - _logger.printError('$error\n$podfileMacOSMigrationInstructions\n', emphasis: true); + _logger.printWarning('$warning\n$podfileMacOSMigrationInstructions\n', emphasis: true); } } } diff --git a/packages/flutter_tools/lib/src/macos/xcdevice.dart b/packages/flutter_tools/lib/src/macos/xcdevice.dart index 4184876c2d..77fa8a0ccd 100644 --- a/packages/flutter_tools/lib/src/macos/xcdevice.dart +++ b/packages/flutter_tools/lib/src/macos/xcdevice.dart @@ -415,7 +415,7 @@ class XCDevice { } else { cpuArchitecture = DarwinArch.arm64; } - _logger.printError( + _logger.printWarning( 'Unknown architecture $architecture, defaulting to ' '${getNameForDarwinArch(cpuArchitecture)}', ); diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart index 75701b62a7..b4dee608d7 100644 --- a/packages/flutter_tools/lib/src/mdns_discovery.dart +++ b/packages/flutter_tools/lib/src/mdns_discovery.dart @@ -110,7 +110,7 @@ class MDnsObservatoryDiscovery { return null; } if (srv.length > 1) { - _logger.printError('Unexpectedly found more than one observatory report for $domainName ' + _logger.printWarning('Unexpectedly found more than one observatory report for $domainName ' '- using first one (${srv.first.port}).'); } _logger.printTrace('Checking for authentication code for $domainName'); diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index eb2ddcf3e3..d13a9a698f 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -479,23 +479,6 @@ class AndroidProject extends FlutterProjectPlatform { } Future ensureReadyForPlatformSpecificTooling() async { - if (getEmbeddingVersion() == AndroidEmbeddingVersion.v1) { - globals.printStatus( -""" -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -Warning -────────────────────────────────────────────────────────────────────────────── -Your Flutter application is created using an older version of the Android -embedding. It's being deprecated in favor of Android embedding v2. Follow the -steps at - -https://flutter.dev/go/android-project-migration - -to migrate your project. -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -""" - ); - } if (isModule && _shouldRegenerateFromTemplate()) { await _regenerateLibrary(); // Add ephemeral host app, if an editable host app does not already exist. diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 1d2065baa5..f843809c4b 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -1178,7 +1178,7 @@ abstract class ResidentRunner extends ResidentHandlers { ); if (!_lastBuild.success) { for (final ExceptionMeasurement exceptionMeasurement in _lastBuild.exceptions.values) { - globals.logger.printError( + globals.printError( exceptionMeasurement.exception.toString(), stackTrace: globals.logger.isVerbose ? exceptionMeasurement.stackTrace @@ -1186,7 +1186,7 @@ abstract class ResidentRunner extends ResidentHandlers { ); } } - globals.logger.printTrace('complete'); + globals.printTrace('complete'); } @protected @@ -1241,7 +1241,7 @@ abstract class ResidentRunner extends ResidentHandlers { if (_dillOutputPath != null) { return; } - globals.logger.printTrace('Caching compiled dill'); + globals.printTrace('Caching compiled dill'); final File outputDill = globals.fs.file(dillOutputPath); if (outputDill.existsSync()) { final String copyPath = getDefaultCachedKernelPath( @@ -1561,7 +1561,7 @@ class TerminalHandler { _logger.printTrace('Deleting pid file (${_actualPidFile.path}).'); _actualPidFile.deleteSync(); } on FileSystemException catch (error) { - _logger.printError('Failed to delete pid file (${_actualPidFile.path}): ${error.message}'); + _logger.printWarning('Failed to delete pid file (${_actualPidFile.path}): ${error.message}'); } _actualPidFile = null; } diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index e13ef28ef4..0656a99d7f 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart @@ -77,8 +77,8 @@ class ColdRunner extends ResidentRunner { return result; } } - } on Exception catch (err) { - globals.printError(err.toString()); + } on Exception catch (err, stack) { + globals.printError('$err\n$stack'); appFailedToStart(); return 1; } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 05f8400471..3ea9aaa019 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -115,6 +115,7 @@ class FlutterOptions { static const String kDeferredComponents = 'deferred-components'; static const String kAndroidProjectArgs = 'android-project-arg'; static const String kInitializeFromDill = 'initialize-from-dill'; + static const String kFatalWarnings = 'fatal-warnings'; } /// flutter command categories for usage. @@ -178,6 +179,8 @@ abstract class FlutterCommand extends Command { bool _usesIpv6Flag = false; + bool _usesFatalWarnings = false; + bool get shouldRunPub => _usesPubOption && boolArg('pub'); bool get shouldUpdateCache => true; @@ -271,6 +274,15 @@ abstract class FlutterCommand extends Command { _usesTargetOption = true; } + void usesFatalWarningsOption({ required bool verboseHelp }) { + argParser.addFlag(FlutterOptions.kFatalWarnings, + hide: !verboseHelp, + help: 'Causes the command to fail if warnings are sent to the console ' + 'during its execution.' + ); + _usesFatalWarnings = true; + } + String get targetFile { if (argResults?.wasParsed('target') == true) { return stringArg('target')!; @@ -413,10 +425,10 @@ abstract class FlutterCommand extends Command { // TODO(ianh): enable the following code once google3 is migrated away from --disable-dds (and add test to flutter_command_test.dart) if (false) { // ignore: dead_code if (ddsEnabled) { - globals.printError('${globals.logger.terminal + globals.printWarning('${globals.logger.terminal .warningMark} The "--no-disable-dds" argument is deprecated and redundant, and should be omitted.'); } else { - globals.printError('${globals.logger.terminal + globals.printWarning('${globals.logger.terminal .warningMark} The "--disable-dds" argument is deprecated. Use "--no-dds" instead.'); } } @@ -1123,6 +1135,9 @@ abstract class FlutterCommand extends Command { name: 'command', overrides: {FlutterCommand: () => this}, body: () async { + if (_usesFatalWarnings) { + globals.logger.fatalWarnings = boolArg(FlutterOptions.kFatalWarnings); + } // Prints the welcome message if needed. globals.flutterUsage.printWelcome(); _printDeprecationWarning(); @@ -1139,6 +1154,9 @@ abstract class FlutterCommand extends Command { if (commandPath != null) { _sendPostUsage(commandPath, commandResult, startTime, endTime); } + if (_usesFatalWarnings) { + globals.logger.checkForFatalLogs(); + } } }, ); @@ -1146,13 +1164,12 @@ abstract class FlutterCommand extends Command { void _printDeprecationWarning() { if (deprecated) { - globals.printError( + globals.printWarning( '${globals.logger.terminal.warningMark} The "$name" command is deprecated and ' 'will be removed in a future version of Flutter. ' 'See https://flutter.dev/docs/development/tools/sdk/releases ' - 'for previous releases of Flutter.', + 'for previous releases of Flutter.\n', ); - globals.printError(''); } } diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index d6927f445c..ef9ed71955 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -277,10 +277,10 @@ class FlutterVersion { ); } on VersionCheckError catch (error) { if (globals.platform.environment.containsKey('FLUTTER_GIT_URL')) { - globals.logger.printError('Warning: the Flutter git upstream was overridden ' + globals.printWarning('Warning: the Flutter git upstream was overridden ' 'by the environment variable FLUTTER_GIT_URL = ${globals.flutterGit}'); } - globals.logger.printError(error.toString()); + globals.printError(error.toString()); rethrow; } finally { await _removeVersionCheckRemoteIfExists(); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 33af827acf..6eb52da71b 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -568,6 +568,20 @@ class StreamLogger extends Logger { int hangingIndent, bool wrap, }) { + hadErrorOutput = true; + _log('[stderr] $message'); + } + + @override + void printWarning( + String message, { + bool emphasis, + TerminalColor color, + int indent, + int hangingIndent, + bool wrap, + }) { + hadWarningOutput = true; _log('[stderr] $message'); } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart index 52b93f5748..9714f74059 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart @@ -227,6 +227,8 @@ void main() { const ['build', 'linux', '--debug', '--no-pub'] ); expect(testLogger.statusText, isNot(contains('STDOUT STUFF'))); + expect(testLogger.warningText, isNot(contains('STDOUT STUFF'))); + expect(testLogger.errorText, isNot(contains('STDOUT STUFF'))); expect(testLogger.traceText, contains('STDOUT STUFF')); }, overrides: { FileSystem: () => fileSystem, @@ -308,6 +310,8 @@ ERROR: No file or variants found for asset: images/a_dot_burr.jpeg ); expect(testLogger.statusText, contains('STDOUT STUFF')); expect(testLogger.traceText, isNot(contains('STDOUT STUFF'))); + expect(testLogger.warningText, isNot(contains('STDOUT STUFF'))); + expect(testLogger.errorText, isNot(contains('STDOUT STUFF'))); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => processManager, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_test.dart index d168ab2ecd..57aba7ecd5 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_test.dart @@ -3,9 +3,13 @@ // found in the LICENSE file. // @dart = 2.8 - import 'package:args/command_runner.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; +import 'package:meta/meta.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -13,27 +17,97 @@ import '../../src/test_flutter_command_runner.dart'; void main() { testUsingContext('obfuscate requires split-debug-info', () { - final FakeBuildCommand command = FakeBuildCommand(); + final FakeBuildInfoCommand command = FakeBuildInfoCommand(); final CommandRunner commandRunner = createTestCommandRunner(command); expect(() => commandRunner.run([ - 'build', + 'fake', '--obfuscate', - ]), throwsToolExit()); + ]), throwsToolExit(message: '"--${FlutterOptions.kDartObfuscationOption}" can only be used in ' + 'combination with "--${FlutterOptions.kSplitDebugInfoOption}"')); + }); + group('Fatal Logs', () { + FakeBuildCommand command; + MemoryFileSystem fs; + + setUp(() { + fs = MemoryFileSystem.test(); + fs.file('/package/pubspec.yaml').createSync(recursive: true); + fs.currentDirectory = '/package'; + Cache.disableLocking(); + }); + + testUsingContext("doesn't fail if --fatal-warnings specified and no warnings occur", () async { + command = FakeBuildCommand(); + try { + await createTestCommandRunner(command).run([ + 'build', + 'test', + '--${FlutterOptions.kFatalWarnings}', + ]); + } on Exception { + fail('Unexpected exception thrown'); + } + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext("doesn't fail if --fatal-warnings not specified", () async { + command = FakeBuildCommand(); + testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); + try { + await createTestCommandRunner(command).run([ + 'build', + 'test', + ]); + } on Exception { + fail('Unexpected exception thrown'); + } + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('fails if --fatal-warnings specified and warnings emitted', () async { + command = FakeBuildCommand(); + testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); + await expectLater(createTestCommandRunner(command).run([ + 'build', + 'test', + '--${FlutterOptions.kFatalWarnings}', + ]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('fails if --fatal-warnings specified and errors emitted', () async { + command = FakeBuildCommand(); + testLogger.printError('Error: Danger Will Robinson!'); + await expectLater(createTestCommandRunner(command).run([ + 'build', + 'test', + '--${FlutterOptions.kFatalWarnings}', + ]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); }); } -class FakeBuildCommand extends FlutterCommand { - FakeBuildCommand() { +class FakeBuildInfoCommand extends FlutterCommand { + FakeBuildInfoCommand() : super() { addSplitDebugInfoOption(); addDartObfuscationOption(); } @override - String get description => throw UnimplementedError(); + String get description => ''; @override - String get name => 'build'; + String get name => 'fake'; @override Future runCommand() async { @@ -41,3 +115,35 @@ class FakeBuildCommand extends FlutterCommand { return FlutterCommandResult.success(); } } + +class FakeBuildCommand extends BuildCommand { + FakeBuildCommand({bool verboseHelp = false}) : super(verboseHelp: verboseHelp) { + addSubcommand(FakeBuildSubcommand(verboseHelp: verboseHelp)); + } + + @override + String get description => ''; + + @override + String get name => 'build'; + + @override + Future runCommand() async { + return FlutterCommandResult.success(); + } +} + +class FakeBuildSubcommand extends BuildSubCommand { + FakeBuildSubcommand({@required bool verboseHelp}) : super(verboseHelp: verboseHelp); + + @override + String get description => ''; + + @override + String get name => 'test'; + + @override + Future runCommand() async { + return FlutterCommandResult.success(); + } +} diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart index 353c3dcf2a..14effc0816 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart @@ -130,6 +130,29 @@ void main() { Logger: () => notifyingLogger, }); + testUsingContext('printWarning should send daemon.logMessage event', () async { + final StreamController> commands = StreamController>(); + final StreamController> responses = StreamController>(); + daemon = Daemon( + commands.stream, + responses.add, + notifyingLogger: notifyingLogger, + ); + globals.printWarning('daemon.logMessage test'); + final Map response = await responses.stream.firstWhere((Map map) { + return map['event'] == 'daemon.logMessage' && (map['params'] as Map)['level'] == 'warning'; + }); + expect(response['id'], isNull); + expect(response['event'], 'daemon.logMessage'); + final Map logMessage = castStringKeyedMap(response['params']).cast(); + expect(logMessage['level'], 'warning'); + expect(logMessage['message'], 'daemon.logMessage test'); + await responses.close(); + await commands.close(); + }, overrides: { + Logger: () => notifyingLogger, + }); + testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async { final StringBuffer buffer = await capturedConsolePrint(() { final StreamController> commands = StreamController>(); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index 96afec3551..6ca5fc6acc 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -294,6 +294,75 @@ void main() { }); }); + group('Fatal Logs', () { + TestRunCommandWithFakeResidentRunner command; + MemoryFileSystem fs; + + setUp(() { + command = TestRunCommandWithFakeResidentRunner() + ..fakeResidentRunner = FakeResidentRunner(); + fs = MemoryFileSystem.test(); + }); + + testUsingContext("doesn't fail if --fatal-warnings specified and no warnings occur", () async { + try { + await createTestCommandRunner(command).run([ + 'run', + '--no-pub', + '--no-hot', + '--${FlutterOptions.kFatalWarnings}', + ]); + } on Exception { + fail('Unexpected exception thrown'); + } + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext("doesn't fail if --fatal-warnings not specified", () async { + testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); + try { + await createTestCommandRunner(command).run([ + 'run', + '--no-pub', + '--no-hot', + ]); + } on Exception { + fail('Unexpected exception thrown'); + } + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('fails if --fatal-warnings specified and warnings emitted', () async { + testLogger.printWarning('Warning: Mild annoyance Will Robinson!'); + await expectLater(createTestCommandRunner(command).run([ + 'run', + '--no-pub', + '--no-hot', + '--${FlutterOptions.kFatalWarnings}', + ]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('fails if --fatal-warnings specified and errors emitted', () async { + testLogger.printError('Error: Danger Will Robinson!'); + await expectLater(createTestCommandRunner(command).run([ + 'run', + '--no-pub', + '--no-hot', + '--${FlutterOptions.kFatalWarnings}', + ]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + }); + testUsingContext('should only request artifacts corresponding to connected devices', () async { mockDeviceManager.devices = [FakeDevice(targetPlatform: TargetPlatform.android_arm)]; @@ -496,7 +565,7 @@ class FakeDevice extends Fake implements Device { @override String get id => 'fake_device'; - void _throwToolExit(int code) => throwToolExit(null, exitCode: code); + void _throwToolExit(int code) => throwToolExit('FakeDevice tool exit', exitCode: code); @override Future get isLocalEmulator => Future.value(_isLocalEmulator); @@ -504,6 +573,9 @@ class FakeDevice extends Fake implements Device { @override bool supportsRuntimeMode(BuildMode mode) => true; + @override + Future get supportsHardwareRendering async => true; + @override bool supportsHotReload = false; @@ -542,7 +614,7 @@ class FakeDevice extends Fake implements Device { @override final PlatformType platformType = PlatformType.ios; - bool startAppSuccess = true; + bool startAppSuccess; @override DevFSWriter createDevFSWriter( @@ -564,9 +636,12 @@ class FakeDevice extends Fake implements Device { bool ipv6 = false, String userIdentifier, }) async { - if (!startAppSuccess) { + if (startAppSuccess == false) { return LaunchResult.failed(); } + if (startAppSuccess == true) { + return LaunchResult.succeeded(); + } final String dartFlags = debuggingOptions.dartFlags; // In release mode, --dart-flags should be set to the empty string and // provided flags should be dropped. In debug and profile modes, @@ -587,18 +662,20 @@ class FakeDevice extends Fake implements Device { } class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory { - ApplicationPackage package; + FakeApplicationPackageFactory(this.applicationPackage); + + ApplicationPackage applicationPackage; @override Future getPackageForPlatform( - TargetPlatform platform, { - BuildInfo buildInfo, - File applicationBinary, - }) async { - return package; - } + TargetPlatform platform, { + BuildInfo buildInfo, + File applicationBinary, + }) async => applicationPackage; } +class FakeApplicationPackage extends Fake implements ApplicationPackage { } + class TestRunCommandWithFakeResidentRunner extends RunCommand { FakeResidentRunner fakeResidentRunner; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index f77b30da3b..8320d0b015 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -16,6 +16,7 @@ import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/test.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/test/runner.dart'; import 'package:flutter_tools/src/test/test_wrapper.dart'; import 'package:flutter_tools/src/test/watcher.dart'; @@ -644,6 +645,60 @@ dev_dependencies: ProcessManager: () => FakeProcessManager.any(), DeviceManager: () => _FakeDeviceManager([]), }); + + group('Fatal Logs', () { + testUsingContext("doesn't fail when --fatal-warnings is set and no warning output", () async { + final FakeFlutterTestRunner testRunner = FakeFlutterTestRunner(0); + + final TestCommand testCommand = TestCommand(testRunner: testRunner); + final CommandRunner commandRunner = createTestCommandRunner(testCommand); + + try { + await commandRunner.run(const [ + 'test', + '--no-pub', + '--${FlutterOptions.kFatalWarnings}', + ]); + } on Exception { + fail('Unexpected exception thrown'); + } + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + testUsingContext('fails if --fatal-warnings specified and warnings emitted', () async { + final FakeFlutterTestRunner testRunner = FakeFlutterTestRunner(0); + + final TestCommand testCommand = TestCommand(testRunner: testRunner); + final CommandRunner commandRunner = createTestCommandRunner(testCommand); + + testLogger.printWarning('Warning: Mild annoyance, Will Robinson!'); + expect(commandRunner.run(const [ + 'test', + '--no-pub', + '--${FlutterOptions.kFatalWarnings}', + ]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + testUsingContext('fails when --fatal-warnings is set and only errors emitted', () async { + final FakeFlutterTestRunner testRunner = FakeFlutterTestRunner(0); + + final TestCommand testCommand = TestCommand(testRunner: testRunner); + final CommandRunner commandRunner = createTestCommandRunner(testCommand); + + testLogger.printError('Error: Danger Will Robinson!'); + expect(commandRunner.run(const [ + 'test', + '--no-pub', + '--${FlutterOptions.kFatalWarnings}', + ]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + }); } class FakeFlutterTestRunner implements FlutterTestRunner { diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart index 35f1c23a6e..5400ddddad 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart @@ -311,7 +311,7 @@ flutter: await writeBundle(directory, {}, loggerOverride: testLogger); - expect(testLogger.errorText, contains('Expected Error Text')); + expect(testLogger.warningText, contains('Expected Error Text')); }); testUsingContext('does not unnecessarily recreate asset manifest, font manifest, license', () async { diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart index 8d87ff12fb..8640af83bc 100644 --- a/packages/flutter_tools/test/general.shard/cache_test.dart +++ b/packages/flutter_tools/test/general.shard/cache_test.dart @@ -137,7 +137,7 @@ void main() { final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache); await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils()); - expect(logger.errorText, contains('stamp write failed')); + expect(logger.warningText, contains('stamp write failed')); }); testWithoutContext('Continues on missing version file', () async { @@ -153,7 +153,7 @@ void main() { final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache); await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils()); - expect(logger.errorText, contains('No known version for the artifact name "fake"')); + expect(logger.warningText, contains('No known version for the artifact name "fake"')); }); testWithoutContext('Gradle wrapper should not be up to date, if some cached artifact is not available', () { @@ -725,7 +725,7 @@ void main() { cache.clearStampFiles(); - expect(logger.errorText, contains('Failed to delete some stamp files')); + expect(logger.warningText, contains('Failed to delete some stamp files')); }); testWithoutContext('FlutterWebSdk fetches web artifacts and deletes previous directory contents', () async { diff --git a/packages/flutter_tools/test/general.shard/commands/build_test.dart b/packages/flutter_tools/test/general.shard/commands/build_test.dart index 40dfce2276..b912556068 100644 --- a/packages/flutter_tools/test/general.shard/commands/build_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/build_test.dart @@ -83,6 +83,8 @@ void main() { } class FakeBuildSubCommand extends BuildSubCommand { + FakeBuildSubCommand() : super(verboseHelp: false); + @override String get description => throw UnimplementedError(); diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart index 62085f75b3..d5e40d8ae1 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart @@ -432,8 +432,8 @@ void main() { buildMode: BuildMode.debug, ); - expect(logger.errorText, contains('Warning: Podfile is out of date')); - expect(logger.errorText, contains('rm macos/Podfile')); + expect(logger.warningText, contains('Warning: Podfile is out of date')); + expect(logger.warningText, contains('rm macos/Podfile')); expect(fakeProcessManager, hasNoRemainingExpectations); }); diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index f98f8232ac..54417f527c 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -747,7 +747,7 @@ dependencies: .childFile('GeneratedPluginRegistrant.java'); expect(registrant.readAsStringSync(), contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));')); - expect(testLogger.errorText, equals( + expect(testLogger.warningText, equals( 'The plugin `plugin3` uses a deprecated version of the Android embedding.\n' 'To avoid unexpected runtime failures, or future build failures, try to see if this plugin supports the Android V2 embedding. ' 'Otherwise, consider removing it since a future release of Flutter will remove these deprecated APIs.\n' @@ -827,7 +827,7 @@ dependencies: await injectPlugins(flutterProject, androidPlatform: true); - expect(testLogger.errorText, equals( + expect(testLogger.warningText, equals( 'This app is using a deprecated version of the Android embedding.\n' 'To avoid unexpected runtime failures, or future build failures, try to migrate this app to the V2 embedding.\n' 'Take a look at the docs for migrating an app: https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects\n' @@ -854,7 +854,7 @@ dependencies: contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));')); expect(registrant.readAsStringSync(), contains('plugin4.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin4.UseOldEmbedding"));')); - expect(testLogger.errorText, equals( + expect(testLogger.warningText, equals( 'The plugins `plugin3, plugin4` use a deprecated version of the Android embedding.\n' 'To avoid unexpected runtime failures, or future build failures, try to see if these plugins support the Android V2 embedding. ' 'Otherwise, consider removing them since a future release of Flutter will remove these deprecated APIs.\n' @@ -882,7 +882,7 @@ dependencies: contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));')); expect(registrant.readAsStringSync(), contains('plugin4.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin4.UseOldEmbedding"));')); - expect(testLogger.errorText, equals( + expect(testLogger.warningText, equals( 'The plugins `plugin3, plugin4` use a deprecated version of the Android embedding.\n' 'To avoid unexpected runtime failures, or future build failures, try to see if these plugins support the Android V2 embedding. ' 'Otherwise, consider removing them since a future release of Flutter will remove these deprecated APIs.\n' diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index ad36f83e6d..3915d7d4cb 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -183,7 +183,7 @@ void main() { // android:name="flutterEmbedding" android:value="2" />. await project.regeneratePlatformSpecificTooling(); - expect(testLogger.statusText, contains('https://flutter.dev/go/android-project-migration')); + expect(testLogger.warningText, contains('https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects')); }); _testInMemory('Android plugin without example app does not show a warning', () async { final FlutterProject project = await aPluginProject(); diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart index cbd94e9636..804998b2b8 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart @@ -87,7 +87,7 @@ void main() { final CommandRunner runner = createTestCommandRunner(flutterCommand); await runner.run(['deprecated']); - expect(testLogger.errorText, + expect(testLogger.warningText, contains('The "deprecated" command is deprecated and will be removed in ' 'a future version of Flutter.')); expect(flutterCommand.usage, diff --git a/packages/flutter_tools/test/general.shard/update_packages_test.dart b/packages/flutter_tools/test/general.shard/update_packages_test.dart index 0a422c30b6..2bf9a2b439 100644 --- a/packages/flutter_tools/test/general.shard/update_packages_test.dart +++ b/packages/flutter_tools/test/general.shard/update_packages_test.dart @@ -129,7 +129,7 @@ void main() { // We get a warning about the unexpected package. expect( - bufferLogger.errorText, + bufferLogger.warningText, contains("Unexpected package 'extra' found in packages directory"), ); diff --git a/packages/flutter_tools/test/integration.shard/cache_test.dart b/packages/flutter_tools/test/integration.shard/cache_test.dart index 8f155d6c59..962d02e2f2 100644 --- a/packages/flutter_tools/test/integration.shard/cache_test.dart +++ b/packages/flutter_tools/test/integration.shard/cache_test.dart @@ -10,12 +10,15 @@ import 'package:file/file.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:process/process.dart'; +import 'package:test/fake.dart'; import '../src/common.dart'; import '../src/context.dart'; +import '../src/fakes.dart'; import 'test_utils.dart'; final String dart = fileSystem.path @@ -24,53 +27,122 @@ final String dart = fileSystem.path void main() { group('Cache.lock', () { // Windows locking is too flaky for this to work reliably. - if (!platform.isWindows) { - testWithoutContext( - 'should log a message to stderr when lock is not acquired', () async { - final String oldRoot = Cache.flutterRoot; - final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('cache_test.'); - final BufferLogger logger = BufferLogger( - terminal: Terminal.test(supportsColor: false, supportsEmoji: false), - outputPreferences: OutputPreferences(), + if (platform.isWindows) { + return; + } + testWithoutContext( + 'should log a message to stderr when lock is not acquired', () async { + final String oldRoot = Cache.flutterRoot; + final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('cache_test.'); + final BufferLogger logger = BufferLogger( + terminal: Terminal.test(supportsColor: false, supportsEmoji: false), + outputPreferences: OutputPreferences(), + ); + logger.fatalWarnings = true; + try { + Cache.flutterRoot = tempDir.absolute.path; + final Cache cache = Cache.test( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, ); - try { - Cache.flutterRoot = tempDir.absolute.path; - final Cache cache = Cache.test( - fileSystem: fileSystem, - processManager: FakeProcessManager.any(), - logger: logger, - ); - final File cacheFile = fileSystem.file(fileSystem.path - .join(Cache.flutterRoot, 'bin', 'cache', 'lockfile')) - ..createSync(recursive: true); - final File script = fileSystem.file(fileSystem.path - .join(Cache.flutterRoot, 'bin', 'cache', 'test_lock.dart')); - script.writeAsStringSync(r''' + final File cacheFile = fileSystem.file(fileSystem.path + .join(Cache.flutterRoot, 'bin', 'cache', 'lockfile')) + ..createSync(recursive: true); + final File script = fileSystem.file(fileSystem.path + .join(Cache.flutterRoot, 'bin', 'cache', 'test_lock.dart')); + script.writeAsStringSync(r''' import 'dart:async'; import 'dart:io'; Future main(List args) async { - File file = File(args[0]); - RandomAccessFile lock = file.openSync(mode: FileMode.write); - lock.lockSync(); - await Future.delayed(const Duration(milliseconds: 1000)); - exit(0); +File file = File(args[0]); +RandomAccessFile lock = file.openSync(mode: FileMode.write); +lock.lockSync(); +await Future.delayed(const Duration(milliseconds: 1000)); +exit(0); } '''); - final Process process = await const LocalProcessManager().start( - [dart, script.absolute.path, cacheFile.absolute.path], - ); - await Future.delayed(const Duration(milliseconds: 500)); - await cache.lock(); - process.kill(io.ProcessSignal.sigkill); - } finally { - tryToDelete(tempDir); - Cache.flutterRoot = oldRoot; - } - expect(logger.statusText, isEmpty); - expect(logger.errorText, - equals('Waiting for another flutter command to release the startup lock...\n')); - }); - } + final Process process = await const LocalProcessManager().start( + [dart, script.absolute.path, cacheFile.absolute.path], + ); + await Future.delayed(const Duration(milliseconds: 500)); + await cache.lock(); + process.kill(io.ProcessSignal.sigkill); + } finally { + tryToDelete(tempDir); + Cache.flutterRoot = oldRoot; + } + expect(logger.statusText, isEmpty); + expect(logger.errorText, isEmpty); + expect(logger.warningText, + equals('Waiting for another flutter command to release the startup lock...\n')); + expect(logger.hadErrorOutput, isFalse); + // Should still be false, since the particular "Waiting..." message above aims to + // avoid triggering failure as a fatal warning. + expect(logger.hadWarningOutput, isFalse); + }); + testWithoutContext( + 'should log a warning message for unknown version ', () async { + final String oldRoot = Cache.flutterRoot; + final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('cache_test.'); + final BufferLogger logger = BufferLogger( + terminal: Terminal.test(supportsColor: false, supportsEmoji: false), + outputPreferences: OutputPreferences(), + ); + logger.fatalWarnings = true; + try { + Cache.flutterRoot = tempDir.absolute.path; + final Cache cache = Cache.test( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + ); + final FakeVersionlessArtifact artifact = FakeVersionlessArtifact(cache); + cache.registerArtifact(artifact); + await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils()); + } finally { + tryToDelete(tempDir); + Cache.flutterRoot = oldRoot; + } + expect(logger.statusText, isEmpty); + expect(logger.warningText, equals('No known version for the artifact name "fake". ' + 'Flutter can continue, but the artifact may be re-downloaded on ' + 'subsequent invocations until the problem is resolved.\n')); + expect(logger.hadErrorOutput, isFalse); + expect(logger.hadWarningOutput, isTrue); + }); }); } + +class FakeArtifactUpdater extends Fake implements ArtifactUpdater { + void Function(String, Uri, Directory) onDownloadZipArchive; + void Function(String, Uri, Directory) onDownloadZipTarball; + + @override + Future downloadZippedTarball(String message, Uri url, Directory location) async { + onDownloadZipTarball?.call(message, url, location); + } + + @override + Future downloadZipArchive(String message, Uri url, Directory location) async { + onDownloadZipArchive?.call(message, url, location); + } + + @override + void removeDownloadedFiles() { } +} + +class FakeVersionlessArtifact extends CachedArtifact { + FakeVersionlessArtifact(Cache cache) : super( + 'fake', + cache, + DevelopmentArtifact.universal, + ); + + @override + String get version => null; + + @override + Future updateInner(ArtifactUpdater artifactUpdater, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils) async { } +} diff --git a/packages/flutter_tools/test/integration.shard/test_test.dart b/packages/flutter_tools/test/integration.shard/test_test.dart index 25ef8a854b..c8aef8f09d 100644 --- a/packages/flutter_tools/test/integration.shard/test_test.dart +++ b/packages/flutter_tools/test/integration.shard/test_test.dart @@ -115,84 +115,66 @@ void main() { testWithoutContext('flutter test should run a test when its name matches a regexp', () async { final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, extraArguments: const ['--name', 'inc.*de']); - if (!(result.stdout as String).contains('+1: All tests passed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(result.exitCode, 0); }); testWithoutContext('flutter test should run a test when its name contains a string', () async { final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, extraArguments: const ['--plain-name', 'include']); - if (!(result.stdout as String).contains('+1: All tests passed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(result.exitCode, 0); }); testWithoutContext('flutter test should run a test with a given tag', () async { final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, extraArguments: const ['--tags', 'include-tag']); - if (!(result.stdout as String).contains('+1: All tests passed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(result.exitCode, 0); }); testWithoutContext('flutter test should not run a test with excluded tag', () async { final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, extraArguments: const ['--exclude-tags', 'exclude-tag']); - if (!(result.stdout as String).contains('+1: All tests passed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(result.exitCode, 0); }); testWithoutContext('flutter test should run all tests when tags are unspecified', () async { final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory); - if (!(result.stdout as String).contains('+1 -1: Some tests failed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.'))); expect(result.exitCode, 1); }); testWithoutContext('flutter test should run a widgetTest with a given tag', () async { final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, extraArguments: const ['--tags', 'include-tag']); - if (!(result.stdout as String).contains('+1: All tests passed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(result.exitCode, 0); }); testWithoutContext('flutter test should not run a widgetTest with excluded tag', () async { final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, extraArguments: const ['--exclude-tags', 'exclude-tag']); - if (!(result.stdout as String).contains('+1: All tests passed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); expect(result.exitCode, 0); }); testWithoutContext('flutter test should run all widgetTest when tags are unspecified', () async { final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory); - if (!(result.stdout as String).contains('+1 -1: Some tests failed')) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.'))); expect(result.exitCode, 1); }); testWithoutContext('flutter test should test runs to completion', () async { final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory, extraArguments: const ['--verbose']); - final String stdout = result.stdout as String; - if ((!stdout.contains('+1: All tests passed')) || - (!stdout.contains('test 0: Starting flutter_tester process with command')) || - (!stdout.contains('test 0: deleting temporary directory')) || - (!stdout.contains('test 0: finished')) || - (!stdout.contains('test package returned with exit code 0'))) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + final String stdout = (result.stdout as String).replaceAll('\r', '\n'); + expect(stdout, contains(RegExp(r'\+\d+: All tests passed\!'))); + expect(stdout, contains('test 0: Starting flutter_tester process with command')); + expect(stdout, contains('test 0: deleting temporary directory')); + expect(stdout, contains('test 0: finished')); + expect(stdout, contains('test package returned with exit code 0')); if ((result.stderr as String).isNotEmpty) { fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); } @@ -202,14 +184,12 @@ void main() { testWithoutContext('flutter test should run all tests inside of a directory with no trailing slash', () async { final ProcessResult result = await _runFlutterTest(null, automatedTestsDirectory, '$flutterTestDirectory/child_directory', extraArguments: const ['--verbose']); - final String stdout = result.stdout as String; - if ((!stdout.contains('+2: All tests passed')) || - (!stdout.contains('test 0: Starting flutter_tester process with command')) || - (!stdout.contains('test 0: deleting temporary directory')) || - (!stdout.contains('test 0: finished')) || - (!stdout.contains('test package returned with exit code 0'))) { - fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); - } + final String stdout = (result.stdout as String).replaceAll('\r', '\n'); + expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed\!'))); + expect(stdout, contains('test 0: Starting flutter_tester process with command')); + expect(stdout, contains('test 0: deleting temporary directory')); + expect(stdout, contains('test 0: finished')); + expect(stdout, contains('test package returned with exit code 0')); if ((result.stderr as String).isNotEmpty) { fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); }