OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, 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 /** | |
6 * This file is sourced from unittest_html, unittest_dom, and unittest_vm. | |
7 * These libraries shold also source 'config.dart' and should define a class | |
8 * called [PlatformConfiguration] that implements [Configuration]. | |
9 */ | |
10 | |
11 /** [Configuration] used by the unittest library. */ | |
12 Configuration _config = null; | |
13 | |
14 /** | |
15 * Description text of the current test group. If multiple groups are nested, | |
16 * this will contain all of their text concatenated. | |
17 */ | |
18 String _currentGroup = ''; | |
19 | |
20 /** Tests executed in this suite. */ | |
21 List<TestCase> _tests; | |
22 | |
23 /** | |
24 * Callback used to run tests. Entrypoints can replace this with their own | |
25 * if they want. | |
26 */ | |
27 Function _testRunner; | |
28 | |
29 /** Current test being executed. */ | |
30 int _currentTest = 0; | |
31 | |
32 /** Total number of callbacks that have been executed in the current test. */ | |
33 int _callbacksCalled = 0; | |
34 | |
35 final _UNINITIALIZED = 0; | |
36 final _READY = 1; | |
37 final _RUNNING_TEST = 2; | |
38 | |
39 /** | |
40 * Whether an undetected error occurred while running the last test. These | |
41 * errors are commonly caused by DOM callbacks that were not guarded in a | |
42 * try-catch block. | |
43 */ | |
44 final _UNCAUGHT_ERROR = 3; | |
45 | |
46 int _state = _UNINITIALIZED; | |
47 | |
48 final _PASS = 'pass'; | |
49 final _FAIL = 'fail'; | |
50 final _ERROR = 'error'; | |
51 | |
52 /** Creates an expectation for the given value. */ | |
53 Expectation expect(value) => new Expectation(value); | |
54 | |
55 /** Evaluates the given function and validates that it throws an exception. */ | |
56 void expectThrow(function) { | |
57 bool threw = false; | |
58 try { | |
59 function(); | |
60 } catch (var e) { | |
61 threw = true; | |
62 } | |
63 Expect.equals(true, threw, 'Expected exception but none was thrown.'); | |
64 } | |
65 | |
66 /** | |
67 * Creates a new test case with the given description and body. The | |
68 * description will include the descriptions of any surrounding group() | |
69 * calls. | |
70 */ | |
71 void test(String spec, TestFunction body) { | |
72 _ensureInitialized(); | |
73 | |
74 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0)); | |
75 } | |
76 | |
77 /** | |
78 * Creates a new async test case with the given description and body. The | |
79 * description will include the descriptions of any surrounding group() | |
80 * calls. | |
81 */ | |
82 void asyncTest(String spec, int callbacks, TestFunction body) { | |
83 _ensureInitialized(); | |
84 | |
85 final testCase = new TestCase( | |
86 _tests.length + 1, _fullSpec(spec), body, callbacks); | |
87 _tests.add(testCase); | |
88 | |
89 if (callbacks < 1) { | |
90 testCase.error( | |
91 'Async tests must wait for at least one callback ', ''); | |
92 } | |
93 } | |
94 | |
95 /** | |
96 * Creates a new named group of tests. Calls to group() or test() within the | |
97 * body of the function passed to this will inherit this group's description. | |
98 */ | |
99 void group(String description, void body()) { | |
100 _ensureInitialized(); | |
101 | |
102 // Concatenate the new group. | |
103 final oldGroup = _currentGroup; | |
104 if (_currentGroup != '') { | |
105 // Add a space. | |
106 _currentGroup = '$_currentGroup $description'; | |
107 } else { | |
108 // The first group. | |
109 _currentGroup = description; | |
110 } | |
111 | |
112 try { | |
113 body(); | |
114 } finally { | |
115 // Now that the group is over, restore the previous one. | |
116 _currentGroup = oldGroup; | |
117 } | |
118 } | |
119 | |
120 /** Called by subclasses to indicate that an asynchronous test completed. */ | |
121 void callbackDone() { | |
122 _callbacksCalled++; | |
123 final testCase = _tests[_currentTest]; | |
124 if (testCase.callbacks == 0) { | |
125 testCase.error( | |
126 "Can't call callbackDone() on a synchronous test", ''); | |
127 _state = _UNCAUGHT_ERROR; | |
128 } else if (_callbacksCalled > testCase.callbacks) { | |
129 final expected = testCase.callbacks; | |
130 testCase.error( | |
131 'More calls to callbackDone() than expected. ' | |
132 + 'Actual: ${_callbacksCalled}, expected: ${expected}', ''); | |
133 _state = _UNCAUGHT_ERROR; | |
134 } else if ((_callbacksCalled == testCase.callbacks) && | |
135 (_state != _RUNNING_TEST)) { | |
136 testCase.pass(); | |
137 _currentTest++; | |
138 _testRunner(); | |
139 } | |
140 } | |
141 | |
142 /** Runs [callback] at the end of the event loop. */ | |
143 _defer(void callback()) { | |
144 // Exploit isolate ports as a platform-independent mechanism to queue a | |
145 // message at the end of the event loop. | |
146 // TODO(sigmund): expose this functionality somewhere in our libraries. | |
147 final port = new ReceivePort(); | |
148 port.receive((msg, reply) { | |
149 callback(); | |
150 port.close(); | |
151 }); | |
152 port.toSendPort().send(null, null); | |
153 } | |
154 | |
155 /** Runs all queued tests, one at a time. */ | |
156 _runTests() { | |
157 _config.onStart(); | |
158 | |
159 _defer(() { | |
160 assert (_currentTest == 0); | |
161 _testRunner(); | |
162 }); | |
163 } | |
164 | |
165 /** Runs a single test. */ | |
166 _runTest(TestCase testCase) { | |
167 try { | |
168 _callbacksCalled = 0; | |
169 _state = _RUNNING_TEST; | |
170 | |
171 testCase.test(); | |
172 | |
173 if (_state != _UNCAUGHT_ERROR) { | |
174 if (testCase.callbacks == _callbacksCalled) { | |
175 testCase.pass(); | |
176 } | |
177 } | |
178 | |
179 } catch (ExpectException e, var trace) { | |
180 if (_state != _UNCAUGHT_ERROR) { | |
181 //TODO(pquitslund) remove guard once dartc reliably propagates traces | |
182 testCase.fail(e.message, trace == null ? '' : trace.toString()); | |
183 } | |
184 } catch (var e, var trace) { | |
185 if (_state != _UNCAUGHT_ERROR) { | |
186 //TODO(pquitslund) remove guard once dartc reliably propagates traces | |
187 testCase.error('Caught ${e}', trace == null ? '' : trace.toString()); | |
188 } | |
189 } finally { | |
190 _state = _READY; | |
191 } | |
192 } | |
193 | |
194 /** | |
195 * Runs a batch of tests, yielding whenever an asynchronous test starts | |
196 * running. Tests will resume executing when such asynchronous test calls | |
197 * [done] or if it fails with an exception. | |
198 */ | |
199 _nextBatch() { | |
200 while (_currentTest < _tests.length) { | |
201 final testCase = _tests[_currentTest]; | |
202 | |
203 _runTest(testCase); | |
204 | |
205 if (!testCase.isComplete && testCase.callbacks > 0) return; | |
206 | |
207 _currentTest++; | |
208 } | |
209 | |
210 _completeTests(); | |
211 } | |
212 | |
213 /** Publish results on the page and notify controller. */ | |
214 _completeTests() { | |
215 _state = _UNINITIALIZED; | |
216 | |
217 int testsPassed_ = 0; | |
218 int testsFailed_ = 0; | |
219 int testsErrors_ = 0; | |
220 | |
221 for (TestCase t in _tests) { | |
222 switch (t.result) { | |
223 case _PASS: testsPassed_++; break; | |
224 case _FAIL: testsFailed_++; break; | |
225 case _ERROR: testsErrors_++; break; | |
226 } | |
227 } | |
228 | |
229 _config.onDone(testsPassed_, testsFailed_, testsErrors_, _tests); | |
230 } | |
231 | |
232 String _fullSpec(String spec) { | |
233 if (spec === null) return '$_currentGroup'; | |
234 return _currentGroup != '' ? '$_currentGroup $spec' : spec; | |
235 } | |
236 | |
237 /** | |
238 * Lazily initializes the test library if not already initialized. | |
239 */ | |
240 _ensureInitialized() { | |
241 if (_state != _UNINITIALIZED) return; | |
242 | |
243 _tests = <TestCase>[]; | |
244 _currentGroup = ''; | |
245 _state = _READY; | |
246 _testRunner = _nextBatch; | |
247 | |
248 if (_config == null) { | |
249 // TODO(sigmund): make this [new Configuration], set configuration | |
250 // for each platform in test.dart | |
251 _config = new PlatformConfiguration(); | |
252 } | |
253 _config.onInit(); | |
254 | |
255 // Immediately queue the suite up. It will run after a timeout (i.e. after | |
256 // main() has returned). | |
257 _defer(_runTests); | |
258 } | |
259 | |
260 /** | |
261 * Wraps an value and provides an "==" operator that can be used to verify that | |
262 * the value matches a given expectation. | |
263 */ | |
264 class Expectation { | |
265 final _value; | |
266 | |
267 Expectation(this._value); | |
268 | |
269 /** Asserts that the value is equivalent to [expected]. */ | |
270 void equals(expected) { | |
271 // Use the type-specialized versions when appropriate to give better | |
272 // error messages. | |
273 if (_value is String && expected is String) { | |
274 Expect.stringEquals(expected, _value); | |
275 } else if (_value is Map && expected is Map) { | |
276 Expect.mapEquals(expected, _value); | |
277 } else if (_value is Set && expected is Set) { | |
278 Expect.setEquals(expected, _value); | |
279 } else { | |
280 Expect.equals(expected, _value); | |
281 } | |
282 } | |
283 | |
284 /** | |
285 * Asserts that the difference between [expected] and the value is within | |
286 * [tolerance]. If no tolerance is given, it is assumed to be the value 4 | |
287 * significant digits smaller than the expected value. | |
288 */ | |
289 void approxEquals(num expected, | |
290 [num tolerance = null, String reason = null]) { | |
291 Expect.approxEquals(expected, _value, tolerance: tolerance, reason: reason); | |
292 } | |
293 | |
294 /** Asserts that the value is [null]. */ | |
295 void isNull() { | |
296 Expect.equals(null, _value); | |
297 } | |
298 | |
299 /** Asserts that the value is not [null]. */ | |
300 void isNotNull() { | |
301 Expect.notEquals(null, _value); | |
302 } | |
303 | |
304 /** Asserts that the value is [true]. */ | |
305 void isTrue() { | |
306 Expect.equals(true, _value); | |
307 } | |
308 | |
309 /** Asserts that the value is [false]. */ | |
310 void isFalse() { | |
311 Expect.equals(false, _value); | |
312 } | |
313 | |
314 /** Asserts that the value has the same elements as [expected]. */ | |
315 void equalsCollection(Collection expected) { | |
316 Expect.listEquals(expected, _value); | |
317 } | |
318 | |
319 /** | |
320 * Checks that every element of [expected] is also in [actual], and that | |
321 * every element of [actual] is also in [expected]. | |
322 */ | |
323 void equalsSet(Iterable expected) { | |
324 Expect.setEquals(expected, _value); | |
325 } | |
326 } | |
327 | |
328 /** Summarizes information about a single test case. */ | |
329 class TestCase { | |
330 /** Identifier for this test. */ | |
331 final id; | |
332 | |
333 /** A description of what the test is specifying. */ | |
334 final String description; | |
335 | |
336 /** The body of the test case. */ | |
337 final TestFunction test; | |
338 | |
339 /** Total number of callbacks to wait for before the test completes. */ | |
340 int callbacks; | |
341 | |
342 /** Error or failure message. */ | |
343 String message = ''; | |
344 | |
345 /** | |
346 * One of [_PASS], [_FAIL], or [_ERROR] or [null] if the test hasn't run yet. | |
347 */ | |
348 String result; | |
349 | |
350 /** Stack trace associated with this test, or null if it succeeded. */ | |
351 String stackTrace; | |
352 | |
353 Date startTime; | |
354 | |
355 Duration runningTime; | |
356 | |
357 TestCase(this.id, this.description, this.test, this.callbacks); | |
358 | |
359 bool get isComplete() => result != null; | |
360 | |
361 void pass() { | |
362 result = _PASS; | |
363 } | |
364 | |
365 void fail(String message_, String stackTrace_) { | |
366 result = _FAIL; | |
367 this.message = message_; | |
368 this.stackTrace = stackTrace_; | |
369 } | |
370 | |
371 void error(String message_, String stackTrace_) { | |
372 result = _ERROR; | |
373 this.message = message_; | |
374 this.stackTrace = stackTrace_; | |
375 } | |
376 } | |
377 | |
378 typedef void TestFunction(); | |
OLD | NEW |