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

Generated by: LCOV version 1.14