mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

This turns on order shuffling for all tests that don't fail with it on, marking those tests that do fail with a tag so that they will be run without shuffling on. To determine which tests fail with it on, I ran all the tests 100 times with different random shuffle seeds, and then also ran it with the date seeds from today until the end of July, and tagged all of the test suites (files) that fail, with a seed that caused them to fail.
254 lines
9.5 KiB
Dart
254 lines
9.5 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' as io;
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'utils.dart';
|
|
|
|
/// Runs the `executable` and returns standard output as a stream of lines.
|
|
///
|
|
/// The returned stream reaches its end immediately after the command exits.
|
|
///
|
|
/// If `expectNonZeroExit` is false and the process exits with a non-zero exit
|
|
/// code fails the test immediately by exiting the test process with exit code
|
|
/// 1.
|
|
Stream<String> runAndGetStdout(String executable, List<String> arguments, {
|
|
String workingDirectory,
|
|
Map<String, String> environment,
|
|
bool expectNonZeroExit = false,
|
|
bool expectNoTests = false,
|
|
}) async* {
|
|
final StreamController<String> output = StreamController<String>();
|
|
final Future<CommandResult> command = runCommand(
|
|
executable,
|
|
arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
expectNonZeroExit: expectNonZeroExit,
|
|
expectNoTests: expectNoTests,
|
|
// Capture the output so it's not printed to the console by default.
|
|
outputMode: OutputMode.capture,
|
|
outputListener: (String line, io.Process process) {
|
|
output.add(line);
|
|
},
|
|
);
|
|
|
|
// Close the stream controller after the command is complete. Otherwise,
|
|
// the yield* will never finish.
|
|
command.whenComplete(output.close);
|
|
|
|
yield* output.stream;
|
|
}
|
|
|
|
/// Represents a running process launched using [startCommand].
|
|
class Command {
|
|
Command._(this.process, this._time, this._savedStdout, this._savedStderr);
|
|
|
|
/// The raw process that was launched for this command.
|
|
final io.Process process;
|
|
|
|
final Stopwatch _time;
|
|
final Future<List<List<int>>> _savedStdout;
|
|
final Future<List<List<int>>> _savedStderr;
|
|
|
|
/// Evaluates when the [process] exits.
|
|
///
|
|
/// Returns the result of running the command.
|
|
Future<CommandResult> get onExit async {
|
|
final int exitCode = await process.exitCode;
|
|
_time.stop();
|
|
|
|
// Saved output is null when OutputMode.print is used.
|
|
final String flattenedStdout = _savedStdout != null ? _flattenToString(await _savedStdout) : null;
|
|
final String flattenedStderr = _savedStderr != null ? _flattenToString(await _savedStderr) : null;
|
|
return CommandResult._(exitCode, _time.elapsed, flattenedStdout, flattenedStderr);
|
|
}
|
|
}
|
|
|
|
/// The result of running a command using [startCommand] and [runCommand];
|
|
class CommandResult {
|
|
CommandResult._(this.exitCode, this.elapsedTime, this.flattenedStdout, this.flattenedStderr);
|
|
|
|
/// The exit code of the process.
|
|
final int exitCode;
|
|
|
|
/// The amount of time it took the process to complete.
|
|
final Duration elapsedTime;
|
|
|
|
/// Standard output decoded as a string using UTF8 decoder.
|
|
final String flattenedStdout;
|
|
|
|
/// Standard error output decoded as a string using UTF8 decoder.
|
|
final String flattenedStderr;
|
|
}
|
|
|
|
/// Starts the `executable` and returns a command object representing the
|
|
/// running process.
|
|
///
|
|
/// `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.
|
|
///
|
|
/// `outputMode` controls where the standard output from the command process
|
|
/// goes. See [OutputMode].
|
|
Future<Command> startCommand(String executable, List<String> arguments, {
|
|
String workingDirectory,
|
|
Map<String, String> environment,
|
|
OutputMode outputMode = OutputMode.print,
|
|
bool Function(String) removeLine,
|
|
void Function(String, io.Process) outputListener,
|
|
}) async {
|
|
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
|
final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
|
|
printProgress('RUNNING', relativeWorkingDir, commandDescription);
|
|
|
|
final Stopwatch time = Stopwatch()..start();
|
|
final io.Process process = await io.Process.start(executable, arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
);
|
|
|
|
Future<List<List<int>>> savedStdout = Future<List<List<int>>>.value(<List<int>>[]);
|
|
Future<List<List<int>>> savedStderr = Future<List<List<int>>>.value(<List<int>>[]);
|
|
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:
|
|
stdoutSource.listen((List<int> output) {
|
|
io.stdout.add(output);
|
|
savedStdout.then((List<List<int>> list) => list.add(output));
|
|
});
|
|
process.stderr.listen((List<int> output) {
|
|
io.stderr.add(output);
|
|
savedStdout.then((List<List<int>> list) => list.add(output));
|
|
});
|
|
break;
|
|
case OutputMode.capture:
|
|
savedStdout = stdoutSource.toList();
|
|
savedStderr = process.stderr.toList();
|
|
break;
|
|
}
|
|
|
|
return Command._(process, time, savedStdout, savedStderr);
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// Returns the result of the finished process, or null if `skip` is true.
|
|
///
|
|
/// `outputMode` controls where the standard output from the command process
|
|
/// goes. See [OutputMode].
|
|
Future<CommandResult> runCommand(String executable, List<String> arguments, {
|
|
String workingDirectory,
|
|
Map<String, String> environment,
|
|
bool expectNonZeroExit = false,
|
|
bool expectNoTests = false,
|
|
int expectedExitCode,
|
|
String failureMessage,
|
|
OutputMode outputMode = OutputMode.print,
|
|
bool skip = false,
|
|
bool Function(String) removeLine,
|
|
void Function(String, io.Process) outputListener,
|
|
}) async {
|
|
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
|
final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
|
|
if (skip) {
|
|
printProgress('SKIPPING', relativeWorkingDir, commandDescription);
|
|
return null;
|
|
}
|
|
|
|
final Command command = await startCommand(executable, arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
outputMode: outputMode,
|
|
removeLine: removeLine,
|
|
outputListener: outputListener,
|
|
);
|
|
|
|
final CommandResult result = await command.onExit;
|
|
|
|
// Currently, the test infrastructure fails if it doesn't find any tests to
|
|
// run, but in the case of tests tagged as "no-shuffle", there might either be
|
|
// none that can be shuffled, or none that shouldn't be shuffled, and since
|
|
// we're running it twice to get all the tests in either category, it
|
|
// shouldn't fail if no tests are run.
|
|
//
|
|
// TODO(gspencergoog): This is a workaround until
|
|
// https://github.com/dart-lang/test/issues/1546 is addressed. Remove the
|
|
// workaround (parsing the test output) when/if that issue is fixed.
|
|
final bool skipErrorExit = expectNoTests &&
|
|
result != null &&
|
|
result.flattenedStdout != null &&
|
|
result.flattenedStdout.trimRight().endsWith('No tests ran.');
|
|
|
|
if (!skipErrorExit && ((result.exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && result.exitCode != expectedExitCode))) {
|
|
// Print the output when we get unexpected results (unless output was
|
|
// printed already).
|
|
switch (outputMode) {
|
|
case OutputMode.print:
|
|
break;
|
|
case OutputMode.capture:
|
|
io.stdout.writeln(result.flattenedStdout);
|
|
io.stderr.writeln(result.flattenedStderr);
|
|
break;
|
|
}
|
|
exitWithError(<String>[
|
|
if (failureMessage != null)
|
|
failureMessage
|
|
else
|
|
'${bold}ERROR: ${red}Last command exited with ${result.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(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
|
|
return result;
|
|
}
|
|
|
|
/// 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 the command output from [runCommand] and [startCommand].
|
|
enum OutputMode {
|
|
/// Forwards standard output and standard error streams to the test process'
|
|
/// respective standard streams.
|
|
///
|
|
/// Use this mode if all you want is print the output of the command to the
|
|
/// console. The output is no longer available after the process exits.
|
|
print,
|
|
|
|
/// Saves standard output and standard error streams in memory.
|
|
///
|
|
/// Captured output can be retrieved from the [CommandResult] object.
|
|
///
|
|
/// Use this mode in tests that need to inspect the output of a command, or
|
|
/// when the output should not be printed to console.
|
|
capture,
|
|
}
|