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 * A library for writing dart unit tests. | |
7 * | |
8 * ##Concepts## | |
9 * | |
10 * * Tests: Tests are specified via the top-level function [test], they can be | |
11 * organized together using [group]. | |
12 * * Checks: Test expectations can be specified via [expect] (see methods in | |
13 * [Expectation]), [expectThrows], or using assertions with the [Expect] | |
14 * class. | |
15 * * Configuration: The framework can be adapted by calling [configure] with a | |
16 * configuration. Common configurations can be found in this package under: | |
17 * 'dom\_config.dart', 'html\_config.dart', and 'vm\_config.dart'. | |
18 * | |
19 * ##Examples## | |
20 * | |
21 * A trivial test: | |
22 * | |
23 * #import('path-to-dart/lib/unittest/unitest.dart'); | |
24 * main() { | |
25 * test('this is a test', () { | |
26 * int x = 2 + 3; | |
27 * expect(x).equals(5); | |
28 * }); | |
29 * } | |
30 * | |
31 * Multiple tests: | |
32 * | |
33 * #import('path-to-dart/lib/unittest/unitest.dart'); | |
34 * main() { | |
35 * test('this is a test', () { | |
36 * int x = 2 + 3; | |
37 * expect(x).equals(5); | |
38 * }); | |
39 * test('this is another test', () { | |
40 * int x = 2 + 3; | |
41 * expect(x).equals(5); | |
42 * }); | |
43 * } | |
44 * | |
45 * Multiple tests, grouped by category: | |
46 * | |
47 * #import('path-to-dart/lib/unittest/unitest.dart'); | |
48 * main() { | |
49 * group('group A', () { | |
50 * test('test A.1', () { | |
51 * int x = 2 + 3; | |
52 * expect(x).equals(5); | |
53 * }); | |
54 * test('test A.2', () { | |
55 * int x = 2 + 3; | |
56 * expect(x).equals(5); | |
57 * }); | |
58 * }); | |
59 * group('group B', () { | |
60 * test('this B.1', () { | |
61 * int x = 2 + 3; | |
62 * expect(x).equals(5); | |
63 * }); | |
64 * }); | |
65 * } | |
66 * | |
67 * Asynchronous tests: under the current API (soon to be deprecated): | |
68 * | |
69 * #import('path-to-dart/lib/unittest/unitest.dart'); | |
70 * #import('dart:dom'); | |
71 * main() { | |
72 * // use [asyncTest], indicate the expected number of callbacks: | |
73 * asyncTest('this is a test', 1, () { | |
74 * window.setTimeout(() { | |
75 * int x = 2 + 3; | |
76 * expect(x).equals(5); | |
77 * // invoke [callbackDone] at the end of the callback. | |
78 * callbackDone(); | |
79 * }, 0); | |
80 * }); | |
81 * } | |
82 * | |
83 * We plan to replace this with a different API, one API we are considering is: | |
84 * | |
85 * #import('path-to-dart/lib/unittest/unitest.dart'); | |
86 * #import('dart:dom'); | |
87 * main() { | |
88 * test('this is a test', () { | |
89 * // wrap the callback of an asynchronous call with [later] | |
90 * window.setTimeout(later(() { | |
91 * int x = 2 + 3; | |
92 * expect(x).equals(5); | |
93 * }), 0); | |
94 * }); | |
95 * } | |
96 */ | |
97 #library('unittest'); | |
98 | |
99 #import('dart:isolate'); | |
100 | |
101 #source('config.dart'); | |
102 #source('expectation.dart'); | |
103 #source('test_case.dart'); | |
104 | |
105 /** [Configuration] used by the unittest library. */ | |
106 Configuration _config = null; | |
107 | |
108 /** Set the [Configuration] used by the unittest library. */ | |
109 void configure(Configuration config) { | |
110 _config = config; | |
111 } | |
112 | |
113 /** | |
114 * Description text of the current test group. If multiple groups are nested, | |
115 * this will contain all of their text concatenated. | |
116 */ | |
117 String _currentGroup = ''; | |
118 | |
119 /** Tests executed in this suite. */ | |
120 List<TestCase> _tests; | |
121 | |
122 /** | |
123 * Callback used to run tests. Entrypoints can replace this with their own | |
124 * if they want. | |
125 */ | |
126 Function _testRunner; | |
127 | |
128 /** Current test being executed. */ | |
129 int _currentTest = 0; | |
130 | |
131 /** Total number of callbacks that have been executed in the current test. */ | |
132 int _callbacksCalled = 0; | |
133 | |
134 final _UNINITIALIZED = 0; | |
135 final _READY = 1; | |
136 final _RUNNING_TEST = 2; | |
137 | |
138 /** | |
139 * Whether an undetected error occurred while running the last test. These | |
140 * errors are commonly caused by DOM callbacks that were not guarded in a | |
141 * try-catch block. | |
142 */ | |
143 final _UNCAUGHT_ERROR = 3; | |
144 | |
145 int _state = _UNINITIALIZED; | |
146 | |
147 final _PASS = 'pass'; | |
148 final _FAIL = 'fail'; | |
149 final _ERROR = 'error'; | |
150 | |
151 /** Creates an expectation for the given value. */ | |
152 Expectation expect(value) => new Expectation(value); | |
153 | |
154 /** Evaluates the given function and validates that it throws an exception. */ | |
155 void expectThrow(function) { | |
156 bool threw = false; | |
157 try { | |
158 function(); | |
159 } catch (var e) { | |
160 threw = true; | |
161 } | |
162 Expect.equals(true, threw, 'Expected exception but none was thrown.'); | |
163 } | |
164 | |
165 /** | |
166 * Creates a new test case with the given description and body. The | |
167 * description will include the descriptions of any surrounding group() | |
168 * calls. | |
169 */ | |
170 void test(String spec, TestFunction body) { | |
171 _ensureInitialized(); | |
172 | |
173 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0)); | |
174 } | |
175 | |
176 /** | |
177 * Creates a new async test case with the given description and body. The | |
178 * description will include the descriptions of any surrounding group() | |
179 * calls. | |
180 */ | |
181 // TODO(sigmund): deprecate this API | |
182 void asyncTest(String spec, int callbacks, TestFunction body) { | |
183 _ensureInitialized(); | |
184 | |
185 final testCase = new TestCase( | |
186 _tests.length + 1, _fullSpec(spec), body, callbacks); | |
187 _tests.add(testCase); | |
188 | |
189 if (callbacks < 1) { | |
190 testCase.error( | |
191 'Async tests must wait for at least one callback ', ''); | |
192 } | |
193 } | |
194 | |
195 class _Sentinel { | |
196 static final _sentinel = const _Sentinel(); | |
197 | |
198 const _Sentinel(); | |
199 } | |
200 | |
201 | |
202 /** | |
203 * Indicate to the unittest framework that a callback is expected. [callback] | |
204 * can take any number of arguments between 0 and 4. | |
205 * | |
206 * The framework will wait for the callback to run before it continues with the | |
207 * following test. The callback must excute once and only once. Using [later] | |
208 * will also ensure that errors that occur within the callback are tracked and | |
209 * reported by the unittest framework. | |
210 */ | |
211 // TODO(sigmund): expose this functionality | |
212 Function _later(Function callback) { | |
213 Expect.isTrue(_currentTest < _tests.length); | |
214 var testCase = _tests[_currentTest]; | |
215 testCase.callbacks++; | |
216 // We simulate spread arguments using named arguments: | |
217 // Note: this works in the vm and dart2js, but not in frog. | |
218 return ([arg0 = _Sentinel.value, arg1 = _Sentinel.value, | |
219 arg2 = _Sentinel.value, arg3 = _Sentinel.value, | |
220 arg4 = _Sentinel.value]) { | |
221 _guard(() { | |
222 if (arg0 == _Sentinel.value) { | |
223 callback(); | |
224 } else if (arg1 == _Sentinel.value) { | |
225 callback(arg0); | |
226 } else if (arg3 == _Sentinel.value) { | |
Emily Fortuna
2012/04/12 19:24:41
arg2!!!
Siggi Cherem (dart-lang)
2012/04/12 21:13:36
Done.
| |
227 callback(arg0, arg1); | |
228 } else if (arg3 == _Sentinel.value) { | |
229 callback(arg0, arg1, arg2); | |
230 } else if (arg4 == _Sentinel.value) { | |
231 callback(arg0, arg1, arg2, arg3); | |
232 } else { | |
233 testCase.error( | |
234 'unittest lib does not support callbacks with more than 4 arguments', | |
235 ''); | |
236 _state = _UNCAUGHT_ERROR; | |
237 } | |
238 }, callbackDone); | |
239 }; | |
240 } | |
241 | |
242 // TODO(sigmund): expose this functionality | |
243 Function _later0(Function callback) { | |
244 Expect.isTrue(_currentTest < _tests.length); | |
245 var testCase = _tests[_currentTest]; | |
246 testCase.callbacks++; | |
247 return () { | |
248 _guard(() => callback(), callbackDone); | |
249 }; | |
250 } | |
251 | |
252 // TODO(sigmund): expose this functionality | |
253 Function _later1(Function callback) { | |
254 Expect.isTrue(_currentTest < _tests.length); | |
255 var testCase = _tests[_currentTest]; | |
256 testCase.callbacks++; | |
257 return (arg0) { | |
258 _guard(() => callback(arg0), callbackDone); | |
259 }; | |
260 } | |
261 | |
262 // TODO(sigmund): expose this functionality | |
263 Function _later2(Function callback) { | |
264 Expect.isTrue(_currentTest < _tests.length); | |
265 var testCase = _tests[_currentTest]; | |
266 testCase.callbacks++; | |
267 return (arg0, arg1) { | |
268 _guard(() => callback(arg0, arg1), callbackDone); | |
269 }; | |
270 } | |
271 | |
272 /** | |
273 * Creates a new named group of tests. Calls to group() or test() within the | |
274 * body of the function passed to this will inherit this group's description. | |
275 */ | |
276 void group(String description, void body()) { | |
277 _ensureInitialized(); | |
278 | |
279 // Concatenate the new group. | |
280 final oldGroup = _currentGroup; | |
281 if (_currentGroup != '') { | |
282 // Add a space. | |
283 _currentGroup = '$_currentGroup $description'; | |
284 } else { | |
285 // The first group. | |
286 _currentGroup = description; | |
287 } | |
288 | |
289 try { | |
290 body(); | |
291 } finally { | |
292 // Now that the group is over, restore the previous one. | |
293 _currentGroup = oldGroup; | |
294 } | |
295 } | |
296 | |
297 /** Called by subclasses to indicate that an asynchronous test completed. */ | |
298 void callbackDone() { | |
299 _callbacksCalled++; | |
300 final testCase = _tests[_currentTest]; | |
301 if (_callbacksCalled > testCase.callbacks) { | |
302 final expected = testCase.callbacks; | |
303 testCase.error( | |
304 'More calls to callbackDone() than expected. ' | |
305 + 'Actual: ${_callbacksCalled}, expected: ${expected}', ''); | |
306 _state = _UNCAUGHT_ERROR; | |
307 } else if ((_callbacksCalled == testCase.callbacks) && | |
308 (_state != _RUNNING_TEST)) { | |
309 if (testCase.result == null) testCase.pass(); | |
310 _currentTest++; | |
311 _testRunner(); | |
312 } | |
313 } | |
314 | |
315 void notifyError(String msg, String trace) { | |
316 if (_currentTest < _tests.length) { | |
317 final testCase = _tests[_currentTest]; | |
318 testCase.error(msg, trace); | |
319 _state = _UNCAUGHT_ERROR; | |
320 if (testCase.callbacks > 0) { | |
321 _currentTest++; | |
322 _testRunner(); | |
323 } | |
324 } | |
325 } | |
326 | |
327 /** Runs [callback] at the end of the event loop. */ | |
328 _defer(void callback()) { | |
329 // Exploit isolate ports as a platform-independent mechanism to queue a | |
330 // message at the end of the event loop. | |
331 // TODO(sigmund): expose this functionality somewhere in our libraries. | |
332 final port = new ReceivePort(); | |
333 port.receive((msg, reply) { | |
334 callback(); | |
335 port.close(); | |
336 }); | |
337 port.toSendPort().send(null, null); | |
338 } | |
339 | |
340 /** Runs all queued tests, one at a time. */ | |
341 _runTests() { | |
342 _config.onStart(); | |
343 | |
344 _defer(() { | |
345 assert (_currentTest == 0); | |
346 _testRunner(); | |
347 }); | |
348 } | |
349 | |
350 /** | |
351 * Run [tryBody] guarded in a try-catch block. If an exception is thrown, update | |
352 * the [_currentTest] status accordingly. | |
353 */ | |
354 _guard(tryBody, [finallyBody]) { | |
355 final testCase = _tests[_currentTest]; | |
356 try { | |
357 tryBody(); | |
358 } catch (ExpectException e, var trace) { | |
359 if (_state != _UNCAUGHT_ERROR) { | |
360 //TODO(pquitslund) remove guard once dartc reliably propagates traces | |
361 testCase.fail(e.message, trace == null ? '' : trace.toString()); | |
362 } | |
363 } catch (var e, var trace) { | |
364 if (_state != _UNCAUGHT_ERROR) { | |
365 //TODO(pquitslund) remove guard once dartc reliably propagates traces | |
366 testCase.error('Caught ${e}', trace == null ? '' : trace.toString()); | |
367 } | |
368 } finally { | |
369 _state = _READY; | |
370 if (finallyBody != null) finallyBody(); | |
371 } | |
372 } | |
373 | |
374 /** | |
375 * Runs a batch of tests, yielding whenever an asynchronous test starts | |
376 * running. Tests will resume executing when such asynchronous test calls | |
377 * [done] or if it fails with an exception. | |
378 */ | |
379 _nextBatch() { | |
380 while (_currentTest < _tests.length) { | |
381 final testCase = _tests[_currentTest]; | |
382 | |
383 _guard(() { | |
384 _callbacksCalled = 0; | |
385 _state = _RUNNING_TEST; | |
386 | |
387 testCase.test(); | |
388 | |
389 if (_state != _UNCAUGHT_ERROR) { | |
390 if (testCase.callbacks == _callbacksCalled) { | |
391 testCase.pass(); | |
392 } | |
393 } | |
394 }); | |
395 | |
396 if (!testCase.isComplete && testCase.callbacks > 0) return; | |
397 | |
398 _currentTest++; | |
399 } | |
400 | |
401 _completeTests(); | |
402 } | |
403 | |
404 /** Publish results on the page and notify controller. */ | |
405 _completeTests() { | |
406 _state = _UNINITIALIZED; | |
407 | |
408 int testsPassed_ = 0; | |
409 int testsFailed_ = 0; | |
410 int testsErrors_ = 0; | |
411 | |
412 for (TestCase t in _tests) { | |
413 switch (t.result) { | |
414 case _PASS: testsPassed_++; break; | |
415 case _FAIL: testsFailed_++; break; | |
416 case _ERROR: testsErrors_++; break; | |
417 } | |
418 } | |
419 | |
420 _config.onDone(testsPassed_, testsFailed_, testsErrors_, _tests); | |
421 } | |
422 | |
423 String _fullSpec(String spec) { | |
424 if (spec === null) return '$_currentGroup'; | |
425 return _currentGroup != '' ? '$_currentGroup $spec' : spec; | |
426 } | |
427 | |
428 /** | |
429 * Lazily initializes the test library if not already initialized. | |
430 */ | |
431 _ensureInitialized() { | |
432 if (_state != _UNINITIALIZED) return; | |
433 | |
434 _tests = <TestCase>[]; | |
435 _currentGroup = ''; | |
436 _state = _READY; | |
437 _testRunner = _nextBatch; | |
438 | |
439 if (_config == null) { | |
440 _config = new Configuration(); | |
441 } | |
442 _config.onInit(); | |
443 | |
444 // Immediately queue the suite up. It will run after a timeout (i.e. after | |
445 // main() has returned). | |
446 _defer(_runTests); | |
447 } | |
448 | |
449 /** Signature for a test function. */ | |
450 typedef void TestFunction(); | |
OLD | NEW |