benchforge 0.1.11 → 0.2.4
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/LICENSE +20 -0
- package/README.md +99 -294
- package/bin/benchforge +1 -2
- package/dist/AnalyzeArchive-8NCJhmhS.mjs +145 -0
- package/dist/AnalyzeArchive-8NCJhmhS.mjs.map +1 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs +1050 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs.map +1 -0
- package/dist/{BenchRunner-BzyUfiyB.d.mts → BenchRunner-DglX1NOn.d.mts} +119 -66
- package/dist/CoverageSampler-D5T9DRqe.mjs +27 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs.map +1 -0
- package/dist/Formatters-BWj3d4sv.mjs +95 -0
- package/dist/Formatters-BWj3d4sv.mjs.map +1 -0
- package/dist/{HeapSampler-B8dtKHn1.mjs → HeapSampler-Dq-hpXem.mjs} +4 -4
- package/dist/HeapSampler-Dq-hpXem.mjs.map +1 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs +3075 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs.map +1 -0
- package/dist/StatisticalUtils-BD92crgM.mjs +255 -0
- package/dist/StatisticalUtils-BD92crgM.mjs.map +1 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs +29 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs.map +1 -0
- package/dist/ViewerServer-BJhdnxlN.mjs +639 -0
- package/dist/ViewerServer-BJhdnxlN.mjs.map +1 -0
- package/dist/ViewerServer-CuMNdNBz.mjs +2 -0
- package/dist/bin/benchforge.mjs +4 -5
- package/dist/bin/benchforge.mjs.map +1 -1
- package/dist/index.d.mts +711 -558
- package/dist/index.mjs +98 -3
- package/dist/index.mjs.map +1 -0
- package/dist/runners/WorkerScript.d.mts +12 -4
- package/dist/runners/WorkerScript.mjs +77 -105
- package/dist/runners/WorkerScript.mjs.map +1 -1
- package/dist/viewer/assets/CIPlot-BkOvMoMa.js +1 -0
- package/dist/viewer/assets/HistogramKde-CmSyUFY0.js +1 -0
- package/dist/viewer/assets/LegendUtils-BJpbn_jr.js +55 -0
- package/dist/viewer/assets/SampleTimeSeries-C4VBhXr3.js +1 -0
- package/dist/viewer/assets/index-Br9bp_cX.js +153 -0
- package/dist/viewer/assets/index-NzXXe_CC.css +1 -0
- package/dist/viewer/index.html +19 -0
- package/dist/viewer/speedscope/LICENSE +21 -0
- package/dist/viewer/speedscope/SourceCodePro-Regular.ttf-ILST5JV6.woff2 +0 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js +2 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js.map +7 -0
- package/dist/viewer/speedscope/favicon-16x16-VSI62OPJ.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-3EB2YCUY.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js +2 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js.map +7 -0
- package/dist/viewer/speedscope/favicon-FOKUP5Y5.ico +0 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js +2 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js.map +7 -0
- package/dist/viewer/speedscope/file-format-schema.json +274 -0
- package/dist/viewer/speedscope/index.html +19 -0
- package/dist/viewer/speedscope/jfrview_bg-BLJXNNQB.wasm +0 -0
- package/dist/viewer/speedscope/perf-vertx-stacks-01-collapsed-all-ZNUIGAJL.txt +199 -0
- package/dist/viewer/speedscope/release.txt +3 -0
- package/dist/viewer/speedscope/source-code-pro.LICENSE.md +93 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css +2 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css.map +7 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js +212 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js.map +7 -0
- package/package.json +52 -27
- package/src/bin/benchforge.ts +2 -2
- package/src/cli/AnalyzeArchive.ts +232 -0
- package/src/cli/BrowserBench.ts +322 -0
- package/src/cli/CliArgs.ts +164 -51
- package/src/cli/CliExport.ts +179 -0
- package/src/cli/CliOptions.ts +147 -0
- package/src/cli/CliReport.ts +197 -0
- package/src/cli/FilterBenchmarks.ts +18 -30
- package/src/cli/RunBenchCLI.ts +132 -866
- package/src/cli/SuiteRunner.ts +160 -0
- package/src/cli/ViewerServer.ts +282 -0
- package/src/export/AllocExport.ts +121 -0
- package/src/export/ArchiveExport.ts +146 -0
- package/src/export/ArchiveFormat.ts +50 -0
- package/src/export/CoverageExport.ts +148 -0
- package/src/export/EditorUri.ts +10 -0
- package/src/export/PerfettoExport.ts +64 -99
- package/src/export/SpeedscopeTypes.ts +98 -0
- package/src/export/TimeExport.ts +115 -0
- package/src/index.ts +86 -67
- package/src/matrix/BenchMatrix.ts +230 -0
- package/src/matrix/CaseLoader.ts +8 -6
- package/src/matrix/MatrixDirRunner.ts +153 -0
- package/src/matrix/MatrixFilter.ts +49 -47
- package/src/matrix/MatrixInlineRunner.ts +50 -0
- package/src/matrix/MatrixReport.ts +90 -250
- package/src/matrix/VariantLoader.ts +5 -5
- package/src/profiling/browser/BenchLoop.ts +51 -0
- package/src/profiling/browser/BrowserCDP.ts +133 -0
- package/src/profiling/browser/BrowserGcStats.ts +33 -0
- package/src/profiling/browser/BrowserProfiler.ts +160 -0
- package/src/profiling/browser/CdpClient.ts +82 -0
- package/src/profiling/browser/CdpPage.ts +138 -0
- package/src/profiling/browser/ChromeLauncher.ts +158 -0
- package/src/profiling/browser/ChromeTraceEvent.ts +28 -0
- package/src/profiling/browser/PageLoadMode.ts +61 -0
- package/src/profiling/node/CoverageSampler.ts +27 -0
- package/src/profiling/node/CoverageTypes.ts +23 -0
- package/src/profiling/node/HeapSampleReport.ts +261 -0
- package/src/{heap-sample → profiling/node}/HeapSampler.ts +1 -2
- package/src/{heap-sample → profiling/node}/ResolvedProfile.ts +18 -9
- package/src/profiling/node/TimeSampler.ts +57 -0
- package/src/report/BenchmarkReport.ts +146 -0
- package/src/report/Colors.ts +9 -0
- package/src/report/Formatters.ts +110 -0
- package/src/report/GcSections.ts +151 -0
- package/src/{GitUtils.ts → report/GitUtils.ts} +18 -19
- package/src/report/HtmlReport.ts +223 -0
- package/src/report/ParseStats.ts +73 -0
- package/src/report/StandardSections.ts +147 -0
- package/src/report/ViewerSections.ts +286 -0
- package/src/report/text/TableReport.ts +253 -0
- package/src/report/text/TextReport.ts +123 -0
- package/src/runners/AdaptiveWrapper.ts +116 -236
- package/src/runners/BenchRunner.ts +20 -15
- package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
- package/src/runners/CreateRunner.ts +5 -7
- package/src/runners/GcStats.ts +47 -50
- package/src/{MeasuredResults.ts → runners/MeasuredResults.ts} +43 -37
- package/src/runners/MergeBatches.ts +123 -0
- package/src/{NodeGC.ts → runners/NodeGC.ts} +2 -3
- package/src/runners/RunnerOrchestrator.ts +127 -243
- package/src/runners/RunnerUtils.ts +75 -1
- package/src/runners/SampleStats.ts +100 -0
- package/src/runners/TimingRunner.ts +244 -0
- package/src/runners/TimingUtils.ts +3 -2
- package/src/runners/WorkerScript.ts +135 -151
- package/src/stats/BootstrapDifference.ts +282 -0
- package/src/{PermutationTest.ts → stats/PermutationTest.ts} +8 -17
- package/src/stats/StatisticalUtils.ts +445 -0
- package/src/{tests → test}/AdaptiveConvergence.test.ts +10 -10
- package/src/test/AdaptiveRunner.test.ts +39 -41
- package/src/{tests → test}/AdaptiveSampling.test.ts +9 -9
- package/src/test/AdaptiveStatistics.integration.ts +2 -2
- package/src/{tests → test}/BenchMatrix.test.ts +19 -16
- package/src/test/BenchmarkReport.test.ts +63 -13
- package/src/test/BrowserBench.e2e.test.ts +186 -17
- package/src/test/BrowserBench.test.ts +10 -5
- package/src/test/BuildTimeSection.test.ts +130 -0
- package/src/test/CapSamples.test.ts +82 -0
- package/src/test/CoverageExport.test.ts +115 -0
- package/src/test/CoverageSampler.test.ts +33 -0
- package/src/test/HeapAttribution.test.ts +14 -14
- package/src/{tests → test}/MatrixFilter.test.ts +1 -1
- package/src/{tests → test}/MatrixReport.test.ts +1 -1
- package/src/test/PermutationTest.test.ts +1 -1
- package/src/{tests → test}/RealDataValidation.test.ts +6 -6
- package/src/test/RunBenchCLI.test.ts +39 -38
- package/src/test/RunnerOrchestrator.test.ts +12 -12
- package/src/test/StatisticalUtils.test.ts +48 -12
- package/src/{table-util/test → test}/TableReport.test.ts +2 -2
- package/src/test/TestUtils.ts +12 -7
- package/src/test/TimeExport.test.ts +139 -0
- package/src/test/TimeSampler.test.ts +37 -0
- package/src/test/ViewerLive.e2e.test.ts +159 -0
- package/src/test/ViewerStatic.static.e2e.test.ts +137 -0
- package/src/{tests → test}/fixtures/baseline/impl.ts +1 -1
- package/src/{tests → test}/fixtures/bevy30-samples.ts +3 -1
- package/src/test/fixtures/cases/asyncCases.ts +9 -0
- package/src/{tests → test}/fixtures/cases/cases.ts +5 -2
- package/src/test/fixtures/cases/variants/product.ts +2 -0
- package/src/test/fixtures/cases/variants/sum.ts +2 -0
- package/src/test/fixtures/discover/fast.ts +1 -0
- package/src/{tests → test}/fixtures/discover/slow.ts +1 -1
- package/src/test/fixtures/invalid/bad.ts +1 -0
- package/src/test/fixtures/loader/fast.ts +1 -0
- package/src/{tests → test}/fixtures/loader/slow.ts +1 -1
- package/src/test/fixtures/loader/stateful.ts +2 -0
- package/src/test/fixtures/stateful/stateful.ts +2 -0
- package/src/test/fixtures/variants/extra.ts +1 -0
- package/src/test/fixtures/variants/impl.ts +1 -0
- package/src/test/fixtures/worker/fast.ts +1 -0
- package/src/{tests → test}/fixtures/worker/slow.ts +1 -1
- package/src/viewer/DateFormat.ts +30 -0
- package/src/viewer/Helpers.ts +23 -0
- package/src/viewer/LineData.ts +120 -0
- package/src/viewer/Providers.ts +191 -0
- package/src/viewer/ReportData.ts +123 -0
- package/src/viewer/State.ts +49 -0
- package/src/viewer/Theme.ts +15 -0
- package/src/viewer/components/App.tsx +73 -0
- package/src/viewer/components/DropZone.tsx +71 -0
- package/src/viewer/components/LazyPlot.ts +33 -0
- package/src/viewer/components/SamplesPanel.tsx +214 -0
- package/src/viewer/components/Shell.tsx +26 -0
- package/src/viewer/components/SourcePanel.tsx +216 -0
- package/src/viewer/components/SummaryPanel.tsx +332 -0
- package/src/viewer/components/TabBar.tsx +131 -0
- package/src/viewer/components/TabContent.tsx +46 -0
- package/src/viewer/components/ThemeToggle.tsx +50 -0
- package/src/viewer/index.html +20 -0
- package/src/viewer/main.tsx +4 -0
- package/src/viewer/plots/CIPlot.ts +313 -0
- package/src/{html/browser → viewer/plots}/HistogramKde.ts +33 -38
- package/src/viewer/plots/LegendUtils.ts +134 -0
- package/src/viewer/plots/PlotTypes.ts +85 -0
- package/src/viewer/plots/RenderPlots.ts +230 -0
- package/src/viewer/plots/SampleTimeSeries.ts +306 -0
- package/src/viewer/plots/SvgHelpers.ts +136 -0
- package/src/viewer/plots/TimeSeriesMarks.ts +319 -0
- package/src/viewer/report.css +427 -0
- package/src/viewer/shell.css +357 -0
- package/src/viewer/tsconfig.json +11 -0
- package/dist/BrowserHeapSampler-B6asLKWQ.mjs +0 -202
- package/dist/BrowserHeapSampler-B6asLKWQ.mjs.map +0 -1
- package/dist/GcStats-wX7Xyblu.mjs +0 -77
- package/dist/GcStats-wX7Xyblu.mjs.map +0 -1
- package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
- package/dist/TimingUtils-DwOwkc8G.mjs +0 -597
- package/dist/TimingUtils-DwOwkc8G.mjs.map +0 -1
- package/dist/browser/index.js +0 -914
- package/dist/src-B-DDaCa9.mjs +0 -3108
- package/dist/src-B-DDaCa9.mjs.map +0 -1
- package/src/BenchMatrix.ts +0 -380
- package/src/BenchmarkReport.ts +0 -161
- package/src/HtmlDataPrep.ts +0 -148
- package/src/StandardSections.ts +0 -261
- package/src/StatisticalUtils.ts +0 -175
- package/src/TypeUtil.ts +0 -8
- package/src/browser/BrowserGcStats.ts +0 -44
- package/src/browser/BrowserHeapSampler.ts +0 -271
- package/src/export/JsonExport.ts +0 -103
- package/src/export/JsonFormat.ts +0 -91
- package/src/export/SpeedscopeExport.ts +0 -202
- package/src/heap-sample/HeapSampleReport.ts +0 -269
- package/src/html/HtmlReport.ts +0 -131
- package/src/html/HtmlTemplate.ts +0 -284
- package/src/html/Types.ts +0 -88
- package/src/html/browser/CIPlot.ts +0 -287
- package/src/html/browser/LegendUtils.ts +0 -163
- package/src/html/browser/RenderPlots.ts +0 -263
- package/src/html/browser/SampleTimeSeries.ts +0 -389
- package/src/html/browser/Types.ts +0 -96
- package/src/html/browser/index.ts +0 -1
- package/src/html/index.ts +0 -17
- package/src/runners/BasicRunner.ts +0 -364
- package/src/table-util/ConvergenceFormatters.ts +0 -19
- package/src/table-util/Formatters.ts +0 -157
- package/src/table-util/README.md +0 -70
- package/src/table-util/TableReport.ts +0 -293
- package/src/tests/fixtures/cases/asyncCases.ts +0 -7
- package/src/tests/fixtures/cases/variants/product.ts +0 -2
- package/src/tests/fixtures/cases/variants/sum.ts +0 -2
- package/src/tests/fixtures/discover/fast.ts +0 -1
- package/src/tests/fixtures/invalid/bad.ts +0 -1
- package/src/tests/fixtures/loader/fast.ts +0 -1
- package/src/tests/fixtures/loader/stateful.ts +0 -2
- package/src/tests/fixtures/stateful/stateful.ts +0 -2
- package/src/tests/fixtures/variants/extra.ts +0 -1
- package/src/tests/fixtures/variants/impl.ts +0 -1
- package/src/tests/fixtures/worker/fast.ts +0 -1
- /package/src/{table-util/test → test}/TableValueExtractor.test.ts +0 -0
- /package/src/{table-util/test → test}/TableValueExtractor.ts +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import type { BenchmarkReport } from "../report/BenchmarkReport.ts";
|
|
3
|
+
import { computeColumnValues } from "../report/BenchmarkReport.ts";
|
|
4
|
+
import { buildTimeSection } from "../report/StandardSections.ts";
|
|
5
|
+
import { reportResults, valuesForReports } from "../report/text/TextReport.ts";
|
|
6
|
+
import type { MeasuredResults } from "../runners/MeasuredResults.ts";
|
|
7
|
+
|
|
8
|
+
/** @return minimal MeasuredResults with the given samples (time fields derived trivially). */
|
|
9
|
+
function measured(samples: number[]): MeasuredResults {
|
|
10
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
11
|
+
return {
|
|
12
|
+
name: "t",
|
|
13
|
+
samples,
|
|
14
|
+
time: {
|
|
15
|
+
min: sorted[0],
|
|
16
|
+
max: sorted[sorted.length - 1],
|
|
17
|
+
avg: samples.reduce((a, b) => a + b, 0) / samples.length,
|
|
18
|
+
p50: sorted[Math.floor(sorted.length * 0.5)],
|
|
19
|
+
p75: sorted[Math.floor(sorted.length * 0.75)],
|
|
20
|
+
p99: sorted[Math.floor(sorted.length * 0.99)],
|
|
21
|
+
p999: sorted[Math.floor(sorted.length * 0.999)],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function report(name: string, samples: number[]): BenchmarkReport {
|
|
27
|
+
return { name, measuredResults: measured(samples) };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function range(n: number): number[] {
|
|
31
|
+
return Array.from({ length: n }, (_, i) => i + 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test("default buildTimeSection produces mean, p50, p99 columns", () => {
|
|
35
|
+
const section = buildTimeSection();
|
|
36
|
+
expect(section.columns.map(c => c.key ?? c.title)).toEqual([
|
|
37
|
+
"mean",
|
|
38
|
+
"p50",
|
|
39
|
+
"p99",
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("computeColumnValues computes values from samples", () => {
|
|
44
|
+
const section = buildTimeSection("mean,p50,max,min");
|
|
45
|
+
const row = computeColumnValues(section, measured([10, 20, 30, 40, 50]));
|
|
46
|
+
expect(row.mean).toBe(30);
|
|
47
|
+
expect(row.min).toBe(10);
|
|
48
|
+
expect(row.max).toBe(50);
|
|
49
|
+
expect(row.p50).toBeGreaterThanOrEqual(20);
|
|
50
|
+
expect(row.p50).toBeLessThanOrEqual(40);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("p70 returns value near 70th percentile of [1..100]", () => {
|
|
54
|
+
const section = buildTimeSection("p70");
|
|
55
|
+
const row = computeColumnValues(section, measured(range(100)));
|
|
56
|
+
expect(row.p70).toBeGreaterThanOrEqual(69);
|
|
57
|
+
expect(row.p70).toBeLessThanOrEqual(71);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("p999 uses divide-by-1000 convention", () => {
|
|
61
|
+
const section = buildTimeSection("p999");
|
|
62
|
+
const row = computeColumnValues(section, measured(range(1000)));
|
|
63
|
+
expect(row.p999).toBeGreaterThanOrEqual(999);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("p9999 uses divide-by-10000 convention", () => {
|
|
67
|
+
const section = buildTimeSection("p9999");
|
|
68
|
+
const row = computeColumnValues(section, measured(range(10000)));
|
|
69
|
+
expect(row.p9999).toBeGreaterThanOrEqual(9999);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("median and p50 produce the same value", () => {
|
|
73
|
+
const a = computeColumnValues(
|
|
74
|
+
buildTimeSection("median"),
|
|
75
|
+
measured(range(100)),
|
|
76
|
+
);
|
|
77
|
+
const b = computeColumnValues(buildTimeSection("p50"), measured(range(100)));
|
|
78
|
+
expect(a.p50).toBe(b.p50);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("mean and avg dedupe to a single column", () => {
|
|
82
|
+
const section = buildTimeSection("mean,avg");
|
|
83
|
+
expect(section.columns.length).toBe(1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("min and max return exact values", () => {
|
|
87
|
+
const section = buildTimeSection("min,max");
|
|
88
|
+
const row = computeColumnValues(section, measured([5, 1, 9, 3, 7]));
|
|
89
|
+
expect(row.min).toBe(1);
|
|
90
|
+
expect(row.max).toBe(9);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("empty stats string throws", () => {
|
|
94
|
+
expect(() => buildTimeSection("")).toThrow(/at least one column/);
|
|
95
|
+
expect(() => buildTimeSection(" , ")).toThrow(/at least one column/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("unknown token throws with vocabulary hint", () => {
|
|
99
|
+
expect(() => buildTimeSection("wat")).toThrow(
|
|
100
|
+
/expected mean, median, min, max, or p<N>/,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("single-digit percentile token is rejected", () => {
|
|
105
|
+
expect(() => buildTimeSection("p5")).toThrow(/at least 2 digits/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("3+ digit percentile tokens not starting with 9 are rejected", () => {
|
|
109
|
+
expect(() => buildTimeSection("p100")).toThrow(/must start with 9/);
|
|
110
|
+
expect(() => buildTimeSection("p500")).toThrow(/must start with 9/);
|
|
111
|
+
expect(() => buildTimeSection("p1000")).toThrow(/must start with 9/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("reportResults renders user-chosen columns as table headers", () => {
|
|
115
|
+
const groups = [{ name: "g", reports: [report("bench", range(100))] }];
|
|
116
|
+
const table = reportResults(groups, [buildTimeSection("p70,p95")]);
|
|
117
|
+
expect(table).toContain("p70");
|
|
118
|
+
expect(table).toContain("p95");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("valuesForReports extracts user-chosen keys", () => {
|
|
122
|
+
const rows = valuesForReports(
|
|
123
|
+
[report("bench", range(100))],
|
|
124
|
+
[buildTimeSection("p70,p95")],
|
|
125
|
+
);
|
|
126
|
+
expect(rows[0].p70).toBeGreaterThanOrEqual(69);
|
|
127
|
+
expect(rows[0].p70).toBeLessThanOrEqual(71);
|
|
128
|
+
expect(rows[0].p95).toBeGreaterThanOrEqual(94);
|
|
129
|
+
expect(rows[0].p95).toBeLessThanOrEqual(96);
|
|
130
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { sampleDifferenceCI } from "../stats/BootstrapDifference.ts";
|
|
3
|
+
import {
|
|
4
|
+
average,
|
|
5
|
+
maxBootstrapInput,
|
|
6
|
+
percentile,
|
|
7
|
+
sampleBootstrap,
|
|
8
|
+
} from "../stats/StatisticalUtils.ts";
|
|
9
|
+
|
|
10
|
+
test("sampleBootstrap uses full samples for point estimate", () => {
|
|
11
|
+
const samples = Array.from({ length: 5000 }, (_, i) => i);
|
|
12
|
+
const result = sampleBootstrap(samples, average, { resamples: 100 });
|
|
13
|
+
expect(result.estimate).toBe(average(samples));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("sampleDifferenceCI preserves point estimate", () => {
|
|
17
|
+
const a = Array.from({ length: 5000 }, () => 50 + Math.random() * 10);
|
|
18
|
+
const b = a.map(v => v * 1.1);
|
|
19
|
+
const result = sampleDifferenceCI(a, b, average, { resamples: 100 });
|
|
20
|
+
const expected = ((average(b) - average(a)) / average(a)) * 100;
|
|
21
|
+
expect(result.percent).toBeCloseTo(expected, 10);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("sampleBootstrap point estimate uses full array when capped", () => {
|
|
25
|
+
const n = maxBootstrapInput + 5000;
|
|
26
|
+
const samples = Array.from({ length: n }, (_, i) => i);
|
|
27
|
+
const result = sampleBootstrap(samples, average, { resamples: 50 });
|
|
28
|
+
expect(result.estimate).toBe(average(samples));
|
|
29
|
+
expect(result.subsampled).toBe(n);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("sampleBootstrap does not set subsampled when under cap", () => {
|
|
33
|
+
const samples = Array.from({ length: 100 }, (_, i) => i);
|
|
34
|
+
const result = sampleBootstrap(samples, average, { resamples: 50 });
|
|
35
|
+
expect(result.subsampled).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("sampleDifferenceCI sets subsampled when inputs exceed cap", () => {
|
|
39
|
+
const n = maxBootstrapInput + 1000;
|
|
40
|
+
const a = Array.from({ length: n }, () => 50 + Math.random() * 10);
|
|
41
|
+
const b = a.map(v => v * 1.1);
|
|
42
|
+
const result = sampleDifferenceCI(a, b, average, { resamples: 50 });
|
|
43
|
+
expect(result.percent).toBeCloseTo(10, 0);
|
|
44
|
+
expect(result.subsampled).toBe(n);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("sampleDifferenceCI no subsampled flag when under cap", () => {
|
|
48
|
+
const a = Array.from({ length: 100 }, () => 50 + Math.random() * 10);
|
|
49
|
+
const b = a.map(v => v * 1.1);
|
|
50
|
+
const result = sampleDifferenceCI(a, b, average, { resamples: 50 });
|
|
51
|
+
expect(result.subsampled).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("quickselect-based percentile matches sorted percentile", () => {
|
|
55
|
+
const data = Array.from({ length: 1000 }, () => Math.random() * 100);
|
|
56
|
+
const sorted = [...data].sort((a, b) => a - b);
|
|
57
|
+
for (const p of [0.25, 0.5, 0.75, 0.99]) {
|
|
58
|
+
const k = Math.max(0, Math.ceil(sorted.length * p) - 1);
|
|
59
|
+
expect(percentile(data, p)).toBe(sorted[k]);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("quickselect handles small arrays", () => {
|
|
64
|
+
expect(percentile([42], 0.5)).toBe(42);
|
|
65
|
+
expect(percentile([1, 2], 0.5)).toBe(1);
|
|
66
|
+
expect(percentile([1, 2], 1.0)).toBe(2);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("quickselect handles duplicate values", () => {
|
|
70
|
+
const data = [5, 5, 5, 5, 5, 10, 10, 10, 10, 10];
|
|
71
|
+
expect(percentile(data, 0.5)).toBe(5);
|
|
72
|
+
expect(percentile(data, 0.99)).toBe(10);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("sampleBootstrap reuses buffer (no per-iteration allocation)", () => {
|
|
76
|
+
const samples = [10, 20, 30, 40, 50];
|
|
77
|
+
const result = sampleBootstrap(samples, average, { resamples: 50 });
|
|
78
|
+
expect(result.estimate).toBe(average(samples));
|
|
79
|
+
expect(result.samples).toHaveLength(50);
|
|
80
|
+
expect(result.ci[0]).toBeLessThanOrEqual(result.estimate);
|
|
81
|
+
expect(result.ci[1]).toBeGreaterThanOrEqual(result.estimate);
|
|
82
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
annotateFramesWithCounts,
|
|
4
|
+
buildCoverageMap,
|
|
5
|
+
} from "../export/CoverageExport.ts";
|
|
6
|
+
import type { CoverageData } from "../profiling/node/CoverageTypes.ts";
|
|
7
|
+
|
|
8
|
+
const source = `function foo() {
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
function bar() {
|
|
12
|
+
return 2;
|
|
13
|
+
}
|
|
14
|
+
const baz = () => 3;
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const coverage: CoverageData = {
|
|
18
|
+
scripts: [
|
|
19
|
+
{
|
|
20
|
+
url: "file:///test.js",
|
|
21
|
+
functions: [
|
|
22
|
+
{
|
|
23
|
+
functionName: "foo",
|
|
24
|
+
ranges: [{ startOffset: 0, endOffset: 30, count: 10 }],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
functionName: "bar",
|
|
28
|
+
ranges: [{ startOffset: 31, endOffset: 60, count: 5 }],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
functionName: "",
|
|
32
|
+
ranges: [{ startOffset: 61, endOffset: 80, count: 3 }],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
test("buildCoverageMap resolves offsets to lines", () => {
|
|
40
|
+
const result = buildCoverageMap(coverage, { "file:///test.js": source });
|
|
41
|
+
|
|
42
|
+
expect(result.map.has("file:///test.js")).toBe(true);
|
|
43
|
+
const entries = result.map.get("file:///test.js")!;
|
|
44
|
+
expect(entries).toHaveLength(3);
|
|
45
|
+
|
|
46
|
+
const foo = entries.find(e => e.functionName === "foo");
|
|
47
|
+
expect(foo).toBeDefined();
|
|
48
|
+
expect(foo!.startLine).toBe(1);
|
|
49
|
+
expect(foo!.count).toBe(10);
|
|
50
|
+
|
|
51
|
+
const bar = entries.find(e => e.functionName === "bar");
|
|
52
|
+
expect(bar).toBeDefined();
|
|
53
|
+
expect(bar!.startLine).toBe(4);
|
|
54
|
+
expect(bar!.count).toBe(5);
|
|
55
|
+
|
|
56
|
+
// byName aggregates across all scripts
|
|
57
|
+
expect(result.byName.get("foo")).toBe(10);
|
|
58
|
+
expect(result.byName.get("bar")).toBe(5);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("annotateFramesWithCounts appends [N] to matched frames", () => {
|
|
62
|
+
const result = buildCoverageMap(coverage, { "file:///test.js": source });
|
|
63
|
+
|
|
64
|
+
const frames = [
|
|
65
|
+
{ name: "foo", file: "file:///test.js", line: 1 },
|
|
66
|
+
{ name: "bar", file: "file:///test.js", line: 4 },
|
|
67
|
+
{ name: "unmatched", file: "file:///other.js", line: 1 },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
annotateFramesWithCounts(frames, result);
|
|
71
|
+
|
|
72
|
+
expect(frames[0].name).toBe("foo [10]");
|
|
73
|
+
expect(frames[1].name).toBe("bar [5]");
|
|
74
|
+
expect(frames[2].name).toBe("unmatched"); // no coverage data for this file
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("annotateFramesWithCounts falls back to name-only for frames without file", () => {
|
|
78
|
+
const result = buildCoverageMap(coverage, { "file:///test.js": source });
|
|
79
|
+
|
|
80
|
+
const frames = [
|
|
81
|
+
{ name: "foo" }, // no file — should match by name
|
|
82
|
+
{ name: "bar" },
|
|
83
|
+
{ name: "(anonymous)" }, // anonymous — should not match by name
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
annotateFramesWithCounts(frames, result);
|
|
87
|
+
|
|
88
|
+
expect(frames[0].name).toBe("foo [10]");
|
|
89
|
+
expect(frames[1].name).toBe("bar [5]");
|
|
90
|
+
expect(frames[2].name).toBe("(anonymous)");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("annotateFramesWithCounts formats large counts", () => {
|
|
94
|
+
const bigCoverage: CoverageData = {
|
|
95
|
+
scripts: [
|
|
96
|
+
{
|
|
97
|
+
url: "file:///big.js",
|
|
98
|
+
functions: [
|
|
99
|
+
{
|
|
100
|
+
functionName: "hot",
|
|
101
|
+
ranges: [{ startOffset: 0, endOffset: 10, count: 1_500_000 }],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
const result = buildCoverageMap(bigCoverage, {
|
|
108
|
+
"file:///big.js": "function hot() {}",
|
|
109
|
+
});
|
|
110
|
+
const frames = [{ name: "hot", file: "file:///big.js", line: 1 }];
|
|
111
|
+
|
|
112
|
+
annotateFramesWithCounts(frames, result);
|
|
113
|
+
|
|
114
|
+
expect(frames[0].name).toBe("hot [1.5M]");
|
|
115
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { withCoverageProfiling } from "../profiling/node/CoverageSampler.ts";
|
|
3
|
+
|
|
4
|
+
test("withCoverageProfiling returns function execution counts", async () => {
|
|
5
|
+
function hotFunction() {
|
|
6
|
+
let sum = 0;
|
|
7
|
+
for (let i = 0; i < 100; i++) sum += i;
|
|
8
|
+
return sum;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { result, coverage } = await withCoverageProfiling(_session => {
|
|
12
|
+
for (let i = 0; i < 10; i++) hotFunction();
|
|
13
|
+
return 42;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(result).toBe(42);
|
|
17
|
+
expect(coverage.scripts.length).toBeGreaterThan(0);
|
|
18
|
+
|
|
19
|
+
// Find our test file in the coverage data
|
|
20
|
+
const thisScript = coverage.scripts.find(s =>
|
|
21
|
+
s.url.includes("CoverageSampler.test"),
|
|
22
|
+
);
|
|
23
|
+
expect(thisScript).toBeDefined();
|
|
24
|
+
expect(thisScript!.functions.length).toBeGreaterThan(0);
|
|
25
|
+
|
|
26
|
+
// Find hotFunction and verify its count
|
|
27
|
+
const hotFn = thisScript!.functions.find(
|
|
28
|
+
f => f.functionName === "hotFunction",
|
|
29
|
+
);
|
|
30
|
+
expect(hotFn).toBeDefined();
|
|
31
|
+
const count = hotFn!.ranges[0].count;
|
|
32
|
+
expect(count).toBe(10);
|
|
33
|
+
});
|
|
@@ -2,12 +2,12 @@ import { expect, test } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
aggregateSites,
|
|
4
4
|
type HeapSite,
|
|
5
|
-
} from "../
|
|
5
|
+
} from "../profiling/node/HeapSampleReport.ts";
|
|
6
6
|
|
|
7
7
|
test("unknown column does not merge distinct functions on same line", () => {
|
|
8
8
|
const sites: HeapSite[] = [
|
|
9
|
-
{
|
|
10
|
-
{
|
|
9
|
+
{ name: "Foo", url: "test.ts", line: 10, col: undefined, bytes: 100 },
|
|
10
|
+
{ name: "Bar", url: "test.ts", line: 10, col: undefined, bytes: 200 },
|
|
11
11
|
];
|
|
12
12
|
const aggregated = aggregateSites(sites);
|
|
13
13
|
expect(aggregated).toHaveLength(2);
|
|
@@ -15,8 +15,8 @@ test("unknown column does not merge distinct functions on same line", () => {
|
|
|
15
15
|
|
|
16
16
|
test("same column merges regardless of function name", () => {
|
|
17
17
|
const sites: HeapSite[] = [
|
|
18
|
-
{
|
|
19
|
-
{
|
|
18
|
+
{ name: "Foo", url: "test.ts", line: 10, col: 5, bytes: 100 },
|
|
19
|
+
{ name: "Foo", url: "test.ts", line: 10, col: 5, bytes: 200 },
|
|
20
20
|
];
|
|
21
21
|
const aggregated = aggregateSites(sites);
|
|
22
22
|
expect(aggregated).toHaveLength(1);
|
|
@@ -25,18 +25,18 @@ test("same column merges regardless of function name", () => {
|
|
|
25
25
|
|
|
26
26
|
test("aggregation preserves distinct caller stacks", () => {
|
|
27
27
|
const stackA = [
|
|
28
|
-
{
|
|
29
|
-
{
|
|
30
|
-
{
|
|
28
|
+
{ name: "root", url: "a.ts", line: 1, col: 0 },
|
|
29
|
+
{ name: "foo", url: "a.ts", line: 10, col: 0 },
|
|
30
|
+
{ name: "alloc", url: "a.ts", line: 20, col: 5 },
|
|
31
31
|
];
|
|
32
32
|
const stackB = [
|
|
33
|
-
{
|
|
34
|
-
{
|
|
35
|
-
{
|
|
33
|
+
{ name: "root", url: "a.ts", line: 1, col: 0 },
|
|
34
|
+
{ name: "bar", url: "a.ts", line: 15, col: 0 },
|
|
35
|
+
{ name: "alloc", url: "a.ts", line: 20, col: 5 },
|
|
36
36
|
];
|
|
37
37
|
const sites: HeapSite[] = [
|
|
38
|
-
{
|
|
39
|
-
{
|
|
38
|
+
{ name: "alloc", url: "a.ts", line: 20, col: 5, bytes: 800, stack: stackA },
|
|
39
|
+
{ name: "alloc", url: "a.ts", line: 20, col: 5, bytes: 200, stack: stackB },
|
|
40
40
|
];
|
|
41
41
|
const aggregated = aggregateSites(sites);
|
|
42
42
|
|
|
@@ -44,7 +44,7 @@ test("aggregation preserves distinct caller stacks", () => {
|
|
|
44
44
|
expect(aggregated[0].bytes).toBe(1000);
|
|
45
45
|
expect(aggregated[0].callers).toHaveLength(2);
|
|
46
46
|
// Primary stack should be the highest-bytes path (foo)
|
|
47
|
-
expect(aggregated[0].stack![1].
|
|
47
|
+
expect(aggregated[0].stack![1].name).toBe("foo");
|
|
48
48
|
// Callers sorted by bytes descending
|
|
49
49
|
expect(aggregated[0].callers![0].bytes).toBe(800);
|
|
50
50
|
expect(aggregated[0].callers![1].bytes).toBe(200);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import type { BenchMatrix } from "../BenchMatrix.ts";
|
|
2
|
+
import type { BenchMatrix } from "../matrix/BenchMatrix.ts";
|
|
3
3
|
import { filterMatrix, parseMatrixFilter } from "../matrix/MatrixFilter.ts";
|
|
4
4
|
|
|
5
5
|
const inlineMatrix: BenchMatrix<string> = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import type { CaseResult, MatrixResults } from "../BenchMatrix.ts";
|
|
2
|
+
import type { CaseResult, MatrixResults } from "../matrix/BenchMatrix.ts";
|
|
3
3
|
import { reportMatrixResults } from "../matrix/MatrixReport.ts";
|
|
4
4
|
|
|
5
5
|
/** Create simple measured results for testing */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import { compareWithBaseline } from "../PermutationTest.ts";
|
|
2
|
+
import { compareWithBaseline } from "../stats/PermutationTest.ts";
|
|
3
3
|
import { assertValid, getSampleData } from "./TestUtils.ts";
|
|
4
4
|
|
|
5
5
|
test("detects 20% performance improvement", () => {
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
coefficientOfVariation,
|
|
5
5
|
medianAbsoluteDeviation,
|
|
6
6
|
percentile,
|
|
7
|
-
} from "../StatisticalUtils.ts";
|
|
7
|
+
} from "../stats/StatisticalUtils.ts";
|
|
8
8
|
import { bevy30SamplesMs, bevy30SamplesNs } from "./fixtures/bevy30-samples.ts";
|
|
9
9
|
|
|
10
|
-
test("bevy30 data characteristics", () => {
|
|
10
|
+
test.skip("bevy30 data characteristics", () => {
|
|
11
11
|
const sortedMs = [...bevy30SamplesMs].sort((a, b) => a - b);
|
|
12
12
|
|
|
13
13
|
const stats = {
|
|
@@ -48,7 +48,7 @@ test("bevy30 data characteristics", () => {
|
|
|
48
48
|
if (stats.cv > 0.5) console.warn("Very high variation - may be unstable");
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test("convergence at different time points matches CLI behavior", () => {
|
|
51
|
+
test.skip("convergence at different time points matches CLI behavior", () => {
|
|
52
52
|
// Simulate 5-second run (approximately 100 samples at ~50ms each)
|
|
53
53
|
const samples5s = bevy30SamplesNs.slice(0, 100);
|
|
54
54
|
const result5s = checkConvergence(samples5s);
|
|
@@ -76,7 +76,7 @@ test("convergence at different time points matches CLI behavior", () => {
|
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
test("warm-up detection in real data", () => {
|
|
79
|
+
test.skip("warm-up detection in real data", () => {
|
|
80
80
|
const windowSize = 20;
|
|
81
81
|
const windows: Array<{ start: number; median: number }> = [];
|
|
82
82
|
|
|
@@ -104,7 +104,7 @@ test("warm-up detection in real data", () => {
|
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
-
test("convergence stability over sliding windows", () => {
|
|
107
|
+
test.skip("convergence stability over sliding windows", () => {
|
|
108
108
|
const windowSize = 100;
|
|
109
109
|
const step = 50;
|
|
110
110
|
const history: Array<{ start: number; confidence: number }> = [];
|
|
@@ -132,7 +132,7 @@ test("convergence stability over sliding windows", () => {
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
test("adaptive algorithm would stop at correct time", () => {
|
|
135
|
+
test.skip("adaptive algorithm would stop at correct time", () => {
|
|
136
136
|
const target = 95;
|
|
137
137
|
const fallback = 80;
|
|
138
138
|
const minSamples = 50;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { expect, test } from "vitest";
|
|
4
|
-
import type { BenchSuite } from "../Benchmark.ts";
|
|
5
4
|
import { filterBenchmarks } from "../cli/FilterBenchmarks.ts";
|
|
5
|
+
import type { BenchSuite } from "../runners/BenchmarkSpec.ts";
|
|
6
6
|
import { runBenchCLITest } from "./TestUtils.ts";
|
|
7
7
|
|
|
8
8
|
const testSuite: BenchSuite = {
|
|
@@ -68,7 +68,7 @@ function executeBenchforgeFile(file: string, args = ""): string {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
test("runs all benchmarks", { timeout: 30000 }, async () => {
|
|
71
|
-
const output = await runBenchCLITest(testSuite, "--
|
|
71
|
+
const output = await runBenchCLITest(testSuite, "--duration 0.1");
|
|
72
72
|
|
|
73
73
|
expect(output).toContain("concatenation");
|
|
74
74
|
expect(output).toContain("template literal");
|
|
@@ -79,7 +79,10 @@ test("runs all benchmarks", { timeout: 30000 }, async () => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
test("filters by substring", { timeout: 15000 }, async () => {
|
|
82
|
-
const output = await runBenchCLITest(
|
|
82
|
+
const output = await runBenchCLITest(
|
|
83
|
+
testSuite,
|
|
84
|
+
"--filter concat --duration 0.1",
|
|
85
|
+
);
|
|
83
86
|
|
|
84
87
|
expect(output).toContain("concatenation");
|
|
85
88
|
expect(output).not.toContain("addition");
|
|
@@ -88,7 +91,7 @@ test("filters by substring", { timeout: 15000 }, async () => {
|
|
|
88
91
|
test("filters by regex", { timeout: 15000 }, async () => {
|
|
89
92
|
const output = await runBenchCLITest(
|
|
90
93
|
testSuite,
|
|
91
|
-
"--filter ^template --
|
|
94
|
+
"--filter ^template --duration 0.1",
|
|
92
95
|
);
|
|
93
96
|
expect(output).toContain("template literal");
|
|
94
97
|
expect(output).not.toContain("addition");
|
|
@@ -106,7 +109,7 @@ test("filter preserves suite structure", () => {
|
|
|
106
109
|
});
|
|
107
110
|
|
|
108
111
|
test("e2e: runs user script", { timeout: 30000 }, () => {
|
|
109
|
-
const output = executeTestScript("--
|
|
112
|
+
const output = executeTestScript("--duration 0.1");
|
|
110
113
|
|
|
111
114
|
expect(output).toContain("plus");
|
|
112
115
|
expect(output).toContain("multiply");
|
|
@@ -122,14 +125,14 @@ test("e2e: runs user script", { timeout: 30000 }, () => {
|
|
|
122
125
|
});
|
|
123
126
|
|
|
124
127
|
test("e2e: filter flag", { timeout: 30000 }, () => {
|
|
125
|
-
const output = executeTestScript('--filter "plus" --
|
|
128
|
+
const output = executeTestScript('--filter "plus" --duration 0.1');
|
|
126
129
|
|
|
127
130
|
expect(output).toContain("plus");
|
|
128
131
|
expect(output).not.toContain("multiply");
|
|
129
132
|
});
|
|
130
133
|
|
|
131
134
|
test("runs benchmarks with setup function", { timeout: 30000 }, async () => {
|
|
132
|
-
const output = await runBenchCLITest(suiteWithSetup, "--
|
|
135
|
+
const output = await runBenchCLITest(suiteWithSetup, "--duration 0.1");
|
|
133
136
|
|
|
134
137
|
expect(output).toContain("sum numbers");
|
|
135
138
|
expect(output).toContain("join strings");
|
|
@@ -137,40 +140,38 @@ test("runs benchmarks with setup function", { timeout: 30000 }, async () => {
|
|
|
137
140
|
expect(output).toContain("runs");
|
|
138
141
|
});
|
|
139
142
|
|
|
140
|
-
test(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
fn: ({ data }: any) => [...data].sort(),
|
|
155
|
-
},
|
|
156
|
-
benchmarks: [
|
|
157
|
-
{
|
|
158
|
-
name: "optimized sort",
|
|
159
|
-
fn: ({ data }: any) => [...data].sort((a, b) => a - b),
|
|
160
|
-
},
|
|
161
|
-
],
|
|
143
|
+
test("runs benchmarks with baseline comparison", {
|
|
144
|
+
timeout: 30000,
|
|
145
|
+
}, async () => {
|
|
146
|
+
const suiteWithBaseline: BenchSuite = {
|
|
147
|
+
name: "Baseline Test",
|
|
148
|
+
groups: [
|
|
149
|
+
{
|
|
150
|
+
name: "Sort Comparison",
|
|
151
|
+
setup: () => ({
|
|
152
|
+
data: Array.from({ length: 10 }, () => Math.random()),
|
|
153
|
+
}),
|
|
154
|
+
baseline: {
|
|
155
|
+
name: "baseline sort",
|
|
156
|
+
fn: ({ data }: any) => [...data].sort(),
|
|
162
157
|
},
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
benchmarks: [
|
|
159
|
+
{
|
|
160
|
+
name: "optimized sort",
|
|
161
|
+
fn: ({ data }: any) => [...data].sort((a, b) => a - b),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
const output = await runBenchCLITest(suiteWithBaseline, "--iterations 20");
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
);
|
|
170
|
+
expect(output).toContain("baseline sort");
|
|
171
|
+
expect(output).toContain("optimized sort");
|
|
172
|
+
expect(output).toContain("Δ%"); // Diff column should appear
|
|
173
|
+
expect(output).toContain("mean");
|
|
174
|
+
});
|
|
174
175
|
|
|
175
176
|
test("file mode: BenchSuite export", { timeout: 30000 }, () => {
|
|
176
177
|
const output = executeBenchforgeFile(
|