overtake 1.4.0 → 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 +4 -15
- package/bin/overtake.js +1 -1
- package/build/executor.d.ts +7 -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 +6 -6
- package/build/utils.d.ts +1 -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 +48 -18
- package/src/index.ts +85 -57
- package/src/reporter.ts +27 -19
- package/src/runner.ts +2 -5
- package/src/types.ts +8 -8
- package/src/utils.ts +15 -54
- package/src/worker.ts +6 -3
- 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 -123
- package/build/executor.cjs.map +0 -1
- package/build/executor.js +0 -113
- 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 -442
- package/build/index.cjs.map +0 -1
- package/build/index.js +0 -377
- package/build/index.js.map +0 -1
- package/build/reporter.cjs +0 -311
- package/build/reporter.cjs.map +0 -1
- package/build/reporter.js +0 -293
- package/build/reporter.js.map +0 -1
- package/build/runner.cjs +0 -532
- package/build/runner.cjs.map +0 -1
- package/build/runner.js +0 -522
- 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 -174
- package/build/utils.cjs.map +0 -1
- package/build/utils.js +0 -132
- package/build/utils.js.map +0 -1
- package/build/worker.cjs +0 -155
- package/build/worker.cjs.map +0 -1
- package/build/worker.js +0 -110
- package/build/worker.js.map +0 -1
- package/src/__tests__/assert-no-closure.js +0 -134
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
|
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,5 +1,5 @@
|
|
|
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;
|
|
@@ -12,4 +12,8 @@ export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOpti
|
|
|
12
12
|
onProgress?: ProgressCallback;
|
|
13
13
|
progressInterval?: number;
|
|
14
14
|
}
|
|
15
|
-
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,12 +53,12 @@ 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
63
|
export declare const DEFAULT_CYCLES = 10000;
|
|
64
64
|
export declare const Z95 = 1.96;
|
package/build/utils.d.ts
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
|
+
export declare const resolveHookUrl: string;
|
|
1
2
|
export declare const isqrt: (n: bigint) => bigint;
|
|
2
|
-
export declare const abs: (value: bigint) => bigint;
|
|
3
3
|
export declare const cmp: (a: bigint | number, b: bigint | number) => number;
|
|
4
4
|
export declare const max: (a: bigint, b: bigint) => bigint;
|
|
5
|
-
export declare const divMod: (a: bigint, b: bigint) => {
|
|
6
|
-
quotient: bigint;
|
|
7
|
-
remainder: bigint;
|
|
8
|
-
};
|
|
9
5
|
export declare function div(a: bigint, b: bigint, decimals?: number): string;
|
|
10
6
|
export declare function divs(a: bigint, b: bigint, scale: bigint): bigint;
|
|
11
|
-
export declare class ScaledBigInt {
|
|
12
|
-
value: bigint;
|
|
13
|
-
scale: bigint;
|
|
14
|
-
constructor(value: bigint, scale: bigint);
|
|
15
|
-
add(value: bigint): void;
|
|
16
|
-
sub(value: bigint): void;
|
|
17
|
-
div(value: bigint): void;
|
|
18
|
-
mul(value: bigint): void;
|
|
19
|
-
unscale(): bigint;
|
|
20
|
-
number(): number;
|
|
21
|
-
}
|
|
22
7
|
export declare function assertNoClosure(code: string, name: string): void;
|
|
23
|
-
export declare const transpile: (code: string) => Promise<string>;
|
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
|
+
});
|