blob: c78e86b024330a1d97e3ad41e86d6f3d69fc77cb [file] [log] [blame]
// Copyright 2019 The Fuchsia 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:io';
import 'package:io/ansi.dart';
import 'package:fxtest/fxtest.dart';
import 'package:meta/meta.dart';
/// Container that holds enough information about a test to execute it and
/// report back on output / events.
class TestBundle {
/// Wrapper around the individual strings needed to run this test.
final TestDefinition testDefinition;
// That which actually launches a process to run the test.
final TestRunner testRunner;
/// The directory from which our test assumes it was invoked.
final String workingDirectory;
/// Tokens to pass through to individual tests.
final List<String> extraFlags;
/// Flag to disable actually running the test.
final bool isDryRun;
/// Flag to raise an exception on any test failure which will bubble all the
/// way up and halt suite execution. Useful when users want "fail-fast"
/// behavior.
final bool raiseOnFailure;
TestBundle(
this.testDefinition, {
@required this.workingDirectory,
this.extraFlags = const [],
this.raiseOnFailure = false,
this.isDryRun = false,
testRunner,
}) : testRunner = testRunner ?? TestRunner.runner;
/// Invokes the actual test that this class wraps.
///
/// Returns a stream of test events that send feedback to the user.
Stream<TestEvent> run() async* {
CommandTokens commandTokens =
testDefinition.executionHandle.getInvocationTokens();
// Unparsed tests imply a major problem with `fx test`, so we
// definitely want to throw an exception
if (commandTokens.tokens == null || commandTokens.tokens.isEmpty) {
throw UnrunnableTestException(
'Failed to determine run context for test:\n$testDefinition',
);
}
// Defer this check to now (as opposed to when the test is compiled), so the
// feedback can be synced to when tests are executed.
if (commandTokens.warning != null && commandTokens.warning != '') {
yield TestInfo(commandTokens.warning);
}
String fullCommand = commandTokens.fullCommand;
yield TestStarted(testName: fullCommand);
if (isDryRun) {
yield TestResult.skipped(testName: fullCommand);
return;
}
DateTime start = DateTime.now();
ProcessResult result = await testRunner.run(
commandTokens.command,
commandTokens.args..addAll(extraFlags),
workingDirectory: workingDirectory,
);
yield TestResult(
testName: fullCommand,
exitCode: result.exitCode,
runtime: DateTime.now().difference(start),
message: result.exitCode == 0
? result.stdout
: _formatError(
fullCommand,
result,
),
);
if (raiseOnFailure && result.exitCode != 0) {
throw FailFastException();
}
}
String _formatError(String cmd, ProcessResult result) {
List<String> resultStdout = result.stdout != ''
? [...result.stdout.split('\n'), '\n']
.where((var val) => val != '')
.cast<String>()
.toList()
: [];
return [
wrapWith('> $cmd', [red]),
...resultStdout,
result.stderr,
].join('\n');
}
}