Chromium Code Reviews| 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 /** | 5 /** |
| 14 * Description text of the current test group. If multiple groups are nested, | 6 * Description text of the current test group. If multiple groups are nested, |
| 15 * this will contain all of their text concatenated. | 7 * this will contain all of their text concatenated. |
| 16 */ | 8 */ |
| 17 String _currentGroup = ''; | 9 String _currentGroup = ''; |
| 18 | 10 |
| 19 /** Base class for unit test suites run in a browser. */ | 11 /** Tests executed in this suite. */ |
| 20 class UnitTestSuite { | 12 List<TestCase> _tests; |
| 21 | 13 |
| 22 /** Tests executed in this suite. */ | 14 /** Whether this is run within dartium layout tests. */ |
| 23 List<TestCase> _tests; | 15 bool _isLayoutTest = false; |
| 24 | 16 |
| 25 /** Whether this suite is run within dartium layout tests. */ | 17 /** Current test being executed. */ |
| 26 bool _isLayoutTest; | 18 int _currentTest = 0; |
| 27 | 19 |
| 28 /** Current test being executed. */ | 20 /** Total number of callbacks that have been executed in the current test. */ |
| 29 int _currentTest; | 21 int _callbacksCalled = 0; |
| 30 | 22 |
| 31 /** Total number of callbacks that have been executed in the current test. */ | 23 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. |
| 32 int _callbacksCalled; | 24 EventListener _onErrorClosure; |
| 33 | 25 |
| 34 /** | 26 final _UNINITIALIZED = 0; |
| 35 * Whether an undetected error occurred while running the last test. This | 27 final _READY = 1; |
| 36 * errors are commonly caused by DOM callbacks that were not guarded in a | 28 final _RUNNING_TEST = 2; |
| 37 * try-catch block. | |
| 38 */ | |
| 39 bool _uncaughtError; | |
| 40 EventListener _onErrorClosure; | |
| 41 | 29 |
| 42 bool _queuedToRun = false; | 30 /** |
| 31 * Whether an undetected error occurred while running the last test. These | |
| 32 * errors are commonly caused by DOM callbacks that were not guarded in a | |
| 33 * try-catch block. | |
| 34 */ | |
| 35 final _stateUncaughtError = 3; | |
|
Siggi Cherem (dart-lang)
2011/10/28 18:21:42
this one too?
Bob Nystrom
2011/10/28 18:42:31
Oops! Missed that. Thanks!
| |
| 43 | 36 |
| 44 /** Whether a test is currently being executed by [runTest]. */ | 37 int _state = _UNINITIALIZED; |
| 45 bool _testIsRunning = false; | |
| 46 | |
| 47 // TODO(sigmund): remove isLayoutTest argument after converting all DOM tests | |
| 48 // to use the named constructor below. | |
| 49 // TODO(vsm): remove the ignoredWindow parameter once all tests are fixed. | |
| 50 UnitTestSuite([var ignoredWindow = null, bool isLayoutTest = false]) | |
| 51 : _isLayoutTest = isLayoutTest, | |
| 52 _tests = new List<TestCase>(), | |
| 53 _currentTest = 0, | |
| 54 _callbacksCalled = 0 { | |
| 55 _onErrorClosure = (e) { _onError(e); }; | |
| 56 if (_currentSuite != null) { | |
| 57 throw 'Cannot have two UnitTestSuites in flight at the same time.'; | |
| 58 } | |
| 59 _currentSuite = this; | |
| 60 | |
| 61 // Immediately queue the suite up. It will run after a timeout (i.e. after | |
| 62 // main() has returned). | |
| 63 run(); | |
| 64 } | |
| 65 | |
| 66 // TODO(jacobr): remove the ignoredWindow parameter once all tests are fixed. | |
| 67 UnitTestSuite.forLayoutTests([var ignoredWindow = null]) : this(null, true); | |
| 68 | |
| 69 /** Starts running the testsuite. */ | |
| 70 void run() { | |
| 71 if (_queuedToRun) { | |
| 72 return; | |
| 73 } | |
| 74 | |
| 75 _queuedToRun = true; | |
| 76 | |
| 77 listener(e) { | |
| 78 _currentGroup = ''; | |
| 79 setUpTestSuite(); | |
| 80 runTests(); | |
| 81 }; | |
| 82 | |
| 83 try { | |
| 84 window.dynamic.on.contentLoaded.add(listener); | |
| 85 } catch(var e) { | |
| 86 // TODO(jacobr): remove this horrible hack to work around dartc bugs. | |
| 87 window.dynamic.addEventListener("DOMContentLoaded", listener, false); | |
| 88 } | |
| 89 } | |
| 90 | |
| 91 /** Subclasses should override this method to register tests. */ | |
| 92 void setUpTestSuite() {} | |
| 93 | |
| 94 /** Enqueues a synchronous test. */ | |
| 95 void addTest(TestFunction body) { | |
| 96 test(null, body); | |
| 97 } | |
| 98 | |
| 99 /** Enqueues an asynchronous test that waits for [callbacks] callbacks. */ | |
| 100 void addAsyncTest(TestFunction body, int callbacks) { | |
| 101 asyncTest(null, callbacks, body); | |
| 102 } | |
| 103 | |
| 104 /** Runs all queued tests, one at a time. */ | |
| 105 void runTests() { | |
| 106 window.postMessage('unittest-suite-start', '*'); | |
| 107 window.setTimeout(() { | |
| 108 assert (_currentTest == 0); | |
| 109 // Listen for uncaught errors (see [_uncaughtError]). | |
| 110 // TODO(jacobr): remove this horrible hack when dartc bugs are fixed. | |
| 111 try { | |
| 112 window.dynamic.on.error.add(_onErrorClosure); | |
| 113 } catch(var e) { | |
| 114 window.dynamic.onerror = _onErrorClosure; | |
| 115 } | |
| 116 _nextBatch(); | |
| 117 }, 0); | |
| 118 } | |
| 119 | |
| 120 void _onError(e) { | |
| 121 if (_currentTest < _tests.length) { | |
| 122 final testCase = _tests[_currentTest]; | |
| 123 // TODO(vsm): figure out how to expose the stack trace here | |
| 124 // Currently e.message works in dartium, but not in dartc. | |
| 125 testCase.recordError('(DOM callback has errors) Caught ${e}', ''); | |
| 126 _uncaughtError = true; | |
| 127 if (testCase.callbacks > 0) { | |
| 128 _currentTest++; | |
| 129 _nextBatch(); | |
| 130 } | |
| 131 } | |
| 132 } | |
| 133 | |
| 134 /** Called by subclasses to indicate that an asynchronous test completed. */ | |
| 135 void callbackDone() { | |
| 136 _callbacksCalled++; | |
| 137 final testCase = _tests[_currentTest]; | |
| 138 if (testCase.callbacks == 0) { | |
| 139 testCase.recordError( | |
| 140 "Can't call callbackDone() on a synchronous test", ''); | |
| 141 _uncaughtError = true; | |
| 142 } else if (_callbacksCalled > testCase.callbacks) { | |
| 143 final expected = testCase.callbacks; | |
| 144 testCase.recordError( | |
| 145 'More calls to callbackDone() than expected. ' | |
| 146 + 'Actual: ${_callbacksCalled}, expected: ${expected}', ''); | |
| 147 _uncaughtError = true; | |
| 148 } else if (_callbacksCalled == testCase.callbacks && !_testIsRunning) { | |
| 149 testCase.recordSuccess(); | |
| 150 _currentTest++; | |
| 151 _nextBatch(); | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 /** | |
| 156 * Runs a batch of tests, yielding whenever an asynchronous test starts | |
| 157 * running. Tests will resume executing when such asynchronous test calls | |
| 158 * [done] or if it fails with an exception. | |
| 159 */ | |
| 160 void _nextBatch() { | |
| 161 while (_currentTest < _tests.length) { | |
| 162 final testCase = _tests[_currentTest]; | |
| 163 runTest(testCase); | |
| 164 if (!testCase.isComplete() && testCase.callbacks > 0) { | |
| 165 return; | |
| 166 } | |
| 167 _currentTest++; | |
| 168 } | |
| 169 _completeTests(); | |
| 170 } | |
| 171 | |
| 172 /** Runs a single test. */ | |
| 173 void runTest(TestCase testCase) { | |
| 174 // TODO(sigmund): remove this declaration once dartc supports trapping error | |
| 175 // traces. | |
| 176 var trace = ''; | |
| 177 _uncaughtError = false; | |
| 178 _callbacksCalled = 0; | |
| 179 try { | |
| 180 _testIsRunning = true; | |
| 181 (testCase.test)(); | |
| 182 if (!_uncaughtError) { | |
| 183 if (testCase.callbacks == _callbacksCalled) { | |
| 184 testCase.recordSuccess(); | |
| 185 } | |
| 186 } | |
| 187 } catch (ExpectException e, var trace) { | |
| 188 if (!_uncaughtError) { | |
| 189 testCase.recordFail(e.message, trace.toString()); | |
| 190 } | |
| 191 } catch (var e, var trace) { | |
| 192 if (!_uncaughtError) { | |
| 193 testCase.recordError('Caught ${e}', trace.toString()); | |
| 194 } | |
| 195 } finally { | |
| 196 _testIsRunning = false; | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 /** Publish results on the page and notify controller. */ | |
| 201 void _completeTests() { | |
| 202 try { | |
| 203 window.dynamic.on.error.remove(_onErrorClosure); | |
| 204 } catch (var e) { | |
| 205 // TODO(jacobr): remove this horrible hack to work around dartc bugs. | |
| 206 window.dynamic.onerror = null; | |
| 207 } | |
| 208 | |
| 209 // This suite is done now, so discard it. | |
| 210 _currentSuite = null; | |
| 211 | |
| 212 int testsFailed = 0; | |
| 213 int testsErrors = 0; | |
| 214 int testsPassed = 0; | |
| 215 | |
| 216 for (TestCase t in _tests) { | |
| 217 if (t.success) { | |
| 218 testsPassed++; | |
| 219 } | |
| 220 if (t.fail) { | |
| 221 testsFailed++; | |
| 222 } | |
| 223 if (t.error) { | |
| 224 testsErrors++; | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 if (_isLayoutTest && testsPassed == _tests.length) { | |
| 229 document.body.innerHTML = "PASS"; | |
| 230 } else { | |
| 231 StringBuffer newBody = new StringBuffer(); | |
| 232 newBody.add("<table class='unittest-table'><tbody>"); | |
| 233 newBody.add(testsPassed == _tests.length | |
| 234 ? "<tr><td colspan='3' class='unittest-pass'>PASS</td></tr>" | |
| 235 : "<tr><td colspan='3' class='unittest-fail'>FAIL</td></tr>"); | |
| 236 | |
| 237 for (TestCase t in _tests) { | |
| 238 newBody.add(t.message); | |
| 239 } | |
| 240 | |
| 241 if (testsPassed == _tests.length) { | |
| 242 newBody.add("<tr><td colspan='3' class='unittest-pass'>All " | |
| 243 + testsPassed + " tests passed</td></tr>"); | |
| 244 } else { | |
| 245 newBody.add(""" | |
| 246 <tr><td colspan='3'>Total | |
| 247 <span class='unittest-pass'>${testsPassed} passed</span>, | |
| 248 <span class='unittest-fail'>${testsFailed} failed</span> | |
| 249 <span class='unittest-error'>${testsErrors} errors</span> | |
| 250 </td></tr>"""); | |
| 251 } | |
| 252 newBody.add("</tbody></table>"); | |
| 253 document.body.innerHTML = newBody.toString(); | |
| 254 } | |
| 255 | |
| 256 window.dynamic/*TODO(5389254)*/.postMessage('unittest-suite-done', '*'); | |
| 257 } | |
| 258 } | |
| 259 | 38 |
| 260 /** Creates an expectation for the given value. */ | 39 /** Creates an expectation for the given value. */ |
| 261 Expectation expect(value) => new Expectation(value); | 40 Expectation expect(value) => new Expectation(value); |
| 262 | 41 |
| 263 /** Evaluates the given function and validates that it throws an exception. */ | 42 /** Evaluates the given function and validates that it throws an exception. */ |
| 264 void expectThrow(function) { | 43 void expectThrow(function) { |
| 265 bool threw = false; | 44 bool threw = false; |
| 266 try { | 45 try { |
| 267 function(); | 46 function(); |
| 268 } catch (var e) { | 47 } catch (var e) { |
| 269 threw = true; | 48 threw = true; |
| 270 } | 49 } |
| 271 Expect.equals(true, threw, 'Expected exception but none was thrown.'); | 50 Expect.equals(true, threw, 'Expected exception but none was thrown.'); |
| 272 } | 51 } |
| 273 | 52 |
| 274 /** | 53 /** |
| 275 * Creates a new test case with the given description and body. The | 54 * Creates a new test case with the given description and body. The |
| 276 * description will include the descriptions of any surrounding group() | 55 * description will include the descriptions of any surrounding group() |
| 277 * calls. | 56 * calls. |
| 278 */ | 57 */ |
| 279 void test(String spec, TestFunction body) { | 58 void test(String spec, TestFunction body) { |
| 280 _ensureActiveSuite(); | 59 _ensureInitialized(); |
| 281 | 60 |
| 282 _currentSuite._tests.add(new TestCase( | 61 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0)); |
| 283 _currentSuite._tests.length + 1, _fullSpec(spec), body, 0)); | |
| 284 } | 62 } |
| 285 | 63 |
| 286 /** | 64 /** |
| 287 * Creates a new async test case with the given description and body. The | 65 * Creates a new async test case with the given description and body. The |
| 288 * description will include the descriptions of any surrounding group() | 66 * description will include the descriptions of any surrounding group() |
| 289 * calls. | 67 * calls. |
| 290 */ | 68 */ |
| 291 void asyncTest(String spec, int callbacks, TestFunction body) { | 69 void asyncTest(String spec, int callbacks, TestFunction body) { |
| 292 _ensureActiveSuite(); | 70 _ensureInitialized(); |
| 293 | 71 |
| 294 final testCase = new TestCase( | 72 final testCase = new TestCase( |
| 295 _currentSuite._tests.length + 1, _fullSpec(spec), body, callbacks); | 73 _tests.length + 1, _fullSpec(spec), body, callbacks); |
| 296 _currentSuite._tests.add(testCase); | 74 _tests.add(testCase); |
| 297 | 75 |
| 298 if (callbacks < 1) { | 76 if (callbacks < 1) { |
| 299 testCase.recordError( | 77 testCase.recordError( |
| 300 'Async tests must wait for at least one callback ', ''); | 78 'Async tests must wait for at least one callback ', ''); |
| 301 } | 79 } |
| 302 } | 80 } |
| 303 | 81 |
| 304 /** | 82 /** |
| 305 * Creates a new named group of tests. Calls to group() or test() within the | 83 * Creates a new named group of tests. Calls to group() or test() within the |
| 306 * body of the function passed to this will inherit this group's description. | 84 * body of the function passed to this will inherit this group's description. |
| 307 */ | 85 */ |
| 308 void group(String description, void body()) { | 86 void group(String description, void body()) { |
| 309 _ensureActiveSuite(); | 87 _ensureInitialized(); |
| 310 | 88 |
| 311 // Concatenate the new group. | 89 // Concatenate the new group. |
| 312 final oldGroup = _currentGroup; | 90 final oldGroup = _currentGroup; |
| 313 if (_currentGroup != '') { | 91 if (_currentGroup != '') { |
| 314 // Add a space. | 92 // Add a space. |
| 315 _currentGroup = '$_currentGroup $description'; | 93 _currentGroup = '$_currentGroup $description'; |
| 316 } else { | 94 } else { |
| 317 // The first group. | 95 // The first group. |
| 318 _currentGroup = description; | 96 _currentGroup = description; |
| 319 } | 97 } |
| 320 | 98 |
| 321 try { | 99 try { |
| 322 body(); | 100 body(); |
| 323 } finally { | 101 } finally { |
| 324 // Now that the group is over, restore the previous one. | 102 // Now that the group is over, restore the previous one. |
| 325 _currentGroup = oldGroup; | 103 _currentGroup = oldGroup; |
| 326 } | 104 } |
| 327 } | 105 } |
| 328 | 106 |
| 107 /** Called by subclasses to indicate that an asynchronous test completed. */ | |
| 329 void callbackDone() { | 108 void callbackDone() { |
| 330 if (_currentSuite == null) { | 109 _callbacksCalled++; |
| 331 throw 'There is no currently-running test suite.'; | 110 final testCase = _tests[_currentTest]; |
| 111 if (testCase.callbacks == 0) { | |
| 112 testCase.recordError( | |
| 113 "Can't call callbackDone() on a synchronous test", ''); | |
| 114 _state = _stateUncaughtError; | |
| 115 } else if (_callbacksCalled > testCase.callbacks) { | |
| 116 final expected = testCase.callbacks; | |
| 117 testCase.recordError( | |
| 118 'More calls to callbackDone() than expected. ' | |
| 119 + 'Actual: ${_callbacksCalled}, expected: ${expected}', ''); | |
| 120 _state = _stateUncaughtError; | |
| 121 } else if ((_callbacksCalled == testCase.callbacks) && | |
| 122 (_state != _RUNNING_TEST)) { | |
| 123 testCase.recordSuccess(); | |
| 124 _currentTest++; | |
| 125 _nextBatch(); | |
| 332 } | 126 } |
| 333 _currentSuite.callbackDone(); | |
| 334 } | 127 } |
| 335 | 128 |
| 336 void forLayoutTests() { | 129 void forLayoutTests() { |
| 337 _ensureActiveSuite(); | 130 _isLayoutTest = true; |
| 338 _currentSuite._isLayoutTest = true; | 131 } |
| 132 | |
| 133 /** Runs all queued tests, one at a time. */ | |
| 134 _runTests() { | |
| 135 window.postMessage('unittest-suite-start', '*'); | |
| 136 window.setTimeout(() { | |
| 137 assert (_currentTest == 0); | |
| 138 // Listen for uncaught errors. | |
| 139 try { | |
| 140 window.on.error.add(_onErrorClosure); | |
| 141 } catch(var e) { | |
| 142 // TODO(jacobr): remove this horrible hack when dartc bugs are fixed. | |
| 143 window.dynamic.onerror = _onErrorClosure; | |
| 144 } | |
| 145 _nextBatch(); | |
| 146 }, 0); | |
| 147 } | |
| 148 | |
| 149 /** Runs a single test. */ | |
| 150 _runTest(TestCase testCase) { | |
| 151 try { | |
| 152 // TODO(sigmund): remove this declaration once dartc supports trapping error | |
| 153 // traces. | |
| 154 var trace = ''; | |
| 155 _callbacksCalled = 0; | |
| 156 _state = _RUNNING_TEST; | |
| 157 | |
| 158 testCase.test(); | |
| 159 | |
| 160 if (_state != _stateUncaughtError) { | |
| 161 if (testCase.callbacks == _callbacksCalled) { | |
| 162 testCase.recordSuccess(); | |
| 163 } | |
| 164 } | |
| 165 } catch (ExpectException e, var trace) { | |
| 166 if (_state != _stateUncaughtError) { | |
| 167 testCase.recordFail(e.message, trace.toString()); | |
| 168 } | |
| 169 } catch (var e, var trace) { | |
| 170 if (_state != _stateUncaughtError) { | |
| 171 testCase.recordError('Caught ${e}', trace.toString()); | |
| 172 } | |
| 173 } finally { | |
| 174 _state = _READY; | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 /** | |
| 179 * Runs a batch of tests, yielding whenever an asynchronous test starts | |
| 180 * running. Tests will resume executing when such asynchronous test calls | |
| 181 * [done] or if it fails with an exception. | |
| 182 */ | |
| 183 _nextBatch() { | |
| 184 while (_currentTest < _tests.length) { | |
| 185 final testCase = _tests[_currentTest]; | |
| 186 | |
| 187 _runTest(testCase); | |
| 188 | |
| 189 if (!testCase.isComplete && testCase.callbacks > 0) return; | |
| 190 | |
| 191 _currentTest++; | |
| 192 } | |
| 193 | |
| 194 _completeTests(); | |
| 195 } | |
| 196 | |
| 197 /** Publish results on the page and notify controller. */ | |
| 198 _completeTests() { | |
| 199 try { | |
| 200 window.on.error.remove(_onErrorClosure); | |
| 201 } catch (var e) { | |
| 202 // TODO(jacobr): remove this horrible hack to work around dartc bugs. | |
| 203 window.dynamic.onerror = null; | |
| 204 } | |
| 205 | |
| 206 _state = _UNINITIALIZED; | |
| 207 | |
| 208 int testsFailed = 0; | |
| 209 int testsErrors = 0; | |
| 210 int testsPassed = 0; | |
| 211 | |
| 212 for (TestCase t in _tests) { | |
| 213 if (t.success) testsPassed++; | |
| 214 if (t.fail) testsFailed++; | |
| 215 if (t.error) testsErrors++; | |
| 216 } | |
| 217 | |
| 218 if (_isLayoutTest && testsPassed == _tests.length) { | |
| 219 document.body.innerHTML = "PASS"; | |
| 220 } else { | |
| 221 var newBody = new StringBuffer(); | |
| 222 newBody.add("<table class='unittest-table'><tbody>"); | |
| 223 newBody.add(testsPassed == _tests.length | |
| 224 ? "<tr><td colspan='3' class='unittest-pass'>PASS</td></tr>" | |
| 225 : "<tr><td colspan='3' class='unittest-fail'>FAIL</td></tr>"); | |
| 226 | |
| 227 for (final test in _tests) { | |
| 228 newBody.add(test.message); | |
| 229 } | |
| 230 | |
| 231 if (testsPassed == _tests.length) { | |
| 232 newBody.add("<tr><td colspan='3' class='unittest-pass'>All " | |
| 233 + testsPassed + " tests passed</td></tr>"); | |
| 234 } else { | |
| 235 newBody.add(""" | |
| 236 <tr><td colspan='3'>Total | |
| 237 <span class='unittest-pass'>${testsPassed} passed</span>, | |
| 238 <span class='unittest-fail'>${testsFailed} failed</span> | |
| 239 <span class='unittest-error'>${testsErrors} errors</span> | |
| 240 </td></tr>"""); | |
| 241 } | |
| 242 newBody.add("</tbody></table>"); | |
| 243 document.body.innerHTML = newBody.toString(); | |
| 244 } | |
| 245 | |
| 246 window.dynamic/*TODO(5389254)*/.postMessage('unittest-suite-done', '*'); | |
| 247 } | |
| 248 | |
| 249 void _onError(e) { | |
| 250 if (_currentTest < _tests.length) { | |
| 251 final testCase = _tests[_currentTest]; | |
| 252 // TODO(vsm): figure out how to expose the stack trace here | |
| 253 // Currently e.message works in dartium, but not in dartc. | |
| 254 testCase.recordError('(DOM callback has errors) Caught ${e}', ''); | |
| 255 _state = _stateUncaughtError; | |
| 256 if (testCase.callbacks > 0) { | |
| 257 _currentTest++; | |
| 258 _nextBatch(); | |
| 259 } | |
| 260 } | |
| 339 } | 261 } |
| 340 | 262 |
| 341 String _fullSpec(String spec) { | 263 String _fullSpec(String spec) { |
| 342 if (spec === null) return '$_currentGroup'; | 264 if (spec === null) return '$_currentGroup'; |
| 343 return _currentGroup != '' ? '$_currentGroup $spec' : spec; | 265 return _currentGroup != '' ? '$_currentGroup $spec' : spec; |
| 344 } | 266 } |
| 345 | 267 |
| 346 /** | 268 /** |
| 347 * Lazily creates a UnitTestSuite if there isn't already an active one. Returns | 269 * Lazily initializes the test library if not already initialized. |
| 348 * whether or not one was created. | |
| 349 */ | 270 */ |
| 350 _ensureActiveSuite() { | 271 _ensureInitialized() { |
| 351 if (_currentSuite != null) { | 272 if (_state != _UNINITIALIZED) return; |
| 352 return; | 273 |
| 274 _tests = <TestCase>[]; | |
| 275 _onErrorClosure = (e) { _onError(e); }; | |
| 276 | |
| 277 // Immediately queue the suite up. It will run after a timeout (i.e. after | |
| 278 // main() has returned). | |
| 279 listener(e) { | |
| 280 _currentGroup = ''; | |
| 281 _runTests(); | |
| 282 }; | |
| 283 | |
| 284 try { | |
| 285 window.dynamic.on.contentLoaded.add(listener); | |
| 286 } catch(var e) { | |
| 287 // TODO(jacobr): remove this horrible hack to work around dartc bugs. | |
| 288 window.dynamic.addEventListener("DOMContentLoaded", listener, false); | |
| 353 } | 289 } |
| 354 | 290 |
| 355 _currentSuite = new UnitTestSuite(); | 291 _state = _READY; |
| 356 } | 292 } |
| 357 | 293 |
| 358 /** | 294 /** |
| 359 * Wraps an value and provides an "==" operator that can be used to verify that | 295 * Wraps an value and provides an "==" operator that can be used to verify that |
| 360 * the value matches a given expectation. | 296 * the value matches a given expectation. |
| 361 */ | 297 */ |
| 362 class Expectation { | 298 class Expectation { |
| 363 final _value; | 299 final _value; |
| 364 | 300 |
| 365 Expectation(this._value); | 301 Expectation(this._value); |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 436 /** Whether this test case had a runtime error. */ | 372 /** Whether this test case had a runtime error. */ |
| 437 bool error; | 373 bool error; |
| 438 | 374 |
| 439 /** Messages to display at the end of the test run. */ | 375 /** Messages to display at the end of the test run. */ |
| 440 String message; | 376 String message; |
| 441 | 377 |
| 442 TestCase(this.id, this.description, this.test, this.callbacks) | 378 TestCase(this.id, this.description, this.test, this.callbacks) |
| 443 : success = false, | 379 : success = false, |
| 444 fail = false, | 380 fail = false, |
| 445 error = false { | 381 error = false { |
| 446 message = """<tr> | 382 message = '''<tr> |
| 447 <td>${id}</td> | 383 <td>${id}</td> |
| 448 <td class='unittest-error'>NO STATUS</td> | 384 <td class="unittest-error">NO STATUS</td> |
| 449 <td>Test did not complete</td> | 385 <td>Test did not complete</td> |
| 450 </tr>"""; | 386 </tr>'''; |
| 451 } | 387 } |
| 452 | 388 |
| 453 bool isComplete() => success || fail || error; | 389 bool get isComplete() => success || fail || error; |
| 454 | 390 |
| 455 void recordSuccess() { | 391 void recordSuccess() { |
| 456 message = "<tr><td>${id}</td><td class='unittest-pass'>PASS</td></tr>"; | 392 _setMessage('pass', '', null); |
| 457 success = true; | 393 success = true; |
| 458 } | 394 } |
| 459 | 395 |
| 460 void recordError(String msg, String stackTrace) { | 396 void recordError(String msg, String stackTrace) { |
| 461 message = """ | 397 _setMessage('error', msg, stackTrace); |
| 462 <tr> | |
| 463 <td>${id}</td> | |
| 464 <td class='unittest-error'>ERROR</td> | |
| 465 <td>${msg}</td> | |
| 466 </tr>"""; | |
| 467 if (stackTrace != null) { | |
| 468 message += | |
| 469 "<tr><td></td><td colspan='2'><pre>${stackTrace}</pre></td></tr>"; | |
| 470 } | |
| 471 error = true; | 398 error = true; |
| 472 } | 399 } |
| 473 | 400 |
| 474 void recordFail(String msg, String stackTrace) { | 401 void recordFail(String msg, String stackTrace) { |
| 475 // Include the spec description if we have one. | 402 _setMessage('fail', msg, stackTrace); |
| 476 // TODO(rnystrom): When all of our tests are using group() and test(), we | 403 fail = true; |
| 477 // can assume description will be non-null and eliminate this check. | 404 } |
| 478 if (description != null) { | |
| 479 msg = 'Expectation: $description. $msg'; | |
| 480 } | |
| 481 | 405 |
| 482 message = """ | 406 void _setMessage(String type, String msg, String stackTrace) { |
| 407 message = | |
| 408 ''' | |
| 483 <tr> | 409 <tr> |
| 484 <td>${id}</td> | 410 <td>${id}</td> |
| 485 <td class='unittest-fail'>FAIL</td> | 411 <td class="unittest-$type">${type.toUpperCase()}</td> |
| 486 <td>${msg}</td> | 412 <td>Expectation: $description. $msg</td> |
| 487 </tr>"""; | 413 </tr> |
| 414 '''; | |
| 415 | |
| 488 if (stackTrace != null) { | 416 if (stackTrace != null) { |
| 489 message += | 417 message += |
| 490 "<tr><td></td><td colspan='2'><pre>${stackTrace}</pre></td></tr>"; | 418 '<tr><td></td><td colspan="2"><pre>${stackTrace}</pre></td></tr>'; |
| 491 } | 419 } |
| 492 fail = true; | |
| 493 } | 420 } |
| 494 } | 421 } |
| 495 | 422 |
| 496 typedef void TestFunction(); | 423 typedef void TestFunction(); |
| OLD | NEW |