Skip to content

Commit 5cc7864

Browse files
authored
fix: improve validation for custom benchmarks (#323)
* add Zod schema for BenchmarkResult * parse custom benchmark output to make sure it conforms to schema * allow for coercion
1 parent 65914f2 commit 5cc7864

File tree

7 files changed

+143
-39
lines changed

7 files changed

+143
-39
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## Unreleased
22

3+
- **fix** improve parsing for custom benchmarks (#323)
4+
35
<a name="v1.20.5"></a>
46
# [v1.20.5](https://github.com/benchmark-action/github-action-benchmark/releases/tag/v1.20.5) - 02 Sep 2025
57
- **feat** allow to parse generic cargo bench/criterion units (#280)

package-lock.json

Lines changed: 50 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"@actions/core": "^1.10.0",
3535
"@actions/exec": "^1.1.1",
3636
"@actions/github": "^5.1.1",
37-
"@actions/io": "^1.1.2"
37+
"@actions/io": "^1.1.2",
38+
"zod": "^4.1.5"
3839
},
3940
"devDependencies": {
4041
"@types/acorn": "^4.0.5",
@@ -43,7 +44,7 @@
4344
"@types/deep-equal": "^1.0.1",
4445
"@types/jest": "^29.5.11",
4546
"@types/markdown-it": "^12.2.3",
46-
"@types/node": "^13.9.1",
47+
"@types/node": "^24.3.1",
4748
"@types/rimraf": "^2.0.3",
4849
"@typescript-eslint/eslint-plugin": "^5.4.0",
4950
"@typescript-eslint/parser": "^5.4.0",
@@ -61,6 +62,6 @@
6162
"prettier": "^2.4.1",
6263
"rimraf": "^3.0.2",
6364
"ts-jest": "^29.1.2",
64-
"typescript": "^4.5.2"
65+
"typescript": "^5.9.2"
6566
}
6667
}

src/extract.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
import { promises as fs } from 'fs';
33
import * as github from '@actions/github';
44
import { Config, ToolType } from './config';
5+
import { z } from 'zod';
56

6-
export interface BenchmarkResult {
7-
name: string;
8-
value: number;
9-
range?: string;
10-
unit: string;
11-
extra?: string;
12-
}
7+
export const BenchmarkResult = z.object({
8+
name: z.coerce.string(),
9+
value: z.coerce.number(),
10+
range: z.coerce.string().optional(),
11+
unit: z.coerce.string(),
12+
extra: z.coerce.string().optional(),
13+
});
14+
15+
export type BenchmarkResult = z.infer<typeof BenchmarkResult>;
16+
17+
export const BenchmarkResults = z.array(BenchmarkResult);
1318

1419
interface GitHubUser {
1520
email?: string;
@@ -658,13 +663,11 @@ function extractBenchmarkDotnetResult(output: string): BenchmarkResult[] {
658663

659664
function extractCustomBenchmarkResult(output: string): BenchmarkResult[] {
660665
try {
661-
const json: BenchmarkResult[] = JSON.parse(output);
662-
return json.map(({ name, value, unit, range, extra }) => {
663-
return { name, value, unit, range, extra };
664-
});
665-
} catch (err: any) {
666+
return BenchmarkResults.parse(JSON.parse(output));
667+
} catch (err: unknown) {
668+
const errMessage = err instanceof Error ? err.message : String(err);
666669
throw new Error(
667-
`Output file for 'custom-(bigger|smaller)-is-better' must be JSON file containing an array of entries in BenchmarkResult format: ${err.message}`,
670+
`Output file for 'custom-(bigger|smaller)-is-better' must be JSON file containing an array of entries in BenchmarkResult format: ${errMessage}`,
668671
);
669672
}
670673
}
@@ -673,7 +676,6 @@ function extractLuauBenchmarkResult(output: string): BenchmarkResult[] {
673676
const lines = output.split(/\n/);
674677
const results: BenchmarkResult[] = [];
675678

676-
output;
677679
for (const line of lines) {
678680
if (!line.startsWith('SUCCESS')) continue;
679681
const [_0, name, _2, valueStr, _4, range, _6, extra] = line.split(/\s+/);

test/__snapshots__/extract.spec.ts.snap

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,9 +427,7 @@ exports[`extractResult() extracts benchmark output from customBiggerIsBetter - c
427427
{
428428
"benches": [
429429
{
430-
"extra": undefined,
431430
"name": "My Custom Bigger Is Better Benchmark - Throughput",
432-
"range": undefined,
433431
"unit": "req/s",
434432
"value": 70,
435433
},
@@ -467,9 +465,7 @@ exports[`extractResult() extracts benchmark output from customSmallerIsBetter -
467465
"value": 50,
468466
},
469467
{
470-
"extra": undefined,
471468
"name": "My Custom Smaller Is Better Benchmark - Memory Used",
472-
"range": undefined,
473469
"unit": "Megabytes",
474470
"value": 100,
475471
},
@@ -487,6 +483,47 @@ exports[`extractResult() extracts benchmark output from customSmallerIsBetter -
487483
}
488484
`;
489485

486+
exports[`extractResult() extracts benchmark output from customSmallerIsBetter - customSmallerIsBetter_output2.json 1`] = `
487+
{
488+
"benches": [
489+
{
490+
"name": "No instrumentation",
491+
"range": "0.519165",
492+
"unit": "ns",
493+
"value": 90.9439,
494+
},
495+
{
496+
"name": "Deactivated probe",
497+
"range": "8.21371",
498+
"unit": "ns",
499+
"value": 445.661,
500+
},
501+
{
502+
"name": "No logging",
503+
"range": "10.2008",
504+
"unit": "ns",
505+
"value": 1847.38,
506+
},
507+
{
508+
"name": "Binary file",
509+
"range": "87.1657",
510+
"unit": "ns",
511+
"value": 3886.75,
512+
},
513+
],
514+
"commit": {
515+
"author": null,
516+
"committer": null,
517+
"id": "123456789abcdef",
518+
"message": "this is dummy",
519+
"timestamp": "dummy timestamp",
520+
"url": "https://github.com/dummy/repo",
521+
},
522+
"date": 1712131503296,
523+
"tool": "customSmallerIsBetter",
524+
}
525+
`;
526+
490527
exports[`extractResult() extracts benchmark output from go - go_fiber_output.txt 1`] = `
491528
{
492529
"benches": [
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"name": "No instrumentation",
4+
"unit": "ns",
5+
"value": 90.9439,
6+
"range": 0.519165
7+
},
8+
{
9+
"name": "Deactivated probe",
10+
"unit": "ns",
11+
"value": 445.661,
12+
"range": 8.21371
13+
},
14+
{
15+
"name": "No logging",
16+
"unit": "ns",
17+
"value": 1847.38,
18+
"range": 10.2008
19+
},
20+
{
21+
"name": "Binary file",
22+
"unit": "ns",
23+
"value": 3886.75,
24+
"range": 87.1657
25+
}
26+
]

test/extract.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ describe('extractResult()', function () {
143143
tool: 'customSmallerIsBetter',
144144
file: 'customSmallerIsBetter_output.json',
145145
},
146+
{
147+
tool: 'customSmallerIsBetter',
148+
file: 'customSmallerIsBetter_output2.json',
149+
},
146150
];
147151

148152
it.each(normalCases)(`extracts benchmark output from $tool - $file`, async function (test) {

0 commit comments

Comments
 (0)