| Index: lib/src/runner/configuration/load.dart
 | 
| diff --git a/lib/src/runner/configuration/load.dart b/lib/src/runner/configuration/load.dart
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..d2def18469e06f6540022fee732263844aec1fd4
 | 
| --- /dev/null
 | 
| +++ b/lib/src/runner/configuration/load.dart
 | 
| @@ -0,0 +1,156 @@
 | 
| +// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
 | 
| +// for details. All rights reserved. Use of this source code is governed by a
 | 
| +// BSD-style license that can be found in the LICENSE file.
 | 
| +
 | 
| +import 'dart:io';
 | 
| +
 | 
| +import 'package:path/path.dart' as p;
 | 
| +import 'package:source_span/source_span.dart';
 | 
| +import 'package:yaml/yaml.dart';
 | 
| +
 | 
| +import '../../utils.dart';
 | 
| +import '../../frontend/timeout.dart';
 | 
| +import '../../backend/test_platform.dart';
 | 
| +import '../configuration.dart';
 | 
| +import 'values.dart';
 | 
| +
 | 
| +/// Loads configuration information from a YAML file at [path].
 | 
| +///
 | 
| +/// Throws a [FormatException] if the configuration is invalid, and a
 | 
| +/// [FileSystemException] if it can't be read.
 | 
| +Configuration load(String path) {
 | 
| +  var source = new File(path).readAsStringSync();
 | 
| +  var document = loadYamlNode(source, sourceUrl: p.toUri(path));
 | 
| +
 | 
| +  if (document.value == null) return new Configuration();
 | 
| +
 | 
| +  if (document is! Map) {
 | 
| +    throw new SourceSpanFormatException(
 | 
| +        "The configuration must be a YAML map.", document.span, source);
 | 
| +  }
 | 
| +
 | 
| +  var loader = new _ConfigurationLoader(document, source);
 | 
| +  return loader.load();
 | 
| +}
 | 
| +
 | 
| +/// A helper for [load] that tracks the YAML document.
 | 
| +class _ConfigurationLoader {
 | 
| +  /// The parsed configuration document.
 | 
| +  final YamlMap _document;
 | 
| +
 | 
| +  /// The source string for [_document].
 | 
| +  ///
 | 
| +  /// Used for error reporting.
 | 
| +  final String _source;
 | 
| +
 | 
| +  _ConfigurationLoader(this._document, this._source);
 | 
| +
 | 
| +  /// Loads the configuration in [_document].
 | 
| +  Configuration load() {
 | 
| +    var verboseTrace = _getBool("verbose_trace");
 | 
| +    var jsTrace = _getBool("js_trace");
 | 
| +
 | 
| +    var reporter = _getString("reporter");
 | 
| +    if (reporter != null && !allReporters.contains(reporter)) {
 | 
| +      _error('Unknown reporter "$reporter".', "reporter");
 | 
| +    }
 | 
| +
 | 
| +    var pubServePort = _getInt("pub_serve");
 | 
| +    var concurrency = _getInt("concurrency");
 | 
| +    var timeout = _parseValue("timeout", (value) => new Timeout.parse(value));
 | 
| +
 | 
| +    var allPlatformIdentifiers =
 | 
| +        TestPlatform.all.map((platform) => platform.identifier).toSet();
 | 
| +    var platforms = _getList("platforms", (platformNode) {
 | 
| +      _validate(platformNode, "Platforms must be strings.",
 | 
| +          (value) => value is String);
 | 
| +      _validate(platformNode, 'Unknown platform "${platformNode.value}".',
 | 
| +          allPlatformIdentifiers.contains);
 | 
| +
 | 
| +      return TestPlatform.find(platformNode.value);
 | 
| +    });
 | 
| +
 | 
| +    // TODO(nweiz): Add support for using globs to define defaults paths to run.
 | 
| +
 | 
| +    return new Configuration(
 | 
| +        verboseTrace: verboseTrace,
 | 
| +        jsTrace: jsTrace,
 | 
| +        reporter: reporter,
 | 
| +        pubServePort: pubServePort,
 | 
| +        concurrency: concurrency,
 | 
| +        timeout: timeout,
 | 
| +        platforms: platforms);
 | 
| +  }
 | 
| +
 | 
| +  /// Throws an exception with [message] if [test] returns `false` when passed
 | 
| +  /// [node]'s value.
 | 
| +  void _validate(YamlNode node, String message, bool test(value)) {
 | 
| +    if (test(node.value)) return;
 | 
| +    throw new SourceSpanFormatException(message, node.span, _source);
 | 
| +  }
 | 
| +
 | 
| +  /// Returns the value of the node at [field].
 | 
| +  ///
 | 
| +  /// If [typeTest] returns `false` for that value, instead throws an error
 | 
| +  /// complaining that the field is not a [typeName].
 | 
| +  _getValue(String field, String typeName, bool typeTest(value)) {
 | 
| +    var value = _document[field];
 | 
| +    if (value == null || typeTest(value)) return value;
 | 
| +    _error("$field must be ${a(typeName)}.", field);
 | 
| +  }
 | 
| +
 | 
| +  /// Returns the YAML node at [field].
 | 
| +  ///
 | 
| +  /// If [typeTest] returns `false` for that node's value, instead throws an
 | 
| +  /// error complaining that the field is not a [typeName].
 | 
| +  YamlNode _getNode(String field, String typeName, bool typeTest(value)) {
 | 
| +    var node = _document.nodes[field];
 | 
| +    if (node == null) return null;
 | 
| +    _validate(node, "$field must be ${a(typeName)}.", typeTest);
 | 
| +    return node;
 | 
| +  }
 | 
| +
 | 
| +  /// Asserts that [field] is an int and returns its value.
 | 
| +  int _getInt(String field) =>
 | 
| +      _getValue(field, "int", (value) => value is int);
 | 
| +
 | 
| +  /// Asserts that [field] is a boolean and returns its value.
 | 
| +  bool _getBool(String field) =>
 | 
| +      _getValue(field, "boolean", (value) => value is bool);
 | 
| +
 | 
| +  /// Asserts that [field] is a string and returns its value.
 | 
| +  String _getString(String field) =>
 | 
| +      _getValue(field, "string", (value) => value is String);
 | 
| +
 | 
| +  /// Asserts that [field] is a list and runs [forElement] for each element it
 | 
| +  /// contains.
 | 
| +  ///
 | 
| +  /// Returns a list of values returned by [forElement].
 | 
| +  List _getList(String field, forElement(YamlNode elementNode)) {
 | 
| +    var node = _getNode(field, "list", (value) => value is List);
 | 
| +    if (node == null) return [];
 | 
| +    return node.nodes.map(forElement).toList();
 | 
| +  }
 | 
| +
 | 
| +  /// Asserts that [field] is a string, passes it to [parse], and returns the
 | 
| +  /// result.
 | 
| +  ///
 | 
| +  /// If [parse] throws a [FormatException], it's wrapped to include [field]'s
 | 
| +  /// span.
 | 
| +  _parseValue(String field, parse(value)) {
 | 
| +    var value = _getString(field);
 | 
| +    if (value == null) return null;
 | 
| +
 | 
| +    try {
 | 
| +      return parse(value);
 | 
| +    } on FormatException catch (error) {
 | 
| +      _error('Invalid $field: ${error.message}', field);
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  /// Throws a [SourceSpanFormatException] with [message] about [field].
 | 
| +  void _error(String message, String field) {
 | 
| +    throw new SourceSpanFormatException(
 | 
| +        message, _document.nodes[field].span, _source);
 | 
| +  }
 | 
| +}
 | 
| 
 |