blob: a11a4af9f7036652d02d95a50a366e3b38e1656a [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 'dart:io';
import 'package:args/args.dart';
import 'package:fxtest/fxtest.dart';
import 'package:fxutils/fxutils.dart';
import 'package:io/ansi.dart';
void usage(ArgParser parser) {
stdout.writeln('''Usage: fx test [testName ...]
Options:
${parser.usage}
Examples:
- Execute all tests
fx test
- Execute the test component available at this URL
fx test fuchsia-pkg://fuchsia.com/myPackageName/#meta/componentName.cm
- Execute the test whose package URL's `package-name` component matches
the value. Runs all tests contained in this package.
fx test myPackageName
- Execute the test whose package URL's `resource-path` component matches
the value. Runs only that test out of its containing package.
fx test componentName
- Execute all tests at and below this path (usually host tests)
fx test //subtree/path
- Multiple test names can be supplied in the same invocation, e.g.:
fx test //subtree/path //another/path fuchsia-pkg://...
- Print help for a test:
fx test -o testName -- --help
The value(s) supplied for `testName` can be fully-formed Fuchsia Package URLs,
Fuchsia package names, or Fuchsia-tree directories. Partial tree paths
will execute all descendent tests.
''');
}
void _directoryBuilder(String path, {bool recursive = false}) {
Directory(path).createSync(recursive: recursive);
}
/// CLI entry point for [FuchsiaTestCommand]. The [main] function mostly
/// handles the SIGINT signal, routing through [FuchsiaTestCommandCli] to handle
/// translating raw command line arguments into constructs that make sense to
/// [FuchsiaTestCommand].
Future<void> main(List<String> args) async {
FuchsiaTestCommandCli cmdCli;
try {
cmdCli = FuchsiaTestCommandCli(
args,
fxEnv: FxEnv(envReader: EnvReader.fromEnvironment()),
usage: usage,
directoryBuilder: _directoryBuilder,
);
} on Exception catch (ex) {
stderr.writeln('Invalid syntax: $ex');
usage(fxTestArgParser);
exitCode = 64; // Improper usage
return null;
}
registerCleanUp(() async {
await cmdCli.terminateEarly();
await cmdCli.cleanUp();
});
_sigintSub ??= ProcessSignal.sigint.watch().listen(cleanUpAndExit);
try {
// Before going through all the trouble of initializing everything for the
// command, make sure everything is valid and that the user doesn't want
// some simple debug output.
bool shouldRun = await cmdCli.preRunChecks(
stdout.write,
);
if (shouldRun) {
// Finally, run the command
await cmdCli.run();
}
} on FxRunException catch (err) {
stderr.writeln('${wrapWith("Error:", [red])} ${err.toString()}');
exitCode = err.exitCode;
} on Exception catch (err) {
if (err is OutputClosedException) {
exitCode = err.exitCode;
} else {
stderr.writeln(wrapWith(err.toString(), [red]));
}
}
await closeSigIntListener();
await cmdCli.cleanUp();
}
StreamSubscription? _sigintSub;
final _cleanup = <Future Function()>[];
void registerCleanUp(Future Function() c) => _cleanup.add(c);
void cleanUpAndExit(ProcessSignal signal,
[int processExitCode = failureExitCode]) async {
// Kick off all registered clean up functions.
List<Future> cleanUpFutures = _cleanup
.map((Function cleanUpFunction) async => cleanUpFunction())
.toList();
await Future.wait(cleanUpFutures);
exitCode = processExitCode;
await closeSigIntListener();
exit(exitCode);
}
Future<void> closeSigIntListener() async {
await _sigintSub?.cancel();
}