OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
6 | 6 |
| 7 import 'package:stack_trace/stack_trace.dart'; |
| 8 |
7 import '../frontend/timeout.dart'; | 9 import '../frontend/timeout.dart'; |
8 import '../utils.dart'; | 10 import '../utils.dart'; |
9 import 'group.dart'; | 11 import 'group.dart'; |
10 import 'group_entry.dart'; | 12 import 'group_entry.dart'; |
11 import 'invoker.dart'; | 13 import 'invoker.dart'; |
12 import 'metadata.dart'; | 14 import 'metadata.dart'; |
13 import 'test.dart'; | 15 import 'test.dart'; |
14 | 16 |
15 /// A class that manages the state of tests as they're declared. | 17 /// A class that manages the state of tests as they're declared. |
16 /// | 18 /// |
17 /// A nested tree of Declarers tracks the current group, set-up, and tear-down | 19 /// A nested tree of Declarers tracks the current group, set-up, and tear-down |
18 /// functions. Each Declarer in the tree corresponds to a group. This tree is | 20 /// functions. Each Declarer in the tree corresponds to a group. This tree is |
19 /// tracked by a zone-scoped "current" Declarer; the current declarer can be set | 21 /// tracked by a zone-scoped "current" Declarer; the current declarer can be set |
20 /// for a block using [Declarer.declare], and it can be accessed using | 22 /// for a block using [Declarer.declare], and it can be accessed using |
21 /// [Declarer.current]. | 23 /// [Declarer.current]. |
22 class Declarer { | 24 class Declarer { |
23 /// The parent declarer, or `null` if this corresponds to the root group. | 25 /// The parent declarer, or `null` if this corresponds to the root group. |
24 final Declarer _parent; | 26 final Declarer _parent; |
25 | 27 |
26 /// The name of the current test group, including the name of any parent | 28 /// The name of the current test group, including the name of any parent |
27 /// groups. | 29 /// groups. |
28 /// | 30 /// |
29 /// This is `null` if this is the root group. | 31 /// This is `null` if this is the root group. |
30 final String _name; | 32 final String _name; |
31 | 33 |
32 /// The metadata for this group, including the metadata of any parent groups | 34 /// The metadata for this group, including the metadata of any parent groups |
33 /// and of the test suite. | 35 /// and of the test suite. |
34 final Metadata _metadata; | 36 final Metadata _metadata; |
35 | 37 |
| 38 /// The stack trace for this group. |
| 39 final Trace _trace; |
| 40 |
36 /// The set-up functions to run for each test in this group. | 41 /// The set-up functions to run for each test in this group. |
37 final _setUps = new List<AsyncFunction>(); | 42 final _setUps = new List<AsyncFunction>(); |
38 | 43 |
39 /// The tear-down functions to run for each test in this group. | 44 /// The tear-down functions to run for each test in this group. |
40 final _tearDowns = new List<AsyncFunction>(); | 45 final _tearDowns = new List<AsyncFunction>(); |
41 | 46 |
42 /// The set-up functions to run once for this group. | 47 /// The set-up functions to run once for this group. |
43 final _setUpAlls = new List<AsyncFunction>(); | 48 final _setUpAlls = new List<AsyncFunction>(); |
44 | 49 |
| 50 /// The trace for the first call to [setUpAll]. |
| 51 /// |
| 52 /// All [setUpAll]s are run in a single logical test, so they can only have |
| 53 /// one trace. The first trace is most often correct, since the first |
| 54 /// [setUpAll] is always run and the rest are only run if that one succeeds. |
| 55 Trace _setUpAllTrace; |
| 56 |
45 /// The tear-down functions to run once for this group. | 57 /// The tear-down functions to run once for this group. |
46 final _tearDownAlls = new List<AsyncFunction>(); | 58 final _tearDownAlls = new List<AsyncFunction>(); |
47 | 59 |
| 60 /// The trace for the first call to [tearDownAll]. |
| 61 /// |
| 62 /// All [tearDownAll]s are run in a single logical test, so they can only have |
| 63 /// one trace. The first trace matches [_setUpAllTrace]. |
| 64 Trace _tearDownAllTrace; |
| 65 |
48 /// The children of this group, either tests or sub-groups. | 66 /// The children of this group, either tests or sub-groups. |
49 final _entries = new List<GroupEntry>(); | 67 final _entries = new List<GroupEntry>(); |
50 | 68 |
51 /// Whether [build] has been called for this declarer. | 69 /// Whether [build] has been called for this declarer. |
52 bool _built = false; | 70 bool _built = false; |
53 | 71 |
54 /// The current zone-scoped declarer. | 72 /// The current zone-scoped declarer. |
55 static Declarer get current => Zone.current[#test.declarer]; | 73 static Declarer get current => Zone.current[#test.declarer]; |
56 | 74 |
57 /// Creates a new declarer for the root group. | 75 /// Creates a new declarer for the root group. |
58 /// | 76 /// |
59 /// This is the implicit group that exists outside of any calls to `group()`. | 77 /// This is the implicit group that exists outside of any calls to `group()`. |
60 /// If [metadata] is passed, it's used as the metadata for the implicit root | 78 /// If [metadata] is passed, it's used as the metadata for the implicit root |
61 /// group. | 79 /// group. |
62 Declarer([Metadata metadata]) | 80 Declarer([Metadata metadata]) |
63 : this._(null, null, metadata == null ? new Metadata() : metadata); | 81 : this._(null, null, metadata == null ? new Metadata() : metadata, null); |
64 | 82 |
65 Declarer._(this._parent, this._name, this._metadata); | 83 Declarer._(this._parent, this._name, this._metadata, this._trace); |
66 | 84 |
67 /// Runs [body] with this declarer as [Declarer.current]. | 85 /// Runs [body] with this declarer as [Declarer.current]. |
68 /// | 86 /// |
69 /// Returns the return value of [body]. | 87 /// Returns the return value of [body]. |
70 declare(body()) => runZoned(body, zoneValues: {#test.declarer: this}); | 88 declare(body()) => runZoned(body, zoneValues: {#test.declarer: this}); |
71 | 89 |
72 /// Defines a test case with the given name and body. | 90 /// Defines a test case with the given name and body. |
73 void test(String name, body(), {String testOn, Timeout timeout, skip, | 91 void test(String name, body(), {String testOn, Timeout timeout, skip, |
74 Map<String, dynamic> onPlatform, tags}) { | 92 Map<String, dynamic> onPlatform, tags}) { |
75 _checkNotBuilt("test"); | 93 _checkNotBuilt("test"); |
76 | 94 |
77 var metadata = _metadata.merge(new Metadata.parse( | 95 var metadata = _metadata.merge(new Metadata.parse( |
78 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform, | 96 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform, |
79 tags: tags)); | 97 tags: tags)); |
80 | 98 |
81 _entries.add(new LocalTest(_prefix(name), metadata, () async { | 99 _entries.add(new LocalTest(_prefix(name), metadata, () async { |
82 // TODO(nweiz): It might be useful to throw an error here if a test starts | 100 // TODO(nweiz): It might be useful to throw an error here if a test starts |
83 // running while other tests from the same declarer are also running, | 101 // running while other tests from the same declarer are also running, |
84 // since they might share closurized state. | 102 // since they might share closurized state. |
85 | 103 |
86 await Invoker.current.waitForOutstandingCallbacks(() async { | 104 await Invoker.current.waitForOutstandingCallbacks(() async { |
87 await _runSetUps(); | 105 await _runSetUps(); |
88 await body(); | 106 await body(); |
89 }); | 107 }); |
90 await _runTearDowns(); | 108 await _runTearDowns(); |
91 })); | 109 }, trace: new Trace.current(2))); |
92 } | 110 } |
93 | 111 |
94 /// Creates a group of tests. | 112 /// Creates a group of tests. |
95 void group(String name, void body(), {String testOn, Timeout timeout, skip, | 113 void group(String name, void body(), {String testOn, Timeout timeout, skip, |
96 Map<String, dynamic> onPlatform, tags}) { | 114 Map<String, dynamic> onPlatform, tags}) { |
97 _checkNotBuilt("group"); | 115 _checkNotBuilt("group"); |
98 | 116 |
99 var metadata = _metadata.merge(new Metadata.parse( | 117 var metadata = _metadata.merge(new Metadata.parse( |
100 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform, | 118 testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform, |
101 tags: tags)); | 119 tags: tags)); |
| 120 var trace = new Trace.current(2); |
102 | 121 |
103 // Don't load the tests for a skipped group. | 122 // Don't load the tests for a skipped group. |
104 if (metadata.skip) { | 123 if (metadata.skip) { |
105 _entries.add(new Group(name, [], metadata: metadata)); | 124 _entries.add(new Group(name, [], metadata: metadata, trace: trace)); |
106 return; | 125 return; |
107 } | 126 } |
108 | 127 |
109 var declarer = new Declarer._(this, _prefix(name), metadata); | 128 var declarer = new Declarer._(this, _prefix(name), metadata, trace); |
110 declarer.declare(body); | 129 declarer.declare(body); |
111 _entries.add(declarer.build()); | 130 _entries.add(declarer.build()); |
112 } | 131 } |
113 | 132 |
114 /// Returns [name] prefixed with this declarer's group name. | 133 /// Returns [name] prefixed with this declarer's group name. |
115 String _prefix(String name) => _name == null ? name : "$_name $name"; | 134 String _prefix(String name) => _name == null ? name : "$_name $name"; |
116 | 135 |
117 /// Registers a function to be run before each test in this group. | 136 /// Registers a function to be run before each test in this group. |
118 void setUp(callback()) { | 137 void setUp(callback()) { |
119 _checkNotBuilt("setUp"); | 138 _checkNotBuilt("setUp"); |
120 _setUps.add(callback); | 139 _setUps.add(callback); |
121 } | 140 } |
122 | 141 |
123 /// Registers a function to be run after each test in this group. | 142 /// Registers a function to be run after each test in this group. |
124 void tearDown(callback()) { | 143 void tearDown(callback()) { |
125 _checkNotBuilt("tearDown"); | 144 _checkNotBuilt("tearDown"); |
126 _tearDowns.add(callback); | 145 _tearDowns.add(callback); |
127 } | 146 } |
128 | 147 |
129 /// Registers a function to be run once before all tests. | 148 /// Registers a function to be run once before all tests. |
130 void setUpAll(callback()) { | 149 void setUpAll(callback()) { |
131 _checkNotBuilt("setUpAll"); | 150 _checkNotBuilt("setUpAll"); |
| 151 _setUpAllTrace ??= new Trace.current(2); |
132 _setUpAlls.add(callback); | 152 _setUpAlls.add(callback); |
133 } | 153 } |
134 | 154 |
135 /// Registers a function to be run once after all tests. | 155 /// Registers a function to be run once after all tests. |
136 void tearDownAll(callback()) { | 156 void tearDownAll(callback()) { |
137 _checkNotBuilt("tearDownAll"); | 157 _checkNotBuilt("tearDownAll"); |
| 158 _tearDownAllTrace ??= new Trace.current(2); |
138 _tearDownAlls.add(callback); | 159 _tearDownAlls.add(callback); |
139 } | 160 } |
140 | 161 |
141 /// Finalizes and returns the group being declared. | 162 /// Finalizes and returns the group being declared. |
142 Group build() { | 163 Group build() { |
143 _checkNotBuilt("build"); | 164 _checkNotBuilt("build"); |
144 | 165 |
145 _built = true; | 166 _built = true; |
146 return new Group(_name, _entries.toList(), | 167 return new Group(_name, _entries.toList(), |
147 metadata: _metadata, | 168 metadata: _metadata, |
| 169 trace: _trace, |
148 setUpAll: _setUpAll, | 170 setUpAll: _setUpAll, |
149 tearDownAll: _tearDownAll); | 171 tearDownAll: _tearDownAll); |
150 } | 172 } |
151 | 173 |
152 /// Throws a [StateError] if [build] has been called. | 174 /// Throws a [StateError] if [build] has been called. |
153 /// | 175 /// |
154 /// [name] should be the name of the method being called. | 176 /// [name] should be the name of the method being called. |
155 void _checkNotBuilt(String name) { | 177 void _checkNotBuilt(String name) { |
156 if (!_built) return; | 178 if (!_built) return; |
157 throw new StateError("Can't call $name() once tests have begun running."); | 179 throw new StateError("Can't call $name() once tests have begun running."); |
(...skipping 24 matching lines...) Expand all Loading... |
182 return Future.forEach(tearDowns, _errorsDontStopTest); | 204 return Future.forEach(tearDowns, _errorsDontStopTest); |
183 }); | 205 }); |
184 } | 206 } |
185 | 207 |
186 /// Returns a [Test] that runs the callbacks in [_setUpAll]. | 208 /// Returns a [Test] that runs the callbacks in [_setUpAll]. |
187 Test get _setUpAll { | 209 Test get _setUpAll { |
188 if (_setUpAlls.isEmpty) return null; | 210 if (_setUpAlls.isEmpty) return null; |
189 | 211 |
190 return new LocalTest(_prefix("(setUpAll)"), _metadata, () { | 212 return new LocalTest(_prefix("(setUpAll)"), _metadata, () { |
191 return Future.forEach(_setUpAlls, (setUp) => setUp()); | 213 return Future.forEach(_setUpAlls, (setUp) => setUp()); |
192 }); | 214 }, trace: _setUpAllTrace); |
193 } | 215 } |
194 | 216 |
195 /// Returns a [Test] that runs the callbacks in [_tearDownAll]. | 217 /// Returns a [Test] that runs the callbacks in [_tearDownAll]. |
196 Test get _tearDownAll { | 218 Test get _tearDownAll { |
197 if (_tearDownAlls.isEmpty) return null; | 219 if (_tearDownAlls.isEmpty) return null; |
198 | 220 |
199 return new LocalTest(_prefix("(tearDownAll)"), _metadata, () { | 221 return new LocalTest(_prefix("(tearDownAll)"), _metadata, () { |
200 return Invoker.current.unclosable(() { | 222 return Invoker.current.unclosable(() { |
201 return Future.forEach(_tearDownAlls.reversed, _errorsDontStopTest); | 223 return Future.forEach(_tearDownAlls.reversed, _errorsDontStopTest); |
202 }); | 224 }); |
203 }); | 225 }, trace: _tearDownAllTrace); |
204 } | 226 } |
205 | 227 |
206 /// Runs [body] with special error-handling behavior. | 228 /// Runs [body] with special error-handling behavior. |
207 /// | 229 /// |
208 /// Errors emitted [body] will still cause the current test to fail, but they | 230 /// Errors emitted [body] will still cause the current test to fail, but they |
209 /// won't cause it to *stop*. In particular, they won't remove any outstanding | 231 /// won't cause it to *stop*. In particular, they won't remove any outstanding |
210 /// callbacks registered outside of [body]. | 232 /// callbacks registered outside of [body]. |
211 Future _errorsDontStopTest(body()) { | 233 Future _errorsDontStopTest(body()) { |
212 var completer = new Completer(); | 234 var completer = new Completer(); |
213 | 235 |
214 Invoker.current.addOutstandingCallback(); | 236 Invoker.current.addOutstandingCallback(); |
215 Invoker.current.waitForOutstandingCallbacks(() { | 237 Invoker.current.waitForOutstandingCallbacks(() { |
216 new Future.sync(body).whenComplete(completer.complete); | 238 new Future.sync(body).whenComplete(completer.complete); |
217 }).then((_) => Invoker.current.removeOutstandingCallback()); | 239 }).then((_) => Invoker.current.removeOutstandingCallback()); |
218 | 240 |
219 return completer.future; | 241 return completer.future; |
220 } | 242 } |
221 } | 243 } |
OLD | NEW |