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:boolean_selector/boolean_selector.dart'; | 7 import 'package:boolean_selector/boolean_selector.dart'; |
8 import 'package:glob/glob.dart'; | 8 import 'package:glob/glob.dart'; |
9 import 'package:path/path.dart' as p; | 9 import 'package:path/path.dart' as p; |
10 import 'package:source_span/source_span.dart'; | 10 import 'package:source_span/source_span.dart'; |
11 import 'package:yaml/yaml.dart'; | 11 import 'package:yaml/yaml.dart'; |
12 | 12 |
13 import '../../backend/operating_system.dart'; | 13 import '../../backend/operating_system.dart'; |
14 import '../../backend/platform_selector.dart'; | 14 import '../../backend/platform_selector.dart'; |
15 import '../../backend/test_platform.dart'; | 15 import '../../backend/test_platform.dart'; |
16 import '../../frontend/timeout.dart'; | 16 import '../../frontend/timeout.dart'; |
17 import '../../utils.dart'; | 17 import '../../utils.dart'; |
18 import '../../util/io.dart'; | 18 import '../../util/io.dart'; |
19 import '../configuration.dart'; | 19 import '../configuration.dart'; |
20 import 'values.dart'; | 20 import 'values.dart'; |
21 | 21 |
22 /// Loads configuration information from a YAML file at [path]. | 22 /// Loads configuration information from a YAML file at [path]. |
23 /// | 23 /// |
24 /// Throws a [FormatException] if the configuration is invalid, and a | 24 /// Throws a [FormatException] if the configuration is invalid, and a |
25 /// [FileSystemException] if it can't be read. | 25 /// [FileSystemException] if it can't be read. |
26 Configuration load(String path) { | 26 Configuration load(String path) { |
27 var source = new File(path).readAsStringSync(); | 27 var source = new File(path).readAsStringSync(); |
28 var document = loadYamlNode(source, sourceUrl: p.toUri(path)); | 28 var document = loadYamlNode(source, sourceUrl: p.toUri(path)); |
29 | 29 |
30 if (document.value == null) return new Configuration(); | 30 if (document.value == null) return Configuration.empty; |
31 | 31 |
32 if (document is! Map) { | 32 if (document is! Map) { |
33 throw new SourceSpanFormatException( | 33 throw new SourceSpanFormatException( |
34 "The configuration must be a YAML map.", document.span, source); | 34 "The configuration must be a YAML map.", document.span, source); |
35 } | 35 } |
36 | 36 |
37 var loader = new _ConfigurationLoader(document, source); | 37 var loader = new _ConfigurationLoader(document, source); |
38 return loader.load(); | 38 return loader.load(); |
39 } | 39 } |
40 | 40 |
(...skipping 27 matching lines...) Loading... |
68 if (skip is String) { | 68 if (skip is String) { |
69 skipReason = skip; | 69 skipReason = skip; |
70 skip = true; | 70 skip = true; |
71 } | 71 } |
72 | 72 |
73 var testOn = _parseValue("test_on", | 73 var testOn = _parseValue("test_on", |
74 (value) => new PlatformSelector.parse(value)); | 74 (value) => new PlatformSelector.parse(value)); |
75 | 75 |
76 var timeout = _parseValue("timeout", (value) => new Timeout.parse(value)); | 76 var timeout = _parseValue("timeout", (value) => new Timeout.parse(value)); |
77 | 77 |
78 var addTags = _getList("add_tags", (tagNode) { | 78 var addTags = _getList("add_tags", |
79 _validate(tagNode, "Tags must be strings.", (value) => value is String); | 79 (tagNode) => _parseIdentifierLike(tagNode, "Tag name")); |
80 _validate( | |
81 tagNode, | |
82 "Invalid tag. Tags must be (optionally hyphenated) Dart identifiers.", | |
83 (value) => value.contains(anchoredHyphenatedIdentifier)); | |
84 return tagNode.value; | |
85 }); | |
86 | 80 |
87 var tags = _getMap("tags", | 81 var tags = _getMap("tags", |
88 key: (keyNode) => _parseNode(keyNode, "tags key", | 82 key: (keyNode) => _parseNode(keyNode, "tags key", |
89 (value) => new BooleanSelector.parse(value)), | 83 (value) => new BooleanSelector.parse(value)), |
90 value: (valueNode) => | 84 value: (valueNode) => |
91 _nestedConfig(valueNode, "tag value", runnerConfig: false)); | 85 _nestedConfig(valueNode, "tag value", runnerConfig: false)); |
92 | 86 |
93 var onPlatform = _getMap("on_platform", | 87 var onPlatform = _getMap("on_platform", |
94 key: (keyNode) => _parseNode(keyNode, "on_platform key", | 88 key: (keyNode) => _parseNode(keyNode, "on_platform key", |
95 (value) => new PlatformSelector.parse(value)), | 89 (value) => new PlatformSelector.parse(value)), |
96 value: (valueNode) => | 90 value: (valueNode) => |
97 _nestedConfig(valueNode, "on_platform value", runnerConfig: false)); | 91 _nestedConfig(valueNode, "on_platform value", runnerConfig: false)); |
98 | 92 |
99 var onOS = _getMap("on_os", key: (keyNode) { | 93 var onOS = _getMap("on_os", key: (keyNode) { |
100 _validate(keyNode, "on_os key must be a string.", | 94 _validate(keyNode, "on_os key must be a string.", |
101 (value) => value is String); | 95 (value) => value is String); |
102 | 96 |
103 var os = OperatingSystem.find(keyNode.value); | 97 var os = OperatingSystem.find(keyNode.value); |
104 if (os != null) return os; | 98 if (os != null) return os; |
105 | 99 |
106 throw new SourceSpanFormatException( | 100 throw new SourceSpanFormatException( |
107 'Invalid on_os key: No such operating system.', | 101 'Invalid on_os key: No such operating system.', |
108 keyNode.span, _source); | 102 keyNode.span, _source); |
109 }, value: (valueNode) => _nestedConfig(valueNode, "on_os value")); | 103 }, value: (valueNode) => _nestedConfig(valueNode, "on_os value")); |
110 | 104 |
| 105 var presets = _getMap("presets", |
| 106 key: (keyNode) => _parseIdentifierLike(keyNode, "presets key"), |
| 107 value: (valueNode) => _nestedConfig(valueNode, "presets value")); |
| 108 |
111 var config = new Configuration( | 109 var config = new Configuration( |
112 verboseTrace: verboseTrace, | 110 verboseTrace: verboseTrace, |
113 jsTrace: jsTrace, | 111 jsTrace: jsTrace, |
114 skip: skip, | 112 skip: skip, |
115 skipReason: skipReason, | 113 skipReason: skipReason, |
116 testOn: testOn, | 114 testOn: testOn, |
117 timeout: timeout, | 115 timeout: timeout, |
118 addTags: addTags, | 116 addTags: addTags, |
119 tags: tags, | 117 tags: tags, |
120 onPlatform: onPlatform); | 118 onPlatform: onPlatform, |
| 119 presets: presets); |
121 | 120 |
122 var osConfig = onOS[currentOS]; | 121 var osConfig = onOS[currentOS]; |
123 return osConfig == null ? config : config.merge(osConfig); | 122 return osConfig == null ? config : config.merge(osConfig); |
124 } | 123 } |
125 | 124 |
126 /// Loads runner configuration (but not test configuration). | 125 /// Loads runner configuration (but not test configuration). |
127 /// | 126 /// |
128 /// If [_runnerConfig] is `false`, this will error if there are any | 127 /// If [_runnerConfig] is `false`, this will error if there are any |
129 /// runner-level configuration fields. | 128 /// runner-level configuration fields. |
130 Configuration _loadRunnerConfig() { | 129 Configuration _loadRunnerConfig() { |
131 if (!_runnerConfig) { | 130 if (!_runnerConfig) { |
132 _disallow("reporter"); | 131 _disallow("reporter"); |
133 _disallow("pub_serve"); | 132 _disallow("pub_serve"); |
134 _disallow("concurrency"); | 133 _disallow("concurrency"); |
135 _disallow("platforms"); | 134 _disallow("platforms"); |
136 _disallow("paths"); | 135 _disallow("paths"); |
137 _disallow("filename"); | 136 _disallow("filename"); |
138 return new Configuration(); | 137 _disallow("add_presets"); |
| 138 return Configuration.empty; |
139 } | 139 } |
140 | 140 |
141 var reporter = _getString("reporter"); | 141 var reporter = _getString("reporter"); |
142 if (reporter != null && !allReporters.contains(reporter)) { | 142 if (reporter != null && !allReporters.contains(reporter)) { |
143 _error('Unknown reporter "$reporter".', "reporter"); | 143 _error('Unknown reporter "$reporter".', "reporter"); |
144 } | 144 } |
145 | 145 |
146 var pubServePort = _getInt("pub_serve"); | 146 var pubServePort = _getInt("pub_serve"); |
147 var concurrency = _getInt("concurrency"); | 147 var concurrency = _getInt("concurrency"); |
148 | 148 |
(...skipping 10 matching lines...) Loading... |
159 | 159 |
160 var paths = _getList("paths", (pathNode) { | 160 var paths = _getList("paths", (pathNode) { |
161 _validate(pathNode, "Paths must be strings.", (value) => value is String); | 161 _validate(pathNode, "Paths must be strings.", (value) => value is String); |
162 _validate(pathNode, "Paths must be relative.", p.url.isRelative); | 162 _validate(pathNode, "Paths must be relative.", p.url.isRelative); |
163 | 163 |
164 return _parseNode(pathNode, "path", p.fromUri); | 164 return _parseNode(pathNode, "path", p.fromUri); |
165 }); | 165 }); |
166 | 166 |
167 var filename = _parseValue("filename", (value) => new Glob(value)); | 167 var filename = _parseValue("filename", (value) => new Glob(value)); |
168 | 168 |
| 169 var chosenPresets = _getList("add_presets", |
| 170 (presetNode) => _parseIdentifierLike(presetNode, "Preset name")); |
| 171 |
169 return new Configuration( | 172 return new Configuration( |
170 reporter: reporter, | 173 reporter: reporter, |
171 pubServePort: pubServePort, | 174 pubServePort: pubServePort, |
172 concurrency: concurrency, | 175 concurrency: concurrency, |
173 platforms: platforms, | 176 platforms: platforms, |
174 paths: paths, | 177 paths: paths, |
175 filename: filename); | 178 filename: filename, |
| 179 chosenPresets: chosenPresets); |
176 } | 180 } |
177 | 181 |
178 /// Throws an exception with [message] if [test] returns `false` when passed | 182 /// Throws an exception with [message] if [test] returns `false` when passed |
179 /// [node]'s value. | 183 /// [node]'s value. |
180 void _validate(YamlNode node, String message, bool test(value)) { | 184 void _validate(YamlNode node, String message, bool test(value)) { |
181 if (test(node.value)) return; | 185 if (test(node.value)) return; |
182 throw new SourceSpanFormatException(message, node.span, _source); | 186 throw new SourceSpanFormatException(message, node.span, _source); |
183 } | 187 } |
184 | 188 |
185 /// Returns the value of the node at [field]. | 189 /// Returns the value of the node at [field]. |
(...skipping 60 matching lines...) Loading... |
246 (value) => value is String); | 250 (value) => value is String); |
247 | 251 |
248 return valueNode.value; | 252 return valueNode.value; |
249 }; | 253 }; |
250 | 254 |
251 return mapMap(node.nodes, | 255 return mapMap(node.nodes, |
252 key: (keyNode, _) => key(keyNode), | 256 key: (keyNode, _) => key(keyNode), |
253 value: (_, valueNode) => value(valueNode)); | 257 value: (_, valueNode) => value(valueNode)); |
254 } | 258 } |
255 | 259 |
| 260 String _parseIdentifierLike(YamlNode node, String name) { |
| 261 _validate(node, "$name must be a string.", (value) => value is String); |
| 262 _validate( |
| 263 node, |
| 264 "$name must be an (optionally hyphenated) Dart identifier.", |
| 265 (value) => value.contains(anchoredHyphenatedIdentifier)); |
| 266 return node.value; |
| 267 } |
| 268 |
256 /// Asserts that [node] is a string, passes its value to [parse], and returns | 269 /// Asserts that [node] is a string, passes its value to [parse], and returns |
257 /// the result. | 270 /// the result. |
258 /// | 271 /// |
259 /// If [parse] throws a [FormatException], it's wrapped to include [node]'s | 272 /// If [parse] throws a [FormatException], it's wrapped to include [node]'s |
260 /// span. | 273 /// span. |
261 _parseNode(YamlNode node, String name, parse(String value)) { | 274 _parseNode(YamlNode node, String name, parse(String value)) { |
262 _validate(node, "$name must be a string.", (value) => value is String); | 275 _validate(node, "$name must be a string.", (value) => value is String); |
263 | 276 |
264 try { | 277 try { |
265 return parse(node.value); | 278 return parse(node.value); |
(...skipping 14 matching lines...) Loading... |
280 return _parseNode(node, field, parse); | 293 return _parseNode(node, field, parse); |
281 } | 294 } |
282 | 295 |
283 /// Parses a nested configuration document. | 296 /// Parses a nested configuration document. |
284 /// | 297 /// |
285 /// [name] is the name of the field, which is used for error-handling. | 298 /// [name] is the name of the field, which is used for error-handling. |
286 /// [runnerConfig] controls whether runner configuration is allowed in the | 299 /// [runnerConfig] controls whether runner configuration is allowed in the |
287 /// nested configuration. It defaults to [_runnerConfig]. | 300 /// nested configuration. It defaults to [_runnerConfig]. |
288 Configuration _nestedConfig(YamlNode node, String name, | 301 Configuration _nestedConfig(YamlNode node, String name, |
289 {bool runnerConfig}) { | 302 {bool runnerConfig}) { |
290 if (node == null || node.value == null) return new Configuration(); | 303 if (node == null || node.value == null) return Configuration.empty; |
291 | 304 |
292 _validate(node, "$name must be a map.", (value) => value is Map); | 305 _validate(node, "$name must be a map.", (value) => value is Map); |
293 var loader = new _ConfigurationLoader(node, _source, | 306 var loader = new _ConfigurationLoader(node, _source, |
294 runnerConfig: runnerConfig ?? _runnerConfig); | 307 runnerConfig: runnerConfig ?? _runnerConfig); |
295 return loader.load(); | 308 return loader.load(); |
296 } | 309 } |
297 | 310 |
298 /// Throws an error if a field named [field] exists at this level. | 311 /// Throws an error if a field named [field] exists at this level. |
299 void _disallow(String field) { | 312 void _disallow(String field) { |
300 if (!_document.containsKey(field)) return; | 313 if (!_document.containsKey(field)) return; |
301 _error("$field isn't supported here.", field); | 314 _error("$field isn't supported here.", field); |
302 } | 315 } |
303 | 316 |
304 /// Throws a [SourceSpanFormatException] with [message] about [field]. | 317 /// Throws a [SourceSpanFormatException] with [message] about [field]. |
305 void _error(String message, String field) { | 318 void _error(String message, String field) { |
306 throw new SourceSpanFormatException( | 319 throw new SourceSpanFormatException( |
307 message, _document.nodes[field].span, _source); | 320 message, _document.nodes[field].span, _source); |
308 } | 321 } |
309 } | 322 } |
OLD | NEW |