helpers.js (22765B)
1 /** 2 * Any copyright is dedicated to the Public Domain. 3 * http://creativecommons.org/publicdomain/zero/1.0/ 4 */ 5 6 /* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ 7 8 // testSteps is expected to be defined by the test using this file. 9 /* global testSteps:false */ 10 11 var testGenerator; 12 if (testSteps.constructor.name === "GeneratorFunction") { 13 testGenerator = testSteps(); 14 } 15 // The test js is shared between xpcshell (which has no SpecialPowers object) 16 // and content mochitests (where the |Components| object is accessible only as 17 // SpecialPowers.Components). Expose Components if necessary here to make things 18 // work everywhere. 19 // 20 // Even if the real |Components| doesn't exist, we might shim in a simple JS 21 // placebo for compat. An easy way to differentiate this from the real thing 22 // is whether the property is read-only or not. 23 var c = Object.getOwnPropertyDescriptor(this, "Components"); 24 if ((!c || !c.value || c.writable) && typeof SpecialPowers === "object") { 25 // eslint-disable-next-line no-global-assign 26 Components = SpecialPowers.wrap(SpecialPowers.Components); 27 } 28 29 function executeSoon(aFun) { 30 SpecialPowers.Services.tm.dispatchToMainThread({ 31 run() { 32 aFun(); 33 }, 34 }); 35 } 36 37 function clearAllDatabases(callback) { 38 let qms = SpecialPowers.Services.qms; 39 let principal = SpecialPowers.wrap(document).effectiveStoragePrincipal; 40 let request = qms.clearStoragesForPrincipal(principal); 41 let cb = SpecialPowers.wrapCallback(callback); 42 request.callback = cb; 43 } 44 45 var testHarnessGenerator = testHarnessSteps(); 46 testHarnessGenerator.next(); 47 48 function* testHarnessSteps() { 49 function nextTestHarnessStep(val) { 50 testHarnessGenerator.next(val); 51 } 52 53 let testScriptPath; 54 let testScriptFilename; 55 56 let scripts = document.getElementsByTagName("script"); 57 for (let i = 0; i < scripts.length; i++) { 58 let src = scripts[i].src; 59 let match = src.match(/indexedDB\/test\/unit\/(test_[^\/]+\.js)$/); 60 if (match && match.length == 2) { 61 testScriptPath = src; 62 testScriptFilename = match[1]; 63 break; 64 } 65 } 66 67 yield undefined; 68 69 info("Running" + (testScriptFilename ? " '" + testScriptFilename + "'" : "")); 70 71 info("Pushing preferences"); 72 73 SpecialPowers.pushPrefEnv( 74 { 75 set: [ 76 ["dom.indexedDB.testing", true], 77 ["dom.indexedDB.experimental", true], 78 ["javascript.options.wasm_baselinejit", true], // This can be removed when on by default 79 ], 80 }, 81 nextTestHarnessStep 82 ); 83 yield undefined; 84 85 info("Pushing permissions"); 86 87 SpecialPowers.pushPermissions( 88 [ 89 { 90 type: "indexedDB", 91 allow: true, 92 context: document, 93 }, 94 ], 95 nextTestHarnessStep 96 ); 97 yield undefined; 98 99 info("Clearing old databases"); 100 101 clearAllDatabases(nextTestHarnessStep); 102 yield undefined; 103 104 if (testScriptFilename && !window.disableWorkerTest) { 105 // For the AsyncFunction, handle the executing sequece using 106 // add_task(). For the GeneratorFunction, we just handle the sequence 107 // manually. 108 if (testSteps.constructor.name === "AsyncFunction") { 109 add_task(function workerTestSteps() { 110 return executeWorkerTestAndCleanUp(testScriptPath); 111 }); 112 } else { 113 ok( 114 testSteps.constructor.name === "GeneratorFunction", 115 "Unsupported function type" 116 ); 117 executeWorkerTestAndCleanUp(testScriptPath).then(nextTestHarnessStep); 118 119 yield undefined; 120 } 121 } else if (testScriptFilename) { 122 todo( 123 false, 124 "Skipping test in a worker because it is explicitly disabled: " + 125 window.disableWorkerTest 126 ); 127 } else { 128 todo( 129 false, 130 "Skipping test in a worker because it's not structured properly" 131 ); 132 } 133 134 info("Running test in main thread"); 135 136 // Now run the test script in the main thread. 137 if (testSteps.constructor.name === "AsyncFunction") { 138 // Register a callback to clean up databases because it's the only way for 139 // add_task() to clean them right before the SimpleTest.FinishTest 140 SimpleTest.registerCleanupFunction(async function () { 141 await new Promise(function (resolve, reject) { 142 clearAllDatabases(function (result) { 143 if (result.resultCode == SpecialPowers.Cr.NS_OK) { 144 resolve(result); 145 } else { 146 reject(result.resultCode); 147 } 148 }); 149 }); 150 }); 151 152 add_task(testSteps); 153 } else { 154 testGenerator.next(); 155 156 yield undefined; 157 } 158 } 159 160 if (!window.runTest) { 161 window.runTest = function () { 162 SimpleTest.waitForExplicitFinish(); 163 testHarnessGenerator.next(); 164 }; 165 } 166 167 function finishTest() { 168 ok( 169 testSteps.constructor.name === "GeneratorFunction", 170 "Async/await tests shouldn't call finishTest()" 171 ); 172 SimpleTest.executeSoon(function () { 173 clearAllDatabases(function () { 174 SimpleTest.finish(); 175 }); 176 }); 177 } 178 179 function browserRunTest() { 180 testGenerator.next(); 181 } 182 183 function browserFinishTest() {} 184 185 function grabEventAndContinueHandler(event) { 186 testGenerator.next(event); 187 } 188 189 function continueToNextStep() { 190 SimpleTest.executeSoon(function () { 191 testGenerator.next(); 192 }); 193 } 194 195 function continueToNextStepSync() { 196 testGenerator.next(); 197 } 198 199 function errorHandler(event) { 200 ok(false, "indexedDB error, '" + event.target.error.name + "'"); 201 finishTest(); 202 } 203 204 // For error callbacks where the argument is not an event object. 205 function errorCallbackHandler(err) { 206 ok(false, "got unexpected error callback: " + err); 207 finishTest(); 208 } 209 210 function expectUncaughtException(expecting) { 211 SimpleTest.expectUncaughtException(expecting); 212 } 213 214 function browserErrorHandler(event) { 215 browserFinishTest(); 216 throw new Error("indexedDB error (" + event.code + "): " + event.message); 217 } 218 219 function unexpectedSuccessHandler() { 220 ok(false, "Got success, but did not expect it!"); 221 finishTest(); 222 } 223 224 function expectedErrorHandler(name) { 225 return function (event) { 226 is(event.type, "error", "Got an error event"); 227 is(event.target.error.name, name, "Expected error was thrown."); 228 event.preventDefault(); 229 grabEventAndContinueHandler(event); 230 }; 231 } 232 233 function ExpectError(name, preventDefault) { 234 this._name = name; 235 this._preventDefault = preventDefault; 236 } 237 ExpectError.prototype = { 238 handleEvent(event) { 239 is(event.type, "error", "Got an error event"); 240 is(event.target.error.name, this._name, "Expected error was thrown."); 241 if (this._preventDefault) { 242 event.preventDefault(); 243 event.stopPropagation(); 244 } 245 grabEventAndContinueHandler(event); 246 }, 247 }; 248 249 function compareKeys(_k1_, _k2_) { 250 let t = typeof _k1_; 251 if (t != typeof _k2_) { 252 return false; 253 } 254 255 if (t !== "object") { 256 return _k1_ === _k2_; 257 } 258 259 if (_k1_ instanceof Date) { 260 return _k2_ instanceof Date && _k1_.getTime() === _k2_.getTime(); 261 } 262 263 if (_k1_ instanceof Array) { 264 if (!(_k2_ instanceof Array) || _k1_.length != _k2_.length) { 265 return false; 266 } 267 268 for (let i = 0; i < _k1_.length; ++i) { 269 if (!compareKeys(_k1_[i], _k2_[i])) { 270 return false; 271 } 272 } 273 274 return true; 275 } 276 277 if (_k1_ instanceof ArrayBuffer) { 278 if (!(_k2_ instanceof ArrayBuffer)) { 279 return false; 280 } 281 282 function arrayBuffersAreEqual(a, b) { 283 if (a.byteLength != b.byteLength) { 284 return false; 285 } 286 let ui8b = new Uint8Array(b); 287 return new Uint8Array(a).every((val, i) => val === ui8b[i]); 288 } 289 290 return arrayBuffersAreEqual(_k1_, _k2_); 291 } 292 293 return false; 294 } 295 296 function removePermission(type, url) { 297 if (!url) { 298 url = window.document; 299 } 300 SpecialPowers.removePermission(type, url); 301 } 302 303 function gc() { 304 SpecialPowers.forceGC(); 305 SpecialPowers.forceCC(); 306 } 307 308 function scheduleGC() { 309 SpecialPowers.exactGC(continueToNextStep); 310 } 311 312 // Assert that eventually a condition becomes true, running a garbage 313 // collection between evaluations. Fails, after a high number of iterations 314 // without a successful evaluation of the condition. 315 function* assertEventuallyWithGC(conditionFunctor, message) { 316 const maxGC = 100; 317 for (let i = 0; i < maxGC; ++i) { 318 let result = 319 conditionFunctor.constructor.name === "GeneratorFunction" 320 ? yield* conditionFunctor() 321 : conditionFunctor(); 322 if (result) { 323 ok(true, message + " (after " + i + " garbage collections)"); 324 return; 325 } 326 SpecialPowers.exactGC(continueToNextStep); 327 yield undefined; 328 } 329 ok(false, message + " (even after " + maxGC + " garbage collections)"); 330 } 331 332 // Asserts that a functor `f` throws an exception that is an instance of 333 // `ctor`. If it doesn't throw, or throws a different type of exception, this 334 // throws an Error, including the optional `msg` given. 335 // Otherwise, it returns the message of the exception. 336 // 337 // TODO This is DUPLICATED from https://searchfox.org/mozilla-central/rev/cfd1cc461f1efe0d66c2fdc17c024a203d5a2fd8/js/src/tests/shell.js#163 338 // This should be moved to a more generic place, as it is in no way specific 339 // to IndexedDB. 340 function assertThrowsInstanceOf(f, ctor, msg) { 341 var fullmsg; 342 try { 343 f(); 344 } catch (exc) { 345 if (exc instanceof ctor) { 346 return exc.message; 347 } 348 fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`; 349 } 350 351 if (fullmsg === undefined) { 352 fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`; 353 } 354 if (msg !== undefined) { 355 fullmsg += " - " + msg; 356 } 357 358 throw new Error(fullmsg); 359 } 360 361 function isWasmSupported() { 362 let testingFunctions = SpecialPowers.Cu.getJSTestingFunctions(); 363 return testingFunctions.wasmIsSupported(); 364 } 365 366 function getWasmModule(_binary_) { 367 let module = new WebAssembly.Module(_binary_); 368 return module; 369 } 370 371 function expectingSuccess(request) { 372 return new Promise(function (resolve, reject) { 373 request.onerror = function (event) { 374 ok(false, "indexedDB error, '" + event.target.error.name + "'"); 375 reject(event); 376 }; 377 request.onsuccess = function (event) { 378 resolve(event); 379 }; 380 request.onupgradeneeded = function (event) { 381 ok(false, "Got upgrade, but did not expect it!"); 382 reject(event); 383 }; 384 }); 385 } 386 387 function expectingUpgrade(request) { 388 return new Promise(function (resolve, reject) { 389 request.onerror = function (event) { 390 ok(false, "indexedDB error, '" + event.target.error.name + "'"); 391 reject(event); 392 }; 393 request.onupgradeneeded = function (event) { 394 resolve(event); 395 }; 396 request.onsuccess = function (event) { 397 ok(false, "Got success, but did not expect it!"); 398 reject(event); 399 }; 400 }); 401 } 402 403 function expectingError(request, errorName) { 404 return new Promise(function (resolve, reject) { 405 request.onerror = function (event) { 406 is(errorName, event.target.error.name, "Correct exception type"); 407 event.stopPropagation(); 408 resolve(event); 409 }; 410 request.onsuccess = function (event) { 411 ok(false, "Got success, but did not expect it!"); 412 reject(event); 413 }; 414 request.onupgradeneeded = function (event) { 415 ok(false, "Got upgrade, but did not expect it!"); 416 reject(event); 417 }; 418 }); 419 } 420 421 function requestSucceeded(request, optionalSyncSuccessCallback) { 422 return new Promise(function (resolve, reject) { 423 request.onerror = function (event) { 424 ok(false, "indexedDB error, '" + event.target.error.name + "'"); 425 reject(event); 426 }; 427 request.onsuccess = function (event) { 428 if (optionalSyncSuccessCallback) { 429 optionalSyncSuccessCallback(); 430 } 431 resolve(event); 432 }; 433 }); 434 } 435 436 function workerScript() { 437 "use strict"; 438 439 self.wasmSupported = false; 440 441 self.repr = function (_thing_) { 442 if (typeof _thing_ == "undefined") { 443 return "undefined"; 444 } 445 446 let str; 447 448 try { 449 str = _thing_ + ""; 450 } catch (e) { 451 return "[" + typeof _thing_ + "]"; 452 } 453 454 if (typeof _thing_ == "function") { 455 str = str.replace(/^\s+/, ""); 456 let idx = str.indexOf("{"); 457 if (idx != -1) { 458 str = str.substr(0, idx) + "{...}"; 459 } 460 } 461 462 return str; 463 }; 464 465 self.ok = function (_condition_, _name_, _diag_) { 466 self.postMessage({ 467 op: "ok", 468 condition: !!_condition_, 469 name: _name_, 470 diag: _diag_, 471 }); 472 }; 473 474 self.is = function (_a_, _b_, _name_) { 475 let pass = _a_ == _b_; 476 let diag = pass ? "" : "got " + repr(_a_) + ", expected " + repr(_b_); 477 ok(pass, _name_, diag); 478 }; 479 480 self.isnot = function (_a_, _b_, _name_) { 481 let pass = _a_ != _b_; 482 let diag = pass ? "" : "didn't expect " + repr(_a_) + ", but got it"; 483 ok(pass, _name_, diag); 484 }; 485 486 self.todo = function (_condition_, _name_, _diag_) { 487 self.postMessage({ 488 op: "todo", 489 condition: !!_condition_, 490 name: _name_, 491 diag: _diag_, 492 }); 493 }; 494 495 self.info = function (_msg_) { 496 self.postMessage({ op: "info", msg: _msg_ }); 497 }; 498 499 self.executeSoon = function (_fun_) { 500 var channel = new MessageChannel(); 501 channel.port1.postMessage(""); 502 channel.port2.onmessage = function () { 503 _fun_(); 504 }; 505 }; 506 507 self.finishTest = function () { 508 self.ok( 509 testSteps.constructor.name === "GeneratorFunction", 510 "Async/await tests shouldn't call finishTest()" 511 ); 512 if (self._expectingUncaughtException) { 513 self.ok( 514 false, 515 "expectUncaughtException was called but no uncaught " + 516 "exception was detected!" 517 ); 518 } 519 self.postMessage({ op: "done" }); 520 }; 521 522 self.grabEventAndContinueHandler = function (_event_) { 523 testGenerator.next(_event_); 524 }; 525 526 self.continueToNextStep = function () { 527 executeSoon(function () { 528 testGenerator.next(); 529 }); 530 }; 531 532 self.continueToNextStepSync = function () { 533 testGenerator.next(); 534 }; 535 536 self.errorHandler = function (_event_) { 537 ok(false, "indexedDB error, '" + _event_.target.error.name + "'"); 538 finishTest(); 539 }; 540 541 self.unexpectedSuccessHandler = function () { 542 ok(false, "Got success, but did not expect it!"); 543 finishTest(); 544 }; 545 546 self.expectedErrorHandler = function (_name_) { 547 return function (_event_) { 548 is(_event_.type, "error", "Got an error event"); 549 is(_event_.target.error.name, _name_, "Expected error was thrown."); 550 _event_.preventDefault(); 551 grabEventAndContinueHandler(_event_); 552 }; 553 }; 554 555 self.ExpectError = function (_name_, _preventDefault_) { 556 this._name = _name_; 557 this._preventDefault = _preventDefault_; 558 }; 559 self.ExpectError.prototype = { 560 handleEvent(_event_) { 561 is(_event_.type, "error", "Got an error event"); 562 is(_event_.target.error.name, this._name, "Expected error was thrown."); 563 if (this._preventDefault) { 564 _event_.preventDefault(); 565 _event_.stopPropagation(); 566 } 567 grabEventAndContinueHandler(_event_); 568 }, 569 }; 570 571 // TODO this is duplicate from the global compareKeys function defined above, 572 // this duplication should be avoided (bug 1565986) 573 self.compareKeys = function (_k1_, _k2_) { 574 let t = typeof _k1_; 575 if (t != typeof _k2_) { 576 return false; 577 } 578 579 if (t !== "object") { 580 return _k1_ === _k2_; 581 } 582 583 if (_k1_ instanceof Date) { 584 return _k2_ instanceof Date && _k1_.getTime() === _k2_.getTime(); 585 } 586 587 if (_k1_ instanceof Array) { 588 if (!(_k2_ instanceof Array) || _k1_.length != _k2_.length) { 589 return false; 590 } 591 592 for (let i = 0; i < _k1_.length; ++i) { 593 if (!compareKeys(_k1_[i], _k2_[i])) { 594 return false; 595 } 596 } 597 598 return true; 599 } 600 601 if (_k1_ instanceof ArrayBuffer) { 602 if (!(_k2_ instanceof ArrayBuffer)) { 603 return false; 604 } 605 606 function arrayBuffersAreEqual(a, b) { 607 if (a.byteLength != b.byteLength) { 608 return false; 609 } 610 let ui8b = new Uint8Array(b); 611 return new Uint8Array(a).every((val, i) => val === ui8b[i]); 612 } 613 614 return arrayBuffersAreEqual(_k1_, _k2_); 615 } 616 617 return false; 618 }; 619 620 self.getRandomBuffer = function (_size_) { 621 let buffer = new ArrayBuffer(_size_); 622 is(buffer.byteLength, _size_, "Correct byte length"); 623 let view = new Uint8Array(buffer); 624 for (let i = 0; i < _size_; i++) { 625 view[i] = parseInt(Math.random() * 255); 626 } 627 return buffer; 628 }; 629 630 self._expectingUncaughtException = false; 631 self.expectUncaughtException = function (_expecting_) { 632 self._expectingUncaughtException = !!_expecting_; 633 self.postMessage({ 634 op: "expectUncaughtException", 635 expecting: !!_expecting_, 636 }); 637 }; 638 639 self._clearAllDatabasesCallback = undefined; 640 self.clearAllDatabases = function (_callback_) { 641 self._clearAllDatabasesCallback = _callback_; 642 self.postMessage({ op: "clearAllDatabases" }); 643 }; 644 645 self.onerror = function (_message_, _file_, _line_) { 646 if (self._expectingUncaughtException) { 647 self._expectingUncaughtException = false; 648 ok( 649 true, 650 "Worker: expected exception [" + 651 _file_ + 652 ":" + 653 _line_ + 654 "]: '" + 655 _message_ + 656 "'" 657 ); 658 return false; 659 } 660 ok( 661 false, 662 "Worker: uncaught exception [" + 663 _file_ + 664 ":" + 665 _line_ + 666 "]: '" + 667 _message_ + 668 "'" 669 ); 670 self.finishTest(); 671 self.close(); 672 return true; 673 }; 674 675 self.isWasmSupported = function () { 676 return self.wasmSupported; 677 }; 678 679 self.getWasmModule = function (_binary_) { 680 let module = new WebAssembly.Module(_binary_); 681 return module; 682 }; 683 684 self.verifyWasmModule = function (_module) { 685 self.todo(false, "Need a verifyWasmModule implementation on workers"); 686 self.continueToNextStep(); 687 }; 688 689 self.onmessage = function (_event_) { 690 let message = _event_.data; 691 switch (message.op) { 692 case "load": 693 info("Worker: loading " + JSON.stringify(message.files)); 694 self.importScripts(message.files); 695 self.postMessage({ op: "loaded" }); 696 break; 697 698 case "start": 699 self.wasmSupported = message.wasmSupported; 700 executeSoon(async function () { 701 info("Worker: starting tests"); 702 if (testSteps.constructor.name === "AsyncFunction") { 703 await testSteps(); 704 if (self._expectingUncaughtException) { 705 self.ok( 706 false, 707 "expectUncaughtException was called but no " + 708 "uncaught exception was detected!" 709 ); 710 } 711 self.postMessage({ op: "done" }); 712 } else { 713 ok( 714 testSteps.constructor.name === "GeneratorFunction", 715 "Unsupported function type" 716 ); 717 testGenerator.next(); 718 } 719 }); 720 break; 721 722 case "clearAllDatabasesDone": 723 info("Worker: all databases are cleared"); 724 if (self._clearAllDatabasesCallback) { 725 self._clearAllDatabasesCallback(); 726 } 727 break; 728 729 default: 730 throw new Error( 731 "Received a bad message from parent: " + JSON.stringify(message) 732 ); 733 } 734 }; 735 736 self.expectingSuccess = function (_request_) { 737 return new Promise(function (_resolve_, _reject_) { 738 _request_.onerror = function (_event_) { 739 ok(false, "indexedDB error, '" + _event_.target.error.name + "'"); 740 _reject_(_event_); 741 }; 742 _request_.onsuccess = function (_event_) { 743 _resolve_(_event_); 744 }; 745 _request_.onupgradeneeded = function (_event_) { 746 ok(false, "Got upgrade, but did not expect it!"); 747 _reject_(_event_); 748 }; 749 }); 750 }; 751 752 self.expectingUpgrade = function (_request_) { 753 return new Promise(function (_resolve_, _reject_) { 754 _request_.onerror = function (_event_) { 755 ok(false, "indexedDB error, '" + _event_.target.error.name + "'"); 756 _reject_(_event_); 757 }; 758 _request_.onupgradeneeded = function (_event_) { 759 _resolve_(_event_); 760 }; 761 _request_.onsuccess = function (_event_) { 762 ok(false, "Got success, but did not expect it!"); 763 _reject_(_event_); 764 }; 765 }); 766 }; 767 768 self.postMessage({ op: "ready" }); 769 } 770 771 async function executeWorkerTestAndCleanUp(testScriptPath) { 772 info("Running test in a worker"); 773 774 let workerScriptBlob = new Blob(["(" + workerScript.toString() + ")();"], { 775 type: "text/javascript", 776 }); 777 let workerScriptURL = URL.createObjectURL(workerScriptBlob); 778 779 let worker; 780 try { 781 await new Promise(function (resolve, reject) { 782 worker = new Worker(workerScriptURL); 783 784 worker._expectingUncaughtException = false; 785 worker.onerror = function (event) { 786 if (worker._expectingUncaughtException) { 787 ok(true, "Worker had an expected error: " + event.message); 788 worker._expectingUncaughtException = false; 789 event.preventDefault(); 790 return; 791 } 792 ok(false, "Worker had an error: " + event.message); 793 worker.terminate(); 794 reject(); 795 }; 796 797 worker.onmessage = function (event) { 798 let message = event.data; 799 switch (message.op) { 800 case "ok": 801 SimpleTest.ok( 802 message.condition, 803 `${message.name}: ${message.diag}` 804 ); 805 break; 806 807 case "todo": 808 todo(message.condition, message.name, message.diag); 809 break; 810 811 case "info": 812 info(message.msg); 813 break; 814 815 case "ready": 816 worker.postMessage({ op: "load", files: [testScriptPath] }); 817 break; 818 819 case "loaded": 820 worker.postMessage({ 821 op: "start", 822 wasmSupported: isWasmSupported(), 823 }); 824 break; 825 826 case "done": 827 ok(true, "Worker finished"); 828 resolve(); 829 break; 830 831 case "expectUncaughtException": 832 worker._expectingUncaughtException = message.expecting; 833 break; 834 835 case "clearAllDatabases": 836 clearAllDatabases(function () { 837 worker.postMessage({ op: "clearAllDatabasesDone" }); 838 }); 839 break; 840 841 default: 842 ok( 843 false, 844 "Received a bad message from worker: " + JSON.stringify(message) 845 ); 846 reject(); 847 } 848 }; 849 }); 850 851 URL.revokeObjectURL(workerScriptURL); 852 } catch (e) { 853 info("Unexpected thing happened: " + e); 854 } 855 856 return new Promise(function (resolve) { 857 info("Cleaning up the databases"); 858 859 if (worker._expectingUncaughtException) { 860 ok( 861 false, 862 "expectUncaughtException was called but no uncaught " + 863 "exception was detected!" 864 ); 865 } 866 867 worker.terminate(); 868 worker = null; 869 870 clearAllDatabases(resolve); 871 }); 872 }