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 /// If [global] is `true`, this restricts the configuration file to only rules |
| 25 /// that are supported globally. |
| 26 /// |
24 /// Throws a [FormatException] if the configuration is invalid, and a | 27 /// Throws a [FormatException] if the configuration is invalid, and a |
25 /// [FileSystemException] if it can't be read. | 28 /// [FileSystemException] if it can't be read. |
26 Configuration load(String path) { | 29 Configuration load(String path, {bool global: false}) { |
27 var source = new File(path).readAsStringSync(); | 30 var source = new File(path).readAsStringSync(); |
28 var document = loadYamlNode(source, sourceUrl: p.toUri(path)); | 31 var document = loadYamlNode(source, sourceUrl: p.toUri(path)); |
29 | 32 |
30 if (document.value == null) return Configuration.empty; | 33 if (document.value == null) return Configuration.empty; |
31 | 34 |
32 if (document is! Map) { | 35 if (document is! Map) { |
33 throw new SourceSpanFormatException( | 36 throw new SourceSpanFormatException( |
34 "The configuration must be a YAML map.", document.span, source); | 37 "The configuration must be a YAML map.", document.span, source); |
35 } | 38 } |
36 | 39 |
37 var loader = new _ConfigurationLoader(document, source); | 40 var loader = new _ConfigurationLoader(document, source, global: global); |
38 return loader.load(); | 41 return loader.load(); |
39 } | 42 } |
40 | 43 |
41 /// A helper for [load] that tracks the YAML document. | 44 /// A helper for [load] that tracks the YAML document. |
42 class _ConfigurationLoader { | 45 class _ConfigurationLoader { |
43 /// The parsed configuration document. | 46 /// The parsed configuration document. |
44 final YamlMap _document; | 47 final YamlMap _document; |
45 | 48 |
46 /// The source string for [_document]. | 49 /// The source string for [_document]. |
47 /// | 50 /// |
48 /// Used for error reporting. | 51 /// Used for error reporting. |
49 final String _source; | 52 final String _source; |
50 | 53 |
| 54 /// Whether this is parsing the global configuration file. |
| 55 final bool _global; |
| 56 |
51 /// Whether runner configuration is allowed at this level. | 57 /// Whether runner configuration is allowed at this level. |
52 final bool _runnerConfig; | 58 final bool _runnerConfig; |
53 | 59 |
54 _ConfigurationLoader(this._document, this._source, {bool runnerConfig: true}) | 60 _ConfigurationLoader(this._document, this._source, {bool global: false, |
55 : _runnerConfig = runnerConfig; | 61 bool runnerConfig: true}) |
| 62 : _global = global, |
| 63 _runnerConfig = runnerConfig; |
56 | 64 |
57 /// Loads the configuration in [_document]. | 65 /// Loads the configuration in [_document]. |
58 Configuration load() => _loadTestConfig().merge(_loadRunnerConfig()); | 66 Configuration load() => _loadGlobalTestConfig() |
| 67 .merge(_loadLocalTestConfig()) |
| 68 .merge(_loadGlobalRunnerConfig()) |
| 69 .merge(_loadLocalRunnerConfig()); |
59 | 70 |
60 /// Loads test configuration (but not runner configuration). | 71 /// Loads test configuration that's allowed in the global configuration file. |
61 Configuration _loadTestConfig() { | 72 Configuration _loadGlobalTestConfig() { |
62 var verboseTrace = _getBool("verbose_trace"); | 73 var verboseTrace = _getBool("verbose_trace"); |
63 var jsTrace = _getBool("js_trace"); | 74 var jsTrace = _getBool("js_trace"); |
64 | 75 |
65 var skip = _getValue("skip", "boolean or string", | |
66 (value) => value is bool || value is String); | |
67 var skipReason; | |
68 if (skip is String) { | |
69 skipReason = skip; | |
70 skip = true; | |
71 } | |
72 | |
73 var testOn = _parseValue("test_on", | |
74 (value) => new PlatformSelector.parse(value)); | |
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", | |
79 (tagNode) => _parseIdentifierLike(tagNode, "Tag name")); | |
80 | |
81 var tags = _getMap("tags", | |
82 key: (keyNode) => _parseNode(keyNode, "tags key", | |
83 (value) => new BooleanSelector.parse(value)), | |
84 value: (valueNode) => | |
85 _nestedConfig(valueNode, "tag value", runnerConfig: false)); | |
86 | |
87 var onPlatform = _getMap("on_platform", | 78 var onPlatform = _getMap("on_platform", |
88 key: (keyNode) => _parseNode(keyNode, "on_platform key", | 79 key: (keyNode) => _parseNode(keyNode, "on_platform key", |
89 (value) => new PlatformSelector.parse(value)), | 80 (value) => new PlatformSelector.parse(value)), |
90 value: (valueNode) => | 81 value: (valueNode) => |
91 _nestedConfig(valueNode, "on_platform value", runnerConfig: false)); | 82 _nestedConfig(valueNode, "on_platform value", runnerConfig: false)); |
92 | 83 |
93 var onOS = _getMap("on_os", key: (keyNode) { | 84 var onOS = _getMap("on_os", key: (keyNode) { |
94 _validate(keyNode, "on_os key must be a string.", | 85 _validate(keyNode, "on_os key must be a string.", |
95 (value) => value is String); | 86 (value) => value is String); |
96 | 87 |
97 var os = OperatingSystem.find(keyNode.value); | 88 var os = OperatingSystem.find(keyNode.value); |
98 if (os != null) return os; | 89 if (os != null) return os; |
99 | 90 |
100 throw new SourceSpanFormatException( | 91 throw new SourceSpanFormatException( |
101 'Invalid on_os key: No such operating system.', | 92 'Invalid on_os key: No such operating system.', |
102 keyNode.span, _source); | 93 keyNode.span, _source); |
103 }, value: (valueNode) => _nestedConfig(valueNode, "on_os value")); | 94 }, value: (valueNode) => _nestedConfig(valueNode, "on_os value")); |
104 | 95 |
105 var presets = _getMap("presets", | 96 var presets = _getMap("presets", |
106 key: (keyNode) => _parseIdentifierLike(keyNode, "presets key"), | 97 key: (keyNode) => _parseIdentifierLike(keyNode, "presets key"), |
107 value: (valueNode) => _nestedConfig(valueNode, "presets value")); | 98 value: (valueNode) => _nestedConfig(valueNode, "presets value")); |
108 | 99 |
109 var config = new Configuration( | 100 var config = new Configuration( |
110 verboseTrace: verboseTrace, | 101 verboseTrace: verboseTrace, |
111 jsTrace: jsTrace, | 102 jsTrace: jsTrace, |
112 skip: skip, | |
113 skipReason: skipReason, | |
114 testOn: testOn, | |
115 timeout: timeout, | 103 timeout: timeout, |
116 addTags: addTags, | |
117 tags: tags, | |
118 onPlatform: onPlatform, | 104 onPlatform: onPlatform, |
119 presets: presets); | 105 presets: presets); |
120 | 106 |
121 var osConfig = onOS[currentOS]; | 107 var osConfig = onOS[currentOS]; |
122 return osConfig == null ? config : config.merge(osConfig); | 108 return osConfig == null ? config : config.merge(osConfig); |
123 } | 109 } |
124 | 110 |
125 /// Loads runner configuration (but not test configuration). | 111 /// Loads test configuration that's not allowed in the global configuration |
| 112 /// file. |
| 113 /// |
| 114 /// If [_global] is `true`, this will error if there are any local test-level |
| 115 /// configuration fields. |
| 116 Configuration _loadLocalTestConfig() { |
| 117 if (_global) { |
| 118 _disallow("skip"); |
| 119 _disallow("test_on"); |
| 120 _disallow("add_tags"); |
| 121 _disallow("tags"); |
| 122 return Configuration.empty; |
| 123 } |
| 124 |
| 125 var skip = _getValue("skip", "boolean or string", |
| 126 (value) => value is bool || value is String); |
| 127 var skipReason; |
| 128 if (skip is String) { |
| 129 skipReason = skip; |
| 130 skip = true; |
| 131 } |
| 132 |
| 133 var testOn = _parseValue("test_on", |
| 134 (value) => new PlatformSelector.parse(value)); |
| 135 |
| 136 var addTags = _getList("add_tags", |
| 137 (tagNode) => _parseIdentifierLike(tagNode, "Tag name")); |
| 138 |
| 139 var tags = _getMap("tags", |
| 140 key: (keyNode) => _parseNode(keyNode, "tags key", |
| 141 (value) => new BooleanSelector.parse(value)), |
| 142 value: (valueNode) => |
| 143 _nestedConfig(valueNode, "tag value", runnerConfig: false)); |
| 144 |
| 145 return new Configuration( |
| 146 skip: skip, |
| 147 skipReason: skipReason, |
| 148 testOn: testOn, |
| 149 addTags: addTags, |
| 150 tags: tags); |
| 151 } |
| 152 |
| 153 /// Loads runner configuration that's allowed in the global configuration |
| 154 /// file. |
126 /// | 155 /// |
127 /// If [_runnerConfig] is `false`, this will error if there are any | 156 /// If [_runnerConfig] is `false`, this will error if there are any |
128 /// runner-level configuration fields. | 157 /// runner-level configuration fields. |
129 Configuration _loadRunnerConfig() { | 158 Configuration _loadGlobalRunnerConfig() { |
130 if (!_runnerConfig) { | 159 if (!_runnerConfig) { |
131 _disallow("pause_after_load"); | 160 _disallow("pause_after_load"); |
132 _disallow("reporter"); | 161 _disallow("reporter"); |
133 _disallow("pub_serve"); | |
134 _disallow("concurrency"); | 162 _disallow("concurrency"); |
135 _disallow("names"); | 163 _disallow("names"); |
136 _disallow("plain_names"); | 164 _disallow("plain_names"); |
137 _disallow("platforms"); | 165 _disallow("platforms"); |
138 _disallow("paths"); | |
139 _disallow("filename"); | |
140 _disallow("add_presets"); | 166 _disallow("add_presets"); |
141 _disallow("include_tags"); | |
142 _disallow("exclude_tags"); | |
143 return Configuration.empty; | 167 return Configuration.empty; |
144 } | 168 } |
145 | 169 |
146 var pauseAfterLoad = _getBool("pause_after_load"); | 170 var pauseAfterLoad = _getBool("pause_after_load"); |
147 | 171 |
148 var reporter = _getString("reporter"); | 172 var reporter = _getString("reporter"); |
149 if (reporter != null && !allReporters.contains(reporter)) { | 173 if (reporter != null && !allReporters.contains(reporter)) { |
150 _error('Unknown reporter "$reporter".', "reporter"); | 174 _error('Unknown reporter "$reporter".', "reporter"); |
151 } | 175 } |
152 | 176 |
153 var pubServePort = _getInt("pub_serve"); | |
154 var concurrency = _getInt("concurrency"); | 177 var concurrency = _getInt("concurrency"); |
155 | 178 |
156 var patterns = _getList("names", (nameNode) { | |
157 _validate(nameNode, "Names must be strings.", (value) => value is String); | |
158 return _parseNode(nameNode, "name", (value) => new RegExp(value)); | |
159 })..addAll(_getList("plain_names", (nameNode) { | |
160 _validate(nameNode, "Names must be strings.", (value) => value is String); | |
161 return nameNode.value; | |
162 })); | |
163 | |
164 var allPlatformIdentifiers = | 179 var allPlatformIdentifiers = |
165 TestPlatform.all.map((platform) => platform.identifier).toSet(); | 180 TestPlatform.all.map((platform) => platform.identifier).toSet(); |
166 var platforms = _getList("platforms", (platformNode) { | 181 var platforms = _getList("platforms", (platformNode) { |
167 _validate(platformNode, "Platforms must be strings.", | 182 _validate(platformNode, "Platforms must be strings.", |
168 (value) => value is String); | 183 (value) => value is String); |
169 _validate(platformNode, 'Unknown platform "${platformNode.value}".', | 184 _validate(platformNode, 'Unknown platform "${platformNode.value}".', |
170 allPlatformIdentifiers.contains); | 185 allPlatformIdentifiers.contains); |
171 | 186 |
172 return TestPlatform.find(platformNode.value); | 187 return TestPlatform.find(platformNode.value); |
173 }); | 188 }); |
174 | 189 |
| 190 var chosenPresets = _getList("add_presets", |
| 191 (presetNode) => _parseIdentifierLike(presetNode, "Preset name")); |
| 192 |
| 193 return new Configuration( |
| 194 pauseAfterLoad: pauseAfterLoad, |
| 195 reporter: reporter, |
| 196 concurrency: concurrency, |
| 197 platforms: platforms, |
| 198 chosenPresets: chosenPresets); |
| 199 } |
| 200 |
| 201 /// Loads runner configuration that's not allowed in the global configuration |
| 202 /// file. |
| 203 /// |
| 204 /// If [_runnerConfig] is `false` or if [_global] is `true`, this will error |
| 205 /// if there are any local test-level configuration fields. |
| 206 Configuration _loadLocalRunnerConfig() { |
| 207 if (!_runnerConfig || _global) { |
| 208 _disallow("pub_serve"); |
| 209 _disallow("names"); |
| 210 _disallow("plain_names"); |
| 211 _disallow("paths"); |
| 212 _disallow("filename"); |
| 213 _disallow("include_tags"); |
| 214 _disallow("exclude_tags"); |
| 215 return Configuration.empty; |
| 216 } |
| 217 |
| 218 var pubServePort = _getInt("pub_serve"); |
| 219 |
| 220 var patterns = _getList("names", (nameNode) { |
| 221 _validate(nameNode, "Names must be strings.", (value) => value is String); |
| 222 return _parseNode(nameNode, "name", (value) => new RegExp(value)); |
| 223 })..addAll(_getList("plain_names", (nameNode) { |
| 224 _validate(nameNode, "Names must be strings.", (value) => value is String); |
| 225 return nameNode.value; |
| 226 })); |
| 227 |
175 var paths = _getList("paths", (pathNode) { | 228 var paths = _getList("paths", (pathNode) { |
176 _validate(pathNode, "Paths must be strings.", (value) => value is String); | 229 _validate(pathNode, "Paths must be strings.", (value) => value is String); |
177 _validate(pathNode, "Paths must be relative.", p.url.isRelative); | 230 _validate(pathNode, "Paths must be relative.", p.url.isRelative); |
178 | 231 |
179 return _parseNode(pathNode, "path", p.fromUri); | 232 return _parseNode(pathNode, "path", p.fromUri); |
180 }); | 233 }); |
181 | 234 |
182 var filename = _parseValue("filename", (value) => new Glob(value)); | 235 var filename = _parseValue("filename", (value) => new Glob(value)); |
183 | 236 |
184 var chosenPresets = _getList("add_presets", | |
185 (presetNode) => _parseIdentifierLike(presetNode, "Preset name")); | |
186 | |
187 var includeTags = _parseBooleanSelector("include_tags"); | 237 var includeTags = _parseBooleanSelector("include_tags"); |
188 var excludeTags = _parseBooleanSelector("exclude_tags"); | 238 var excludeTags = _parseBooleanSelector("exclude_tags"); |
189 | 239 |
190 return new Configuration( | 240 return new Configuration( |
191 pauseAfterLoad: pauseAfterLoad, | |
192 reporter: reporter, | |
193 pubServePort: pubServePort, | 241 pubServePort: pubServePort, |
194 concurrency: concurrency, | |
195 patterns: patterns, | 242 patterns: patterns, |
196 platforms: platforms, | |
197 paths: paths, | 243 paths: paths, |
198 filename: filename, | 244 filename: filename, |
199 chosenPresets: chosenPresets, | |
200 includeTags: includeTags, | 245 includeTags: includeTags, |
201 excludeTags: excludeTags); | 246 excludeTags: excludeTags); |
202 } | 247 } |
203 | 248 |
204 /// Throws an exception with [message] if [test] returns `false` when passed | 249 /// Throws an exception with [message] if [test] returns `false` when passed |
205 /// [node]'s value. | 250 /// [node]'s value. |
206 void _validate(YamlNode node, String message, bool test(value)) { | 251 void _validate(YamlNode node, String message, bool test(value)) { |
207 if (test(node.value)) return; | 252 if (test(node.value)) return; |
208 throw new SourceSpanFormatException(message, node.span, _source); | 253 throw new SourceSpanFormatException(message, node.span, _source); |
209 } | 254 } |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
325 /// | 370 /// |
326 /// [name] is the name of the field, which is used for error-handling. | 371 /// [name] is the name of the field, which is used for error-handling. |
327 /// [runnerConfig] controls whether runner configuration is allowed in the | 372 /// [runnerConfig] controls whether runner configuration is allowed in the |
328 /// nested configuration. It defaults to [_runnerConfig]. | 373 /// nested configuration. It defaults to [_runnerConfig]. |
329 Configuration _nestedConfig(YamlNode node, String name, | 374 Configuration _nestedConfig(YamlNode node, String name, |
330 {bool runnerConfig}) { | 375 {bool runnerConfig}) { |
331 if (node == null || node.value == null) return Configuration.empty; | 376 if (node == null || node.value == null) return Configuration.empty; |
332 | 377 |
333 _validate(node, "$name must be a map.", (value) => value is Map); | 378 _validate(node, "$name must be a map.", (value) => value is Map); |
334 var loader = new _ConfigurationLoader(node, _source, | 379 var loader = new _ConfigurationLoader(node, _source, |
| 380 global: _global, |
335 runnerConfig: runnerConfig ?? _runnerConfig); | 381 runnerConfig: runnerConfig ?? _runnerConfig); |
336 return loader.load(); | 382 return loader.load(); |
337 } | 383 } |
338 | 384 |
339 /// Throws an error if a field named [field] exists at this level. | 385 /// Throws an error if a field named [field] exists at this level. |
340 void _disallow(String field) { | 386 void _disallow(String field) { |
341 if (!_document.containsKey(field)) return; | 387 if (!_document.containsKey(field)) return; |
342 _error("$field isn't supported here.", field); | 388 |
| 389 throw new SourceSpanFormatException( |
| 390 "$field isn't supported here.", |
| 391 // We need the key as a [YamlNode] to get its span. |
| 392 _document.nodes.keys.firstWhere((key) => key.value == field).span, |
| 393 _source); |
343 } | 394 } |
344 | 395 |
345 /// Throws a [SourceSpanFormatException] with [message] about [field]. | 396 /// Throws a [SourceSpanFormatException] with [message] about [field]. |
346 void _error(String message, String field) { | 397 void _error(String message, String field) { |
347 throw new SourceSpanFormatException( | 398 throw new SourceSpanFormatException( |
348 message, _document.nodes[field].span, _source); | 399 message, _document.nodes[field].span, _source); |
349 } | 400 } |
350 } | 401 } |
OLD | NEW |