| 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 |