Skip to main content

supercov_engine/
go_test_harness.rs

1//! Binding Go's test functions to the evidence they produce.
2//!
3//! Attribution needs two things the source does not have: every test function
4//! announcing itself, and somewhere to write the evidence once the package has
5//! finished. Go gives exactly one after-all hook, `TestMain`, so this augments
6//! the package's own when it has one and synthesises it when it does not.
7//!
8//! The wrapping is deliberate rather than a deferred call. The idiomatic
9//! `TestMain` ends in `os.Exit(m.Run())`, and `os.Exit` runs no deferred
10//! function, so a `defer` there would silently produce no evidence for exactly
11//! the projects that wrote the most careful harness.
12
13use tree_sitter::Node;
14
15use crate::go_instrumenter::{GoEdit, GoInstrumenterError, RUNTIME_IMPORT, import_edit, parse};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct GoTestFile {
19    /// Test functions this file declares, in source order.
20    pub tests: Vec<String>,
21    /// Functions `go test` runs that get a checkpoint rather than an
22    /// announcement, because nothing they reach can be credited to them: a
23    /// test that calls `t.Parallel()`, whose work runs alongside other tests;
24    /// and an Example or Fuzz target, which takes no `*testing.T` to be named
25    /// by. What they reach is swept into the run-wide totals all the same.
26    pub unattributed: Vec<String>,
27    /// True when this file declares `TestMain`, which the package may only
28    /// have one of.
29    pub declares_test_main: bool,
30    pub edits: Vec<GoEdit>,
31}
32
33fn function_name(node: Node, source: &str) -> Option<String> {
34    node.child_by_field_name("name")
35        .map(|name| source[name.byte_range()].to_owned())
36}
37
38/// Go's own rule for what the toolchain will run as a test, from
39/// `cmd/go/internal/load/test.go`: the prefix, then either nothing or a rune
40/// that is not lowercase.
41///
42/// So a bare `Test` is a test, and `Testify` is not — the `i` makes it an
43/// ordinary function that merely starts with the word. Guessing differently
44/// from the toolchain would attribute evidence to something `go test` never
45/// runs, or miss a test that it does.
46fn is_test_function(name: &str) -> bool {
47    name.strip_prefix("Test")
48        .is_some_and(|rest| rest.chars().next().is_none_or(|c| !c.is_lowercase()))
49}
50
51/// `go test` runs more than `TestX`. An `ExampleX` with an "Output:" comment
52/// runs like any other test, and a `FuzzX` runs its seed corpus. Neither takes
53/// a `*testing.T`, so neither can be announced -- but both reach product code,
54/// and where the end-of-run write is never made, everything they reach has to
55/// be swept before they return or it is never recorded at all.
56///
57/// samber/lo has three and a half thousand lines of examples and a TestMain
58/// that ends in goleak's VerifyTestMain, which exits the process itself. Its
59/// example coverage survived or did not according to which test happened to
60/// checkpoint last, and the run reported anywhere between 74% and 95% of the
61/// same suite.
62fn is_checkpoint_only_function(name: &str, node: Node, source: &str) -> bool {
63    let after = |prefix: &str| {
64        name.strip_prefix(prefix)
65            .is_some_and(|rest| rest.chars().next().is_none_or(|c| !c.is_lowercase()))
66    };
67    if after("Example") {
68        // An example takes nothing and returns nothing; anything else that
69        // starts with the word is an ordinary function.
70        return first_parameter(node).is_none();
71    }
72    after("Fuzz") && parameter_type(node, source).as_deref() == Some("*testing.F")
73}
74
75fn first_parameter<'t>(node: Node<'t>) -> Option<Node<'t>> {
76    let parameters = node.child_by_field_name("parameters")?;
77    let mut cursor = parameters.walk();
78    parameters
79        .children(&mut cursor)
80        .find(|child| child.kind() == "parameter_declaration")
81}
82
83fn parameter_type(node: Node, source: &str) -> Option<String> {
84    let kind = first_parameter(node)?.child_by_field_name("type")?;
85    Some(source[kind.byte_range()].trim().to_owned())
86}
87
88/// What the test calls its `*testing.T`, which the announcement needs in order
89/// to read the outcome from it. Almost always `t`, but nothing requires that,
90/// and a harness that assumed it would fail to compile on the files that
91/// chose otherwise.
92fn parameter_name(node: Node, source: &str) -> Option<String> {
93    let name = first_parameter(node)?.child_by_field_name("name")?;
94    let text = source[name.byte_range()].trim();
95    // `func TestX(*testing.T)` is legal and names nothing there is to read.
96    (text != "_" && !text.is_empty()).then(|| text.to_owned())
97}
98
99/// Instrument one `_test.go` file so every test announces itself.
100///
101/// `serialised` says the run was asked for exact attribution and the command
102/// carries `-parallel=1`, so a test that calls `t.Parallel()` is the only test
103/// running while its probes fire. It can then be announced like any other, and
104/// the run credits it with what it reached. Without that, announcing it would
105/// bind whatever ran beside it to its name.
106pub fn instrument_test_file(
107    source: &str,
108    alias: &str,
109    evidence_path: &str,
110    serialised: bool,
111) -> Result<GoTestFile, GoInstrumenterError> {
112    let tree = parse(source)?;
113    let mut file = GoTestFile {
114        tests: Vec::new(),
115        unattributed: Vec::new(),
116        declares_test_main: false,
117        edits: Vec::new(),
118    };
119    let root = tree.root_node();
120    // Only the edits that name the runtime directly need its import. The
121    // per-test announcement goes through a package-local helper instead, so a
122    // file of ordinary tests must not gain an import it never mentions.
123    let mut needs_runtime = false;
124    let mut cursor = root.walk();
125    for child in root.children(&mut cursor) {
126        if child.kind() != "function_declaration" {
127            continue;
128        }
129        let Some(name) = function_name(child, source) else {
130            continue;
131        };
132        let Some(body) = child.child_by_field_name("body") else {
133            continue;
134        };
135        if name == "TestMain" {
136            file.declares_test_main = true;
137            needs_runtime = true;
138            // Whether there will be an end-of-run write decides how eagerly
139            // the runtime has to persist, so the wrapping is decided first and
140            // the destination written with the answer.
141            let mut wrapping = Vec::new();
142            let wrapped = wrap_run_calls(body, source, alias, evidence_path, &mut wrapping);
143            file.edits.push(GoEdit {
144                at: body.start_byte() + 1,
145                rank: 100,
146                // On the brace's line, as every insertion into a test file
147                // is: lines of its own would move the lines below it.
148                text: format!(
149                    "{alias}.Arm(__supercovProbeCount, __supercovDecisionWidths); {alias}.Destination(\"{evidence_path}\", {});",
150                    !wrapped
151                ),
152            });
153            file.edits.extend(wrapping);
154            continue;
155        }
156        if !is_test_function(&name) {
157            if is_checkpoint_only_function(&name, child, source) {
158                file.unattributed.push(name.clone());
159                // A Fuzz target takes a *testing.F, which reports an outcome
160                // the same way a *testing.T does. An Example takes nothing at
161                // all, so the only honest status for it is that it ran: `go
162                // test` fails the run when an Example's output does not match,
163                // and the run's own exit code carries that.
164                let record = match parameter_name(child, source) {
165                    Some(parameter)
166                        if parameter_type(child, source).as_deref() == Some("*testing.F") =>
167                    {
168                        format!("defer {HARNESS_UNATTRIBUTED_FUZZ}({parameter}, \"{name}\")();")
169                    }
170                    _ => format!(
171                        "defer func() {{ {alias}.Ran(\"{name}\", \"unknown\"); {alias}.Checkpoint() }}();"
172                    ),
173                };
174                // Only a file that names the runtime needs its import, and
175                // the Fuzz form names nothing but the generated helper: an
176                // import it never mentions does not fail to measure, it fails
177                // to compile, and Supercov breaks the suite it was measuring.
178                needs_runtime |= record.contains(&format!("{alias}."));
179                file.edits.push(GoEdit {
180                    at: body.start_byte() + 1,
181                    rank: 100,
182                    text: record,
183                });
184            }
185            continue;
186        }
187        // `func TestX(t *testing.T)` is a test; `func TestX(b *testing.B)` is
188        // not, whatever its name suggests.
189        if parameter_type(child, source).as_deref() != Some("*testing.T") {
190            continue;
191        }
192        file.tests.push(name.clone());
193        // Serialised, and the test pauses itself: the announcement waits until
194        // `t.Parallel()` has returned, which is when this test is the one
195        // running.
196        let announce_at = if serialised && calls_parallel(body, source) {
197            own_parallel_call_end(body, source).unwrap_or(body.start_byte() + 1)
198        } else {
199            body.start_byte() + 1
200        };
201        if calls_parallel(body, source) && !serialised {
202            // Announcing it would bind whatever runs next to this test, and
203            // what runs next includes the other parallel tests. Its coverage
204            // still counts run-wide; it simply belongs to no test, which is
205            // the truth rather than a guess dressed as a measurement.
206            //
207            // It still gets a checkpoint. Go resumes parallel tests after the
208            // serial ones are done, so without one there is no announcement
209            // left to sweep at and everything the parallel phase reached sits
210            // in the probe array until the process ends -- which, where the
211            // end-of-run write is never reached, means it is never recorded.
212            file.unattributed.push(name.clone());
213            // Named, with its real outcome, and credited with nothing. Leaving
214            // it out of the evidence entirely was what made a suite of
215            // parallel tests publish zero tests: `runs <id> test <name>`
216            // answered "Test not found" for a test that had just passed, and
217            // affected-test selection returned an empty set for a change those
218            // tests exercised.
219            let record = match parameter_name(child, source) {
220                Some(parameter) => {
221                    format!("defer {HARNESS_UNATTRIBUTED}({parameter}, \"{name}\")();")
222                }
223                // `func TestX(*testing.T)` names nothing to read an outcome
224                // from, and is reported as having passed unless the run says
225                // otherwise -- the same rule the announced path uses.
226                None => format!(
227                    "defer func() {{ {alias}.Ran(\"{name}\", \"passed\"); {alias}.Checkpoint() }}();"
228                ),
229            };
230            needs_runtime |= record.contains(&format!("{alias}."));
231            file.edits.push(GoEdit {
232                at: body.start_byte() + 1,
233                rank: 100,
234                text: record,
235            });
236            continue;
237        }
238        // Through the generated helper rather than the runtime directly, so
239        // the announcement can read the outcome off the test's own *testing.T
240        // without the runtime ever importing `testing`.
241        // On the line it follows, and ended with a semicolon. Lines of its own
242        // moved every line below them: a failure in the third test of a file
243        // read `crypto_test.go:26` for line 21. The semicolon matters: a body
244        // written on one line puts its first statement straight after the
245        // brace, and an announcement run into it would not compile. After
246        // `t.Parallel()` it needs one before it as well.
247        let after = if announce_at == body.start_byte() + 1 {
248            ""
249        } else {
250            "; "
251        };
252        let announcement = match parameter_name(child, source) {
253            Some(parameter) => {
254                format!("{after}defer {HARNESS_ENTER}({parameter}, \"{name}\")();")
255            }
256            None => format!("{after}defer {alias}.EnterTest(\"{name}\")();"),
257        };
258        if announcement.contains(&format!("{alias}.")) {
259            needs_runtime = true;
260        }
261        file.edits.push(GoEdit {
262            at: announce_at,
263            rank: 100,
264            text: announcement,
265        });
266    }
267    // A file that names the runtime needs the import that makes it resolve;
268    // one that only calls the generated helper must not get an unused import.
269    if needs_runtime && let Some(import) = import_edit(source, alias, RUNTIME_IMPORT) {
270        file.edits.push(import);
271    }
272    Ok(file)
273}
274
275/// Where a test's own `t.Parallel()` call ends, if it makes one directly.
276///
277/// `t.Parallel()` does not return until the serial phase is over and this test
278/// is the one being run, so a statement after it runs with the test actually
279/// running. That is the only place an announcement can go when the run is
280/// serialised: at the top of the body it would fire while every other parallel
281/// test was also entering and pausing, all of them open at once, and the
282/// runtime would rightly give up on attributing any of them.
283///
284/// Only a call the test makes itself counts. `t.Parallel()` inside a subtest
285/// closure pauses the subtest, not this function, so there is nothing to wait
286/// for here.
287fn own_parallel_call_end(body: Node, source: &str) -> Option<usize> {
288    // A block holds its statements in a `statement_list`, not directly, so the
289    // statements are one level further down than a block's own children.
290    let statements = {
291        let mut cursor = body.walk();
292        body.children(&mut cursor)
293            .find(|child| child.kind() == "statement_list")
294            .unwrap_or(body)
295    };
296    let mut cursor = statements.walk();
297    for statement in statements.children(&mut cursor).filter(Node::is_named) {
298        // The grammar wraps a call standing alone as a statement in an
299        // `expression_statement`; it is never a statement's child on its own.
300        let call = match statement.kind() {
301            "expression_statement" => match statement.named_child(0) {
302                Some(inner) if inner.kind() == "call_expression" => inner,
303                // Some other expression standing alone -- a channel receive,
304                // say -- which is not the call being looked for; the next
305                // statement might be.
306                _ => continue,
307            },
308            _ => continue,
309        };
310        if call
311            .child_by_field_name("function")
312            .is_some_and(|function| {
313                source[function.byte_range()]
314                    .trim_end()
315                    .ends_with(".Parallel")
316            })
317        {
318            return Some(statement.end_byte());
319        }
320    }
321    None
322}
323
324/// Whether a test hands itself to Go's parallel scheduler.
325///
326/// Detected from the source rather than at runtime, because by the time
327/// `t.Parallel()` returns the test has already been descheduled and anything
328/// observed afterwards may belong to another one.
329fn calls_parallel(node: Node, source: &str) -> bool {
330    if node.kind() == "call_expression"
331        && let Some(function) = node.child_by_field_name("function")
332        && source[function.byte_range()]
333            .trim_end()
334            .ends_with(".Parallel")
335    {
336        return true;
337    }
338    let mut cursor = node.walk();
339    node.children(&mut cursor)
340        .filter(Node::is_named)
341        .any(|child| calls_parallel(child, source))
342}
343
344fn wrap_run_calls(
345    node: Node,
346    source: &str,
347    alias: &str,
348    evidence: &str,
349    edits: &mut Vec<GoEdit>,
350) -> bool {
351    if node.kind() == "call_expression"
352        && let Some(function) = node.child_by_field_name("function")
353        && source[function.byte_range()].trim_end().ends_with(".Run")
354    {
355        edits.push(GoEdit {
356            at: node.start_byte(),
357            rank: 50,
358            text: format!("{alias}.Finish("),
359        });
360        edits.push(GoEdit {
361            at: node.end_byte(),
362            rank: 50,
363            text: format!(", \"{evidence}\")"),
364        });
365        return true;
366    }
367    let mut cursor = node.walk();
368    let mut wrapped = false;
369    for child in node.children(&mut cursor) {
370        if child.is_named() {
371            wrapped |= wrap_run_calls(child, source, alias, evidence, edits);
372        }
373    }
374    wrapped
375}
376
377/// The generated file that gives a package the array its probes store into.
378///
379/// A normal source file, not a test one: instrumented statements live in
380/// ordinary code and must compile in every build of the package, not only
381/// under `go test`.
382pub fn probe_array_file(package: &str, alias: &str, import: &str, probe_count: usize) -> String {
383    format!(
384        "// Code generated by Supercov. DO NOT EDIT.\n\npackage {package}\n\nimport {alias} \"{import}\"\n\n// Reserved once per package and shared across the module, so a probe is an\n// index into an array this file already holds rather than a call that has to\n// find one.\nvar {} = {alias}.Reserve({probe_count})\n",
385        crate::go_instrumenter::HITS_VARIABLE
386    )
387}
388
389/// The package-local function every instrumented test defers to.
390pub const HARNESS_ENTER: &str = "__supercovTest";
391
392/// The package-local function a test that cannot be attributed defers to.
393pub const HARNESS_UNATTRIBUTED: &str = "__supercovUnattributed";
394
395/// The same for a Fuzz target, which reports its outcome through a
396/// `*testing.F` rather than a `*testing.T`.
397pub const HARNESS_UNATTRIBUTED_FUZZ: &str = "__supercovUnattributedFuzz";
398
399/// The generated `TestMain` for a package that has none, plus the probe count
400/// every instrumented file in the package refers to.
401pub fn synthesized_harness(
402    package: &str,
403    alias: &str,
404    import: &str,
405    probe_count: usize,
406    decision_widths: &[u8],
407    evidence_path: &str,
408    declares_test_main: bool,
409) -> String {
410    let mut out =
411        format!("// Code generated by Supercov. DO NOT EDIT.\n\npackage {package}\n\nimport (\n");
412    // Always, because the announcement helper below takes a *testing.T. This
413    // file is a `_test.go`, so importing `testing` here reaches only the test
414    // binary and never a build of the product.
415    out.push_str("\t\"testing\"\n\n");
416    out.push_str(&format!("\t{alias} \"{import}\"\n)\n\n"));
417    // The runtime sizes each decision's vector from this, so a width the
418    // harness got wrong would record vectors of the wrong shape.
419    let widths = decision_widths
420        .iter()
421        .map(u8::to_string)
422        .collect::<Vec<_>>()
423        .join(", ");
424    out.push_str(&format!(
425        "const __supercovProbeCount = {probe_count}\n\nvar __supercovDecisionWidths = []uint8{{{widths}}}\n\n"
426    ));
427    // Binding and outcome in one place, so the edit inside the author's test
428    // is a single deferred call. Reading *testing.T here rather than in the
429    // runtime is what keeps `testing` out of every product binary that
430    // imports the runtime.
431    out.push_str(&format!(
432        "func {HARNESS_ENTER}(t *testing.T, name string) func() {{\n\tdone := {alias}.EnterTest(name)\n\treturn func() {{\n\t\tif t.Skipped() {{\n\t\t\t{alias}.Outcome(\"skipped\")\n\t\t}} else if t.Failed() {{\n\t\t\t{alias}.Outcome(\"failed\")\n\t\t}}\n\t\tdone()\n\t}}\n}}\n\n"
433    ));
434    // Named and credited with nothing. The outcome is read here for the same
435    // reason the announced path reads it here: so the runtime never imports
436    // `testing` and never reaches a product binary.
437    out.push_str(&format!(
438        "func {HARNESS_UNATTRIBUTED}(t *testing.T, name string) func() {{\n\treturn func() {{\n\t\tstatus := \"passed\"\n\t\tif t.Skipped() {{\n\t\t\tstatus = \"skipped\"\n\t\t}} else if t.Failed() {{\n\t\t\tstatus = \"failed\"\n\t\t}}\n\t\t{alias}.Ran(name, status)\n\t\t{alias}.Checkpoint()\n\t}}\n}}\n\n"
439    ));
440    out.push_str(&format!(
441        "func {HARNESS_UNATTRIBUTED_FUZZ}(f *testing.F, name string) func() {{\n\treturn func() {{\n\t\tstatus := \"passed\"\n\t\tif f.Skipped() {{\n\t\t\tstatus = \"skipped\"\n\t\t}} else if f.Failed() {{\n\t\t\tstatus = \"failed\"\n\t\t}}\n\t\t{alias}.Ran(name, status)\n\t\t{alias}.Checkpoint()\n\t}}\n}}\n\n"
442    ));
443    if declares_test_main {
444        // The author's TestMain arms the runtime; this keeps the widths
445        // referenced even in a package where nothing else names them.
446        out.push_str("var _ = __supercovDecisionWidths\n");
447        return out;
448    }
449    out.push_str(&format!(
450        "func TestMain(m *testing.M) {{\n\t{alias}.Arm(__supercovProbeCount, __supercovDecisionWidths)\n\t{alias}.Destination(\"{evidence_path}\", false)\n\tos.Exit({alias}.Finish(m.Run(), \"{evidence_path}\"))\n}}\n"
451    ));
452    out.replace("\t\"testing\"\n", "\t\"os\"\n\t\"testing\"\n")
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458    use crate::go_instrumenter::rewrite;
459
460    fn instrumented(source: &str) -> (GoTestFile, String) {
461        let file =
462            instrument_test_file(source, "__supercov", "evidence.bin", false).expect("instrument");
463        let out = rewrite(source, &file.edits);
464        parse(&out).unwrap_or_else(|error| panic!("{error}\n{out}"));
465        (file, without_positions(&out))
466    }
467
468    /// The rewritten text without the comments that tell Go where what
469    /// follows an insertion was written (`/*line :9:10*/`), which is how the
470    /// tests here read it.
471    fn without_positions(text: &str) -> String {
472        let mut out = String::with_capacity(text.len());
473        let mut rest = text;
474        while let Some(at) = rest.find("/*line :") {
475            out.push_str(&rest[..at]);
476            let end = rest[at..].find("*/").expect("a comment ends") + 2;
477            rest = &rest[at + end..];
478        }
479        out.push_str(rest);
480        out
481    }
482
483    #[test]
484    fn go_s_own_rule_decides_what_counts_as_a_test() {
485        // `Test` followed by a non-lowercase rune, taking *testing.T. A
486        // benchmark named like a test is not one, and neither is a helper.
487        let (file, _) = instrumented(
488            "package p\n\nimport \"testing\"\n\nfunc Test(t *testing.T) {}\nfunc TestOne(t *testing.T) {}\nfunc Testify(t *testing.T) {}\nfunc TestBench(b *testing.B) {}\nfunc helper(t *testing.T) {}\n",
489        );
490        // Bare `Test` counts; `Testify` does not, because the rune after the
491        // prefix is lowercase. This is the toolchain's rule, not a guess at it.
492        assert_eq!(file.tests, ["Test", "TestOne"]);
493    }
494
495    #[test]
496    fn an_existing_test_main_is_wrapped_rather_than_deferred_into() {
497        // `os.Exit(m.Run())` runs no deferred function, so a defer here would
498        // produce no evidence for exactly the projects with the most careful
499        // harness. The run call is wrapped instead.
500        let (file, out) = instrumented(
501            "package p\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestMain(m *testing.M) {\n\tos.Exit(m.Run())\n}\n",
502        );
503        assert!(file.declares_test_main);
504        assert!(
505            out.contains("__supercov.Finish(m.Run(), \"evidence.bin\")"),
506            "{out}"
507        );
508        assert!(
509            out.contains("__supercov.Arm(__supercovProbeCount, __supercovDecisionWidths)"),
510            "{out}"
511        );
512        assert!(
513            !out.contains("defer __supercov.Write"),
514            "a defer would never run:\n{out}"
515        );
516    }
517
518    #[test]
519    fn a_package_without_test_main_gets_one_and_never_two() {
520        // Go allows a package exactly one TestMain, so the generated file must
521        // declare it only when the package has none.
522        let generated = synthesized_harness(
523            "p",
524            "__supercov",
525            "example.com/rt",
526            42,
527            &[2, 3],
528            "e.bin",
529            false,
530        );
531        assert!(
532            generated.contains("[]uint8{2, 3}"),
533            "the runtime sizes its vectors from this:\n{generated}"
534        );
535        assert!(
536            generated.contains("func TestMain(m *testing.M)"),
537            "{generated}"
538        );
539        assert!(generated.contains("const __supercovProbeCount = 42"));
540        assert!(
541            generated.contains("os.Exit"),
542            "the generated harness preserves the exit code"
543        );
544
545        let alongside =
546            synthesized_harness("p", "__supercov", "example.com/rt", 42, &[], "e.bin", true);
547        assert!(!alongside.contains("func TestMain"), "{alongside}");
548        assert!(
549            alongside.contains("func __supercovTest(t *testing.T"),
550            "every package gets the announcement helper, TestMain or not:\n{alongside}"
551        );
552    }
553
554    #[test]
555    fn a_serialised_parallel_test_is_announced_after_it_resumes() {
556        // `t.Parallel()` does not return until the serial phase is over, so an
557        // announcement above it fires while every other parallel test is also
558        // entering and pausing. All of them would be open at once and the
559        // runtime would give up attributing any of them -- which is how asking
560        // for exact attribution produced a run with no tests in it at all.
561        let file = instrument_test_file(
562            "package p\n\nimport \"testing\"\n\nfunc TestParallel(t *testing.T) {\n\tt.Parallel()\n\tdoWork()\n}\n",
563            "__supercov",
564            "evidence.bin",
565            true,
566        )
567        .expect("instrument");
568        let out = rewrite(
569            "package p\n\nimport \"testing\"\n\nfunc TestParallel(t *testing.T) {\n\tt.Parallel()\n\tdoWork()\n}\n",
570            &file.edits,
571        );
572        parse(&out).unwrap_or_else(|error| panic!("{error}\n{out}"));
573        assert!(file.unattributed.is_empty(), "{file:?}");
574        let parallel = out.find("t.Parallel()").expect("the call");
575        let announced = out
576            .find("__supercovTest(t, \"TestParallel\")")
577            .expect("announced");
578        assert!(
579            parallel < announced,
580            "the announcement has to wait for the test to resume:\n{out}"
581        );
582    }
583
584    #[test]
585    fn a_serialised_run_leaves_a_serial_test_where_it_was() {
586        // `serialised` only moves the announcement for a test that pauses
587        // itself. A serial test in the same run has no `t.Parallel()` to wait
588        // for, and putting its announcement anywhere but the top of the body
589        // would leave whatever ran first uncredited.
590        //
591        // Measured rather than guessed: the decision that reads `serialised &&
592        // calls_parallel(..)` had no witness for the second operand until this
593        // existed, so nothing showed the two apart.
594        let source =
595            "package p\n\nimport \"testing\"\n\nfunc TestSerial(t *testing.T) {\n\tdoWork()\n}\n";
596        let file =
597            instrument_test_file(source, "__supercov", "evidence.bin", true).expect("instrument");
598        let out = rewrite(source, &file.edits);
599        parse(&out).unwrap_or_else(|error| panic!("{error}\n{out}"));
600        assert!(file.unattributed.is_empty(), "{file:?}");
601        let announced = out
602            .find("__supercovTest(t, \"TestSerial\")")
603            .expect("announced");
604        let work = out.find("doWork()").expect("body");
605        assert!(
606            announced < work,
607            "a serial test is announced before anything it calls:\n{out}"
608        );
609    }
610
611    #[test]
612    fn a_test_file_is_read_past_whatever_else_it_declares() {
613        // A `_test.go` holds more than functions -- fixtures, tables, helper
614        // types -- and the scan has to walk past all of it rather than stop.
615        let (file, out) = instrumented(concat!(
616            "package p\n\nimport \"testing\"\n\n",
617            "type fixture struct{ n int }\n\n",
618            "var cases = []fixture{{1}, {2}}\n\n",
619            "const limit = 3\n\n",
620            "func TestOne(t *testing.T) {\n\tdoWork()\n}\n",
621        ));
622        assert_eq!(file.tests, ["TestOne"]);
623        assert!(out.contains("__supercovTest(t, \"TestOne\")"), "{out}");
624    }
625
626    #[test]
627    fn a_parallel_call_is_found_wherever_the_test_makes_it() {
628        // `t.Parallel()` first is the shape every other test here uses, and it
629        // is not the shape most suites are written in: a test sets something
630        // up and then hands itself to the scheduler. The scan has to walk past
631        // whatever came before, which is a path nothing exercised -- the
632        // measurement showed the statement loop only ever matching on its
633        // first look.
634        let source = concat!(
635            "package p\n\nimport \"testing\"\n\n",
636            "func TestLater(t *testing.T) {\n",
637            "\tsetUp()\n",
638            "\tname := \"x\"\n",
639            "\t<-ready\n",
640            "\tt.Parallel()\n",
641            "\tdoWork(name)\n}\n",
642        );
643        let file =
644            instrument_test_file(source, "__supercov", "evidence.bin", true).expect("instrument");
645        let out = rewrite(source, &file.edits);
646        parse(&out).unwrap_or_else(|error| panic!("{error}\n{out}"));
647        assert!(file.unattributed.is_empty(), "{file:?}");
648        let parallel = out.find("t.Parallel()").expect("the call");
649        let announced = out
650            .find("__supercovTest(t, \"TestLater\")")
651            .expect("announced");
652        assert!(
653            parallel < announced,
654            "the announcement still waits for the call, wherever it is:\n{out}"
655        );
656        // And it waits for that call rather than landing after the setup it
657        // happened to follow.
658        let work = out.find("doWork(name)").expect("body");
659        assert!(announced < work, "{out}");
660    }
661
662    #[test]
663    fn a_function_with_no_body_is_walked_past() {
664        // Go declares a function with no body when the implementation is in
665        // assembly. It is not a test, it has nothing to instrument, and the
666        // scan has to keep going rather than reach for a body that is not
667        // there.
668        let (file, out) = instrumented(concat!(
669            "package p\n\nimport \"testing\"\n\n",
670            "func fastSum(a, b int) int\n\n",
671            "func TestOne(t *testing.T) {\n\tdoWork()\n}\n",
672        ));
673        assert_eq!(file.tests, ["TestOne"]);
674        assert!(out.contains("__supercovTest(t, \"TestOne\")"), "{out}");
675    }
676
677    #[test]
678    fn a_test_file_with_nothing_in_it_is_read_without_complaint() {
679        // The loop over a file's declarations has to survive having none.
680        let file = instrument_test_file("package p\n", "__supercov", "evidence.bin", false)
681            .expect("instrument");
682        assert!(file.tests.is_empty());
683        assert!(file.edits.is_empty(), "{file:?}");
684        assert!(!file.declares_test_main);
685    }
686
687    #[test]
688    fn a_parallel_test_is_named_rather_than_attributed_by_guesswork() {
689        // Go runs these alongside each other. Binding probes to whichever was
690        // most recently announced would produce per-test numbers that look
691        // exact and are not; the coverage still counts run-wide.
692        let (file, out) = instrumented(
693            "package p\n\nimport \"testing\"\n\nfunc TestSerial(t *testing.T) {\n\tdoWork()\n}\n\nfunc TestParallel(t *testing.T) {\n\tt.Parallel()\n\tdoWork()\n}\n",
694        );
695        assert_eq!(file.tests, ["TestSerial", "TestParallel"]);
696        assert_eq!(file.unattributed, ["TestParallel"]);
697        assert!(out.contains("__supercovTest(t, \"TestSerial\")"), "{out}");
698        // It is named, and it claims nothing. Those are different edits: the
699        // announcement binds whatever runs next to the test, and what runs
700        // next includes the other parallel tests. This one records that the
701        // test ran and how it ended, and credits it with no coverage at all.
702        //
703        // Leaving the name out entirely was the other extreme, and it read
704        // downstream as a test that never ran: a package of nothing but
705        // parallel tests published zero tests, so asking for one by name
706        // answered "Test not found" for a test that had just passed.
707        assert!(
708            out.contains("__supercovUnattributed(t, \"TestParallel\")"),
709            "{out}"
710        );
711        assert!(
712            !out.contains("__supercovTest(t, \"TestParallel\")"),
713            "a parallel test must not claim what ran beside it:\n{out}"
714        );
715    }
716
717    #[test]
718    fn a_file_that_names_only_the_generated_helper_gains_no_import() {
719        // Go rejects an import nothing mentions, so an import added for a file
720        // that reaches the runtime only through a package-local helper does
721        // not fail to measure -- it fails to compile, and Supercov breaks the
722        // suite it was asked to measure. A Fuzz target is exactly that shape.
723        let (file, out) = instrumented(
724            "package p\n\nimport \"testing\"\n\nfunc FuzzWork(f *testing.F) {\n\tf.Add(1)\n\tf.Fuzz(func(t *testing.T, n int) {\n\t\tdoWork(n)\n\t})\n}\n",
725        );
726        assert_eq!(file.unattributed, ["FuzzWork"]);
727        assert!(
728            out.contains("__supercovUnattributedFuzz(f, \"FuzzWork\")"),
729            "{out}"
730        );
731        assert!(
732            !out.contains(RUNTIME_IMPORT),
733            "the file names only the generated helper:\n{out}"
734        );
735
736        // An Example has no receiver to read an outcome from, so it names the
737        // runtime directly and does need the import.
738        let (_, out) =
739            instrumented("package p\n\nfunc ExampleWork() {\n\tdoWork()\n\t// Output: 1\n}\n");
740        assert!(out.contains("__supercov.Ran(\"ExampleWork\""), "{out}");
741        assert!(out.contains(RUNTIME_IMPORT), "{out}");
742    }
743
744    #[test]
745    fn a_test_whose_subtests_run_in_parallel_cannot_claim_them_either() {
746        // `t.Run(name, func(t *testing.T) { t.Parallel() })` is how a Go suite
747        // usually reaches for parallelism, and it is the same problem one
748        // level down: Go resumes the parallel subtests after the parent
749        // returns, so what they reach arrives with no announcement standing
750        // and cannot be credited to the parent that started them.
751        //
752        // The parent is therefore checkpointed rather than announced. It costs
753        // the parent its own attribution -- the serial part of its body is
754        // swept in with the rest -- which is the honest reading: nothing can
755        // say which of the two it came from.
756        let (file, out) = instrumented(
757            "package p\n\nimport \"testing\"\n\nfunc TestGroup(t *testing.T) {\n\tsetUp()\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tdoWork(c)\n\t\t})\n\t}\n}\n",
758        );
759        assert_eq!(file.tests, ["TestGroup"]);
760        assert_eq!(file.unattributed, ["TestGroup"]);
761        assert!(
762            !out.contains("__supercovTest(t, \"TestGroup\")"),
763            "a parent cannot claim what its parallel subtests reached:\n{out}"
764        );
765        assert!(
766            out.contains("__supercovUnattributed(t, \"TestGroup\")"),
767            "{out}"
768        );
769
770        // Where the subtests are serial there is nothing to run beside them,
771        // and the parent is named for all of it as before.
772        let (file, out) = instrumented(
773            "package p\n\nimport \"testing\"\n\nfunc TestGroup(t *testing.T) {\n\tt.Run(\"one\", func(t *testing.T) {\n\t\tdoWork()\n\t})\n}\n",
774        );
775        assert!(file.unattributed.is_empty(), "{file:?}");
776        assert!(out.contains("__supercovTest(t, \"TestGroup\")"), "{out}");
777    }
778
779    #[test]
780    fn an_example_is_swept_even_though_it_cannot_be_announced() {
781        // `go test` runs an Example with an Output comment like any other
782        // test, and a Fuzz target runs its seed corpus, but neither takes a
783        // *testing.T and neither can be attributed. What they reach still has
784        // to be swept before they return: where TestMain never returns --
785        // goleak's VerifyTestMain exits the process itself -- the only writes
786        // are the ones made at a boundary, and anything reached after the last
787        // one is never recorded. samber/lo has three and a half thousand lines
788        // of examples, and reported between 74% and 95% of one unchanged suite
789        // depending on which test finished last.
790        let (file, out) = instrumented(
791            "package p\n\nimport \"testing\"\n\nfunc ExampleWork() {\n\tdoWork()\n\t// Output: 1\n}\n\nfunc FuzzWork(f *testing.F) {\n\tdoWork()\n}\n\nfunc Examples(t *testing.T) {\n\tdoWork()\n}\n\nfunc ExampleHelper(x int) {\n\tdoWork()\n}\n",
792        );
793        // Both are swept before they return, and both are named for having
794        // run. The Example sweeps inline because it has no receiver to read an
795        // outcome from; the Fuzz target goes through the generated helper,
796        // which sweeps in turn -- so counting the word here would count one.
797        assert!(
798            out.contains("Ran(\"ExampleWork\", \"unknown\"); __supercov.Checkpoint()"),
799            "{out}"
800        );
801        let generated =
802            synthesized_harness("p", "__supercov", "example.com/rt", 0, &[], "e.bin", true);
803        assert!(
804            generated.matches("__supercov.Checkpoint()").count() == 2,
805            "both generated helpers sweep before returning:\n{generated}"
806        );
807        // A Fuzz target takes a *testing.F, which reports an outcome the same
808        // way a *testing.T does, so its status is read rather than guessed.
809        assert!(
810            out.contains("__supercovUnattributedFuzz(f, \"FuzzWork\")"),
811            "{out}"
812        );
813        assert!(
814            file.unattributed.contains(&"ExampleWork".to_owned()),
815            "{file:?}"
816        );
817        assert!(
818            file.unattributed.contains(&"FuzzWork".to_owned()),
819            "{file:?}"
820        );
821        // `Examples` is an ordinary function whose name starts with the word,
822        // exactly as `Testify` is, and one taking arguments is not an example
823        // `go test` will ever run.
824        assert!(file.tests.is_empty(), "{file:?}");
825        assert!(!out.contains("\"Examples\""), "{out}");
826        assert!(!out.contains("\"ExampleHelper\""), "{out}");
827    }
828
829    #[test]
830    fn the_announcement_uses_whatever_the_test_called_its_t() {
831        // `t` is the convention, not a rule. A harness that assumed it would
832        // fail to compile on the files that chose otherwise, and a file of
833        // ordinary tests must not gain a runtime import it never names.
834        let (_, out) = instrumented(
835            "package p\n\nimport \"testing\"\n\nfunc TestOne(tt *testing.T) {\n\tdoWork()\n}\n",
836        );
837        assert!(out.contains("__supercovTest(tt, \"TestOne\")"), "{out}");
838        assert!(
839            !out.contains(RUNTIME_IMPORT),
840            "an ordinary test file names only the generated helper:\n{out}"
841        );
842    }
843
844    #[test]
845    fn a_test_that_names_no_t_still_gets_bound() {
846        // `func TestX(*testing.T)` is legal Go and there is nothing to read an
847        // outcome from, so it announces itself directly and is reported as
848        // having passed unless the run says otherwise.
849        let (file, out) = instrumented(
850            "package p\n\nimport \"testing\"\n\nfunc TestOne(*testing.T) {\n\tdoWork()\n}\n",
851        );
852        assert_eq!(file.tests, ["TestOne"]);
853        assert!(out.contains("__supercov.EnterTest(\"TestOne\")"), "{out}");
854        assert!(
855            out.contains(RUNTIME_IMPORT),
856            "naming the runtime directly requires its import:\n{out}"
857        );
858    }
859
860    #[test]
861    fn a_test_file_using_go_1_26_new_is_still_instrumented() {
862        // A source file that could not be parsed was dropped and the run went
863        // on; a test file that could not be parsed took the whole run down
864        // with it, exit 1. Both came from the same construct, and neither is
865        // the author's mistake -- `new` has taken a value since Go 1.26.
866        let (file, out) = instrumented(
867            "package p\n\nimport \"testing\"\n\nfunc TestOne(t *testing.T) {\n\twant := new(\"hello\")\n\tif *greet() != *want {\n\t\tt.Fatal(\"bad\")\n\t}\n}\n",
868        );
869        assert_eq!(file.tests, ["TestOne"]);
870        assert!(out.contains("__supercovTest(t, \"TestOne\")"), "{out}");
871        // And what is written back is the author's own file, not the stand-in
872        // the parse read.
873        assert!(out.contains("new(\"hello\")"), "{out}");
874        assert!(!out.contains("nEw"), "{out}");
875    }
876
877    #[test]
878    fn a_test_announces_itself_before_anything_it_calls() {
879        let (_, out) = instrumented(
880            "package p\n\nimport \"testing\"\n\nfunc TestOne(t *testing.T) {\n\tdoWork()\n}\n",
881        );
882        let body = out.find("TestOne").unwrap();
883        let enter = out
884            .find("__supercovTest(t, \"TestOne\")")
885            .expect("announcement");
886        let work = out.find("doWork()").unwrap();
887        assert!(body < enter && enter < work, "{out}");
888    }
889
890    #[test]
891    fn a_test_written_on_one_line_still_compiles() {
892        // A body on one line puts its first statement immediately after the
893        // brace. An announcement inserted there with nothing after it runs
894        // straight into that statement, and the file stops being Go -- so
895        // Supercov breaks the suite it was asked to measure.
896        let (file, out) = instrumented(
897            "package p\n\nimport \"testing\"\n\nfunc TestOne(t *testing.T) { if work() != 1 { t.Fatal(\"no\") } }\n",
898        );
899        assert_eq!(file.tests, ["TestOne"]);
900        // `instrumented` parses the result, so reaching here is most of the
901        // claim; this says the announcement ends before the statement begins.
902        assert!(
903            out.contains("{defer __supercovTest(t, \"TestOne\")(); if work() != 1"),
904            "{out}"
905        );
906
907        // The same for a TestMain nobody spread over several lines.
908        let (_, out) = instrumented(
909            "package p\n\nimport \"testing\"\n\nfunc TestMain(m *testing.M) { os.Exit(m.Run()) }\n",
910        );
911        assert!(out.contains("__supercovDecisionWidths); "), "{out}");
912    }
913
914    #[test]
915    fn a_rewritten_test_file_has_the_lines_it_had() {
916        // Go reports a failure by its line. The announcement used to take two
917        // lines of its own in every test, the import one more, and TestMain's
918        // arming two: the third test of a file failed at `:26` for line 21.
919        let source = "package p\n\nimport \"testing\"\n\nfunc TestMain(m *testing.M) {\n\tos.Exit(m.Run())\n}\n\nfunc TestOne(t *testing.T) {\n\tdoWork()\n}\n\nfunc TestParallel(t *testing.T) {\n\tt.Parallel()\n\tdoWork()\n}\n\nfunc FuzzIt(f *testing.F) {\n\tdoWork()\n}\n";
920        for serialised in [false, true] {
921            let file = instrument_test_file(source, "__supercov", "evidence", serialised).unwrap();
922            let out = crate::go_instrumenter::rewrite(source, &file.edits);
923            assert_eq!(out.lines().count(), source.lines().count(), "{out}");
924            // And each line still begins as it did.
925            for (written, rewritten) in source.lines().zip(out.lines()) {
926                let start = written.split(['(', '{']).next().unwrap_or(written);
927                assert!(
928                    rewritten.starts_with(start),
929                    "{written:?} became {rewritten:?}"
930                );
931            }
932            parse(&out).unwrap();
933        }
934    }
935
936    #[test]
937    fn a_test_main_that_never_calls_run_makes_the_runtime_persist_eagerly() {
938        // The idiomatic TestMain ends in os.Exit(m.Run()), which the harness
939        // wraps so everything is written at the end. A TestMain that hands `m`
940        // to something else -- goleak's VerifyTestMain, testcontainers, a
941        // hand-written harness -- runs the suite and exits itself, and Go can
942        // run no code on os.Exit. Then the only evidence that survives is what
943        // was already written, so every checkpoint has to persist rather than
944        // wait out the window: samber/lo's parallel phase finishes inside one,
945        // and with a window it recorded 36% of its statements where the same
946        // run records 86% without.
947        let (_, out) = instrumented(
948            "package p\n\nimport \"testing\"\n\nfunc TestMain(m *testing.M) {\n\tgoleak.VerifyTestMain(m)\n}\n",
949        );
950        assert!(
951            out.contains(".Destination(\"evidence.bin\", true)"),
952            "{out}"
953        );
954        assert!(
955            !out.contains(".Finish("),
956            "there is no m.Run() here to wrap:\n{out}"
957        );
958
959        // Where the end-of-run write does happen, the window is safe and the
960        // per-test write is not paid.
961        let (_, out) = instrumented(
962            "package p\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestMain(m *testing.M) {\n\tos.Exit(m.Run())\n}\n",
963        );
964        assert!(
965            out.contains(".Destination(\"evidence.bin\", false)"),
966            "{out}"
967        );
968        assert!(out.contains(".Finish(m.Run()"), "{out}");
969    }
970}