Index: lib/src/runner/configuration.dart |
diff --git a/lib/src/runner/configuration.dart b/lib/src/runner/configuration.dart |
index b9e849455c89491e990830b485336dfb343dd385..28f42788636b964fc87e35d648c96ab29b377b56 100644 |
--- a/lib/src/runner/configuration.dart |
+++ b/lib/src/runner/configuration.dart |
@@ -3,165 +3,99 @@ |
// BSD-style license that can be found in the LICENSE file. |
import 'dart:io'; |
-import 'dart:math' as math; |
-import 'package:args/args.dart'; |
import 'package:path/path.dart' as p; |
import '../frontend/timeout.dart'; |
import '../backend/metadata.dart'; |
import '../backend/test_platform.dart'; |
-import '../utils.dart'; |
import '../util/io.dart'; |
- |
-/// The default number of test suites to run at once. |
-/// |
-/// This defaults to half the available processors, since presumably some of |
-/// them will be used for the OS and other processes. |
-final _defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2); |
+import 'configuration/args.dart' as args; |
+import 'configuration/load.dart'; |
+import 'configuration/values.dart'; |
/// A class that encapsulates the command-line configuration of the test runner. |
class Configuration { |
- /// The parser used to parse the command-line arguments. |
- static final ArgParser _parser = (() { |
- var parser = new ArgParser(allowTrailingOptions: true); |
- |
- var allPlatforms = TestPlatform.all.toList(); |
- if (!Platform.isMacOS) allPlatforms.remove(TestPlatform.safari); |
- if (!Platform.isWindows) allPlatforms.remove(TestPlatform.internetExplorer); |
- |
- parser.addFlag("help", abbr: "h", negatable: false, |
- help: "Shows this usage information."); |
- parser.addFlag("version", negatable: false, |
- help: "Shows the package's version."); |
- parser.addOption("package-root", hide: true); |
- |
- parser.addSeparator("======== Selecting Tests"); |
- parser.addOption("name", |
- abbr: 'n', |
- help: 'A substring of the name of the test to run.\n' |
- 'Regular expression syntax is supported.'); |
- parser.addOption("plain-name", |
- abbr: 'N', |
- help: 'A plain-text substring of the name of the test to run.'); |
- // TODO(nweiz): Support the full platform-selector syntax for choosing which |
- // tags to run. In the shorter term, disallow non-"identifier" tags. |
- parser.addOption("tags", |
- abbr: 't', |
- help: 'Run only tests with all of the specified tags.', |
- allowMultiple: true); |
- parser.addOption("tag", hide: true, allowMultiple: true); |
- parser.addOption("exclude-tags", |
- abbr: 'x', |
- help: "Don't run tests with any of the specified tags.", |
- allowMultiple: true); |
- parser.addOption("exclude-tag", hide: true, allowMultiple: true); |
- |
- parser.addSeparator("======== Running Tests"); |
- parser.addOption("platform", |
- abbr: 'p', |
- help: 'The platform(s) on which to run the tests.', |
- allowed: allPlatforms.map((platform) => platform.identifier).toList(), |
- defaultsTo: 'vm', |
- allowMultiple: true); |
- parser.addOption("concurrency", |
- abbr: 'j', |
- help: 'The number of concurrent test suites run.\n' |
- '(defaults to $_defaultConcurrency)', |
- valueHelp: 'threads'); |
- parser.addOption("pub-serve", |
- help: 'The port of a pub serve instance serving "test/".', |
- valueHelp: 'port'); |
- |
- // Note: although we list the 30s default timeout as though it were a |
- // default value for this argument, it's actually encoded in the [Invoker]'s |
- // call to [Timeout.apply]. |
- parser.addOption("timeout", |
- help: 'The default test timeout. For example: 15s, 2x, none\n' |
- '(defaults to 30s)'); |
- parser.addFlag("pause-after-load", |
- help: 'Pauses for debugging before any tests execute.\n' |
- 'Implies --concurrency=1 and --timeout=none.\n' |
- 'Currently only supported for browser tests.', |
- negatable: false); |
- |
- parser.addSeparator("======== Output"); |
- parser.addOption("reporter", |
- abbr: 'r', |
- help: 'The runner used to print test results.', |
- allowed: ['compact', 'expanded', 'json'], |
- defaultsTo: Platform.isWindows ? 'expanded' : 'compact', |
- allowedHelp: { |
- 'compact': 'A single line, updated continuously.', |
- 'expanded': 'A separate line for each update.', |
- 'json': 'A machine-readable format (see https://goo.gl/0HRhdZ).' |
- }); |
- parser.addFlag("verbose-trace", negatable: false, |
- help: 'Whether to emit stack traces with core library frames.'); |
- parser.addFlag("js-trace", negatable: false, |
- help: 'Whether to emit raw JavaScript stack traces for browser tests.'); |
- parser.addFlag("color", defaultsTo: null, |
- help: 'Whether to use terminal colors.\n(auto-detected by default)'); |
- |
- return parser; |
- })(); |
- |
/// The usage string for the command-line arguments. |
- static String get usage => _parser.usage; |
+ static String get usage => args.usage; |
/// Whether `--help` was passed. |
- final bool help; |
+ bool get help => _help ?? false; |
+ final bool _help; |
/// Whether `--version` was passed. |
- final bool version; |
+ bool get version => _version ?? false; |
+ final bool _version; |
/// Whether stack traces should be presented as-is or folded to remove |
/// irrelevant packages. |
- final bool verboseTrace; |
+ bool get verboseTrace => _verboseTrace ?? false; |
+ final bool _verboseTrace; |
/// Whether JavaScript stack traces should be left as-is or converted to |
/// Dart-like traces. |
- final bool jsTrace; |
+ bool get jsTrace => _jsTrace ?? false; |
+ final bool _jsTrace; |
/// Whether to pause for debugging after loading each test suite. |
- final bool pauseAfterLoad; |
+ bool get pauseAfterLoad => _pauseAfterLoad ?? false; |
+ final bool _pauseAfterLoad; |
/// The package root for resolving "package:" URLs. |
- final String packageRoot; |
+ String get packageRoot => _packageRoot ?? p.join(p.current, 'packages'); |
+ final String _packageRoot; |
/// The name of the reporter to use to display results. |
- final String reporter; |
+ String get reporter => _reporter ?? defaultReporter; |
+ final String _reporter; |
/// The URL for the `pub serve` instance from which to load tests, or `null` |
/// if tests should be loaded from the filesystem. |
final Uri pubServeUrl; |
/// The default test timeout. |
+ /// |
+ /// When [merge]d, this combines with the other configuration's timeout using |
+ /// [Timeout.merge]. |
final Timeout timeout; |
/// Whether to use command-line color escapes. |
- final bool color; |
+ bool get color => _color ?? canUseSpecialChars; |
+ final bool _color; |
/// How many tests to run concurrently. |
- final int concurrency; |
+ int get concurrency => |
+ pauseAfterLoad ? 1 : (_concurrency ?? defaultConcurrency); |
+ final int _concurrency; |
- /// The from which to load tests. |
- final List<String> paths; |
+ /// The paths from which to load tests. |
+ List<String> get paths => _paths ?? ["test"]; |
+ final List<String> _paths; |
/// Whether the load paths were passed explicitly or the default was used. |
- final bool explicitPaths; |
+ bool get explicitPaths => _paths != null; |
/// The pattern to match against test names to decide which to run, or `null` |
/// if all tests should be run. |
final Pattern pattern; |
/// The set of platforms on which to run tests. |
- final List<TestPlatform> platforms; |
+ List<TestPlatform> get platforms => _platforms ?? [TestPlatform.vm]; |
+ final List<TestPlatform> _platforms; |
- /// Restricts the set of tests to a set of tags |
+ /// Restricts the set of tests to a set of tags. |
+ /// |
+ /// If this is empty, it applies no restrictions. |
+ /// |
+ /// When [merge]d, this is unioned with the other configuration's tags. |
final Set<String> tags; |
- /// Does not run tests with tags from this set |
+ /// Does not run tests with tags from this set. |
+ /// |
+ /// If this is empty, it applies no restrictions. |
+ /// |
+ /// When [merge]d, this is unioned with the other configuration's excluded |
+ /// tags. |
final Set<String> excludeTags; |
/// The global test metadata derived from this configuration. |
@@ -171,100 +105,70 @@ class Configuration { |
/// Parses the configuration from [args]. |
/// |
/// Throws a [FormatException] if [args] are invalid. |
- factory Configuration.parse(List<String> args) { |
- var options = _parser.parse(args); |
- |
- var pattern; |
- if (options['name'] != null) { |
- if (options["plain-name"] != null) { |
- throw new FormatException( |
- "--name and --plain-name may not both be passed."); |
- } |
+ factory Configuration.parse(List<String> arguments) => args.parse(arguments); |
- pattern = _wrapFormatException( |
- options, 'name', (value) => new RegExp(value)); |
- } else if (options['plain-name'] != null) { |
- pattern = options['plain-name']; |
- } |
- |
- var tags = new Set(); |
- tags.addAll(options['tags'] ?? []); |
- tags.addAll(options['tag'] ?? []); |
- |
- var excludeTags = new Set(); |
- excludeTags.addAll(options['exclude-tags'] ?? []); |
- excludeTags.addAll(options['exclude-tag'] ?? []); |
- |
- var tagIntersection = tags.intersection(excludeTags); |
- if (tagIntersection.isNotEmpty) { |
- throw new FormatException( |
- 'The ${pluralize('tag', tagIntersection.length)} ' |
- '${toSentence(tagIntersection)} ' |
- '${pluralize('was', tagIntersection.length, plural: 'were')} ' |
- 'both included and excluded.'); |
- } |
- |
- return new Configuration( |
- help: options['help'], |
- version: options['version'], |
- verboseTrace: options['verbose-trace'], |
- jsTrace: options['js-trace'], |
- pauseAfterLoad: options['pause-after-load'], |
- color: options['color'], |
- packageRoot: options['package-root'], |
- reporter: options['reporter'], |
- pubServePort: _wrapFormatException(options, 'pub-serve', int.parse), |
- concurrency: _wrapFormatException(options, 'concurrency', int.parse, |
- orElse: () => _defaultConcurrency), |
- timeout: _wrapFormatException(options, 'timeout', |
- (value) => new Timeout.parse(value), |
- orElse: () => new Timeout.factor(1)), |
- pattern: pattern, |
- platforms: options['platform'].map(TestPlatform.find), |
- paths: options.rest.isEmpty ? null : options.rest, |
- tags: tags, |
- excludeTags: excludeTags); |
- } |
- |
- /// Runs [parse] on the value of the option [name], and wraps any |
- /// [FormatException] it throws with additional information. |
- static _wrapFormatException(ArgResults options, String name, parse(value), |
- {orElse()}) { |
- var value = options[name]; |
- if (value == null) return orElse == null ? null : orElse(); |
- |
- try { |
- return parse(value); |
- } on FormatException catch (error) { |
- throw new FormatException('Couldn\'t parse --$name "${options[name]}": ' |
- '${error.message}'); |
- } |
- } |
- |
- Configuration({this.help: false, this.version: false, |
- this.verboseTrace: false, this.jsTrace: false, |
- bool pauseAfterLoad: false, bool color, String packageRoot, |
- String reporter, int pubServePort, int concurrency, Timeout timeout, |
- this.pattern, Iterable<TestPlatform> platforms, |
- Iterable<String> paths, Set<String> tags, Set<String> excludeTags}) |
- : pauseAfterLoad = pauseAfterLoad, |
- color = color == null ? canUseSpecialChars : color, |
- packageRoot = packageRoot == null |
- ? p.join(p.current, 'packages') |
- : packageRoot, |
- reporter = reporter == null ? 'compact' : reporter, |
+ /// Loads the configuration from [path]. |
+ /// |
+ /// Throws an [IOException] if [path] does not exist or cannot be read. Throws |
+ /// a [FormatException] if its contents are invalid. |
+ factory Configuration.load(String path) => load(path); |
+ |
+ Configuration({bool help, bool version, bool verboseTrace, bool jsTrace, |
+ bool pauseAfterLoad, bool color, String packageRoot, String reporter, |
+ int pubServePort, int concurrency, Timeout timeout, this.pattern, |
+ Iterable<TestPlatform> platforms, Iterable<String> paths, |
+ Iterable<String> tags, Iterable<String> excludeTags}) |
+ : _help = help, |
+ _version = version, |
+ _verboseTrace = verboseTrace, |
+ _jsTrace = jsTrace, |
+ _pauseAfterLoad = pauseAfterLoad, |
+ _color = color, |
+ _packageRoot = packageRoot, |
+ _reporter = reporter, |
pubServeUrl = pubServePort == null |
? null |
: Uri.parse("http://localhost:$pubServePort"), |
- concurrency = pauseAfterLoad |
- ? 1 |
- : (concurrency == null ? _defaultConcurrency : concurrency), |
- timeout = pauseAfterLoad |
+ _concurrency = concurrency, |
+ timeout = (pauseAfterLoad ?? false) |
? Timeout.none |
: (timeout == null ? new Timeout.factor(1) : timeout), |
- platforms = platforms == null ? [TestPlatform.vm] : platforms.toList(), |
- paths = paths == null ? ["test"] : paths.toList(), |
- explicitPaths = paths != null, |
- this.tags = tags, |
- this.excludeTags = excludeTags; |
+ _platforms = _list(platforms), |
+ _paths = _list(paths), |
+ tags = tags?.toSet() ?? new Set(), |
+ excludeTags = excludeTags?.toSet() ?? new Set(); |
+ |
+ /// Returns a [input] as a list or `null`. |
+ /// |
+ /// If [input] is `null` or empty, this returns `null`. Otherwise, it returns |
+ /// `input.toList()`. |
+ static List _list(Iterable input) { |
+ if (input == null) return null; |
+ input = input.toList(); |
+ if (input.isEmpty) return null; |
+ return input; |
+ } |
+ |
+ /// Merges this with [other]. |
+ /// |
+ /// For most fields, if both configurations have values set, [other]'s value |
+ /// takes precedence. However, certain fields are merged together instead. |
+ /// This is indicated in those fields' documentation. |
+ Configuration merge(Configuration other) => new Configuration( |
+ help: other._help ?? _help, |
+ version: other._version ?? _version, |
+ verboseTrace: other._verboseTrace ?? _verboseTrace, |
+ jsTrace: other._jsTrace ?? _jsTrace, |
+ pauseAfterLoad: other._pauseAfterLoad ?? _pauseAfterLoad, |
+ color: other._color ?? _color, |
+ packageRoot: other._packageRoot ?? _packageRoot, |
+ reporter: other._reporter ?? _reporter, |
+ pubServePort: (other.pubServeUrl ?? pubServeUrl)?.port, |
+ concurrency: other._concurrency ?? _concurrency, |
+ timeout: timeout.merge(other.timeout), |
+ pattern: other.pattern ?? pattern, |
+ platforms: other._platforms ?? _platforms, |
+ paths: other._paths ?? _paths, |
+ tags: other.tags.union(tags), |
+ excludeTags: other.excludeTags.union(excludeTags)); |
} |