mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
b34046903b
* Fix stack trace parsing on non-debug builds; add e2e tests
165 lines
6 KiB
Dart
165 lines
6 KiB
Dart
// Copyright 2014 The Flutter 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:core' hide print;
|
|
import 'dart:io' hide exit;
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'utils.dart';
|
|
|
|
// TODO(ianh): These two functions should be refactored into something that avoids all this code duplication.
|
|
|
|
Stream<String> runAndGetStdout(String executable, List<String> arguments, {
|
|
String workingDirectory,
|
|
Map<String, String> environment,
|
|
bool expectNonZeroExit = false,
|
|
int expectedExitCode,
|
|
String failureMessage,
|
|
bool skip = false,
|
|
}) async* {
|
|
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
|
final String relativeWorkingDir = path.relative(workingDirectory);
|
|
if (skip) {
|
|
printProgress('SKIPPING', relativeWorkingDir, commandDescription);
|
|
return;
|
|
}
|
|
printProgress('RUNNING', relativeWorkingDir, commandDescription);
|
|
|
|
final Stopwatch time = Stopwatch()..start();
|
|
final Process process = await Process.start(executable, arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
);
|
|
|
|
stderr.addStream(process.stderr);
|
|
final Stream<String> lines = process.stdout.transform(utf8.decoder).transform(const LineSplitter());
|
|
yield* lines;
|
|
|
|
final int exitCode = await process.exitCode;
|
|
if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
|
|
exitWithError(<String>[
|
|
if (failureMessage != null)
|
|
failureMessage
|
|
else
|
|
'${bold}ERROR: ${red}Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
|
|
'${bold}Command: $green$commandDescription$reset',
|
|
'${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
|
|
]);
|
|
}
|
|
print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
|
|
}
|
|
|
|
/// Runs the `executable` and waits until the process exits.
|
|
///
|
|
/// If the process exits with a non-zero exit code, exits this process with
|
|
/// exit code 1, unless `expectNonZeroExit` is set to true.
|
|
///
|
|
/// `outputListener` is called for every line of standard output from the
|
|
/// process, and is given the [Process] object. This can be used to interrupt
|
|
/// an indefinitely running process, for example, by waiting until the process
|
|
/// emits certain output.
|
|
Future<void> runCommand(String executable, List<String> arguments, {
|
|
String workingDirectory,
|
|
Map<String, String> environment,
|
|
bool expectNonZeroExit = false,
|
|
int expectedExitCode,
|
|
String failureMessage,
|
|
OutputMode outputMode = OutputMode.print,
|
|
CapturedOutput output,
|
|
bool skip = false,
|
|
bool Function(String) removeLine,
|
|
void Function(String, Process) outputListener,
|
|
}) async {
|
|
assert(
|
|
(outputMode == OutputMode.capture) == (output != null),
|
|
'The output parameter must be non-null with and only with OutputMode.capture',
|
|
);
|
|
|
|
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
|
final String relativeWorkingDir = path.relative(workingDirectory);
|
|
if (skip) {
|
|
printProgress('SKIPPING', relativeWorkingDir, commandDescription);
|
|
return;
|
|
}
|
|
printProgress('RUNNING', relativeWorkingDir, commandDescription);
|
|
|
|
final Stopwatch time = Stopwatch()..start();
|
|
final Process process = await Process.start(executable, arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
);
|
|
|
|
Future<List<List<int>>> savedStdout, savedStderr;
|
|
final Stream<List<int>> stdoutSource = process.stdout
|
|
.transform<String>(const Utf8Decoder())
|
|
.transform(const LineSplitter())
|
|
.where((String line) => removeLine == null || !removeLine(line))
|
|
.map((String line) {
|
|
final String formattedLine = '$line\n';
|
|
if (outputListener != null) {
|
|
outputListener(formattedLine, process);
|
|
}
|
|
return formattedLine;
|
|
})
|
|
.transform(const Utf8Encoder());
|
|
switch (outputMode) {
|
|
case OutputMode.print:
|
|
await Future.wait<void>(<Future<void>>[
|
|
stdout.addStream(stdoutSource),
|
|
stderr.addStream(process.stderr),
|
|
]);
|
|
break;
|
|
case OutputMode.capture:
|
|
case OutputMode.discard:
|
|
savedStdout = stdoutSource.toList();
|
|
savedStderr = process.stderr.toList();
|
|
break;
|
|
}
|
|
|
|
final int exitCode = await process.exitCode;
|
|
if (output != null) {
|
|
output.stdout = _flattenToString(await savedStdout);
|
|
output.stderr = _flattenToString(await savedStderr);
|
|
}
|
|
|
|
if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
|
|
// Print the output when we get unexpected results (unless output was
|
|
// printed already).
|
|
switch (outputMode) {
|
|
case OutputMode.print:
|
|
break;
|
|
case OutputMode.capture:
|
|
case OutputMode.discard:
|
|
stdout.writeln(_flattenToString(await savedStdout));
|
|
stderr.writeln(_flattenToString(await savedStderr));
|
|
break;
|
|
}
|
|
exitWithError(<String>[
|
|
if (failureMessage != null)
|
|
failureMessage
|
|
else
|
|
'${bold}ERROR: ${red}Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
|
|
'${bold}Command: $green$commandDescription$reset',
|
|
'${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
|
|
]);
|
|
}
|
|
print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
|
|
}
|
|
|
|
/// Flattens a nested list of UTF-8 code units into a single string.
|
|
String _flattenToString(List<List<int>> chunks) =>
|
|
utf8.decode(chunks.expand<int>((List<int> ints) => ints).toList());
|
|
|
|
/// Specifies what to do with command output from [runCommand].
|
|
enum OutputMode { print, capture, discard }
|
|
|
|
/// Stores command output from [runCommand] when used with [OutputMode.capture].
|
|
class CapturedOutput {
|
|
String stdout;
|
|
String stderr;
|
|
}
|