| 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 /** | 5 /** |
| 6 * 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, |
| 7 * this will contain all of their text concatenated. | 7 * this will contain all of their text concatenated. |
| 8 */ | 8 */ |
| 9 String _currentGroup = ''; | 9 String _currentGroup = ''; |
| 10 | 10 |
| 11 /** Tests executed in this suite. */ | 11 /** Tests executed in this suite. */ |
| 12 List<TestCase> _tests; | 12 List<TestCase> _tests; |
| 13 | 13 |
| 14 /** Whether this is run within dartium layout tests. */ | 14 /** Whether this is run within dartium layout tests. */ |
| 15 bool _isLayoutTest = false; | 15 bool _isLayoutTest = false; |
| 16 | 16 |
| 17 /** Current test being executed. */ | 17 /** Current test being executed. */ |
| 18 int _currentTest = 0; | 18 int _currentTest = 0; |
| 19 | 19 |
| 20 /** Total number of callbacks that have been executed in the current test. */ | 20 /** Total number of callbacks that have been executed in the current test. */ |
| 21 int _callbacksCalled = 0; | 21 int _callbacksCalled = 0; |
| 22 | 22 |
| 23 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. | |
| 24 EventListener _onErrorClosure; | |
| 25 | |
| 26 final _UNINITIALIZED = 0; | 23 final _UNINITIALIZED = 0; |
| 27 final _READY = 1; | 24 final _READY = 1; |
| 28 final _RUNNING_TEST = 2; | 25 final _RUNNING_TEST = 2; |
| 29 | 26 |
| 30 /** | 27 /** |
| 31 * Whether an undetected error occurred while running the last test. These | 28 * 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 | 29 * errors are commonly caused by DOM callbacks that were not guarded in a |
| 33 * try-catch block. | 30 * try-catch block. |
| 34 */ | 31 */ |
| 35 final _UNCAUGHT_ERROR = 3; | 32 final _UNCAUGHT_ERROR = 3; |
| 36 | 33 |
| 37 int _state = _UNINITIALIZED; | 34 int _state = _UNINITIALIZED; |
| 38 | 35 |
| 36 final _PASS = 'pass'; |
| 37 final _FAIL = 'fail'; |
| 38 final _ERROR = 'error'; |
| 39 |
| 39 /** Creates an expectation for the given value. */ | 40 /** Creates an expectation for the given value. */ |
| 40 Expectation expect(value) => new Expectation(value); | 41 Expectation expect(value) => new Expectation(value); |
| 41 | 42 |
| 42 /** Evaluates the given function and validates that it throws an exception. */ | 43 /** Evaluates the given function and validates that it throws an exception. */ |
| 43 void expectThrow(function) { | 44 void expectThrow(function) { |
| 44 bool threw = false; | 45 bool threw = false; |
| 45 try { | 46 try { |
| 46 function(); | 47 function(); |
| 47 } catch (var e) { | 48 } catch (var e) { |
| 48 threw = true; | 49 threw = true; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 67 * calls. | 68 * calls. |
| 68 */ | 69 */ |
| 69 void asyncTest(String spec, int callbacks, TestFunction body) { | 70 void asyncTest(String spec, int callbacks, TestFunction body) { |
| 70 _ensureInitialized(); | 71 _ensureInitialized(); |
| 71 | 72 |
| 72 final testCase = new TestCase( | 73 final testCase = new TestCase( |
| 73 _tests.length + 1, _fullSpec(spec), body, callbacks); | 74 _tests.length + 1, _fullSpec(spec), body, callbacks); |
| 74 _tests.add(testCase); | 75 _tests.add(testCase); |
| 75 | 76 |
| 76 if (callbacks < 1) { | 77 if (callbacks < 1) { |
| 77 testCase.recordError( | 78 testCase.error( |
| 78 'Async tests must wait for at least one callback ', ''); | 79 'Async tests must wait for at least one callback ', ''); |
| 79 } | 80 } |
| 80 } | 81 } |
| 81 | 82 |
| 82 /** | 83 /** |
| 83 * Creates a new named group of tests. Calls to group() or test() within the | 84 * Creates a new named group of tests. Calls to group() or test() within the |
| 84 * body of the function passed to this will inherit this group's description. | 85 * body of the function passed to this will inherit this group's description. |
| 85 */ | 86 */ |
| 86 void group(String description, void body()) { | 87 void group(String description, void body()) { |
| 87 _ensureInitialized(); | 88 _ensureInitialized(); |
| (...skipping 14 matching lines...) Expand all Loading... |
| 102 // Now that the group is over, restore the previous one. | 103 // Now that the group is over, restore the previous one. |
| 103 _currentGroup = oldGroup; | 104 _currentGroup = oldGroup; |
| 104 } | 105 } |
| 105 } | 106 } |
| 106 | 107 |
| 107 /** Called by subclasses to indicate that an asynchronous test completed. */ | 108 /** Called by subclasses to indicate that an asynchronous test completed. */ |
| 108 void callbackDone() { | 109 void callbackDone() { |
| 109 _callbacksCalled++; | 110 _callbacksCalled++; |
| 110 final testCase = _tests[_currentTest]; | 111 final testCase = _tests[_currentTest]; |
| 111 if (testCase.callbacks == 0) { | 112 if (testCase.callbacks == 0) { |
| 112 testCase.recordError( | 113 testCase.error( |
| 113 "Can't call callbackDone() on a synchronous test", ''); | 114 "Can't call callbackDone() on a synchronous test", ''); |
| 114 _state = _UNCAUGHT_ERROR; | 115 _state = _UNCAUGHT_ERROR; |
| 115 } else if (_callbacksCalled > testCase.callbacks) { | 116 } else if (_callbacksCalled > testCase.callbacks) { |
| 116 final expected = testCase.callbacks; | 117 final expected = testCase.callbacks; |
| 117 testCase.recordError( | 118 testCase.error( |
| 118 'More calls to callbackDone() than expected. ' | 119 'More calls to callbackDone() than expected. ' |
| 119 + 'Actual: ${_callbacksCalled}, expected: ${expected}', ''); | 120 + 'Actual: ${_callbacksCalled}, expected: ${expected}', ''); |
| 120 _state = _UNCAUGHT_ERROR; | 121 _state = _UNCAUGHT_ERROR; |
| 121 } else if ((_callbacksCalled == testCase.callbacks) && | 122 } else if ((_callbacksCalled == testCase.callbacks) && |
| 122 (_state != _RUNNING_TEST)) { | 123 (_state != _RUNNING_TEST)) { |
| 123 testCase.recordSuccess(); | 124 testCase.pass(); |
| 124 _currentTest++; | 125 _currentTest++; |
| 125 _nextBatch(); | 126 _nextBatch(); |
| 126 } | 127 } |
| 127 } | 128 } |
| 128 | 129 |
| 129 void forLayoutTests() { | 130 void forLayoutTests() { |
| 130 _isLayoutTest = true; | 131 _isLayoutTest = true; |
| 131 } | 132 } |
| 132 | 133 |
| 133 /** Runs all queued tests, one at a time. */ | 134 /** Runs all queued tests, one at a time. */ |
| 134 _runTests() { | 135 _runTests() { |
| 135 window.postMessage('unittest-suite-start', '*'); | 136 _platformStartTests(); |
| 136 window.setTimeout(() { | 137 |
| 138 _platformDefer(() { |
| 137 assert (_currentTest == 0); | 139 assert (_currentTest == 0); |
| 138 | |
| 139 // Listen for uncaught errors. | |
| 140 window.onerror = _onErrorClosure; | |
| 141 _nextBatch(); | 140 _nextBatch(); |
| 142 }, 0); | 141 }); |
| 143 } | 142 } |
| 144 | 143 |
| 145 /** Runs a single test. */ | 144 /** Runs a single test. */ |
| 146 _runTest(TestCase testCase) { | 145 _runTest(TestCase testCase) { |
| 147 try { | 146 try { |
| 148 // TODO(sigmund): remove this declaration once dartc supports trapping error | 147 // TODO(sigmund): remove this declaration once dartc supports trapping error |
| 149 // traces. | 148 // traces. |
| 150 var trace = ''; | 149 var trace = ''; |
| 151 _callbacksCalled = 0; | 150 _callbacksCalled = 0; |
| 152 _state = _RUNNING_TEST; | 151 _state = _RUNNING_TEST; |
| 153 | 152 |
| 154 testCase.test(); | 153 testCase.test(); |
| 155 | 154 |
| 156 if (_state != _UNCAUGHT_ERROR) { | 155 if (_state != _UNCAUGHT_ERROR) { |
| 157 if (testCase.callbacks == _callbacksCalled) { | 156 if (testCase.callbacks == _callbacksCalled) { |
| 158 testCase.recordSuccess(); | 157 testCase.pass(); |
| 159 } | 158 } |
| 160 } | 159 } |
| 160 |
| 161 } catch (ExpectException e, var trace) { | 161 } catch (ExpectException e, var trace) { |
| 162 if (_state != _UNCAUGHT_ERROR) { | 162 if (_state != _UNCAUGHT_ERROR) { |
| 163 testCase.recordFail(e.message, trace.toString()); | 163 testCase.fail(e.message, trace.toString()); |
| 164 } | 164 } |
| 165 } catch (var e, var trace) { | 165 } catch (var e, var trace) { |
| 166 if (_state != _UNCAUGHT_ERROR) { | 166 if (_state != _UNCAUGHT_ERROR) { |
| 167 testCase.recordError('Caught ${e}', trace.toString()); | 167 testCase.error('Caught ${e}', trace.toString()); |
| 168 } | 168 } |
| 169 } finally { | 169 } finally { |
| 170 _state = _READY; | 170 _state = _READY; |
| 171 } | 171 } |
| 172 } | 172 } |
| 173 | 173 |
| 174 /** | 174 /** |
| 175 * Runs a batch of tests, yielding whenever an asynchronous test starts | 175 * Runs a batch of tests, yielding whenever an asynchronous test starts |
| 176 * running. Tests will resume executing when such asynchronous test calls | 176 * running. Tests will resume executing when such asynchronous test calls |
| 177 * [done] or if it fails with an exception. | 177 * [done] or if it fails with an exception. |
| 178 */ | 178 */ |
| 179 _nextBatch() { | 179 _nextBatch() { |
| 180 while (_currentTest < _tests.length) { | 180 while (_currentTest < _tests.length) { |
| 181 final testCase = _tests[_currentTest]; | 181 final testCase = _tests[_currentTest]; |
| 182 | 182 |
| 183 _runTest(testCase); | 183 _runTest(testCase); |
| 184 | 184 |
| 185 if (!testCase.isComplete && testCase.callbacks > 0) return; | 185 if (!testCase.isComplete && testCase.callbacks > 0) return; |
| 186 | 186 |
| 187 _currentTest++; | 187 _currentTest++; |
| 188 } | 188 } |
| 189 | 189 |
| 190 _completeTests(); | 190 _completeTests(); |
| 191 } | 191 } |
| 192 | 192 |
| 193 /** Publish results on the page and notify controller. */ | 193 /** Publish results on the page and notify controller. */ |
| 194 _completeTests() { | 194 _completeTests() { |
| 195 window.onerror = null; | |
| 196 | |
| 197 _state = _UNINITIALIZED; | 195 _state = _UNINITIALIZED; |
| 198 | 196 |
| 197 int testsPassed = 0; |
| 199 int testsFailed = 0; | 198 int testsFailed = 0; |
| 200 int testsErrors = 0; | 199 int testsErrors = 0; |
| 201 int testsPassed = 0; | |
| 202 | 200 |
| 203 for (TestCase t in _tests) { | 201 for (TestCase t in _tests) { |
| 204 if (t.success) testsPassed++; | 202 switch (t.result) { |
| 205 if (t.fail) testsFailed++; | 203 case _PASS: testsPassed++; break; |
| 206 if (t.error) testsErrors++; | 204 case _FAIL: testsFailed++; break; |
| 205 case _ERROR: testsErrors++; break; |
| 206 } |
| 207 } | 207 } |
| 208 | 208 |
| 209 if (_isLayoutTest && testsPassed == _tests.length) { | 209 _platformCompleteTests(testsPassed, testsFailed, testsErrors); |
| 210 document.body.innerHTML = "PASS"; | |
| 211 } else { | |
| 212 var newBody = new StringBuffer(); | |
| 213 newBody.add("<table class='unittest-table'><tbody>"); | |
| 214 newBody.add(testsPassed == _tests.length | |
| 215 ? "<tr><td colspan='3' class='unittest-pass'>PASS</td></tr>" | |
| 216 : "<tr><td colspan='3' class='unittest-fail'>FAIL</td></tr>"); | |
| 217 | |
| 218 for (final test in _tests) { | |
| 219 newBody.add(test.message); | |
| 220 } | |
| 221 | |
| 222 if (testsPassed == _tests.length) { | |
| 223 newBody.add("<tr><td colspan='3' class='unittest-pass'>All " | |
| 224 + testsPassed + " tests passed</td></tr>"); | |
| 225 } else { | |
| 226 newBody.add(""" | |
| 227 <tr><td colspan='3'>Total | |
| 228 <span class='unittest-pass'>${testsPassed} passed</span>, | |
| 229 <span class='unittest-fail'>${testsFailed} failed</span> | |
| 230 <span class='unittest-error'>${testsErrors} errors</span> | |
| 231 </td></tr>"""); | |
| 232 } | |
| 233 newBody.add("</tbody></table>"); | |
| 234 document.body.innerHTML = newBody.toString(); | |
| 235 } | |
| 236 | |
| 237 window.postMessage('unittest-suite-done', '*'); | |
| 238 } | |
| 239 | |
| 240 void _onError(e) { | |
| 241 if (_currentTest < _tests.length) { | |
| 242 final testCase = _tests[_currentTest]; | |
| 243 // TODO(vsm): figure out how to expose the stack trace here | |
| 244 // Currently e.message works in dartium, but not in dartc. | |
| 245 testCase.recordError('(DOM callback has errors) Caught ${e}', ''); | |
| 246 _state = _UNCAUGHT_ERROR; | |
| 247 if (testCase.callbacks > 0) { | |
| 248 _currentTest++; | |
| 249 _nextBatch(); | |
| 250 } | |
| 251 } | |
| 252 } | 210 } |
| 253 | 211 |
| 254 String _fullSpec(String spec) { | 212 String _fullSpec(String spec) { |
| 255 if (spec === null) return '$_currentGroup'; | 213 if (spec === null) return '$_currentGroup'; |
| 256 return _currentGroup != '' ? '$_currentGroup $spec' : spec; | 214 return _currentGroup != '' ? '$_currentGroup $spec' : spec; |
| 257 } | 215 } |
| 258 | 216 |
| 259 /** | 217 /** |
| 260 * Lazily initializes the test library if not already initialized. | 218 * Lazily initializes the test library if not already initialized. |
| 261 */ | 219 */ |
| 262 _ensureInitialized() { | 220 _ensureInitialized() { |
| 263 if (_state != _UNINITIALIZED) return; | 221 if (_state != _UNINITIALIZED) return; |
| 264 | 222 |
| 265 _tests = <TestCase>[]; | 223 _tests = <TestCase>[]; |
| 266 _onErrorClosure = (e) { _onError(e); }; | 224 _currentGroup = ''; |
| 225 _state = _READY; |
| 226 |
| 227 _platformInitialize(); |
| 267 | 228 |
| 268 // Immediately queue the suite up. It will run after a timeout (i.e. after | 229 // Immediately queue the suite up. It will run after a timeout (i.e. after |
| 269 // main() has returned). | 230 // main() has returned). |
| 270 listener() { | 231 _platformDefer(_runTests); |
| 271 _currentGroup = ''; | |
| 272 _runTests(); | |
| 273 }; | |
| 274 | |
| 275 window.setTimeout(listener, 0); | |
| 276 | |
| 277 _state = _READY; | |
| 278 } | 232 } |
| 279 | 233 |
| 280 /** | 234 /** |
| 281 * Wraps an value and provides an "==" operator that can be used to verify that | 235 * Wraps an value and provides an "==" operator that can be used to verify that |
| 282 * the value matches a given expectation. | 236 * the value matches a given expectation. |
| 283 */ | 237 */ |
| 284 class Expectation { | 238 class Expectation { |
| 285 final _value; | 239 final _value; |
| 286 | 240 |
| 287 Expectation(this._value); | 241 Expectation(this._value); |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 342 | 296 |
| 343 /** A description of what the test is specifying. */ | 297 /** A description of what the test is specifying. */ |
| 344 final String description; | 298 final String description; |
| 345 | 299 |
| 346 /** The body of the test case. */ | 300 /** The body of the test case. */ |
| 347 final TestFunction test; | 301 final TestFunction test; |
| 348 | 302 |
| 349 /** Total number of callbacks to wait for before the test completes. */ | 303 /** Total number of callbacks to wait for before the test completes. */ |
| 350 int callbacks; | 304 int callbacks; |
| 351 | 305 |
| 352 /** Whether this test case was succesful. */ | 306 /** Error or failure message. */ |
| 353 bool success; | 307 String message = ''; |
| 354 | 308 |
| 355 /** Whether an Expect call failed in this test. */ | 309 /** |
| 356 bool fail; | 310 * One of [_PASS], [_FAIL], or [_ERROR] or [null] if the test hasn't run yet. |
| 311 */ |
| 312 String result; |
| 357 | 313 |
| 358 /** Whether this test case had a runtime error. */ | 314 /** Stack trace associated with this test, or null if it succeeded. */ |
| 359 bool error; | 315 String stackTrace; |
| 360 | 316 |
| 361 /** Messages to display at the end of the test run. */ | 317 TestCase(this.id, this.description, this.test, this.callbacks); |
| 362 String message; | |
| 363 | 318 |
| 364 TestCase(this.id, this.description, this.test, this.callbacks) | 319 bool get isComplete() => result != null; |
| 365 : success = false, | 320 |
| 366 fail = false, | 321 void pass() { |
| 367 error = false { | 322 result = _PASS; |
| 368 message = '''<tr> | |
| 369 <td>${id}</td> | |
| 370 <td class="unittest-error">NO STATUS</td> | |
| 371 <td>Test did not complete</td> | |
| 372 </tr>'''; | |
| 373 } | 323 } |
| 374 | 324 |
| 375 bool get isComplete() => success || fail || error; | 325 void fail(String message, String stackTrace) { |
| 376 | 326 result = _FAIL; |
| 377 void recordSuccess() { | 327 this.message = message; |
| 378 _setMessage('pass', '', null); | 328 this.stackTrace = stackTrace; |
| 379 success = true; | |
| 380 } | 329 } |
| 381 | 330 |
| 382 void recordError(String msg, String stackTrace) { | 331 void error(String message, String stackTrace) { |
| 383 _setMessage('error', msg, stackTrace); | 332 result = _ERROR; |
| 384 error = true; | 333 this.message = message; |
| 385 } | 334 this.stackTrace = stackTrace; |
| 386 | |
| 387 void recordFail(String msg, String stackTrace) { | |
| 388 _setMessage('fail', msg, stackTrace); | |
| 389 fail = true; | |
| 390 } | |
| 391 | |
| 392 void _setMessage(String type, String msg, String stackTrace) { | |
| 393 message = | |
| 394 ''' | |
| 395 <tr> | |
| 396 <td>${id}</td> | |
| 397 <td class="unittest-$type">${type.toUpperCase()}</td> | |
| 398 <td>Expectation: $description. $msg</td> | |
| 399 </tr> | |
| 400 '''; | |
| 401 | |
| 402 if (stackTrace != null) { | |
| 403 message += | |
| 404 '<tr><td></td><td colspan="2"><pre>${stackTrace}</pre></td></tr>'; | |
| 405 } | |
| 406 } | 335 } |
| 407 } | 336 } |
| 408 | 337 |
| 409 typedef void TestFunction(); | 338 typedef void TestFunction(); |
| OLD | NEW |