(label, run, options = {})
| 765 | } |
| 766 | |
| 767 | async function measuredStep(label, run, options = {}) { |
| 768 | const started = process.hrtime.bigint(); |
| 769 | let timeoutId = null; |
| 770 | if (verbose) { |
| 771 | console.log(`> ${label}`); |
| 772 | } |
| 773 | try { |
| 774 | const timeoutMs = options.timeoutMs ?? defaultStepTimeoutMs; |
| 775 | const result = await Promise.race([ |
| 776 | Promise.resolve().then(run), |
| 777 | new Promise((_, reject) => { |
| 778 | timeoutId = setTimeout( |
| 779 | () => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), |
| 780 | timeoutMs, |
| 781 | ); |
| 782 | }), |
| 783 | ]); |
| 784 | const elapsedMs = Number(process.hrtime.bigint() - started) / 1_000_000; |
| 785 | stepTimings.push({ label, elapsedMs }); |
| 786 | if (verbose) { |
| 787 | console.log(`ok ${label} ${elapsedMs.toFixed(1)}ms`); |
| 788 | } |
| 789 | if (options.timeoutMs && elapsedMs > options.timeoutMs) { |
| 790 | throw new Error( |
| 791 | `${label} exceeded ${options.timeoutMs}ms budget (${elapsedMs.toFixed( |
| 792 | 1, |
| 793 | )}ms)`, |
| 794 | ); |
| 795 | } |
| 796 | return result; |
| 797 | } catch (error) { |
| 798 | const elapsedMs = Number(process.hrtime.bigint() - started) / 1_000_000; |
| 799 | console.error(`fail ${label} ${elapsedMs.toFixed(1)}ms`); |
| 800 | throw error; |
| 801 | } finally { |
| 802 | if (timeoutId) { |
| 803 | clearTimeout(timeoutId); |
| 804 | } |
| 805 | } |
| 806 | } |
| 807 | |
| 808 | async function retryAsync(run, label, options = {}) { |
| 809 | const attempts = options.attempts ?? 3; |
no test coverage detected