blob: e5e004f04ee8ba88a5f30b1c5f0dcb8cfe67a003 [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:async';
import 'package:fxtest/fxtest.dart';
import 'package:meta/meta.dart';
/// Main entry-point for all Fuchsia tests, both host and on-device.
///
/// [FuchsiaTestCommand] receives a combination of test names and feature flags,
/// and due to the nature of Fuchsia tests, needs to run each passed test name
/// against the full set of feature flags. For example, if the way to invoke
/// this command is `fx test <...args>`, and the developer executes
/// `... test1 test2 -flag1 -flag2`, the desired behavior for our user lies
/// behind pretending the user entered `... test1 -flag1 -flag2` and
/// `... test2 -flag1 flag2` separately. Note that the individual tests indicated
/// by these two imaginary commands are then rolled back into one singular test
/// suite, so end users will be none the wiser.
///
/// The following is a high level road map of how [FuchsiaTestCommand] works:
///
/// - Parse commands into testNames and flags
/// - Produce a list of each individual `testName` paired with all provided flags
/// - loop over this list `testName` and flag combinations, matching tests
/// out of `out/default/tests.json`
/// - Load each matching test's raw JSON into a [TestDefinition] instance which
/// handles determining all the runtime invocation details for a given test
/// - Loop over the list of [TestDefinition] instances, listening to emitted
/// [TestEvent] instances with a [FormattedOutput] class
/// - Every time a [TestEvent] instance is emitted, our [FormattedOutput]
/// class captures it and flushes appropriate text to [stdout].
/// - Once the [TestDefinition] list has been completely processed, flush any
/// captured stderr from individual test processes to show errors / stacktraces
class FuchsiaTestCommand {
// ignore: close_sinks
final _eventStreamController = StreamController<TestEvent>();
/// Bundle of configuration options for this invocation.
final TestFlags testFlags;
/// Absolute path of the active Fuchsia build.
final FuchsiaLocator fuchsiaLocator;
/// Translator between [TestEvent] instances and output for the user.
final OutputFormatter outputFormatter;
FuchsiaTestCommand({
@required this.fuchsiaLocator,
@required this.outputFormatter,
@required this.testFlags,
});
Stream<TestEvent> get stream => _eventStreamController.stream;
void emitEvent(TestEvent event) {
_eventStreamController.sink.add(event);
}
void dispose() {
_eventStreamController.sink.close();
}
Future<int> runTestSuite({TestsManifestReader manifestReader}) async {
stream.listen(outputFormatter.update);
manifestReader ??= TestsManifestReader();
List<TestDefinition> testDefinitions = await manifestReader.loadTestsJson(
buildDir: fuchsiaLocator.buildDir,
manifestFileName: 'tests.json',
);
ParsedManifest parsedManifest = manifestReader.aggregateTests(
buildDir: fuchsiaLocator.buildDir,
eventEmitter: emitEvent,
testDefinitions: testDefinitions,
testFlags: testFlags,
);
manifestReader.reportOnTestBundles(
userFriendlyBuildDir: fuchsiaLocator.userFriendlyBuildDir,
eventEmitter: emitEvent,
parsedManifest: parsedManifest,
testFlags: testFlags,
);
var exitCode = 0;
try {
await _runTests(parsedManifest.testBundles).forEach((event) {
emitEvent(event);
if (event is TestResult && !event.isSuccess) {
exitCode = 2;
}
});
} on FailFastException catch (_) {
exitCode = 2;
}
emitEvent(AllTestsCompleted());
return exitCode;
}
Stream<TestEvent> _runTests(List<TestBundle> testBundles) async* {
// Let the output formatter know that we're done parsing and
// emitting preliminary events
yield BeginningTests();
var count = 0;
for (TestBundle testBundle in testBundles) {
yield* testBundle.run();
count += 1;
if (testFlags.limit > 0 && count >= testFlags.limit) {
break;
}
}
}
}