Unverified Commit 52ae102f authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Adds tool warning log level and command line options to fail on warning/error output (#92031)

parent 02cfbc32
...@@ -170,6 +170,7 @@ Future<void> main(List<String> args) async { ...@@ -170,6 +170,7 @@ Future<void> main(List<String> args) async {
try { try {
flutterTestArgs.addAll(args); flutterTestArgs.addAll(args);
final Set<String> removeArgs = <String>{}; final Set<String> removeArgs = <String>{};
bool runSmokeTests = true;
for (final String arg in args) { for (final String arg in args) {
if (arg.startsWith('--local-engine=')) { if (arg.startsWith('--local-engine=')) {
localEngineEnv['FLUTTER_LOCAL_ENGINE'] = arg.substring('--local-engine='.length); localEngineEnv['FLUTTER_LOCAL_ENGINE'] = arg.substring('--local-engine='.length);
...@@ -181,12 +182,18 @@ Future<void> main(List<String> args) async { ...@@ -181,12 +182,18 @@ Future<void> main(List<String> args) async {
_shuffleSeed = arg.substring('--test-randomize-ordering-seed='.length); _shuffleSeed = arg.substring('--test-randomize-ordering-seed='.length);
removeArgs.add(arg); removeArgs.add(arg);
} }
if (arg == '--no-smoke-tests') {
runSmokeTests = false;
removeArgs.add(arg);
}
} }
flutterTestArgs.removeWhere((String arg) => removeArgs.contains(arg)); flutterTestArgs.removeWhere((String arg) => removeArgs.contains(arg));
if (Platform.environment.containsKey(CIRRUS_TASK_NAME)) if (Platform.environment.containsKey(CIRRUS_TASK_NAME))
print('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}'); print('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}');
print('═' * 80); print('═' * 80);
await _runSmokeTests(); if (runSmokeTests) {
await _runSmokeTests();
}
print('═' * 80); print('═' * 80);
await selectShard(<String, ShardRunner>{ await selectShard(<String, ShardRunner>{
'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests, 'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests,
...@@ -474,7 +481,7 @@ Future<void> _runBuildTests() async { ...@@ -474,7 +481,7 @@ Future<void> _runBuildTests() async {
Future<void> _runExampleProjectBuildTests(Directory exampleDirectory, [File? mainFile]) async { Future<void> _runExampleProjectBuildTests(Directory exampleDirectory, [File? mainFile]) async {
// Only verify caching with flutter gallery. // Only verify caching with flutter gallery.
final bool verifyCaching = exampleDirectory.path.contains('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 bool hasNullSafety = File(path.join(examplePath, 'null_safety')).existsSync();
final List<String> additionalArgs = <String>[ final List<String> additionalArgs = <String>[
if (hasNullSafety) '--no-sound-null-safety', if (hasNullSafety) '--no-sound-null-safety',
...@@ -786,7 +793,8 @@ Future<void> _runFrameworkTests() async { ...@@ -786,7 +793,8 @@ Future<void> _runFrameworkTests() async {
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots')); 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', 'devicelab'), ensurePrecompiledTool: false); // See https://github.com/flutter/flutter/issues/86209
await _pubRunTest(path.join(flutterRoot, 'dev', 'conductor', 'core'), forceSingleCore: true); 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', 'manual_tests'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes'));
...@@ -1621,6 +1629,7 @@ Future<void> _runFlutterTest(String workingDirectory, { ...@@ -1621,6 +1629,7 @@ Future<void> _runFlutterTest(String workingDirectory, {
Map<String, String>? environment, Map<String, String>? environment,
List<String> tests = const <String>[], List<String> tests = const <String>[],
bool shuffleTests = true, bool shuffleTests = true,
bool fatalWarnings = true,
}) async { }) async {
assert(!printOutput || outputChecker == null, 'Output either can be printed or checked but not both'); assert(!printOutput || outputChecker == null, 'Output either can be printed or checked but not both');
...@@ -1634,6 +1643,7 @@ Future<void> _runFlutterTest(String workingDirectory, { ...@@ -1634,6 +1643,7 @@ Future<void> _runFlutterTest(String workingDirectory, {
final List<String> args = <String>[ final List<String> args = <String>[
'test', 'test',
if (shuffleTests) '--test-randomize-ordering-seed=$shuffleSeed', if (shuffleTests) '--test-randomize-ordering-seed=$shuffleSeed',
if (fatalWarnings) '--fatal-warnings',
...options, ...options,
...tags, ...tags,
...flutterTestArgs, ...flutterTestArgs,
......
...@@ -43,15 +43,13 @@ class CodesignCommand extends Command<void> { ...@@ -43,15 +43,13 @@ class CodesignCommand extends Command<void> {
} }
argParser.addFlag( argParser.addFlag(
kVerify, kVerify,
help: help: 'Only verify expected binaries exist and are codesigned with entitlements.',
'Only verify expected binaries exist and are codesigned with entitlements.',
); );
argParser.addFlag( argParser.addFlag(
kSignatures, kSignatures,
defaultsTo: true, defaultsTo: true,
help: help: 'When off, this command will only verify the existence of binaries, and not their\n'
'When off, this command will only verify the existence of binaries, and not their\n' 'signatures or entitlements. Must be used with --verify flag.',
'signatures or entitlements. Must be used with --verify flag.',
); );
argParser.addOption( argParser.addOption(
kUpstream, kUpstream,
...@@ -92,24 +90,25 @@ class CodesignCommand extends Command<void> { ...@@ -92,24 +90,25 @@ class CodesignCommand extends Command<void> {
Future<void> run() async { Future<void> run() async {
if (!platform.isMacOS) { if (!platform.isMacOS) {
throw ConductorException( throw ConductorException(
'Error! Expected operating system "macos", actual operating system is: ' 'Error! Expected operating system "macos", actual operating system is: '
'"${platform.operatingSystem}"'); '"${platform.operatingSystem}"',
);
} }
if (argResults!['verify'] as bool != true) { if (argResults!['verify'] as bool != true) {
throw ConductorException( throw ConductorException(
'Sorry, but codesigning is not implemented yet. Please pass the ' 'Sorry, but codesigning is not implemented yet. Please pass the '
'--$kVerify flag to verify signatures.'); '--$kVerify flag to verify signatures.',
);
} }
String revision; String revision;
if (argResults!.wasParsed(kRevision)) { if (argResults!.wasParsed(kRevision)) {
stdio.printError( stdio.printWarning(
'Warning! When providing an arbitrary revision, the contents of the cache may not'); '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 '
'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',
stdio.printError( );
'the desired revision and run that version of the conductor.\n');
revision = argResults![kRevision] as String; revision = argResults![kRevision] as String;
} else { } else {
revision = ((await processManager.run( revision = ((await processManager.run(
...@@ -225,13 +224,11 @@ class CodesignCommand extends Command<void> { ...@@ -225,13 +224,11 @@ class CodesignCommand extends Command<void> {
) )
.toList(); .toList();
stdio.printError( stdio.printError(
'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n'); 'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n\n'
stdio.printError( 'If this commit is removing binaries from the cache, this test should be fixed by\n'
'If this commit is removing binaries from the cache, this test should be fixed by'); 'removing the relevant entry from either the "binariesWithEntitlements" or\n'
stdio.printError( '"binariesWithoutEntitlements" getters in dev/tools/lib/codesign.dart.',
'removing the relevant entry from either the `binariesWithEntitlements` or'); );
stdio.printError(
'`binariesWithoutEntitlements` getters in dev/tools/lib/codesign.dart.');
throw ConductorException('Did not find all expected binaries!'); throw ConductorException('Did not find all expected binaries!');
} }
...@@ -271,9 +268,10 @@ class CodesignCommand extends Command<void> { ...@@ -271,9 +268,10 @@ class CodesignCommand extends Command<void> {
if (codeSignResult.exitCode != 0) { if (codeSignResult.exitCode != 0) {
unsignedBinaries.add(binaryPath); unsignedBinaries.add(binaryPath);
stdio.printError( stdio.printError(
'File "$binaryPath" does not appear to be codesigned.\n' 'File "$binaryPath" does not appear to be codesigned.\n'
'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n' 'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n'
'${codeSignResult.stderr}\n'); '${codeSignResult.stderr}\n',
);
continue; continue;
} }
if (verifyEntitlements) { if (verifyEntitlements) {
...@@ -291,42 +289,39 @@ class CodesignCommand extends Command<void> { ...@@ -291,42 +289,39 @@ class CodesignCommand extends Command<void> {
} }
if (wrongEntitlementBinaries.isNotEmpty) { if (wrongEntitlementBinaries.isNotEmpty) {
stdio.printError( stdio.printError('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
'Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
wrongEntitlementBinaries.forEach(stdio.printError); wrongEntitlementBinaries.forEach(stdio.printError);
} }
if (unexpectedBinaries.isNotEmpty) { if (unexpectedBinaries.isNotEmpty) {
stdio.printError( stdio.printError('Found ${unexpectedBinaries.length} unexpected binaries in the cache:');
'Found ${unexpectedBinaries.length} unexpected binaries in the cache:');
unexpectedBinaries.forEach(print); unexpectedBinaries.forEach(print);
} }
// Finally, exit on any invalid state // Finally, exit on any invalid state
if (unsignedBinaries.isNotEmpty) { if (unsignedBinaries.isNotEmpty) {
throw ConductorException( throw ConductorException('Test failed because unsigned binaries detected.');
'Test failed because unsigned binaries detected.');
} }
if (wrongEntitlementBinaries.isNotEmpty) { if (wrongEntitlementBinaries.isNotEmpty) {
throw ConductorException( throw ConductorException(
'Test failed because files found with the wrong entitlements:\n' 'Test failed because files found with the wrong entitlements:\n'
'${wrongEntitlementBinaries.join('\n')}'); '${wrongEntitlementBinaries.join('\n')}',
);
} }
if (unexpectedBinaries.isNotEmpty) { if (unexpectedBinaries.isNotEmpty) {
throw ConductorException( throw ConductorException('Test failed because unexpected binaries found in the cache.');
'Test failed because unexpected binaries found in the cache.');
} }
final String? desiredRevision = argResults![kRevision] as String?; final String? desiredRevision = argResults![kRevision] as String?;
if (desiredRevision == null) { if (desiredRevision == null) {
stdio.printStatus( stdio.printStatus('Verified that binaries are codesigned and have expected entitlements.');
'Verified that binaries are codesigned and have expected entitlements.');
} else { } else {
stdio.printStatus( stdio.printStatus(
'Verified that binaries for commit $desiredRevision are codesigned and have ' 'Verified that binaries for commit $desiredRevision are codesigned and have '
'expected entitlements.'); 'expected entitlements.',
);
} }
} }
...@@ -387,8 +382,9 @@ class CodesignCommand extends Command<void> { ...@@ -387,8 +382,9 @@ class CodesignCommand extends Command<void> {
if (entitlementResult.exitCode != 0) { if (entitlementResult.exitCode != 0) {
stdio.printError( stdio.printError(
'The `codesign --entitlements` command failed with exit code ${entitlementResult.exitCode}:\n' 'The `codesign --entitlements` command failed with exit code ${entitlementResult.exitCode}:\n'
'${entitlementResult.stderr}\n'); '${entitlementResult.stderr}\n',
);
return false; return false;
} }
...@@ -399,8 +395,9 @@ class CodesignCommand extends Command<void> { ...@@ -399,8 +395,9 @@ class CodesignCommand extends Command<void> {
(await binariesWithEntitlements).contains(binaryPath); (await binariesWithEntitlements).contains(binaryPath);
if (output.contains(entitlement) != entitlementExpected) { if (output.contains(entitlement) != entitlementExpected) {
stdio.printError( stdio.printError(
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} ' 'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '
'entitlement $entitlement.'); 'entitlement $entitlement.',
);
passes = false; passes = false;
} }
} }
......
...@@ -9,25 +9,44 @@ import 'package:meta/meta.dart'; ...@@ -9,25 +9,44 @@ import 'package:meta/meta.dart';
abstract class Stdio { abstract class Stdio {
final List<String> logs = <String>[]; final List<String> logs = <String>[];
/// 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 @mustCallSuper
void printError(String message) { void printError(String message) {
logs.add('[error] $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. /// 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 @mustCallSuper
void printStatus(String message) { void printStatus(String message) {
logs.add('[status] $message'); logs.add('[status] $message');
} }
/// Debug messages that are only printed in verbose mode. /// 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 @mustCallSuper
void printTrace(String message) { void printTrace(String message) {
logs.add('[trace] $message'); logs.add('[trace] $message');
} }
/// Write string to STDOUT without trailing newline. /// Write the `message` string to STDOUT without a trailing newline.
@mustCallSuper @mustCallSuper
void write(String message) { void write(String message) {
logs.add('[write] $message'); logs.add('[write] $message');
......
...@@ -49,6 +49,7 @@ class TestCommand extends Command<void> { ...@@ -49,6 +49,7 @@ class TestCommand extends Command<void> {
'task, will write test results to the file.'); 'task, will write test results to the file.');
argParser.addFlag( argParser.addFlag(
'silent', 'silent',
help: 'Suppresses standard output and only print standard error output.',
); );
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:flutter_tools/src/asset.dart' hide defaultManifestPath; 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/context.dart';
import 'package:flutter_tools/src/base/file_system.dart' as libfs; import 'package:flutter_tools/src/base/file_system.dart' as libfs;
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
...@@ -70,8 +71,7 @@ Future<void> run(List<String> args) async { ...@@ -70,8 +71,7 @@ Future<void> run(List<String> args) async {
); );
if (assets == null) { if (assets == null) {
print('Unable to find assets.'); throwToolExit('Unable to find assets.', exitCode: 1);
exit(1);
} }
final List<Future<void>> calls = <Future<void>>[]; final List<Future<void>> calls = <Future<void>>[];
......
...@@ -39,7 +39,7 @@ Future<int> run( ...@@ -39,7 +39,7 @@ Future<int> run(
// Remove the verbose option; for help and doctor, users don't need to see // Remove the verbose option; for help and doctor, users don't need to see
// verbose logs. // verbose logs.
args = List<String>.of(args); args = List<String>.of(args);
args.removeWhere((String option) => option == '-v' || option == '--verbose'); args.removeWhere((String option) => option == '-vv' || option == '-v' || option == '--verbose');
} }
return runInContext<int>(() async { return runInContext<int>(() async {
......
...@@ -751,7 +751,7 @@ HostPlatform getCurrentHostPlatform() { ...@@ -751,7 +751,7 @@ HostPlatform getCurrentHostPlatform() {
return HostPlatform.windows_x64; return HostPlatform.windows_x64;
} }
globals.printError('Unsupported host platform, defaulting to Linux'); globals.printWarning('Unsupported host platform, defaulting to Linux');
return HostPlatform.linux_x64; return HostPlatform.linux_x64;
} }
......
...@@ -266,7 +266,7 @@ class Dart2JSTarget extends Target { ...@@ -266,7 +266,7 @@ class Dart2JSTarget extends Target {
final File dart2jsDeps = environment.buildDir final File dart2jsDeps = environment.buildDir
.childFile('app.dill.deps'); .childFile('app.dill.deps');
if (!dart2jsDeps.existsSync()) { 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}'); '${dart2jsDeps.path}');
return; return;
} }
......
...@@ -140,7 +140,7 @@ Future<void> writeBundle( ...@@ -140,7 +140,7 @@ Future<void> writeBundle(
try { try {
bundleDir.deleteSync(recursive: true); bundleDir.deleteSync(recursive: true);
} on FileSystemException catch (err) { } on FileSystemException catch (err) {
loggerOverride.printError( loggerOverride.printWarning(
'Failed to clean up asset directory ${bundleDir.path}: $err\n' 'Failed to clean up asset directory ${bundleDir.path}: $err\n'
'To clean build artifacts, use the command "flutter clean".' 'To clean build artifacts, use the command "flutter clean".'
); );
......
...@@ -164,6 +164,7 @@ class Cache { ...@@ -164,6 +164,7 @@ class Cache {
late final ArtifactUpdater _artifactUpdater = _createUpdater(); late final ArtifactUpdater _artifactUpdater = _createUpdater();
@visibleForTesting
@protected @protected
void registerArtifact(ArtifactSet artifactSet) { void registerArtifact(ArtifactSet artifactSet) {
_artifacts.add(artifactSet); _artifacts.add(artifactSet);
...@@ -320,13 +321,17 @@ class Cache { ...@@ -320,13 +321,17 @@ class Cache {
} on FileSystemException { } on FileSystemException {
if (!printed) { if (!printed) {
_logger.printTrace('Waiting to be able to obtain lock of Flutter binary artifacts directory: ${_lock!.path}'); _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 // This needs to go to stderr to avoid cluttering up stdout if a
// process is collecting stdout. It's not really an "error" though, // parent process is collecting stdout (e.g. when calling "flutter
// so print it in grey. // version --machine"). It's not really a "warning" though, so print it
_logger.printError( // 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...', 'Waiting for another flutter command to release the startup lock...',
color: TerminalColor.grey, color: TerminalColor.grey,
); );
_logger.hadWarningOutput = oldWarnings;
printed = true; printed = true;
} }
await Future<void>.delayed(const Duration(milliseconds: 50)); await Future<void>.delayed(const Duration(milliseconds: 50));
...@@ -509,7 +514,7 @@ class Cache { ...@@ -509,7 +514,7 @@ class Cache {
ErrorHandlingFileSystem.deleteIfExists(file); ErrorHandlingFileSystem.deleteIfExists(file);
} }
} on FileSystemException catch (err) { } 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 { ...@@ -703,7 +708,7 @@ abstract class CachedArtifact extends ArtifactSet {
await updateInner(artifactUpdater, fileSystem, operatingSystemUtils); await updateInner(artifactUpdater, fileSystem, operatingSystemUtils);
try { try {
if (version == null) { if (version == null) {
logger.printError( logger.printWarning(
'No known version for the artifact name "$name". ' 'No known version for the artifact name "$name". '
'Flutter can continue, but the artifact may be re-downloaded on ' 'Flutter can continue, but the artifact may be re-downloaded on '
'subsequent invocations until the problem is resolved.', 'subsequent invocations until the problem is resolved.',
...@@ -712,7 +717,7 @@ abstract class CachedArtifact extends ArtifactSet { ...@@ -712,7 +717,7 @@ abstract class CachedArtifact extends ArtifactSet {
cache.setStampFor(stampName, version!); cache.setStampFor(stampName, version!);
} }
} on FileSystemException catch (err) { } on FileSystemException catch (err) {
logger.printError( logger.printWarning(
'The new artifact "$name" was downloaded, but Flutter failed to update ' 'The new artifact "$name" was downloaded, but Flutter failed to update '
'its stamp file, receiving the error "$err". ' 'its stamp file, receiving the error "$err". '
'Flutter can continue, but the artifact may be re-downloaded on ' 'Flutter can continue, but the artifact may be re-downloaded on '
...@@ -1103,7 +1108,7 @@ class ArtifactUpdater { ...@@ -1103,7 +1108,7 @@ class ArtifactUpdater {
try { try {
file.deleteSync(); file.deleteSync();
} on FileSystemException catch (e) { } 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; continue;
} }
for (Directory directory = file.parent; directory.absolute.path != _tempStorage.absolute.path; directory = directory.parent) { for (Directory directory = file.parent; directory.absolute.path != _tempStorage.absolute.path; directory = directory.parent) {
......
...@@ -65,8 +65,9 @@ class BuildCommand extends FlutterCommand { ...@@ -65,8 +65,9 @@ class BuildCommand extends FlutterCommand {
} }
abstract class BuildSubCommand extends FlutterCommand { abstract class BuildSubCommand extends FlutterCommand {
BuildSubCommand() { BuildSubCommand({@required bool verboseHelp}) {
requiresPubspecYaml(); requiresPubspecYaml();
usesFatalWarningsOption(verboseHelp: verboseHelp);
} }
@override @override
......
...@@ -20,7 +20,7 @@ import '../runner/flutter_command.dart' show FlutterCommandResult; ...@@ -20,7 +20,7 @@ import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart'; import 'build.dart';
class BuildAarCommand extends BuildSubCommand { class BuildAarCommand extends BuildSubCommand {
BuildAarCommand({ @required bool verboseHelp }) { BuildAarCommand({ @required bool verboseHelp }) : super(verboseHelp: verboseHelp) {
argParser argParser
..addFlag( ..addFlag(
'debug', 'debug',
......
...@@ -16,7 +16,7 @@ import '../runner/flutter_command.dart' show FlutterCommandResult; ...@@ -16,7 +16,7 @@ import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart'; import 'build.dart';
class BuildApkCommand extends BuildSubCommand { class BuildApkCommand extends BuildSubCommand {
BuildApkCommand({bool verboseHelp = false}) { BuildApkCommand({bool verboseHelp = false}) : super(verboseHelp: verboseHelp) {
addTreeShakeIconsFlag(); addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(verboseHelp: verboseHelp); addBuildModeFlags(verboseHelp: verboseHelp);
......
...@@ -19,7 +19,9 @@ import '../runner/flutter_command.dart' show FlutterCommandResult; ...@@ -19,7 +19,9 @@ import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart'; import 'build.dart';
class BuildAppBundleCommand extends BuildSubCommand { class BuildAppBundleCommand extends BuildSubCommand {
BuildAppBundleCommand({bool verboseHelp = false}) { BuildAppBundleCommand({
bool verboseHelp = false,
}) : super(verboseHelp: verboseHelp) {
addTreeShakeIconsFlag(); addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(verboseHelp: verboseHelp); addBuildModeFlags(verboseHelp: verboseHelp);
......
...@@ -16,7 +16,10 @@ import '../runner/flutter_command.dart'; ...@@ -16,7 +16,10 @@ import '../runner/flutter_command.dart';
import 'build.dart'; import 'build.dart';
class BuildBundleCommand extends BuildSubCommand { class BuildBundleCommand extends BuildSubCommand {
BuildBundleCommand({bool verboseHelp = false, this.bundleBuilder}) { BuildBundleCommand({
bool verboseHelp = false,
this.bundleBuilder,
}) : super(verboseHelp: verboseHelp) {
usesTargetOption(); usesTargetOption();
usesFilesystemOptions(hide: !verboseHelp); usesFilesystemOptions(hide: !verboseHelp);
usesBuildNumberOption(); usesBuildNumberOption();
......
...@@ -19,7 +19,9 @@ import 'build.dart'; ...@@ -19,7 +19,9 @@ import 'build.dart';
/// A command to build a Fuchsia target. /// A command to build a Fuchsia target.
class BuildFuchsiaCommand extends BuildSubCommand { class BuildFuchsiaCommand extends BuildSubCommand {
BuildFuchsiaCommand({ @required bool verboseHelp }) { BuildFuchsiaCommand({
@required bool verboseHelp,
}) : super(verboseHelp: verboseHelp) {
addTreeShakeIconsFlag(); addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
usesDartDefineOption(); usesDartDefineOption();
......
...@@ -129,7 +129,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -129,7 +129,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
// xcarchive failed or not at expected location. // xcarchive failed or not at expected location.
if (xcarchiveResult.exitStatus != ExitStatus.success) { if (xcarchiveResult.exitStatus != ExitStatus.success) {
globals.logger.printStatus('Skipping IPA'); globals.printStatus('Skipping IPA');
return xcarchiveResult; return xcarchiveResult;
} }
...@@ -176,14 +176,16 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -176,14 +176,16 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
throwToolExit('Encountered error while building IPA:\n$errorMessage'); throwToolExit('Encountered error while building IPA:\n$errorMessage');
} }
globals.logger.printStatus('Built IPA to $outputPath.'); globals.printStatus('Built IPA to $outputPath.');
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
} }
abstract class _BuildIOSSubCommand extends BuildSubCommand { abstract class _BuildIOSSubCommand extends BuildSubCommand {
_BuildIOSSubCommand({ @required bool verboseHelp }) { _BuildIOSSubCommand({
@required bool verboseHelp
}) : super(verboseHelp: verboseHelp) {
addTreeShakeIconsFlag(); addTreeShakeIconsFlag();
addSplitDebugInfoOption(); addSplitDebugInfoOption();
addBuildModeFlags(verboseHelp: verboseHelp); addBuildModeFlags(verboseHelp: verboseHelp);
......
...@@ -39,7 +39,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -39,7 +39,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
}) : _flutterVersion = flutterVersion, }) : _flutterVersion = flutterVersion,
_buildSystem = buildSystem, _buildSystem = buildSystem,
_injectedCache = cache, _injectedCache = cache,
_injectedPlatform = platform { _injectedPlatform = platform,
super(verboseHelp: verboseHelp) {
addTreeShakeIconsFlag(); addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
usesFlavorOption(); usesFlavorOption();
......
...@@ -23,7 +23,8 @@ class BuildLinuxCommand extends BuildSubCommand { ...@@ -23,7 +23,8 @@ class BuildLinuxCommand extends BuildSubCommand {
BuildLinuxCommand({ BuildLinuxCommand({
@required OperatingSystemUtils operatingSystemUtils, @required OperatingSystemUtils operatingSystemUtils,
bool verboseHelp = false, bool verboseHelp = false,
}) : _operatingSystemUtils = operatingSystemUtils { }) : _operatingSystemUtils = operatingSystemUtils,
super(verboseHelp: verboseHelp) {
addCommonDesktopBuildOptions(verboseHelp: verboseHelp); addCommonDesktopBuildOptions(verboseHelp: verboseHelp);
final String defaultTargetPlatform = final String defaultTargetPlatform =
(_operatingSystemUtils.hostPlatform == HostPlatform.linux_arm64) ? (_operatingSystemUtils.hostPlatform == HostPlatform.linux_arm64) ?
......
...@@ -19,7 +19,9 @@ import 'build.dart'; ...@@ -19,7 +19,9 @@ import 'build.dart';
/// A command to build a macOS desktop target through a build shell script. /// A command to build a macOS desktop target through a build shell script.
class BuildMacosCommand extends BuildSubCommand { class BuildMacosCommand extends BuildSubCommand {
BuildMacosCommand({ @required bool verboseHelp }) { BuildMacosCommand({
@required bool verboseHelp,
}) : super(verboseHelp: verboseHelp) {
addCommonDesktopBuildOptions(verboseHelp: verboseHelp); addCommonDesktopBuildOptions(verboseHelp: verboseHelp);
usesBuildNumberOption(); usesBuildNumberOption();
usesBuildNameOption(); usesBuildNameOption();
......
...@@ -20,7 +20,7 @@ import 'build.dart'; ...@@ -20,7 +20,7 @@ import 'build.dart';
class BuildWebCommand extends BuildSubCommand { class BuildWebCommand extends BuildSubCommand {
BuildWebCommand({ BuildWebCommand({
@required bool verboseHelp, @required bool verboseHelp,
}) { }) : super(verboseHelp: verboseHelp) {
addTreeShakeIconsFlag(enabledByDefault: false); addTreeShakeIconsFlag(enabledByDefault: false);
usesTargetOption(); usesTargetOption();
usesPubOption(); usesPubOption();
......
...@@ -20,7 +20,9 @@ import 'build.dart'; ...@@ -20,7 +20,9 @@ import 'build.dart';
/// A command to build a windows desktop target through a build shell script. /// A command to build a windows desktop target through a build shell script.
class BuildWindowsCommand extends BuildSubCommand { class BuildWindowsCommand extends BuildSubCommand {
BuildWindowsCommand({ bool verboseHelp = false }) { BuildWindowsCommand({
bool verboseHelp = false,
}) : super(verboseHelp: verboseHelp) {
addCommonDesktopBuildOptions(verboseHelp: verboseHelp); addCommonDesktopBuildOptions(verboseHelp: verboseHelp);
} }
......
...@@ -19,7 +19,9 @@ import 'build.dart'; ...@@ -19,7 +19,9 @@ import 'build.dart';
/// A command to build a Windows UWP desktop target. /// A command to build a Windows UWP desktop target.
class BuildWindowsUwpCommand extends BuildSubCommand { class BuildWindowsUwpCommand extends BuildSubCommand {
BuildWindowsUwpCommand({ bool verboseHelp = false }) { BuildWindowsUwpCommand({
bool verboseHelp = false,
}) : super(verboseHelp: verboseHelp) {
addCommonDesktopBuildOptions(verboseHelp: verboseHelp); addCommonDesktopBuildOptions(verboseHelp: verboseHelp);
} }
......
...@@ -228,7 +228,7 @@ class CreateCommand extends CreateBase { ...@@ -228,7 +228,7 @@ class CreateCommand extends CreateBase {
validateProjectDir(overwrite: overwrite); validateProjectDir(overwrite: overwrite);
if (boolArg('with-driver-test')) { if (boolArg('with-driver-test')) {
globals.printError( globals.printWarning(
'The "--with-driver-test" argument has been deprecated and will no longer add a flutter ' '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 ' 'driver template. Instead, learn how to use package:integration_test by '
'visiting https://pub.dev/packages/integration_test .' 'visiting https://pub.dev/packages/integration_test .'
......
...@@ -309,7 +309,7 @@ class DaemonDomain extends Domain { ...@@ -309,7 +309,7 @@ class DaemonDomain extends Domain {
// capture the print output for testing. // capture the print output for testing.
// ignore: avoid_print // ignore: avoid_print
print(message.message); print(message.message);
} else if (message.level == 'error') { } else if (message.level == 'error' || message.level == 'warning') {
globals.stdio.stderrWrite('${message.message}\n'); globals.stdio.stderrWrite('${message.message}\n');
if (message.stackTrace != null) { if (message.stackTrace != null) {
globals.stdio.stderrWrite( globals.stdio.stderrWrite(
...@@ -1011,6 +1011,18 @@ class NotifyingLogger extends DelegatingLogger { ...@@ -1011,6 +1011,18 @@ class NotifyingLogger extends DelegatingLogger {
_sendMessage(LogMessage('error', message, stackTrace)); _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 @override
void printStatus( void printStatus(
String message, { String message, {
......
...@@ -48,7 +48,7 @@ class DevicesCommand extends FlutterCommand { ...@@ -48,7 +48,7 @@ class DevicesCommand extends FlutterCommand {
@override @override
Future<void> validateCommand() { Future<void> validateCommand() {
if (argResults?['timeout'] != null) { 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(); return super.validateCommand();
} }
......
...@@ -98,7 +98,7 @@ Future<bool> installApp( ...@@ -98,7 +98,7 @@ Future<bool> installApp(
if (uninstall && await device.isAppInstalled(package, userIdentifier: userIdentifier)) { if (uninstall && await device.isAppInstalled(package, userIdentifier: userIdentifier)) {
globals.printStatus('Uninstalling old version...'); globals.printStatus('Uninstalling old version...');
if (!await device.uninstallApp(package, userIdentifier: userIdentifier)) { 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) { } on ProcessException catch (e) {
......
...@@ -153,6 +153,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -153,6 +153,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
addDdsOptions(verboseHelp: verboseHelp); addDdsOptions(verboseHelp: verboseHelp);
addDevToolsOptions(verboseHelp: verboseHelp); addDevToolsOptions(verboseHelp: verboseHelp);
addAndroidSpecificBuildOptions(hide: !verboseHelp); addAndroidSpecificBuildOptions(hide: !verboseHelp);
usesFatalWarningsOption(verboseHelp: verboseHelp);
} }
bool get traceStartup => boolArg('trace-startup'); bool get traceStartup => boolArg('trace-startup');
......
...@@ -216,7 +216,8 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ...@@ -216,7 +216,8 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
'or as the string "none" to disable the timeout entirely.', 'or as the string "none" to disable the timeout entirely.',
defaultsTo: '30s', defaultsTo: '30s',
); );
addDdsOptions(verboseHelp: verboseHelp); addDdsOptions(verboseHelp: verboseHelp);
usesFatalWarningsOption(verboseHelp: verboseHelp);
} }
/// The interface for starting and configuring the tester. /// The interface for starting and configuring the tester.
...@@ -283,7 +284,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ...@@ -283,7 +284,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
// correct [requiredArtifacts] can be identified before [run] takes place. // correct [requiredArtifacts] can be identified before [run] takes place.
_isIntegrationTest = _shouldRunAsIntegrationTests(globals.fs.currentDirectory.absolute.path, _testFiles); _isIntegrationTest = _shouldRunAsIntegrationTests(globals.fs.currentDirectory.absolute.path, _testFiles);
globals.logger.printTrace( globals.printTrace(
'Found ${_testFiles.length} files which will be executed as ' 'Found ${_testFiles.length} files which will be executed as '
'${_isIntegrationTest ? 'Integration' : 'Widget'} Tests.', '${_isIntegrationTest ? 'Integration' : 'Widget'} Tests.',
); );
...@@ -338,7 +339,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ...@@ -338,7 +339,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
} }
if (_isIntegrationTest) { if (_isIntegrationTest) {
if (argResults.wasParsed('concurrency')) { if (argResults.wasParsed('concurrency')) {
globals.logger.printStatus( globals.printStatus(
'-j/--concurrency was parsed but will be ignored, this option is not ' '-j/--concurrency was parsed but will be ignored, this option is not '
'supported when running Integration Tests.', 'supported when running Integration Tests.',
); );
......
...@@ -190,7 +190,7 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -190,7 +190,7 @@ class UpdatePackagesCommand extends FlutterCommand {
if (pubspec.checksum.value == null) { if (pubspec.checksum.value == null) {
// If the checksum is invalid or missing, we can just ask them run to run // If the checksum is invalid or missing, we can just ask them run to run
// upgrade again to compute it. // upgrade again to compute it.
globals.printError( globals.printWarning(
'Warning: pubspec in ${directory.path} has out of date dependencies. ' 'Warning: pubspec in ${directory.path} has out of date dependencies. '
'Please run "flutter update-packages --force-upgrade" to update them correctly.' 'Please run "flutter update-packages --force-upgrade" to update them correctly.'
); );
...@@ -207,7 +207,7 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -207,7 +207,7 @@ class UpdatePackagesCommand extends FlutterCommand {
if (checksum != pubspec.checksum.value) { if (checksum != pubspec.checksum.value) {
// If the checksum doesn't match, they may have added or removed some dependencies. // 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. // 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. ' 'Warning: pubspec in ${directory.path} has updated or new dependencies. '
'Please run "flutter update-packages --force-upgrade" to update them correctly ' 'Please run "flutter update-packages --force-upgrade" to update them correctly '
'(checksum ${pubspec.checksum.value} != $checksum).' '(checksum ${pubspec.checksum.value} != $checksum).'
...@@ -1501,7 +1501,7 @@ Directory createTemporaryFlutterSdk( ...@@ -1501,7 +1501,7 @@ Directory createTemporaryFlutterSdk(
..createSync(recursive: true); ..createSync(recursive: true);
final PubspecYaml pubspecYaml = pubspecsByName[flutterPackage]; final PubspecYaml pubspecYaml = pubspecsByName[flutterPackage];
if (pubspecYaml == null) { if (pubspecYaml == null) {
logger.printError( logger.printWarning(
"Unexpected package '$flutterPackage' found in packages directory", "Unexpected package '$flutterPackage' found in packages directory",
); );
continue; continue;
......
...@@ -339,11 +339,11 @@ class FlutterManifest { ...@@ -339,11 +339,11 @@ class FlutterManifest {
final YamlList? fontFiles = fontFamily['fonts'] as YamlList?; final YamlList? fontFiles = fontFamily['fonts'] as YamlList?;
final String? familyName = fontFamily['family'] as String?; final String? familyName = fontFamily['family'] as String?;
if (familyName == null) { if (familyName == null) {
_logger.printError('Warning: Missing family name for font.', emphasis: true); _logger.printWarning('Warning: Missing family name for font.', emphasis: true);
continue; continue;
} }
if (fontFiles == null) { 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; continue;
} }
...@@ -351,7 +351,7 @@ class FlutterManifest { ...@@ -351,7 +351,7 @@ class FlutterManifest {
for (final Map<Object?, Object?> fontFile in fontFiles.cast<Map<Object?, Object?>>()) { for (final Map<Object?, Object?> fontFile in fontFiles.cast<Map<Object?, Object?>>()) {
final String? asset = fontFile['asset'] as String?; final String? asset = fontFile['asset'] as String?;
if (asset == null) { 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; continue;
} }
......
...@@ -393,7 +393,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> ...@@ -393,7 +393,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
} }
} }
if (pluginsUsingV1.length > 1) { if (pluginsUsingV1.length > 1) {
globals.printError( globals.printWarning(
'The plugins `${pluginsUsingV1.join(', ')}` use a deprecated version of the Android embedding.\n' '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 ' '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 ' 'support the Android V2 embedding. Otherwise, consider removing them since a future release '
...@@ -402,7 +402,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> ...@@ -402,7 +402,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
'https://flutter.dev/go/android-plugin-migration.' 'https://flutter.dev/go/android-plugin-migration.'
); );
} else if (pluginsUsingV1.isNotEmpty) { } else if (pluginsUsingV1.isNotEmpty) {
globals.printError( globals.printWarning(
'The plugin `${pluginsUsingV1.first}` uses a deprecated version of the Android embedding.\n' '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 ' '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 ' 'supports the Android V2 embedding. Otherwise, consider removing it since a future release '
...@@ -414,7 +414,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> ...@@ -414,7 +414,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
templateContent = _androidPluginRegistryTemplateNewEmbedding; templateContent = _androidPluginRegistryTemplateNewEmbedding;
break; break;
case AndroidEmbeddingVersion.v1: case AndroidEmbeddingVersion.v1:
globals.printError( globals.printWarning(
'This app is using a deprecated version of the Android embedding.\n' '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 ' 'To avoid unexpected runtime failures, or future build failures, try to migrate this '
'app to the V2 embedding.\n' 'app to the V2 embedding.\n'
...@@ -1301,7 +1301,7 @@ Future<void> generateMainDartWithPluginRegistrant( ...@@ -1301,7 +1301,7 @@ Future<void> generateMainDartWithPluginRegistrant(
newMainDart.deleteSync(); newMainDart.deleteSync();
} }
} on FileSystemException catch (error) { } on FileSystemException catch (error) {
globals.printError( globals.printWarning(
'Unable to remove ${newMainDart.path}, received error: $error.\n' 'Unable to remove ${newMainDart.path}, received error: $error.\n'
'You might need to run flutter clean.' 'You might need to run flutter clean.'
); );
......
...@@ -124,6 +124,10 @@ Future<void> _buildAssets( ...@@ -124,6 +124,10 @@ Future<void> _buildAssets(
assetDirPath: assetDir, assetDirPath: assetDir,
); );
if (assets == null) {
throwToolExit('Unable to find assets.', exitCode: 1);
}
final Map<String, DevFSContent> assetEntries = final Map<String, DevFSContent> assetEntries =
Map<String, DevFSContent>.of(assets.entries); Map<String, DevFSContent>.of(assets.entries);
await writeBundle(globals.fs.directory(assetDir), assetEntries); await writeBundle(globals.fs.directory(assetDir), assetEntries);
......
...@@ -150,6 +150,30 @@ void printError( ...@@ -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 /// Display normal output of the command. This should be used for things like
/// progress messages, success messages, or just normal command output. /// progress messages, success messages, or just normal command output.
/// ///
......
...@@ -176,7 +176,7 @@ class CocoaPods { ...@@ -176,7 +176,7 @@ class CocoaPods {
final CocoaPodsStatus installation = await evaluateCocoaPodsInstallation; final CocoaPodsStatus installation = await evaluateCocoaPodsInstallation;
switch (installation) { switch (installation) {
case CocoaPodsStatus.notInstalled: case CocoaPodsStatus.notInstalled:
_logger.printError( _logger.printWarning(
'Warning: CocoaPods not installed. Skipping pod install.\n' 'Warning: CocoaPods not installed. Skipping pod install.\n'
'$noCocoaPodsConsequence\n' '$noCocoaPodsConsequence\n'
'To install $cocoaPodsInstallInstructions\n', 'To install $cocoaPodsInstallInstructions\n',
...@@ -184,7 +184,7 @@ class CocoaPods { ...@@ -184,7 +184,7 @@ class CocoaPods {
); );
return false; return false;
case CocoaPodsStatus.brokenInstall: case CocoaPodsStatus.brokenInstall:
_logger.printError( _logger.printWarning(
'Warning: CocoaPods is installed but broken. Skipping pod install.\n' 'Warning: CocoaPods is installed but broken. Skipping pod install.\n'
'$brokenCocoaPodsConsequence\n' '$brokenCocoaPodsConsequence\n'
'To re-install $cocoaPodsInstallInstructions\n', 'To re-install $cocoaPodsInstallInstructions\n',
...@@ -192,7 +192,7 @@ class CocoaPods { ...@@ -192,7 +192,7 @@ class CocoaPods {
); );
return false; return false;
case CocoaPodsStatus.unknownVersion: case CocoaPodsStatus.unknownVersion:
_logger.printError( _logger.printWarning(
'Warning: Unknown CocoaPods version installed.\n' 'Warning: Unknown CocoaPods version installed.\n'
'$unknownCocoaPodsConsequence\n' '$unknownCocoaPodsConsequence\n'
'To upgrade $cocoaPodsInstallInstructions\n', 'To upgrade $cocoaPodsInstallInstructions\n',
...@@ -200,7 +200,7 @@ class CocoaPods { ...@@ -200,7 +200,7 @@ class CocoaPods {
); );
break; break;
case CocoaPodsStatus.belowMinimumVersion: case CocoaPodsStatus.belowMinimumVersion:
_logger.printError( _logger.printWarning(
'Warning: CocoaPods minimum required version $cocoaPodsMinimumVersion or greater not installed. Skipping pod install.\n' 'Warning: CocoaPods minimum required version $cocoaPodsMinimumVersion or greater not installed. Skipping pod install.\n'
'$noCocoaPodsConsequence\n' '$noCocoaPodsConsequence\n'
'To upgrade $cocoaPodsInstallInstructions\n', 'To upgrade $cocoaPodsInstallInstructions\n',
...@@ -208,7 +208,7 @@ class CocoaPods { ...@@ -208,7 +208,7 @@ class CocoaPods {
); );
return false; return false;
case CocoaPodsStatus.belowRecommendedVersion: case CocoaPodsStatus.belowRecommendedVersion:
_logger.printError( _logger.printWarning(
'Warning: CocoaPods recommended version $cocoaPodsRecommendedVersion or greater not installed.\n' 'Warning: CocoaPods recommended version $cocoaPodsRecommendedVersion or greater not installed.\n'
'Pods handling may fail on some projects involving plugins.\n' 'Pods handling may fail on some projects involving plugins.\n'
'To upgrade $cocoaPodsInstallInstructions\n', 'To upgrade $cocoaPodsInstallInstructions\n',
...@@ -406,15 +406,15 @@ class CocoaPods { ...@@ -406,15 +406,15 @@ class CocoaPods {
// plugin_pods = parse_KV_file('../.flutter-plugins') // plugin_pods = parse_KV_file('../.flutter-plugins')
if (xcodeProject.podfile.existsSync() && if (xcodeProject.podfile.existsSync() &&
xcodeProject.podfile.readAsStringSync().contains(".flutter-plugins'")) { 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' '$outOfDatePluginsPodfileConsequence\n'
'To regenerate the Podfile, run:\n'; 'To regenerate the Podfile, run:\n';
if (isIos) { if (isIos) {
throwToolExit('$error\n$podfileIosMigrationInstructions\n'); throwToolExit('$warning\n$podfileIosMigrationInstructions\n');
} else { } else {
// The old macOS Podfile will work until `.flutter-plugins` is removed. // The old macOS Podfile will work until `.flutter-plugins` is removed.
// Warn instead of exit. // Warn instead of exit.
_logger.printError('$error\n$podfileMacOSMigrationInstructions\n', emphasis: true); _logger.printWarning('$warning\n$podfileMacOSMigrationInstructions\n', emphasis: true);
} }
} }
} }
......
...@@ -415,7 +415,7 @@ class XCDevice { ...@@ -415,7 +415,7 @@ class XCDevice {
} else { } else {
cpuArchitecture = DarwinArch.arm64; cpuArchitecture = DarwinArch.arm64;
} }
_logger.printError( _logger.printWarning(
'Unknown architecture $architecture, defaulting to ' 'Unknown architecture $architecture, defaulting to '
'${getNameForDarwinArch(cpuArchitecture)}', '${getNameForDarwinArch(cpuArchitecture)}',
); );
......
...@@ -110,7 +110,7 @@ class MDnsObservatoryDiscovery { ...@@ -110,7 +110,7 @@ class MDnsObservatoryDiscovery {
return null; return null;
} }
if (srv.length > 1) { 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}).'); '- using first one (${srv.first.port}).');
} }
_logger.printTrace('Checking for authentication code for $domainName'); _logger.printTrace('Checking for authentication code for $domainName');
......
...@@ -479,23 +479,6 @@ class AndroidProject extends FlutterProjectPlatform { ...@@ -479,23 +479,6 @@ class AndroidProject extends FlutterProjectPlatform {
} }
Future<void> ensureReadyForPlatformSpecificTooling() async { Future<void> 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()) { if (isModule && _shouldRegenerateFromTemplate()) {
await _regenerateLibrary(); await _regenerateLibrary();
// Add ephemeral host app, if an editable host app does not already exist. // Add ephemeral host app, if an editable host app does not already exist.
......
...@@ -1178,7 +1178,7 @@ abstract class ResidentRunner extends ResidentHandlers { ...@@ -1178,7 +1178,7 @@ abstract class ResidentRunner extends ResidentHandlers {
); );
if (!_lastBuild.success) { if (!_lastBuild.success) {
for (final ExceptionMeasurement exceptionMeasurement in _lastBuild.exceptions.values) { for (final ExceptionMeasurement exceptionMeasurement in _lastBuild.exceptions.values) {
globals.logger.printError( globals.printError(
exceptionMeasurement.exception.toString(), exceptionMeasurement.exception.toString(),
stackTrace: globals.logger.isVerbose stackTrace: globals.logger.isVerbose
? exceptionMeasurement.stackTrace ? exceptionMeasurement.stackTrace
...@@ -1186,7 +1186,7 @@ abstract class ResidentRunner extends ResidentHandlers { ...@@ -1186,7 +1186,7 @@ abstract class ResidentRunner extends ResidentHandlers {
); );
} }
} }
globals.logger.printTrace('complete'); globals.printTrace('complete');
} }
@protected @protected
...@@ -1241,7 +1241,7 @@ abstract class ResidentRunner extends ResidentHandlers { ...@@ -1241,7 +1241,7 @@ abstract class ResidentRunner extends ResidentHandlers {
if (_dillOutputPath != null) { if (_dillOutputPath != null) {
return; return;
} }
globals.logger.printTrace('Caching compiled dill'); globals.printTrace('Caching compiled dill');
final File outputDill = globals.fs.file(dillOutputPath); final File outputDill = globals.fs.file(dillOutputPath);
if (outputDill.existsSync()) { if (outputDill.existsSync()) {
final String copyPath = getDefaultCachedKernelPath( final String copyPath = getDefaultCachedKernelPath(
...@@ -1561,7 +1561,7 @@ class TerminalHandler { ...@@ -1561,7 +1561,7 @@ class TerminalHandler {
_logger.printTrace('Deleting pid file (${_actualPidFile.path}).'); _logger.printTrace('Deleting pid file (${_actualPidFile.path}).');
_actualPidFile.deleteSync(); _actualPidFile.deleteSync();
} on FileSystemException catch (error) { } 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; _actualPidFile = null;
} }
......
...@@ -77,8 +77,8 @@ class ColdRunner extends ResidentRunner { ...@@ -77,8 +77,8 @@ class ColdRunner extends ResidentRunner {
return result; return result;
} }
} }
} on Exception catch (err) { } on Exception catch (err, stack) {
globals.printError(err.toString()); globals.printError('$err\n$stack');
appFailedToStart(); appFailedToStart();
return 1; return 1;
} }
......
...@@ -115,6 +115,7 @@ class FlutterOptions { ...@@ -115,6 +115,7 @@ class FlutterOptions {
static const String kDeferredComponents = 'deferred-components'; static const String kDeferredComponents = 'deferred-components';
static const String kAndroidProjectArgs = 'android-project-arg'; static const String kAndroidProjectArgs = 'android-project-arg';
static const String kInitializeFromDill = 'initialize-from-dill'; static const String kInitializeFromDill = 'initialize-from-dill';
static const String kFatalWarnings = 'fatal-warnings';
} }
/// flutter command categories for usage. /// flutter command categories for usage.
...@@ -178,6 +179,8 @@ abstract class FlutterCommand extends Command<void> { ...@@ -178,6 +179,8 @@ abstract class FlutterCommand extends Command<void> {
bool _usesIpv6Flag = false; bool _usesIpv6Flag = false;
bool _usesFatalWarnings = false;
bool get shouldRunPub => _usesPubOption && boolArg('pub'); bool get shouldRunPub => _usesPubOption && boolArg('pub');
bool get shouldUpdateCache => true; bool get shouldUpdateCache => true;
...@@ -271,6 +274,15 @@ abstract class FlutterCommand extends Command<void> { ...@@ -271,6 +274,15 @@ abstract class FlutterCommand extends Command<void> {
_usesTargetOption = true; _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 { String get targetFile {
if (argResults?.wasParsed('target') == true) { if (argResults?.wasParsed('target') == true) {
return stringArg('target')!; return stringArg('target')!;
...@@ -413,10 +425,10 @@ abstract class FlutterCommand extends Command<void> { ...@@ -413,10 +425,10 @@ abstract class FlutterCommand extends Command<void> {
// TODO(ianh): enable the following code once google3 is migrated away from --disable-dds (and add test to flutter_command_test.dart) // 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 (false) { // ignore: dead_code
if (ddsEnabled) { 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.'); .warningMark} The "--no-disable-dds" argument is deprecated and redundant, and should be omitted.');
} else { } else {
globals.printError('${globals.logger.terminal globals.printWarning('${globals.logger.terminal
.warningMark} The "--disable-dds" argument is deprecated. Use "--no-dds" instead.'); .warningMark} The "--disable-dds" argument is deprecated. Use "--no-dds" instead.');
} }
} }
...@@ -1123,6 +1135,9 @@ abstract class FlutterCommand extends Command<void> { ...@@ -1123,6 +1135,9 @@ abstract class FlutterCommand extends Command<void> {
name: 'command', name: 'command',
overrides: <Type, Generator>{FlutterCommand: () => this}, overrides: <Type, Generator>{FlutterCommand: () => this},
body: () async { body: () async {
if (_usesFatalWarnings) {
globals.logger.fatalWarnings = boolArg(FlutterOptions.kFatalWarnings);
}
// Prints the welcome message if needed. // Prints the welcome message if needed.
globals.flutterUsage.printWelcome(); globals.flutterUsage.printWelcome();
_printDeprecationWarning(); _printDeprecationWarning();
...@@ -1139,6 +1154,9 @@ abstract class FlutterCommand extends Command<void> { ...@@ -1139,6 +1154,9 @@ abstract class FlutterCommand extends Command<void> {
if (commandPath != null) { if (commandPath != null) {
_sendPostUsage(commandPath, commandResult, startTime, endTime); _sendPostUsage(commandPath, commandResult, startTime, endTime);
} }
if (_usesFatalWarnings) {
globals.logger.checkForFatalLogs();
}
} }
}, },
); );
...@@ -1146,13 +1164,12 @@ abstract class FlutterCommand extends Command<void> { ...@@ -1146,13 +1164,12 @@ abstract class FlutterCommand extends Command<void> {
void _printDeprecationWarning() { void _printDeprecationWarning() {
if (deprecated) { if (deprecated) {
globals.printError( globals.printWarning(
'${globals.logger.terminal.warningMark} The "$name" command is deprecated and ' '${globals.logger.terminal.warningMark} The "$name" command is deprecated and '
'will be removed in a future version of Flutter. ' 'will be removed in a future version of Flutter. '
'See https://flutter.dev/docs/development/tools/sdk/releases ' 'See https://flutter.dev/docs/development/tools/sdk/releases '
'for previous releases of Flutter.', 'for previous releases of Flutter.\n',
); );
globals.printError('');
} }
} }
......
...@@ -277,10 +277,10 @@ class FlutterVersion { ...@@ -277,10 +277,10 @@ class FlutterVersion {
); );
} on VersionCheckError catch (error) { } on VersionCheckError catch (error) {
if (globals.platform.environment.containsKey('FLUTTER_GIT_URL')) { 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}'); 'by the environment variable FLUTTER_GIT_URL = ${globals.flutterGit}');
} }
globals.logger.printError(error.toString()); globals.printError(error.toString());
rethrow; rethrow;
} finally { } finally {
await _removeVersionCheckRemoteIfExists(); await _removeVersionCheckRemoteIfExists();
......
...@@ -568,6 +568,20 @@ class StreamLogger extends Logger { ...@@ -568,6 +568,20 @@ class StreamLogger extends Logger {
int hangingIndent, int hangingIndent,
bool wrap, 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'); _log('[stderr] $message');
} }
......
...@@ -227,6 +227,8 @@ void main() { ...@@ -227,6 +227,8 @@ void main() {
const <String>['build', 'linux', '--debug', '--no-pub'] const <String>['build', 'linux', '--debug', '--no-pub']
); );
expect(testLogger.statusText, isNot(contains('STDOUT STUFF'))); 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')); expect(testLogger.traceText, contains('STDOUT STUFF'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
...@@ -308,6 +310,8 @@ ERROR: No file or variants found for asset: images/a_dot_burr.jpeg ...@@ -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.statusText, contains('STDOUT STUFF'));
expect(testLogger.traceText, isNot(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: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => processManager, ProcessManager: () => processManager,
......
...@@ -3,9 +3,13 @@ ...@@ -3,9 +3,13 @@
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8 // @dart = 2.8
import 'package:args/command_runner.dart'; 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:flutter_tools/src/runner/flutter_command.dart';
import 'package:meta/meta.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -13,27 +17,97 @@ import '../../src/test_flutter_command_runner.dart'; ...@@ -13,27 +17,97 @@ import '../../src/test_flutter_command_runner.dart';
void main() { void main() {
testUsingContext('obfuscate requires split-debug-info', () { testUsingContext('obfuscate requires split-debug-info', () {
final FakeBuildCommand command = FakeBuildCommand(); final FakeBuildInfoCommand command = FakeBuildInfoCommand();
final CommandRunner<void> commandRunner = createTestCommandRunner(command); final CommandRunner<void> commandRunner = createTestCommandRunner(command);
expect(() => commandRunner.run(<String>[ expect(() => commandRunner.run(<String>[
'build', 'fake',
'--obfuscate', '--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(<String>[
'build',
'test',
'--${FlutterOptions.kFatalWarnings}',
]);
} on Exception {
fail('Unexpected exception thrown');
}
}, overrides: <Type, Generator>{
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(<String>[
'build',
'test',
]);
} on Exception {
fail('Unexpected exception thrown');
}
}, overrides: <Type, Generator>{
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(<String>[
'build',
'test',
'--${FlutterOptions.kFatalWarnings}',
]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
}, overrides: <Type, Generator>{
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(<String>[
'build',
'test',
'--${FlutterOptions.kFatalWarnings}',
]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
}); });
} }
class FakeBuildCommand extends FlutterCommand { class FakeBuildInfoCommand extends FlutterCommand {
FakeBuildCommand() { FakeBuildInfoCommand() : super() {
addSplitDebugInfoOption(); addSplitDebugInfoOption();
addDartObfuscationOption(); addDartObfuscationOption();
} }
@override @override
String get description => throw UnimplementedError(); String get description => '';
@override @override
String get name => 'build'; String get name => 'fake';
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
...@@ -41,3 +115,35 @@ class FakeBuildCommand extends FlutterCommand { ...@@ -41,3 +115,35 @@ class FakeBuildCommand extends FlutterCommand {
return FlutterCommandResult.success(); 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<FlutterCommandResult> 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<FlutterCommandResult> runCommand() async {
return FlutterCommandResult.success();
}
}
...@@ -130,6 +130,29 @@ void main() { ...@@ -130,6 +130,29 @@ void main() {
Logger: () => notifyingLogger, Logger: () => notifyingLogger,
}); });
testUsingContext('printWarning should send daemon.logMessage event', () async {
final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
daemon = Daemon(
commands.stream,
responses.add,
notifyingLogger: notifyingLogger,
);
globals.printWarning('daemon.logMessage test');
final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
return map['event'] == 'daemon.logMessage' && (map['params'] as Map<String, dynamic>)['level'] == 'warning';
});
expect(response['id'], isNull);
expect(response['event'], 'daemon.logMessage');
final Map<String, String> logMessage = castStringKeyedMap(response['params']).cast<String, String>();
expect(logMessage['level'], 'warning');
expect(logMessage['message'], 'daemon.logMessage test');
await responses.close();
await commands.close();
}, overrides: <Type, Generator>{
Logger: () => notifyingLogger,
});
testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async { testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async {
final StringBuffer buffer = await capturedConsolePrint(() { final StringBuffer buffer = await capturedConsolePrint(() {
final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>(); final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
......
...@@ -294,6 +294,75 @@ void main() { ...@@ -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(<String>[
'run',
'--no-pub',
'--no-hot',
'--${FlutterOptions.kFatalWarnings}',
]);
} on Exception {
fail('Unexpected exception thrown');
}
}, overrides: <Type, Generator>{
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(<String>[
'run',
'--no-pub',
'--no-hot',
]);
} on Exception {
fail('Unexpected exception thrown');
}
}, overrides: <Type, Generator>{
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(<String>[
'run',
'--no-pub',
'--no-hot',
'--${FlutterOptions.kFatalWarnings}',
]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
}, overrides: <Type, Generator>{
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(<String>[
'run',
'--no-pub',
'--no-hot',
'--${FlutterOptions.kFatalWarnings}',
]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
testUsingContext('should only request artifacts corresponding to connected devices', () async { testUsingContext('should only request artifacts corresponding to connected devices', () async {
mockDeviceManager.devices = <Device>[FakeDevice(targetPlatform: TargetPlatform.android_arm)]; mockDeviceManager.devices = <Device>[FakeDevice(targetPlatform: TargetPlatform.android_arm)];
...@@ -496,7 +565,7 @@ class FakeDevice extends Fake implements Device { ...@@ -496,7 +565,7 @@ class FakeDevice extends Fake implements Device {
@override @override
String get id => 'fake_device'; String get id => 'fake_device';
void _throwToolExit(int code) => throwToolExit(null, exitCode: code); void _throwToolExit(int code) => throwToolExit('FakeDevice tool exit', exitCode: code);
@override @override
Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator); Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator);
...@@ -504,6 +573,9 @@ class FakeDevice extends Fake implements Device { ...@@ -504,6 +573,9 @@ class FakeDevice extends Fake implements Device {
@override @override
bool supportsRuntimeMode(BuildMode mode) => true; bool supportsRuntimeMode(BuildMode mode) => true;
@override
Future<bool> get supportsHardwareRendering async => true;
@override @override
bool supportsHotReload = false; bool supportsHotReload = false;
...@@ -542,7 +614,7 @@ class FakeDevice extends Fake implements Device { ...@@ -542,7 +614,7 @@ class FakeDevice extends Fake implements Device {
@override @override
final PlatformType platformType = PlatformType.ios; final PlatformType platformType = PlatformType.ios;
bool startAppSuccess = true; bool startAppSuccess;
@override @override
DevFSWriter createDevFSWriter( DevFSWriter createDevFSWriter(
...@@ -564,9 +636,12 @@ class FakeDevice extends Fake implements Device { ...@@ -564,9 +636,12 @@ class FakeDevice extends Fake implements Device {
bool ipv6 = false, bool ipv6 = false,
String userIdentifier, String userIdentifier,
}) async { }) async {
if (!startAppSuccess) { if (startAppSuccess == false) {
return LaunchResult.failed(); return LaunchResult.failed();
} }
if (startAppSuccess == true) {
return LaunchResult.succeeded();
}
final String dartFlags = debuggingOptions.dartFlags; final String dartFlags = debuggingOptions.dartFlags;
// In release mode, --dart-flags should be set to the empty string and // In release mode, --dart-flags should be set to the empty string and
// provided flags should be dropped. In debug and profile modes, // provided flags should be dropped. In debug and profile modes,
...@@ -587,18 +662,20 @@ class FakeDevice extends Fake implements Device { ...@@ -587,18 +662,20 @@ class FakeDevice extends Fake implements Device {
} }
class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory { class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory {
ApplicationPackage package; FakeApplicationPackageFactory(this.applicationPackage);
ApplicationPackage applicationPackage;
@override @override
Future<ApplicationPackage> getPackageForPlatform( Future<ApplicationPackage> getPackageForPlatform(
TargetPlatform platform, { TargetPlatform platform, {
BuildInfo buildInfo, BuildInfo buildInfo,
File applicationBinary, File applicationBinary,
}) async { }) async => applicationPackage;
return package;
}
} }
class FakeApplicationPackage extends Fake implements ApplicationPackage { }
class TestRunCommandWithFakeResidentRunner extends RunCommand { class TestRunCommandWithFakeResidentRunner extends RunCommand {
FakeResidentRunner fakeResidentRunner; FakeResidentRunner fakeResidentRunner;
......
...@@ -16,6 +16,7 @@ import 'package:flutter_tools/src/cache.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/commands/test.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/project.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/runner.dart';
import 'package:flutter_tools/src/test/test_wrapper.dart'; import 'package:flutter_tools/src/test/test_wrapper.dart';
import 'package:flutter_tools/src/test/watcher.dart'; import 'package:flutter_tools/src/test/watcher.dart';
...@@ -644,6 +645,60 @@ dev_dependencies: ...@@ -644,6 +645,60 @@ dev_dependencies:
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
DeviceManager: () => _FakeDeviceManager(<Device>[]), DeviceManager: () => _FakeDeviceManager(<Device>[]),
}); });
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<void> commandRunner = createTestCommandRunner(testCommand);
try {
await commandRunner.run(const <String>[
'test',
'--no-pub',
'--${FlutterOptions.kFatalWarnings}',
]);
} on Exception {
fail('Unexpected exception thrown');
}
}, overrides: <Type, Generator>{
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<void> commandRunner = createTestCommandRunner(testCommand);
testLogger.printWarning('Warning: Mild annoyance, Will Robinson!');
expect(commandRunner.run(const <String>[
'test',
'--no-pub',
'--${FlutterOptions.kFatalWarnings}',
]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
}, overrides: <Type, Generator>{
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<void> commandRunner = createTestCommandRunner(testCommand);
testLogger.printError('Error: Danger Will Robinson!');
expect(commandRunner.run(const <String>[
'test',
'--no-pub',
'--${FlutterOptions.kFatalWarnings}',
]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
} }
class FakeFlutterTestRunner implements FlutterTestRunner { class FakeFlutterTestRunner implements FlutterTestRunner {
......
...@@ -311,7 +311,7 @@ flutter: ...@@ -311,7 +311,7 @@ flutter:
await writeBundle(directory, <String, DevFSContent>{}, loggerOverride: testLogger); await writeBundle(directory, <String, DevFSContent>{}, 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 { testUsingContext('does not unnecessarily recreate asset manifest, font manifest, license', () async {
......
...@@ -137,7 +137,7 @@ void main() { ...@@ -137,7 +137,7 @@ void main() {
final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache); final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache);
await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils()); 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 { testWithoutContext('Continues on missing version file', () async {
...@@ -153,7 +153,7 @@ void main() { ...@@ -153,7 +153,7 @@ void main() {
final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache); final FakeSimpleArtifact artifact = FakeSimpleArtifact(cache);
await artifact.update(FakeArtifactUpdater(), logger, fileSystem, FakeOperatingSystemUtils()); 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', () { testWithoutContext('Gradle wrapper should not be up to date, if some cached artifact is not available', () {
...@@ -725,7 +725,7 @@ void main() { ...@@ -725,7 +725,7 @@ void main() {
cache.clearStampFiles(); 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 { testWithoutContext('FlutterWebSdk fetches web artifacts and deletes previous directory contents', () async {
......
...@@ -83,6 +83,8 @@ void main() { ...@@ -83,6 +83,8 @@ void main() {
} }
class FakeBuildSubCommand extends BuildSubCommand { class FakeBuildSubCommand extends BuildSubCommand {
FakeBuildSubCommand() : super(verboseHelp: false);
@override @override
String get description => throw UnimplementedError(); String get description => throw UnimplementedError();
......
...@@ -432,8 +432,8 @@ void main() { ...@@ -432,8 +432,8 @@ void main() {
buildMode: BuildMode.debug, buildMode: BuildMode.debug,
); );
expect(logger.errorText, contains('Warning: Podfile is out of date')); expect(logger.warningText, contains('Warning: Podfile is out of date'));
expect(logger.errorText, contains('rm macos/Podfile')); expect(logger.warningText, contains('rm macos/Podfile'));
expect(fakeProcessManager, hasNoRemainingExpectations); expect(fakeProcessManager, hasNoRemainingExpectations);
}); });
......
...@@ -747,7 +747,7 @@ dependencies: ...@@ -747,7 +747,7 @@ dependencies:
.childFile('GeneratedPluginRegistrant.java'); .childFile('GeneratedPluginRegistrant.java');
expect(registrant.readAsStringSync(), expect(registrant.readAsStringSync(),
contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));')); 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' '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. ' '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' 'Otherwise, consider removing it since a future release of Flutter will remove these deprecated APIs.\n'
...@@ -827,7 +827,7 @@ dependencies: ...@@ -827,7 +827,7 @@ dependencies:
await injectPlugins(flutterProject, androidPlatform: true); 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' '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' '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' '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: ...@@ -854,7 +854,7 @@ dependencies:
contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));')); contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
expect(registrant.readAsStringSync(), expect(registrant.readAsStringSync(),
contains('plugin4.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin4.UseOldEmbedding"));')); 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' '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. ' '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' 'Otherwise, consider removing them since a future release of Flutter will remove these deprecated APIs.\n'
...@@ -882,7 +882,7 @@ dependencies: ...@@ -882,7 +882,7 @@ dependencies:
contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));')); contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
expect(registrant.readAsStringSync(), expect(registrant.readAsStringSync(),
contains('plugin4.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin4.UseOldEmbedding"));')); 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' '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. ' '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' 'Otherwise, consider removing them since a future release of Flutter will remove these deprecated APIs.\n'
......
...@@ -183,7 +183,7 @@ void main() { ...@@ -183,7 +183,7 @@ void main() {
// android:name="flutterEmbedding" android:value="2" />. // android:name="flutterEmbedding" android:value="2" />.
await project.regeneratePlatformSpecificTooling(); 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 { _testInMemory('Android plugin without example app does not show a warning', () async {
final FlutterProject project = await aPluginProject(); final FlutterProject project = await aPluginProject();
......
...@@ -87,7 +87,7 @@ void main() { ...@@ -87,7 +87,7 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(flutterCommand); final CommandRunner<void> runner = createTestCommandRunner(flutterCommand);
await runner.run(<String>['deprecated']); await runner.run(<String>['deprecated']);
expect(testLogger.errorText, expect(testLogger.warningText,
contains('The "deprecated" command is deprecated and will be removed in ' contains('The "deprecated" command is deprecated and will be removed in '
'a future version of Flutter.')); 'a future version of Flutter.'));
expect(flutterCommand.usage, expect(flutterCommand.usage,
......
...@@ -129,7 +129,7 @@ void main() { ...@@ -129,7 +129,7 @@ void main() {
// We get a warning about the unexpected package. // We get a warning about the unexpected package.
expect( expect(
bufferLogger.errorText, bufferLogger.warningText,
contains("Unexpected package 'extra' found in packages directory"), contains("Unexpected package 'extra' found in packages directory"),
); );
......
...@@ -10,12 +10,15 @@ import 'package:file/file.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/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.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/base/terminal.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:test/fake.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
import '../src/fakes.dart';
import 'test_utils.dart'; import 'test_utils.dart';
final String dart = fileSystem.path final String dart = fileSystem.path
...@@ -24,53 +27,122 @@ final String dart = fileSystem.path ...@@ -24,53 +27,122 @@ final String dart = fileSystem.path
void main() { void main() {
group('Cache.lock', () { group('Cache.lock', () {
// Windows locking is too flaky for this to work reliably. // Windows locking is too flaky for this to work reliably.
if (!platform.isWindows) { if (platform.isWindows) {
testWithoutContext( return;
'should log a message to stderr when lock is not acquired', () async { }
final String oldRoot = Cache.flutterRoot; testWithoutContext(
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('cache_test.'); 'should log a message to stderr when lock is not acquired', () async {
final BufferLogger logger = BufferLogger( final String oldRoot = Cache.flutterRoot;
terminal: Terminal.test(supportsColor: false, supportsEmoji: false), final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('cache_test.');
outputPreferences: OutputPreferences(), 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 { final File cacheFile = fileSystem.file(fileSystem.path
Cache.flutterRoot = tempDir.absolute.path; .join(Cache.flutterRoot, 'bin', 'cache', 'lockfile'))
final Cache cache = Cache.test( ..createSync(recursive: true);
fileSystem: fileSystem, final File script = fileSystem.file(fileSystem.path
processManager: FakeProcessManager.any(), .join(Cache.flutterRoot, 'bin', 'cache', 'test_lock.dart'));
logger: logger, 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:async';
import 'dart:io'; import 'dart:io';
Future<void> main(List<String> args) async { Future<void> main(List<String> args) async {
File file = File(args[0]); File file = File(args[0]);
RandomAccessFile lock = file.openSync(mode: FileMode.write); RandomAccessFile lock = file.openSync(mode: FileMode.write);
lock.lockSync(); lock.lockSync();
await Future<void>.delayed(const Duration(milliseconds: 1000)); await Future<void>.delayed(const Duration(milliseconds: 1000));
exit(0); exit(0);
} }
'''); ''');
final Process process = await const LocalProcessManager().start( final Process process = await const LocalProcessManager().start(
<String>[dart, script.absolute.path, cacheFile.absolute.path], <String>[dart, script.absolute.path, cacheFile.absolute.path],
); );
await Future<void>.delayed(const Duration(milliseconds: 500)); await Future<void>.delayed(const Duration(milliseconds: 500));
await cache.lock(); await cache.lock();
process.kill(io.ProcessSignal.sigkill); process.kill(io.ProcessSignal.sigkill);
} finally { } finally {
tryToDelete(tempDir); tryToDelete(tempDir);
Cache.flutterRoot = oldRoot; Cache.flutterRoot = oldRoot;
} }
expect(logger.statusText, isEmpty); expect(logger.statusText, isEmpty);
expect(logger.errorText, expect(logger.errorText, isEmpty);
equals('Waiting for another flutter command to release the startup lock...\n')); 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<void> downloadZippedTarball(String message, Uri url, Directory location) async {
onDownloadZipTarball?.call(message, url, location);
}
@override
Future<void> 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<void> updateInner(ArtifactUpdater artifactUpdater, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils) async { }
}
...@@ -115,84 +115,66 @@ void main() { ...@@ -115,84 +115,66 @@ void main() {
testWithoutContext('flutter test should run a test when its name matches a regexp', () async { testWithoutContext('flutter test should run a test when its name matches a regexp', () async {
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--name', 'inc.*de']); extraArguments: const <String>['--name', 'inc.*de']);
if (!(result.stdout as String).contains('+1: All tests passed')) { expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 0); expect(result.exitCode, 0);
}); });
testWithoutContext('flutter test should run a test when its name contains a string', () async { testWithoutContext('flutter test should run a test when its name contains a string', () async {
final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--plain-name', 'include']); extraArguments: const <String>['--plain-name', 'include']);
if (!(result.stdout as String).contains('+1: All tests passed')) { expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 0); expect(result.exitCode, 0);
}); });
testWithoutContext('flutter test should run a test with a given tag', () async { testWithoutContext('flutter test should run a test with a given tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--tags', 'include-tag']); extraArguments: const <String>['--tags', 'include-tag']);
if (!(result.stdout as String).contains('+1: All tests passed')) { expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 0); expect(result.exitCode, 0);
}); });
testWithoutContext('flutter test should not run a test with excluded tag', () async { testWithoutContext('flutter test should not run a test with excluded tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--exclude-tags', 'exclude-tag']); extraArguments: const <String>['--exclude-tags', 'exclude-tag']);
if (!(result.stdout as String).contains('+1: All tests passed')) { expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 0); expect(result.exitCode, 0);
}); });
testWithoutContext('flutter test should run all tests when tags are unspecified', () async { testWithoutContext('flutter test should run all tests when tags are unspecified', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory); final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory);
if (!(result.stdout as String).contains('+1 -1: Some tests failed')) { expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 1); expect(result.exitCode, 1);
}); });
testWithoutContext('flutter test should run a widgetTest with a given tag', () async { testWithoutContext('flutter test should run a widgetTest with a given tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--tags', 'include-tag']); extraArguments: const <String>['--tags', 'include-tag']);
if (!(result.stdout as String).contains('+1: All tests passed')) { expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 0); expect(result.exitCode, 0);
}); });
testWithoutContext('flutter test should not run a widgetTest with excluded tag', () async { testWithoutContext('flutter test should not run a widgetTest with excluded tag', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--exclude-tags', 'exclude-tag']); extraArguments: const <String>['--exclude-tags', 'exclude-tag']);
if (!(result.stdout as String).contains('+1: All tests passed')) { expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 0); expect(result.exitCode, 0);
}); });
testWithoutContext('flutter test should run all widgetTest when tags are unspecified', () async { testWithoutContext('flutter test should run all widgetTest when tags are unspecified', () async {
final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory); final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory);
if (!(result.stdout as String).contains('+1 -1: Some tests failed')) { expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.')));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
expect(result.exitCode, 1); expect(result.exitCode, 1);
}); });
testWithoutContext('flutter test should test runs to completion', () async { testWithoutContext('flutter test should test runs to completion', () async {
final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory, final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory,
extraArguments: const <String>['--verbose']); extraArguments: const <String>['--verbose']);
final String stdout = result.stdout as String; final String stdout = (result.stdout as String).replaceAll('\r', '\n');
if ((!stdout.contains('+1: All tests passed')) || expect(stdout, contains(RegExp(r'\+\d+: All tests passed\!')));
(!stdout.contains('test 0: Starting flutter_tester process with command')) || expect(stdout, contains('test 0: Starting flutter_tester process with command'));
(!stdout.contains('test 0: deleting temporary directory')) || expect(stdout, contains('test 0: deleting temporary directory'));
(!stdout.contains('test 0: finished')) || expect(stdout, contains('test 0: finished'));
(!stdout.contains('test package returned with exit code 0'))) { expect(stdout, contains('test package returned with exit code 0'));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
if ((result.stderr as String).isNotEmpty) { if ((result.stderr as String).isNotEmpty) {
fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n');
} }
...@@ -202,14 +184,12 @@ void main() { ...@@ -202,14 +184,12 @@ void main() {
testWithoutContext('flutter test should run all tests inside of a directory with no trailing slash', () async { 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', final ProcessResult result = await _runFlutterTest(null, automatedTestsDirectory, '$flutterTestDirectory/child_directory',
extraArguments: const <String>['--verbose']); extraArguments: const <String>['--verbose']);
final String stdout = result.stdout as String; final String stdout = (result.stdout as String).replaceAll('\r', '\n');
if ((!stdout.contains('+2: All tests passed')) || expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed\!')));
(!stdout.contains('test 0: Starting flutter_tester process with command')) || expect(stdout, contains('test 0: Starting flutter_tester process with command'));
(!stdout.contains('test 0: deleting temporary directory')) || expect(stdout, contains('test 0: deleting temporary directory'));
(!stdout.contains('test 0: finished')) || expect(stdout, contains('test 0: finished'));
(!stdout.contains('test package returned with exit code 0'))) { expect(stdout, contains('test package returned with exit code 0'));
fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n');
}
if ((result.stderr as String).isNotEmpty) { if ((result.stderr as String).isNotEmpty) {
fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n');
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment