| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 // TODO(gram): | |
| 6 // Unfortunately I can't seem to test anything that involves timeouts, e.g. | |
| 7 // insufficient callbacks, because the timeout is controlled externally | |
| 8 // (test.dart?), and we would need to use a shorter timeout for the inner tests | |
| 9 // so the outer timeout doesn't fire. So I removed all such tests. | |
| 10 // I'd like to revisit this at some point. | |
| 11 | |
| 12 library unittestTest; | |
| 13 import 'dart:isolate'; | |
| 14 import 'dart:async'; | |
| 15 import 'package:unittest/unittest.dart'; | |
| 16 | |
| 17 Future _defer(void fn()) { | |
| 18 return new Future.sync(fn); | |
| 19 } | |
| 20 | |
| 21 String buildStatusString(int passed, int failed, int errors, | |
| 22 var results, | |
| 23 {int count: 0, | |
| 24 String setup: '', String teardown: '', | |
| 25 String uncaughtError: null, | |
| 26 String message: ''}) { | |
| 27 var totalTests = 0; | |
| 28 var testDetails = new StringBuffer(); | |
| 29 if(results == null) { | |
| 30 // no op | |
| 31 assert(message == ''); | |
| 32 } else if (results is String) { | |
| 33 totalTests = passed + failed + errors; | |
| 34 testDetails.write(':$results:$message'); | |
| 35 } else { | |
| 36 totalTests = results.length; | |
| 37 for (var i = 0; i < results.length; i++) { | |
| 38 testDetails.write(':${results[i].description}:' | |
| 39 '${collapseWhitespace(results[i].message)}'); | |
| 40 } | |
| 41 } | |
| 42 return '$passed:$failed:$errors:$totalTests:$count:' | |
| 43 '$setup:$teardown:$uncaughtError$testDetails'; | |
| 44 } | |
| 45 | |
| 46 class TestConfiguration extends Configuration { | |
| 47 | |
| 48 // Some test state that is captured | |
| 49 int count = 0; // a count of callbacks | |
| 50 String setup = ''; // the name of the test group setup function, if any | |
| 51 String teardown = ''; // the name of the test group teardown function, if any | |
| 52 | |
| 53 // The port to communicate with the parent isolate | |
| 54 final SendPort _port; | |
| 55 String _result; | |
| 56 | |
| 57 TestConfiguration(this._port); | |
| 58 | |
| 59 void onSummary(int passed, int failed, int errors, List<TestCase> results, | |
| 60 String uncaughtError) { | |
| 61 _result = buildStatusString(passed, failed, errors, results, | |
| 62 count: count, setup: setup, teardown: teardown, | |
| 63 uncaughtError: uncaughtError); | |
| 64 } | |
| 65 | |
| 66 void onDone(bool success) { | |
| 67 _port.send(_result); | |
| 68 } | |
| 69 } | |
| 70 | |
| 71 makeDelayedSetup(index, s) => () { | |
| 72 return new Future.delayed(new Duration(milliseconds:1), () { | |
| 73 s.write('l$index U '); | |
| 74 }); | |
| 75 }; | |
| 76 | |
| 77 makeDelayedTeardown(index, s) => () { | |
| 78 return new Future.delayed(new Duration(milliseconds:1), () { | |
| 79 s.write('l$index D '); | |
| 80 }); | |
| 81 }; | |
| 82 | |
| 83 makeImmediateSetup(index, s) => () { | |
| 84 s.write('l$index U '); | |
| 85 }; | |
| 86 | |
| 87 makeImmediateTeardown(index, s) => () { | |
| 88 s.write('l$index D '); | |
| 89 }; | |
| 90 | |
| 91 runTest() { | |
| 92 port.receive((String testName, sendport) { | |
| 93 var testConfig = new TestConfiguration(sendport); | |
| 94 unittestConfiguration = testConfig; | |
| 95 | |
| 96 if (testName == 'single correct test') { | |
| 97 test(testName, () => expect(2 + 3, equals(5))); | |
| 98 } else if (testName == 'single failing test') { | |
| 99 test(testName, () => expect(2 + 2, equals(5))); | |
| 100 } else if (testName == 'exception test') { | |
| 101 test(testName, () { throw new Exception('Fail.'); }); | |
| 102 } else if (testName == 'group name test') { | |
| 103 group('a', () { | |
| 104 test('a', () {}); | |
| 105 group('b', () { | |
| 106 test('b', () {}); | |
| 107 }); | |
| 108 }); | |
| 109 } else if (testName == 'setup test') { | |
| 110 group('a', () { | |
| 111 setUp(() { testConfig.setup = 'setup'; }); | |
| 112 test(testName, () {}); | |
| 113 }); | |
| 114 } else if (testName == 'teardown test') { | |
| 115 group('a', () { | |
| 116 tearDown(() { testConfig.teardown = 'teardown'; }); | |
| 117 test(testName, () {}); | |
| 118 }); | |
| 119 } else if (testName == 'setup and teardown test') { | |
| 120 group('a', () { | |
| 121 setUp(() { testConfig.setup = 'setup'; }); | |
| 122 tearDown(() { testConfig.teardown = 'teardown'; }); | |
| 123 test(testName, () {}); | |
| 124 }); | |
| 125 } else if (testName == 'correct callback test') { | |
| 126 test(testName, | |
| 127 () =>_defer(expectAsync0((){ ++testConfig.count;}))); | |
| 128 } else if (testName == 'excess callback test') { | |
| 129 test(testName, () { | |
| 130 var _callback0 = expectAsync0(() => ++testConfig.count); | |
| 131 var _callback1 = expectAsync0(() => ++testConfig.count); | |
| 132 var _callback2 = expectAsync0(() { | |
| 133 _callback1(); | |
| 134 _callback1(); | |
| 135 _callback0(); | |
| 136 }); | |
| 137 _defer(_callback2); | |
| 138 }); | |
| 139 } else if (testName == 'completion test') { | |
| 140 test(testName, () { | |
| 141 var _callback; | |
| 142 _callback = expectAsyncUntil0(() { | |
| 143 if (++testConfig.count < 10) { | |
| 144 _defer(_callback); | |
| 145 } | |
| 146 }, | |
| 147 () => (testConfig.count == 10)); | |
| 148 _defer(_callback); | |
| 149 }); | |
| 150 } else if (testName == 'async exception test') { | |
| 151 test(testName, () { | |
| 152 expectAsync0(() {}); | |
| 153 _defer(() => guardAsync(() { throw "error!"; })); | |
| 154 }); | |
| 155 } else if (testName == 'late exception test') { | |
| 156 var f; | |
| 157 test('testOne', () { | |
| 158 f = expectAsync0(() {}); | |
| 159 _defer(f); | |
| 160 }); | |
| 161 test('testTwo', () { | |
| 162 _defer(expectAsync0(() { f(); })); | |
| 163 }); | |
| 164 } else if (testName == 'middle exception test') { | |
| 165 test('testOne', () { expect(true, isTrue); }); | |
| 166 test('testTwo', () { expect(true, isFalse); }); | |
| 167 test('testThree', () { | |
| 168 var done = expectAsync0((){}); | |
| 169 _defer(() { | |
| 170 expect(true, isTrue); | |
| 171 done(); | |
| 172 }); | |
| 173 }); | |
| 174 } else if (testName == 'async setup/teardown test') { | |
| 175 group('good setup/good teardown', () { | |
| 176 setUp(() { | |
| 177 return new Future.value(0); | |
| 178 }); | |
| 179 tearDown(() { | |
| 180 return new Future.value(0); | |
| 181 }); | |
| 182 test('foo1', (){}); | |
| 183 }); | |
| 184 group('good setup/bad teardown', () { | |
| 185 setUp(() { | |
| 186 return new Future.value(0); | |
| 187 }); | |
| 188 tearDown(() { | |
| 189 return new Future.error("Failed to complete tearDown"); | |
| 190 }); | |
| 191 test('foo2', (){}); | |
| 192 }); | |
| 193 group('bad setup/good teardown', () { | |
| 194 setUp(() { | |
| 195 return new Future.error("Failed to complete setUp"); | |
| 196 }); | |
| 197 tearDown(() { | |
| 198 return new Future.value(0); | |
| 199 }); | |
| 200 test('foo3', (){}); | |
| 201 }); | |
| 202 group('bad setup/bad teardown', () { | |
| 203 setUp(() { | |
| 204 return new Future.error("Failed to complete setUp"); | |
| 205 }); | |
| 206 tearDown(() { | |
| 207 return new Future.error("Failed to complete tearDown"); | |
| 208 }); | |
| 209 test('foo4', (){}); | |
| 210 }); | |
| 211 // The next test is just to make sure we make steady progress | |
| 212 // through the tests. | |
| 213 test('post groups', () {}); | |
| 214 } else if (testName == 'test returning future') { | |
| 215 test("successful", () { | |
| 216 return _defer(() { | |
| 217 expect(true, true); | |
| 218 }); | |
| 219 }); | |
| 220 // We repeat the fail and error tests, because during development | |
| 221 // I had a situation where either worked fine on their own, and | |
| 222 // error/fail worked, but fail/error would time out. | |
| 223 test("error1", () { | |
| 224 var callback = expectAsync0((){}); | |
| 225 var excesscallback = expectAsync0((){}); | |
| 226 return _defer(() { | |
| 227 excesscallback(); | |
| 228 excesscallback(); | |
| 229 excesscallback(); | |
| 230 callback(); | |
| 231 }); | |
| 232 }); | |
| 233 test("fail1", () { | |
| 234 return _defer(() { | |
| 235 expect(true, false); | |
| 236 }); | |
| 237 }); | |
| 238 test("error2", () { | |
| 239 var callback = expectAsync0((){}); | |
| 240 var excesscallback = expectAsync0((){}); | |
| 241 return _defer(() { | |
| 242 excesscallback(); | |
| 243 excesscallback(); | |
| 244 callback(); | |
| 245 }); | |
| 246 }); | |
| 247 test("fail2", () { | |
| 248 return _defer(() { | |
| 249 fail('failure'); | |
| 250 }); | |
| 251 }); | |
| 252 test('foo5', () { | |
| 253 }); | |
| 254 } else if (testName == 'test returning future using runAsync') { | |
| 255 test("successful", () { | |
| 256 return _defer(() { | |
| 257 runAsync(() { | |
| 258 guardAsync(() { | |
| 259 expect(true, true); | |
| 260 }); | |
| 261 }); | |
| 262 }); | |
| 263 }); | |
| 264 test("fail1", () { | |
| 265 var callback = expectAsync0((){}); | |
| 266 return _defer(() { | |
| 267 runAsync(() { | |
| 268 guardAsync(() { | |
| 269 expect(true, false); | |
| 270 callback(); | |
| 271 }); | |
| 272 }); | |
| 273 }); | |
| 274 }); | |
| 275 test('error1', () { | |
| 276 var callback = expectAsync0((){}); | |
| 277 var excesscallback = expectAsync0((){}); | |
| 278 return _defer(() { | |
| 279 runAsync(() { | |
| 280 guardAsync(() { | |
| 281 excesscallback(); | |
| 282 excesscallback(); | |
| 283 callback(); | |
| 284 }); | |
| 285 }); | |
| 286 }); | |
| 287 }); | |
| 288 test("fail2", () { | |
| 289 var callback = expectAsync0((){}); | |
| 290 return _defer(() { | |
| 291 runAsync(() { | |
| 292 guardAsync(() { | |
| 293 fail('failure'); | |
| 294 callback(); | |
| 295 }); | |
| 296 }); | |
| 297 }); | |
| 298 }); | |
| 299 test('error2', () { | |
| 300 var callback = expectAsync0((){}); | |
| 301 var excesscallback = expectAsync0((){}); | |
| 302 return _defer(() { | |
| 303 runAsync(() { | |
| 304 guardAsync(() { | |
| 305 excesscallback(); | |
| 306 excesscallback(); | |
| 307 excesscallback(); | |
| 308 callback(); | |
| 309 }); | |
| 310 }); | |
| 311 }); | |
| 312 }); | |
| 313 test('foo6', () { | |
| 314 }); | |
| 315 } else if (testName == 'testCases immutable') { | |
| 316 test(testName, () { | |
| 317 expect(() => testCases.clear(), throwsUnsupportedError); | |
| 318 expect(() => testCases.removeLast(), throwsUnsupportedError); | |
| 319 }); | |
| 320 } else if (testName == 'runTests without tests') { | |
| 321 runTests(); | |
| 322 } else if (testName == 'nested groups setup/teardown') { | |
| 323 StringBuffer s = new StringBuffer(); | |
| 324 group('level 1', () { | |
| 325 setUp(makeDelayedSetup(1, s)); | |
| 326 group('level 2', () { | |
| 327 setUp(makeImmediateSetup(2, s)); | |
| 328 tearDown(makeDelayedTeardown(2, s)); | |
| 329 group('level 3', () { | |
| 330 group('level 4', () { | |
| 331 setUp(makeDelayedSetup(4, s)); | |
| 332 tearDown(makeImmediateTeardown(4, s)); | |
| 333 group('level 5', () { | |
| 334 setUp(makeImmediateSetup(5, s)); | |
| 335 group('level 6', () { | |
| 336 tearDown(makeDelayedTeardown(6, s)); | |
| 337 test('inner', () {}); | |
| 338 }); | |
| 339 }); | |
| 340 }); | |
| 341 }); | |
| 342 }); | |
| 343 }); | |
| 344 test('after nest', () { | |
| 345 expect(s.toString(), "l1 U l2 U l4 U l5 U l6 D l4 D l2 D "); | |
| 346 }); | |
| 347 } else if (testName == 'skipped/soloed nested groups with setup/teardown') { | |
| 348 StringBuffer s = null; | |
| 349 setUp(() { | |
| 350 if (s == null) | |
| 351 s = new StringBuffer(); | |
| 352 }); | |
| 353 test('top level', () { | |
| 354 s.write('A'); | |
| 355 }); | |
| 356 skip_test('skipped top level', () { | |
| 357 s.write('B'); | |
| 358 }); | |
| 359 skip_group('skipped top level group', () { | |
| 360 setUp(() { | |
| 361 s.write('C'); | |
| 362 }); | |
| 363 solo_test('skipped solo nested test', () { | |
| 364 s.write('D'); | |
| 365 }); | |
| 366 }); | |
| 367 group('non-solo group', () { | |
| 368 setUp(() { | |
| 369 s.write('E'); | |
| 370 }); | |
| 371 test('in non-solo group', () { | |
| 372 s.write('F'); | |
| 373 }); | |
| 374 solo_test('solo_test in non-solo group', () { | |
| 375 s.write('G'); | |
| 376 }); | |
| 377 }); | |
| 378 solo_group('solo group', () { | |
| 379 setUp(() { | |
| 380 s.write('H'); | |
| 381 }); | |
| 382 test('solo group non-solo test', () { | |
| 383 s.write('I'); | |
| 384 }); | |
| 385 solo_test('solo group solo test', () { | |
| 386 s.write('J'); | |
| 387 }); | |
| 388 group('nested non-solo group in solo group', () { | |
| 389 test('nested non-solo group non-solo test', () { | |
| 390 s.write('K'); | |
| 391 }); | |
| 392 solo_test('nested non-solo group solo test', () { | |
| 393 s.write('L'); | |
| 394 }); | |
| 395 }); | |
| 396 }); | |
| 397 solo_test('final', () { | |
| 398 expect(s.toString(), "EGHIHJHKHL"); | |
| 399 }); | |
| 400 } | |
| 401 }); | |
| 402 } | |
| 403 | |
| 404 main() { | |
| 405 var tests = { | |
| 406 'single correct test': buildStatusString(1, 0, 0, 'single correct test'), | |
| 407 'single failing test': buildStatusString(0, 1, 0, 'single failing test', | |
| 408 message: 'Expected: <5> Actual: <4>'), | |
| 409 'exception test': buildStatusString(0, 0, 1, 'exception test', | |
| 410 message: 'Test failed: Caught Exception: Fail.'), | |
| 411 'group name test': buildStatusString(2, 0, 0, 'a a::a b b'), | |
| 412 'setup test': buildStatusString(1, 0, 0, 'a setup test', | |
| 413 count: 0, setup: 'setup'), | |
| 414 'teardown test': buildStatusString(1, 0, 0, 'a teardown test', | |
| 415 count: 0, setup: '', teardown: 'teardown'), | |
| 416 'setup and teardown test': buildStatusString(1, 0, 0, | |
| 417 'a setup and teardown test', count: 0, setup: 'setup', | |
| 418 teardown: 'teardown'), | |
| 419 'correct callback test': buildStatusString(1, 0, 0, 'correct callback test', | |
| 420 count: 1), | |
| 421 'excess callback test': buildStatusString(0, 1, 0, 'excess callback test', | |
| 422 count: 1, message: 'Callback called more times than expected (1).'), | |
| 423 'completion test': buildStatusString(1, 0, 0, 'completion test', count: 10), | |
| 424 'async exception test': buildStatusString(0, 1, 0, 'async exception test', | |
| 425 message: 'Caught error!'), | |
| 426 'late exception test': buildStatusString(1, 0, 1, 'testOne', | |
| 427 message: 'Callback called (2) after test case testOne has already ' | |
| 428 'been marked as pass.:testTwo:'), | |
| 429 'middle exception test': buildStatusString(2, 1, 0, | |
| 430 'testOne::testTwo:Expected: false Actual: <true>:testThree'), | |
| 431 'async setup/teardown test': buildStatusString(2, 0, 3, | |
| 432 'good setup/good teardown foo1::' | |
| 433 'good setup/bad teardown foo2:' | |
| 434 'Teardown failed: Caught Failed to complete tearDown:' | |
| 435 'bad setup/good teardown foo3:' | |
| 436 'Setup failed: Caught Failed to complete setUp:' | |
| 437 'bad setup/bad teardown foo4:' | |
| 438 'Setup failed: Caught Failed to complete setUp:' | |
| 439 'post groups'), | |
| 440 'test returning future': buildStatusString(2, 4, 0, | |
| 441 'successful::' | |
| 442 'error1:Callback called more times than expected (1).:' | |
| 443 'fail1:Expected: <false> Actual: <true>:' | |
| 444 'error2:Callback called more times than expected (1).:' | |
| 445 'fail2:failure:' | |
| 446 'foo5'), | |
| 447 'test returning future using runAsync': buildStatusString(2, 4, 0, | |
| 448 'successful::' | |
| 449 'fail1:Expected: <false> Actual: <true>:' | |
| 450 'error1:Callback called more times than expected (1).:' | |
| 451 'fail2:failure:' | |
| 452 'error2:Callback called more times than expected (1).:' | |
| 453 'foo6'), | |
| 454 'testCases immutable': | |
| 455 buildStatusString(1, 0, 0, 'testCases immutable'), | |
| 456 'runTests without tests': buildStatusString(0, 0, 0, null), | |
| 457 'nested groups setup/teardown': | |
| 458 buildStatusString(2, 0, 0, | |
| 459 'level 1 level 2 level 3 level 4 level 5 level 6 inner::' | |
| 460 'after nest'), | |
| 461 'skipped/soloed nested groups with setup/teardown': | |
| 462 buildStatusString(6, 0, 0, | |
| 463 'non-solo group solo_test in non-solo group::' | |
| 464 'solo group solo group non-solo test::' | |
| 465 'solo group solo group solo test::' | |
| 466 'solo group nested non-solo group in solo group nested non-' | |
| 467 'solo group non-solo test::' | |
| 468 'solo group nested non-solo group in solo' | |
| 469 ' group nested non-solo group solo test::' | |
| 470 'final') | |
| 471 }; | |
| 472 | |
| 473 tests.forEach((String name, String expected) { | |
| 474 test(name, () => spawnFunction(runTest) | |
| 475 .call(name) | |
| 476 .then((String msg) => expect(msg.trim(), equals(expected)))); | |
| 477 }); | |
| 478 } | |
| 479 | |
| OLD | NEW |