diff --git a/dev/devicelab/bin/tasks/run_release_test.dart b/dev/devicelab/bin/tasks/run_release_test.dart new file mode 100644 index 00000000000..d60e11c9eac --- /dev/null +++ b/dev/devicelab/bin/tasks/run_release_test.dart @@ -0,0 +1,75 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; + +void main() { + task(() async { + final Device device = await devices.workingDevice; + await device.unlock(); + final Directory appDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/ui')); + await inDirectory(appDir, () async { + final Completer ready = new Completer(); + print('run: starting...'); + final Process run = await startProcess( + path.join(flutterDirectory.path, 'bin', 'flutter'), + ['--suppress-analytics', 'run', '--release', '-d', device.deviceId, 'lib/main.dart'], + isBot: false, // we just want to test the output, not have any debugging info + ); + final List stdout = []; + final List stderr = []; + int runExitCode; + run.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + print('run:stdout: $line'); + stdout.add(line); + if (line.contains('To quit, press "q".')) + ready.complete(); + }); + run.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + print('run:stderr: $line'); + stdout.add(line); + }); + run.exitCode.then((int exitCode) { runExitCode = exitCode; }); + await Future.any(>[ ready.future, run.exitCode ]); + if (runExitCode != null) + throw 'Failed to run test app; runner unexpected exited, with exit code $runExitCode.'; + run.stdin.write('q'); + await run.exitCode; + if (stderr.isNotEmpty) + throw 'flutter run --release had output on standard error.'; + if (stdout.first == 'Building flutter tool...') + stdout.removeAt(0); + if (stdout.first == 'Running "flutter packages get" in ui...') + stdout.removeAt(0); + if (stdout.first == 'Initializing gradle...') + stdout.removeAt(0); + if (!(stdout.first.startsWith('Launching lib/main.dart on ') && stdout.first.endsWith(' in release mode...'))) + throw 'flutter run --release had unexpected first line: ${stdout.first}'; + stdout.removeAt(0); + if (stdout.first != 'Running \'gradlew assembleRelease\'...') + throw 'flutter run --release had unexpected second line: ${stdout.first}'; + stdout.removeAt(0); + if (!(stdout.first.startsWith('Built build/app/outputs/apk/release/app-release.apk (') && stdout.first.endsWith('MB).'))) + throw 'flutter run --release had unexpected third line: ${stdout.first}'; + stdout.removeAt(0); + if (stdout.join('\n') != '\nTo quit, press "q".\n\nApplication finished.') + throw 'flutter run --release had unexpected output after third line'; + }); + return new TaskResult.success(null); + }); +} diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index eaab51691d5..15b3450a595 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -183,16 +183,42 @@ Future getFlutterRepoCommitTimestamp(String commit) { }); } +/// Starts a subprocess. +/// +/// The first argument is the full path to the executable to run. +/// +/// The second argument is the list of arguments to provide on the command line. +/// This argument can be null, indicating no arguments (same as the empty list). +/// +/// The `environment` argument can be provided to configure environment variables +/// that will be made available to the subprocess. The `BOT` environment variable +/// is always set and overrides any value provided in the `environment` argument. +/// The `isBot` argument controls the value of the `BOT` variable. It will either +/// be "true", if `isBot` is true (the default), or "false" if it is false. +/// +/// The `BOT` variable is in particular used by the `flutter` tool to determine +/// how verbose to be and whether to enable analytics by default. +/// +/// The working directory can be provided using the `workingDirectory` argument. +/// By default it will default to the current working directory (see [cwd]). +/// +/// Information regarding the execution of the subprocess is printed to the +/// console. +/// +/// The actual process executes asynchronously. A handle to the subprocess is +/// returned in the form of a [Future] that completes to a [Process] object. Future startProcess( String executable, List arguments, { Map environment, + bool isBot: true, // set to false to pretend not to be on a bot (e.g. to test user-facing outputs) String workingDirectory, }) async { + assert(isBot != null); final String command = '$executable ${arguments?.join(" ") ?? ""}'; print('\nExecuting: $command'); environment ??= {}; - environment['BOT'] = 'true'; + environment['BOT'] = isBot ? 'true' : 'false'; final Process process = await _processManager.start( [executable]..addAll(arguments), environment: environment, diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index b7f18f39744..65b82cb9b0a 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -124,6 +124,13 @@ tasks: stage: devicelab required_agent_capabilities: ["mac/android"] + run_release_test: + description: > + Checks that `flutter run --release` does not crash. + stage: devicelab + required_agent_capabilities: ["mac/android"] + flaky: true + platform_interaction_test: description: > Checks platform interaction on Android. diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index a5394c2a87f..108e4f30e88 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -48,7 +48,6 @@ class GenSnapshot { '--causal_async_stacks', '--packages=$packagesPath', '--dependencies=$depfilePath', - '--print_snapshot_sizes', ]..addAll(additionalArgs); final String snapshotterPath = artifacts.getArtifactPath(Artifact.genSnapshot, snapshotType.platform, snapshotType.mode); diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index 735ab94483b..a0ef1fd2b98 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -21,26 +21,26 @@ class BotDetector { const BotDetector(); bool get isRunningOnBot { - return - platform.environment['BOT'] == 'true' || + return platform.environment['BOT'] != 'false' + && (platform.environment['BOT'] == 'true' - // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables - platform.environment['TRAVIS'] == 'true' || - platform.environment['CONTINUOUS_INTEGRATION'] == 'true' || - platform.environment.containsKey('CI') || // Travis and AppVeyor + // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables + || platform.environment['TRAVIS'] == 'true' + || platform.environment['CONTINUOUS_INTEGRATION'] == 'true' + || platform.environment.containsKey('CI') // Travis and AppVeyor - // https://www.appveyor.com/docs/environment-variables/ - platform.environment.containsKey('APPVEYOR') || + // https://www.appveyor.com/docs/environment-variables/ + || platform.environment.containsKey('APPVEYOR') - // https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - (platform.environment.containsKey('AWS_REGION') && platform.environment.containsKey('CODEBUILD_INITIATOR')) || + // https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html + || (platform.environment.containsKey('AWS_REGION') && platform.environment.containsKey('CODEBUILD_INITIATOR')) - // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables - platform.environment.containsKey('JENKINS_URL') || + // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables + || platform.environment.containsKey('JENKINS_URL') - // Properties on Flutter's Chrome Infra bots. - platform.environment['CHROME_HEADLESS'] == '1' || - platform.environment.containsKey('BUILDBOT_BUILDERNAME'); + // Properties on Flutter's Chrome Infra bots. + || platform.environment['CHROME_HEADLESS'] == '1' + || platform.environment.containsKey('BUILDBOT_BUILDERNAME')); } } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 0889174d9e4..2f84b944cd0 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -841,8 +841,9 @@ abstract class ResidentRunner { } printStatus('To display the performance overlay (WidgetsApp.showPerformanceOverlay), press "P".'); } - if (flutterDevices.any((FlutterDevice d) => d.device.supportsScreenshot)) + if (flutterDevices.any((FlutterDevice d) => d.device.supportsScreenshot)) { printStatus('To save a screenshot to flutter.png, press "s".'); + } } /// Called when a signal has requested we exit. diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index 20872b53a1d..f3e7e3d3dd4 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart @@ -125,22 +125,29 @@ class ColdRunner extends ResidentRunner { @override void printHelp({ @required bool details }) { bool haveDetails = false; + bool haveAnything = false; for (FlutterDevice device in flutterDevices) { final String dname = device.device.name; if (device.observatoryUris != null) { - for (Uri uri in device.observatoryUris) + for (Uri uri in device.observatoryUris) { printStatus('An Observatory debugger and profiler on $dname is available at $uri'); + haveAnything = true; + } } } if (supportsServiceProtocol) { haveDetails = true; - if (details) + if (details) { printHelpDetails(); + haveAnything = true; + } } if (haveDetails && !details) { printStatus('For a more detailed help message, press "h". To quit, press "q".'); - } else { + } else if (haveAnything) { printStatus('To repeat this help message, press "h". To quit, press "q".'); + } else { + printStatus('To quit, press "q".'); } } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 37567f1ca86..d140424aac2 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -53,7 +53,8 @@ class FlutterCommandRunner extends CommandRunner { argParser.addFlag('verbose', abbr: 'v', negatable: false, - help: 'Noisy logging, including all shell commands executed.'); + help: 'Noisy logging, including all shell commands executed.\n' + 'If used with --help, shows hidden options.'); argParser.addFlag('quiet', negatable: false, hide: !verboseHelp, @@ -66,11 +67,12 @@ class FlutterCommandRunner extends CommandRunner { help: 'Reports the version of this tool.'); argParser.addFlag('machine', negatable: false, - hide: true); + hide: !verboseHelp, + help: 'When used with the --version flag, outputs the information using JSON.'); argParser.addFlag('color', negatable: true, hide: !verboseHelp, - help: 'Whether to use terminal colors.'); + help: 'Whether to use terminal colors (requires support for ANSI escape sequences).'); argParser.addFlag('version-check', negatable: true, defaultsTo: true, @@ -78,61 +80,67 @@ class FlutterCommandRunner extends CommandRunner { help: 'Allow Flutter to check for updates when this command runs.'); argParser.addFlag('suppress-analytics', negatable: false, - hide: !verboseHelp, help: 'Suppress analytics reporting when this command runs.'); argParser.addFlag('bug-report', negatable: false, - help: - 'Captures a bug report file to submit to the Flutter team ' - '(contains local paths, device\nidentifiers, and log snippets).'); - argParser.addFlag('show-test-device', - negatable: false, - hide: !verboseHelp, - help: 'List the special \'flutter-tester\' device in device listings. ' - 'This headless device is used to\ntest Flutter tooling.'); + help: 'Captures a bug report file to submit to the Flutter team.\n' + 'Contains local paths, device identifiers, and log snippets.'); String packagesHelp; - if (fs.isFileSync(kPackagesFileName)) - packagesHelp = '\n(defaults to "$kPackagesFileName")'; - else - packagesHelp = '\n(required, since the current directory does not contain a "$kPackagesFileName" file)'; + bool showPackagesCommand; + if (fs.isFileSync(kPackagesFileName)) { + packagesHelp = '(defaults to "$kPackagesFileName")'; + showPackagesCommand = verboseHelp; + } else { + packagesHelp = '(required, since the current directory does not contain a "$kPackagesFileName" file)'; + showPackagesCommand = true; + } argParser.addOption('packages', - hide: !verboseHelp, - help: 'Path to your ".packages" file.$packagesHelp'); + hide: !showPackagesCommand, + help: 'Path to your ".packages" file.\n$packagesHelp'); + argParser.addOption('flutter-root', - help: 'The root directory of the Flutter repository (uses \$$kFlutterRootEnvironmentVariableName if set).'); + hide: !verboseHelp, + help: 'The root directory of the Flutter repository.\n' + 'Defaults to \$$kFlutterRootEnvironmentVariableName if set, otherwise uses the parent of the\n' + 'directory that the "flutter" script itself is in.'); if (verboseHelp) argParser.addSeparator('Local build selection options (not normally required):'); argParser.addOption('local-engine-src-path', hide: !verboseHelp, - help: - 'Path to your engine src directory, if you are building Flutter locally.\n' - 'Defaults to \$$kFlutterEngineEnvironmentVariableName if set, otherwise defaults to the path given in your pubspec.yaml\n' - 'dependency_overrides for $kFlutterEnginePackageName, if any, or, failing that, tries to guess at the location\n' - 'based on the value of the --flutter-root option.'); + help: 'Path to your engine src directory, if you are building Flutter locally.\n' + 'Defaults to \$$kFlutterEngineEnvironmentVariableName if set, otherwise defaults to the path given in your pubspec.yaml\n' + 'dependency_overrides for $kFlutterEnginePackageName, if any, or, failing that, tries to guess at the location\n' + 'based on the value of the --flutter-root option.'); argParser.addOption('local-engine', hide: !verboseHelp, - help: - 'Name of a build output within the engine out directory, if you are building Flutter locally.\n' - 'Use this to select a specific version of the engine if you have built multiple engine targets.\n' - 'This path is relative to --local-engine-src-path/out.'); + help: 'Name of a build output within the engine out directory, if you are building Flutter locally.\n' + 'Use this to select a specific version of the engine if you have built multiple engine targets.\n' + 'This path is relative to --local-engine-src-path/out.'); + + if (verboseHelp) + argParser.addSeparator('Options for testing the "flutter" tool itself:'); + argParser.addOption('record-to', - hide: true, - help: - 'Enables recording of process invocations (including stdout and stderr of all such invocations),\n' - 'and file system access (reads and writes).\n' - 'Serializes that recording to a directory with the path specified in this flag. If the\n' - 'directory does not already exist, it will be created.'); + hide: !verboseHelp, + help: 'Enables recording of process invocations (including stdout and stderr of all such invocations),\n' + 'and file system access (reads and writes).\n' + 'Serializes that recording to a directory with the path specified in this flag. If the\n' + 'directory does not already exist, it will be created.'); argParser.addOption('replay-from', - hide: true, - help: - 'Enables mocking of process invocations by replaying their stdout, stderr, and exit code from\n' - 'the specified recording (obtained via --record-to). The path specified in this flag must refer\n' - 'to a directory that holds serialized process invocations structured according to the output of\n' - '--record-to.'); + hide: !verboseHelp, + help: 'Enables mocking of process invocations by replaying their stdout, stderr, and exit code from\n' + 'the specified recording (obtained via --record-to). The path specified in this flag must refer\n' + 'to a directory that holds serialized process invocations structured according to the output of\n' + '--record-to.'); + argParser.addFlag('show-test-device', + negatable: false, + hide: !verboseHelp, + help: 'List the special \'flutter-tester\' device in device listings. ' + 'This headless device is used to\ntest Flutter tooling.'); } @override