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:stack_trace/stack_trace.dart';
8 :
9 : import '../frontend/timeout.dart';
10 : import '../utils.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 stack trace for this group.
39 : final Trace _trace;
40 :
41 : /// Whether to collect stack traces for [GroupEntry]s.
42 : final bool _collectTraces;
43 :
44 : /// Whether to disable retries of tests.
45 : final bool _noRetry;
46 :
47 : /// The set-up functions to run for each test in this group.
48 : final _setUps = new List<AsyncFunction>();
49 :
50 : /// The tear-down functions to run for each test in this group.
51 : final _tearDowns = new List<AsyncFunction>();
52 :
53 : /// The set-up functions to run once for this group.
54 : final _setUpAlls = new List<AsyncFunction>();
55 :
56 : /// The trace for the first call to [setUpAll].
57 : ///
58 : /// All [setUpAll]s are run in a single logical test, so they can only have
59 : /// one trace. The first trace is most often correct, since the first
60 : /// [setUpAll] is always run and the rest are only run if that one succeeds.
61 : Trace _setUpAllTrace;
62 :
63 : /// The tear-down functions to run once for this group.
64 : final _tearDownAlls = new List<AsyncFunction>();
65 :
66 : /// The trace for the first call to [tearDownAll].
67 : ///
68 : /// All [tearDownAll]s are run in a single logical test, so they can only have
69 : /// one trace. The first trace matches [_setUpAllTrace].
70 : Trace _tearDownAllTrace;
71 :
72 : /// The children of this group, either tests or sub-groups.
73 : final _entries = new List<GroupEntry>();
74 :
75 : /// Whether [build] has been called for this declarer.
76 : bool _built = false;
77 :
78 : /// The current zone-scoped declarer.
79 10 : static Declarer get current => Zone.current[#test.declarer];
80 :
81 : /// Creates a new declarer for the root group.
82 : ///
83 : /// This is the implicit group that exists outside of any calls to `group()`.
84 : /// If [metadata] is passed, it's used as the metadata for the implicit root
85 : /// group.
86 : ///
87 : /// If [collectTraces] is `true`, this will set [GroupEntry.trace] for all
88 : /// entries built by the declarer. Note that this can be noticeably slow when
89 : /// thousands of tests are being declared (see #457).
90 : ///
91 : /// If [noRetry] is `true` tests will be run at most once.
92 : Declarer({Metadata metadata, bool collectTraces: false, bool noRetry: false})
93 10 : : this._(null, null, metadata ?? new Metadata(), collectTraces, null,
94 : noRetry);
95 :
96 : Declarer._(this._parent, this._name, this._metadata, this._collectTraces,
97 5 : this._trace, this._noRetry);
98 :
99 : /// Runs [body] with this declarer as [Declarer.current].
100 : ///
101 : /// Returns the return value of [body].
102 10 : declare(body()) => runZoned(body, zoneValues: {#test.declarer: this});
103 :
104 : /// Defines a test case with the given name and body.
105 : void test(String name, body(),
106 : {String testOn,
107 : Timeout timeout,
108 : skip,
109 : Map<String, dynamic> onPlatform,
110 : tags,
111 : int retry}) {
112 5 : _checkNotBuilt("test");
113 :
114 15 : var metadata = _metadata.merge(new Metadata.parse(
115 : testOn: testOn,
116 : timeout: timeout,
117 : skip: skip,
118 : onPlatform: onPlatform,
119 : tags: tags,
120 5 : retry: _noRetry ? 0 : retry));
121 :
122 20 : _entries.add(new LocalTest(_prefix(name), metadata, () async {
123 5 : var parents = <Declarer>[];
124 5 : for (var declarer = this; declarer != null; declarer = declarer._parent) {
125 5 : parents.add(declarer);
126 : }
127 :
128 : // Register all tear-down functions in all declarers. Iterate through
129 : // parents outside-in so that the Invoker gets the functions in the order
130 : // they were declared in source.
131 15 : for (var declarer in parents.reversed) {
132 10 : for (var tearDown in declarer._tearDowns) {
133 0 : Invoker.current.addTearDown(tearDown);
134 : }
135 : }
136 :
137 15 : await Invoker.current.waitForOutstandingCallbacks(() async {
138 10 : await _runSetUps();
139 10 : await body();
140 0 : });
141 5 : }, trace: _collectTraces ? new Trace.current(2) : null));
142 : }
143 :
144 : /// Creates a group of tests.
145 : void group(String name, void body(),
146 : {String testOn,
147 : Timeout timeout,
148 : skip,
149 : Map<String, dynamic> onPlatform,
150 : tags,
151 : int retry}) {
152 5 : _checkNotBuilt("group");
153 :
154 15 : var metadata = _metadata.merge(new Metadata.parse(
155 : testOn: testOn,
156 : timeout: timeout,
157 : skip: skip,
158 : onPlatform: onPlatform,
159 : tags: tags,
160 : retry: retry));
161 5 : var trace = _collectTraces ? new Trace.current(2) : null;
162 :
163 5 : var declarer = new Declarer._(
164 15 : this, _prefix(name), metadata, _collectTraces, trace, _noRetry);
165 5 : declarer.declare(() {
166 : // Cast to dynamic to avoid the analyzer complaining about us using the
167 : // result of a void method.
168 5 : var result = (body as dynamic)();
169 5 : if (result is! Future) return;
170 0 : throw new ArgumentError("Groups may not be async.");
171 : });
172 15 : _entries.add(declarer.build());
173 : }
174 :
175 : /// Returns [name] prefixed with this declarer's group name.
176 15 : String _prefix(String name) => _name == null ? name : "$_name $name";
177 :
178 : /// Registers a function to be run before each test in this group.
179 : void setUp(callback()) {
180 5 : _checkNotBuilt("setUp");
181 10 : _setUps.add(callback);
182 : }
183 :
184 : /// Registers a function to be run after each test in this group.
185 : void tearDown(callback()) {
186 0 : _checkNotBuilt("tearDown");
187 0 : _tearDowns.add(callback);
188 : }
189 :
190 : /// Registers a function to be run once before all tests.
191 : void setUpAll(callback()) {
192 0 : _checkNotBuilt("setUpAll");
193 0 : if (_collectTraces) _setUpAllTrace ??= new Trace.current(2);
194 0 : _setUpAlls.add(callback);
195 : }
196 :
197 : /// Registers a function to be run once after all tests.
198 : void tearDownAll(callback()) {
199 0 : _checkNotBuilt("tearDownAll");
200 0 : if (_collectTraces) _tearDownAllTrace ??= new Trace.current(2);
201 0 : _tearDownAlls.add(callback);
202 : }
203 :
204 : /// Finalizes and returns the group being declared.
205 : Group build() {
206 5 : _checkNotBuilt("build");
207 :
208 5 : _built = true;
209 20 : return new Group(_name, _entries.toList(),
210 5 : metadata: _metadata,
211 5 : trace: _trace,
212 5 : setUpAll: _setUpAll,
213 5 : tearDownAll: _tearDownAll);
214 : }
215 :
216 : /// Throws a [StateError] if [build] has been called.
217 : ///
218 : /// [name] should be the name of the method being called.
219 : void _checkNotBuilt(String name) {
220 5 : if (!_built) return;
221 0 : throw new StateError("Can't call $name() once tests have begun running.");
222 : }
223 :
224 : /// Run the set-up functions for this and any parent groups.
225 : ///
226 : /// If no set-up functions are declared, this returns a [Future] that
227 : /// completes immediately.
228 : Future _runSetUps() async {
229 20 : if (_parent != null) await _parent._runSetUps();
230 20 : await Future.forEach(_setUps, (setUp) => setUp());
231 0 : }
232 :
233 : /// Returns a [Test] that runs the callbacks in [_setUpAll].
234 : Test get _setUpAll {
235 10 : if (_setUpAlls.isEmpty) return null;
236 :
237 0 : return new LocalTest(_prefix("(setUpAll)"), _metadata, () {
238 0 : return Future.forEach(_setUpAlls, (setUp) => setUp());
239 0 : }, trace: _setUpAllTrace);
240 : }
241 :
242 : /// Returns a [Test] that runs the callbacks in [_tearDownAll].
243 : Test get _tearDownAll {
244 10 : if (_tearDownAlls.isEmpty) return null;
245 :
246 0 : return new LocalTest(_prefix("(tearDownAll)"), _metadata, () {
247 0 : return Invoker.current.unclosable(() {
248 0 : return Future.forEach(_tearDownAlls.reversed, errorsDontStopTest);
249 : });
250 0 : }, trace: _tearDownAllTrace);
251 : }
252 : }
|