blob: d0eed3db0e7d729cd8c4dc9bc60dc6b6f6eee28a [file] [log] [blame]
// Copyright (c) 2019, 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.
/// This library defines how to read a test specification from a Yaml
/// file. We expect specifications written in this format:
///
/// dependencies:
/// b: a
/// main: [b, expect]
/// flags:
/// - constant-update-2018
///
/// Where the dependencies section describe how modules depend on one another,
/// and the flags section show what flags are needed to run that specific test.
///
/// When defining dependencies:
/// - Each name corresponds to a module.
/// - Module names correlate to either a file, a folder, or a package.
/// - A map entry contains all the dependencies of a module, if any.
/// - If a module has a single dependency, it can be written as a single
/// value.
///
/// The logic in this library mostly treats these names as strings, separately
/// `loader.dart` is responsible for validating and attaching this dependency
/// information to a set of module definitions.
///
/// The framework is agnostic of what the flags are, but at this time we only
/// use the name of experimental language features. These are then used to
/// decide what options to pass to the tools that compile and run the tests.
import 'package:yaml/yaml.dart';
/// Parses [contents] containing a module dependencies specification written in
/// yaml, and returns a [TestSpecification].
TestSpecification parseTestSpecification(String contents) {
var spec = loadYaml(contents);
if (spec is! YamlMap) {
return _invalidSpecification("spec is not a map");
}
var dependencies = spec['dependencies'];
if (dependencies == null) {
return _invalidSpecification("no dependencies section");
}
if (dependencies is! YamlMap) {
return _invalidSpecification("dependencies is not a map");
}
Map<String, List<String>> normalizedMap = {};
dependencies.forEach((key, value) {
if (key is! String) {
_invalidSpecification("key: '$key' is not a string");
}
normalizedMap[key] = [];
if (value is String) {
normalizedMap[key].add(value);
} else if (value is List) {
value.forEach((entry) {
if (entry is! String) {
_invalidSpecification("entry: '$entry' is not a string");
}
normalizedMap[key].add(entry);
});
} else {
_invalidSpecification(
"entry: '$value' is not a string or a list of strings");
}
});
List<String> normalizedFlags = [];
dynamic flags = spec['flags'];
if (flags is String) {
normalizedFlags.add(flags);
} else if (flags is List) {
normalizedFlags.addAll(flags.cast<String>());
} else if (flags != null) {
_invalidSpecification(
"flags: '$flags' expected to be string or list of strings");
}
return new TestSpecification(normalizedFlags, normalizedMap);
}
/// Data specifying details about a modular test including dependencies and
/// flags that are necessary in order to properly run a test.
///
class TestSpecification {
/// Set of flags necessary to properly run a test.
///
/// Usually this contains flags enabling language experiments.
final List<String> flags;
/// Dependencies of the modules that are expected to exist on the test.
///
/// Note: some values in the map may not have a corresponding key. That may be
/// the case for modules that have no dependencies and modules that are not
/// specified explicitly because they are added automatically by the framework
/// (for instance, the module of `package:expect` or the sdk itself).
final Map<String, List<String>> dependencies;
TestSpecification(this.flags, this.dependencies);
}
_invalidSpecification(String message) {
throw new InvalidSpecificationError(message);
}
class InvalidSpecificationError extends Error {
final String message;
InvalidSpecificationError(this.message);
String toString() => "Invalid specification: $message";
}