Index: lib/src/runner/configuration.dart |
diff --git a/lib/src/runner/configuration.dart b/lib/src/runner/configuration.dart |
index aaeb94fbba041502ab40d8b18736d2f41fe20a1e..b8e2ed5eac9aec66797e78620254cd965d6a9374 100644 |
--- a/lib/src/runner/configuration.dart |
+++ b/lib/src/runner/configuration.dart |
@@ -21,6 +21,12 @@ import 'configuration/values.dart'; |
/// A class that encapsulates the command-line configuration of the test runner. |
class Configuration { |
+ /// An empty configuration with only default values. |
+ /// |
+ /// Using this is slightly more efficient than manually constructing a new |
+ /// configuration with no arguments. |
+ static final empty = new Configuration._(); |
+ |
/// The usage string for the command-line arguments. |
static String get usage => args.usage; |
@@ -107,6 +113,14 @@ class Configuration { |
List<TestPlatform> get platforms => _platforms ?? [TestPlatform.vm]; |
final List<TestPlatform> _platforms; |
+ /// The set of presets to use. |
+ /// |
+ /// Any chosen presets for the parent configuration are added to the chosen |
+ /// preset sets for child configurations as well. |
+ /// |
+ /// Note that the order of this set matters. |
+ final Set<String> chosenPresets; |
+ |
/// Only run tests whose tags match this selector. |
/// |
/// When [merge]d, this is intersected with the other configuration's included |
@@ -142,17 +156,21 @@ class Configuration { |
forTag: mapMap(tags, value: (_, config) => config.metadata), |
onPlatform: mapMap(onPlatform, value: (_, config) => config.metadata)); |
- /// The set of tags that have been declaredin any way in this configuration. |
+ /// The set of tags that have been declared in any way in this configuration. |
Set<String> get knownTags { |
if (_knownTags != null) return _knownTags; |
var known = includeTags.variables.toSet() |
..addAll(excludeTags.variables) |
..addAll(addTags); |
- tags.forEach((selector, config) { |
+ |
+ for (var selector in tags.keys) { |
known.addAll(selector.variables); |
- known.addAll(config.knownTags); |
- }); |
+ } |
+ |
+ for (var configuration in _children) { |
+ known.addAll(configuration.knownTags); |
+ } |
_knownTags = new UnmodifiableSetView(known); |
return _knownTags; |
@@ -166,6 +184,40 @@ class Configuration { |
/// configuration fields, but that isn't enforced. |
final Map<PlatformSelector, Configuration> onPlatform; |
+ /// Configuration presets. |
+ /// |
+ /// These are configurations that can be explicitly selected by the user via |
+ /// the command line. Preset configuration takes precedence over the base |
+ /// configuration. |
+ /// |
+ /// This is guaranteed not to have any keys that match [chosenPresets]; those |
+ /// are resolved when the configuration is constructed. |
+ final Map<String, Configuration> presets; |
+ |
+ /// All preset names that are known to be valid. |
+ /// |
+ /// This includes presets that have already been resolved. |
+ Set<String> get knownPresets { |
+ if (_knownPresets != null) return _knownPresets; |
+ |
+ var known = presets.keys.toSet(); |
+ for (var configuration in _children) { |
+ known.addAll(configuration.knownPresets); |
+ } |
+ |
+ _knownPresets = new UnmodifiableSetView(known); |
+ return _knownPresets; |
+ } |
+ Set<String> _knownPresets; |
+ |
+ /// All child configurations of [this] that may be selected under various |
+ /// circumstances. |
+ Iterable<Configuration> get _children sync* { |
+ yield* tags.values; |
+ yield* onPlatform.values; |
+ yield* presets.values; |
+ } |
+ |
/// Parses the configuration from [args]. |
/// |
/// Throws a [FormatException] if [args] are invalid. |
@@ -177,7 +229,97 @@ class Configuration { |
/// a [FormatException] if its contents are invalid. |
factory Configuration.load(String path) => load(path); |
- Configuration({ |
+ factory Configuration({ |
+ bool help, |
+ bool version, |
+ bool verboseTrace, |
+ bool jsTrace, |
+ bool skip, |
+ String skipReason, |
+ PlatformSelector testOn, |
+ bool pauseAfterLoad, |
+ bool color, |
+ String packageRoot, |
+ String reporter, |
+ int pubServePort, |
+ int concurrency, |
+ Timeout timeout, |
+ Pattern pattern, |
+ Iterable<TestPlatform> platforms, |
+ Iterable<String> paths, |
+ Glob filename, |
+ Iterable<String> chosenPresets, |
+ BooleanSelector includeTags, |
+ BooleanSelector excludeTags, |
+ Iterable addTags, |
+ Map<BooleanSelector, Configuration> tags, |
+ Map<PlatformSelector, Configuration> onPlatform, |
+ Map<String, Configuration> presets}) { |
+ _unresolved() => new Configuration._( |
+ help: help, |
+ version: version, |
+ verboseTrace: verboseTrace, |
+ jsTrace: jsTrace, |
+ skip: skip, |
+ skipReason: skipReason, |
+ testOn: testOn, |
+ pauseAfterLoad: pauseAfterLoad, |
+ color: color, |
+ packageRoot: packageRoot, |
+ reporter: reporter, |
+ pubServePort: pubServePort, |
+ concurrency: concurrency, |
+ timeout: timeout, |
+ pattern: pattern, |
+ platforms: platforms, |
+ paths: paths, |
+ filename: filename, |
+ chosenPresets: chosenPresets, |
+ includeTags: includeTags, |
+ excludeTags: excludeTags, |
+ addTags: addTags, |
+ |
+ // Make sure we pass [chosenPresets] to the child configurations as |
+ // well. This ensures that |
+ tags: _withChosenPresets(tags, chosenPresets), |
+ onPlatform: _withChosenPresets(onPlatform, chosenPresets), |
+ presets: _withChosenPresets(presets, chosenPresets)); |
+ |
+ if (chosenPresets == null) return _unresolved(); |
+ chosenPresets = new Set.from(chosenPresets); |
+ |
+ if (presets == null) return _unresolved(); |
+ presets = new Map.from(presets); |
+ |
+ var knownPresets = presets.keys.toSet(); |
+ |
+ var merged = chosenPresets.fold(Configuration.empty, (merged, preset) { |
+ if (!presets.containsKey(preset)) return merged; |
+ return merged.merge(presets.remove(preset)); |
+ }); |
+ |
+ var result = merged == Configuration.empty |
+ ? _unresolved() |
+ : _unresolved().merge(merged); |
+ |
+ // Make sure the configuration knows about presets that were selected and |
+ // thus removed from [presets]. |
+ result._knownPresets = result.knownPresets.union(knownPresets); |
+ |
+ return result; |
+ } |
+ |
+ static Map<Object, Configuration> _withChosenPresets( |
+ Map<Object, Configuration> map, Set<String> chosenPresets) { |
+ if (map == null || chosenPresets == null) return map; |
+ return mapMap(map, value: (_, config) => config.change( |
+ chosenPresets: config.chosenPresets.union(chosenPresets))); |
+ } |
+ |
+ /// Creates new Configuration. |
+ /// |
+ /// Unlike [new Configuration], this assumes [presets] is already resolved. |
+ Configuration._({ |
bool help, |
bool version, |
bool verboseTrace, |
@@ -196,11 +338,13 @@ class Configuration { |
Iterable<TestPlatform> platforms, |
Iterable<String> paths, |
Glob filename, |
+ Iterable<String> chosenPresets, |
BooleanSelector includeTags, |
BooleanSelector excludeTags, |
Iterable addTags, |
Map<BooleanSelector, Configuration> tags, |
- Map<PlatformSelector, Configuration> onPlatform}) |
+ Map<PlatformSelector, Configuration> onPlatform, |
+ Map<String, Configuration> presets}) |
: _help = help, |
_version = version, |
_verboseTrace = verboseTrace, |
@@ -221,13 +365,13 @@ class Configuration { |
_platforms = _list(platforms), |
_paths = _list(paths), |
_filename = filename, |
+ chosenPresets = new Set.from(chosenPresets ?? []), |
includeTags = includeTags ?? BooleanSelector.all, |
excludeTags = excludeTags ?? BooleanSelector.none, |
- addTags = addTags?.toSet() ?? new Set(), |
- tags = tags == null ? const {} : new Map.unmodifiable(tags), |
- onPlatform = onPlatform == null |
- ? const {} |
- : new Map.unmodifiable(onPlatform) { |
+ addTags = new UnmodifiableSetView(addTags?.toSet() ?? new Set()), |
+ tags = _map(tags), |
+ onPlatform = _map(onPlatform), |
+ presets = _map(presets) { |
if (_filename != null && _filename.context.style != p.style) { |
throw new ArgumentError( |
"filename's context must match the current operating system, was " |
@@ -235,24 +379,33 @@ class Configuration { |
} |
} |
- /// Returns a [input] as a list or `null`. |
+ /// Returns a [input] as an unmodifiable list or `null`. |
/// |
/// If [input] is `null` or empty, this returns `null`. Otherwise, it returns |
/// `input.toList()`. |
static List _list(Iterable input) { |
if (input == null) return null; |
- input = input.toList(); |
+ input = new List.unmodifiable(input); |
if (input.isEmpty) return null; |
return input; |
} |
+ /// Returns an modifiable copy of [input] or an empty unmodifiable map. |
+ static Map _map(Map input) { |
+ if (input == null) return const {}; |
+ return new Map.unmodifiable(input); |
+ } |
+ |
/// Merges this with [other]. |
/// |
/// For most fields, if both configurations have values set, [other]'s value |
/// takes precedence. However, certain fields are merged together instead. |
/// This is indicated in those fields' documentation. |
Configuration merge(Configuration other) { |
- return new Configuration( |
+ if (this == Configuration.empty) return other; |
+ if (other == Configuration.empty) return this; |
+ |
+ var result = new Configuration( |
help: other._help ?? _help, |
version: other._version ?? _version, |
verboseTrace: other._verboseTrace ?? _verboseTrace, |
@@ -271,12 +424,84 @@ class Configuration { |
platforms: other._platforms ?? _platforms, |
paths: other._paths ?? _paths, |
filename: other._filename ?? _filename, |
+ chosenPresets: chosenPresets.union(other.chosenPresets), |
includeTags: includeTags.intersection(other.includeTags), |
excludeTags: excludeTags.union(other.excludeTags), |
addTags: other.addTags.union(addTags), |
- tags: mergeMaps(tags, other.tags, |
- value: (config1, config2) => config1.merge(config2)), |
- onPlatform: mergeMaps(onPlatform, other.onPlatform, |
- value: (config1, config2) => config1.merge(config2))); |
+ tags: _mergeConfigMaps(tags, other.tags), |
+ onPlatform: _mergeConfigMaps(onPlatform, other.onPlatform), |
+ presets: _mergeConfigMaps(presets, other.presets)); |
+ |
+ // Make sure the merged config preserves any presets that were chosen and |
+ // discarded. |
+ result._knownPresets = knownPresets.union(other.knownPresets); |
+ return result; |
} |
+ |
+ /// Returns a copy of this configuration with the given fields updated. |
+ /// |
+ /// Note that unlike [merge], this has no merging behavior—the old value is |
+ /// always replaced by the new one. |
+ Configuration change({ |
+ bool help, |
+ bool version, |
+ bool verboseTrace, |
+ bool jsTrace, |
+ bool skip, |
+ String skipReason, |
+ PlatformSelector testOn, |
+ bool pauseAfterLoad, |
+ bool color, |
+ String packageRoot, |
+ String reporter, |
+ int pubServePort, |
+ int concurrency, |
+ Timeout timeout, |
+ Pattern pattern, |
+ Iterable<TestPlatform> platforms, |
+ Iterable<String> paths, |
+ Glob filename, |
+ Iterable<String> chosenPresets, |
+ BooleanSelector includeTags, |
+ BooleanSelector excludeTags, |
+ Iterable addTags, |
+ Map<BooleanSelector, Configuration> tags, |
+ Map<PlatformSelector, Configuration> onPlatform, |
+ Map<String, Configuration> presets}) { |
+ return new Configuration( |
+ help: help ?? _help, |
+ version: version ?? _version, |
+ verboseTrace: verboseTrace ?? _verboseTrace, |
+ jsTrace: jsTrace ?? _jsTrace, |
+ skip: skip ?? _skip, |
+ skipReason: skipReason ?? this.skipReason, |
+ testOn: testOn ?? this.testOn, |
+ pauseAfterLoad: pauseAfterLoad ?? _pauseAfterLoad, |
+ color: color ?? _color, |
+ packageRoot: packageRoot ?? _packageRoot, |
+ reporter: reporter ?? _reporter, |
+ pubServePort: pubServePort ?? pubServeUrl?.port, |
+ concurrency: concurrency ?? _concurrency, |
+ timeout: timeout ?? this.timeout, |
+ pattern: pattern ?? this.pattern, |
+ platforms: platforms ?? _platforms, |
+ paths: paths ?? _paths, |
+ filename: filename ?? _filename, |
+ chosenPresets: chosenPresets ?? this.chosenPresets, |
+ includeTags: includeTags ?? this.includeTags, |
+ excludeTags: excludeTags ?? this.excludeTags, |
+ addTags: addTags ?? this.addTags, |
+ tags: tags ?? this.tags, |
+ onPlatform: onPlatform ?? this.onPlatform, |
+ presets: presets ?? this.presets); |
+ } |
+ |
+ /// Merges two maps whose values are [Configuration]s. |
+ /// |
+ /// Any overlapping keys in the maps have their configurations merged in the |
+ /// returned map. |
+ Map<Object, Configuration> _mergeConfigMaps(Map<Object, Configuration> map1, |
+ Map<Object, Configuration> map2) => |
+ mergeMaps(map1, map2, |
+ value: (config1, config2) => config1.merge(config2)); |
} |