| 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 = new UnmodifiableSetView( |
| 173 tags == null ? new Set() : tags.toSet()), |
| 116 onPlatform = onPlatform == null | 174 onPlatform = onPlatform == null |
| 117 ? const {} | 175 ? const {} |
| 118 : new UnmodifiableMapView(onPlatform), | 176 : new UnmodifiableMapView(onPlatform), |
| 119 tags = tags == null | 177 forTag = forTag == null |
| 120 ? new Set() | 178 ? const {} |
| 121 : new UnmodifiableSetView(tags.toSet()) { | 179 : new UnmodifiableMapView(forTag) { |
| 122 _validateTags(); | 180 _validateTags(); |
| 123 } | 181 } |
| 124 | 182 |
| 125 /// Creates a new Metadata, but with fields parsed from caller-friendly values | 183 /// Creates a new Metadata, but with fields parsed from caller-friendly values |
| 126 /// where applicable. | 184 /// where applicable. |
| 127 /// | 185 /// |
| 128 /// Throws a [FormatException] if any field is invalid. | 186 /// Throws a [FormatException] if any field is invalid. |
| 129 Metadata.parse({String testOn, Timeout timeout, skip, | 187 Metadata.parse({String testOn, Timeout timeout, skip, |
| 130 this.verboseTrace: false, Map<String, dynamic> onPlatform, | 188 this.verboseTrace: false, Map<String, dynamic> onPlatform, |
| 131 tags}) | 189 tags}) |
| 132 : testOn = testOn == null | 190 : testOn = testOn == null |
| 133 ? PlatformSelector.all | 191 ? PlatformSelector.all |
| 134 : new PlatformSelector.parse(testOn), | 192 : new PlatformSelector.parse(testOn), |
| 135 timeout = timeout == null ? const Timeout.factor(1) : timeout, | 193 timeout = timeout == null ? const Timeout.factor(1) : timeout, |
| 136 skip = skip != null && skip != false, | 194 skip = skip != null && skip != false, |
| 137 skipReason = skip is String ? skip : null, | 195 skipReason = skip is String ? skip : null, |
| 138 onPlatform = _parseOnPlatform(onPlatform), | 196 onPlatform = _parseOnPlatform(onPlatform), |
| 139 tags = _parseTags(tags) { | 197 tags = _parseTags(tags), |
| 198 forTag = const {} { |
| 140 if (skip != null && skip is! String && skip is! bool) { | 199 if (skip != null && skip is! String && skip is! bool) { |
| 141 throw new ArgumentError( | 200 throw new ArgumentError( |
| 142 '"skip" must be a String or a bool, was "$skip".'); | 201 '"skip" must be a String or a bool, was "$skip".'); |
| 143 } | 202 } |
| 144 | 203 |
| 145 _validateTags(); | 204 _validateTags(); |
| 146 } | 205 } |
| 147 | 206 |
| 148 /// Deserializes the result of [Metadata.serialize] into a new [Metadata]. | 207 /// Deserializes the result of [Metadata.serialize] into a new [Metadata]. |
| 149 Metadata.deserialize(serialized) | 208 Metadata.deserialize(serialized) |
| 150 : testOn = serialized['testOn'] == null | 209 : testOn = serialized['testOn'] == null |
| 151 ? PlatformSelector.all | 210 ? PlatformSelector.all |
| 152 : new PlatformSelector.parse(serialized['testOn']), | 211 : new PlatformSelector.parse(serialized['testOn']), |
| 153 timeout = _deserializeTimeout(serialized['timeout']), | 212 timeout = _deserializeTimeout(serialized['timeout']), |
| 154 skip = serialized['skip'], | 213 skip = serialized['skip'], |
| 155 skipReason = serialized['skipReason'], | 214 skipReason = serialized['skipReason'], |
| 156 verboseTrace = serialized['verboseTrace'], | 215 verboseTrace = serialized['verboseTrace'], |
| 157 tags = new Set.from(serialized['tags']), | 216 tags = new Set.from(serialized['tags']), |
| 158 onPlatform = new Map.fromIterable(serialized['onPlatform'], | 217 onPlatform = new Map.fromIterable(serialized['onPlatform'], |
| 159 key: (pair) => new PlatformSelector.parse(pair.first), | 218 key: (pair) => new PlatformSelector.parse(pair.first), |
| 160 value: (pair) => new Metadata.deserialize(pair.last)); | 219 value: (pair) => new Metadata.deserialize(pair.last)), |
| 220 forTag = mapMap(serialized['forTag'], |
| 221 value: (_, nested) => new Metadata.deserialize(nested)); |
| 161 | 222 |
| 162 /// Deserializes timeout from the format returned by [_serializeTimeout]. | 223 /// Deserializes timeout from the format returned by [_serializeTimeout]. |
| 163 static _deserializeTimeout(serialized) { | 224 static _deserializeTimeout(serialized) { |
| 164 if (serialized == 'none') return Timeout.none; | 225 if (serialized == 'none') return Timeout.none; |
| 165 var scaleFactor = serialized['scaleFactor']; | 226 var scaleFactor = serialized['scaleFactor']; |
| 166 if (scaleFactor != null) return new Timeout.factor(scaleFactor); | 227 if (scaleFactor != null) return new Timeout.factor(scaleFactor); |
| 167 return new Timeout( | 228 return new Timeout( |
| 168 new Duration(microseconds: serialized['duration'])); | 229 new Duration(microseconds: serialized['duration'])); |
| 169 } | 230 } |
| 170 | 231 |
| 171 /// Throws an [ArgumentError] if any tags in [tags] aren't hyphenated | 232 /// Throws an [ArgumentError] if any tags in [tags] aren't hyphenated |
| 172 /// identifiers. | 233 /// identifiers. |
| 173 void _validateTags() { | 234 void _validateTags() { |
| 174 var invalidTags = tags | 235 var invalidTags = tags |
| 175 .where((tag) => !tag.contains(anchoredHyphenatedIdentifier)) | 236 .where((tag) => !tag.contains(anchoredHyphenatedIdentifier)) |
| 176 .map((tag) => '"$tag"') | 237 .map((tag) => '"$tag"') |
| 177 .toList(); | 238 .toList(); |
| 178 | 239 |
| 179 if (invalidTags.isEmpty) return; | 240 if (invalidTags.isEmpty) return; |
| 180 | 241 |
| 181 throw new ArgumentError( | 242 throw new ArgumentError( |
| 182 "Invalid ${pluralize('tag', invalidTags.length)} " | 243 "Invalid ${pluralize('tag', invalidTags.length)} " |
| 183 "${toSentence(invalidTags)}. Tags must be (optionally hyphenated) " | 244 "${toSentence(invalidTags)}. Tags must be (optionally hyphenated) " |
| 184 "Dart identifiers."); | 245 "Dart identifiers."); |
| 185 } | 246 } |
| 186 | 247 |
| 187 /// Return a new [Metadata] that merges [this] with [other]. | 248 /// Return a new [Metadata] that merges [this] with [other]. |
| 188 /// | 249 /// |
| 189 /// If the two [Metadata]s have conflicting properties, [other] wins. | 250 /// If the two [Metadata]s have conflicting properties, [other] wins. If |
| 251 /// either has a [forTag] metadata for one of the other's tags, that metadata |
| 252 /// is merged as well. |
| 190 Metadata merge(Metadata other) => | 253 Metadata merge(Metadata other) => |
| 191 new Metadata( | 254 new Metadata( |
| 192 testOn: testOn.intersect(other.testOn), | 255 testOn: testOn.intersect(other.testOn), |
| 193 timeout: timeout.merge(other.timeout), | 256 timeout: timeout.merge(other.timeout), |
| 194 skip: skip || other.skip, | 257 skip: skip || other.skip, |
| 258 skipReason: other.skipReason == null ? skipReason : other.skipReason, |
| 195 verboseTrace: verboseTrace || other.verboseTrace, | 259 verboseTrace: verboseTrace || other.verboseTrace, |
| 196 skipReason: other.skipReason == null ? skipReason : other.skipReason, | 260 tags: tags.union(other.tags), |
| 197 onPlatform: mergeMaps(onPlatform, other.onPlatform), | 261 onPlatform: mergeMaps(onPlatform, other.onPlatform, |
| 198 tags: tags.union(other.tags)); | 262 value: (metadata1, metadata2) => metadata1.merge(metadata2)), |
| 263 forTag: mergeMaps(forTag, other.forTag, |
| 264 value: (metadata1, metadata2) => metadata1.merge(metadata2))); |
| 199 | 265 |
| 200 /// Returns a copy of [this] with the given fields changed. | 266 /// Returns a copy of [this] with the given fields changed. |
| 201 Metadata change({PlatformSelector testOn, Timeout timeout, bool skip, | 267 Metadata change({PlatformSelector testOn, Timeout timeout, bool skip, |
| 202 bool verboseTrace, String skipReason, | 268 bool verboseTrace, String skipReason, |
| 203 Map<PlatformSelector, Metadata> onPlatform}) { | 269 Map<PlatformSelector, Metadata> onPlatform}) { |
| 204 if (testOn == null) testOn = this.testOn; | 270 if (testOn == null) testOn = this.testOn; |
| 205 if (timeout == null) timeout = this.timeout; | 271 if (timeout == null) timeout = this.timeout; |
| 206 if (skip == null) skip = this.skip; | 272 if (skip == null) skip = this.skip; |
| 207 if (verboseTrace == null) verboseTrace = this.verboseTrace; | 273 if (verboseTrace == null) verboseTrace = this.verboseTrace; |
| 208 if (skipReason == null) skipReason = this.skipReason; | 274 if (skipReason == null) skipReason = this.skipReason; |
| (...skipping 24 matching lines...) Expand all Loading... |
| 233 onPlatform.forEach((key, value) { | 299 onPlatform.forEach((key, value) { |
| 234 serializedOnPlatform.add([key.toString(), value.serialize()]); | 300 serializedOnPlatform.add([key.toString(), value.serialize()]); |
| 235 }); | 301 }); |
| 236 | 302 |
| 237 return { | 303 return { |
| 238 'testOn': testOn == PlatformSelector.all ? null : testOn.toString(), | 304 'testOn': testOn == PlatformSelector.all ? null : testOn.toString(), |
| 239 'timeout': _serializeTimeout(timeout), | 305 'timeout': _serializeTimeout(timeout), |
| 240 'skip': skip, | 306 'skip': skip, |
| 241 'skipReason': skipReason, | 307 'skipReason': skipReason, |
| 242 'verboseTrace': verboseTrace, | 308 'verboseTrace': verboseTrace, |
| 309 'tags': tags.toList(), |
| 243 'onPlatform': serializedOnPlatform, | 310 'onPlatform': serializedOnPlatform, |
| 244 'tags': tags.toList() | 311 'forTag': mapMap(forTag, value: (_, metadata) => metadata.serialize()) |
| 245 }; | 312 }; |
| 246 } | 313 } |
| 247 | 314 |
| 248 /// Serializes timeout into a JSON-safe object. | 315 /// Serializes timeout into a JSON-safe object. |
| 249 _serializeTimeout(Timeout timeout) { | 316 _serializeTimeout(Timeout timeout) { |
| 250 if (timeout == Timeout.none) return 'none'; | 317 if (timeout == Timeout.none) return 'none'; |
| 251 return { | 318 return { |
| 252 'duration': timeout.duration == null | 319 'duration': timeout.duration == null |
| 253 ? null | 320 ? null |
| 254 : timeout.duration.inMicroseconds, | 321 : timeout.duration.inMicroseconds, |
| 255 'scaleFactor': timeout.scaleFactor | 322 'scaleFactor': timeout.scaleFactor |
| 256 }; | 323 }; |
| 257 } | 324 } |
| 258 } | 325 } |
| OLD | NEW |