LCOV - code coverage report
Current view: top level - test_api-0.4.8/lib/src/backend - declarer.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 77 112 68.8 %
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 'dart:async';
       6             : 
       7             : import 'package:collection/collection.dart';
       8             : import 'package:stack_trace/stack_trace.dart';
       9             : 
      10             : import 'configuration/timeout.dart';
      11             : import 'group.dart';
      12             : import 'group_entry.dart';
      13             : import 'invoker.dart';
      14             : import 'metadata.dart';
      15             : import 'test.dart';
      16             : 
      17             : /// A class that manages the state of tests as they're declared.
      18             : ///
      19             : /// A nested tree of Declarers tracks the current group, set-up, and tear-down
      20             : /// functions. Each Declarer in the tree corresponds to a group. This tree is
      21             : /// tracked by a zone-scoped "current" Declarer; the current declarer can be set
      22             : /// for a block using [Declarer.declare], and it can be accessed using
      23             : /// [Declarer.current].
      24             : class Declarer {
      25             :   /// The parent declarer, or `null` if this corresponds to the root group.
      26             :   final Declarer? _parent;
      27             : 
      28             :   /// The name of the current test group, including the name of any parent
      29             :   /// groups.
      30             :   ///
      31             :   /// This is `null` if this is the root group.
      32             :   final String? _name;
      33             : 
      34             :   /// The metadata for this group, including the metadata of any parent groups
      35             :   /// and of the test suite.
      36             :   final Metadata _metadata;
      37             : 
      38             :   /// The set of variables that are valid for platform selectors, in addition to
      39             :   /// the built-in variables that are allowed everywhere.
      40             :   final Set<String> _platformVariables;
      41             : 
      42             :   /// The stack trace for this group.
      43             :   ///
      44             :   /// This is `null` for the root (implicit) group.
      45             :   final Trace? _trace;
      46             : 
      47             :   /// Whether to collect stack traces for [GroupEntry]s.
      48             :   final bool _collectTraces;
      49             : 
      50             :   /// Whether to disable retries of tests.
      51             :   final bool _noRetry;
      52             : 
      53             :   /// The set-up functions to run for each test in this group.
      54             :   final _setUps = <dynamic Function()>[];
      55             : 
      56             :   /// The tear-down functions to run for each test in this group.
      57             :   final _tearDowns = <dynamic Function()>[];
      58             : 
      59             :   /// The set-up functions to run once for this group.
      60             :   final _setUpAlls = <dynamic Function()>[];
      61             : 
      62             :   /// The default timeout for synthetic tests.
      63             :   final _timeout = Timeout(Duration(minutes: 12));
      64             : 
      65             :   /// The trace for the first call to [setUpAll].
      66             :   ///
      67             :   /// All [setUpAll]s are run in a single logical test, so they can only have
      68             :   /// one trace. The first trace is most often correct, since the first
      69             :   /// [setUpAll] is always run and the rest are only run if that one succeeds.
      70             :   Trace? _setUpAllTrace;
      71             : 
      72             :   /// The tear-down functions to run once for this group.
      73             :   final _tearDownAlls = <Function()>[];
      74             : 
      75             :   /// The trace for the first call to [tearDownAll].
      76             :   ///
      77             :   /// All [tearDownAll]s are run in a single logical test, so they can only have
      78             :   /// one trace. The first trace matches [_setUpAllTrace].
      79             :   Trace? _tearDownAllTrace;
      80             : 
      81             :   /// The children of this group, either tests or sub-groups.
      82             :   ///
      83             :   /// All modifications to this must go through [_addEntry].
      84             :   final _entries = <GroupEntry>[];
      85             : 
      86             :   /// Whether [build] has been called for this declarer.
      87             :   bool _built = false;
      88             : 
      89             :   /// The tests and/or groups that have been flagged as solo.
      90             :   final _soloEntries = <GroupEntry>[];
      91             : 
      92             :   /// Whether any tests and/or groups have been flagged as solo.
      93          33 :   bool get _solo => _soloEntries.isNotEmpty;
      94             : 
      95             :   /// An exact full test name to match.
      96             :   ///
      97             :   /// When non-null only tests with exactly this name will be considered. The
      98             :   /// full test name is the combination of the test case name with all group
      99             :   /// prefixes. All other tests, including their metadata like `solo`, is
     100             :   /// ignored. Uniqueness is not guaranteed so this may match more than one
     101             :   /// test.
     102             :   ///
     103             :   /// Groups which are not a strict prefix of this name will be ignored.
     104             :   final String? _fullTestName;
     105             : 
     106             :   /// The current zone-scoped declarer.
     107          33 :   static Declarer? get current => Zone.current[#test.declarer] as Declarer?;
     108             : 
     109             :   /// All the test and group names that have been declared in the entire suite.
     110             :   ///
     111             :   /// If duplicate test names are allowed, this is not tracked and it will be
     112             :   /// `null`.
     113             :   final Set<String>? _seenNames;
     114             : 
     115             :   /// Creates a new declarer for the root group.
     116             :   ///
     117             :   /// This is the implicit group that exists outside of any calls to `group()`.
     118             :   /// If [metadata] is passed, it's used as the metadata for the implicit root
     119             :   /// group.
     120             :   ///
     121             :   /// The [platformVariables] are the set of variables that are valid for
     122             :   /// platform selectors in test and group metadata, in addition to the built-in
     123             :   /// variables that are allowed everywhere.
     124             :   ///
     125             :   /// If [collectTraces] is `true`, this will set [GroupEntry.trace] for all
     126             :   /// entries built by the declarer. Note that this can be noticeably slow when
     127             :   /// thousands of tests are being declared (see #457).
     128             :   ///
     129             :   /// If [noRetry] is `true` tests will be run at most once.
     130             :   ///
     131             :   /// If [allowDuplicateTestNames] is `false`, then a
     132             :   /// [DuplicateTestNameException] will be thrown if two tests (or groups) have
     133             :   /// the same name.
     134          11 :   Declarer({
     135             :     Metadata? metadata,
     136             :     Set<String>? platformVariables,
     137             :     bool collectTraces = false,
     138             :     bool noRetry = false,
     139             :     String? fullTestName,
     140             :     // TODO: Change the default https://github.com/dart-lang/test/issues/1571
     141             :     bool allowDuplicateTestNames = true,
     142          11 :   }) : this._(
     143             :             null,
     144             :             null,
     145           0 :             metadata ?? Metadata(),
     146             :             platformVariables ?? const UnmodifiableSetView.empty(),
     147             :             collectTraces,
     148             :             null,
     149             :             noRetry,
     150             :             fullTestName,
     151             :             allowDuplicateTestNames ? null : <String>{});
     152             : 
     153          11 :   Declarer._(
     154             :     this._parent,
     155             :     this._name,
     156             :     this._metadata,
     157             :     this._platformVariables,
     158             :     this._collectTraces,
     159             :     this._trace,
     160             :     this._noRetry,
     161             :     this._fullTestName,
     162             :     this._seenNames,
     163             :   );
     164             : 
     165             :   /// Runs [body] with this declarer as [Declarer.current].
     166             :   ///
     167             :   /// Returns the return value of [body].
     168          11 :   T declare<T>(T Function() body) =>
     169          22 :       runZoned(body, zoneValues: {#test.declarer: this});
     170             : 
     171             :   /// Defines a test case with the given name and body.
     172          11 :   void test(String name, dynamic Function() body,
     173             :       {String? testOn,
     174             :       Timeout? timeout,
     175             :       skip,
     176             :       Map<String, dynamic>? onPlatform,
     177             :       tags,
     178             :       int? retry,
     179             :       bool solo = false}) {
     180          11 :     _checkNotBuilt('test');
     181             : 
     182          11 :     final fullName = _prefix(name);
     183          11 :     if (_fullTestName != null && fullName != _fullTestName) {
     184             :       return;
     185             :     }
     186             : 
     187          11 :     var newMetadata = Metadata.parse(
     188             :         testOn: testOn,
     189             :         timeout: timeout,
     190             :         skip: skip,
     191             :         onPlatform: onPlatform,
     192             :         tags: tags,
     193          11 :         retry: _noRetry ? 0 : retry);
     194          22 :     newMetadata.validatePlatformSelectors(_platformVariables);
     195          22 :     var metadata = _metadata.merge(newMetadata);
     196          33 :     _addEntry(LocalTest(fullName, metadata, () async {
     197          11 :       var parents = <Declarer>[];
     198             :       for (Declarer? declarer = this;
     199             :           declarer != null;
     200          11 :           declarer = declarer._parent) {
     201          11 :         parents.add(declarer);
     202             :       }
     203             : 
     204             :       // Register all tear-down functions in all declarers. Iterate through
     205             :       // parents outside-in so that the Invoker gets the functions in the order
     206             :       // they were declared in source.
     207          22 :       for (var declarer in parents.reversed) {
     208          14 :         for (var tearDown in declarer._tearDowns) {
     209           6 :           Invoker.current!.addTearDown(tearDown);
     210             :         }
     211             :       }
     212             : 
     213          33 :       await runZoned(() async {
     214          22 :         await _runSetUps();
     215          11 :         await body();
     216             :       },
     217             :           // Make the declarer visible to running tests so that they'll throw
     218             :           // useful errors when calling `test()` and `group()` within a test.
     219          11 :           zoneValues: {#test.declarer: this});
     220          22 :     }, trace: _collectTraces ? Trace.current(2) : null, guarded: false));
     221             : 
     222             :     if (solo) {
     223           0 :       _soloEntries.add(_entries.last);
     224             :     }
     225             :   }
     226             : 
     227             :   /// Creates a group of tests.
     228           2 :   void group(String name, void Function() body,
     229             :       {String? testOn,
     230             :       Timeout? timeout,
     231             :       skip,
     232             :       Map<String, dynamic>? onPlatform,
     233             :       tags,
     234             :       int? retry,
     235             :       bool solo = false}) {
     236           2 :     _checkNotBuilt('group');
     237             : 
     238           2 :     final fullTestPrefix = _prefix(name);
     239           2 :     if (_fullTestName != null && !_fullTestName!.startsWith(fullTestPrefix)) {
     240             :       return;
     241             :     }
     242             : 
     243           2 :     var newMetadata = Metadata.parse(
     244             :         testOn: testOn,
     245             :         timeout: timeout,
     246             :         skip: skip,
     247             :         onPlatform: onPlatform,
     248             :         tags: tags,
     249           2 :         retry: _noRetry ? 0 : retry);
     250           4 :     newMetadata.validatePlatformSelectors(_platformVariables);
     251           4 :     var metadata = _metadata.merge(newMetadata);
     252           4 :     var trace = _collectTraces ? Trace.current(2) : null;
     253             : 
     254           2 :     var declarer = Declarer._(
     255             :         this,
     256             :         fullTestPrefix,
     257             :         metadata,
     258           2 :         _platformVariables,
     259           2 :         _collectTraces,
     260             :         trace,
     261           2 :         _noRetry,
     262           2 :         _fullTestName,
     263           2 :         _seenNames);
     264           4 :     declarer.declare(() {
     265             :       // Cast to dynamic to avoid the analyzer complaining about us using the
     266             :       // result of a void method.
     267           2 :       var result = (body as dynamic)();
     268           2 :       if (result is! Future) return;
     269           0 :       throw ArgumentError('Groups may not be async.');
     270             :     });
     271           4 :     _addEntry(declarer.build());
     272             : 
     273           2 :     if (solo || declarer._solo) {
     274           0 :       _soloEntries.add(_entries.last);
     275             :     }
     276             :   }
     277             : 
     278             :   /// Returns [name] prefixed with this declarer's group name.
     279          26 :   String _prefix(String name) => _name == null ? name : '$_name $name';
     280             : 
     281             :   /// Registers a function to be run before each test in this group.
     282           3 :   void setUp(dynamic Function() callback) {
     283           3 :     _checkNotBuilt('setUp');
     284           6 :     _setUps.add(callback);
     285             :   }
     286             : 
     287             :   /// Registers a function to be run after each test in this group.
     288           3 :   void tearDown(dynamic Function() callback) {
     289           3 :     _checkNotBuilt('tearDown');
     290           6 :     _tearDowns.add(callback);
     291             :   }
     292             : 
     293             :   /// Registers a function to be run once before all tests.
     294           0 :   void setUpAll(dynamic Function() callback) {
     295           0 :     _checkNotBuilt('setUpAll');
     296           0 :     if (_collectTraces) _setUpAllTrace ??= Trace.current(2);
     297           0 :     _setUpAlls.add(callback);
     298             :   }
     299             : 
     300             :   /// Registers a function to be run once after all tests.
     301           0 :   void tearDownAll(dynamic Function() callback) {
     302           0 :     _checkNotBuilt('tearDownAll');
     303           0 :     if (_collectTraces) _tearDownAllTrace ??= Trace.current(2);
     304           0 :     _tearDownAlls.add(callback);
     305             :   }
     306             : 
     307             :   /// Like [tearDownAll], but called from within a running [setUpAll] test to
     308             :   /// dynamically add a [tearDownAll].
     309           0 :   void addTearDownAll(dynamic Function() callback) =>
     310           0 :       _tearDownAlls.add(callback);
     311             : 
     312             :   /// Finalizes and returns the group being declared.
     313             :   ///
     314             :   /// **Note**: The tests in this group must be run in a [Invoker.guard]
     315             :   /// context; otherwise, test errors won't be captured.
     316          11 :   Group build() {
     317          11 :     _checkNotBuilt('build');
     318             : 
     319          11 :     _built = true;
     320          33 :     var entries = _entries.map((entry) {
     321          11 :       if (_solo && !_soloEntries.contains(entry)) {
     322           0 :         entry = LocalTest(
     323           0 :             entry.name,
     324           0 :             entry.metadata
     325           0 :                 .change(skip: true, skipReason: 'does not have "solo"'),
     326           0 :             () {});
     327             :       }
     328             :       return entry;
     329          11 :     }).toList();
     330             : 
     331          22 :     return Group(_name ?? '', entries,
     332          11 :         metadata: _metadata,
     333          11 :         trace: _trace,
     334          11 :         setUpAll: _setUpAll,
     335          11 :         tearDownAll: _tearDownAll);
     336             :   }
     337             : 
     338             :   /// Throws a [StateError] if [build] has been called.
     339             :   ///
     340             :   /// [name] should be the name of the method being called.
     341          11 :   void _checkNotBuilt(String name) {
     342          11 :     if (!_built) return;
     343           0 :     throw StateError("Can't call $name() once tests have begun running.");
     344             :   }
     345             : 
     346             :   /// Run the set-up functions for this and any parent groups.
     347             :   ///
     348             :   /// If no set-up functions are declared, this returns a [Future] that
     349             :   /// completes immediately.
     350          11 :   Future _runSetUps() async {
     351          17 :     if (_parent != null) await _parent!._runSetUps();
     352             :     // TODO: why does type inference not work here?
     353          39 :     await Future.forEach<Function>(_setUps, (setUp) => setUp());
     354             :   }
     355             : 
     356             :   /// Returns a [Test] that runs the callbacks in [_setUpAll], or `null`.
     357          11 :   Test? get _setUpAll {
     358          22 :     if (_setUpAlls.isEmpty) return null;
     359             : 
     360           0 :     return LocalTest(_prefix('(setUpAll)'), _metadata.change(timeout: _timeout),
     361           0 :         () {
     362           0 :       return runZoned(
     363           0 :           () => Future.forEach<Function>(_setUpAlls, (setUp) => setUp()),
     364             :           // Make the declarer visible to running scaffolds so they can add to
     365             :           // the declarer's `tearDownAll()` list.
     366           0 :           zoneValues: {#test.declarer: this});
     367           0 :     }, trace: _setUpAllTrace, guarded: false, isScaffoldAll: true);
     368             :   }
     369             : 
     370             :   /// Returns a [Test] that runs the callbacks in [_tearDownAll], or `null`.
     371          11 :   Test? get _tearDownAll {
     372             :     // We have to create a tearDownAll if there's a setUpAll, since it might
     373             :     // dynamically add tear-down code using [addTearDownAll].
     374          44 :     if (_setUpAlls.isEmpty && _tearDownAlls.isEmpty) return null;
     375             : 
     376           0 :     return LocalTest(
     377           0 :         _prefix('(tearDownAll)'), _metadata.change(timeout: _timeout), () {
     378           0 :       return runZoned(() => Invoker.current!.runTearDowns(_tearDownAlls),
     379             :           // Make the declarer visible to running scaffolds so they can add to
     380             :           // the declarer's `tearDownAll()` list.
     381           0 :           zoneValues: {#test.declarer: this});
     382           0 :     }, trace: _tearDownAllTrace, guarded: false, isScaffoldAll: true);
     383             :   }
     384             : 
     385          11 :   void _addEntry(GroupEntry entry) {
     386          22 :     if (_seenNames?.add(entry.name) == false) {
     387           0 :       throw DuplicateTestNameException(entry.name);
     388             :     }
     389          22 :     _entries.add(entry);
     390             :   }
     391             : }
     392             : 
     393             : /// An exception thrown when two test cases in the same test suite (same `main`)
     394             : /// have an identical name.
     395             : class DuplicateTestNameException implements Exception {
     396             :   final String name;
     397           0 :   DuplicateTestNameException(this.name);
     398             : 
     399           0 :   @override
     400           0 :   String toString() => 'A test with the name "$name" was already declared. '
     401             :       'Test cases must have unique names.\n\n'
     402             :       'See https://github.com/dart-lang/test/blob/master/pkgs/test/doc/'
     403             :       'configuration.md#allow_test_randomization for info on enabling this.';
     404             : }

Generated by: LCOV version 1.14