| 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...) Expand all 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...) Expand all 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...) Expand 10 before | Expand all | Expand 10 after 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...) Expand all 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 |