blob: c97a08136dc656c5083e1d79332adb33e3cfa332 [file] [log] [blame]
// Copyright 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;
final bool hasColor = stdout.supportsAnsiEscapes;
final String bold = hasColor ? '\x1B[1m' : '';
final String red = hasColor ? '\x1B[31m' : '';
final String green = hasColor ? '\x1B[32m' : '';
final String yellow = hasColor ? '\x1B[33m' : '';
final String cyan = hasColor ? '\x1B[36m' : '';
final String reset = hasColor ? '\x1B[0m' : '';
final String redLine = '$red━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$reset';
const String arrow = '⏩';
const String clock = '🕐';
const Duration _kLongTimeout = Duration(minutes: 45);
String elapsedTime(DateTime start) {
return DateTime.now().difference(start).toString();
}
void printProgress(String action, String workingDir, String command) {
print('$arrow $action: cd $cyan$workingDir$reset; $yellow$command$reset');
}
Stream<String> runAndGetStdout(String executable, List<String> arguments, {
String workingDirectory,
Map<String, String> environment,
bool expectNonZeroExit = false,
int expectedExitCode,
String failureMessage,
Duration timeout = _kLongTimeout,
Function beforeExit,
}) async* {
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
final String relativeWorkingDir = path.relative(workingDirectory);
printProgress('RUNNING', relativeWorkingDir, commandDescription);
final DateTime start = DateTime.now();
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());
await for (String line in lines) {
yield line;
}
final int exitCode = await process.exitCode.timeout(timeout, onTimeout: () {
stderr.writeln('Process timed out after $timeout');
return expectNonZeroExit ? 0 : 1;
});
print('$clock ELAPSED TIME: $bold${elapsedTime(start)}$reset for $commandDescription in $relativeWorkingDir: ');
if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
if (failureMessage != null) {
print(failureMessage);
}
print(
'$redLine\n'
'${bold}ERROR:$red Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset\n'
'${bold}Command:$cyan $commandDescription$reset\n'
'${bold}Relative working directory:$red $relativeWorkingDir$reset\n'
'$redLine'
);
beforeExit?.call();
exit(1);
}
}
Future<void> runCommand(String executable, List<String> arguments, {
String workingDirectory,
Map<String, String> environment,
bool expectNonZeroExit = false,
int expectedExitCode,
String failureMessage,
bool printOutput = true,
bool skip = false,
bool expectFlaky = false,
Duration timeout = _kLongTimeout,
}) 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 DateTime start = DateTime.now();
final Process process = await Process.start(executable, arguments,
workingDirectory: workingDirectory,
environment: environment,
);
Future<List<List<int>>> savedStdout, savedStderr;
if (printOutput) {
await Future.wait<void>(<Future<void>>[
stdout.addStream(process.stdout),
stderr.addStream(process.stderr),
]);
} else {
savedStdout = process.stdout.toList();
savedStderr = process.stderr.toList();
}
final int exitCode = await process.exitCode.timeout(timeout, onTimeout: () {
stderr.writeln('Process timed out after $timeout');
return (expectNonZeroExit || expectFlaky) ? 0 : 1;
});
print('$clock ELAPSED TIME: $bold${elapsedTime(start)}$reset for $commandDescription in $relativeWorkingDir: ');
// If the test is flaky we don't care about the actual exit.
if (expectFlaky) {
return;
}
if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
if (failureMessage != null) {
print(failureMessage);
}
if (!printOutput) {
stdout.writeln(utf8.decode((await savedStdout).expand<int>((List<int> ints) => ints).toList()));
stderr.writeln(utf8.decode((await savedStderr).expand<int>((List<int> ints) => ints).toList()));
}
print(
'$redLine\n'
'${bold}ERROR:$red Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset\n'
'${bold}Command:$cyan $commandDescription$reset\n'
'${bold}Relative working directory:$red $relativeWorkingDir$reset\n'
'$redLine'
);
exit(1);
}
}