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' import {RetryHelper} from '../lib/retry-helper'
let info: string[] let info: string[]
let retryHelper: any
describe('retry-helper tests', () => { describe('retry-helper tests', () => {
beforeAll(() => { beforeAll(() => {
@ -10,8 +9,6 @@ describe('retry-helper tests', () => {
jest.spyOn(core, 'info').mockImplementation((message: string) => { jest.spyOn(core, 'info').mockImplementation((message: string) => {
info.push(message) info.push(message)
}) })
retryHelper = new RetryHelper(3, 0, 0)
}) })
beforeEach(() => { beforeEach(() => {
@ -25,14 +22,22 @@ describe('retry-helper tests', () => {
}) })
it('first attempt succeeds', async () => { 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 () => { const actual = await retryHelper.execute(async () => {
return 'some result' return 'some result'
}) })
expect(actual).toBe('some result') expect(actual).toBe('some result')
expect(info).toHaveLength(0) expect(info).toHaveLength(0)
expect(sleep).not.toHaveBeenCalled()
}) })
it('second attempt succeeds', async () => { 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 let attempts = 0
const actual = await retryHelper.execute(() => { const actual = await retryHelper.execute(() => {
if (++attempts == 1) { if (++attempts == 1) {
@ -45,10 +50,15 @@ describe('retry-helper tests', () => {
expect(actual).toBe('some result') expect(actual).toBe('some result')
expect(info).toHaveLength(2) expect(info).toHaveLength(2)
expect(info[0]).toBe('some error') 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 () => { 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 let attempts = 0
const actual = await retryHelper.execute(() => { const actual = await retryHelper.execute(() => {
if (++attempts < 3) { if (++attempts < 3) {
@ -61,12 +71,18 @@ describe('retry-helper tests', () => {
expect(actual).toBe('some result') expect(actual).toBe('some result')
expect(info).toHaveLength(4) expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1') 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[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 () => { 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 attempts = 0
let error: Error = null as unknown as Error let error: Error = null as unknown as Error
try { try {
@ -80,8 +96,42 @@ describe('retry-helper tests', () => {
expect(attempts).toBe(3) expect(attempts).toBe(3)
expect(info).toHaveLength(4) expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1') 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[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); core.info(err === null || err === void 0 ? void 0 : err.message);
} }
// Sleep // Sleep
const seconds = this.getSleepAmount(); const seconds = this.getSleepAmount(attempt);
core.info(`Waiting ${seconds} seconds before trying again`); core.info(`Waiting ${seconds} seconds before trying again`);
yield this.sleep(seconds); yield this.sleep(seconds);
attempt++; attempt++;
@ -2576,9 +2576,11 @@ class RetryHelper {
return yield action(); return yield action();
}); });
} }
getSleepAmount() { getSleepAmount(attempt) {
return (Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) + if (this.minSeconds === 0) {
this.minSeconds); return 0;
}
return Math.min(this.minSeconds * Math.pow(2, attempt - 1), this.maxSeconds);
} }
sleep(seconds) { sleep(seconds) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {

View File

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