| // Copyright (c) 2015, 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. |
| |
| library test.backend.metadata; |
| |
| import 'dart:collection'; |
| |
| import '../frontend/skip.dart'; |
| import '../frontend/timeout.dart'; |
| import '../utils.dart'; |
| import 'operating_system.dart'; |
| import 'platform_selector.dart'; |
| import 'test_platform.dart'; |
| |
| /// Metadata for a test or test suite. |
| /// |
| /// This metadata comes from declarations on the test itself; it doesn't include |
| /// configuration from the user. |
| class Metadata { |
| /// The selector indicating which platforms the suite supports. |
| final PlatformSelector testOn; |
| |
| /// The modification to the timeout for the test or suite. |
| final Timeout timeout; |
| |
| /// Whether the test or suite should be skipped. |
| final bool skip; |
| |
| /// Whether to use verbose stack traces. |
| final bool verboseTrace; |
| |
| /// The reason the test or suite should be skipped, if given. |
| final String skipReason; |
| |
| /// Platform-specific metadata. |
| /// |
| /// Each key identifies a platform, and its value identifies the specific |
| /// metadata for that platform. These can be applied by calling [forPlatform]. |
| final Map<PlatformSelector, Metadata> onPlatform; |
| |
| /// Parses a user-provided map into the value for [onPlatform]. |
| static Map<PlatformSelector, Metadata> _parseOnPlatform( |
| Map<String, dynamic> onPlatform) { |
| if (onPlatform == null) return {}; |
| |
| var result = {}; |
| onPlatform.forEach((platform, metadata) { |
| if (metadata is Timeout || metadata is Skip) { |
| metadata = [metadata]; |
| } else if (metadata is! List) { |
| throw new ArgumentError('Metadata for platform "$platform" must be a ' |
| 'Timeout, Skip, or List of those; was "$metadata".'); |
| } |
| |
| var selector = new PlatformSelector.parse(platform); |
| |
| var timeout; |
| var skip; |
| for (var metadatum in metadata) { |
| if (metadatum is Timeout) { |
| if (timeout != null) { |
| throw new ArgumentError('Only a single Timeout may be declared for ' |
| '"$platform".'); |
| } |
| |
| timeout = metadatum; |
| } else if (metadatum is Skip) { |
| if (skip != null) { |
| throw new ArgumentError('Only a single Skip may be declared for ' |
| '"$platform".'); |
| } |
| |
| skip = metadatum.reason == null ? true : metadatum.reason; |
| } else { |
| throw new ArgumentError('Metadata for platform "$platform" must be a ' |
| 'Timeout, Skip, or List of those; was "$metadata".'); |
| } |
| } |
| |
| result[selector] = new Metadata.parse(timeout: timeout, skip: skip); |
| }); |
| return result; |
| } |
| |
| /// Creates new Metadata. |
| /// |
| /// [testOn] defaults to [PlatformSelector.all]. |
| Metadata({PlatformSelector testOn, Timeout timeout, bool skip: false, |
| this.verboseTrace: false, this.skipReason, |
| Map<PlatformSelector, Metadata> onPlatform}) |
| : testOn = testOn == null ? PlatformSelector.all : testOn, |
| timeout = timeout == null ? const Timeout.factor(1) : timeout, |
| skip = skip, |
| onPlatform = onPlatform == null |
| ? const {} |
| : new UnmodifiableMapView(onPlatform); |
| |
| /// Creates a new Metadata, but with fields parsed from caller-friendly values |
| /// where applicable. |
| /// |
| /// Throws a [FormatException] if any field is invalid. |
| Metadata.parse({String testOn, Timeout timeout, skip, |
| this.verboseTrace: false, Map<String, dynamic> onPlatform}) |
| : testOn = testOn == null |
| ? PlatformSelector.all |
| : new PlatformSelector.parse(testOn), |
| timeout = timeout == null ? const Timeout.factor(1) : timeout, |
| skip = skip != null && skip != false, |
| skipReason = skip is String ? skip : null, |
| onPlatform = _parseOnPlatform(onPlatform) { |
| if (skip != null && skip is! String && skip is! bool) { |
| throw new ArgumentError( |
| '"skip" must be a String or a bool, was "$skip".'); |
| } |
| } |
| |
| /// Dezerializes the result of [Metadata.serialize] into a new [Metadata]. |
| Metadata.deserialize(serialized) |
| : testOn = serialized['testOn'] == null |
| ? PlatformSelector.all |
| : new PlatformSelector.parse(serialized['testOn']), |
| timeout = _deserializeTimeout(serialized['timeout']), |
| skip = serialized['skip'], |
| skipReason = serialized['skipReason'], |
| verboseTrace = serialized['verboseTrace'], |
| onPlatform = new Map.fromIterable(serialized['onPlatform'], |
| key: (pair) => new PlatformSelector.parse(pair.first), |
| value: (pair) => new Metadata.deserialize(pair.last)); |
| |
| /// Deserializes timeout from the format returned by [_serializeTimeout]. |
| static _deserializeTimeout(serialized) { |
| if (serialized == 'none') return Timeout.none; |
| var scaleFactor = serialized['scaleFactor']; |
| if (scaleFactor != null) return new Timeout.factor(scaleFactor); |
| return new Timeout( |
| new Duration(microseconds: serialized['duration'])); |
| } |
| |
| /// Return a new [Metadata] that merges [this] with [other]. |
| /// |
| /// If the two [Metadata]s have conflicting properties, [other] wins. |
| Metadata merge(Metadata other) => |
| new Metadata( |
| testOn: testOn.intersect(other.testOn), |
| timeout: timeout.merge(other.timeout), |
| skip: skip || other.skip, |
| verboseTrace: verboseTrace || other.verboseTrace, |
| skipReason: other.skipReason == null ? skipReason : other.skipReason, |
| onPlatform: mergeMaps(onPlatform, other.onPlatform)); |
| |
| /// Returns a copy of [this] with the given fields changed. |
| Metadata change({PlatformSelector testOn, Timeout timeout, bool skip, |
| bool verboseTrace, String skipReason, |
| Map<PlatformSelector, Metadata> onPlatform}) { |
| if (testOn == null) testOn = this.testOn; |
| if (timeout == null) timeout = this.timeout; |
| if (skip == null) skip = this.skip; |
| if (verboseTrace == null) verboseTrace = this.verboseTrace; |
| if (skipReason == null) skipReason = this.skipReason; |
| if (onPlatform == null) onPlatform = this.onPlatform; |
| return new Metadata(testOn: testOn, timeout: timeout, skip: skip, |
| verboseTrace: verboseTrace, skipReason: skipReason, |
| onPlatform: onPlatform); |
| } |
| |
| /// Returns a copy of [this] with all platform-specific metadata from |
| /// [onPlatform] resolved. |
| Metadata forPlatform(TestPlatform platform, {OperatingSystem os}) { |
| if (onPlatform.isEmpty) return this; |
| |
| var metadata = this; |
| onPlatform.forEach((platformSelector, platformMetadata) { |
| if (!platformSelector.evaluate(platform, os: os)) return; |
| metadata = metadata.merge(platformMetadata); |
| }); |
| return metadata.change(onPlatform: {}); |
| } |
| |
| /// Serializes [this] into a JSON-safe object that can be deserialized using |
| /// [new Metadata.deserialize]. |
| serialize() { |
| // Make this a list to guarantee that the order is preserved. |
| var serializedOnPlatform = []; |
| onPlatform.forEach((key, value) { |
| serializedOnPlatform.add([key.toString(), value.serialize()]); |
| }); |
| |
| return { |
| 'testOn': testOn == PlatformSelector.all ? null : testOn.toString(), |
| 'timeout': _serializeTimeout(timeout), |
| 'skip': skip, |
| 'skipReason': skipReason, |
| 'verboseTrace': verboseTrace, |
| 'onPlatform': serializedOnPlatform |
| }; |
| } |
| |
| /// Serializes timeout into a JSON-safe object. |
| _serializeTimeout(Timeout timeout) { |
| if (timeout == Timeout.none) return 'none'; |
| return { |
| 'duration': timeout.duration == null |
| ? null |
| : timeout.duration.inMicroseconds, |
| 'scaleFactor': timeout.scaleFactor |
| }; |
| } |
| } |