Skip to content

feat: add support for setup/cleanup functions during each test run #563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
6 changes: 6 additions & 0 deletions .changeset/silent-cups-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'reassure': minor
'@callstack/reassure-measure': minor
---

feat: add support for setup/cleanup functions during each test run
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ interface MeasureRendersOptions {
wrapper?: React.ComponentType<{ children: ReactElement }>;
scenario?: (view?: RenderResult) => Promise<any>;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

Expand All @@ -383,6 +385,8 @@ interface MeasureRendersOptions {
- **`wrapper`**: React component, such as a `Provider`, which the `ui` will be wrapped with. Note: the render duration of the `wrapper` itself is excluded from the results; only the wrapped component is measured.
- **`scenario`**: a custom async function, which defines user interaction within the UI by utilising RNTL or RTL functions
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

#### `measureFunction` function

Expand All @@ -403,13 +407,17 @@ interface MeasureFunctionOptions {
warmupRuns?: number;
removeOutliers?: boolean;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

- **`runs`**: number of runs per series for the particular test
- **`warmupRuns`**: number of additional warmup runs that will be done and discarded before the actual runs
- **`removeOutliers`**: should remove statistical outlier results (default: `true`)
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

#### `measureAsyncFunction` function

Expand All @@ -432,13 +440,17 @@ interface MeasureAsyncFunctionOptions {
warmupRuns?: number;
removeOutliers?: boolean;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

- **`runs`**: number of runs per series for the particular test
- **`warmupRuns`**: number of additional warmup runs that will be done and discarded before the actual runs
- **`removeOutliers`**: should remove statistical outlier results (default: `true`)
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

### Configuration

Expand Down
12 changes: 12 additions & 0 deletions docusaurus/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ interface MeasureRendersOptions {
wrapper?: React.ComponentType<{ children: ReactElement }>;
scenario?: (view?: RenderResult) => Promise<any>;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

Expand All @@ -63,6 +65,8 @@ interface MeasureRendersOptions {
- **`wrapper`**: React component, such as a `Provider`, which the `ui` will be wrapped with. Note: the render duration of the `wrapper` itself is excluded from the results, only the wrapped component is measured.
- **`scenario`**: a custom async function, which defines user interaction within the ui by utilized RNTL functions
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

### `measureFunction` function {#measure-function}

Expand Down Expand Up @@ -95,13 +99,17 @@ interface MeasureFunctionOptions {
warmupRuns?: number;
removeOutliers?: boolean;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

- **`runs`**: number of runs per series for the particular test
- **`warmupRuns`**: number of additional warmup runs that will be done and discarded before the actual runs
- **`removeOutliers`**: should remove statistical outlier results (default: `true`)
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

### `measureAsyncFunction` function {#measure-async-function}

Expand Down Expand Up @@ -142,13 +150,17 @@ interface MeasureAsyncFunctionOptions {
warmupRuns?: number;
removeOutliers?: boolean;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

- **`runs`**: number of runs per series for the particular test
- **`warmupRuns`**: number of additional warmup runs that will be done and discarded before the actual runs
- **`removeOutliers`**: should remove statistical outlier results (default: `true`)
- **`writeFile`**: (default `true`) should write output to file
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

## Configuration

Expand Down
20 changes: 20 additions & 0 deletions packages/measure/src/__tests__/measure-async-function.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ test('measureAsyncFunction applies "warmupRuns" option', async () => {
expect(results.stdevCount).toBe(0);
});

test('measureAsyncFunction executes setup and cleanup functions for each run', async () => {
const fn = jest.fn(() => Promise.resolve().then(() => fib(5)));
const beforeFn = jest.fn();
const afterFn = jest.fn();
const results = await measureAsyncFunction(fn, {
runs: 10,
warmupRuns: 1,
writeFile: false,
beforeEach: beforeFn,
afterEach: afterFn,
});

expect(beforeFn).toHaveBeenCalledTimes(11);
expect(fn).toHaveBeenCalledTimes(11);
expect(afterFn).toHaveBeenCalledTimes(11);
expect(results.runs).toBe(10);
expect(results.durations.length + (results.outlierDurations?.length ?? 0)).toBe(10);
expect(results.counts).toHaveLength(10);
});

const errorsToIgnore = ['❌ Measure code is running under incorrect Node.js configuration.'];
const realConsole = jest.requireActual('console') as Console;

Expand Down
20 changes: 20 additions & 0 deletions packages/measure/src/__tests__/measure-function.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ test('measureFunction applies "warmupRuns" option', async () => {
expect(results.stdevCount).toBe(0);
});

test('measureFunction executes setup and cleanup functions for each run', async () => {
const fn = jest.fn(() => fib(5));
const beforeFn = jest.fn();
const afterFn = jest.fn();
const results = await measureFunction(fn, {
runs: 10,
warmupRuns: 1,
writeFile: false,
beforeEach: beforeFn,
afterEach: afterFn,
});

expect(beforeFn).toHaveBeenCalledTimes(11);
expect(fn).toHaveBeenCalledTimes(11);
expect(afterFn).toHaveBeenCalledTimes(11);
expect(results.runs).toBe(10);
expect(results.durations.length + (results.outlierDurations?.length ?? 0)).toBe(10);
expect(results.counts).toHaveLength(10);
});

const errorsToIgnore = ['❌ Measure code is running under incorrect Node.js configuration.'];
const realConsole = jest.requireActual('console') as Console;

Expand Down
21 changes: 21 additions & 0 deletions packages/measure/src/__tests__/measure-renders.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ test('measureRenders applies "warmupRuns" option', async () => {
expect(results.stdevCount).toBe(0);
});

test('measureRenders executes setup and cleanup functions for each run', async () => {
const scenario = jest.fn(() => Promise.resolve(null));
const beforeFn = jest.fn();
const afterFn = jest.fn();
const results = await measureRenders(<View />, {
runs: 10,
warmupRuns: 1,
scenario,
writeFile: false,
beforeEach: beforeFn,
afterEach: afterFn,
});

expect(beforeFn).toHaveBeenCalledTimes(11);
expect(scenario).toHaveBeenCalledTimes(11);
expect(afterFn).toHaveBeenCalledTimes(11);
expect(results.runs).toBe(10);
expect(results.durations.length + (results.outlierDurations?.length ?? 0)).toBe(10);
expect(results.counts).toHaveLength(10);
});

test('measureRenders should log error when running under incorrect node flags', async () => {
jest.spyOn(realConsole, 'error').mockImplementation((message) => {
if (!errorsToIgnore.some((error) => message.includes(error))) {
Expand Down
4 changes: 4 additions & 0 deletions packages/measure/src/measure-async-function.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ async function measureAsyncFunctionInternal(

const runResults: RunResult[] = [];
for (let i = 0; i < runs + warmupRuns; i += 1) {
await options?.beforeEach?.();

const timeStart = getCurrentTime();
await fn();
const timeEnd = getCurrentTime();

await options?.afterEach?.();

const duration = timeEnd - timeStart;
runResults.push({ duration, count: 1 });
}
Expand Down
10 changes: 8 additions & 2 deletions packages/measure/src/measure-function.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ export interface MeasureFunctionOptions {
warmupRuns?: number;
removeOutliers?: boolean;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}

export async function measureFunction(fn: () => void, options?: MeasureFunctionOptions): Promise<MeasureResults> {
const stats = measureFunctionInternal(fn, options);
const stats = await measureFunctionInternal(fn, options);

if (options?.writeFile !== false) {
await writeTestStats(stats, 'function');
Expand All @@ -20,7 +22,7 @@ export async function measureFunction(fn: () => void, options?: MeasureFunctionO
return stats;
}

function measureFunctionInternal(fn: () => void, options?: MeasureFunctionOptions): MeasureResults {
async function measureFunctionInternal(fn: () => void, options?: MeasureFunctionOptions): Promise<MeasureResults> {
const runs = options?.runs ?? config.runs;
const warmupRuns = options?.warmupRuns ?? config.warmupRuns;
const removeOutliers = options?.removeOutliers ?? config.removeOutliers;
Expand All @@ -29,10 +31,14 @@ function measureFunctionInternal(fn: () => void, options?: MeasureFunctionOption

const runResults: RunResult[] = [];
for (let i = 0; i < runs + warmupRuns; i += 1) {
await options?.beforeEach?.();

const timeStart = getCurrentTime();
fn();
const timeEnd = getCurrentTime();

await options?.afterEach?.();

const duration = timeEnd - timeStart;
runResults.push({ duration, count: 1 });
}
Expand Down
6 changes: 6 additions & 0 deletions packages/measure/src/measure-renders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface MeasureRendersOptions {
wrapper?: React.ComponentType<{ children: React.ReactElement }>;
scenario?: (screen: any) => Promise<any>;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}

export async function measureRenders(
Expand Down Expand Up @@ -69,6 +71,8 @@ async function measureRendersInternal(
let initialRenderCount = 0;

for (let iteration = 0; iteration < runs + warmupRuns; iteration += 1) {
await options?.beforeEach?.();

let duration = 0;
let count = 0;
let renderResult: any = null;
Expand Down Expand Up @@ -108,6 +112,8 @@ async function measureRendersInternal(
cleanup();
global.gc?.();

await options?.afterEach?.();

runResults.push({ duration, count });
}

Expand Down
12 changes: 12 additions & 0 deletions packages/reassure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ interface MeasureRendersOptions {
wrapper?: React.ComponentType<{ children: ReactElement }>;
scenario?: (view?: RenderResult) => Promise<any>;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

Expand All @@ -383,6 +385,8 @@ interface MeasureRendersOptions {
- **`wrapper`**: React component, such as a `Provider`, which the `ui` will be wrapped with. Note: the render duration of the `wrapper` itself is excluded from the results; only the wrapped component is measured.
- **`scenario`**: a custom async function, which defines user interaction within the UI by utilising RNTL or RTL functions
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

#### `measureFunction` function

Expand All @@ -403,13 +407,17 @@ interface MeasureFunctionOptions {
warmupRuns?: number;
removeOutliers?: boolean;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

- **`runs`**: number of runs per series for the particular test
- **`warmupRuns`**: number of additional warmup runs that will be done and discarded before the actual runs
- **`removeOutliers`**: should remove statistical outlier results (default: `true`)
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

#### `measureAsyncFunction` function

Expand All @@ -432,13 +440,17 @@ interface MeasureAsyncFunctionOptions {
warmupRuns?: number;
removeOutliers?: boolean;
writeFile?: boolean;
beforeEach?: () => Promise<void> | void;
afterEach?: () => Promise<void> | void;
}
```

- **`runs`**: number of runs per series for the particular test
- **`warmupRuns`**: number of additional warmup runs that will be done and discarded before the actual runs
- **`removeOutliers`**: should remove statistical outlier results (default: `true`)
- **`writeFile`**: should write output to file (default `true`)
- **`beforeEach`**: function to execute before each test run.
- **`afterEach`**: function to execute after each test run.

### Configuration

Expand Down