Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 58 additions & 8 deletions __test__/retry-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ import * as core from '@actions/core'
import {RetryHelper} from '../lib/retry-helper'

let info: string[]
let retryHelper: any

describe('retry-helper tests', () => {
beforeAll(() => {
// Mock @actions/core info()
jest.spyOn(core, 'info').mockImplementation((message: string) => {
info.push(message)
})

retryHelper = new RetryHelper(3, 0, 0)
})

beforeEach(() => {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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 changes: 6 additions & 4 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand All @@ -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* () {
Expand Down
13 changes: 7 additions & 6 deletions src/retry-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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++
Expand All @@ -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> {
Expand Down