mirror of
https://github.com/actions/checkout.git
synced 2026-05-14 04:59:54 +08:00
Switch the shared retry helper from randomized delays to exponential backoff so transient GitHub 500 errors are retried predictably. Add coverage for the backoff sequence and regenerate the bundled dist output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
138 lines
4.1 KiB
TypeScript
138 lines
4.1 KiB
TypeScript
import * as core from '@actions/core'
|
|
import {RetryHelper} from '../lib/retry-helper'
|
|
|
|
let info: string[]
|
|
|
|
describe('retry-helper tests', () => {
|
|
beforeAll(() => {
|
|
// Mock @actions/core info()
|
|
jest.spyOn(core, 'info').mockImplementation((message: string) => {
|
|
info.push(message)
|
|
})
|
|
})
|
|
|
|
beforeEach(() => {
|
|
// Reset info
|
|
info = []
|
|
})
|
|
|
|
afterAll(() => {
|
|
// Restore
|
|
jest.restoreAllMocks()
|
|
})
|
|
|
|
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) {
|
|
throw new Error('some error')
|
|
}
|
|
|
|
return Promise.resolve('some result')
|
|
})
|
|
expect(attempts).toBe(2)
|
|
expect(actual).toBe('some result')
|
|
expect(info).toHaveLength(2)
|
|
expect(info[0]).toBe('some error')
|
|
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) {
|
|
throw new Error(`some error ${attempts}`)
|
|
}
|
|
|
|
return Promise.resolve('some result')
|
|
})
|
|
expect(attempts).toBe(3)
|
|
expect(actual).toBe('some result')
|
|
expect(info).toHaveLength(4)
|
|
expect(info[0]).toBe('some error 1')
|
|
expect(info[1]).toBe('Waiting 1 seconds before trying again')
|
|
expect(info[2]).toBe('some error 2')
|
|
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 {
|
|
await retryHelper.execute(() => {
|
|
throw new Error(`some error ${++attempts}`)
|
|
})
|
|
} catch (err) {
|
|
error = err as Error
|
|
}
|
|
expect(error.message).toBe('some error 3')
|
|
expect(attempts).toBe(3)
|
|
expect(info).toHaveLength(4)
|
|
expect(info[0]).toBe('some error 1')
|
|
expect(info[1]).toBe('Waiting 1 seconds before trying again')
|
|
expect(info[2]).toBe('some error 2')
|
|
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)
|
|
})
|
|
})
|