overtake 1.3.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -15
- package/bin/overtake.js +1 -1
- package/build/executor.d.ts +8 -3
- package/build/index.d.ts +10 -11
- package/build/reporter.d.ts +10 -2
- package/build/runner.d.ts +1 -1
- package/build/types.d.ts +7 -7
- package/build/utils.d.ts +3 -17
- package/package.json +8 -26
- package/src/__tests__/assert-no-closure.ts +135 -0
- package/src/__tests__/benchmark-execute.ts +48 -0
- package/src/cli.ts +139 -144
- package/src/executor.ts +59 -24
- package/src/index.ts +135 -68
- package/src/reporter.ts +77 -125
- package/src/runner.ts +28 -25
- package/src/types.ts +9 -9
- package/src/utils.ts +62 -46
- package/src/worker.ts +13 -12
- package/tsconfig.json +3 -1
- package/build/cli.cjs +0 -179
- package/build/cli.cjs.map +0 -1
- package/build/cli.js +0 -134
- package/build/cli.js.map +0 -1
- package/build/executor.cjs +0 -116
- package/build/executor.cjs.map +0 -1
- package/build/executor.js +0 -106
- package/build/executor.js.map +0 -1
- package/build/gc-watcher.cjs +0 -30
- package/build/gc-watcher.cjs.map +0 -1
- package/build/gc-watcher.js +0 -20
- package/build/gc-watcher.js.map +0 -1
- package/build/index.cjs +0 -400
- package/build/index.cjs.map +0 -1
- package/build/index.js +0 -335
- package/build/index.js.map +0 -1
- package/build/reporter.cjs +0 -364
- package/build/reporter.cjs.map +0 -1
- package/build/reporter.js +0 -346
- package/build/reporter.js.map +0 -1
- package/build/runner.cjs +0 -528
- package/build/runner.cjs.map +0 -1
- package/build/runner.js +0 -518
- package/build/runner.js.map +0 -1
- package/build/types.cjs +0 -66
- package/build/types.cjs.map +0 -1
- package/build/types.js +0 -33
- package/build/types.js.map +0 -1
- package/build/utils.cjs +0 -121
- package/build/utils.cjs.map +0 -1
- package/build/utils.js +0 -85
- package/build/utils.js.map +0 -1
- package/build/worker.cjs +0 -158
- package/build/worker.cjs.map +0 -1
- package/build/worker.js +0 -113
- package/build/worker.js.map +0 -1
package/README.md
CHANGED
|
@@ -98,8 +98,7 @@ benchmark('data', getData)
|
|
|
98
98
|
|
|
99
99
|
### Importing Local Files
|
|
100
100
|
|
|
101
|
-
- **CLI mode (`npx overtake`)**:
|
|
102
|
-
- **Programmatic mode (`suite.execute`)**: pass `baseUrl: import.meta.url` (the benchmark’s file URL) so relative imports resolve correctly. If you omit it, Overtake falls back to `process.cwd()` and relative imports may fail.
|
|
101
|
+
- **CLI mode (`npx overtake`)**: relative imports resolve from the benchmark file automatically.
|
|
103
102
|
|
|
104
103
|
```typescript
|
|
105
104
|
// CLI usage – relative path is fine
|
|
@@ -109,16 +108,6 @@ benchmark('local', () => 1)
|
|
|
109
108
|
return { helper };
|
|
110
109
|
})
|
|
111
110
|
.measure('use helper', ({ helper }) => helper());
|
|
112
|
-
|
|
113
|
-
// Programmatic usage – provide baseUrl
|
|
114
|
-
const suite = new Benchmark('local');
|
|
115
|
-
suite
|
|
116
|
-
.target('helper', async () => {
|
|
117
|
-
const { helper } = await import('./helpers.js');
|
|
118
|
-
return { helper };
|
|
119
|
-
})
|
|
120
|
-
.measure('use helper', ({ helper }) => helper());
|
|
121
|
-
await suite.execute({ baseUrl: import.meta.url });
|
|
122
111
|
```
|
|
123
112
|
|
|
124
113
|
## Usage
|
|
@@ -285,7 +274,7 @@ npx overtake <pattern> [options]
|
|
|
285
274
|
| Option | Short | Description | Default |
|
|
286
275
|
| -------------------- | ----- | ----------------------------------------------------- | --------- |
|
|
287
276
|
| `--format` | `-f` | Output format (see [Output Formats](#output-formats)) | `simple` |
|
|
288
|
-
| `--report-types` | `-r` | Stats to show (
|
|
277
|
+
| `--report-types` | `-r` | Stats to show, repeat for multiple (`-r ops -r p99`) | `['ops']` |
|
|
289
278
|
| `--workers` | `-w` | Concurrent workers | CPU count |
|
|
290
279
|
| `--min-cycles` | | Minimum measurement iterations | 50 |
|
|
291
280
|
| `--max-cycles` | | Maximum measurement iterations | 1000 |
|
|
@@ -304,7 +293,7 @@ npx overtake <pattern> [options]
|
|
|
304
293
|
npx overtake "**/*.bench.ts" -f table
|
|
305
294
|
|
|
306
295
|
# Show detailed statistics
|
|
307
|
-
npx overtake bench.ts -r ops mean p95 p99
|
|
296
|
+
npx overtake bench.ts -r ops -r mean -r p95 -r p99
|
|
308
297
|
|
|
309
298
|
# Output JSON for CI
|
|
310
299
|
npx overtake bench.ts -f json > results.json
|
|
@@ -400,7 +389,7 @@ Specify with `--report-types` or `reportTypes` option.
|
|
|
400
389
|
**Example:**
|
|
401
390
|
|
|
402
391
|
```bash
|
|
403
|
-
npx overtake bench.ts -r ops mean sd rme p50 p95 p99
|
|
392
|
+
npx overtake bench.ts -r ops -r mean -r sd -r rme -r p50 -r p95 -r p99
|
|
404
393
|
```
|
|
405
394
|
|
|
406
395
|
## Baseline Comparison
|
|
@@ -503,6 +492,14 @@ CLI mode enforces one benchmark per file. Calling `benchmark()` twice throws an
|
|
|
503
492
|
|
|
504
493
|
**Solution**: In CLI mode, don't import Benchmark or call `.execute()`. Use the global `benchmark` function.
|
|
505
494
|
|
|
495
|
+
### Worker out of memory
|
|
496
|
+
|
|
497
|
+
Benchmarks that retain objects across iterations (e.g. pushing to an array to prevent GC) can exhaust the worker heap. Increase the heap limit via `NODE_OPTIONS`:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
NODE_OPTIONS='--max-old-space-size=8192' npx overtake bench.ts
|
|
501
|
+
```
|
|
502
|
+
|
|
506
503
|
### Results vary between runs
|
|
507
504
|
|
|
508
505
|
**Solution**: Increase `--min-cycles` for more samples, or use the `gcBlock` pattern to prevent garbage collection.
|
package/bin/overtake.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --experimental-vm-modules --no-warnings --expose-gc
|
|
2
|
-
import '../
|
|
2
|
+
import '../src/cli.ts';
|
package/build/executor.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Report } from './reporter.
|
|
2
|
-
import { ExecutorRunOptions, ReportOptions, BenchmarkOptions, ReportTypeList, ProgressCallback } from './types.
|
|
1
|
+
import { Report } from './reporter.ts';
|
|
2
|
+
import { type ExecutorRunOptions, type ReportOptions, type BenchmarkOptions, type ReportTypeList, type ProgressCallback } from './types.ts';
|
|
3
3
|
export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & {
|
|
4
4
|
count: number;
|
|
5
5
|
heapUsedKB: number;
|
|
6
6
|
dceWarning: boolean;
|
|
7
|
+
error?: string;
|
|
7
8
|
};
|
|
8
9
|
export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {
|
|
9
10
|
workers?: number;
|
|
@@ -11,4 +12,8 @@ export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOpti
|
|
|
11
12
|
onProgress?: ProgressCallback;
|
|
12
13
|
progressInterval?: number;
|
|
13
14
|
}
|
|
14
|
-
export
|
|
15
|
+
export interface Executor<TContext, TInput> {
|
|
16
|
+
pushAsync<T>(task: ExecutorRunOptions<TContext, TInput>): Promise<T>;
|
|
17
|
+
kill(): void;
|
|
18
|
+
}
|
|
19
|
+
export declare const createExecutor: <TContext, TInput, R extends ReportTypeList>(options: Required<ExecutorOptions<R>>) => Executor<TContext, TInput>;
|
package/build/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { ExecutorOptions, ExecutorReport } from './executor.
|
|
2
|
-
import { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList } from './types.
|
|
1
|
+
import { type ExecutorOptions, type ExecutorReport } from './executor.ts';
|
|
2
|
+
import { type MaybePromise, type StepFn, type SetupFn, type TeardownFn, type FeedFn, type ReportType, type ReportTypeList } from './types.ts';
|
|
3
3
|
declare global {
|
|
4
4
|
const benchmark: typeof Benchmark.create;
|
|
5
5
|
}
|
|
6
6
|
export declare const DEFAULT_WORKERS: number;
|
|
7
|
-
export declare const AsyncFunction: Function;
|
|
8
7
|
export interface TargetReport<R extends ReportTypeList> {
|
|
9
8
|
target: string;
|
|
10
9
|
measures: MeasureReport<R>[];
|
|
@@ -20,10 +19,10 @@ export interface FeedReport<R extends ReportTypeList> {
|
|
|
20
19
|
export declare const DEFAULT_REPORT_TYPES: readonly ["ops"];
|
|
21
20
|
export type DefaultReportTypes = (typeof DEFAULT_REPORT_TYPES)[number];
|
|
22
21
|
export declare class MeasureContext<TContext, TInput> {
|
|
23
|
-
title: string;
|
|
24
|
-
run: StepFn<TContext, TInput>;
|
|
25
22
|
pre?: StepFn<TContext, TInput>;
|
|
26
23
|
post?: StepFn<TContext, TInput>;
|
|
24
|
+
title: string;
|
|
25
|
+
run: StepFn<TContext, TInput>;
|
|
27
26
|
constructor(title: string, run: StepFn<TContext, TInput>);
|
|
28
27
|
}
|
|
29
28
|
export declare class Measure<TContext, TInput> {
|
|
@@ -33,11 +32,11 @@ export declare class Measure<TContext, TInput> {
|
|
|
33
32
|
post(fn: StepFn<TContext, TInput>): Measure<TContext, TInput>;
|
|
34
33
|
}
|
|
35
34
|
export declare class TargetContext<TContext, TInput> {
|
|
36
|
-
readonly title: string;
|
|
37
|
-
readonly setup?: SetupFn<MaybePromise<TContext>> | undefined;
|
|
38
35
|
teardown?: TeardownFn<TContext>;
|
|
39
36
|
measures: MeasureContext<TContext, TInput>[];
|
|
40
|
-
|
|
37
|
+
readonly title: string;
|
|
38
|
+
readonly setup?: SetupFn<MaybePromise<TContext>>;
|
|
39
|
+
constructor(title: string, setup?: SetupFn<MaybePromise<TContext>>);
|
|
41
40
|
}
|
|
42
41
|
export declare class Target<TContext, TInput> {
|
|
43
42
|
#private;
|
|
@@ -47,8 +46,8 @@ export declare class Target<TContext, TInput> {
|
|
|
47
46
|
}
|
|
48
47
|
export declare class FeedContext<TInput> {
|
|
49
48
|
readonly title: string;
|
|
50
|
-
readonly fn?: FeedFn<TInput
|
|
51
|
-
constructor(title: string, fn?: FeedFn<TInput>
|
|
49
|
+
readonly fn?: FeedFn<TInput>;
|
|
50
|
+
constructor(title: string, fn?: FeedFn<TInput>);
|
|
52
51
|
}
|
|
53
52
|
export declare class Benchmark<TInput> {
|
|
54
53
|
#private;
|
|
@@ -60,7 +59,7 @@ export declare class Benchmark<TInput> {
|
|
|
60
59
|
feed<I>(title: string, fn: FeedFn<I>): Benchmark<TInput | I>;
|
|
61
60
|
target<TContext>(title: string): Target<void, TInput>;
|
|
62
61
|
target<TContext>(title: string, setup: SetupFn<Awaited<TContext>>): Target<TContext, TInput>;
|
|
63
|
-
execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>(options
|
|
62
|
+
execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>(options?: Partial<ExecutorOptions<R>> & {
|
|
64
63
|
progress?: boolean;
|
|
65
64
|
}): Promise<TargetReport<R>[]>;
|
|
66
65
|
}
|
package/build/reporter.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReportType } from './types.
|
|
1
|
+
import { type ReportType } from './types.ts';
|
|
2
2
|
export declare class Report {
|
|
3
3
|
readonly type: ReportType;
|
|
4
4
|
readonly value: bigint;
|
|
@@ -8,4 +8,12 @@ export declare class Report {
|
|
|
8
8
|
valueOf(): number;
|
|
9
9
|
toString(): string;
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
type Stats = {
|
|
12
|
+
sum: bigint;
|
|
13
|
+
mean: bigint;
|
|
14
|
+
ssd: bigint;
|
|
15
|
+
n: bigint;
|
|
16
|
+
};
|
|
17
|
+
export declare const computeStats: (durations: BigUint64Array) => Stats;
|
|
18
|
+
export declare const createReport: (durations: BigUint64Array, type: ReportType, stats?: Stats) => Report;
|
|
19
|
+
export {};
|
package/build/runner.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Options } from './types.
|
|
1
|
+
import { type Options } from './types.ts';
|
|
2
2
|
export declare const benchmark: <TContext, TInput>({ setup, teardown, pre, run: runRaw, post, data, warmupCycles, minCycles, absThreshold, relThreshold, gcObserver, durationsSAB, controlSAB, }: Required<Options<TContext, TInput>>) => Promise<number>;
|
package/build/types.d.ts
CHANGED
|
@@ -53,14 +53,14 @@ export interface Options<TContext, TInput> extends RunOptions<TContext, TInput>,
|
|
|
53
53
|
durationsSAB: SharedArrayBuffer;
|
|
54
54
|
controlSAB: SharedArrayBuffer;
|
|
55
55
|
}
|
|
56
|
-
export declare
|
|
57
|
-
INDEX
|
|
58
|
-
PROGRESS
|
|
59
|
-
COMPLETE
|
|
60
|
-
HEAP_USED
|
|
61
|
-
}
|
|
56
|
+
export declare const Control: {
|
|
57
|
+
readonly INDEX: 0;
|
|
58
|
+
readonly PROGRESS: 1;
|
|
59
|
+
readonly COMPLETE: 2;
|
|
60
|
+
readonly HEAP_USED: 3;
|
|
61
|
+
};
|
|
62
62
|
export declare const CONTROL_SLOTS: number;
|
|
63
|
-
export declare const DEFAULT_CYCLES =
|
|
63
|
+
export declare const DEFAULT_CYCLES = 10000;
|
|
64
64
|
export declare const Z95 = 1.96;
|
|
65
65
|
export declare const DURATION_SCALE = 1000n;
|
|
66
66
|
export declare const COMPLETE_VALUE = 10000;
|
package/build/utils.d.ts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const resolveHookUrl: string;
|
|
2
|
+
export declare const isqrt: (n: bigint) => bigint;
|
|
2
3
|
export declare const cmp: (a: bigint | number, b: bigint | number) => number;
|
|
3
4
|
export declare const max: (a: bigint, b: bigint) => bigint;
|
|
4
|
-
export declare const divMod: (a: bigint, b: bigint) => {
|
|
5
|
-
quotient: bigint;
|
|
6
|
-
remainder: bigint;
|
|
7
|
-
};
|
|
8
5
|
export declare function div(a: bigint, b: bigint, decimals?: number): string;
|
|
9
6
|
export declare function divs(a: bigint, b: bigint, scale: bigint): bigint;
|
|
10
|
-
export declare
|
|
11
|
-
value: bigint;
|
|
12
|
-
scale: bigint;
|
|
13
|
-
constructor(value: bigint, scale: bigint);
|
|
14
|
-
add(value: bigint): void;
|
|
15
|
-
sub(value: bigint): void;
|
|
16
|
-
div(value: bigint): void;
|
|
17
|
-
mul(value: bigint): void;
|
|
18
|
-
unscale(): bigint;
|
|
19
|
-
number(): number;
|
|
20
|
-
}
|
|
21
|
-
export declare const transpile: (code: string) => Promise<string>;
|
|
7
|
+
export declare function assertNoClosure(code: string, name: string): void;
|
package/package.json
CHANGED
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overtake",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "NodeJS performance benchmark",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
7
|
-
"main": "build/index.cjs",
|
|
8
|
-
"module": "build/index.js",
|
|
9
7
|
"exports": {
|
|
10
8
|
"types": "./build/index.d.ts",
|
|
11
|
-
"
|
|
12
|
-
"import": "./build/index.js"
|
|
13
|
-
},
|
|
14
|
-
"typesVersions": {
|
|
15
|
-
"*": {
|
|
16
|
-
"*": [
|
|
17
|
-
"build/index.d.ts"
|
|
18
|
-
]
|
|
19
|
-
}
|
|
9
|
+
"import": "./src/index.ts"
|
|
20
10
|
},
|
|
21
11
|
"bin": {
|
|
22
12
|
"overtake": "bin/overtake.js"
|
|
23
13
|
},
|
|
24
14
|
"engines": {
|
|
25
|
-
"node": ">=
|
|
15
|
+
"node": ">=24"
|
|
26
16
|
},
|
|
27
17
|
"repository": {
|
|
28
18
|
"type": "git",
|
|
@@ -41,30 +31,22 @@
|
|
|
41
31
|
},
|
|
42
32
|
"homepage": "https://github.com/3axap4eHko/overtake#readme",
|
|
43
33
|
"devDependencies": {
|
|
44
|
-
"@jest/globals": "^30.2.0",
|
|
45
|
-
"@swc/jest": "^0.2.39",
|
|
46
|
-
"@types/async": "^3.2.25",
|
|
47
|
-
"@types/jest": "^30.0.0",
|
|
48
34
|
"@types/node": "^25.3.0",
|
|
49
35
|
"@types/progress": "^2.0.7",
|
|
50
36
|
"husky": "^9.1.7",
|
|
51
|
-
"inop": "^0.9.0",
|
|
52
|
-
"jest": "^30.2.0",
|
|
53
37
|
"overtake": "^1.3.1",
|
|
54
38
|
"prettier": "^3.8.1",
|
|
55
39
|
"pretty-quick": "^4.2.2",
|
|
56
|
-
"typescript": "
|
|
40
|
+
"typescript": "5.9.3"
|
|
57
41
|
},
|
|
58
42
|
"dependencies": {
|
|
59
|
-
"@swc/core": "^1.15.
|
|
60
|
-
"async": "^3.2.6",
|
|
61
|
-
"commander": "^14.0.3",
|
|
62
|
-
"glob": "^13.0.6",
|
|
43
|
+
"@swc/core": "^1.15.18",
|
|
63
44
|
"progress": "^2.0.3"
|
|
64
45
|
},
|
|
65
46
|
"scripts": {
|
|
66
|
-
"build": "rm -rf build &&
|
|
47
|
+
"build": "rm -rf build && tsc --declaration --emitDeclarationOnly",
|
|
67
48
|
"start": "./bin/overtake.js",
|
|
68
|
-
"test": "
|
|
49
|
+
"test": "node --experimental-test-module-mocks --test src/__tests__/*.ts",
|
|
50
|
+
"test:cov": "node --experimental-test-module-mocks --experimental-test-coverage --test src/__tests__/*.ts"
|
|
69
51
|
}
|
|
70
52
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { assertNoClosure } from '../utils.ts';
|
|
4
|
+
|
|
5
|
+
describe('assertNoClosure', () => {
|
|
6
|
+
describe('allows functions without closures', () => {
|
|
7
|
+
it('arrow with params only', () => {
|
|
8
|
+
assert.doesNotThrow(() => assertNoClosure('(x) => x * 2', 'run'));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('arrow with local variables', () => {
|
|
12
|
+
assert.doesNotThrow(() => assertNoClosure('(ctx, input) => { const y = ctx.value + input; return y; }', 'run'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('function expression', () => {
|
|
16
|
+
assert.doesNotThrow(() => assertNoClosure('function(x) { return x + 1; }', 'run'));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('named function expression', () => {
|
|
20
|
+
assert.doesNotThrow(() => assertNoClosure('function run(x) { return x + 1; }', 'run'));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('async arrow', () => {
|
|
24
|
+
assert.doesNotThrow(() => assertNoClosure('async (ctx) => { const r = await fetch("url"); return r; }', 'run'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('destructured params', () => {
|
|
28
|
+
assert.doesNotThrow(() => assertNoClosure('({a, b: c}, [d, ...e]) => a + c + d + e.length', 'run'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('nested function declaration', () => {
|
|
32
|
+
assert.doesNotThrow(() => assertNoClosure('(arr) => { function helper(x) { return x * 2; } return arr.map(helper); }', 'run'));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('for-of loop variable', () => {
|
|
36
|
+
assert.doesNotThrow(() => assertNoClosure('(arr) => { let sum = 0; for (const x of arr) sum += x; return sum; }', 'run'));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('member access on params', () => {
|
|
40
|
+
assert.doesNotThrow(() => assertNoClosure('(ctx) => ctx.data.map(x => x.value)', 'run'));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('globals like console, Buffer, Math, Array', () => {
|
|
44
|
+
assert.doesNotThrow(() => assertNoClosure('(ctx) => { console.log(Math.max(...ctx)); return Buffer.from(Array.of(1)); }', 'run'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('try-catch with error binding', () => {
|
|
48
|
+
assert.doesNotThrow(() => assertNoClosure('(ctx) => { try { return ctx(); } catch (e) { return e; } }', 'run'));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('class expression', () => {
|
|
52
|
+
assert.doesNotThrow(() => assertNoClosure('() => { class Foo { bar() { return 1; } } return new Foo(); }', 'run'));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('label statements', () => {
|
|
56
|
+
assert.doesNotThrow(() => assertNoClosure('() => { outer: for (let i = 0; i < 10; i++) { break outer; } }', 'run'));
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('detects closures', () => {
|
|
61
|
+
it('single closed-over variable', () => {
|
|
62
|
+
assert.throws(() => assertNoClosure('(x) => x + closedOver', 'run'), /closedOver/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('multiple closed-over variables', () => {
|
|
66
|
+
assert.throws(() => assertNoClosure('(ctx) => sharedData.filter(x => x > threshold)', 'run'), /sharedData/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('closed-over function call', () => {
|
|
70
|
+
assert.throws(() => assertNoClosure('(ctx) => helper(ctx)', 'run'), /helper/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('closed-over array', () => {
|
|
74
|
+
assert.throws(() => assertNoClosure('() => myArray.map(x => x * 2)', 'run'), /myArray/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('computed member access with outer variable', () => {
|
|
78
|
+
assert.throws(() => assertNoClosure('(obj) => obj[key]', 'run'), /key/);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('variable used as argument', () => {
|
|
82
|
+
assert.throws(() => assertNoClosure('() => JSON.stringify(config)', 'run'), /config/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('variable in template literal', () => {
|
|
86
|
+
assert.throws(() => assertNoClosure('() => `${prefix}-value`', 'run'), /prefix/);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('error message', () => {
|
|
91
|
+
it('includes the function name', () => {
|
|
92
|
+
assert.throws(() => assertNoClosure('() => x', 'setup'), /"setup"/);
|
|
93
|
+
assert.throws(() => assertNoClosure('() => x', 'run'), /"run"/);
|
|
94
|
+
assert.throws(() => assertNoClosure('() => x', 'teardown'), /"teardown"/);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('lists all closed-over variables', () => {
|
|
98
|
+
try {
|
|
99
|
+
assertNoClosure('() => a + b + c', 'run');
|
|
100
|
+
assert.fail('should have thrown');
|
|
101
|
+
} catch (e) {
|
|
102
|
+
assert.match((e as Error).message, /\ba\b/);
|
|
103
|
+
assert.match((e as Error).message, /\bb\b/);
|
|
104
|
+
assert.match((e as Error).message, /\bc\b/);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('explains the problem and suggests fix', () => {
|
|
109
|
+
try {
|
|
110
|
+
assertNoClosure('() => x', 'run');
|
|
111
|
+
assert.fail('should have thrown');
|
|
112
|
+
} catch (e) {
|
|
113
|
+
const msg = (e as Error).message;
|
|
114
|
+
assert.ok(msg.includes('.toString()'));
|
|
115
|
+
assert.ok(msg.includes('worker'));
|
|
116
|
+
assert.ok(msg.includes('setup'));
|
|
117
|
+
assert.ok(msg.includes('data'));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('edge cases', () => {
|
|
123
|
+
it('silently passes on unparseable code', () => {
|
|
124
|
+
assert.doesNotThrow(() => assertNoClosure('not valid js {{{', 'run'));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('empty arrow function', () => {
|
|
128
|
+
assert.doesNotThrow(() => assertNoClosure('() => {}', 'run'));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('undefined return', () => {
|
|
132
|
+
assert.doesNotThrow(() => assertNoClosure('() => undefined', 'run'));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, mock } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
describe('Benchmark.execute', () => {
|
|
5
|
+
it('returns an error report when executor task setup fails', async () => {
|
|
6
|
+
const pushAsync = mock.fn(() => Promise.reject(new Error('Benchmark "run" function references outer-scope variables: port')));
|
|
7
|
+
const kill = mock.fn();
|
|
8
|
+
|
|
9
|
+
mock.module('../executor.ts', {
|
|
10
|
+
namedExports: {
|
|
11
|
+
createExecutor: () => ({ pushAsync, kill }),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const { Benchmark } = await import('../index.ts');
|
|
16
|
+
|
|
17
|
+
const bench = Benchmark.create('feed');
|
|
18
|
+
bench.target('target').measure('run', () => 1);
|
|
19
|
+
|
|
20
|
+
const reports = await bench.execute();
|
|
21
|
+
|
|
22
|
+
assert.deepStrictEqual(reports, [
|
|
23
|
+
{
|
|
24
|
+
target: 'target',
|
|
25
|
+
measures: [
|
|
26
|
+
{
|
|
27
|
+
measure: 'run',
|
|
28
|
+
feeds: [
|
|
29
|
+
{
|
|
30
|
+
feed: 'feed',
|
|
31
|
+
data: {
|
|
32
|
+
count: 0,
|
|
33
|
+
heapUsedKB: 0,
|
|
34
|
+
dceWarning: false,
|
|
35
|
+
error: 'Benchmark "run" function references outer-scope variables: port',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
assert.strictEqual(pushAsync.mock.callCount(), 1);
|
|
44
|
+
assert.strictEqual(kill.mock.callCount(), 1);
|
|
45
|
+
|
|
46
|
+
mock.restoreAll();
|
|
47
|
+
});
|
|
48
|
+
});
|