OLD | NEW |
---|---|
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 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 | 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 // TODO(rnystrom): This code is gradually moving from a Java/JUnit style to | |
6 // something closer to JS/Jasmine. Eventually, the UnitTestSuite class can go | |
7 // away completely (or become private to this library) and the only exposed API | |
8 // will be group()/test()/expect(). Until then, both ways are supported, which | |
9 // is why things look a bit weird in here. | |
10 | |
11 UnitTestSuite _currentSuite; | |
12 | |
13 /** | |
14 * Description text of the current test group. If multiple groups are nested, | |
15 * this will contain all of their text concatenated. | |
16 */ | |
17 String _currentGroup = ''; | |
18 | |
5 /** Base class for unit test suites run in a browser. */ | 19 /** Base class for unit test suites run in a browser. */ |
6 class UnitTestSuite { | 20 class UnitTestSuite { |
7 | 21 |
8 /** Tests executed in this suite. */ | 22 /** Tests executed in this suite. */ |
9 List<TestCase> _tests; | 23 List<TestCase> _tests; |
10 | 24 |
11 /** | |
12 * Description text of the current test group. If multiple groups are nested, | |
13 * this will contain all of their text concatenated. | |
14 */ | |
15 String _group; | |
16 | |
17 /** Whether this suite is run within dartium layout tests. */ | 25 /** Whether this suite is run within dartium layout tests. */ |
18 bool _isLayoutTest; | 26 bool _isLayoutTest; |
19 | 27 |
20 /** Current test being executed. */ | 28 /** Current test being executed. */ |
21 int _currentTest; | 29 int _currentTest; |
22 | 30 |
23 /** Total number of callbacks that have been executed in the current test. */ | 31 /** Total number of callbacks that have been executed in the current test. */ |
24 int _callbacksCalled; | 32 int _callbacksCalled; |
25 | 33 |
26 /** | 34 /** |
27 * Whether an undetected error occurred while running the last test. This | 35 * Whether an undetected error occurred while running the last test. This |
28 * errors are commonly caused by DOM callbacks that were not guarded in a | 36 * errors are commonly caused by DOM callbacks that were not guarded in a |
29 * try-catch block. | 37 * try-catch block. |
30 */ | 38 */ |
31 bool _uncaughtError; | 39 bool _uncaughtError; |
32 EventListener _onErrorClosure; | 40 EventListener _onErrorClosure; |
33 | 41 |
34 // TODO(sigmund): remove isLayoutTest argument after converting all DOM tests | 42 // TODO(sigmund): remove isLayoutTest argument after converting all DOM tests |
35 // to use the named constructor below. | 43 // to use the named constructor below. |
36 // TODO(vsm): remove the ignoredWindow parameter once all tests are fixed. | 44 // TODO(vsm): remove the ignoredWindow parameter once all tests are fixed. |
37 UnitTestSuite([var ignoredWindow = null, bool isLayoutTest = false]) | 45 UnitTestSuite([var ignoredWindow = null, bool isLayoutTest = false]) |
38 : _isLayoutTest = isLayoutTest, | 46 : _isLayoutTest = isLayoutTest, |
39 _tests = new List<TestCase>(), | 47 _tests = new List<TestCase>(), |
40 _currentTest = 0, | 48 _currentTest = 0, |
41 _callbacksCalled = 0 { | 49 _callbacksCalled = 0 { |
42 _onErrorClosure = (e) { _onError(e); }; | 50 _onErrorClosure = (e) { _onError(e); }; |
51 if (_currentSuite != null) { | |
52 throw 'Cannot have two UnitTestSuites in flight at the same time.'; | |
53 } | |
54 _currentSuite = this; | |
43 } | 55 } |
44 | 56 |
45 // TODO(jacobr): remove the ignoredWindow parameter once all tests are fixed. | 57 // TODO(jacobr): remove the ignoredWindow parameter once all tests are fixed. |
46 UnitTestSuite.forLayoutTests([var ignoredWindow = null]) | 58 UnitTestSuite.forLayoutTests([var ignoredWindow = null]) |
47 : _isLayoutTest = true, | 59 : _isLayoutTest = true, |
48 _tests = new List<TestCase>(), | 60 _tests = new List<TestCase>(), |
49 _currentTest = 0, | 61 _currentTest = 0, |
50 _callbacksCalled = 0 {} | 62 _callbacksCalled = 0 { |
63 if (_currentSuite != null) { | |
64 throw 'Cannot have two UnitTestSuites in flight at the same time.'; | |
65 } | |
66 _currentSuite = this; | |
67 } | |
51 | 68 |
52 /** Starts running the testsuite. */ | 69 /** Starts running the testsuite. */ |
53 void run() { | 70 void run() { |
54 final listener = (e) { | 71 listener(e) { |
55 _group = ''; | 72 _currentGroup = ''; |
56 setUpTestSuite(); | 73 setUpTestSuite(); |
57 runTests(); | 74 runTests(); |
75 | |
76 // This suite is done now, so discard it. | |
77 _currentSuite = null; | |
58 }; | 78 }; |
79 | |
59 try { | 80 try { |
60 window.dynamic.on.contentLoaded.add(listener); | 81 window.dynamic.on.contentLoaded.add(listener); |
61 } catch(var e) { | 82 } catch(var e) { |
62 // TODO(jacobr): remove this horrible hack to work around dartc bugs. | 83 // TODO(jacobr): remove this horrible hack to work around dartc bugs. |
63 window.dynamic.addEventListener("DOMContentLoaded", listener, false); | 84 window.dynamic.addEventListener("DOMContentLoaded", listener, false); |
64 } | 85 } |
65 } | 86 } |
66 | 87 |
67 /** Subclasses should override this method to register tests. */ | 88 /** Subclasses should override this method to register tests. */ |
68 void setUpTestSuite() {} | 89 void setUpTestSuite() {} |
69 | 90 |
70 /** Enqueues a synchronous test. */ | 91 /** Enqueues a synchronous test. */ |
71 UnitTestSuite addTest(TestFunction body) => test(null, body); | 92 UnitTestSuite addTest(TestFunction body) { |
93 test(null, body); | |
94 } | |
72 | 95 |
73 /** Adds the tests defined by the given TestSet to this suite. */ | 96 /** Adds the tests defined by the given TestSet to this suite. */ |
74 void addTestSet(TestSet test) { | 97 void addTestSet(TestSet test) { |
75 test._bindToSuite(this); | 98 test._bindToSuite(this); |
76 test.setup(); | 99 test.setup(); |
77 } | 100 } |
78 | 101 |
79 /** Adds the tests defined by the given TestSets to this suite. */ | 102 /** Adds the tests defined by the given TestSets to this suite. */ |
80 void addTestSets(Iterable<TestSet> tests) { | 103 void addTestSets(Iterable<TestSet> tests) { |
81 for (TestSet test in tests) { | 104 for (TestSet test in tests) { |
82 addTestSet(test); | 105 addTestSet(test); |
83 } | 106 } |
84 } | 107 } |
85 | 108 |
86 /** Enqueues an asynchronous test that waits for [callbacks] callbacks. */ | 109 /** Enqueues an asynchronous test that waits for [callbacks] callbacks. */ |
87 UnitTestSuite addAsyncTest(TestFunction body, int callbacks) => | 110 void addAsyncTest(TestFunction body, int callbacks) { |
Siggi Cherem (dart-lang)
2011/10/14 23:26:29
I'm not sure if this is currently used by any of t
| |
88 asyncTest(null, callbacks, body); | 111 asyncTest(null, callbacks, body); |
112 } | |
89 | 113 |
90 /** Runs all queued tests, one at a time. */ | 114 /** Runs all queued tests, one at a time. */ |
91 void runTests() { | 115 void runTests() { |
92 window.dynamic/*TODO(5389254)*/.postMessage('unittest-suite-start', '*'); | 116 window.dynamic/*TODO(5389254)*/.postMessage('unittest-suite-start', '*'); |
93 // Isolate.bind makes sure the closure runs in the same isolate (i.e. this | 117 // Isolate.bind makes sure the closure runs in the same isolate (i.e. this |
94 // one) where it has been created. | 118 // one) where it has been created. |
95 window.setTimeout(Isolate.bind(() { | 119 window.setTimeout(Isolate.bind(() { |
96 assert (_currentTest == 0); | 120 assert (_currentTest == 0); |
97 // Listen for uncaught errors (see [_uncaughtError]). | 121 // Listen for uncaught errors (see [_uncaughtError]). |
98 // TODO(jacobr): remove this horrible hack when dartc bugs are fixed. | 122 // TODO(jacobr): remove this horrible hack when dartc bugs are fixed. |
(...skipping 13 matching lines...) Expand all Loading... | |
112 // Currently e.message works in dartium, but not in dartc. | 136 // Currently e.message works in dartium, but not in dartc. |
113 testCase.recordError('(DOM callback has errors) Caught ${e}', ''); | 137 testCase.recordError('(DOM callback has errors) Caught ${e}', ''); |
114 _uncaughtError = true; | 138 _uncaughtError = true; |
115 if (testCase.callbacks > 0) { | 139 if (testCase.callbacks > 0) { |
116 _currentTest++; | 140 _currentTest++; |
117 _nextBatch(); | 141 _nextBatch(); |
118 } | 142 } |
119 } | 143 } |
120 } | 144 } |
121 | 145 |
122 /** | |
123 * Creates a new test case with the given description and body. The | |
124 * description will include the descriptions of any surrounding group() | |
125 * calls. | |
126 */ | |
127 UnitTestSuite test(String spec, TestFunction body) { | |
128 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0)); | |
129 return this; | |
130 } | |
131 | |
132 /** | |
133 * Creates a new async test case with the given description and body. The | |
134 * description will include the descriptions of any surrounding group() | |
135 * calls. | |
136 */ | |
137 UnitTestSuite asyncTest(String spec, int callbacks, TestFunction body) { | |
138 final testCase = | |
139 new TestCase(_tests.length + 1, _fullSpec(spec), body, callbacks); | |
140 _tests.add(testCase); | |
141 if (callbacks < 1) { | |
142 testCase.recordError( | |
143 'Async tests must wait for at least one callback ', ''); | |
144 } | |
145 return this; | |
146 } | |
147 | |
148 /** | |
149 * Creates a new named group of tests. Calls to group() or test() within the | |
150 * body of the function passed to this will inherit this group's description. | |
151 */ | |
152 void group(String description, void body()) { | |
153 // Concatenate the new group. | |
154 final oldGroup = _group; | |
155 if (_group != '') { | |
156 // Add a space. | |
157 _group = '$_group $description'; | |
158 } else { | |
159 // The first group. | |
160 _group = description; | |
161 } | |
162 | |
163 try { | |
164 body(); | |
165 } finally { | |
166 // Now that the group is over, restore the previous one. | |
167 _group = oldGroup; | |
168 } | |
169 } | |
170 | |
171 String _fullSpec(String spec) { | |
172 if (spec === null) return '$_group'; | |
173 return _group != '' ? '$_group $spec' : spec; | |
174 } | |
175 | |
176 /** Creates an expectation for the given value. */ | |
177 Expectation expect(value) => new Expectation(value); | |
178 | |
179 /** Called by subclasses to indicate that an asynchronous test completed. */ | 146 /** Called by subclasses to indicate that an asynchronous test completed. */ |
180 void callbackDone() { | 147 void callbackDone() { |
181 _callbacksCalled++; | 148 _callbacksCalled++; |
182 final testCase = _tests[_currentTest]; | 149 final testCase = _tests[_currentTest]; |
183 if (testCase.callbacks == 0) { | 150 if (testCase.callbacks == 0) { |
184 testCase.recordError( | 151 testCase.recordError( |
185 "Can't call callbackDone() on a synchronous test", ''); | 152 "Can't call callbackDone() on a synchronous test", ''); |
186 _uncaughtError = true; | 153 _uncaughtError = true; |
187 } else if (_callbacksCalled > testCase.callbacks) { | 154 } else if (_callbacksCalled > testCase.callbacks) { |
188 final expected = testCase.callbacks; | 155 final expected = testCase.callbacks; |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
240 } | 207 } |
241 | 208 |
242 /** Publish results on the page and notify controller. */ | 209 /** Publish results on the page and notify controller. */ |
243 void _completeTests() { | 210 void _completeTests() { |
244 try { | 211 try { |
245 window.dynamic.on.error.remove(_onErrorClosure); | 212 window.dynamic.on.error.remove(_onErrorClosure); |
246 } catch (var e) { | 213 } catch (var e) { |
247 // TODO(jacobr): remove this horrible hack to work around dartc bugs. | 214 // TODO(jacobr): remove this horrible hack to work around dartc bugs. |
248 window.dynamic.onerror = null; | 215 window.dynamic.onerror = null; |
249 } | 216 } |
217 | |
250 int testsFailed = 0; | 218 int testsFailed = 0; |
251 int testsErrors = 0; | 219 int testsErrors = 0; |
252 int testsPassed = 0; | 220 int testsPassed = 0; |
253 | 221 |
254 for (TestCase t in _tests) { | 222 for (TestCase t in _tests) { |
255 if (t.success) { | 223 if (t.success) { |
256 testsPassed++; | 224 testsPassed++; |
257 } | 225 } |
258 if (t.fail) { | 226 if (t.fail) { |
259 testsFailed++; | 227 testsFailed++; |
(...skipping 28 matching lines...) Expand all Loading... | |
288 </td></tr>"""); | 256 </td></tr>"""); |
289 } | 257 } |
290 newBody.add("</tbody></table>"); | 258 newBody.add("</tbody></table>"); |
291 document.body.innerHTML = newBody.toString(); | 259 document.body.innerHTML = newBody.toString(); |
292 } | 260 } |
293 | 261 |
294 window.dynamic/*TODO(5389254)*/.postMessage('unittest-suite-done', '*'); | 262 window.dynamic/*TODO(5389254)*/.postMessage('unittest-suite-done', '*'); |
295 } | 263 } |
296 } | 264 } |
297 | 265 |
266 /** Creates an expectation for the given value. */ | |
267 Expectation expect(value) => new Expectation(value); | |
268 | |
269 /** | |
270 * Creates a new test case with the given description and body. The | |
271 * description will include the descriptions of any surrounding group() | |
272 * calls. | |
273 */ | |
274 void test(String spec, TestFunction body) { | |
275 bool outermost = _ensureActiveSuite(); | |
276 | |
277 _currentSuite._tests.add(new TestCase( | |
278 _currentSuite._tests.length + 1, _fullSpec(spec), body, 0)); | |
279 | |
280 if (outermost) { | |
281 _currentSuite.run(); | |
282 } | |
283 } | |
284 | |
285 /** | |
286 * Creates a new async test case with the given description and body. The | |
287 * description will include the descriptions of any surrounding group() | |
288 * calls. | |
289 */ | |
290 void asyncTest(String spec, int callbacks, TestFunction body) { | |
291 bool outermost = _ensureActiveSuite(); | |
292 | |
293 final testCase = new TestCase( | |
294 _currentSuite._tests.length + 1, _fullSpec(spec), body, callbacks); | |
295 _currentSuite._tests.add(testCase); | |
296 | |
297 if (callbacks < 1) { | |
298 testCase.recordError( | |
299 'Async tests must wait for at least one callback ', ''); | |
300 } | |
301 | |
302 if (outermost) { | |
303 _currentSuite.run(); | |
304 } | |
305 } | |
306 | |
307 /** | |
308 * Creates a new named group of tests. Calls to group() or test() within the | |
309 * body of the function passed to this will inherit this group's description. | |
310 */ | |
311 void group(String description, void body()) { | |
312 bool outermost = _ensureActiveSuite(); | |
313 | |
314 // Concatenate the new group. | |
315 final oldGroup = _currentGroup; | |
316 if (_currentGroup != '') { | |
317 // Add a space. | |
318 _currentGroup = '$_currentGroup $description'; | |
319 } else { | |
320 // The first group. | |
321 _currentGroup = description; | |
322 } | |
323 | |
324 try { | |
325 body(); | |
326 } finally { | |
327 // Now that the group is over, restore the previous one. | |
328 _currentGroup = oldGroup; | |
329 } | |
330 | |
331 if (outermost) { | |
332 _currentSuite.run(); | |
333 } | |
334 } | |
335 | |
336 String _fullSpec(String spec) { | |
337 if (spec === null) return '$_currentGroup'; | |
338 return _currentGroup != '' ? '$_currentGroup $spec' : spec; | |
339 } | |
340 | |
341 /** | |
342 * Lazily creates a UnitTestSuite if there isn't already an active one. Returns | |
343 * whether or not one was created. | |
344 */ | |
345 _ensureActiveSuite() { | |
346 if (_currentSuite != null) { | |
347 return false; | |
348 } | |
349 | |
350 _currentSuite = new UnitTestSuite(); | |
351 return true; | |
352 } | |
353 | |
298 /** | 354 /** |
299 * Wraps an value and provides an "==" operator that can be used to verify that | 355 * Wraps an value and provides an "==" operator that can be used to verify that |
300 * the value matches a given expectation. | 356 * the value matches a given expectation. |
301 */ | 357 */ |
302 // TODO(rnystrom): Note that because Dart does not currently allow overloading | 358 // TODO(rnystrom): Note that because Dart does not currently allow overloading |
303 // != that this *cannot* be used with !=. If you do expect(1) != 2, it will do | 359 // != that this *cannot* be used with !=. If you do expect(1) != 2, it will do |
304 // the exact wrong thing. (It will invoke == which validates that 1 *does* | 360 // the exact wrong thing. (It will invoke == which validates that 1 *does* |
305 // equal 2.) If we get an overloadable != operator, that can be fixed. | 361 // equal 2.) If we get an overloadable != operator, that can be fixed. |
306 class Expectation { | 362 class Expectation { |
307 final _value; | 363 final _value; |
308 | 364 |
309 Expectation(this._value); | 365 Expectation(this._value); |
310 | 366 |
367 // TODO(rnystrom): Get rid of this and use .equals(). After some discussion, | |
368 // we decided this is the wrong approach for a bunch of reasons, clever as it | |
369 // may be. | |
311 /** Asserts that the value is equivalent to the given expected value. */ | 370 /** Asserts that the value is equivalent to the given expected value. */ |
312 operator ==(expected) { | 371 operator ==(expected) { |
313 Expect.equals(expected, _value); | 372 Expect.equals(expected, _value); |
314 return _value == expected; | 373 return _value == expected; |
315 } | 374 } |
316 | 375 |
317 /** Asserts that the value is not null. */ | 376 /** Asserts that the value is not null. */ |
318 void isNotNull() { | 377 void isNotNull() { |
319 Expect.notEquals(null, _value); | 378 Expect.notEquals(null, _value); |
320 } | 379 } |
321 | 380 |
322 /** Asserts that the value has the same elements as the given collection. */ | 381 /** Asserts that the value has the same elements as the given collection. */ |
323 void equalsCollection(Collection expected) { | 382 void equalsCollection(Collection expected) { |
324 Expect.listEquals(expected, _value); | 383 Expect.listEquals(expected, _value); |
325 } | 384 } |
326 } | 385 } |
327 | 386 |
328 /** | 387 /** |
329 * A TestSet lets you break a test suite down into a collection of classes to | 388 * A TestSet lets you break a test suite down into a collection of classes to |
330 * keep things manageable. It exposes the same interface as UnitTestSuite | 389 * keep things manageable. It exposes the same interface as UnitTestSuite |
331 * (test(), group(), expect(), etc.) but defers to a parent suite that owns it. | 390 * (test(), group(), expect(), etc.) but defers to a parent suite that owns it. |
332 */ | 391 */ |
333 class TestSet { | 392 class TestSet { |
334 UnitTestSuite _suite = null; | 393 UnitTestSuite _currentSuite = null; |
335 | 394 |
336 // TODO(rnystrom): Remove this when default constructors are supported. | 395 // TODO(rnystrom): Remove this when default constructors are supported. |
337 TestSet(); | 396 TestSet(); |
338 | 397 |
339 void _bindToSuite(UnitTestSuite suite) { | 398 void _bindToSuite(UnitTestSuite suite) { |
340 _suite = suite; | 399 _currentSuite = suite; |
341 } | 400 } |
342 | 401 |
343 /** Override this to define the specifications for this test set. */ | 402 /** Override this to define the specifications for this test set. */ |
344 void setup() { | 403 void setup() { |
345 // Do nothing. | 404 // Do nothing. |
346 } | 405 } |
347 | 406 |
348 /** Enqueues a synchronous test. */ | 407 /** Enqueues a synchronous test. */ |
349 void addTest(TestFunction test) { | 408 void addTest(TestFunction test) { |
350 _suite.addTest(test); | 409 _currentSuite.addTest(test); |
351 } | 410 } |
352 | 411 |
353 /** Adds the tests defined by the given TestSet to this suite. */ | 412 /** Adds the tests defined by the given TestSet to this suite. */ |
354 void addTestSet(TestSet test) { | 413 void addTestSet(TestSet test) { |
355 _suite.addTestSet(test); | 414 _currentSuite.addTestSet(test); |
356 } | 415 } |
357 | 416 |
358 /** Adds the tests defined by the given TestSets to this suite. */ | 417 /** Adds the tests defined by the given TestSets to this suite. */ |
359 void addTestSets(Iterable<TestSet> tests) { | 418 void addTestSets(Iterable<TestSet> tests) { |
360 _suite.addTestSets(tests); | 419 _currentSuite.addTestSets(tests); |
361 } | 420 } |
362 | 421 |
363 /** Enqueues an asynchronous test that waits for [callbacks] callbacks. */ | 422 /** Enqueues an asynchronous test that waits for [callbacks] callbacks. */ |
364 void addAsyncTest(TestFunction test, int callbacks) { | 423 void addAsyncTest(TestFunction test, int callbacks) { |
365 _suite.addAsyncTest(test, callbacks); | 424 _currentSuite.addAsyncTest(test, callbacks); |
366 } | 425 } |
367 | |
368 /** | |
369 * Creates a new test case with the given description and body. The | |
370 * description will include the descriptions of any surrounding group() | |
371 * calls. | |
372 */ | |
373 void test(String spec, void body()) { | |
374 _suite.test(spec, body); | |
375 } | |
376 | |
377 /** | |
378 * Creates a new named group of tests. Calls to group() or test() within the | |
379 * body of the function passed to this will inherit this group's description. | |
380 */ | |
381 void group(String description, void body()) { | |
382 _suite.group(description, body); | |
383 } | |
384 | |
385 /** Creates an expectation for the given value. */ | |
386 Expectation expect(value) => _suite.expect(value); | |
387 } | 426 } |
388 | 427 |
389 /** Summarizes information about a single test case. */ | 428 /** Summarizes information about a single test case. */ |
390 class TestCase { | 429 class TestCase { |
391 /** Identifier for this test. */ | 430 /** Identifier for this test. */ |
392 final id; | 431 final id; |
393 | 432 |
394 /** A description of what the test is specifying. */ | 433 /** A description of what the test is specifying. */ |
395 final String description; | 434 final String description; |
396 | 435 |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
460 </tr>"""; | 499 </tr>"""; |
461 if (stackTrace != null) { | 500 if (stackTrace != null) { |
462 message += | 501 message += |
463 "<tr><td></td><td colspan='2'><pre>${stackTrace}</pre></td></tr>"; | 502 "<tr><td></td><td colspan='2'><pre>${stackTrace}</pre></td></tr>"; |
464 } | 503 } |
465 fail = true; | 504 fail = true; |
466 } | 505 } |
467 } | 506 } |
468 | 507 |
469 typedef void TestFunction(); | 508 typedef void TestFunction(); |
OLD | NEW |