Give _runFlutterTest
the ability to validate command output
In another change (#37646), I want to test that a test fails and prints expected output. I didn't see an existing way to do that, so I modified `_runFlutterTest` and `runCommand` to allow capturing the output. Currently capturing and printing output are mutually exclusive since we don't need both. Some awkward bits: * There already exists a `runAndGetStdout` function that is very similar to `runCommand`, and this change makes the conceptual distinction more confusing. * `runFlutterTest` has multiple code paths for different configurations. I don't understand what the different paths are for, and I added output checking only along one of them.
This commit is contained in:
parent
01feddbece
commit
c02b805cdc
@ -585,7 +585,7 @@ Future<void> _verifyGeneratedPluginRegistrants(String flutterRoot) async {
|
|||||||
}
|
}
|
||||||
await runCommand(flutter, <String>['inject-plugins'],
|
await runCommand(flutter, <String>['inject-plugins'],
|
||||||
workingDirectory: package,
|
workingDirectory: package,
|
||||||
printOutput: false,
|
outputMode: OutputMode.discard,
|
||||||
);
|
);
|
||||||
for (File registrant in fileToContent.keys) {
|
for (File registrant in fileToContent.keys) {
|
||||||
if (registrant.readAsStringSync() != fileToContent[registrant]) {
|
if (registrant.readAsStringSync() != fileToContent[registrant]) {
|
||||||
|
@ -83,12 +83,15 @@ Future<void> runCommand(String executable, List<String> arguments, {
|
|||||||
bool expectNonZeroExit = false,
|
bool expectNonZeroExit = false,
|
||||||
int expectedExitCode,
|
int expectedExitCode,
|
||||||
String failureMessage,
|
String failureMessage,
|
||||||
bool printOutput = true,
|
OutputMode outputMode = OutputMode.print,
|
||||||
|
CapturedOutput output,
|
||||||
bool skip = false,
|
bool skip = false,
|
||||||
bool expectFlaky = false,
|
bool expectFlaky = false,
|
||||||
Duration timeout = _kLongTimeout,
|
Duration timeout = _kLongTimeout,
|
||||||
bool Function(String) removeLine,
|
bool Function(String) removeLine,
|
||||||
}) async {
|
}) async {
|
||||||
|
assert((outputMode == OutputMode.capture) == (output != null));
|
||||||
|
|
||||||
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
||||||
final String relativeWorkingDir = path.relative(workingDirectory);
|
final String relativeWorkingDir = path.relative(workingDirectory);
|
||||||
if (skip) {
|
if (skip) {
|
||||||
@ -110,7 +113,7 @@ Future<void> runCommand(String executable, List<String> arguments, {
|
|||||||
.where((String line) => removeLine == null || !removeLine(line))
|
.where((String line) => removeLine == null || !removeLine(line))
|
||||||
.map((String line) => '$line\n')
|
.map((String line) => '$line\n')
|
||||||
.transform(const Utf8Encoder());
|
.transform(const Utf8Encoder());
|
||||||
if (printOutput) {
|
if (outputMode == OutputMode.print) {
|
||||||
await Future.wait<void>(<Future<void>>[
|
await Future.wait<void>(<Future<void>>[
|
||||||
stdout.addStream(stdoutSource),
|
stdout.addStream(stdoutSource),
|
||||||
stderr.addStream(process.stderr),
|
stderr.addStream(process.stderr),
|
||||||
@ -125,6 +128,12 @@ Future<void> runCommand(String executable, List<String> arguments, {
|
|||||||
return (expectNonZeroExit || expectFlaky) ? 0 : 1;
|
return (expectNonZeroExit || expectFlaky) ? 0 : 1;
|
||||||
});
|
});
|
||||||
print('$clock ELAPSED TIME: $bold${elapsedTime(start)}$reset for $commandDescription in $relativeWorkingDir: ');
|
print('$clock ELAPSED TIME: $bold${elapsedTime(start)}$reset for $commandDescription in $relativeWorkingDir: ');
|
||||||
|
|
||||||
|
if (output != null) {
|
||||||
|
output.stdout = flattenToString(await savedStdout);
|
||||||
|
output.stderr = flattenToString(await savedStderr);
|
||||||
|
}
|
||||||
|
|
||||||
// If the test is flaky we don't care about the actual exit.
|
// If the test is flaky we don't care about the actual exit.
|
||||||
if (expectFlaky) {
|
if (expectFlaky) {
|
||||||
return;
|
return;
|
||||||
@ -133,9 +142,12 @@ Future<void> runCommand(String executable, List<String> arguments, {
|
|||||||
if (failureMessage != null) {
|
if (failureMessage != null) {
|
||||||
print(failureMessage);
|
print(failureMessage);
|
||||||
}
|
}
|
||||||
if (!printOutput) {
|
|
||||||
stdout.writeln(utf8.decode((await savedStdout).expand<int>((List<int> ints) => ints).toList()));
|
// Print the output when we get unexpected results (unless output was
|
||||||
stderr.writeln(utf8.decode((await savedStderr).expand<int>((List<int> ints) => ints).toList()));
|
// printed already).
|
||||||
|
if (outputMode != OutputMode.print) {
|
||||||
|
stdout.writeln(flattenToString(await savedStdout));
|
||||||
|
stderr.writeln(flattenToString(await savedStderr));
|
||||||
}
|
}
|
||||||
print(
|
print(
|
||||||
'$redLine\n'
|
'$redLine\n'
|
||||||
@ -147,3 +159,18 @@ Future<void> runCommand(String executable, List<String> arguments, {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T identity<T>(T x) => x;
|
||||||
|
|
||||||
|
/// Flattens a nested list of UTF-8 code units into a single string.
|
||||||
|
String flattenToString(List<List<int>> chunks) =>
|
||||||
|
utf8.decode(chunks.expand<int>(identity).toList(growable: false));
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
@ -15,6 +15,14 @@ import 'run_command.dart';
|
|||||||
|
|
||||||
typedef ShardRunner = Future<void> Function();
|
typedef ShardRunner = Future<void> Function();
|
||||||
|
|
||||||
|
/// A function used to validate the output of a test.
|
||||||
|
///
|
||||||
|
/// If the output matches expectations, the function shall return null.
|
||||||
|
///
|
||||||
|
/// If the output does not match expectations, the function shall return an
|
||||||
|
/// appropriate error message.
|
||||||
|
typedef OutputChecker = String Function(CapturedOutput);
|
||||||
|
|
||||||
final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
|
final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
|
||||||
final String flutter = path.join(flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
|
final String flutter = path.join(flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
|
||||||
final String dart = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', Platform.isWindows ? 'dart.exe' : 'dart');
|
final String dart = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', Platform.isWindows ? 'dart.exe' : 'dart');
|
||||||
@ -142,7 +150,7 @@ Future<void> _runSmokeTests() async {
|
|||||||
<String>['drive', '--use-existing-app', '-t', path.join('test_driver', 'failure.dart')],
|
<String>['drive', '--use-existing-app', '-t', path.join('test_driver', 'failure.dart')],
|
||||||
workingDirectory: path.join(flutterRoot, 'packages', 'flutter_driver'),
|
workingDirectory: path.join(flutterRoot, 'packages', 'flutter_driver'),
|
||||||
expectNonZeroExit: true,
|
expectNonZeroExit: true,
|
||||||
printOutput: false,
|
outputMode: OutputMode.discard,
|
||||||
timeout: _kShortTimeout,
|
timeout: _kShortTimeout,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -765,8 +773,6 @@ class EvalResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runFlutterWebTest(String workingDirectory, {
|
Future<void> _runFlutterWebTest(String workingDirectory, {
|
||||||
bool printOutput = true,
|
|
||||||
bool skip = false,
|
|
||||||
Duration timeout = _kLongTimeout,
|
Duration timeout = _kLongTimeout,
|
||||||
List<String> tests,
|
List<String> tests,
|
||||||
}) async {
|
}) async {
|
||||||
@ -802,6 +808,7 @@ Future<void> _runFlutterTest(String workingDirectory, {
|
|||||||
String script,
|
String script,
|
||||||
bool expectFailure = false,
|
bool expectFailure = false,
|
||||||
bool printOutput = true,
|
bool printOutput = true,
|
||||||
|
OutputChecker outputChecker,
|
||||||
List<String> options = const <String>[],
|
List<String> options = const <String>[],
|
||||||
bool skip = false,
|
bool skip = false,
|
||||||
Duration timeout = _kLongTimeout,
|
Duration timeout = _kLongTimeout,
|
||||||
@ -809,6 +816,9 @@ Future<void> _runFlutterTest(String workingDirectory, {
|
|||||||
Map<String, String> environment,
|
Map<String, String> environment,
|
||||||
List<String> tests = const <String>[],
|
List<String> tests = const <String>[],
|
||||||
}) async {
|
}) async {
|
||||||
|
// Support printing output or capturing it for matching, but not both.
|
||||||
|
assert(_implies(printOutput, outputChecker == null));
|
||||||
|
|
||||||
final List<String> args = <String>[
|
final List<String> args = <String>[
|
||||||
'test',
|
'test',
|
||||||
...options,
|
...options,
|
||||||
@ -838,14 +848,38 @@ Future<void> _runFlutterTest(String workingDirectory, {
|
|||||||
args.addAll(tests);
|
args.addAll(tests);
|
||||||
|
|
||||||
if (!shouldProcessOutput) {
|
if (!shouldProcessOutput) {
|
||||||
return runCommand(flutter, args,
|
OutputMode outputMode = OutputMode.discard;
|
||||||
|
CapturedOutput output;
|
||||||
|
|
||||||
|
if (outputChecker != null) {
|
||||||
|
outputMode = OutputMode.capture;
|
||||||
|
output = CapturedOutput();
|
||||||
|
} else if (printOutput) {
|
||||||
|
outputMode = OutputMode.print;
|
||||||
|
}
|
||||||
|
|
||||||
|
await runCommand(
|
||||||
|
flutter,
|
||||||
|
args,
|
||||||
workingDirectory: workingDirectory,
|
workingDirectory: workingDirectory,
|
||||||
expectNonZeroExit: expectFailure,
|
expectNonZeroExit: expectFailure,
|
||||||
printOutput: printOutput,
|
outputMode: outputMode,
|
||||||
|
output: output,
|
||||||
skip: skip,
|
skip: skip,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
environment: environment,
|
environment: environment,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (outputChecker != null) {
|
||||||
|
final String message = outputChecker(output);
|
||||||
|
if (message != null) {
|
||||||
|
print('$redLine');
|
||||||
|
print(message);
|
||||||
|
print('$redLine');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useFlutterTestFormatter) {
|
if (useFlutterTestFormatter) {
|
||||||
@ -975,3 +1009,8 @@ Future<void> _androidGradleTests(String subShard) async {
|
|||||||
await _runDevicelabTest('module_host_with_custom_build_test', env: env);
|
await _runDevicelabTest('module_host_with_custom_build_test', env: env);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if `p` logically implies `q`, false otherwise.
|
||||||
|
///
|
||||||
|
/// If `p` is true, `q` must be true.
|
||||||
|
bool _implies(bool p, bool q) => !p || q;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user