This commit is contained in:
Bob Vincent 2026-05-11 12:00:38 -04:00 committed by GitHub
commit 664a8261c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 18 deletions

View File

@ -2,7 +2,6 @@ import * as core from '@actions/core'
import {RetryHelper} from '../lib/retry-helper'
let info: string[]
let retryHelper: any
describe('retry-helper tests', () => {
beforeAll(() => {
@ -10,8 +9,6 @@ describe('retry-helper tests', () => {
jest.spyOn(core, 'info').mockImplementation((message: string) => {
info.push(message)
})
retryHelper = new RetryHelper(3, 0, 0)
})
beforeEach(() => {
@ -25,14 +22,22 @@ describe('retry-helper tests', () => {
})
it('first attempt succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
const actual = await retryHelper.execute(async () => {
return 'some result'
})
expect(actual).toBe('some result')
expect(info).toHaveLength(0)
expect(sleep).not.toHaveBeenCalled()
})
it('second attempt succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts == 1) {
@ -45,10 +50,15 @@ describe('retry-helper tests', () => {
expect(actual).toBe('some result')
expect(info).toHaveLength(2)
expect(info[0]).toBe('some error')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[1]).toBe('Waiting 1 seconds before trying again')
expect(sleep).toHaveBeenCalledTimes(1)
expect(sleep).toHaveBeenCalledWith(1)
})
it('third attempt succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts < 3) {
@ -61,12 +71,18 @@ describe('retry-helper tests', () => {
expect(actual).toBe('some result')
expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[1]).toBe('Waiting 1 seconds before trying again')
expect(info[2]).toBe('some error 2')
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[3]).toBe('Waiting 2 seconds before trying again')
expect(sleep).toHaveBeenCalledTimes(2)
expect(sleep).toHaveBeenNthCalledWith(1, 1)
expect(sleep).toHaveBeenNthCalledWith(2, 2)
})
it('all attempts fail succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
let error: Error = null as unknown as Error
try {
@ -80,8 +96,42 @@ describe('retry-helper tests', () => {
expect(attempts).toBe(3)
expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[1]).toBe('Waiting 1 seconds before trying again')
expect(info[2]).toBe('some error 2')
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[3]).toBe('Waiting 2 seconds before trying again')
expect(sleep).toHaveBeenCalledTimes(2)
expect(sleep).toHaveBeenNthCalledWith(1, 1)
expect(sleep).toHaveBeenNthCalledWith(2, 2)
})
it('server-side 500 errors are retried with exponential backoff', async () => {
const retryHelper: any = new RetryHelper(4, 2, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts < 3) {
const error: Error & {status?: number} = new Error(
`server error ${attempts}`
)
error.status = 500
throw error
}
return Promise.resolve('some result')
})
expect(actual).toBe('some result')
expect(attempts).toBe(3)
expect(info).toEqual([
'server error 1',
'Waiting 2 seconds before trying again',
'server error 2',
'Waiting 4 seconds before trying again'
])
expect(sleep).toHaveBeenCalledTimes(2)
expect(sleep).toHaveBeenNthCalledWith(1, 2)
expect(sleep).toHaveBeenNthCalledWith(2, 4)
})
})

10
dist/index.js vendored
View File

@ -2567,7 +2567,7 @@ class RetryHelper {
core.info(err === null || err === void 0 ? void 0 : err.message);
}
// Sleep
const seconds = this.getSleepAmount();
const seconds = this.getSleepAmount(attempt);
core.info(`Waiting ${seconds} seconds before trying again`);
yield this.sleep(seconds);
attempt++;
@ -2576,9 +2576,11 @@ class RetryHelper {
return yield action();
});
}
getSleepAmount() {
return (Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) +
this.minSeconds);
getSleepAmount(attempt) {
if (this.minSeconds === 0) {
return 0;
}
return Math.min(this.minSeconds * Math.pow(2, attempt - 1), this.maxSeconds);
}
sleep(seconds) {
return __awaiter(this, void 0, void 0, function* () {

View File

@ -33,7 +33,7 @@ export class RetryHelper {
}
// Sleep
const seconds = this.getSleepAmount()
const seconds = this.getSleepAmount(attempt)
core.info(`Waiting ${seconds} seconds before trying again`)
await this.sleep(seconds)
attempt++
@ -43,11 +43,12 @@ export class RetryHelper {
return await action()
}
private getSleepAmount(): number {
return (
Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) +
this.minSeconds
)
private getSleepAmount(attempt: number): number {
if (this.minSeconds === 0) {
return 0
}
return Math.min(this.minSeconds * Math.pow(2, attempt - 1), this.maxSeconds)
}
private async sleep(seconds: number): Promise<void> {