Chromium Code Reviews| Index: lib/src/runner/configuration/args.dart | 
| diff --git a/lib/src/runner/configuration/args.dart b/lib/src/runner/configuration/args.dart | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..ccc6a455a43c6d700b8c152e960869de857a71dd | 
| --- /dev/null | 
| +++ b/lib/src/runner/configuration/args.dart | 
| @@ -0,0 +1,177 @@ | 
| +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 
| +// for details. 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:args/args.dart'; | 
| + | 
| +import '../../frontend/timeout.dart'; | 
| +import '../../backend/test_platform.dart'; | 
| +import '../../utils.dart'; | 
| +import '../configuration.dart'; | 
| +import 'values.dart'; | 
| + | 
| +/// The parser used to parse the command-line arguments. | 
| +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); | 
| + | 
| + // Note that defaultsTo declarations here are only for documentation purposes. | 
| + // We pass null values rather than defaults to [new Configuration] so that it | 
| + // merges properly with the config file. | 
| + | 
| + 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.', | 
| + defaultsTo: 'vm', | 
| + allowed: allPlatforms.map((platform) => platform.identifier).toList(), | 
| + allowMultiple: true); | 
| + parser.addOption("concurrency", | 
| + abbr: 'j', | 
| + help: 'The number of concurrent test suites run.', | 
| + defaultsTo: defaultConcurrency, | 
| 
 
kevmoo
2016/02/03 23:01:56
defaultConcurrency must be a String
 
 | 
| + valueHelp: 'threads'); | 
| + parser.addOption("pub-serve", | 
| + help: 'The port of a pub serve instance serving "test/".', | 
| + valueHelp: 'port'); | 
| + parser.addOption("timeout", | 
| + help: 'The default test timeout. For example: 15s, 2x, none', | 
| + defaultsTo: '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.', | 
| + defaultsTo: defaultReporter, | 
| + allowed: allReporters, | 
| + 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", | 
| + help: 'Whether to use terminal colors.\n(auto-detected by default)'); | 
| + | 
| + return parser; | 
| +})(); | 
| + | 
| +/// The usage string for the command-line arguments. | 
| +String get usage => _parser.usage; | 
| + | 
| +/// Parses the configuration from [args]. | 
| +/// | 
| +/// Throws a [FormatException] if [args] are invalid. | 
| +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."); | 
| + } | 
| + | 
| + 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.'); | 
| + } | 
| + | 
| + // If the user hasn't explicitly chosen a value, we want to pass null values | 
| + // to [new Configuration] so that it considers those fields unset when merging | 
| + // with configuration from the config file. | 
| + ifParsed(name) => options.wasParsed(name) ? options[name] : null; | 
| + | 
| + return new Configuration( | 
| + help: ifParsed('help'), | 
| + version: ifParsed('version'), | 
| + verboseTrace: ifParsed('verbose-trace'), | 
| + jsTrace: ifParsed('js-trace'), | 
| + pauseAfterLoad: ifParsed('pause-after-load'), | 
| + color: ifParsed('color'), | 
| + packageRoot: ifParsed('package-root'), | 
| + reporter: ifParsed('reporter'), | 
| + pubServePort: _wrapFormatException(options, 'pub-serve', int.parse), | 
| + concurrency: _wrapFormatException(options, 'concurrency', int.parse), | 
| + timeout: _wrapFormatException(options, 'timeout', | 
| + (value) => new Timeout.parse(value)), | 
| + pattern: pattern, | 
| + platforms: ifParsed('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. | 
| +_wrapFormatException(ArgResults options, String name, parse(value)) { | 
| + if (!options.wasParsed(name)) return null; | 
| + | 
| + var value = options[name]; | 
| + if (value == null) return null; | 
| + | 
| + try { | 
| + return parse(value); | 
| + } on FormatException catch (error) { | 
| + throw new FormatException('Couldn\'t parse --$name "${options[name]}": ' | 
| + '${error.message}'); | 
| + } | 
| +} |