Index: lib/src/runner/configuration/load.dart |
diff --git a/lib/src/runner/configuration/load.dart b/lib/src/runner/configuration/load.dart |
index c3d2ff1f2e23a39c5508195076b8f4dc213d17ca..212ba34bd3177298df8a5fc70cd5b06408874515 100644 |
--- a/lib/src/runner/configuration/load.dart |
+++ b/lib/src/runner/configuration/load.dart |
@@ -44,13 +44,57 @@ class _ConfigurationLoader { |
/// Used for error reporting. |
final String _source; |
- _ConfigurationLoader(this._document, this._source); |
+ /// Whether runner configuration is allowed at this level. |
+ final bool _runnerConfig; |
+ |
+ _ConfigurationLoader(this._document, this._source, {bool runnerConfig: true}) |
+ : _runnerConfig = runnerConfig; |
/// Loads the configuration in [_document]. |
- Configuration load() { |
+ Configuration load() => _loadTestConfig().merge(_loadRunnerConfig()); |
+ |
+ /// Loads test configuration (but not runner configuration). |
+ Configuration _loadTestConfig() { |
var verboseTrace = _getBool("verbose_trace"); |
var jsTrace = _getBool("js_trace"); |
+ var timeout = _parseValue("timeout", (value) => new Timeout.parse(value)); |
+ |
+ var tags = _getMap("tags", key: (keyNode) { |
+ _validate(keyNode, "tags key must be a string.", |
+ (value) => value is String); |
+ _validate( |
+ keyNode, |
+ "Invalid tag. Tags must be (optionally hyphenated) Dart identifiers.", |
+ (value) => value.contains(anchoredHyphenatedIdentifier)); |
+ |
+ return keyNode.value; |
+ }, value: (valueNode) { |
+ return _nestedConfig(valueNode, "tag value", runnerConfig: false); |
+ }); |
+ |
+ return new Configuration( |
+ verboseTrace: verboseTrace, |
+ jsTrace: jsTrace, |
+ timeout: timeout, |
+ tags: tags); |
+ } |
+ |
+ /// Loads runner configuration (but not test configuration). |
+ /// |
+ /// If [_runnerConfig] is `false`, this will error if there are any |
+ /// runner-level configuration fields. |
+ Configuration _loadRunnerConfig() { |
+ if (!_runnerConfig) { |
+ _disallow("reporter"); |
+ _disallow("pub_serve"); |
+ _disallow("concurrency"); |
+ _disallow("platforms"); |
+ _disallow("paths"); |
+ _disallow("filename"); |
+ return new Configuration(); |
+ } |
+ |
var reporter = _getString("reporter"); |
if (reporter != null && !allReporters.contains(reporter)) { |
_error('Unknown reporter "$reporter".', "reporter"); |
@@ -58,7 +102,6 @@ class _ConfigurationLoader { |
var pubServePort = _getInt("pub_serve"); |
var concurrency = _getInt("concurrency"); |
- var timeout = _parseValue("timeout", (value) => new Timeout.parse(value)); |
var allPlatformIdentifiers = |
TestPlatform.all.map((platform) => platform.identifier).toSet(); |
@@ -81,12 +124,9 @@ class _ConfigurationLoader { |
var filename = _parseValue("filename", (value) => new Glob(value)); |
return new Configuration( |
- verboseTrace: verboseTrace, |
- jsTrace: jsTrace, |
reporter: reporter, |
pubServePort: pubServePort, |
concurrency: concurrency, |
- timeout: timeout, |
platforms: platforms, |
paths: paths, |
filename: filename); |
@@ -142,6 +182,34 @@ class _ConfigurationLoader { |
return node.nodes.map(forElement).toList(); |
} |
+ /// Asserts that [field] is a map and runs [key] and [value] for each pair. |
+ /// |
+ /// Returns a map with the keys and values returned by [key] and [value]. Each |
+ /// of these defaults to asserting that the value is a string. |
+ Map _getMap(String field, {key(YamlNode keyNode), |
+ value(YamlNode valueNode)}) { |
+ var node = _getNode(field, "map", (value) => value is Map); |
+ if (node == null) return {}; |
+ |
+ key ??= (keyNode) { |
+ _validate(keyNode, "$field keys must be strings.", |
+ (value) => value is String); |
+ |
+ return keyNode.value; |
+ }; |
+ |
+ value ??= (valueNode) { |
+ _validate(valueNode, "$field values must be strings.", |
+ (value) => value is String); |
+ |
+ return valueNode.value; |
+ }; |
+ |
+ return mapMap(node.nodes, |
+ key: (keyNode, _) => key(keyNode), |
+ value: (_, valueNode) => value(valueNode)); |
+ } |
+ |
/// Asserts that [node] is a string, passes its value to [parse], and returns |
/// the result. |
/// |
@@ -169,6 +237,27 @@ class _ConfigurationLoader { |
return _parseNode(node, field, parse); |
} |
+ /// Parses a nested configuration document. |
+ /// |
+ /// [name] is the name of the field, which is used for error-handling. |
+ /// [runnerConfig] controls whether runner configuration is allowed in the |
+ /// nested configuration. It defaults to [_runnerConfig]. |
+ Configuration _nestedConfig(YamlNode node, String name, |
+ {bool runnerConfig}) { |
+ if (node == null || node.value == null) return new Configuration(); |
+ |
+ _validate(node, "$name must be a map.", (value) => value is Map); |
+ var loader = new _ConfigurationLoader(node, _source, |
+ runnerConfig: runnerConfig ?? _runnerConfig); |
+ return loader.load(); |
+ } |
+ |
+ /// Throws an error if a field named [field] exists at this level. |
+ void _disallow(String field) { |
+ if (!_document.containsKey(field)) return; |
+ _error("$field isn't supported here.", field); |
+ } |
+ |
/// Throws a [SourceSpanFormatException] with [message] about [field]. |
void _error(String message, String field) { |
throw new SourceSpanFormatException( |