From 69ea6d4d8fce5b1a77ab160dc0a0ff711d347274 Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Thu, 8 Jan 2026 14:06:06 +0100 Subject: [PATCH 1/9] fix: include package name for duplicate bench names [#264] * add test output --- .gitignore | 1 + .../go_fiber_duplicate_names_output.txt | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 test/data/extract/go_fiber_duplicate_names_output.txt diff --git a/.gitignore b/.gitignore index 9578ebeea..993e966be 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /coverage /dist /.idea +/.ai target/ jmh-result.json .env diff --git a/test/data/extract/go_fiber_duplicate_names_output.txt b/test/data/extract/go_fiber_duplicate_names_output.txt new file mode 100644 index 000000000..d686b064c --- /dev/null +++ b/test/data/extract/go_fiber_duplicate_names_output.txt @@ -0,0 +1,27 @@ +PASS +ok github.com/gofiber/fiber/v3/log 0.173s +PASS +ok github.com/gofiber/fiber/v3/middleware/adaptor 0.184s +PASS +ok github.com/gofiber/fiber/v3/middleware/basicauth 0.173s +goos: darwin +goarch: arm64 +pkg: github.com/gofiber/fiber/v3/middleware/cache +BenchmarkAppendMsgitem +BenchmarkAppendMsgitem-12 63634455 19.01 ns/op 3103.57 MB/s 0 B/op 0 allocs/op +BenchmarkAppendMsgitem-12 66411781 18.42 ns/op 3202.97 MB/s 0 B/op 0 allocs/op +PASS +ok github.com/gofiber/fiber/v3/middleware/cache 2.649s +PASS +ok github.com/gofiber/fiber/v3/middleware/compress 0.219s +PASS +ok github.com/gofiber/fiber/v3/middleware/cors 0.188s +goos: darwin +goarch: arm64 +pkg: github.com/gofiber/fiber/v3/middleware/csrf +BenchmarkAppendMsgitem +BenchmarkAppendMsgitem-12 1000000000 0.2926 ns/op 3417.23 MB/s 0 B/op 0 allocs/op +BenchmarkAppendMsgitem-12 1000000000 0.2883 ns/op 3468.54 MB/s 0 B/op 0 allocs/op +PASS +ok github.com/gofiber/fiber/v3/middleware/csrf 0.842s +PASS From 7fe60a9758959145a449e2cc54cf2a9ffdd56f16 Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 21:18:09 +0100 Subject: [PATCH 2/9] fix: include package name for duplicate bench names [#264] * add test file with a snapshot * add test output --- test/__snapshots__/extract.spec.ts.snap | 157 ++++++++++++++++++ .../go_fiber_duplicate_names_output.txt | 20 +-- test/extract.spec.ts | 4 + 3 files changed, 171 insertions(+), 10 deletions(-) diff --git a/test/__snapshots__/extract.spec.ts.snap b/test/__snapshots__/extract.spec.ts.snap index 720bfa5f5..6666d6549 100644 --- a/test/__snapshots__/extract.spec.ts.snap +++ b/test/__snapshots__/extract.spec.ts.snap @@ -524,6 +524,163 @@ exports[`extractResult() extracts benchmark output from customSmallerIsBetter - } `; +exports[`extractResult() extracts benchmark output from go - go_fiber_duplicate_names_output.txt 1`] = ` +{ + "benches": [ + { + "extra": "21228057 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/cache)", + "unit": "ns/op 2833.37 MB/s 0 B/op 0 allocs/op", + "value": 55.76, + }, + { + "extra": "21228057 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/cache) - ns/op", + "unit": "ns/op", + "value": 55.76, + }, + { + "extra": "21228057 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/cache) - MB/s", + "unit": "MB/s", + "value": 2833.37, + }, + { + "extra": "21228057 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/cache) - B/op", + "unit": "B/op", + "value": 0, + }, + { + "extra": "21228057 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/cache) - allocs/op", + "unit": "allocs/op", + "value": 0, + }, + { + "extra": "6855655 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/cache)", + "unit": "ns/op 907.83 MB/s 0 B/op 0 allocs/op", + "value": 174, + }, + { + "extra": "6855655 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/cache) - ns/op", + "unit": "ns/op", + "value": 174, + }, + { + "extra": "6855655 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/cache) - MB/s", + "unit": "MB/s", + "value": 907.83, + }, + { + "extra": "6855655 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/cache) - B/op", + "unit": "B/op", + "value": 0, + }, + { + "extra": "6855655 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/cache) - allocs/op", + "unit": "allocs/op", + "value": 0, + }, + { + "extra": "1000000000 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/csrf)", + "unit": "ns/op 2678.46 MB/s 0 B/op 0 allocs/op", + "value": 0.3733, + }, + { + "extra": "1000000000 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/csrf) - ns/op", + "unit": "ns/op", + "value": 0.3733, + }, + { + "extra": "1000000000 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/csrf) - MB/s", + "unit": "MB/s", + "value": 2678.46, + }, + { + "extra": "1000000000 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/csrf) - B/op", + "unit": "B/op", + "value": 0, + }, + { + "extra": "1000000000 times +4 procs", + "name": "BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/csrf) - allocs/op", + "unit": "allocs/op", + "value": 0, + }, + { + "extra": "275056135 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/csrf)", + "unit": "ns/op 229.35 MB/s 0 B/op 0 allocs/op", + "value": 4.36, + }, + { + "extra": "275056135 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/csrf) - ns/op", + "unit": "ns/op", + "value": 4.36, + }, + { + "extra": "275056135 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/csrf) - MB/s", + "unit": "MB/s", + "value": 229.35, + }, + { + "extra": "275056135 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/csrf) - B/op", + "unit": "B/op", + "value": 0, + }, + { + "extra": "275056135 times +4 procs", + "name": "BenchmarkUnmarshalitem (github.com/gofiber/fiber/v3/middleware/csrf) - allocs/op", + "unit": "allocs/op", + "value": 0, + }, + ], + "commit": { + "author": null, + "committer": null, + "id": "123456789abcdef", + "message": "this is dummy", + "timestamp": "dummy timestamp", + "url": "https://github.com/dummy/repo", + }, + "date": 1712131503296, + "tool": "go", +} +`; + exports[`extractResult() extracts benchmark output from go - go_fiber_output.txt 1`] = ` { "benches": [ diff --git a/test/data/extract/go_fiber_duplicate_names_output.txt b/test/data/extract/go_fiber_duplicate_names_output.txt index d686b064c..343e04d37 100644 --- a/test/data/extract/go_fiber_duplicate_names_output.txt +++ b/test/data/extract/go_fiber_duplicate_names_output.txt @@ -4,24 +4,24 @@ PASS ok github.com/gofiber/fiber/v3/middleware/adaptor 0.184s PASS ok github.com/gofiber/fiber/v3/middleware/basicauth 0.173s -goos: darwin -goarch: arm64 +goos: linux +goarch: amd64 pkg: github.com/gofiber/fiber/v3/middleware/cache -BenchmarkAppendMsgitem -BenchmarkAppendMsgitem-12 63634455 19.01 ns/op 3103.57 MB/s 0 B/op 0 allocs/op -BenchmarkAppendMsgitem-12 66411781 18.42 ns/op 3202.97 MB/s 0 B/op 0 allocs/op +cpu: AMD EPYC 7763 64-Core Processor +BenchmarkAppendMsgitem-4 21228057 55.76 ns/op 2833.37 MB/s 0 B/op 0 allocs/op +BenchmarkUnmarshalitem-4 6855655 174.0 ns/op 907.83 MB/s 0 B/op 0 allocs/op PASS ok github.com/gofiber/fiber/v3/middleware/cache 2.649s PASS ok github.com/gofiber/fiber/v3/middleware/compress 0.219s PASS ok github.com/gofiber/fiber/v3/middleware/cors 0.188s -goos: darwin -goarch: arm64 +goos: linux +goarch: amd64 pkg: github.com/gofiber/fiber/v3/middleware/csrf -BenchmarkAppendMsgitem -BenchmarkAppendMsgitem-12 1000000000 0.2926 ns/op 3417.23 MB/s 0 B/op 0 allocs/op -BenchmarkAppendMsgitem-12 1000000000 0.2883 ns/op 3468.54 MB/s 0 B/op 0 allocs/op +cpu: AMD EPYC 7763 64-Core Processor +BenchmarkAppendMsgitem-4 1000000000 0.3733 ns/op 2678.46 MB/s 0 B/op 0 allocs/op +BenchmarkUnmarshalitem-4 275056135 4.360 ns/op 229.35 MB/s 0 B/op 0 allocs/op PASS ok github.com/gofiber/fiber/v3/middleware/csrf 0.842s PASS diff --git a/test/extract.spec.ts b/test/extract.spec.ts index f07a81d53..8184b4a8d 100644 --- a/test/extract.spec.ts +++ b/test/extract.spec.ts @@ -95,6 +95,10 @@ describe('extractResult()', function () { tool: 'go', file: 'go_fiber_output.txt', }, + { + tool: 'go', + file: 'go_fiber_duplicate_names_output.txt', + }, { tool: 'benchmarkjs', file: 'benchmarkjs_output.txt', From 143085824b06a25381ba40b92352b0b3dd2689ca Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 21:22:15 +0100 Subject: [PATCH 3/9] fix: include package name for duplicate bench names [#264] * add test for backwards compatibility * add test output --- test/__snapshots__/extract.spec.ts.snap | 31 +++++++++++++++++++ .../data/extract/go_single_package_output.txt | 9 ++++++ test/extract.spec.ts | 4 +++ 3 files changed, 44 insertions(+) create mode 100644 test/data/extract/go_single_package_output.txt diff --git a/test/__snapshots__/extract.spec.ts.snap b/test/__snapshots__/extract.spec.ts.snap index 6666d6549..9f54fb7f8 100644 --- a/test/__snapshots__/extract.spec.ts.snap +++ b/test/__snapshots__/extract.spec.ts.snap @@ -963,6 +963,37 @@ exports[`extractResult() extracts benchmark output from go - go_output.txt 1`] = } `; +exports[`extractResult() extracts benchmark output from go - go_single_package_output.txt 1`] = ` +{ + "benches": [ + { + "extra": "5000000 times +8 procs", + "name": "BenchmarkFib10", + "unit": "ns/op", + "value": 325, + }, + { + "extra": "30000 times +8 procs", + "name": "BenchmarkFib20", + "unit": "ns/op", + "value": 40537, + }, + ], + "commit": { + "author": null, + "committer": null, + "id": "123456789abcdef", + "message": "this is dummy", + "timestamp": "dummy timestamp", + "url": "https://github.com/dummy/repo", + }, + "date": 1712131503296, + "tool": "go", +} +`; + exports[`extractResult() extracts benchmark output from googlecpp - googlecpp_output.json 1`] = ` { "benches": [ diff --git a/test/data/extract/go_single_package_output.txt b/test/data/extract/go_single_package_output.txt new file mode 100644 index 000000000..d276db6ef --- /dev/null +++ b/test/data/extract/go_single_package_output.txt @@ -0,0 +1,9 @@ +goos: darwin +goarch: arm64 +pkg: github.com/example/mypackage +BenchmarkFib10 +BenchmarkFib10-8 5000000 325 ns/op +BenchmarkFib20 +BenchmarkFib20-8 30000 40537 ns/op +PASS +ok github.com/example/mypackage 3.614s diff --git a/test/extract.spec.ts b/test/extract.spec.ts index 8184b4a8d..631b64f43 100644 --- a/test/extract.spec.ts +++ b/test/extract.spec.ts @@ -99,6 +99,10 @@ describe('extractResult()', function () { tool: 'go', file: 'go_fiber_duplicate_names_output.txt', }, + { + tool: 'go', + file: 'go_single_package_output.txt', + }, { tool: 'benchmarkjs', file: 'benchmarkjs_output.txt', From 34121ba7c140be87822e8ff12970698b0aa9fd5f Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 21:27:08 +0100 Subject: [PATCH 4/9] fix: include package name for duplicate bench names [#264] * initial implementation * add test output --- package.json | 1 + src/extract.ts | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a2c9472cb..fe40e2973 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "format": "prettier -w **", "format:check": "prettier -c **", "test": "jest", + "test-s": "jest --silent", "test:watch": "jest --watch", "coverage": "jest --coverage", "coverage:open": "jest --coverage && open ./coverage/lcov-report/index.html" diff --git a/src/extract.ts b/src/extract.ts index 60736e61c..163504f13 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -359,7 +359,28 @@ function extractGoResult(output: string): BenchmarkResult[] { const reExtract = /^(?Benchmark\w+[\w()$%^&*-=|,[\]{}"#]*?)(?-\d+)?\s+(?\d+)\s+(?.+)$/; + // First pass: detect all unique packages + // This is used to determine if we need to append package names to benchmark names for disambiguation + const packageRegex = /^pkg:\s+(.+)$/; + const packages = new Set(); for (const line of lines) { + const pkgMatch = line.match(packageRegex); + if (pkgMatch) { + packages.add(pkgMatch[1]); + } + } + const hasMultiplePackages = packages.size > 1; + + // Second pass: extract benchmarks with package context + let currentPackage = ''; + for (const line of lines) { + // Track current package from "pkg:" lines + const pkgMatch = line.match(packageRegex); + if (pkgMatch) { + currentPackage = pkgMatch[1]; + continue; + } + const m = line.match(reExtract); if (m?.groups) { const procs = m.groups.procs !== undefined ? m.groups.procs.slice(1) : null; @@ -374,6 +395,10 @@ function extractGoResult(output: string): BenchmarkResult[] { pieces.unshift(pieces[0], remainder.slice(remainder.indexOf(pieces[1]))); } + // Build base benchmark name with optional package suffix for disambiguation + const baseName = + hasMultiplePackages && currentPackage ? `${m.groups.name} (${currentPackage})` : m.groups.name; + for (let i = 0; i < pieces.length; i = i + 2) { let extra = `${times} times`.replace(/\s\s+/g, ' '); if (procs !== null) { @@ -383,9 +408,9 @@ function extractGoResult(output: string): BenchmarkResult[] { const unit = pieces[i + 1]; let name; if (i > 0) { - name = m.groups.name + ' - ' + unit; + name = baseName + ' - ' + unit; } else { - name = m.groups.name; + name = baseName; } ret.push({ name, value, unit, extra }); } From cb8cc78cbbf62dd890ff38425642e756e5180dee Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 21:43:15 +0100 Subject: [PATCH 5/9] fix: include package name for duplicate bench names [#264] * add more granular tests --- src/extract.ts | 2 +- test/extractGoResult.spec.ts | 254 +++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 test/extractGoResult.spec.ts diff --git a/src/extract.ts b/src/extract.ts index 163504f13..f36eed6c2 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -343,7 +343,7 @@ function extractCargoResult(output: string): BenchmarkResult[] { return ret; } -function extractGoResult(output: string): BenchmarkResult[] { +export function extractGoResult(output: string): BenchmarkResult[] { const lines = output.split(/\r?\n/g); const ret = []; // Example: diff --git a/test/extractGoResult.spec.ts b/test/extractGoResult.spec.ts new file mode 100644 index 000000000..f2e2a9eae --- /dev/null +++ b/test/extractGoResult.spec.ts @@ -0,0 +1,254 @@ +import { extractGoResult } from '../src/extract'; +import dedent from 'dedent'; + +describe('extractGoResult()', () => { + describe('basic benchmark extraction', () => { + it('extracts a simple benchmark result', () => { + const output = `BenchmarkFib10-8 5000000 325 ns/op`; + + const results = extractGoResult(output); + + expect(results).toHaveLength(1); + expect(results[0]).toEqual({ + name: 'BenchmarkFib10', + value: 325, + unit: 'ns/op', + extra: '5000000 times\n8 procs', + }); + }); + + it('extracts benchmark without processor count', () => { + const output = `BenchmarkFib10 5000000 325 ns/op`; + + const results = extractGoResult(output); + + expect(results).toHaveLength(1); + expect(results[0]).toEqual({ + name: 'BenchmarkFib10', + value: 325, + unit: 'ns/op', + extra: '5000000 times', + }); + }); + + it('extracts multiple benchmarks', () => { + const output = dedent` + BenchmarkFib10-8 5000000 325 ns/op + BenchmarkFib20-8 30000 40537 ns/op + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(2); + expect(results[0].name).toBe('BenchmarkFib10'); + expect(results[1].name).toBe('BenchmarkFib20'); + }); + + it('handles benchmarks with special characters in name', () => { + const output = `BenchmarkFib/my/tabled/benchmark_-_20,var1=13,var2=14-8 5000000 325 ns/op`; + + const results = extractGoResult(output); + + expect(results).toHaveLength(1); + expect(results[0].name).toBe('BenchmarkFib/my/tabled/benchmark_-_20,var1=13,var2=14'); + }); + }); + + describe('multiple metrics per benchmark', () => { + it('extracts all metrics from a benchmark with multiple values', () => { + const output = `BenchmarkAlloc-8 1000000 1024 ns/op 512 B/op 8 allocs/op`; + + const results = extractGoResult(output); + + expect(results).toHaveLength(4); + // First entry is the combined metrics (backward compatibility) + expect(results[0].name).toBe('BenchmarkAlloc'); + expect(results[0].unit).toContain('ns/op'); + // Second entry is ns/op metric + expect(results[1].name).toBe('BenchmarkAlloc - ns/op'); + expect(results[1].value).toBe(1024); + expect(results[1].unit).toBe('ns/op'); + // Third entry is B/op metric + expect(results[2].name).toBe('BenchmarkAlloc - B/op'); + expect(results[2].value).toBe(512); + expect(results[2].unit).toBe('B/op'); + // Fourth entry is allocs/op metric + expect(results[3].name).toBe('BenchmarkAlloc - allocs/op'); + expect(results[3].value).toBe(8); + expect(results[3].unit).toBe('allocs/op'); + }); + }); + + describe('single package (backward compatibility)', () => { + it('does not add package suffix when only one package exists', () => { + const output = dedent` + goos: darwin + goarch: arm64 + pkg: github.com/example/mypackage + BenchmarkFib10 + BenchmarkFib10-8 5000000 325 ns/op + BenchmarkFib20 + BenchmarkFib20-8 30000 40537 ns/op + PASS + ok github.com/example/mypackage 3.614s + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(2); + expect(results[0].name).toBe('BenchmarkFib10'); + expect(results[1].name).toBe('BenchmarkFib20'); + }); + + it('does not add package suffix when no pkg lines exist', () => { + const output = dedent` + BenchmarkFib10-8 5000000 325 ns/op + BenchmarkFib20-8 30000 40537 ns/op + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(2); + expect(results[0].name).toBe('BenchmarkFib10'); + expect(results[1].name).toBe('BenchmarkFib20'); + }); + }); + + describe('multiple packages (issue #264)', () => { + it('adds package suffix when multiple packages have benchmarks', () => { + const output = dedent` + goos: darwin + goarch: arm64 + pkg: github.com/example/package1 + BenchmarkFoo + BenchmarkFoo-8 5000000 100 ns/op + pkg: github.com/example/package2 + BenchmarkBar + BenchmarkBar-8 3000000 200 ns/op + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(2); + expect(results[0].name).toBe('BenchmarkFoo (github.com/example/package1)'); + expect(results[1].name).toBe('BenchmarkBar (github.com/example/package2)'); + }); + + it('disambiguates benchmarks with the same name in different packages', () => { + const output = dedent` + goos: darwin + goarch: arm64 + pkg: github.com/gofiber/fiber/v3/middleware/cache + BenchmarkAppendMsgitem + BenchmarkAppendMsgitem-12 63634455 19.01 ns/op + pkg: github.com/gofiber/fiber/v3/middleware/csrf + BenchmarkAppendMsgitem + BenchmarkAppendMsgitem-12 1000000000 0.2926 ns/op + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(2); + expect(results[0].name).toBe('BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/cache)'); + expect(results[0].value).toBe(19.01); + expect(results[1].name).toBe('BenchmarkAppendMsgitem (github.com/gofiber/fiber/v3/middleware/csrf)'); + expect(results[1].value).toBe(0.2926); + }); + + it('applies package suffix to all metrics when multiple packages exist', () => { + const output = dedent` + pkg: github.com/example/pkg1 + BenchmarkAlloc-8 1000000 100 ns/op 512 B/op 4 allocs/op + pkg: github.com/example/pkg2 + BenchmarkAlloc-8 2000000 200 ns/op 256 B/op 2 allocs/op + `; + + const results = extractGoResult(output); + + // Each benchmark produces 4 entries (combined + ns/op + B/op + allocs/op) + expect(results).toHaveLength(8); + + // First package benchmarks + expect(results[0].name).toBe('BenchmarkAlloc (github.com/example/pkg1)'); + expect(results[1].name).toBe('BenchmarkAlloc (github.com/example/pkg1) - ns/op'); + expect(results[2].name).toBe('BenchmarkAlloc (github.com/example/pkg1) - B/op'); + expect(results[3].name).toBe('BenchmarkAlloc (github.com/example/pkg1) - allocs/op'); + + // Second package benchmarks + expect(results[4].name).toBe('BenchmarkAlloc (github.com/example/pkg2)'); + expect(results[5].name).toBe('BenchmarkAlloc (github.com/example/pkg2) - ns/op'); + expect(results[6].name).toBe('BenchmarkAlloc (github.com/example/pkg2) - B/op'); + expect(results[7].name).toBe('BenchmarkAlloc (github.com/example/pkg2) - allocs/op'); + }); + }); + + describe('edge cases', () => { + it('handles benchmarks before any pkg line in multi-package output', () => { + const output = dedent` + BenchmarkOrphan-8 1000000 50 ns/op + pkg: github.com/example/pkg1 + BenchmarkFoo-8 5000000 100 ns/op + pkg: github.com/example/pkg2 + BenchmarkBar-8 3000000 200 ns/op + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(3); + // Orphan benchmark has no package context, so no suffix even though multiple packages exist + expect(results[0].name).toBe('BenchmarkOrphan'); + expect(results[1].name).toBe('BenchmarkFoo (github.com/example/pkg1)'); + expect(results[2].name).toBe('BenchmarkBar (github.com/example/pkg2)'); + }); + + it('returns empty array for output with no benchmarks', () => { + const output = dedent` + goos: darwin + goarch: arm64 + PASS + ok github.com/example/mypackage 0.001s + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(0); + }); + + it('returns empty array for empty input', () => { + const results = extractGoResult(''); + + expect(results).toHaveLength(0); + }); + + it('handles Windows line endings', () => { + const output = + 'pkg: github.com/example/pkg1\r\n' + + 'BenchmarkFoo-8 5000000 100 ns/op\r\n' + + 'pkg: github.com/example/pkg2\r\n' + + 'BenchmarkBar-8 3000000 200 ns/op'; + + const results = extractGoResult(output); + + expect(results).toHaveLength(2); + expect(results[0].name).toBe('BenchmarkFoo (github.com/example/pkg1)'); + expect(results[1].name).toBe('BenchmarkBar (github.com/example/pkg2)'); + }); + + it('handles multiple benchmarks within the same package', () => { + const output = dedent` + pkg: github.com/example/pkg1 + BenchmarkFoo-8 5000000 100 ns/op + BenchmarkBar-8 3000000 150 ns/op + pkg: github.com/example/pkg2 + BenchmarkBaz-8 2000000 200 ns/op + `; + + const results = extractGoResult(output); + + expect(results).toHaveLength(3); + expect(results[0].name).toBe('BenchmarkFoo (github.com/example/pkg1)'); + expect(results[1].name).toBe('BenchmarkBar (github.com/example/pkg1)'); + expect(results[2].name).toBe('BenchmarkBaz (github.com/example/pkg2)'); + }); + }); +}); From ae1c3388e422a6c8839b8213cc996a930d65e968 Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 22:27:36 +0100 Subject: [PATCH 6/9] refactor: add chunkPairs helper for cleaner Go result extraction Replace index arithmetic (i * 2, i * 2 + 1) with explicit chunking using a purpose-built chunkPairs function. This makes the [value, unit] pair structure obvious through destructuring and isolates the index math. --- src/extract.ts | 90 ++++++++++++------------------------ test/extractGoResult.spec.ts | 2 +- 2 files changed, 31 insertions(+), 61 deletions(-) diff --git a/src/extract.ts b/src/extract.ts index f36eed6c2..69d7c73db 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -344,48 +344,27 @@ function extractCargoResult(output: string): BenchmarkResult[] { } export function extractGoResult(output: string): BenchmarkResult[] { - const lines = output.split(/\r?\n/g); - const ret = []; - // Example: - // BenchmarkFib20-8 30000 41653 ns/op - // BenchmarkDoWithConfigurer1-8 30000000 42.3 ns/op - - // Example if someone has used the ReportMetric function to add additional metrics to each benchmark: - // BenchmarkThing-16 1 95258906556 ns/op 64.02 UnitsForMeasure2 31.13 UnitsForMeasure3 - - // reference, "Proposal: Go Benchmark Data Format": https://go.googlesource.com/proposal/+/master/design/14313-benchmark-format.md - // "A benchmark result line has the general form: [ ...]" - // "The fields are separated by runs of space characters (as defined by unicode.IsSpace), so the line can be parsed with strings.Fields. The line must have an even number of fields, and at least four." - const reExtract = + const benchmarkRegex = /^(?Benchmark\w+[\w()$%^&*-=|,[\]{}"#]*?)(?-\d+)?\s+(?\d+)\s+(?.+)$/; - // First pass: detect all unique packages - // This is used to determine if we need to append package names to benchmark names for disambiguation - const packageRegex = /^pkg:\s+(.+)$/; - const packages = new Set(); - for (const line of lines) { - const pkgMatch = line.match(packageRegex); - if (pkgMatch) { - packages.add(pkgMatch[1]); - } - } - const hasMultiplePackages = packages.size > 1; + // Split into sections by "pkg:" lines, keeping package name with each section + const sections = output.split(/^pkg:\s+/m).map((section, index) => { + if (index === 0) return { pkg: '', lines: section.split(/\r?\n/g) }; + const [pkg, ...rest] = section.split(/\r?\n/g); + return { pkg, lines: rest }; + }); - // Second pass: extract benchmarks with package context - let currentPackage = ''; - for (const line of lines) { - // Track current package from "pkg:" lines - const pkgMatch = line.match(packageRegex); - if (pkgMatch) { - currentPackage = pkgMatch[1]; - continue; - } + const hasMultiplePackages = sections.filter((s) => s.pkg).length > 1; - const m = line.match(reExtract); - if (m?.groups) { - const procs = m.groups.procs !== undefined ? m.groups.procs.slice(1) : null; - const times = m.groups.times; - const remainder = m.groups.remainder; + // Process each section and flatten results + return sections.flatMap(({ pkg, lines }) => + lines.flatMap((line) => { + const match = line.match(benchmarkRegex); + if (!match?.groups) return []; + + const { name, procs: procsRaw, times, remainder } = match.groups; + const procs = procsRaw?.slice(1) ?? null; + const extra = procs !== null ? `${times} times\n${procs} procs` : `${times} times`; const pieces = remainder.split(/[ \t]+/); @@ -395,29 +374,20 @@ export function extractGoResult(output: string): BenchmarkResult[] { pieces.unshift(pieces[0], remainder.slice(remainder.indexOf(pieces[1]))); } - // Build base benchmark name with optional package suffix for disambiguation - const baseName = - hasMultiplePackages && currentPackage ? `${m.groups.name} (${currentPackage})` : m.groups.name; - - for (let i = 0; i < pieces.length; i = i + 2) { - let extra = `${times} times`.replace(/\s\s+/g, ' '); - if (procs !== null) { - extra += `\n${procs} procs`; - } - const value = parseFloat(pieces[i]); - const unit = pieces[i + 1]; - let name; - if (i > 0) { - name = baseName + ' - ' + unit; - } else { - name = baseName; - } - ret.push({ name, value, unit, extra }); - } - } - } + const baseName = hasMultiplePackages && pkg ? `${name} (${pkg})` : name; + // Chunk into [value, unit] pairs and map to results + return chunkPairs(pieces).map(([valueStr, unit], i) => ({ + name: i > 0 ? `${baseName} - ${unit}` : baseName, + value: parseFloat(valueStr), + unit, + extra, + })); + }), + ); +} - return ret; +function chunkPairs(arr: string[]): Array<[string, string]> { + return Array.from({ length: Math.floor(arr.length / 2) }, (_, i) => [arr[i * 2], arr[i * 2 + 1]]); } function extractBenchmarkJsResult(output: string): BenchmarkResult[] { diff --git a/test/extractGoResult.spec.ts b/test/extractGoResult.spec.ts index f2e2a9eae..59f4efd67 100644 --- a/test/extractGoResult.spec.ts +++ b/test/extractGoResult.spec.ts @@ -1,5 +1,5 @@ -import { extractGoResult } from '../src/extract'; import dedent from 'dedent'; +import { extractGoResult } from '../src/extract'; describe('extractGoResult()', () => { describe('basic benchmark extraction', () => { From f5dbe1382048d3ef86b6cc54e3baaf7c2ef84cfb Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 22:31:59 +0100 Subject: [PATCH 7/9] get closer to original structure --- src/extract.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/extract.ts b/src/extract.ts index 69d7c73db..7cb9687db 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -344,9 +344,6 @@ function extractCargoResult(output: string): BenchmarkResult[] { } export function extractGoResult(output: string): BenchmarkResult[] { - const benchmarkRegex = - /^(?Benchmark\w+[\w()$%^&*-=|,[\]{}"#]*?)(?-\d+)?\s+(?\d+)\s+(?.+)$/; - // Split into sections by "pkg:" lines, keeping package name with each section const sections = output.split(/^pkg:\s+/m).map((section, index) => { if (index === 0) return { pkg: '', lines: section.split(/\r?\n/g) }; @@ -356,10 +353,23 @@ export function extractGoResult(output: string): BenchmarkResult[] { const hasMultiplePackages = sections.filter((s) => s.pkg).length > 1; + // Example: + // BenchmarkFib20-8 30000 41653 ns/op + // BenchmarkDoWithConfigurer1-8 30000000 42.3 ns/op + + // Example if someone has used the ReportMetric function to add additional metrics to each benchmark: + // BenchmarkThing-16 1 95258906556 ns/op 64.02 UnitsForMeasure2 31.13 UnitsForMeasure3 + + // reference, "Proposal: Go Benchmark Data Format": https://go.googlesource.com/proposal/+/master/design/14313-benchmark-format.md + // "A benchmark result line has the general form: [ ...]" + // "The fields are separated by runs of space characters (as defined by unicode.IsSpace), so the line can be parsed with strings.Fields. The line must have an even number of fields, and at least four." + const reExtractRegexp = + /^(?Benchmark\w+[\w()$%^&*-=|,[\]{}"#]*?)(?-\d+)?\s+(?\d+)\s+(?.+)$/; + // Process each section and flatten results return sections.flatMap(({ pkg, lines }) => lines.flatMap((line) => { - const match = line.match(benchmarkRegex); + const match = line.match(reExtractRegexp); if (!match?.groups) return []; const { name, procs: procsRaw, times, remainder } = match.groups; From c4d55d2fdac01c7cdcc22132f5fa45492616ebac Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 22:35:48 +0100 Subject: [PATCH 8/9] use chained flatMap instead of nested --- src/extract.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/extract.ts b/src/extract.ts index 7cb9687db..6876752c4 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -366,9 +366,10 @@ export function extractGoResult(output: string): BenchmarkResult[] { const reExtractRegexp = /^(?Benchmark\w+[\w()$%^&*-=|,[\]{}"#]*?)(?-\d+)?\s+(?\d+)\s+(?.+)$/; - // Process each section and flatten results - return sections.flatMap(({ pkg, lines }) => - lines.flatMap((line) => { + // Flatten sections into lines with package context, then process each line + return sections + .flatMap(({ pkg, lines }) => lines.map((line) => ({ pkg, line }))) + .flatMap(({ pkg, line }) => { const match = line.match(reExtractRegexp); if (!match?.groups) return []; @@ -392,8 +393,7 @@ export function extractGoResult(output: string): BenchmarkResult[] { unit, extra, })); - }), - ); + }); } function chunkPairs(arr: string[]): Array<[string, string]> { From 3d0659f86013fcbfffa8f7f482b7349857039cc5 Mon Sep 17 00:00:00 2001 From: Chris Trzesniewski Date: Fri, 30 Jan 2026 22:46:51 +0100 Subject: [PATCH 9/9] disable doc string pre merge check --- .coderabbit.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..4ef516b11 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,4 @@ +reviews: + pre_merge_checks: + docstrings: + mode: "off"