blob: 364e58348d0b5fc79c3095a25dcc1b615d83fd01 [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 'package:args/args.dart';
import 'package:fxtest/fxtest.dart';
import 'package:io/ansi.dart' as ansi;
import 'package:meta/meta.dart';
// ignore: prefer_generic_function_type_aliases
typedef String Stylizer(String value, Iterable<ansi.AnsiCode> codes,
{bool forScript});
/// Simple class to hold shared parameters.
class Flags {
final bool dryRun;
final bool isVerbose;
/// The maximum number of tests to run. If 0, all tests will be executed.
final int limit;
/// The realm name to run the test inside of. If null, a random name is used.
final String realm;
final bool allOutput;
final bool shouldRebuild;
/// Extra tokens to be passed through to individual tests.
final bool exactMatches;
final bool infoOnly;
final bool shouldFailFast;
final bool simpleOutput;
final bool shouldOnlyRunDeviceTests;
final bool shouldOnlyRunHostTests;
final bool shouldPrintSkipped;
final bool shouldRandomizeTestOrder;
final bool shouldSilenceUnsupported;
final bool shouldUpdateIfInBase;
final int slowThreshold;
Flags({
this.dryRun = false,
this.isVerbose = false,
this.limit = 0,
this.realm,
this.allOutput = false,
this.exactMatches = false,
this.infoOnly = false,
this.simpleOutput = false,
this.shouldFailFast = false,
this.shouldOnlyRunDeviceTests = false,
this.shouldOnlyRunHostTests = false,
this.shouldPrintSkipped = false,
this.shouldRandomizeTestOrder = false,
this.shouldRebuild = true,
this.shouldSilenceUnsupported = false,
this.shouldUpdateIfInBase = true,
this.slowThreshold = 0,
});
factory Flags.fromArgResults(ArgResults argResults) {
return Flags(
allOutput: argResults['output'],
dryRun: argResults['info'] || argResults['dry'],
exactMatches: argResults['exact'],
infoOnly: argResults['info'],
isVerbose: argResults['verbose'] || argResults['output'],
limit: int.parse(argResults['limit'] ?? '0'),
realm: argResults['realm'],
simpleOutput: argResults['simple'],
shouldFailFast: argResults['fail'],
shouldOnlyRunDeviceTests: argResults['device'],
shouldOnlyRunHostTests: argResults['host'],
shouldPrintSkipped: argResults['skipped'],
// True (aka, yes rebuild) if `no-build` is missing or set to `False`
shouldRebuild: (!argResults['info'] && !argResults['dry']) &&
(argResults['build'] == null || argResults['build']),
shouldRandomizeTestOrder: argResults['random'],
shouldSilenceUnsupported: argResults['silenceunsupported'],
shouldUpdateIfInBase: argResults['updateifinbase'],
slowThreshold: int.parse(argResults['slow'] ?? '0'),
);
}
@override
String toString() => '''<Flags
dryRun: $dryRun
allOutput: $allOutput,
limit: $limit
realm: $realm
isVerbose: $isVerbose
exactMatches: $exactMatches,
info: $infoOnly,
simpleOutput: $simpleOutput,
shouldOnlyRunDeviceTests: $shouldOnlyRunDeviceTests
shouldOnlyRunHostTests: $shouldOnlyRunHostTests
shouldFailFast: $shouldFailFast
shouldPrintSkipped: $shouldPrintSkipped
shouldRandomizeTestOrder: $shouldRandomizeTestOrder
shouldSilenceUnsupported: $shouldSilenceUnsupported
shouldUpdateIfInBase: $shouldUpdateIfInBase
slowThreshold: $slowThreshold
>''';
}
/// The parsed parameters passed by our test-running user for evaluation
/// against specific tests available to the current build.
///
/// This handles the fact that users can invoke flags we need to honor in a
/// combinatorial sort of way. For example, consider this invocation:
///
/// ```sh
/// fx test //network //bootloader -d
/// ```
///
/// Here, our developer wants to run all device network tests and all device
/// bootloader tests. To streamline the code that delivers this, we will expand
/// parameters and pretend the developer executed the following two commands:
///
/// ```sh
/// fx test //network -d
/// fx test //bootloader -d
/// ```
/// Each "imagined" command will produce a set of tests which will be combined
/// into a master list of tests we run in one go, with aggregated output.
/// However, the intermediate layer streamlines much of our matching logic.
///
/// [TestsConfig] executes this combinatorial explosion by expanding its
/// parameters into a list of [PermutatedTestFlag] instances.
class TestsConfig {
final Flags flags;
final List<String> runnerTokens;
final TestArguments testArguments;
final FuchsiaLocator fuchsiaLocator;
final List<List<MatchableTestName>> testNameGroups;
TestsConfig({
@required this.flags,
@required this.runnerTokens,
@required this.testArguments,
@required this.testNameGroups,
@required this.fuchsiaLocator,
});
factory TestsConfig.fromRawArgs({
@required List<String> rawArgs,
FuchsiaLocator fuchsiaLocator,
}) {
var _testArguments = TestArguments(rawArgs: rawArgs);
var _testNamesCollector = TestNamesCollector(
rawArgs: _testArguments.parsedArgs.arguments,
rawTestNames: _testArguments.parsedArgs.rest,
fuchsiaLocator: fuchsiaLocator,
);
Flags flags = Flags.fromArgResults(_testArguments.parsedArgs);
return TestsConfig(
flags: flags,
fuchsiaLocator: fuchsiaLocator ?? FuchsiaLocator.shared,
runnerTokens:
flags.realm != null ? ['--realm-label=${flags.realm}'] : const [],
testArguments: _testArguments,
testNameGroups: _testNamesCollector.collect(),
);
}
Iterable<PermutatedTestsConfig> get permutations sync* {
// Check for having zero `testName` instances, which indicates that the
// developer wants a wide-open run that includes as many tests as possible
if (testNameGroups.isEmpty) {
yield PermutatedTestsConfig(
flags: flags,
testNameGroup: null,
);
return;
}
for (List<MatchableTestName> testNameGroup in testNameGroups) {
yield PermutatedTestsConfig(
flags: flags,
testNameGroup: testNameGroup,
);
}
}
/// Wrapper around io.ansi.wrapWith which first honors config flags.
String wrapWith(String value, Iterable<ansi.AnsiCode> codes,
{bool forScript = false}) =>
flags.simpleOutput
? value
: ansi.wrapWith(value, codes, forScript: forScript);
}
/// An expanded set of flags passed to `fx test` against which all available
/// tests will be examined.
class PermutatedTestsConfig {
final List<MatchableTestName> testNameGroup;
final Flags flags;
PermutatedTestsConfig({
@required this.flags,
@required this.testNameGroup,
});
@override
String toString() {
var chunks = <String>[
if (testNameGroup != null) testNameGroup.join(', '),
if (flags.shouldOnlyRunDeviceTests) '-d',
if (flags.shouldOnlyRunHostTests) '-h',
];
var chunksStr = chunks.isNotEmpty ? ' ${chunks.join(" ")}' : '';
return '<PermuatedTestsConfig$chunksStr>';
}
}