OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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:collection'; | 5 import 'dart:collection'; |
6 | 6 |
7 import 'package:collection/collection.dart'; | 7 import 'package:collection/collection.dart'; |
8 | 8 |
9 import '../frontend/skip.dart'; | 9 import '../frontend/skip.dart'; |
10 import '../frontend/timeout.dart'; | 10 import '../frontend/timeout.dart'; |
(...skipping 24 matching lines...) Expand all Loading... | |
35 | 35 |
36 /// The user-defined tags attached to the test or suite. | 36 /// The user-defined tags attached to the test or suite. |
37 final Set<String> tags; | 37 final Set<String> tags; |
38 | 38 |
39 /// Platform-specific metadata. | 39 /// Platform-specific metadata. |
40 /// | 40 /// |
41 /// Each key identifies a platform, and its value identifies the specific | 41 /// Each key identifies a platform, and its value identifies the specific |
42 /// metadata for that platform. These can be applied by calling [forPlatform]. | 42 /// metadata for that platform. These can be applied by calling [forPlatform]. |
43 final Map<PlatformSelector, Metadata> onPlatform; | 43 final Map<PlatformSelector, Metadata> onPlatform; |
44 | 44 |
45 /// Metadata that applies only when specific tags are applied. | |
46 /// | |
47 /// Tag-specific metadata is applied when merging this with other metadata. | |
48 /// Note that unlike [onPlatform], the base metadata takes precedence over any | |
49 /// tag-specific metadata. | |
50 /// | |
51 /// This is guaranteed not to have any keys that appear in [tags]; those are | |
52 /// resolved when the metadata is constructed. | |
53 final Map<String, Metadata> forTag; | |
54 | |
45 /// Parses a user-provided map into the value for [onPlatform]. | 55 /// Parses a user-provided map into the value for [onPlatform]. |
46 static Map<PlatformSelector, Metadata> _parseOnPlatform( | 56 static Map<PlatformSelector, Metadata> _parseOnPlatform( |
47 Map<String, dynamic> onPlatform) { | 57 Map<String, dynamic> onPlatform) { |
48 if (onPlatform == null) return {}; | 58 if (onPlatform == null) return {}; |
49 | 59 |
50 var result = {}; | 60 var result = {}; |
51 onPlatform.forEach((platform, metadata) { | 61 onPlatform.forEach((platform, metadata) { |
52 if (metadata is Timeout || metadata is Skip) { | 62 if (metadata is Timeout || metadata is Skip) { |
53 metadata = [metadata]; | 63 metadata = [metadata]; |
54 } else if (metadata is! List) { | 64 } else if (metadata is! List) { |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
100 if (tags.any((tag) => tag is! String)) { | 110 if (tags.any((tag) => tag is! String)) { |
101 throw new ArgumentError.value(tags, "tags", "must contain only Strings."); | 111 throw new ArgumentError.value(tags, "tags", "must contain only Strings."); |
102 } | 112 } |
103 | 113 |
104 return new Set.from(tags); | 114 return new Set.from(tags); |
105 } | 115 } |
106 | 116 |
107 /// Creates new Metadata. | 117 /// Creates new Metadata. |
108 /// | 118 /// |
109 /// [testOn] defaults to [PlatformSelector.all]. | 119 /// [testOn] defaults to [PlatformSelector.all]. |
110 Metadata({PlatformSelector testOn, Timeout timeout, bool skip: false, | 120 /// |
111 this.verboseTrace: false, this.skipReason, | 121 /// If [forTag] contains metadata for any tags in [tags], that metadata is |
112 Map<PlatformSelector, Metadata> onPlatform, Iterable<String> tags}) | 122 /// included inline in the returned value. The values directly passed to the |
123 /// constructor take precedence over tag-specific metadata. | |
124 factory Metadata({PlatformSelector testOn, Timeout timeout, bool skip: false, | |
125 bool verboseTrace: false, String skipReason, Iterable<String> tags, | |
126 Map<PlatformSelector, Metadata> onPlatform, | |
127 Map<String, Metadata> forTag}) { | |
128 // If there's no tag-specific metadata, or if none of it applies, just | |
129 // return the metadata as-is. | |
130 if (forTag == null || tags == null || !tags.any(forTag.containsKey)) { | |
131 return new Metadata._( | |
132 testOn: testOn, | |
133 timeout: timeout, | |
134 skip: skip, | |
135 verboseTrace: verboseTrace, | |
136 skipReason: skipReason, | |
137 tags: tags, | |
138 onPlatform: onPlatform, | |
139 forTag: forTag); | |
140 } | |
141 | |
142 // Otherwise, resolve the tag-specific components. Doing this eagerly means | |
143 // we only have to resolve suite- or group-level tags once, rather than | |
144 // doing it for every test individually. | |
145 forTag = new Map.from(forTag); | |
146 var merged = tags.fold(new Metadata._(), (merged, tag) { | |
147 var tagMetadata = forTag.remove(tag); | |
148 return tagMetadata == null ? merged : merged.merge(tagMetadata); | |
149 }); | |
150 | |
151 return merged.merge(new Metadata._( | |
152 testOn: testOn, | |
153 timeout: timeout, | |
154 skip: skip, | |
155 verboseTrace: verboseTrace, | |
156 skipReason: skipReason, | |
157 tags: tags, | |
158 onPlatform: onPlatform, | |
159 forTag: forTag)); | |
160 } | |
161 | |
162 /// Creates new Metadata. | |
163 /// | |
164 /// Unlike [new Metadata], this assumes [forTag] is already resolved. | |
165 Metadata._({PlatformSelector testOn, Timeout timeout, bool skip: false, | |
166 this.verboseTrace: false, this.skipReason, Iterable<String> tags, | |
167 Map<PlatformSelector, Metadata> onPlatform, | |
168 Map<String, Metadata> forTag}) | |
113 : testOn = testOn == null ? PlatformSelector.all : testOn, | 169 : testOn = testOn == null ? PlatformSelector.all : testOn, |
114 timeout = timeout == null ? const Timeout.factor(1) : timeout, | 170 timeout = timeout == null ? const Timeout.factor(1) : timeout, |
115 skip = skip, | 171 skip = skip, |
172 tags = tags == null | |
173 ? new Set() | |
174 : new UnmodifiableSetView(tags.toSet()), | |
kevmoo
2016/02/12 18:29:30
put the null check inside this ctor – so even the
nweiz
2016/02/16 23:36:31
Done.
| |
116 onPlatform = onPlatform == null | 175 onPlatform = onPlatform == null |
117 ? const {} | 176 ? const {} |
118 : new UnmodifiableMapView(onPlatform), | 177 : new UnmodifiableMapView(onPlatform), |
119 tags = tags == null | 178 forTag = forTag == null |
120 ? new Set() | 179 ? const {} |
121 : new UnmodifiableSetView(tags.toSet()) { | 180 : new UnmodifiableMapView(forTag) { |
122 _validateTags(); | 181 _validateTags(); |
123 } | 182 } |
124 | 183 |
125 /// Creates a new Metadata, but with fields parsed from caller-friendly values | 184 /// Creates a new Metadata, but with fields parsed from caller-friendly values |
126 /// where applicable. | 185 /// where applicable. |
127 /// | 186 /// |
128 /// Throws a [FormatException] if any field is invalid. | 187 /// Throws a [FormatException] if any field is invalid. |
129 Metadata.parse({String testOn, Timeout timeout, skip, | 188 Metadata.parse({String testOn, Timeout timeout, skip, |
130 this.verboseTrace: false, Map<String, dynamic> onPlatform, | 189 this.verboseTrace: false, Map<String, dynamic> onPlatform, |
131 tags}) | 190 tags}) |
132 : testOn = testOn == null | 191 : testOn = testOn == null |
133 ? PlatformSelector.all | 192 ? PlatformSelector.all |
134 : new PlatformSelector.parse(testOn), | 193 : new PlatformSelector.parse(testOn), |
135 timeout = timeout == null ? const Timeout.factor(1) : timeout, | 194 timeout = timeout == null ? const Timeout.factor(1) : timeout, |
136 skip = skip != null && skip != false, | 195 skip = skip != null && skip != false, |
137 skipReason = skip is String ? skip : null, | 196 skipReason = skip is String ? skip : null, |
138 onPlatform = _parseOnPlatform(onPlatform), | 197 onPlatform = _parseOnPlatform(onPlatform), |
139 tags = _parseTags(tags) { | 198 tags = _parseTags(tags), |
199 forTag = const {} { | |
140 if (skip != null && skip is! String && skip is! bool) { | 200 if (skip != null && skip is! String && skip is! bool) { |
141 throw new ArgumentError( | 201 throw new ArgumentError( |
142 '"skip" must be a String or a bool, was "$skip".'); | 202 '"skip" must be a String or a bool, was "$skip".'); |
143 } | 203 } |
144 | 204 |
145 _validateTags(); | 205 _validateTags(); |
146 } | 206 } |
147 | 207 |
148 /// Deserializes the result of [Metadata.serialize] into a new [Metadata]. | 208 /// Deserializes the result of [Metadata.serialize] into a new [Metadata]. |
149 Metadata.deserialize(serialized) | 209 Metadata.deserialize(serialized) |
150 : testOn = serialized['testOn'] == null | 210 : testOn = serialized['testOn'] == null |
151 ? PlatformSelector.all | 211 ? PlatformSelector.all |
152 : new PlatformSelector.parse(serialized['testOn']), | 212 : new PlatformSelector.parse(serialized['testOn']), |
153 timeout = _deserializeTimeout(serialized['timeout']), | 213 timeout = _deserializeTimeout(serialized['timeout']), |
154 skip = serialized['skip'], | 214 skip = serialized['skip'], |
155 skipReason = serialized['skipReason'], | 215 skipReason = serialized['skipReason'], |
156 verboseTrace = serialized['verboseTrace'], | 216 verboseTrace = serialized['verboseTrace'], |
157 tags = new Set.from(serialized['tags']), | 217 tags = new Set.from(serialized['tags']), |
158 onPlatform = new Map.fromIterable(serialized['onPlatform'], | 218 onPlatform = new Map.fromIterable(serialized['onPlatform'], |
159 key: (pair) => new PlatformSelector.parse(pair.first), | 219 key: (pair) => new PlatformSelector.parse(pair.first), |
160 value: (pair) => new Metadata.deserialize(pair.last)); | 220 value: (pair) => new Metadata.deserialize(pair.last)), |
221 forTag = mapMap(serialized['forTag'], | |
222 value: (_, nested) => new Metadata.deserialize(nested)); | |
161 | 223 |
162 /// Deserializes timeout from the format returned by [_serializeTimeout]. | 224 /// Deserializes timeout from the format returned by [_serializeTimeout]. |
163 static _deserializeTimeout(serialized) { | 225 static _deserializeTimeout(serialized) { |
164 if (serialized == 'none') return Timeout.none; | 226 if (serialized == 'none') return Timeout.none; |
165 var scaleFactor = serialized['scaleFactor']; | 227 var scaleFactor = serialized['scaleFactor']; |
166 if (scaleFactor != null) return new Timeout.factor(scaleFactor); | 228 if (scaleFactor != null) return new Timeout.factor(scaleFactor); |
167 return new Timeout( | 229 return new Timeout( |
168 new Duration(microseconds: serialized['duration'])); | 230 new Duration(microseconds: serialized['duration'])); |
169 } | 231 } |
170 | 232 |
171 /// Throws an [ArgumentError] if any tags in [tags] aren't hyphenated | 233 /// Throws an [ArgumentError] if any tags in [tags] aren't hyphenated |
172 /// identifiers. | 234 /// identifiers. |
173 void _validateTags() { | 235 void _validateTags() { |
174 var invalidTags = tags | 236 var invalidTags = tags |
175 .where((tag) => !tag.contains(anchoredHyphenatedIdentifier)) | 237 .where((tag) => !tag.contains(anchoredHyphenatedIdentifier)) |
176 .map((tag) => '"$tag"') | 238 .map((tag) => '"$tag"') |
177 .toList(); | 239 .toList(); |
178 | 240 |
179 if (invalidTags.isEmpty) return; | 241 if (invalidTags.isEmpty) return; |
180 | 242 |
181 throw new ArgumentError( | 243 throw new ArgumentError( |
182 "Invalid ${pluralize('tag', invalidTags.length)} " | 244 "Invalid ${pluralize('tag', invalidTags.length)} " |
183 "${toSentence(invalidTags)}. Tags must be (optionally hyphenated) " | 245 "${toSentence(invalidTags)}. Tags must be (optionally hyphenated) " |
184 "Dart identifiers."); | 246 "Dart identifiers."); |
185 } | 247 } |
186 | 248 |
187 /// Return a new [Metadata] that merges [this] with [other]. | 249 /// Return a new [Metadata] that merges [this] with [other]. |
188 /// | 250 /// |
189 /// If the two [Metadata]s have conflicting properties, [other] wins. | 251 /// If the two [Metadata]s have conflicting properties, [other] wins. If |
252 /// either has a [forTag] metadata for one of the other's tags, that metadata | |
253 /// is merged as well. | |
190 Metadata merge(Metadata other) => | 254 Metadata merge(Metadata other) => |
191 new Metadata( | 255 new Metadata( |
192 testOn: testOn.intersect(other.testOn), | 256 testOn: testOn.intersect(other.testOn), |
193 timeout: timeout.merge(other.timeout), | 257 timeout: timeout.merge(other.timeout), |
194 skip: skip || other.skip, | 258 skip: skip || other.skip, |
259 skipReason: other.skipReason == null ? skipReason : other.skipReason, | |
195 verboseTrace: verboseTrace || other.verboseTrace, | 260 verboseTrace: verboseTrace || other.verboseTrace, |
196 skipReason: other.skipReason == null ? skipReason : other.skipReason, | 261 tags: tags.union(other.tags), |
197 onPlatform: mergeMaps(onPlatform, other.onPlatform), | 262 onPlatform: mergeMaps(onPlatform, other.onPlatform, |
198 tags: tags.union(other.tags)); | 263 value: (metadata1, metadata2) => metadata1.merge(metadata2)), |
264 forTag: mergeMaps(forTag, other.forTag, | |
265 value: (metadata1, metadata2) => metadata1.merge(metadata2))); | |
199 | 266 |
200 /// Returns a copy of [this] with the given fields changed. | 267 /// Returns a copy of [this] with the given fields changed. |
201 Metadata change({PlatformSelector testOn, Timeout timeout, bool skip, | 268 Metadata change({PlatformSelector testOn, Timeout timeout, bool skip, |
202 bool verboseTrace, String skipReason, | 269 bool verboseTrace, String skipReason, |
203 Map<PlatformSelector, Metadata> onPlatform}) { | 270 Map<PlatformSelector, Metadata> onPlatform}) { |
204 if (testOn == null) testOn = this.testOn; | 271 if (testOn == null) testOn = this.testOn; |
205 if (timeout == null) timeout = this.timeout; | 272 if (timeout == null) timeout = this.timeout; |
206 if (skip == null) skip = this.skip; | 273 if (skip == null) skip = this.skip; |
207 if (verboseTrace == null) verboseTrace = this.verboseTrace; | 274 if (verboseTrace == null) verboseTrace = this.verboseTrace; |
208 if (skipReason == null) skipReason = this.skipReason; | 275 if (skipReason == null) skipReason = this.skipReason; |
(...skipping 24 matching lines...) Expand all Loading... | |
233 onPlatform.forEach((key, value) { | 300 onPlatform.forEach((key, value) { |
234 serializedOnPlatform.add([key.toString(), value.serialize()]); | 301 serializedOnPlatform.add([key.toString(), value.serialize()]); |
235 }); | 302 }); |
236 | 303 |
237 return { | 304 return { |
238 'testOn': testOn == PlatformSelector.all ? null : testOn.toString(), | 305 'testOn': testOn == PlatformSelector.all ? null : testOn.toString(), |
239 'timeout': _serializeTimeout(timeout), | 306 'timeout': _serializeTimeout(timeout), |
240 'skip': skip, | 307 'skip': skip, |
241 'skipReason': skipReason, | 308 'skipReason': skipReason, |
242 'verboseTrace': verboseTrace, | 309 'verboseTrace': verboseTrace, |
310 'tags': tags.toList(), | |
243 'onPlatform': serializedOnPlatform, | 311 'onPlatform': serializedOnPlatform, |
244 'tags': tags.toList() | 312 'forTag': mapMap(forTag, value: (_, metadata) => metadata.serialize()) |
245 }; | 313 }; |
246 } | 314 } |
247 | 315 |
248 /// Serializes timeout into a JSON-safe object. | 316 /// Serializes timeout into a JSON-safe object. |
249 _serializeTimeout(Timeout timeout) { | 317 _serializeTimeout(Timeout timeout) { |
250 if (timeout == Timeout.none) return 'none'; | 318 if (timeout == Timeout.none) return 'none'; |
251 return { | 319 return { |
252 'duration': timeout.duration == null | 320 'duration': timeout.duration == null |
253 ? null | 321 ? null |
254 : timeout.duration.inMicroseconds, | 322 : timeout.duration.inMicroseconds, |
255 'scaleFactor': timeout.scaleFactor | 323 'scaleFactor': timeout.scaleFactor |
256 }; | 324 }; |
257 } | 325 } |
258 } | 326 } |
OLD | NEW |