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