LCOV - code coverage report
Current view: top level - test-0.12.24+8/lib/src/backend - metadata.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 41 132 31.1 %
Date: 2017-10-10 20:17:03 Functions: 0 0 -

          Line data    Source code
       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
       3             : // BSD-style license that can be found in the LICENSE file.
       4             : 
       5             : import 'dart:collection';
       6             : 
       7             : import 'package:boolean_selector/boolean_selector.dart';
       8             : import 'package:collection/collection.dart';
       9             : 
      10             : import '../frontend/skip.dart';
      11             : import '../frontend/timeout.dart';
      12             : import '../utils.dart';
      13             : import 'operating_system.dart';
      14             : import 'platform_selector.dart';
      15             : import 'test_platform.dart';
      16             : 
      17             : /// Metadata for a test or test suite.
      18             : ///
      19             : /// This metadata comes from declarations on the test itself; it doesn't include
      20             : /// configuration from the user.
      21             : class Metadata {
      22             :   /// Empty metadata with only default values.
      23             :   ///
      24             :   /// Using this is slightly more efficient than manually constructing a new
      25             :   /// metadata with no arguments.
      26             :   static final empty = new Metadata._();
      27             : 
      28             :   /// The selector indicating which platforms the suite supports.
      29             :   final PlatformSelector testOn;
      30             : 
      31             :   /// The modification to the timeout for the test or suite.
      32             :   final Timeout timeout;
      33             : 
      34             :   /// Whether the test or suite should be skipped.
      35           5 :   bool get skip => _skip ?? false;
      36             :   final bool _skip;
      37             : 
      38             :   /// The reason the test or suite should be skipped, if given.
      39             :   final String skipReason;
      40             : 
      41             :   /// Whether to use verbose stack traces.
      42           0 :   bool get verboseTrace => _verboseTrace ?? false;
      43             :   final bool _verboseTrace;
      44             : 
      45             :   /// Whether to chain stack traces.
      46           5 :   bool get chainStackTraces => _chainStackTraces ?? true;
      47             :   final bool _chainStackTraces;
      48             : 
      49             :   /// The user-defined tags attached to the test or suite.
      50             :   final Set<String> tags;
      51             : 
      52             :   /// The number of times to re-run a test before being marked as a failure.
      53           0 :   int get retry => _retry ?? 0;
      54             :   final int _retry;
      55             : 
      56             :   /// Platform-specific metadata.
      57             :   ///
      58             :   /// Each key identifies a platform, and its value identifies the specific
      59             :   /// metadata for that platform. These can be applied by calling [forPlatform].
      60             :   final Map<PlatformSelector, Metadata> onPlatform;
      61             : 
      62             :   /// Metadata that applies only when specific tags are applied.
      63             :   ///
      64             :   /// Tag-specific metadata is applied when merging this with other metadata.
      65             :   /// Note that unlike [onPlatform], the base metadata takes precedence over any
      66             :   /// tag-specific metadata.
      67             :   ///
      68             :   /// This is guaranteed not to have any keys that match [tags]; those are
      69             :   /// resolved when the metadata is constructed.
      70             :   final Map<BooleanSelector, Metadata> forTag;
      71             : 
      72             :   /// Parses a user-provided map into the value for [onPlatform].
      73             :   static Map<PlatformSelector, Metadata> _parseOnPlatform(
      74             :       Map<String, dynamic> onPlatform) {
      75           5 :     if (onPlatform == null) return {};
      76             : 
      77           0 :     var result = <PlatformSelector, Metadata>{};
      78           0 :     onPlatform.forEach((platform, metadata) {
      79           0 :       if (metadata is Timeout || metadata is Skip) {
      80           0 :         metadata = [metadata];
      81           0 :       } else if (metadata is! List) {
      82           0 :         throw new ArgumentError('Metadata for platform "$platform" must be a '
      83           0 :             'Timeout, Skip, or List of those; was "$metadata".');
      84             :       }
      85             : 
      86           0 :       var selector = new PlatformSelector.parse(platform);
      87             : 
      88             :       var timeout;
      89             :       var skip;
      90           0 :       for (var metadatum in metadata) {
      91           0 :         if (metadatum is Timeout) {
      92             :           if (timeout != null) {
      93           0 :             throw new ArgumentError('Only a single Timeout may be declared for '
      94           0 :                 '"$platform".');
      95             :           }
      96             : 
      97             :           timeout = metadatum;
      98           0 :         } else if (metadatum is Skip) {
      99             :           if (skip != null) {
     100           0 :             throw new ArgumentError('Only a single Skip may be declared for '
     101           0 :                 '"$platform".');
     102             :           }
     103             : 
     104           0 :           skip = metadatum.reason == null ? true : metadatum.reason;
     105             :         } else {
     106           0 :           throw new ArgumentError('Metadata for platform "$platform" must be a '
     107           0 :               'Timeout, Skip, or List of those; was "$metadata".');
     108             :         }
     109             :       }
     110             : 
     111           0 :       result[selector] = new Metadata.parse(timeout: timeout, skip: skip);
     112             :     });
     113             :     return result;
     114             :   }
     115             : 
     116             :   /// Parses a user-provided [String] or [Iterable] into the value for [tags].
     117             :   ///
     118             :   /// Throws an [ArgumentError] if [tags] is not a [String] or an [Iterable].
     119             :   static Set<String> _parseTags(tags) {
     120           5 :     if (tags == null) return new Set();
     121           1 :     if (tags is String) return new Set.from([tags]);
     122           1 :     if (tags is! Iterable) {
     123           0 :       throw new ArgumentError.value(
     124             :           tags, "tags", "must be either a String or an Iterable.");
     125             :     }
     126             : 
     127           2 :     if (tags.any((tag) => tag is! String)) {
     128           0 :       throw new ArgumentError.value(tags, "tags", "must contain only Strings.");
     129             :     }
     130             : 
     131           1 :     return new Set.from(tags);
     132             :   }
     133             : 
     134             :   /// Creates new Metadata.
     135             :   ///
     136             :   /// [testOn] defaults to [PlatformSelector.all].
     137             :   ///
     138             :   /// If [forTag] contains metadata that applies to [tags], that metadata is
     139             :   /// included inline in the returned value. The values directly passed to the
     140             :   /// constructor take precedence over tag-specific metadata.
     141             :   factory Metadata(
     142             :       {PlatformSelector testOn,
     143             :       Timeout timeout,
     144             :       bool skip,
     145             :       bool verboseTrace,
     146             :       bool chainStackTraces,
     147             :       int retry,
     148             :       String skipReason,
     149             :       Iterable<String> tags,
     150             :       Map<PlatformSelector, Metadata> onPlatform,
     151             :       Map<BooleanSelector, Metadata> forTag}) {
     152             :     // Returns metadata without forTag resolved at all.
     153           5 :     _unresolved() => new Metadata._(
     154             :         testOn: testOn,
     155             :         timeout: timeout,
     156             :         skip: skip,
     157             :         verboseTrace: verboseTrace,
     158             :         chainStackTraces: chainStackTraces,
     159             :         retry: retry,
     160             :         skipReason: skipReason,
     161             :         tags: tags,
     162             :         onPlatform: onPlatform,
     163             :         forTag: forTag);
     164             : 
     165             :     // If there's no tag-specific metadata, or if none of it applies, just
     166             :     // return the metadata as-is.
     167           5 :     if (forTag == null || tags == null) return _unresolved();
     168           5 :     tags = new Set.from(tags);
     169           5 :     forTag = new Map.from(forTag);
     170             : 
     171             :     // Otherwise, resolve the tag-specific components. Doing this eagerly means
     172             :     // we only have to resolve suite- or group-level tags once, rather than
     173             :     // doing it for every test individually.
     174           5 :     var empty = new Metadata._();
     175          15 :     var merged = forTag.keys.toList().fold(empty, (merged, selector) {
     176           0 :       if (!selector.evaluate(tags)) return merged;
     177           0 :       return merged.merge(forTag.remove(selector));
     178             :     });
     179             : 
     180          10 :     if (merged == empty) return _unresolved();
     181           0 :     return merged.merge(_unresolved());
     182             :   }
     183             : 
     184             :   /// Creates new Metadata.
     185             :   ///
     186             :   /// Unlike [new Metadata], this assumes [forTag] is already resolved.
     187             :   Metadata._(
     188             :       {PlatformSelector testOn,
     189             :       Timeout timeout,
     190             :       bool skip,
     191             :       this.skipReason,
     192             :       bool verboseTrace,
     193             :       bool chainStackTraces,
     194             :       int retry,
     195             :       Iterable<String> tags,
     196             :       Map<PlatformSelector, Metadata> onPlatform,
     197             :       Map<BooleanSelector, Metadata> forTag})
     198             :       : testOn = testOn == null ? PlatformSelector.all : testOn,
     199             :         timeout = timeout == null ? const Timeout.factor(1) : timeout,
     200             :         _skip = skip,
     201             :         _verboseTrace = verboseTrace,
     202             :         _chainStackTraces = chainStackTraces,
     203             :         _retry = retry,
     204          15 :         tags = new UnmodifiableSetView(tags == null ? new Set() : tags.toSet()),
     205             :         onPlatform =
     206           5 :             onPlatform == null ? const {} : new UnmodifiableMapView(onPlatform),
     207          10 :         forTag = forTag == null ? const {} : new UnmodifiableMapView(forTag) {
     208           0 :     if (retry != null) RangeError.checkNotNegative(retry, "retry");
     209           5 :     _validateTags();
     210             :   }
     211             : 
     212             :   /// Creates a new Metadata, but with fields parsed from caller-friendly values
     213             :   /// where applicable.
     214             :   ///
     215             :   /// Throws a [FormatException] if any field is invalid.
     216             :   Metadata.parse(
     217             :       {String testOn,
     218             :       Timeout timeout,
     219             :       skip,
     220             :       bool verboseTrace,
     221             :       bool chainStackTraces,
     222             :       int retry,
     223             :       Map<String, dynamic> onPlatform,
     224             :       tags})
     225             :       : testOn = testOn == null
     226             :             ? PlatformSelector.all
     227           0 :             : new PlatformSelector.parse(testOn),
     228             :         timeout = timeout == null ? const Timeout.factor(1) : timeout,
     229           0 :         _skip = skip == null ? null : skip != false,
     230             :         _verboseTrace = verboseTrace,
     231             :         _chainStackTraces = chainStackTraces,
     232             :         _retry = retry,
     233           5 :         skipReason = skip is String ? skip : null,
     234           5 :         onPlatform = _parseOnPlatform(onPlatform),
     235           5 :         tags = _parseTags(tags),
     236           5 :         forTag = const {} {
     237           0 :     if (skip != null && skip is! String && skip is! bool) {
     238           0 :       throw new ArgumentError(
     239           0 :           '"skip" must be a String or a bool, was "$skip".');
     240             :     }
     241             : 
     242           0 :     if (retry != null) RangeError.checkNotNegative(retry, "retry");
     243             : 
     244           5 :     _validateTags();
     245             :   }
     246             : 
     247             :   /// Deserializes the result of [Metadata.serialize] into a new [Metadata].
     248             :   Metadata.deserialize(serialized)
     249           0 :       : testOn = serialized['testOn'] == null
     250             :             ? PlatformSelector.all
     251           0 :             : new PlatformSelector.parse(serialized['testOn']),
     252           0 :         timeout = _deserializeTimeout(serialized['timeout']),
     253           0 :         _skip = serialized['skip'],
     254           0 :         skipReason = serialized['skipReason'],
     255           0 :         _verboseTrace = serialized['verboseTrace'],
     256           0 :         _chainStackTraces = serialized['chainStackTraces'],
     257           0 :         _retry = serialized['retry'],
     258           0 :         tags = new Set.from(serialized['tags']),
     259           0 :         onPlatform = new Map.fromIterable(serialized['onPlatform'],
     260           0 :             key: (pair) => new PlatformSelector.parse(pair.first),
     261           0 :             value: (pair) => new Metadata.deserialize(pair.last)),
     262           0 :         forTag = mapMap(serialized['forTag'],
     263           0 :             key: (key, _) => new BooleanSelector.parse(key),
     264           0 :             value: (_, nested) => new Metadata.deserialize(nested));
     265             : 
     266             :   /// Deserializes timeout from the format returned by [_serializeTimeout].
     267             :   static _deserializeTimeout(serialized) {
     268           0 :     if (serialized == 'none') return Timeout.none;
     269           0 :     var scaleFactor = serialized['scaleFactor'];
     270           0 :     if (scaleFactor != null) return new Timeout.factor(scaleFactor);
     271           0 :     return new Timeout(new Duration(microseconds: serialized['duration']));
     272             :   }
     273             : 
     274             :   /// Throws an [ArgumentError] if any tags in [tags] aren't hyphenated
     275             :   /// identifiers.
     276             :   void _validateTags() {
     277           5 :     var invalidTags = tags
     278           7 :         .where((tag) => !tag.contains(anchoredHyphenatedIdentifier))
     279           5 :         .map((tag) => '"$tag"')
     280           5 :         .toList();
     281             : 
     282           5 :     if (invalidTags.isEmpty) return;
     283             : 
     284           0 :     throw new ArgumentError("Invalid ${pluralize('tag', invalidTags.length)} "
     285           0 :         "${toSentence(invalidTags)}. Tags must be (optionally hyphenated) "
     286           0 :         "Dart identifiers.");
     287             :   }
     288             : 
     289             :   /// Return a new [Metadata] that merges [this] with [other].
     290             :   ///
     291             :   /// If the two [Metadata]s have conflicting properties, [other] wins. If
     292             :   /// either has a [forTag] metadata for one of the other's tags, that metadata
     293             :   /// is merged as well.
     294           5 :   Metadata merge(Metadata other) => new Metadata(
     295          15 :       testOn: testOn.intersection(other.testOn),
     296          15 :       timeout: timeout.merge(other.timeout),
     297          10 :       skip: other._skip ?? _skip,
     298          10 :       skipReason: other.skipReason ?? skipReason,
     299          10 :       verboseTrace: other._verboseTrace ?? _verboseTrace,
     300          10 :       chainStackTraces: other._chainStackTraces ?? _chainStackTraces,
     301          10 :       retry: other._retry ?? _retry,
     302          15 :       tags: tags.union(other.tags),
     303          15 :       onPlatform: mergeMaps(onPlatform, other.onPlatform,
     304           0 :           value: (metadata1, metadata2) => metadata1.merge(metadata2)),
     305          15 :       forTag: mergeMaps(forTag, other.forTag,
     306           0 :           value: (metadata1, metadata2) => metadata1.merge(metadata2)));
     307             : 
     308             :   /// Returns a copy of [this] with the given fields changed.
     309             :   Metadata change(
     310             :       {PlatformSelector testOn,
     311             :       Timeout timeout,
     312             :       bool skip,
     313             :       bool verboseTrace,
     314             :       bool chainStackTraces,
     315             :       int retry,
     316             :       String skipReason,
     317             :       Map<PlatformSelector, Metadata> onPlatform,
     318             :       Set<String> tags,
     319             :       Map<BooleanSelector, Metadata> forTag}) {
     320           0 :     testOn ??= this.testOn;
     321           0 :     timeout ??= this.timeout;
     322           0 :     skip ??= this._skip;
     323           0 :     verboseTrace ??= this._verboseTrace;
     324           0 :     chainStackTraces ??= this._chainStackTraces;
     325           0 :     retry ??= this._retry;
     326           0 :     skipReason ??= this.skipReason;
     327           0 :     onPlatform ??= this.onPlatform;
     328           0 :     tags ??= this.tags;
     329           0 :     forTag ??= this.forTag;
     330           0 :     return new Metadata(
     331             :         testOn: testOn,
     332             :         timeout: timeout,
     333             :         skip: skip,
     334             :         verboseTrace: verboseTrace,
     335             :         chainStackTraces: chainStackTraces,
     336             :         skipReason: skipReason,
     337             :         onPlatform: onPlatform,
     338             :         tags: tags,
     339             :         forTag: forTag,
     340             :         retry: retry);
     341             :   }
     342             : 
     343             :   /// Returns a copy of [this] with all platform-specific metadata from
     344             :   /// [onPlatform] resolved.
     345             :   Metadata forPlatform(TestPlatform platform, {OperatingSystem os}) {
     346          10 :     if (onPlatform.isEmpty) return this;
     347             : 
     348             :     var metadata = this;
     349           0 :     onPlatform.forEach((platformSelector, platformMetadata) {
     350           0 :       if (!platformSelector.evaluate(platform, os: os)) return;
     351           0 :       metadata = metadata.merge(platformMetadata);
     352             :     });
     353           0 :     return metadata.change(onPlatform: {});
     354             :   }
     355             : 
     356             :   /// Serializes [this] into a JSON-safe object that can be deserialized using
     357             :   /// [new Metadata.deserialize].
     358             :   serialize() {
     359             :     // Make this a list to guarantee that the order is preserved.
     360           0 :     var serializedOnPlatform = [];
     361           0 :     onPlatform.forEach((key, value) {
     362           0 :       serializedOnPlatform.add([key.toString(), value.serialize()]);
     363             :     });
     364             : 
     365           0 :     return {
     366           0 :       'testOn': testOn == PlatformSelector.all ? null : testOn.toString(),
     367           0 :       'timeout': _serializeTimeout(timeout),
     368           0 :       'skip': _skip,
     369           0 :       'skipReason': skipReason,
     370           0 :       'verboseTrace': _verboseTrace,
     371           0 :       'chainStackTraces': _chainStackTraces,
     372           0 :       'retry': _retry,
     373           0 :       'tags': tags.toList(),
     374             :       'onPlatform': serializedOnPlatform,
     375           0 :       'forTag': mapMap(forTag,
     376           0 :           key: (selector, _) => selector.toString(),
     377           0 :           value: (_, metadata) => metadata.serialize())
     378             :     };
     379             :   }
     380             : 
     381             :   /// Serializes timeout into a JSON-safe object.
     382             :   _serializeTimeout(Timeout timeout) {
     383           0 :     if (timeout == Timeout.none) return 'none';
     384           0 :     return {
     385             :       'duration':
     386           0 :           timeout.duration == null ? null : timeout.duration.inMicroseconds,
     387           0 :       'scaleFactor': timeout.scaleFactor
     388             :     };
     389             :   }
     390             : }

Generated by: LCOV version 1.13