Skip to content

Commit

Permalink
Merge 99554d3 into 143cac1
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Sep 20, 2019
2 parents 143cac1 + 99554d3 commit bf77324
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 96 deletions.
21 changes: 15 additions & 6 deletions src/check/property/TimeoutProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import { Shrinkable } from '../arbitrary/definition/Shrinkable';
import { IProperty } from './IProperty';

/** @hidden */
const timeoutAfter = async (timeMs: number) =>
new Promise<string>((resolve, reject) =>
setTimeout(() => {
const timeoutAfter = (timeMs: number) => {
let timeoutHandle: (ReturnType<typeof setTimeout>) | null = null;
const promise = new Promise<string>((resolve, reject) => {
timeoutHandle = setTimeout(() => {
resolve(`Property timeout: exceeded limit of ${timeMs} milliseconds`);
}, timeMs)
);
}, timeMs);
});
return {
clear: () => clearTimeout(timeoutHandle!),
promise
};
};

/** @hidden */
export class TimeoutProperty<Ts> implements IProperty<Ts> {
Expand All @@ -18,6 +24,9 @@ export class TimeoutProperty<Ts> implements IProperty<Ts> {
return this.property.generate(mrng, runId);
}
async run(v: Ts) {
return Promise.race([this.property.run(v), timeoutAfter(this.timeMs)]);
const t = timeoutAfter(this.timeMs);
const propRun = Promise.race([this.property.run(v), t.promise]);
propRun.then(t.clear, t.clear); // always clear timeout handle - catch should never occur
return propRun;
}
}
90 changes: 0 additions & 90 deletions test/unit/check/property/TimeoutProperty.spec.ts

This file was deleted.

171 changes: 171 additions & 0 deletions test/unit/check/property/TimeoutProperty.utest.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Shrinkable } from '../../../../src/check/arbitrary/definition/Shrinkable';
import { IProperty } from '../../../../src/check/property/IProperty';
import { TimeoutProperty } from '../../../../src/check/property/TimeoutProperty';

import * as stubRng from '../../stubs/generators';
const mrng = () => stubRng.mutable.nocall();

jest.useFakeTimers();

describe('TimeoutProperty', () => {
beforeEach(() => {
jest.clearAllTimers();
jest.clearAllMocks();
});

it('Should forward calls to generate', () => {
// Arrange
const expectedMrng = mrng();
const expectedRunId = 42;
const expectedOut = new Shrinkable({});
const p: IProperty<number> = {
isAsync: () => true,
generate: jest.fn().mockReturnValueOnce(expectedOut),
run: jest.fn()
};

// Act
const timeoutProp = new TimeoutProperty(p, 100);
const out = timeoutProp.generate(expectedMrng, expectedRunId);

// Assert
expect(out).toBe(expectedOut);
expect(p.generate).toBeCalledTimes(1);
expect(p.generate).toBeCalledWith(expectedMrng, expectedRunId);
});

it('Should forward inputs to run', async () => {
// Arrange
const runInput = {};
const p: IProperty<typeof runInput> = {
isAsync: () => true,
generate: jest.fn(),
run: jest.fn()
};

// Act
const timeoutProp = new TimeoutProperty(p, 10);
const runPromise = timeoutProp.run({});
jest.advanceTimersByTime(10);
await runPromise;

// Assert
expect(p.run).toHaveBeenCalledTimes(1);
expect(p.run).toHaveBeenCalledWith(runInput);
});

it('Should not timeout if it succeeds in time', async () => {
// Arrange
const p: IProperty<{}> = {
isAsync: () => true,
generate: jest.fn(),
run: jest.fn().mockReturnValueOnce(
new Promise(function(resolve, reject) {
setTimeout(() => resolve(null), 10);
})
)
};

// Act
const timeoutProp = new TimeoutProperty(p, 100);
const runPromise = timeoutProp.run({});
jest.advanceTimersByTime(10);

// Assert
expect(await runPromise).toBe(null);
});

it('Should not timeout if it fails in time', async () => {
// Arrange
const p: IProperty<{}> = {
isAsync: () => true,
generate: jest.fn(),
run: jest.fn().mockReturnValueOnce(
new Promise(function(resolve, reject) {
// underlying property is not supposed to throw (reject)
setTimeout(() => resolve('plop'), 10);
})
)
};

// Act
const timeoutProp = new TimeoutProperty(p, 100);
const runPromise = timeoutProp.run({});
jest.advanceTimersByTime(10);

// Assert
expect(await runPromise).toEqual('plop');
});

it('Should clear all started timeouts on success', async () => {
// Arrange
const p: IProperty<{}> = {
isAsync: () => true,
generate: jest.fn(),
run: jest.fn().mockResolvedValueOnce(null)
};

// Act
const timeoutProp = new TimeoutProperty(p, 100);
await timeoutProp.run({});

// Assert
expect(setTimeout).toBeCalledTimes(1);
expect(clearTimeout).toBeCalledTimes(1);
});

it('Should clear all started timeouts on failure', async () => {
// Arrange
const p: IProperty<{}> = {
isAsync: () => true,
generate: jest.fn(),
run: jest.fn().mockResolvedValueOnce('plop')
};

// Act
const timeoutProp = new TimeoutProperty(p, 100);
await timeoutProp.run({});

// Assert
expect(setTimeout).toBeCalledTimes(1);
expect(clearTimeout).toBeCalledTimes(1);
});

it('Should timeout if it takes to long', async () => {
// Arrange
const p: IProperty<{}> = {
isAsync: () => true,
generate: jest.fn(),
run: jest.fn().mockReturnValueOnce(
new Promise(function(resolve, reject) {
setTimeout(() => resolve(null), 100);
})
)
};

// Act
const timeoutProp = new TimeoutProperty(p, 10);
const runPromise = timeoutProp.run({});
jest.advanceTimersByTime(10);

// Assert
expect(await runPromise).toEqual(`Property timeout: exceeded limit of 10 milliseconds`);
});

it('Should timeout if it never ends', async () => {
// Arrange
const p: IProperty<{}> = {
isAsync: () => true,
generate: jest.fn(),
run: jest.fn().mockReturnValueOnce(new Promise(function(resolve, reject) {}))
};

// Act
const timeoutProp = new TimeoutProperty(p, 10);
const runPromise = timeoutProp.run({});
jest.advanceTimersByTime(10);

// Assert
expect(await runPromise).toEqual(`Property timeout: exceeded limit of 10 milliseconds`);
});
});

0 comments on commit bf77324

Please sign in to comment.