mirror of
https://github.com/flutter/flutter
synced 2024-08-25 02:46:31 +00:00
Disallow calling stop() or cancel() multiple times. This means that when you use startProgress you have to more carefully think about what exactly is going on. Properly cancel startProgress in non-ANSI situations, so that back-to-back startProgress calls all render to the console.
This commit is contained in:
parent
ee396272d3
commit
acf4b6c1aa
|
@ -106,6 +106,18 @@ You must set the `ANDROID_HOME` environment variable to run tests on Android. If
|
|||
you have a local build of the Flutter engine, then you have a copy of the
|
||||
Android SDK at `.../engine/src/third_party/android_tools/sdk`.
|
||||
|
||||
You can find where your Android SDK is using `flutter doctor`.
|
||||
|
||||
## Running all tests
|
||||
|
||||
To run all tests defined in `manifest.yaml`, use option `-a` (`--all`):
|
||||
|
||||
```sh
|
||||
dart bin/run.dart -a
|
||||
```
|
||||
|
||||
## Running specific tests
|
||||
|
||||
To run a test, use option `-t` (`--task`):
|
||||
|
||||
```sh
|
||||
|
@ -127,20 +139,15 @@ To run multiple tests, repeat option `-t` (`--task`) multiple times:
|
|||
dart bin/run.dart -t test1 -t test2 -t test3
|
||||
```
|
||||
|
||||
To run all tests defined in `manifest.yaml`, use option `-a` (`--all`):
|
||||
To run tests from a specific stage, use option `-s` (`--stage`).
|
||||
Currently there are only three stages defined, `devicelab`,
|
||||
`devicelab_ios` and `devicelab_win`.
|
||||
|
||||
```sh
|
||||
dart bin/run.dart -a
|
||||
```
|
||||
|
||||
To run tests from a specific stage, use option `-s` (`--stage`):
|
||||
|
||||
```sh
|
||||
dart bin/run.dart -s {NAME_OF_STAGE}
|
||||
```
|
||||
|
||||
Currently there are only three stages defined, `devicelab`, `devicelab_ios` and `devicelab_win`.
|
||||
|
||||
# Reproducing broken builds locally
|
||||
|
||||
To reproduce the breakage locally `git checkout` the corresponding Flutter
|
||||
|
|
|
@ -31,14 +31,13 @@ Future<void> testReload(Process process, { Future<void> Function() onListening }
|
|||
.listen((String line) {
|
||||
print('attach:stdout: $line');
|
||||
stdout.add(line);
|
||||
if (line.contains('Waiting') && onListening != null) {
|
||||
if (line.contains('Waiting') && onListening != null)
|
||||
listening.complete(onListening());
|
||||
}
|
||||
if (line.contains('To quit, press "q".'))
|
||||
ready.complete();
|
||||
if (line.contains('Reloaded '))
|
||||
reloaded.complete();
|
||||
if (line.contains('Restarted app in '))
|
||||
if (line.contains('Restarted application in '))
|
||||
restarted.complete();
|
||||
if (line.contains('Application finished'))
|
||||
finished.complete();
|
||||
|
@ -91,7 +90,7 @@ void main() {
|
|||
await device.unlock();
|
||||
final Directory appDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/ui'));
|
||||
await inDirectory(appDir, () async {
|
||||
section('Build: starting...');
|
||||
section('Building');
|
||||
final String buildStdout = await eval(
|
||||
path.join(flutterDirectory.path, 'bin', 'flutter'),
|
||||
<String>['--suppress-analytics', 'build', 'apk', '--debug', 'lib/main.dart'],
|
||||
|
@ -105,7 +104,7 @@ void main() {
|
|||
await device.adb(<String>['install', '-r', apkPath]);
|
||||
|
||||
try {
|
||||
section('Launching attach.');
|
||||
section('Launching `flutter attach`');
|
||||
Process attachProcess = await startProcess(
|
||||
path.join(flutterDirectory.path, 'bin', 'flutter'),
|
||||
<String>['--suppress-analytics', 'attach', '-d', device.deviceId],
|
||||
|
@ -113,7 +112,6 @@ void main() {
|
|||
);
|
||||
|
||||
await testReload(attachProcess, onListening: () async {
|
||||
section('Launching app.');
|
||||
await device.shellExec('am', <String>['start', '-n', kActivityId]);
|
||||
});
|
||||
|
||||
|
@ -124,15 +122,16 @@ void main() {
|
|||
|
||||
final String currentTime = (await device.shellEval('date', <String>['"+%F %R:%S.000"'])).trim();
|
||||
print('Start time on device: $currentTime');
|
||||
section('Launching app');
|
||||
section('Relaunching application');
|
||||
await device.shellExec('am', <String>['start', '-n', kActivityId]);
|
||||
|
||||
// If the next line fails, your device may not support regexp search.
|
||||
final String observatoryLine = await device.adb(<String>['logcat', '-e', 'Observatory listening on http:', '-m', '1', '-T', currentTime]);
|
||||
print('Found observatory line: $observatoryLine');
|
||||
final String observatoryPort = new RegExp(r'Observatory listening on http://.*:([0-9]+)').firstMatch(observatoryLine)[1];
|
||||
print('Extracted observatory port: $observatoryPort');
|
||||
|
||||
section('Launching attach with given port.');
|
||||
section('Launching attach with given port');
|
||||
attachProcess = await startProcess(
|
||||
path.join(flutterDirectory.path, 'bin', 'flutter'),
|
||||
<String>['--suppress-analytics', 'attach', '--debug-port', observatoryPort, '-d', device.deviceId],
|
||||
|
|
|
@ -58,6 +58,8 @@ void main() {
|
|||
stdout.removeAt(0);
|
||||
if (stdout.first == 'Initializing gradle...')
|
||||
stdout.removeAt(0);
|
||||
if (stdout.first == 'Resolving dependencies...')
|
||||
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);
|
||||
|
|
|
@ -93,29 +93,34 @@ Future<GradleProject> _readGradleProject() async {
|
|||
final FlutterProject flutterProject = new FlutterProject(fs.currentDirectory);
|
||||
final String gradle = await _ensureGradle(flutterProject);
|
||||
await updateLocalProperties(project: flutterProject);
|
||||
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
|
||||
GradleProject project;
|
||||
try {
|
||||
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
|
||||
final RunResult runResult = await runCheckedAsync(
|
||||
<String>[gradle, 'app:properties'],
|
||||
workingDirectory: flutterProject.android.directory.path,
|
||||
environment: _gradleEnv,
|
||||
);
|
||||
final String properties = runResult.stdout.trim();
|
||||
final GradleProject project = new GradleProject.fromAppProperties(properties);
|
||||
status.stop();
|
||||
return project;
|
||||
} catch (e) {
|
||||
project = new GradleProject.fromAppProperties(properties);
|
||||
} catch (exception) {
|
||||
if (getFlutterPluginVersion(flutterProject.android) == FlutterPluginVersion.managed) {
|
||||
status.cancel();
|
||||
// Handle known exceptions. This will exit if handled.
|
||||
handleKnownGradleExceptions(e);
|
||||
handleKnownGradleExceptions(exception);
|
||||
|
||||
// Print a general Gradle error and exit.
|
||||
printError('* Error running Gradle:\n$e\n');
|
||||
printError('* Error running Gradle:\n$exception\n');
|
||||
throwToolExit('Please review your Gradle project setup in the android/ folder.');
|
||||
}
|
||||
// Fall back to the default
|
||||
project = new GradleProject(
|
||||
<String>['debug', 'profile', 'release'],
|
||||
<String>[], flutterProject.android.gradleAppOutV1Directory,
|
||||
);
|
||||
}
|
||||
// Fall back to the default
|
||||
return new GradleProject(<String>['debug', 'profile', 'release'], <String>[], flutterProject.android.gradleAppOutV1Directory);
|
||||
status.stop();
|
||||
return project;
|
||||
}
|
||||
|
||||
void handleKnownGradleExceptions(String exceptionString) {
|
||||
|
|
|
@ -13,6 +13,8 @@ import 'utils.dart';
|
|||
|
||||
const int kDefaultStatusPadding = 59;
|
||||
|
||||
typedef void VoidCallback();
|
||||
|
||||
abstract class Logger {
|
||||
bool get isVerbose => false;
|
||||
|
||||
|
@ -53,8 +55,6 @@ abstract class Logger {
|
|||
});
|
||||
}
|
||||
|
||||
typedef void _FinishCallback();
|
||||
|
||||
class StdoutLogger extends Logger {
|
||||
|
||||
Status _status;
|
||||
|
@ -66,7 +66,6 @@ class StdoutLogger extends Logger {
|
|||
void printError(String message, { StackTrace stackTrace, bool emphasis = false }) {
|
||||
_status?.cancel();
|
||||
_status = null;
|
||||
|
||||
if (emphasis)
|
||||
message = terminal.bolden(message);
|
||||
stderr.writeln(message);
|
||||
|
@ -109,16 +108,25 @@ class StdoutLogger extends Logger {
|
|||
}) {
|
||||
if (_status != null) {
|
||||
// Ignore nested progresses; return a no-op status object.
|
||||
return new Status()..start();
|
||||
return new Status(onFinish: _clearStatus)..start();
|
||||
}
|
||||
if (terminal.supportsColor) {
|
||||
_status = new AnsiStatus(message, expectSlowOperation, () { _status = null; }, progressIndicatorPadding)..start();
|
||||
_status = new AnsiStatus(
|
||||
message: message,
|
||||
expectSlowOperation: expectSlowOperation,
|
||||
padding: progressIndicatorPadding,
|
||||
onFinish: _clearStatus,
|
||||
)..start();
|
||||
} else {
|
||||
printStatus(message);
|
||||
_status = new Status()..start();
|
||||
_status = new Status(onFinish: _clearStatus)..start();
|
||||
}
|
||||
return _status;
|
||||
}
|
||||
|
||||
void _clearStatus() {
|
||||
_status = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// A [StdoutLogger] which replaces Unicode characters that cannot be printed to
|
||||
|
@ -180,7 +188,7 @@ class BufferLogger extends Logger {
|
|||
int progressIndicatorPadding = kDefaultStatusPadding,
|
||||
}) {
|
||||
printStatus(message);
|
||||
return new Status();
|
||||
return new Status()..start();
|
||||
}
|
||||
|
||||
/// Clears all buffers.
|
||||
|
@ -230,7 +238,9 @@ class VerboseLogger extends Logger {
|
|||
int progressIndicatorPadding = kDefaultStatusPadding,
|
||||
}) {
|
||||
printStatus(message);
|
||||
return new Status();
|
||||
return new Status(onFinish: () {
|
||||
printTrace('$message (completed)');
|
||||
})..start();
|
||||
}
|
||||
|
||||
void _emit(_LogType type, String message, [StackTrace stackTrace]) {
|
||||
|
@ -275,75 +285,91 @@ enum _LogType {
|
|||
/// A [Status] class begins when start is called, and may produce progress
|
||||
/// information asynchronously.
|
||||
///
|
||||
/// When stop is called, summary information supported by this class is printed.
|
||||
/// If cancel is called, no summary information is displayed.
|
||||
/// The base class displays nothing at all.
|
||||
/// The [Status] class itself never has any output.
|
||||
///
|
||||
/// The [AnsiSpinner] subclass shows a spinner, and replaces it with a single
|
||||
/// space character when stopped or canceled.
|
||||
///
|
||||
/// The [AnsiStatus] subclass shows a spinner, and replaces it with timing
|
||||
/// information when stopped. When canceled, the information isn't shown. In
|
||||
/// either case, a newline is printed.
|
||||
///
|
||||
/// Generally, consider `logger.startProgress` instead of directly creating
|
||||
/// a [Status] or one of its subclasses.
|
||||
class Status {
|
||||
Status();
|
||||
Status({ this.onFinish });
|
||||
|
||||
/// A straight [Status] or an [AnsiSpinner] (depending on whether the
|
||||
/// terminal is fancy enough), already started.
|
||||
factory Status.withSpinner({ VoidCallback onFinish }) {
|
||||
if (terminal.supportsColor)
|
||||
return new AnsiSpinner(onFinish: onFinish)..start();
|
||||
return new Status(onFinish: onFinish)..start();
|
||||
}
|
||||
|
||||
final VoidCallback onFinish;
|
||||
|
||||
bool _isStarted = false;
|
||||
|
||||
factory Status.withSpinner() {
|
||||
if (terminal.supportsColor)
|
||||
return new AnsiSpinner()..start();
|
||||
return new Status()..start();
|
||||
}
|
||||
|
||||
/// Display summary information for this spinner; called by [stop].
|
||||
void summaryInformation() {}
|
||||
|
||||
/// Call to start spinning. Call this method via super at the beginning
|
||||
/// of a subclass [start] method.
|
||||
/// Call to start spinning.
|
||||
void start() {
|
||||
assert(!_isStarted);
|
||||
_isStarted = true;
|
||||
}
|
||||
|
||||
/// Call to stop spinning and delete the spinner. Print summary information,
|
||||
/// if applicable to the spinner.
|
||||
/// Call to stop spinning after success.
|
||||
void stop() {
|
||||
if (_isStarted) {
|
||||
cancel();
|
||||
summaryInformation();
|
||||
}
|
||||
assert(_isStarted);
|
||||
_isStarted = false;
|
||||
if (onFinish != null)
|
||||
onFinish();
|
||||
}
|
||||
|
||||
/// Call to cancel the spinner without printing any summary output. Call
|
||||
/// this method via super at the end of a subclass [cancel] method.
|
||||
/// Call to cancel the spinner after failure or cancelation.
|
||||
void cancel() {
|
||||
assert(_isStarted);
|
||||
_isStarted = false;
|
||||
if (onFinish != null)
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
/// An [AnsiSpinner] is a simple animation that does nothing but implement an
|
||||
/// ASCII spinner. When stopped or canceled, the animation erases itself.
|
||||
/// ASCII spinner. When stopped or canceled, the animation erases itself.
|
||||
class AnsiSpinner extends Status {
|
||||
AnsiSpinner({ VoidCallback onFinish }) : super(onFinish: onFinish);
|
||||
|
||||
int ticks = 0;
|
||||
Timer timer;
|
||||
|
||||
static final List<String> _progress = <String>['-', r'\', '|', r'/'];
|
||||
static final List<String> _progress = <String>[r'-', r'\', r'|', r'/'];
|
||||
|
||||
void _callback(Timer _) {
|
||||
void _callback(Timer timer) {
|
||||
stdout.write('\b${_progress[ticks++ % _progress.length]}');
|
||||
}
|
||||
|
||||
@override
|
||||
void start() {
|
||||
super.start();
|
||||
assert(timer == null);
|
||||
stdout.write(' ');
|
||||
_callback(null);
|
||||
timer = new Timer.periodic(const Duration(milliseconds: 100), _callback);
|
||||
_callback(timer);
|
||||
}
|
||||
|
||||
@override
|
||||
void stop() {
|
||||
assert(timer.isActive);
|
||||
timer.cancel();
|
||||
stdout.write('\b \b');
|
||||
super.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
/// Clears the spinner. After cancel, the cursor will be one space right
|
||||
/// of where it was when [start] was called (assuming no other input).
|
||||
void cancel() {
|
||||
if (timer?.isActive == true) {
|
||||
timer.cancel();
|
||||
// Many terminals do not interpret backspace as deleting a character,
|
||||
// but rather just moving the cursor back one.
|
||||
stdout.write('\b \b');
|
||||
}
|
||||
assert(timer.isActive);
|
||||
timer.cancel();
|
||||
stdout.write('\b \b');
|
||||
super.cancel();
|
||||
}
|
||||
}
|
||||
|
@ -353,59 +379,50 @@ class AnsiSpinner extends Status {
|
|||
/// On [stop], will additionally print out summary information in
|
||||
/// milliseconds if [expectSlowOperation] is false, as seconds otherwise.
|
||||
class AnsiStatus extends AnsiSpinner {
|
||||
AnsiStatus(this.message, this.expectSlowOperation, this.onFinish, this.padding);
|
||||
AnsiStatus({
|
||||
this.message,
|
||||
this.expectSlowOperation,
|
||||
this.padding,
|
||||
VoidCallback onFinish,
|
||||
}) : super(onFinish: onFinish);
|
||||
|
||||
final String message;
|
||||
final bool expectSlowOperation;
|
||||
final _FinishCallback onFinish;
|
||||
final int padding;
|
||||
|
||||
Stopwatch stopwatch;
|
||||
bool _finished = false;
|
||||
|
||||
@override
|
||||
/// Writes [message] to [stdout] with padding, then begins spinning.
|
||||
void start() {
|
||||
stopwatch = new Stopwatch()..start();
|
||||
stdout.write('${message.padRight(padding)} ');
|
||||
assert(!_finished);
|
||||
super.start();
|
||||
}
|
||||
|
||||
@override
|
||||
/// Calls onFinish.
|
||||
void stop() {
|
||||
if (!_finished) {
|
||||
onFinish();
|
||||
_finished = true;
|
||||
super.cancel();
|
||||
summaryInformation();
|
||||
}
|
||||
super.stop();
|
||||
writeSummaryInformation();
|
||||
stdout.write('\n');
|
||||
}
|
||||
|
||||
@override
|
||||
void cancel() {
|
||||
super.cancel();
|
||||
stdout.write('\n');
|
||||
}
|
||||
|
||||
/// Backs up 4 characters and prints a (minimum) 5 character padded time. If
|
||||
/// [expectSlowOperation] is true, the time is in seconds; otherwise,
|
||||
/// milliseconds. Only backs up 4 characters because [super.cancel] backs
|
||||
/// up one.
|
||||
///
|
||||
/// Example: '\b\b\b\b 0.5s', '\b\b\b\b150ms', '\b\b\b\b1600ms'
|
||||
void summaryInformation() {
|
||||
void writeSummaryInformation() {
|
||||
if (expectSlowOperation) {
|
||||
stdout.writeln('\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
|
||||
stdout.write('\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
|
||||
} else {
|
||||
stdout.writeln('\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
/// Calls [onFinish].
|
||||
void cancel() {
|
||||
if (!_finished) {
|
||||
onFinish();
|
||||
_finished = true;
|
||||
super.cancel();
|
||||
stdout.write('\n');
|
||||
stdout.write('\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -295,11 +295,15 @@ abstract class CachedArtifact {
|
|||
return _withDownloadFile('${flattenNameSubdirs(url)}', (File tempFile) async {
|
||||
if (!verifier(tempFile)) {
|
||||
final Status status = logger.startProgress(message, expectSlowOperation: true);
|
||||
await _downloadFile(url, tempFile).then<Null>((_) {
|
||||
try {
|
||||
await _downloadFile(url, tempFile);
|
||||
status.stop();
|
||||
}).whenComplete(status.cancel);
|
||||
} catch (exception) {
|
||||
status.cancel();
|
||||
rethrow;
|
||||
}
|
||||
} else {
|
||||
logger.printStatus('$message(cached)');
|
||||
logger.printTrace('$message (cached)');
|
||||
}
|
||||
_ensureExists(location);
|
||||
extractor(tempFile, location);
|
||||
|
|
|
@ -74,8 +74,10 @@ class BuildAotCommand extends BuildSubCommand {
|
|||
Status status;
|
||||
if (!argResults['quiet']) {
|
||||
final String typeName = artifacts.getEngineType(platform, buildMode);
|
||||
status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...',
|
||||
expectSlowOperation: true);
|
||||
status = logger.startProgress(
|
||||
'Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...',
|
||||
expectSlowOperation: true,
|
||||
);
|
||||
}
|
||||
final String outputPath = argResults['output-dir'] ?? getAotBuildDirectory();
|
||||
try {
|
||||
|
@ -120,8 +122,6 @@ class BuildAotCommand extends BuildSubCommand {
|
|||
buildSharedLibrary: false,
|
||||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||
).then((int buildExitCode) {
|
||||
if (buildExitCode != 0)
|
||||
printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
|
||||
return buildExitCode;
|
||||
});
|
||||
});
|
||||
|
@ -134,6 +134,12 @@ class BuildAotCommand extends BuildSubCommand {
|
|||
..addAll(dylibs)
|
||||
..addAll(<String>['-create', '-output', fs.path.join(outputPath, 'App.framework', 'App')]),
|
||||
);
|
||||
} else {
|
||||
status?.cancel();
|
||||
exitCodes.forEach((IOSArch iosArch, Future<int> exitCodeFuture) async {
|
||||
final int buildExitCode = await exitCodeFuture;
|
||||
printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Android AOT snapshot.
|
||||
|
@ -148,12 +154,14 @@ class BuildAotCommand extends BuildSubCommand {
|
|||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
status?.cancel();
|
||||
printError('Snapshotting exited with non-zero exit code: $snapshotExitCode');
|
||||
return;
|
||||
}
|
||||
}
|
||||
} on String catch (error) {
|
||||
// Catch the String exceptions thrown from the `runCheckedSync` methods below.
|
||||
status?.cancel();
|
||||
printError(error);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -901,7 +901,14 @@ class _AppRunLogger extends Logger {
|
|||
'message': message,
|
||||
});
|
||||
|
||||
_status = new _AppLoggerStatus(this, id, progressId);
|
||||
_status = new Status(onFinish: () {
|
||||
_status = null;
|
||||
_sendProgressEvent(<String, dynamic>{
|
||||
'id': id.toString(),
|
||||
'progressId': progressId,
|
||||
'finished': true
|
||||
});
|
||||
});
|
||||
return _status;
|
||||
}
|
||||
|
||||
|
@ -924,37 +931,6 @@ class _AppRunLogger extends Logger {
|
|||
}
|
||||
}
|
||||
|
||||
class _AppLoggerStatus extends Status {
|
||||
_AppLoggerStatus(this.logger, this.id, this.progressId);
|
||||
|
||||
final _AppRunLogger logger;
|
||||
final int id;
|
||||
final String progressId;
|
||||
|
||||
@override
|
||||
void start() {}
|
||||
|
||||
@override
|
||||
void stop() {
|
||||
logger._status = null;
|
||||
_sendFinished();
|
||||
}
|
||||
|
||||
@override
|
||||
void cancel() {
|
||||
logger._status = null;
|
||||
_sendFinished();
|
||||
}
|
||||
|
||||
void _sendFinished() {
|
||||
logger._sendProgressEvent(<String, dynamic>{
|
||||
'id': id.toString(),
|
||||
'progressId': progressId,
|
||||
'finished': true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LogMessage {
|
||||
final String level;
|
||||
final String message;
|
||||
|
|
|
@ -84,7 +84,10 @@ class UpdatePackagesCommand extends FlutterCommand {
|
|||
final bool hidden;
|
||||
|
||||
Future<Null> _downloadCoverageData() async {
|
||||
final Status status = logger.startProgress('Downloading lcov data for package:flutter...', expectSlowOperation: true);
|
||||
final Status status = logger.startProgress(
|
||||
'Downloading lcov data for package:flutter...',
|
||||
expectSlowOperation: true,
|
||||
);
|
||||
final String urlBase = platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com';
|
||||
final List<int> data = await fetchUrl(Uri.parse('$urlBase/flutter_infra/flutter/coverage/lcov.info'));
|
||||
final String coverageDir = fs.path.join(Cache.flutterRoot, 'packages/flutter/coverage');
|
||||
|
|
|
@ -109,8 +109,10 @@ Future<Null> pubGet({
|
|||
failureMessage: 'pub $command failed',
|
||||
retry: true,
|
||||
);
|
||||
} finally {
|
||||
status.stop();
|
||||
} catch (exception) {
|
||||
status.cancel();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,9 +145,13 @@ class Doctor {
|
|||
for (ValidatorTask validatorTask in startValidatorTasks()) {
|
||||
final DoctorValidator validator = validatorTask.validator;
|
||||
final Status status = new Status.withSpinner();
|
||||
await (validatorTask.result).then<void>((_) {
|
||||
status.stop();
|
||||
}).whenComplete(status.cancel);
|
||||
try {
|
||||
await validatorTask.result;
|
||||
} catch (exception) {
|
||||
status.cancel();
|
||||
rethrow;
|
||||
}
|
||||
status.stop();
|
||||
|
||||
final ValidationResult result = await validatorTask.result;
|
||||
if (result.type == ValidationType.missing) {
|
||||
|
|
|
@ -299,7 +299,6 @@ class IOSDevice extends Device {
|
|||
bundlePath: bundle.path,
|
||||
launchArguments: launchArguments,
|
||||
);
|
||||
installStatus.stop();
|
||||
} else {
|
||||
// Debugging is enabled, look for the observatory server port post launch.
|
||||
printTrace('Debugging is enabled, connecting to observatory');
|
||||
|
|
|
@ -385,7 +385,7 @@ class FlutterDevice {
|
|||
}) async {
|
||||
final Status devFSStatus = logger.startProgress(
|
||||
'Syncing files to device ${device.name}...',
|
||||
expectSlowOperation: true
|
||||
expectSlowOperation: true,
|
||||
);
|
||||
int bytes = 0;
|
||||
try {
|
||||
|
@ -554,8 +554,9 @@ abstract class ResidentRunner {
|
|||
for (FlutterView view in device.views)
|
||||
await view.uiIsolate.flutterDebugAllowBanner(false);
|
||||
} catch (error) {
|
||||
status.stop();
|
||||
status.cancel();
|
||||
printError('Error communicating with Flutter on the device: $error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
@ -566,8 +567,9 @@ abstract class ResidentRunner {
|
|||
for (FlutterView view in device.views)
|
||||
await view.uiIsolate.flutterDebugAllowBanner(true);
|
||||
} catch (error) {
|
||||
status.stop();
|
||||
status.cancel();
|
||||
printError('Error communicating with Flutter on the device: $error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -575,7 +577,7 @@ abstract class ResidentRunner {
|
|||
status.stop();
|
||||
printStatus('Screenshot written to ${fs.path.relative(outputFile.path)} (${sizeKB}kB).');
|
||||
} catch (error) {
|
||||
status.stop();
|
||||
status.cancel();
|
||||
printError('Error taking screenshot: $error');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -441,8 +441,7 @@ class HotRunner extends ResidentRunner {
|
|||
: mainPath;
|
||||
await _launchFromDevFS(launchPath);
|
||||
restartTimer.stop();
|
||||
printTrace('Restart performed in '
|
||||
'${getElapsedAsMilliseconds(restartTimer.elapsed)}.');
|
||||
printTrace('Hot restart performed in ${getElapsedAsMilliseconds(restartTimer.elapsed)}.');
|
||||
// We are now running from sources.
|
||||
_runningFromSnapshot = false;
|
||||
_addBenchmarkData('hotRestartMillisecondsToFrame',
|
||||
|
@ -494,26 +493,21 @@ class HotRunner extends ResidentRunner {
|
|||
|
||||
@override
|
||||
Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false }) async {
|
||||
final Stopwatch timer = new Stopwatch()..start();
|
||||
if (fullRestart) {
|
||||
final Status status = logger.startProgress(
|
||||
'Performing hot restart...',
|
||||
progressId: 'hot.restart'
|
||||
progressId: 'hot.restart',
|
||||
);
|
||||
try {
|
||||
final Stopwatch timer = new Stopwatch()..start();
|
||||
if (!(await hotRunnerConfig.setupHotRestart())) {
|
||||
status.cancel();
|
||||
if (!(await hotRunnerConfig.setupHotRestart()))
|
||||
return new OperationResult(1, 'setupHotRestart failed');
|
||||
}
|
||||
await _restartFromSources();
|
||||
timer.stop();
|
||||
} finally {
|
||||
status.cancel();
|
||||
printStatus('Restarted app in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
return OperationResult.ok;
|
||||
} catch (error) {
|
||||
status.cancel();
|
||||
rethrow;
|
||||
}
|
||||
printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
return OperationResult.ok;
|
||||
} else {
|
||||
final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
|
||||
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
|
||||
|
@ -521,20 +515,17 @@ class HotRunner extends ResidentRunner {
|
|||
'$progressPrefix hot reload...',
|
||||
progressId: 'hot.reload'
|
||||
);
|
||||
OperationResult result;
|
||||
try {
|
||||
final Stopwatch timer = new Stopwatch()..start();
|
||||
final OperationResult result = await _reloadSources(pause: pauseAfterRestart);
|
||||
timer.stop();
|
||||
result = await _reloadSources(pause: pauseAfterRestart);
|
||||
} finally {
|
||||
status.cancel();
|
||||
if (result.isOk)
|
||||
printStatus('${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
if (result.hintMessage != null)
|
||||
printStatus('\n${result.hintMessage}');
|
||||
return result;
|
||||
} catch (error) {
|
||||
status.cancel();
|
||||
rethrow;
|
||||
}
|
||||
if (result.isOk)
|
||||
printStatus('${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
if (result.hintMessage != null)
|
||||
printStatus('\n${result.hintMessage}');
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -637,9 +628,11 @@ class HotRunner extends ResidentRunner {
|
|||
final int errorCode = error['code'];
|
||||
final String errorMessage = error['message'];
|
||||
if (errorCode == Isolate.kIsolateReloadBarred) {
|
||||
printError('Unable to hot reload app due to an unrecoverable error in '
|
||||
'the source code. Please address the error and then use '
|
||||
'"R" to restart the app.');
|
||||
printError(
|
||||
'Unable to hot reload application due to an unrecoverable error in '
|
||||
'the source code. Please address the error and then use "R" to '
|
||||
'restart the app.'
|
||||
);
|
||||
flutterUsage.sendEvent('hot', 'reload-barred');
|
||||
return new OperationResult(errorCode, errorMessage);
|
||||
}
|
||||
|
@ -714,8 +707,7 @@ class HotRunner extends ResidentRunner {
|
|||
reassembleTimer.elapsed.inMilliseconds);
|
||||
|
||||
reloadTimer.stop();
|
||||
printTrace('Hot reload performed in '
|
||||
'${getElapsedAsMilliseconds(reloadTimer.elapsed)}.');
|
||||
printTrace('Hot reload performed in ${getElapsedAsMilliseconds(reloadTimer.elapsed)}.');
|
||||
// Record complete time it took for the reload.
|
||||
_addBenchmarkData('hotReloadMillisecondsToFrame',
|
||||
reloadTimer.elapsed.inMilliseconds);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_tools/src/base/context.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
@ -23,9 +24,9 @@ void main() {
|
|||
verboseLogger.printError('Helpless!');
|
||||
|
||||
expect(mockLogger.statusText, matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] Hey Hey Hey Hey\n'
|
||||
r'\[ (?: {0,2}\+[0-9]{1,3} ms| )\] Oooh, I do I do I do\n$'));
|
||||
r'\[ (?: {0,2}\+[0-9]{1,3} ms| )\] Oooh, I do I do I do\n$'));
|
||||
expect(mockLogger.traceText, '');
|
||||
expect(mockLogger.errorText, matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] Helpless!\n$'));
|
||||
expect(mockLogger.errorText, matches( r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] Helpless!\n$'));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -40,23 +41,27 @@ void main() {
|
|||
mockStdio = new MockStdio();
|
||||
ansiSpinner = new AnsiSpinner();
|
||||
called = 0;
|
||||
ansiStatus = new AnsiStatus('Hello world', true, () => called++, 20);
|
||||
ansiStatus = new AnsiStatus(
|
||||
message: 'Hello world',
|
||||
expectSlowOperation: true,
|
||||
padding: 20,
|
||||
onFinish: () => called++,
|
||||
);
|
||||
});
|
||||
|
||||
List<String> outputLines() => mockStdio.writtenToStdout.join('').split('\n');
|
||||
|
||||
Future<void> doWhile(bool doThis()) async {
|
||||
return Future.doWhile(() async {
|
||||
// Future.doWhile() isn't enough by itself, because the VM never gets
|
||||
// around to scheduling the other tasks for some reason.
|
||||
await new Future<void>.delayed(const Duration(milliseconds: 0));
|
||||
return doThis();
|
||||
Future<void> doWhileAsync(bool doThis()) async {
|
||||
return Future.doWhile(() {
|
||||
// We want to let other tasks run at the same time, so we schedule these
|
||||
// using a timer rather than a microtask.
|
||||
return Future<bool>.delayed(Duration.zero, doThis);
|
||||
});
|
||||
}
|
||||
|
||||
testUsingContext('AnsiSpinner works', () async {
|
||||
ansiSpinner.start();
|
||||
await doWhile(() => ansiSpinner.ticks < 10);
|
||||
await doWhileAsync(() => ansiSpinner.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith(' \b-\b\\\b|\b/\b-\b\\\b|\b/'));
|
||||
expect(lines[0].endsWith('\n'), isFalse);
|
||||
|
@ -66,44 +71,37 @@ void main() {
|
|||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(lines.length, equals(1));
|
||||
|
||||
// Verify that stopping multiple times doesn't clear multiple times.
|
||||
ansiSpinner.stop();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b '), isFalse);
|
||||
expect(lines.length, equals(1));
|
||||
ansiSpinner.cancel();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b '), isFalse);
|
||||
expect(lines.length, equals(1));
|
||||
// Verify that stopping or canceling multiple times throws.
|
||||
expect(() { ansiSpinner.stop(); }, throwsA(const isInstanceOf<AssertionError>()));
|
||||
expect(() { ansiSpinner.cancel(); }, throwsA(const isInstanceOf<AssertionError>()));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
|
||||
testUsingContext('AnsiStatus works when cancelled', () async {
|
||||
ansiStatus.start();
|
||||
await doWhile(() => ansiStatus.ticks < 10);
|
||||
await doWhileAsync(() => ansiStatus.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
|
||||
expect(lines[0].endsWith('\n'), isFalse);
|
||||
expect(lines.length, equals(1));
|
||||
expect(lines[0].endsWith('\n'), isFalse);
|
||||
|
||||
// Verify a cancel does _not_ print the time and prints a newline.
|
||||
ansiStatus.cancel();
|
||||
lines = outputLines();
|
||||
final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, isEmpty);
|
||||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(lines.length, equals(2));
|
||||
expect(called, equals(1));
|
||||
ansiStatus.cancel();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b\b \b'), isFalse);
|
||||
expect(lines.length, equals(2));
|
||||
expect(called, equals(1));
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
expect(lines[0].endsWith('\b \b\b \b'), isFalse);
|
||||
expect(lines.length, equals(2));
|
||||
expect(called, equals(1));
|
||||
expect(lines[1], equals(''));
|
||||
|
||||
// Verify that stopping or canceling multiple times throws.
|
||||
expect(() { ansiStatus.cancel(); }, throwsA(const isInstanceOf<AssertionError>()));
|
||||
expect(() { ansiStatus.stop(); }, throwsA(const isInstanceOf<AssertionError>()));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
|
||||
testUsingContext('AnsiStatus works when stopped', () async {
|
||||
ansiStatus.start();
|
||||
await doWhile(() => ansiStatus.ticks < 10);
|
||||
await doWhileAsync(() => ansiStatus.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
|
||||
expect(lines.length, equals(1));
|
||||
|
@ -111,55 +109,55 @@ void main() {
|
|||
// Verify a stop prints the time.
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
List<Match> matches = secondDigits.allMatches(lines[0]).toList();
|
||||
final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, isNotNull);
|
||||
expect(matches, hasLength(1));
|
||||
Match match = matches.first;
|
||||
final Match match = matches.first;
|
||||
expect(lines[0], endsWith(match.group(0)));
|
||||
final String initialTime = match.group(0);
|
||||
expect(called, equals(1));
|
||||
expect(lines.length, equals(2));
|
||||
expect(lines[1], equals(''));
|
||||
|
||||
// Verify stopping more than once generates no additional output.
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, hasLength(1));
|
||||
match = matches.first;
|
||||
expect(lines[0], endsWith(initialTime));
|
||||
expect(called, equals(1));
|
||||
expect(lines.length, equals(2));
|
||||
expect(lines[1], equals(''));
|
||||
// Verify that stopping or canceling multiple times throws.
|
||||
expect(() { ansiStatus.stop(); }, throwsA(const isInstanceOf<AssertionError>()));
|
||||
expect(() { ansiStatus.cancel(); }, throwsA(const isInstanceOf<AssertionError>()));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
|
||||
testUsingContext('AnsiStatus works when cancelled', () async {
|
||||
ansiStatus.start();
|
||||
await doWhile(() => ansiStatus.ticks < 10);
|
||||
List<String> lines = outputLines();
|
||||
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
|
||||
expect(lines.length, equals(1));
|
||||
testUsingContext('sequential startProgress calls with StdoutLogger', () async {
|
||||
context[Logger].startProgress('AAA')..stop();
|
||||
context[Logger].startProgress('BBB')..stop();
|
||||
expect(outputLines(), <String>[
|
||||
'AAA',
|
||||
'BBB',
|
||||
'',
|
||||
]);
|
||||
}, overrides: <Type, Generator>{
|
||||
Stdio: () => mockStdio,
|
||||
Logger: () => new StdoutLogger(),
|
||||
});
|
||||
|
||||
// Verify a cancel does _not_ print the time and prints a newline.
|
||||
ansiStatus.cancel();
|
||||
lines = outputLines();
|
||||
List<Match> matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, isEmpty);
|
||||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(called, equals(1));
|
||||
// TODO(jcollins-g): Consider having status objects print the newline
|
||||
// when canceled, or never printing a newline at all.
|
||||
expect(lines.length, equals(2));
|
||||
testUsingContext('sequential startProgress calls with VerboseLogger and StdoutLogger', () async {
|
||||
context[Logger].startProgress('AAA')..stop();
|
||||
context[Logger].startProgress('BBB')..stop();
|
||||
expect(outputLines(), <Matcher>[
|
||||
matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] AAA$'),
|
||||
matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] AAA \(completed\)$'),
|
||||
matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] BBB$'),
|
||||
matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms| )\] BBB \(completed\)$'),
|
||||
matches(r'^$'),
|
||||
]);
|
||||
}, overrides: <Type, Generator>{
|
||||
Stdio: () => mockStdio,
|
||||
Logger: () => new VerboseLogger(new StdoutLogger()),
|
||||
});
|
||||
|
||||
// Verifying calling stop after cancel doesn't print anything weird.
|
||||
ansiStatus.stop();
|
||||
lines = outputLines();
|
||||
matches = secondDigits.allMatches(lines[0]).toList();
|
||||
expect(matches, isEmpty);
|
||||
expect(lines[0], endsWith('\b \b'));
|
||||
expect(called, equals(1));
|
||||
expect(lines[0], isNot(endsWith('\b \b\b \b')));
|
||||
expect(lines.length, equals(2));
|
||||
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
|
||||
testUsingContext('sequential startProgress calls with BufferLogger', () async {
|
||||
context[Logger].startProgress('AAA')..stop();
|
||||
context[Logger].startProgress('BBB')..stop();
|
||||
final BufferLogger logger = context[Logger];
|
||||
expect(logger.statusText, 'AAA\nBBB\n');
|
||||
}, overrides: <Type, Generator>{
|
||||
Logger: () => new BufferLogger(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue