1'use strict'; 2const { 3 ArrayPrototypePush, 4 ArrayPrototypePushApply, 5 ArrayPrototypeReduce, 6 ArrayPrototypeShift, 7 ArrayPrototypeSlice, 8 ArrayPrototypeSome, 9 ArrayPrototypeUnshift, 10 FunctionPrototype, 11 MathMax, 12 Number, 13 ObjectSeal, 14 PromisePrototypeThen, 15 PromiseResolve, 16 SafePromisePrototypeFinally, 17 ReflectApply, 18 RegExpPrototypeExec, 19 SafeMap, 20 SafeSet, 21 SafePromiseAll, 22 SafePromiseRace, 23 SymbolDispose, 24 ObjectDefineProperty, 25 Symbol, 26} = primordials; 27const { getCallerLocation } = internalBinding('util'); 28const { addAbortListener } = require('events'); 29const { AsyncResource } = require('async_hooks'); 30const { AbortController } = require('internal/abort_controller'); 31const { 32 codes: { 33 ERR_INVALID_ARG_TYPE, 34 ERR_TEST_FAILURE, 35 }, 36 AbortError, 37} = require('internal/errors'); 38const { MockTracker } = require('internal/test_runner/mock/mock'); 39const { TestsStream } = require('internal/test_runner/tests_stream'); 40const { 41 createDeferredCallback, 42 countCompletedTest, 43 isTestFailureError, 44 parseCommandLine, 45} = require('internal/test_runner/utils'); 46const { 47 createDeferredPromise, 48 kEmptyObject, 49 once: runOnce, 50} = require('internal/util'); 51const { isPromise } = require('internal/util/types'); 52const { 53 validateAbortSignal, 54 validateNumber, 55 validateOneOf, 56 validateUint32, 57} = require('internal/validators'); 58const { setTimeout } = require('timers'); 59const { TIMEOUT_MAX } = require('internal/timers'); 60const { availableParallelism } = require('os'); 61const { bigint: hrtime } = process.hrtime; 62const kCallbackAndPromisePresent = 'callbackAndPromisePresent'; 63const kCancelledByParent = 'cancelledByParent'; 64const kAborted = 'testAborted'; 65const kParentAlreadyFinished = 'parentAlreadyFinished'; 66const kSubtestsFailed = 'subtestsFailed'; 67const kTestCodeFailure = 'testCodeFailure'; 68const kTestTimeoutFailure = 'testTimeoutFailure'; 69const kHookFailure = 'hookFailed'; 70const kDefaultTimeout = null; 71const noop = FunctionPrototype; 72const kShouldAbort = Symbol('kShouldAbort'); 73const kFilename = process.argv?.[1]; 74const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']); 75const kUnwrapErrors = new SafeSet() 76 .add(kTestCodeFailure).add(kHookFailure) 77 .add('uncaughtException').add('unhandledRejection'); 78const { testNamePatterns, testOnlyFlag } = parseCommandLine(); 79let kResistStopPropagation; 80 81function stopTest(timeout, signal) { 82 const deferred = createDeferredPromise(); 83 const abortListener = addAbortListener(signal, deferred.resolve); 84 let timer; 85 let disposeFunction; 86 87 if (timeout === kDefaultTimeout) { 88 disposeFunction = abortListener[SymbolDispose]; 89 } else { 90 timer = setTimeout(() => deferred.resolve(), timeout); 91 timer.unref(); 92 93 ObjectDefineProperty(deferred, 'promise', { 94 __proto__: null, 95 configurable: true, 96 writable: true, 97 value: PromisePrototypeThen(deferred.promise, () => { 98 throw new ERR_TEST_FAILURE( 99 `test timed out after ${timeout}ms`, 100 kTestTimeoutFailure, 101 ); 102 }), 103 }); 104 105 disposeFunction = () => { 106 abortListener[SymbolDispose](); 107 timer[SymbolDispose](); 108 }; 109 } 110 111 ObjectDefineProperty(deferred.promise, SymbolDispose, { 112 __proto__: null, 113 configurable: true, 114 writable: true, 115 value: disposeFunction, 116 }); 117 return deferred.promise; 118} 119 120class TestContext { 121 #test; 122 123 constructor(test) { 124 this.#test = test; 125 } 126 127 get signal() { 128 return this.#test.signal; 129 } 130 131 get name() { 132 return this.#test.name; 133 } 134 135 diagnostic(message) { 136 this.#test.diagnostic(message); 137 } 138 139 get mock() { 140 this.#test.mock ??= new MockTracker(); 141 return this.#test.mock; 142 } 143 144 runOnly(value) { 145 this.#test.runOnlySubtests = !!value; 146 } 147 148 skip(message) { 149 this.#test.skip(message); 150 } 151 152 todo(message) { 153 this.#test.todo(message); 154 } 155 156 test(name, options, fn) { 157 const overrides = { 158 __proto__: null, 159 loc: getCallerLocation(), 160 }; 161 162 const subtest = this.#test.createSubtest( 163 // eslint-disable-next-line no-use-before-define 164 Test, name, options, fn, overrides, 165 ); 166 167 return subtest.start(); 168 } 169 170 before(fn, options) { 171 this.#test.createHook('before', fn, options); 172 } 173 174 after(fn, options) { 175 this.#test.createHook('after', fn, options); 176 } 177 178 beforeEach(fn, options) { 179 this.#test.createHook('beforeEach', fn, options); 180 } 181 182 afterEach(fn, options) { 183 this.#test.createHook('afterEach', fn, options); 184 } 185} 186 187class SuiteContext { 188 #suite; 189 190 constructor(suite) { 191 this.#suite = suite; 192 } 193 194 get signal() { 195 return this.#suite.signal; 196 } 197 198 get name() { 199 return this.#suite.name; 200 } 201} 202 203class Test extends AsyncResource { 204 abortController; 205 outerSignal; 206 #reportedSubtest; 207 208 constructor(options) { 209 super('Test'); 210 211 let { fn, name, parent, skip } = options; 212 const { concurrency, loc, only, timeout, todo, signal } = options; 213 214 if (typeof fn !== 'function') { 215 fn = noop; 216 } 217 218 if (typeof name !== 'string' || name === '') { 219 name = fn.name || '<anonymous>'; 220 } 221 222 if (!(parent instanceof Test)) { 223 parent = null; 224 } 225 226 if (parent === null) { 227 this.concurrency = 1; 228 this.nesting = 0; 229 this.only = testOnlyFlag; 230 this.reporter = new TestsStream(); 231 this.runOnlySubtests = this.only; 232 this.testNumber = 0; 233 this.timeout = kDefaultTimeout; 234 this.root = this; 235 this.hooks = { 236 __proto__: null, 237 before: [], 238 after: [], 239 beforeEach: [], 240 afterEach: [], 241 }; 242 } else { 243 const nesting = parent.parent === null ? parent.nesting : 244 parent.nesting + 1; 245 246 this.concurrency = parent.concurrency; 247 this.nesting = nesting; 248 this.only = only ?? !parent.runOnlySubtests; 249 this.reporter = parent.reporter; 250 this.runOnlySubtests = !this.only; 251 this.testNumber = parent.subtests.length + 1; 252 this.timeout = parent.timeout; 253 this.root = parent.root; 254 this.hooks = { 255 __proto__: null, 256 before: [], 257 after: [], 258 beforeEach: ArrayPrototypeSlice(parent.hooks.beforeEach), 259 afterEach: ArrayPrototypeSlice(parent.hooks.afterEach), 260 }; 261 } 262 263 switch (typeof concurrency) { 264 case 'number': 265 validateUint32(concurrency, 'options.concurrency', 1); 266 this.concurrency = concurrency; 267 break; 268 269 case 'boolean': 270 if (concurrency) { 271 this.concurrency = parent === null ? 272 MathMax(availableParallelism() - 1, 1) : Infinity; 273 } else { 274 this.concurrency = 1; 275 } 276 break; 277 278 default: 279 if (concurrency != null) 280 throw new ERR_INVALID_ARG_TYPE('options.concurrency', ['boolean', 'number'], concurrency); 281 } 282 283 if (timeout != null && timeout !== Infinity) { 284 validateNumber(timeout, 'options.timeout', 0, TIMEOUT_MAX); 285 this.timeout = timeout; 286 } 287 288 this.name = name; 289 this.parent = parent; 290 291 if (testNamePatterns !== null && !this.matchesTestNamePatterns()) { 292 skip = 'test name does not match pattern'; 293 } 294 295 if (testOnlyFlag && !this.only) { 296 skip = '\'only\' option not set'; 297 } 298 299 if (skip) { 300 fn = noop; 301 } 302 303 this.abortController = new AbortController(); 304 this.outerSignal = signal; 305 this.signal = this.abortController.signal; 306 307 validateAbortSignal(signal, 'options.signal'); 308 if (signal) { 309 kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation; 310 } 311 312 this.outerSignal?.addEventListener( 313 'abort', 314 this.#abortHandler, 315 { __proto__: null, [kResistStopPropagation]: true }, 316 ); 317 318 this.fn = fn; 319 this.harness = null; // Configured on the root test by the test harness. 320 this.mock = null; 321 this.cancelled = false; 322 this.skipped = skip !== undefined && skip !== false; 323 this.isTodo = todo !== undefined && todo !== false; 324 this.startTime = null; 325 this.endTime = null; 326 this.passed = false; 327 this.error = null; 328 this.diagnostics = []; 329 this.message = typeof skip === 'string' ? skip : 330 typeof todo === 'string' ? todo : null; 331 this.activeSubtests = 0; 332 this.pendingSubtests = []; 333 this.readySubtests = new SafeMap(); 334 this.subtests = []; 335 this.waitingOn = 0; 336 this.finished = false; 337 338 if (!testOnlyFlag && (only || this.runOnlySubtests)) { 339 const warning = 340 "'only' and 'runOnly' require the --test-only command-line option."; 341 this.diagnostic(warning); 342 } 343 344 if (loc === undefined || kFilename === undefined) { 345 this.loc = undefined; 346 } else { 347 this.loc = { 348 __proto__: null, 349 line: loc[0], 350 column: loc[1], 351 file: loc[2], 352 }; 353 } 354 } 355 356 matchesTestNamePatterns() { 357 return ArrayPrototypeSome(testNamePatterns, (re) => RegExpPrototypeExec(re, this.name) !== null) || 358 this.parent?.matchesTestNamePatterns(); 359 } 360 361 hasConcurrency() { 362 return this.concurrency > this.activeSubtests; 363 } 364 365 addPendingSubtest(deferred) { 366 ArrayPrototypePush(this.pendingSubtests, deferred); 367 } 368 369 async processPendingSubtests() { 370 while (this.pendingSubtests.length > 0 && this.hasConcurrency()) { 371 const deferred = ArrayPrototypeShift(this.pendingSubtests); 372 const test = deferred.test; 373 this.reporter.dequeue(test.nesting, test.loc, test.name); 374 await test.run(); 375 deferred.resolve(); 376 } 377 } 378 379 addReadySubtest(subtest) { 380 this.readySubtests.set(subtest.testNumber, subtest); 381 } 382 383 processReadySubtestRange(canSend) { 384 const start = this.waitingOn; 385 const end = start + this.readySubtests.size; 386 387 for (let i = start; i < end; i++) { 388 const subtest = this.readySubtests.get(i); 389 390 // Check if the specified subtest is in the map. If it is not, return 391 // early to avoid trying to process any more tests since they would be 392 // out of order. 393 if (subtest === undefined) { 394 return; 395 } 396 397 // Call isClearToSend() in the loop so that it is: 398 // - Only called if there are results to report in the correct order. 399 // - Guaranteed to only be called a maximum of once per call to 400 // processReadySubtestRange(). 401 canSend = canSend || this.isClearToSend(); 402 403 if (!canSend) { 404 return; 405 } 406 407 if (i === 1 && this.parent !== null) { 408 this.reportStarted(); 409 } 410 411 // Report the subtest's results and remove it from the ready map. 412 subtest.finalize(); 413 this.readySubtests.delete(i); 414 } 415 } 416 417 createSubtest(Factory, name, options, fn, overrides) { 418 if (typeof name === 'function') { 419 fn = name; 420 } else if (name !== null && typeof name === 'object') { 421 fn = options; 422 options = name; 423 } else if (typeof options === 'function') { 424 fn = options; 425 } 426 427 if (options === null || typeof options !== 'object') { 428 options = kEmptyObject; 429 } 430 431 let parent = this; 432 433 // If this test has already ended, attach this test to the root test so 434 // that the error can be properly reported. 435 const preventAddingSubtests = this.finished || this.buildPhaseFinished; 436 if (preventAddingSubtests) { 437 while (parent.parent !== null) { 438 parent = parent.parent; 439 } 440 } 441 442 const test = new Factory({ __proto__: null, fn, name, parent, ...options, ...overrides }); 443 444 if (parent.waitingOn === 0) { 445 parent.waitingOn = test.testNumber; 446 } 447 448 if (preventAddingSubtests) { 449 test.startTime = test.startTime || hrtime(); 450 test.fail( 451 new ERR_TEST_FAILURE( 452 'test could not be started because its parent finished', 453 kParentAlreadyFinished, 454 ), 455 ); 456 } 457 458 ArrayPrototypePush(parent.subtests, test); 459 return test; 460 } 461 462 #abortHandler = () => { 463 const error = this.outerSignal?.reason || new AbortError('The test was aborted'); 464 error.failureType = kAborted; 465 this.#cancel(error); 466 }; 467 468 #cancel(error) { 469 if (this.endTime !== null) { 470 return; 471 } 472 473 this.fail(error || 474 new ERR_TEST_FAILURE( 475 'test did not finish before its parent and was cancelled', 476 kCancelledByParent, 477 ), 478 ); 479 this.startTime = this.startTime || this.endTime; // If a test was canceled before it was started, e.g inside a hook 480 this.cancelled = true; 481 this.abortController.abort(); 482 } 483 484 createHook(name, fn, options) { 485 validateOneOf(name, 'hook name', kHookNames); 486 // eslint-disable-next-line no-use-before-define 487 const hook = new TestHook(fn, options); 488 if (name === 'before' || name === 'after') { 489 hook.run = runOnce(hook.run); 490 } 491 ArrayPrototypePush(this.hooks[name], hook); 492 return hook; 493 } 494 495 fail(err) { 496 if (this.error !== null) { 497 return; 498 } 499 500 this.endTime = hrtime(); 501 this.passed = false; 502 this.error = err; 503 } 504 505 pass() { 506 if (this.endTime !== null) { 507 return; 508 } 509 510 this.endTime = hrtime(); 511 this.passed = true; 512 } 513 514 skip(message) { 515 this.skipped = true; 516 this.message = message; 517 } 518 519 todo(message) { 520 this.isTodo = true; 521 this.message = message; 522 } 523 524 diagnostic(message) { 525 ArrayPrototypePush(this.diagnostics, message); 526 } 527 528 start() { 529 // If there is enough available concurrency to run the test now, then do 530 // it. Otherwise, return a Promise to the caller and mark the test as 531 // pending for later execution. 532 this.reporter.enqueue(this.nesting, this.loc, this.name); 533 if (!this.parent.hasConcurrency()) { 534 const deferred = createDeferredPromise(); 535 536 deferred.test = this; 537 this.parent.addPendingSubtest(deferred); 538 return deferred.promise; 539 } 540 541 this.reporter.dequeue(this.nesting, this.loc, this.name); 542 return this.run(); 543 } 544 545 [kShouldAbort]() { 546 if (this.signal.aborted) { 547 return true; 548 } 549 if (this.outerSignal?.aborted) { 550 this.#abortHandler(); 551 return true; 552 } 553 } 554 555 getRunArgs() { 556 const ctx = new TestContext(this); 557 return { __proto__: null, ctx, args: [ctx] }; 558 } 559 560 async runHook(hook, args) { 561 validateOneOf(hook, 'hook name', kHookNames); 562 try { 563 await ArrayPrototypeReduce(this.hooks[hook], async (prev, hook) => { 564 await prev; 565 await hook.run(args); 566 if (hook.error) { 567 throw hook.error; 568 } 569 }, PromiseResolve()); 570 } catch (err) { 571 const error = new ERR_TEST_FAILURE(`failed running ${hook} hook`, kHookFailure); 572 error.cause = isTestFailureError(err) ? err.cause : err; 573 throw error; 574 } 575 } 576 577 async run() { 578 if (this.parent !== null) { 579 this.parent.activeSubtests++; 580 } 581 this.startTime = hrtime(); 582 583 if (this[kShouldAbort]()) { 584 this.postRun(); 585 return; 586 } 587 588 const { args, ctx } = this.getRunArgs(); 589 const after = async () => { 590 if (this.hooks.after.length > 0) { 591 await this.runHook('after', { __proto__: null, args, ctx }); 592 } 593 }; 594 const afterEach = runOnce(async () => { 595 if (this.parent?.hooks.afterEach.length > 0) { 596 await this.parent.runHook('afterEach', { __proto__: null, args, ctx }); 597 } 598 }); 599 600 let stopPromise; 601 602 try { 603 if (this.parent?.hooks.before.length > 0) { 604 await this.parent.runHook('before', this.parent.getRunArgs()); 605 } 606 if (this.parent?.hooks.beforeEach.length > 0) { 607 await this.parent.runHook('beforeEach', { __proto__: null, args, ctx }); 608 } 609 stopPromise = stopTest(this.timeout, this.signal); 610 const runArgs = ArrayPrototypeSlice(args); 611 ArrayPrototypeUnshift(runArgs, this.fn, ctx); 612 613 if (this.fn.length === runArgs.length - 1) { 614 // This test is using legacy Node.js error first callbacks. 615 const { promise, cb } = createDeferredCallback(); 616 617 ArrayPrototypePush(runArgs, cb); 618 const ret = ReflectApply(this.runInAsyncScope, this, runArgs); 619 620 if (isPromise(ret)) { 621 this.fail(new ERR_TEST_FAILURE( 622 'passed a callback but also returned a Promise', 623 kCallbackAndPromisePresent, 624 )); 625 await SafePromiseRace([ret, stopPromise]); 626 } else { 627 await SafePromiseRace([PromiseResolve(promise), stopPromise]); 628 } 629 } else { 630 // This test is synchronous or using Promises. 631 const promise = ReflectApply(this.runInAsyncScope, this, runArgs); 632 await SafePromiseRace([PromiseResolve(promise), stopPromise]); 633 } 634 635 if (this[kShouldAbort]()) { 636 this.postRun(); 637 return; 638 } 639 640 await afterEach(); 641 await after(); 642 this.pass(); 643 } catch (err) { 644 try { await afterEach(); } catch { /* test is already failing, let's ignore the error */ } 645 try { await after(); } catch { /* Ignore error. */ } 646 if (isTestFailureError(err)) { 647 if (err.failureType === kTestTimeoutFailure) { 648 this.#cancel(err); 649 } else { 650 this.fail(err); 651 } 652 } else { 653 this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); 654 } 655 } finally { 656 stopPromise?.[SymbolDispose](); 657 658 // Do not abort hooks and the root test as hooks instance are shared between tests suite so aborting them will 659 // cause them to not run for further tests. 660 if (this.parent !== null) { 661 this.abortController.abort(); 662 } 663 } 664 665 if (this.parent !== null || typeof this.hookType === 'string') { 666 // Clean up the test. Then, try to report the results and execute any 667 // tests that were pending due to available concurrency. 668 // 669 // The root test is skipped here because it is a special case. Its 670 // postRun() method is called when the process is getting ready to exit. 671 // This helps catch any asynchronous activity that occurs after the tests 672 // have finished executing. 673 this.postRun(); 674 } 675 } 676 677 postRun(pendingSubtestsError) { 678 // If the test was failed before it even started, then the end time will 679 // be earlier than the start time. Correct that here. 680 if (this.endTime < this.startTime) { 681 this.endTime = hrtime(); 682 } 683 this.startTime ??= this.endTime; 684 685 // The test has run, so recursively cancel any outstanding subtests and 686 // mark this test as failed if any subtests failed. 687 this.pendingSubtests = []; 688 let failed = 0; 689 for (let i = 0; i < this.subtests.length; i++) { 690 const subtest = this.subtests[i]; 691 692 if (!subtest.finished) { 693 subtest.#cancel(pendingSubtestsError); 694 subtest.postRun(pendingSubtestsError); 695 } 696 if (!subtest.passed && !subtest.isTodo) { 697 failed++; 698 } 699 } 700 701 if ((this.passed || this.parent === null) && failed > 0) { 702 const subtestString = `subtest${failed > 1 ? 's' : ''}`; 703 const msg = `${failed} ${subtestString} failed`; 704 705 this.fail(new ERR_TEST_FAILURE(msg, kSubtestsFailed)); 706 } 707 708 this.outerSignal?.removeEventListener('abort', this.#abortHandler); 709 this.mock?.reset(); 710 711 if (this.parent !== null) { 712 this.parent.activeSubtests--; 713 this.parent.addReadySubtest(this); 714 this.parent.processReadySubtestRange(false); 715 this.parent.processPendingSubtests(); 716 717 if (this.parent === this.root && 718 this.root.activeSubtests === 0 && 719 this.root.pendingSubtests.length === 0 && 720 this.root.readySubtests.size === 0 && 721 this.root.hooks.after.length > 0) { 722 // This is done so that any global after() hooks are run. At this point 723 // all of the tests have finished running. However, there might be 724 // ref'ed handles keeping the event loop alive. This gives the global 725 // after() hook a chance to clean them up. 726 this.root.run(); 727 } 728 } else if (!this.reported) { 729 const { 730 diagnostics, 731 harness, 732 loc, 733 nesting, 734 reporter, 735 } = this; 736 737 this.reported = true; 738 reporter.plan(nesting, loc, harness.counters.topLevel); 739 740 // Call this harness.coverage() before collecting diagnostics, since failure to collect coverage is a diagnostic. 741 const coverage = harness.coverage(); 742 for (let i = 0; i < diagnostics.length; i++) { 743 reporter.diagnostic(nesting, loc, diagnostics[i]); 744 } 745 746 reporter.diagnostic(nesting, loc, `tests ${harness.counters.all}`); 747 reporter.diagnostic(nesting, loc, `suites ${harness.counters.suites}`); 748 reporter.diagnostic(nesting, loc, `pass ${harness.counters.passed}`); 749 reporter.diagnostic(nesting, loc, `fail ${harness.counters.failed}`); 750 reporter.diagnostic(nesting, loc, `cancelled ${harness.counters.cancelled}`); 751 reporter.diagnostic(nesting, loc, `skipped ${harness.counters.skipped}`); 752 reporter.diagnostic(nesting, loc, `todo ${harness.counters.todo}`); 753 reporter.diagnostic(nesting, loc, `duration_ms ${this.duration()}`); 754 755 if (coverage) { 756 reporter.coverage(nesting, loc, coverage); 757 } 758 759 reporter.end(); 760 } 761 } 762 763 isClearToSend() { 764 return this.parent === null || 765 ( 766 this.parent.waitingOn === this.testNumber && this.parent.isClearToSend() 767 ); 768 } 769 770 finalize() { 771 // By the time this function is called, the following can be relied on: 772 // - The current test has completed or been cancelled. 773 // - All of this test's subtests have completed or been cancelled. 774 // - It is the current test's turn to report its results. 775 776 // Report any subtests that have not been reported yet. Since all of the 777 // subtests have finished, it's safe to pass true to 778 // processReadySubtestRange(), which will finalize all remaining subtests. 779 this.processReadySubtestRange(true); 780 781 // Output this test's results and update the parent's waiting counter. 782 this.report(); 783 this.parent.waitingOn++; 784 this.finished = true; 785 } 786 787 duration() { 788 // Duration is recorded in BigInt nanoseconds. Convert to milliseconds. 789 return Number(this.endTime - this.startTime) / 1_000_000; 790 } 791 792 report() { 793 countCompletedTest(this); 794 if (this.subtests.length > 0) { 795 this.reporter.plan(this.subtests[0].nesting, this.loc, this.subtests.length); 796 } else { 797 this.reportStarted(); 798 } 799 let directive; 800 const details = { __proto__: null, duration_ms: this.duration() }; 801 802 if (this.skipped) { 803 directive = this.reporter.getSkip(this.message); 804 } else if (this.isTodo) { 805 directive = this.reporter.getTodo(this.message); 806 } 807 808 if (this.reportedType) { 809 details.type = this.reportedType; 810 } 811 812 if (this.passed) { 813 this.reporter.ok(this.nesting, this.loc, this.testNumber, this.name, details, directive); 814 } else { 815 details.error = this.error; 816 this.reporter.fail(this.nesting, this.loc, this.testNumber, this.name, details, directive); 817 } 818 819 for (let i = 0; i < this.diagnostics.length; i++) { 820 this.reporter.diagnostic(this.nesting, this.loc, this.diagnostics[i]); 821 } 822 } 823 824 reportStarted() { 825 if (this.#reportedSubtest || this.parent === null) { 826 return; 827 } 828 this.#reportedSubtest = true; 829 this.parent.reportStarted(); 830 this.reporter.start(this.nesting, this.loc, this.name); 831 } 832} 833 834class TestHook extends Test { 835 #args; 836 constructor(fn, options) { 837 if (options === null || typeof options !== 'object') { 838 options = kEmptyObject; 839 } 840 const { loc, timeout, signal } = options; 841 super({ __proto__: null, fn, loc, timeout, signal }); 842 843 this.parentTest = options.parent ?? null; 844 this.hookType = options.hookType; 845 } 846 run(args) { 847 if (this.error && !this.outerSignal?.aborted) { 848 this.passed = false; 849 this.error = null; 850 this.abortController.abort(); 851 this.abortController = new AbortController(); 852 this.signal = this.abortController.signal; 853 } 854 855 this.#args = args; 856 return super.run(); 857 } 858 getRunArgs() { 859 return this.#args; 860 } 861 matchesTestNamePatterns() { 862 return true; 863 } 864 postRun() { 865 const { error, loc, parentTest: parent } = this; 866 867 // Report failures in the root test's after() hook. 868 if (error && parent !== null && 869 parent === parent.root && this.hookType === 'after') { 870 871 if (isTestFailureError(error)) { 872 error.failureType = kHookFailure; 873 } 874 875 parent.reporter.fail(0, loc, parent.subtests.length + 1, loc.file, { 876 __proto__: null, 877 duration_ms: this.duration(), 878 error, 879 }, undefined); 880 } 881 } 882} 883 884class Suite extends Test { 885 reportedType = 'suite'; 886 constructor(options) { 887 super(options); 888 889 if (testNamePatterns !== null && !options.skip && !options.todo) { 890 this.fn = options.fn || this.fn; 891 this.skipped = false; 892 } 893 this.runOnlySubtests = testOnlyFlag; 894 895 try { 896 const { ctx, args } = this.getRunArgs(); 897 const runArgs = [this.fn, ctx]; 898 ArrayPrototypePushApply(runArgs, args); 899 this.buildSuite = SafePromisePrototypeFinally( 900 PromisePrototypeThen( 901 PromiseResolve(ReflectApply(this.runInAsyncScope, this, runArgs)), 902 undefined, 903 (err) => { 904 this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); 905 }), 906 () => { 907 this.buildPhaseFinished = true; 908 }, 909 ); 910 } catch (err) { 911 this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); 912 913 this.buildPhaseFinished = true; 914 } 915 this.fn = () => {}; 916 } 917 918 getRunArgs() { 919 const ctx = new SuiteContext(this); 920 return { __proto__: null, ctx, args: [ctx] }; 921 } 922 923 async run() { 924 const hookArgs = this.getRunArgs(); 925 926 let stopPromise; 927 try { 928 this.parent.activeSubtests++; 929 await this.buildSuite; 930 this.startTime = hrtime(); 931 932 if (this[kShouldAbort]()) { 933 this.subtests = []; 934 this.postRun(); 935 return; 936 } 937 938 if (this.parent.hooks.before.length > 0) { 939 await this.parent.runHook('before', this.parent.getRunArgs()); 940 } 941 942 await this.runHook('before', hookArgs); 943 944 stopPromise = stopTest(this.timeout, this.signal); 945 const subtests = this.skipped || this.error ? [] : this.subtests; 946 const promise = SafePromiseAll(subtests, (subtests) => subtests.start()); 947 948 await SafePromiseRace([promise, stopPromise]); 949 await this.runHook('after', hookArgs); 950 951 this.pass(); 952 } catch (err) { 953 if (isTestFailureError(err)) { 954 this.fail(err); 955 } else { 956 this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)); 957 } 958 } finally { 959 stopPromise?.[SymbolDispose](); 960 } 961 962 this.postRun(); 963 } 964} 965 966module.exports = { 967 kCancelledByParent, 968 kSubtestsFailed, 969 kTestCodeFailure, 970 kTestTimeoutFailure, 971 kAborted, 972 kUnwrapErrors, 973 Suite, 974 Test, 975}; 976