Skip to content

Commit f005945

Browse files
committed
src/goTestExplorer: explain benchmark output
- Add test_events.md to detail what go test -json looks like - Add comments
1 parent 1ee5851 commit f005945

File tree

2 files changed

+61
-10
lines changed

2 files changed

+61
-10
lines changed

src/goTestExplorer.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@ async function processSymbol(
262262
seen: Set<string>,
263263
symbol: DocumentSymbol
264264
) {
265-
// Skip TestMain(*testing.M)
266-
if (symbol.name === 'TestMain' || /\*testing.M\)/.test(symbol.detail)) {
265+
// Skip TestMain(*testing.M) - allow TestMain(*testing.T)
266+
if (symbol.name === 'TestMain' && /\*testing.M\)/.test(symbol.detail)) {
267267
return;
268268
}
269269

@@ -325,7 +325,7 @@ async function walk(
325325
let dirs = [uri];
326326

327327
// While there are directories to be scanned
328-
while (dirs.length) {
328+
while (dirs.length > 0) {
329329
const d = dirs;
330330
dirs = [];
331331

@@ -561,6 +561,7 @@ function resolveTestName(ctrl: TestController, tests: Record<string, TestItem>,
561561
return test;
562562
}
563563

564+
// Process benchmark test events (see test_events.md)
564565
function consumeGoBenchmarkEvent<T>(
565566
ctrl: TestController,
566567
run: TestRun<T>,
@@ -569,18 +570,19 @@ function consumeGoBenchmarkEvent<T>(
569570
e: GoTestOutput
570571
) {
571572
if (e.Test) {
573+
// Find (or create) the (sub)benchmark
572574
const test = resolveTestName(ctrl, benchmarks, e.Test);
573575
if (!test) {
574576
return;
575577
}
576578

577579
switch (e.Action) {
578-
case 'fail':
580+
case 'fail': // Failed
579581
run.setState(test, TestResultState.Failed);
580582
complete.add(test);
581583
break;
582584

583-
case 'skip':
585+
case 'skip': // Skipped
584586
run.setState(test, TestResultState.Skipped);
585587
complete.add(test);
586588
break;
@@ -589,22 +591,29 @@ function consumeGoBenchmarkEvent<T>(
589591
return;
590592
}
591593

594+
// Ignore anything that's not an output event
592595
if (!e.Output) {
593596
return;
594597
}
595598

596-
// Started: "BenchmarkFooBar"
597-
// Completed: "BenchmarkFooBar-4 123456 123.4 ns/op 123 B/op 12 allocs/op"
599+
// On start: "BenchmarkFooBar"
600+
// On complete: "BenchmarkFooBar-4 123456 123.4 ns/op 123 B/op 12 allocs/op"
601+
602+
// Extract the benchmark name and status
598603
const m = e.Output.match(/^(?<name>Benchmark[/\w]+)(?:-(?<procs>\d+)\s+(?<result>.*))?(?:$|\n)/);
599604
if (!m) {
605+
// If the output doesn't start with `BenchmarkFooBar`, ignore it
600606
return;
601607
}
602608

609+
// Find (or create) the (sub)benchmark
603610
const test = resolveTestName(ctrl, benchmarks, m.groups.name);
604611
if (!test) {
605612
return;
606613
}
607614

615+
// If output includes benchmark results, the benchmark passed. If output
616+
// only includes the benchmark name, the benchmark is running.
608617
if (m.groups.result) {
609618
run.appendMessage(test, {
610619
message: m.groups.result,
@@ -618,6 +627,7 @@ function consumeGoBenchmarkEvent<T>(
618627
}
619628
}
620629

630+
// Pass any incomplete benchmarks (see test_events.md)
621631
function passBenchmarks<T>(run: TestRun<T>, items: Record<string, TestItem>, complete: Set<TestItem>) {
622632
function pass(item: TestItem) {
623633
if (!complete.has(item)) {
@@ -746,7 +756,7 @@ async function runTest<T>(ctrl: TestController, request: TestRunRequest<T>) {
746756
for (const item of items) {
747757
run.setState(item, TestResultState.Queued);
748758

749-
// Remove any subtests
759+
// Clear any dynamic subtests generated by a previous run
750760
item.canResolveChildren = false;
751761
Array.from(item.children.values()).forEach((x) => x.dispose());
752762

@@ -762,7 +772,8 @@ async function runTest<T>(ctrl: TestController, request: TestRunRequest<T>) {
762772
const testFns = Object.keys(tests);
763773
const benchmarkFns = Object.keys(benchmarks);
764774

765-
if (testFns.length) {
775+
if (testFns.length > 0) {
776+
// Run tests
766777
await goTest({
767778
goConfig,
768779
flags,
@@ -774,7 +785,8 @@ async function runTest<T>(ctrl: TestController, request: TestRunRequest<T>) {
774785
});
775786
}
776787

777-
if (benchmarkFns.length) {
788+
if (benchmarkFns.length > 0) {
789+
// Run benchmarks
778790
const complete = new Set<TestItem>();
779791
await goTest({
780792
goConfig,
@@ -787,6 +799,7 @@ async function runTest<T>(ctrl: TestController, request: TestRunRequest<T>) {
787799
goTestOutputConsumer: (e) => consumeGoBenchmarkEvent(ctrl, run, benchmarks, complete, e)
788800
});
789801

802+
// Explicitly pass any incomplete benchmarks (see test_events.md)
790803
passBenchmarks(run, benchmarks, complete);
791804
}
792805

src/test_events.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Go test events
2+
3+
Running tests with the `-json` flag or passing test output through `go tool
4+
test2json1` will produce a stream of JSON events. Each event specifies an
5+
action, such as `run`, `pass`, `output`, etc. An event *may* specify what test
6+
it belongs to. The VSCode Go test controller must capture these events in order
7+
to notify VSCode of test output and lifecycle events.
8+
9+
## Tests
10+
11+
Processing test events generated by `TestXxx(*testing.T)` functions is easy.
12+
Events with an empty `Test` field can be ignored, and all other events have a
13+
meaningful `Action` field. Output is recorded, and run/pass/fail/skip events are
14+
converted to VSCode test API events.
15+
16+
[go#37555](https://github.com/golang/go/issues/37555) did require special
17+
handling, but that only appeared in Go 1.14 and was backported to 1.14.1.
18+
19+
## Benchmarks
20+
21+
Test events generated by `BenchmarkXxx(*testing.B)` functions require
22+
significantly more processing. If a benchmark fails or is skipped, the `Test`
23+
and `Action` fields are populated appropriately. Otherwise, `Test` is empty and
24+
`Action` is always `output`. Thus, nominal lifecycle events (run/pass) must be
25+
deduced purely from test output. When a benchmark begins, an output such as
26+
`BenchmarkFooBar\n` is produced. When a benchmark completes, an output such as
27+
`BencmarkFooBar-4 123456 123.4 ns/op 123 B/op 12 allocs/op` is produced. No
28+
explicit `run` or `pass` events are generated. Thus:
29+
30+
- When `BenchmarkFooBar\n` is seen, the benchmark will be marked as running
31+
- When an explicit fail/skip is seen, the benchmark will be marked as failed/skipped
32+
- When benchmark results are seen, the benchmark will be marked as passed
33+
34+
Thus, a benchmark that does not produce results (and does not fail or skip) will
35+
never produce an event indicating that it has completed. Benchmarks that call
36+
`(*testing.B).Run` will not produce results. In practice, this means that any
37+
incomplete benchmarks must be explicitly marked as passed once `go test`
38+
returns.

0 commit comments

Comments
 (0)