OLD | NEW |
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:io'; | 5 import 'dart:io'; |
6 | 6 |
7 import 'package:glob/glob.dart'; | 7 import 'package:glob/glob.dart'; |
8 import 'package:path/path.dart' as p; | 8 import 'package:path/path.dart' as p; |
9 import 'package:source_span/source_span.dart'; | 9 import 'package:source_span/source_span.dart'; |
10 import 'package:yaml/yaml.dart'; | 10 import 'package:yaml/yaml.dart'; |
(...skipping 26 matching lines...) Expand all Loading... |
37 /// A helper for [load] that tracks the YAML document. | 37 /// A helper for [load] that tracks the YAML document. |
38 class _ConfigurationLoader { | 38 class _ConfigurationLoader { |
39 /// The parsed configuration document. | 39 /// The parsed configuration document. |
40 final YamlMap _document; | 40 final YamlMap _document; |
41 | 41 |
42 /// The source string for [_document]. | 42 /// The source string for [_document]. |
43 /// | 43 /// |
44 /// Used for error reporting. | 44 /// Used for error reporting. |
45 final String _source; | 45 final String _source; |
46 | 46 |
47 _ConfigurationLoader(this._document, this._source); | 47 /// Whether runner configuration is allowed at this level. |
| 48 final bool _runnerConfig; |
| 49 |
| 50 _ConfigurationLoader(this._document, this._source, {bool runnerConfig: true}) |
| 51 : _runnerConfig = runnerConfig; |
48 | 52 |
49 /// Loads the configuration in [_document]. | 53 /// Loads the configuration in [_document]. |
50 Configuration load() { | 54 Configuration load() => _loadTestConfig().merge(_loadRunnerConfig()); |
| 55 |
| 56 /// Loads test configuration (but not runner configuration). |
| 57 Configuration _loadTestConfig() { |
51 var verboseTrace = _getBool("verbose_trace"); | 58 var verboseTrace = _getBool("verbose_trace"); |
52 var jsTrace = _getBool("js_trace"); | 59 var jsTrace = _getBool("js_trace"); |
53 | 60 |
| 61 var timeout = _parseValue("timeout", (value) => new Timeout.parse(value)); |
| 62 |
| 63 var tags = _getMap("tags", key: (keyNode) { |
| 64 _validate(keyNode, "tags key must be a string.", |
| 65 (value) => value is String); |
| 66 _validate( |
| 67 keyNode, |
| 68 "Invalid tag. Tags must be (optionally hyphenated) Dart identifiers.", |
| 69 (value) => value.contains(anchoredHyphenatedIdentifier)); |
| 70 |
| 71 return keyNode.value; |
| 72 }, value: (valueNode) { |
| 73 return _nestedConfig(valueNode, "tag value", runnerConfig: false); |
| 74 }); |
| 75 |
| 76 return new Configuration( |
| 77 verboseTrace: verboseTrace, |
| 78 jsTrace: jsTrace, |
| 79 timeout: timeout, |
| 80 tags: tags); |
| 81 } |
| 82 |
| 83 /// Loads runner configuration (but not test configuration). |
| 84 /// |
| 85 /// If [_runnerConfig] is `false`, this will error if there are any |
| 86 /// runner-level configuration fields. |
| 87 Configuration _loadRunnerConfig() { |
| 88 if (!_runnerConfig) { |
| 89 _disallow("reporter"); |
| 90 _disallow("pub_serve"); |
| 91 _disallow("concurrency"); |
| 92 _disallow("platforms"); |
| 93 _disallow("paths"); |
| 94 _disallow("filename"); |
| 95 return new Configuration(); |
| 96 } |
| 97 |
54 var reporter = _getString("reporter"); | 98 var reporter = _getString("reporter"); |
55 if (reporter != null && !allReporters.contains(reporter)) { | 99 if (reporter != null && !allReporters.contains(reporter)) { |
56 _error('Unknown reporter "$reporter".', "reporter"); | 100 _error('Unknown reporter "$reporter".', "reporter"); |
57 } | 101 } |
58 | 102 |
59 var pubServePort = _getInt("pub_serve"); | 103 var pubServePort = _getInt("pub_serve"); |
60 var concurrency = _getInt("concurrency"); | 104 var concurrency = _getInt("concurrency"); |
61 var timeout = _parseValue("timeout", (value) => new Timeout.parse(value)); | |
62 | 105 |
63 var allPlatformIdentifiers = | 106 var allPlatformIdentifiers = |
64 TestPlatform.all.map((platform) => platform.identifier).toSet(); | 107 TestPlatform.all.map((platform) => platform.identifier).toSet(); |
65 var platforms = _getList("platforms", (platformNode) { | 108 var platforms = _getList("platforms", (platformNode) { |
66 _validate(platformNode, "Platforms must be strings.", | 109 _validate(platformNode, "Platforms must be strings.", |
67 (value) => value is String); | 110 (value) => value is String); |
68 _validate(platformNode, 'Unknown platform "${platformNode.value}".', | 111 _validate(platformNode, 'Unknown platform "${platformNode.value}".', |
69 allPlatformIdentifiers.contains); | 112 allPlatformIdentifiers.contains); |
70 | 113 |
71 return TestPlatform.find(platformNode.value); | 114 return TestPlatform.find(platformNode.value); |
72 }); | 115 }); |
73 | 116 |
74 var paths = _getList("paths", (pathNode) { | 117 var paths = _getList("paths", (pathNode) { |
75 _validate(pathNode, "Paths must be strings.", (value) => value is String); | 118 _validate(pathNode, "Paths must be strings.", (value) => value is String); |
76 _validate(pathNode, "Paths must be relative.", p.url.isRelative); | 119 _validate(pathNode, "Paths must be relative.", p.url.isRelative); |
77 | 120 |
78 return _parseNode(pathNode, "path", p.fromUri); | 121 return _parseNode(pathNode, "path", p.fromUri); |
79 }); | 122 }); |
80 | 123 |
81 var filename = _parseValue("filename", (value) => new Glob(value)); | 124 var filename = _parseValue("filename", (value) => new Glob(value)); |
82 | 125 |
83 return new Configuration( | 126 return new Configuration( |
84 verboseTrace: verboseTrace, | |
85 jsTrace: jsTrace, | |
86 reporter: reporter, | 127 reporter: reporter, |
87 pubServePort: pubServePort, | 128 pubServePort: pubServePort, |
88 concurrency: concurrency, | 129 concurrency: concurrency, |
89 timeout: timeout, | |
90 platforms: platforms, | 130 platforms: platforms, |
91 paths: paths, | 131 paths: paths, |
92 filename: filename); | 132 filename: filename); |
93 } | 133 } |
94 | 134 |
95 /// Throws an exception with [message] if [test] returns `false` when passed | 135 /// Throws an exception with [message] if [test] returns `false` when passed |
96 /// [node]'s value. | 136 /// [node]'s value. |
97 void _validate(YamlNode node, String message, bool test(value)) { | 137 void _validate(YamlNode node, String message, bool test(value)) { |
98 if (test(node.value)) return; | 138 if (test(node.value)) return; |
99 throw new SourceSpanFormatException(message, node.span, _source); | 139 throw new SourceSpanFormatException(message, node.span, _source); |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
135 /// Asserts that [field] is a list and runs [forElement] for each element it | 175 /// Asserts that [field] is a list and runs [forElement] for each element it |
136 /// contains. | 176 /// contains. |
137 /// | 177 /// |
138 /// Returns a list of values returned by [forElement]. | 178 /// Returns a list of values returned by [forElement]. |
139 List _getList(String field, forElement(YamlNode elementNode)) { | 179 List _getList(String field, forElement(YamlNode elementNode)) { |
140 var node = _getNode(field, "list", (value) => value is List); | 180 var node = _getNode(field, "list", (value) => value is List); |
141 if (node == null) return []; | 181 if (node == null) return []; |
142 return node.nodes.map(forElement).toList(); | 182 return node.nodes.map(forElement).toList(); |
143 } | 183 } |
144 | 184 |
| 185 /// Asserts that [field] is a map and runs [key] and [value] for each pair. |
| 186 /// |
| 187 /// Returns a map with the keys and values returned by [key] and [value]. Each |
| 188 /// of these defaults to asserting that the value is a string. |
| 189 Map _getMap(String field, {key(YamlNode keyNode), |
| 190 value(YamlNode valueNode)}) { |
| 191 var node = _getNode(field, "map", (value) => value is Map); |
| 192 if (node == null) return {}; |
| 193 |
| 194 key ??= (keyNode) { |
| 195 _validate(keyNode, "$field keys must be strings.", |
| 196 (value) => value is String); |
| 197 |
| 198 return keyNode.value; |
| 199 }; |
| 200 |
| 201 value ??= (valueNode) { |
| 202 _validate(valueNode, "$field values must be strings.", |
| 203 (value) => value is String); |
| 204 |
| 205 return valueNode.value; |
| 206 }; |
| 207 |
| 208 return mapMap(node.nodes, |
| 209 key: (keyNode, _) => key(keyNode), |
| 210 value: (_, valueNode) => value(valueNode)); |
| 211 } |
| 212 |
145 /// Asserts that [node] is a string, passes its value to [parse], and returns | 213 /// Asserts that [node] is a string, passes its value to [parse], and returns |
146 /// the result. | 214 /// the result. |
147 /// | 215 /// |
148 /// If [parse] throws a [FormatException], it's wrapped to include [node]'s | 216 /// If [parse] throws a [FormatException], it's wrapped to include [node]'s |
149 /// span. | 217 /// span. |
150 _parseNode(YamlNode node, String name, parse(String value)) { | 218 _parseNode(YamlNode node, String name, parse(String value)) { |
151 _validate(node, "$name must be a string.", (value) => value is String); | 219 _validate(node, "$name must be a string.", (value) => value is String); |
152 | 220 |
153 try { | 221 try { |
154 return parse(node.value); | 222 return parse(node.value); |
155 } on FormatException catch (error) { | 223 } on FormatException catch (error) { |
156 throw new SourceSpanFormatException( | 224 throw new SourceSpanFormatException( |
157 'Invalid $name: ${error.message}', node.span, _source); | 225 'Invalid $name: ${error.message}', node.span, _source); |
158 } | 226 } |
159 } | 227 } |
160 | 228 |
161 /// Asserts that [field] is a string, passes it to [parse], and returns the | 229 /// Asserts that [field] is a string, passes it to [parse], and returns the |
162 /// result. | 230 /// result. |
163 /// | 231 /// |
164 /// If [parse] throws a [FormatException], it's wrapped to include [field]'s | 232 /// If [parse] throws a [FormatException], it's wrapped to include [field]'s |
165 /// span. | 233 /// span. |
166 _parseValue(String field, parse(String value)) { | 234 _parseValue(String field, parse(String value)) { |
167 var node = _document.nodes[field]; | 235 var node = _document.nodes[field]; |
168 if (node == null) return null; | 236 if (node == null) return null; |
169 return _parseNode(node, field, parse); | 237 return _parseNode(node, field, parse); |
170 } | 238 } |
171 | 239 |
| 240 /// Parses a nested configuration document. |
| 241 /// |
| 242 /// [name] is the name of the field, which is used for error-handling. |
| 243 /// [runnerConfig] controls whether runner configuration is allowed in the |
| 244 /// nested configuration. It defaults to [_runnerConfig]. |
| 245 Configuration _nestedConfig(YamlNode node, String name, |
| 246 {bool runnerConfig}) { |
| 247 if (node == null || node.value == null) return new Configuration(); |
| 248 |
| 249 _validate(node, "$name must be a map.", (value) => value is Map); |
| 250 var loader = new _ConfigurationLoader(node, _source, |
| 251 runnerConfig: runnerConfig ?? _runnerConfig); |
| 252 return loader.load(); |
| 253 } |
| 254 |
| 255 /// Throws an error if a field named [field] exists at this level. |
| 256 void _disallow(String field) { |
| 257 if (!_document.containsKey(field)) return; |
| 258 _error("$field isn't supported here.", field); |
| 259 } |
| 260 |
172 /// Throws a [SourceSpanFormatException] with [message] about [field]. | 261 /// Throws a [SourceSpanFormatException] with [message] about [field]. |
173 void _error(String message, String field) { | 262 void _error(String message, String field) { |
174 throw new SourceSpanFormatException( | 263 throw new SourceSpanFormatException( |
175 message, _document.nodes[field].span, _source); | 264 message, _document.nodes[field].span, _source); |
176 } | 265 } |
177 } | 266 } |
OLD | NEW |