|
| 1 | +import process from 'node:process'; |
| 2 | +import fs from 'node:fs/promises'; |
| 3 | +import path from 'node:path'; |
| 4 | +import {execa} from 'execa'; |
1 | 5 | import test from 'ava'; |
2 | 6 | import delay from 'delay'; |
3 | 7 | import pRetry, {AbortError} from './index.js'; |
@@ -626,3 +630,70 @@ test.serial('unref option prevents timeout from keeping process alive', async t |
626 | 630 |
|
627 | 631 | t.true(timeoutUnrefCalled, 'timeout.unref() should be called when unref option is true'); |
628 | 632 | }); |
| 633 | + |
| 634 | +test('preserves user stack trace through async retries', async t => { |
| 635 | + const script = ` |
| 636 | +import pRetry from './index.js'; |
| 637 | +
|
| 638 | +async function foo1() { |
| 639 | + return await foo2(); |
| 640 | +} |
| 641 | +
|
| 642 | +async function foo2() { |
| 643 | + return await pRetry( |
| 644 | + async () => { |
| 645 | + throw new Error('foo2 failed'); |
| 646 | + }, |
| 647 | + { |
| 648 | + retries: 1, |
| 649 | + } |
| 650 | + ); |
| 651 | +} |
| 652 | +
|
| 653 | +async function main() { |
| 654 | + try { |
| 655 | + await foo1(); |
| 656 | + } catch (error) { |
| 657 | + console.error('STACKTRACE_START'); |
| 658 | + console.error(error.stack); |
| 659 | + console.error('STACKTRACE_END'); |
| 660 | + } |
| 661 | +} |
| 662 | +
|
| 663 | +main(); |
| 664 | +`.trim(); |
| 665 | + |
| 666 | + const temporaryFile = path.join(process.cwd(), 'p-retry-stack-test.js'); |
| 667 | + await fs.writeFile(temporaryFile, script); |
| 668 | + |
| 669 | + try { |
| 670 | + const {stderr, stdout} = await execa('node', [temporaryFile], {reject: false}); |
| 671 | + const output = stderr + stdout; |
| 672 | + const stack = output.split('STACKTRACE_START')[1]?.split('STACKTRACE_END')[0]?.trim(); |
| 673 | + |
| 674 | + t.truthy(stack, 'Should capture stack trace output'); |
| 675 | + |
| 676 | + t.regex(stack, /Error: foo2 failed/); |
| 677 | + |
| 678 | + // Print the stack for debugging if needed |
| 679 | + if (!/foo2/.test(stack) || !/foo1/.test(stack) || !/main/.test(stack)) { |
| 680 | + console.log('\n==== Full stack trace for debugging ====\n' + stack + '\n==== End stack trace ====\n'); |
| 681 | + } |
| 682 | + |
| 683 | + t.regex(stack, /foo2/); |
| 684 | + t.regex(stack, /foo1/); |
| 685 | + t.regex(stack, /main/); |
| 686 | + |
| 687 | + // Check order |
| 688 | + const lines = stack.split('\n'); |
| 689 | + const foo2Index = lines.findIndex(line => /foo2/.test(line)); |
| 690 | + const foo1Index = lines.findIndex(line => /foo1/.test(line)); |
| 691 | + const mainIndex = lines.findIndex(line => /main/.test(line)); |
| 692 | + |
| 693 | + t.true(foo2Index !== -1, 'foo2 should appear in the stack trace'); |
| 694 | + t.true(foo1Index > foo2Index, 'foo1 should appear after foo2'); |
| 695 | + t.true(mainIndex > foo1Index, 'main should appear after foo1'); |
| 696 | + } finally { |
| 697 | + await fs.unlink(temporaryFile); |
| 698 | + } |
| 699 | +}); |
0 commit comments