Skip to content

Instantly share code, notes, and snippets.

@abachman
Last active July 21, 2022 16:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abachman/0c341522237122291c91f4377a6ceb8e to your computer and use it in GitHub Desktop.
Save abachman/0c341522237122291c91f4377a6ceb8e to your computer and use it in GitHub Desktop.
attempt at an all-in-one jest performance expectation
declare global {
namespace jest {
interface Matchers<R> {
toBeFasterThan(target: number): Promise<CustomMatcherResult>;
}
}
}
import { matcherHint, printDiffOrStringify } from 'jest-matcher-utils';
// This is an adaptation of https://github.com/nickheal/test-performance and
// https://github.com/nickheal/jest-test-performance for use with our test
// suite.
/**
* This runs a single performance test of a function
*/
async function perfTest(func: Function): Promise<number> {
const start = performance.now();
await func();
const end = performance.now();
return end - start;
}
async function getScore(
func: Function,
numberOfTests: number,
): Promise<number> {
const tests = new Array(numberOfTests)
.fill(undefined)
.map(async () => perfTest(func));
const results = await Promise.all(tests);
return results.reduce((acc, val) => acc + val, 0) / numberOfTests;
}
// can change based on system performance
const EXPECTED_BASELINE_RUNTIME = 12;
function baselineFunc(dummyParam?: number) {
function test() {}
const a = dummyParam || Math.random();
if (a > 0.5) {
test();
} else {
test();
}
}
async function runTests(
baselineFunc: Function,
targetFunc: Function,
numberOfTests: number,
): Promise<[number, number]> {
/**
* Tests deliberately run in sequence to catch performance changes over time
*/
const initialBaselineScore = await getScore(baselineFunc, numberOfTests);
const initialTargetScore = await getScore(targetFunc, numberOfTests);
const midwayBaselineScore = await getScore(baselineFunc, numberOfTests);
const endTargetScore = await getScore(targetFunc, numberOfTests);
const endBaselineScore = await getScore(baselineFunc, numberOfTests);
const totalBaselineScore =
(initialBaselineScore + midwayBaselineScore + endBaselineScore) / 3;
const totalTargetScore = (initialTargetScore + endTargetScore) / 2;
return [totalBaselineScore, totalTargetScore];
}
/**
* This calculates the expected performance of the function, based on comparison
* to a baseline.
*/
function calculateExpectedPerformance(
baselineExpected: number,
baselineActual: number,
target: number,
): number {
const performanceRatio = baselineActual / baselineExpected;
return target / performanceRatio;
}
async function getPerformanceScore(
func: Function,
numberOfTests: number,
): Promise<number> {
if (typeof func !== 'function') throw new Error(`${func} is not a function`);
const [baseline, target] = await runTests(baselineFunc, func, numberOfTests);
return calculateExpectedPerformance(
EXPECTED_BASELINE_RUNTIME,
baseline,
target,
);
}
async function predicate(func: Function, time: number, numberOfTests: number) {
const result = await getPerformanceScore(func, numberOfTests);
return [result < time, result];
}
const passMessage =
(func: Function, target: number, result: number | boolean) => () =>
`
${matcherHint('.not.toBeFasterThan', 'received', '')}
Function '${func}' ran faster than expected:
${printDiffOrStringify(target, result, 'Expected', 'Received', true)}
`;
const failMessage =
(func: Function, target: number, result: number | boolean) => () =>
`
${matcherHint('.toBeFasterThan', 'received', '')}
Function '${func}' ran slower than expected:
${printDiffOrStringify(target, result, 'Expected', 'Received', true)}
`;
const matcher = {
toBeFasterThan: async (
func: Function,
target: number,
times: number = 25,
) => {
const [pass, result] = await predicate(func, target, times);
if (pass) {
return { pass: true, message: passMessage(func, target, result) };
}
return { pass: false, message: failMessage(func, target, result) };
},
};
expect.extend(matcher);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment