Line data Source code
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
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : import 'package:boolean_selector/boolean_selector.dart';
6 : import 'package:collection/collection.dart';
7 :
8 : import '../../backend/metadata.dart';
9 : import '../../backend/operating_system.dart';
10 : import '../../backend/platform_selector.dart';
11 : import '../../backend/test_platform.dart';
12 : import '../../frontend/timeout.dart';
13 :
14 : /// Suite-level configuration.
15 : ///
16 : /// This tracks configuration that can differ from suite to suite.
17 : class SuiteConfiguration {
18 : /// Empty configuration with only default values.
19 : ///
20 : /// Using this is slightly more efficient than manually constructing a new
21 : /// configuration with no arguments.
22 : static final empty = new SuiteConfiguration._();
23 :
24 : /// Whether JavaScript stack traces should be left as-is or converted to
25 : /// Dart-like traces.
26 0 : bool get jsTrace => _jsTrace ?? false;
27 : final bool _jsTrace;
28 :
29 : /// Whether skipped tests should be run.
30 5 : bool get runSkipped => _runSkipped ?? false;
31 : final bool _runSkipped;
32 :
33 : /// The path to a mirror of this package containing HTML that points to
34 : /// precompiled JS.
35 : ///
36 : /// This is used by the internal Google test runner so that test compilation
37 : /// can more effectively make use of Google's build tools.
38 : final String precompiledPath;
39 :
40 : /// Additional arguments to pass to dart2js.
41 : ///
42 : /// Note that this if multiple suites run the same JavaScript on different
43 : /// platforms, and they have different [dart2jsArgs], only one (undefined)
44 : /// suite's arguments will be used.
45 : final List<String> dart2jsArgs;
46 :
47 : /// The patterns to match against test names to decide which to run, or `null`
48 : /// if all tests should be run.
49 : ///
50 : /// All patterns must match in order for a test to be run.
51 : final Set<Pattern> patterns;
52 :
53 : /// The set of platforms on which to run tests.
54 0 : List<TestPlatform> get platforms => _platforms ?? const [TestPlatform.vm];
55 : final List<TestPlatform> _platforms;
56 :
57 : /// Only run tests whose tags match this selector.
58 : ///
59 : /// When [merge]d, this is intersected with the other configuration's included
60 : /// tags.
61 : final BooleanSelector includeTags;
62 :
63 : /// Do not run tests whose tags match this selector.
64 : ///
65 : /// When [merge]d, this is unioned with the other configuration's
66 : /// excluded tags.
67 : final BooleanSelector excludeTags;
68 :
69 : /// Configuration for particular tags.
70 : ///
71 : /// The keys are tag selectors, and the values are configurations for tests
72 : /// whose tags match those selectors.
73 : final Map<BooleanSelector, SuiteConfiguration> tags;
74 :
75 : /// Configuration for particular platforms.
76 : ///
77 : /// The keys are platform selectors, and the values are configurations for
78 : /// those platforms. These configuration should only contain test-level
79 : /// configuration fields, but that isn't enforced.
80 : final Map<PlatformSelector, SuiteConfiguration> onPlatform;
81 :
82 : /// The global test metadata derived from this configuration.
83 : Metadata get metadata {
84 0 : if (tags.isEmpty && onPlatform.isEmpty) return _metadata;
85 0 : return _metadata.change(
86 0 : forTag: mapMap(tags, value: (_, config) => config.metadata),
87 0 : onPlatform: mapMap(onPlatform, value: (_, config) => config.metadata));
88 : }
89 :
90 : final Metadata _metadata;
91 :
92 : /// The set of tags that have been declared in any way in this configuration.
93 : Set<String> get knownTags {
94 0 : if (_knownTags != null) return _knownTags;
95 :
96 0 : var known = includeTags.variables.toSet()
97 0 : ..addAll(excludeTags.variables)
98 0 : ..addAll(_metadata.tags);
99 :
100 0 : for (var selector in tags.keys) {
101 0 : known.addAll(selector.variables);
102 : }
103 :
104 0 : for (var configuration in _children) {
105 0 : known.addAll(configuration.knownTags);
106 : }
107 :
108 0 : _knownTags = new UnmodifiableSetView(known);
109 0 : return _knownTags;
110 : }
111 :
112 : Set<String> _knownTags;
113 :
114 : /// All child configurations of [this] that may be selected under various
115 : /// circumstances.
116 : Iterable<SuiteConfiguration> get _children sync* {
117 0 : yield* tags.values;
118 0 : yield* onPlatform.values;
119 : }
120 :
121 : factory SuiteConfiguration(
122 : {bool jsTrace,
123 : bool runSkipped,
124 : Iterable<String> dart2jsArgs,
125 : String precompiledPath,
126 : Iterable<Pattern> patterns,
127 : Iterable<TestPlatform> platforms,
128 : BooleanSelector includeTags,
129 : BooleanSelector excludeTags,
130 : Map<BooleanSelector, SuiteConfiguration> tags,
131 : Map<PlatformSelector, SuiteConfiguration> onPlatform,
132 :
133 : // Test-level configuration
134 : Timeout timeout,
135 : bool verboseTrace,
136 : bool chainStackTraces,
137 : bool skip,
138 : int retry,
139 : String skipReason,
140 : PlatformSelector testOn,
141 : Iterable<String> addTags}) {
142 0 : var config = new SuiteConfiguration._(
143 : jsTrace: jsTrace,
144 : runSkipped: runSkipped,
145 : dart2jsArgs: dart2jsArgs,
146 : precompiledPath: precompiledPath,
147 : patterns: patterns,
148 : platforms: platforms,
149 : includeTags: includeTags,
150 : excludeTags: excludeTags,
151 : tags: tags,
152 : onPlatform: onPlatform,
153 0 : metadata: new Metadata(
154 : timeout: timeout,
155 : verboseTrace: verboseTrace,
156 : chainStackTraces: chainStackTraces,
157 : skip: skip,
158 : retry: retry,
159 : skipReason: skipReason,
160 : testOn: testOn,
161 : tags: addTags));
162 0 : return config._resolveTags();
163 : }
164 :
165 : /// Creates new SuiteConfiguration.
166 : ///
167 : /// Unlike [new SuiteConfiguration], this assumes [tags] is already
168 : /// resolved.
169 : SuiteConfiguration._(
170 : {bool jsTrace,
171 : bool runSkipped,
172 : Iterable<String> dart2jsArgs,
173 : this.precompiledPath,
174 : Iterable<Pattern> patterns,
175 : Iterable<TestPlatform> platforms,
176 : BooleanSelector includeTags,
177 : BooleanSelector excludeTags,
178 : Map<BooleanSelector, SuiteConfiguration> tags,
179 : Map<PlatformSelector, SuiteConfiguration> onPlatform,
180 : Metadata metadata})
181 : : _jsTrace = jsTrace,
182 : _runSkipped = runSkipped,
183 5 : dart2jsArgs = _list(dart2jsArgs) ?? const [],
184 10 : patterns = new UnmodifiableSetView(patterns?.toSet() ?? new Set()),
185 5 : _platforms = _list(platforms),
186 : includeTags = includeTags ?? BooleanSelector.all,
187 : excludeTags = excludeTags ?? BooleanSelector.none,
188 5 : tags = _map(tags),
189 5 : onPlatform = _map(onPlatform),
190 10 : _metadata = metadata ?? Metadata.empty;
191 :
192 : /// Creates a new [SuiteConfiguration] that takes its configuration from
193 : /// [metadata].
194 : factory SuiteConfiguration.fromMetadata(Metadata metadata) =>
195 0 : new SuiteConfiguration._(
196 0 : tags: mapMap(metadata.forTag,
197 0 : value: (_, child) => new SuiteConfiguration.fromMetadata(child)),
198 0 : onPlatform: mapMap(metadata.onPlatform,
199 0 : value: (_, child) => new SuiteConfiguration.fromMetadata(child)),
200 0 : metadata: metadata.change(forTag: {}, onPlatform: {}));
201 :
202 : /// Returns an unmodifiable copy of [input].
203 : ///
204 : /// If [input] is `null` or empty, this returns `null`.
205 : static List<T> _list<T>(Iterable<T> input) {
206 : if (input == null) return null;
207 0 : var list = new List<T>.unmodifiable(input);
208 0 : if (list.isEmpty) return null;
209 : return list;
210 : }
211 :
212 : /// Returns an unmodifiable copy of [input] or an empty unmodifiable map.
213 : static Map<K, V> _map<K, V>(Map<K, V> input) {
214 0 : if (input == null || input.isEmpty) return const {};
215 0 : return new Map.unmodifiable(input);
216 : }
217 :
218 : /// Merges this with [other].
219 : ///
220 : /// For most fields, if both configurations have values set, [other]'s value
221 : /// takes precedence. However, certain fields are merged together instead.
222 : /// This is indicated in those fields' documentation.
223 : SuiteConfiguration merge(SuiteConfiguration other) {
224 0 : if (this == SuiteConfiguration.empty) return other;
225 0 : if (other == SuiteConfiguration.empty) return this;
226 :
227 0 : var config = new SuiteConfiguration._(
228 0 : jsTrace: other._jsTrace ?? _jsTrace,
229 0 : runSkipped: other._runSkipped ?? _runSkipped,
230 0 : dart2jsArgs: dart2jsArgs.toList()..addAll(other.dart2jsArgs),
231 0 : precompiledPath: other.precompiledPath ?? precompiledPath,
232 0 : patterns: patterns.union(other.patterns),
233 0 : platforms: other._platforms ?? _platforms,
234 0 : includeTags: includeTags.intersection(other.includeTags),
235 0 : excludeTags: excludeTags.union(other.excludeTags),
236 0 : tags: _mergeConfigMaps(tags, other.tags),
237 0 : onPlatform: _mergeConfigMaps(onPlatform, other.onPlatform),
238 0 : metadata: metadata.merge(other.metadata));
239 0 : return config._resolveTags();
240 : }
241 :
242 : /// Returns a copy of this configuration with the given fields updated.
243 : ///
244 : /// Note that unlike [merge], this has no merging behavior—the old value is
245 : /// always replaced by the new one.
246 : SuiteConfiguration change(
247 : {bool jsTrace,
248 : bool runSkipped,
249 : Iterable<String> dart2jsArgs,
250 : String precompiledPath,
251 : Iterable<Pattern> patterns,
252 : Iterable<TestPlatform> platforms,
253 : BooleanSelector includeTags,
254 : BooleanSelector excludeTags,
255 : Map<BooleanSelector, SuiteConfiguration> tags,
256 : Map<PlatformSelector, SuiteConfiguration> onPlatform,
257 :
258 : // Test-level configuration
259 : Timeout timeout,
260 : bool verboseTrace,
261 : bool chainStackTraces,
262 : bool skip,
263 : int retry,
264 : String skipReason,
265 : PlatformSelector testOn,
266 : Iterable<String> addTags}) {
267 0 : var config = new SuiteConfiguration._(
268 0 : jsTrace: jsTrace ?? _jsTrace,
269 0 : runSkipped: runSkipped ?? _runSkipped,
270 0 : dart2jsArgs: dart2jsArgs?.toList() ?? this.dart2jsArgs,
271 0 : precompiledPath: precompiledPath ?? this.precompiledPath,
272 0 : patterns: patterns ?? this.patterns,
273 0 : platforms: platforms ?? _platforms,
274 0 : includeTags: includeTags ?? this.includeTags,
275 0 : excludeTags: excludeTags ?? this.excludeTags,
276 0 : tags: tags ?? this.tags,
277 0 : onPlatform: onPlatform ?? this.onPlatform,
278 0 : metadata: _metadata.change(
279 : timeout: timeout,
280 : verboseTrace: verboseTrace,
281 : chainStackTraces: chainStackTraces,
282 : skip: skip,
283 : retry: retry,
284 : skipReason: skipReason,
285 : testOn: testOn,
286 : tags: addTags));
287 0 : return config._resolveTags();
288 : }
289 :
290 : /// Returns a copy of [this] with all platform-specific configuration from
291 : /// [onPlatform] resolved.
292 : SuiteConfiguration forPlatform(TestPlatform platform, {OperatingSystem os}) {
293 0 : if (onPlatform.isEmpty) return this;
294 :
295 : var config = this;
296 0 : onPlatform.forEach((platformSelector, platformConfig) {
297 0 : if (!platformSelector.evaluate(platform, os: os)) return;
298 0 : config = config.merge(platformConfig);
299 : });
300 0 : return config.change(onPlatform: {});
301 : }
302 :
303 : /// Merges two maps whose values are [SuiteConfiguration]s.
304 : ///
305 : /// Any overlapping keys in the maps have their configurations merged in the
306 : /// returned map.
307 : Map<Object, SuiteConfiguration> _mergeConfigMaps(
308 : Map<Object, SuiteConfiguration> map1,
309 : Map<Object, SuiteConfiguration> map2) =>
310 0 : mergeMaps(map1, map2,
311 0 : value: (config1, config2) => config1.merge(config2));
312 :
313 : SuiteConfiguration _resolveTags() {
314 : // If there's no tag-specific configuration, or if none of it applies, just
315 : // return the configuration as-is.
316 0 : if (_metadata.tags.isEmpty || tags.isEmpty) return this;
317 :
318 : // Otherwise, resolve the tag-specific components.
319 0 : var newTags = new Map<BooleanSelector, SuiteConfiguration>.from(tags);
320 0 : var merged = tags.keys.fold(empty, (merged, selector) {
321 0 : if (!selector.evaluate(_metadata.tags)) return merged;
322 0 : return merged.merge(newTags.remove(selector));
323 : });
324 :
325 0 : if (merged == empty) return this;
326 0 : return this.change(tags: newTags).merge(merged);
327 : }
328 : }
|