reexec: add reexectest package - #211
Conversation
1b6866e to
fe0a892
Compare
5e3a915 to
9046543
Compare
|
Hmm does it only work against test binaries created by |
|
Yup, should still work; adding a quick patch to show the output; diff --git a/reexec/reexectest/reexectest_test.go b/reexec/reexectest/reexectest_test.go
index c8dd5cd..1b57bf3 100644
--- a/reexec/reexectest/reexectest_test.go
+++ b/reexec/reexectest/reexectest_test.go
@@ -33,6 +33,8 @@ func TestRun(t *testing.T) {
}
if got := strings.TrimSpace(strings.TrimSuffix(string(out), "PASS\n")); got != expected {
t.Errorf("env-and-output output: got %q, want %q", got, expected)
+ } else {
+ t.Logf("env-and-output output: got %q", out)
}
})Then run; go test -v -run TestRun
=== RUN TestRun
=== RUN TestRun/env-and-output
reexectest_test.go:37: env-and-output output: got "child-env-and-output-ok\nPASS\n"
=== RUN TestRun/exit-code
=== RUN TestRun/args-passthrough
=== RUN TestRun/context
--- PASS: TestRun (0.05s)
--- PASS: TestRun/env-and-output (0.02s)
--- PASS: TestRun/exit-code (0.01s)
--- PASS: TestRun/args-passthrough (0.01s)
--- PASS: TestRun/context (0.01s)
=== RUN TestRunNonSubtest
--- PASS: TestRunNonSubtest (0.01s)
PASS
ok github.com/moby/sys/reexec/reexectest 0.401s |
This package allows using the reexec functionality to execute child processes as part of a test. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
9046543 to
845a186
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Argument handling is incorrect and the package is incompatible with its declared minimum Go version.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds test helpers for re-executing the current test binary as a controlled child process.
Changes:
- Adds child dispatch and command helpers.
- Adds Linux parent-death signaling.
- Adds subprocess behavior tests.
File summaries
| File | Description |
|---|---|
reexec/reexectest/reexectest.go |
Implements the public helpers. |
reexec/reexectest/reexectest_test.go |
Tests subprocess behavior. |
reexec/reexectest/reexectest_linux.go |
Configures Linux Pdeathsig. |
reexec/reexectest/reexectest_other.go |
Provides non-Linux behavior. |
Review details
Suppressed comments (1)
reexec/reexectest/reexectest.go:107
- User arguments that begin with
-are parsed by the test binary before the selected test runs. Unknown flags abort the child, while recognized-test.*flags can alter or override its execution, so this command cannot reliably pass CLI flags tof. Add a--separator before user arguments and updateRunto scrub that separator together with the injected-test.runargument.
cmd.Args = append([]string{argv0, "-test.run=" + pattern}, args...)
- Files reviewed: 4/4 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // It is a convenience wrapper around [CommandContext] using [testing.T.Context] | ||
| // as context. | ||
| func Command(t *testing.T, name string, args ...string) *exec.Cmd { | ||
| return commandContext(t, t.Context(), name, args...) |
| return | ||
| } | ||
|
|
||
| cmd := reexectest.CommandContext(t, t.Context(), "context") |
| // Scrub the "-test.run=<pattern>" that was injected by CommandContext | ||
| origArgs := os.Args | ||
| if len(os.Args) > 1 && strings.HasPrefix(os.Args[1], "-test.run=") { | ||
| os.Args = append(os.Args[:1], os.Args[2:]...) |
vvoland
left a comment
There was a problem hiding this comment.
AI review:
[High] Anchor every -test.run path component
reexec/reexectest/reexectest.go:103
Go’s test runner splits -test.run expressions at unbracketed /. For a subtest named TestTarget/child, the generated expression:
^TestTarget/child$
is treated as separate ^TestTarget and child$ expressions. The first also matches TestTargetExtra, so TestTargetExtra/child runs in the re-exec child. Its Run call rejects the
child because argv[0] differs, after which its parent branch can execute and start further subprocesses.
I reproduced this deterministically: targeting TestTarget/child produced output from both the intended test and TestTargetExtra/child.
Construct an exact expression for each slash-delimited test-name component rather than anchoring the complete name only.
[High] Preserve the module’s Go 1.20 compatibility
reexec/reexectest/reexectest.go:73
testing.T.Context was added in Go 1.24, while reexec/go.mod declares Go 1.20. Consequently, consumers cannot compile the new package with Go 1.20–1.23.
A Go 1.20 container reported:
reexectest/reexectest.go:73:29: t.Context undefined
The green CI matrix does not catch this because its Go 1.18 job skips the reexec module and oldstable is already new enough. This is also raised in an existing review thread.
[High] Separate test-runner flags from child arguments
reexec/reexectest/reexectest.go:107
The test binary parses child arguments beginning with - before the selected test runs. For example:
reexectest.Command(t, "worker", "-user-flag")exits with status 2:
flag provided but not defined: -user-flag
Recognized -test.* arguments can instead alter the child test runner, including overriding test selection. Add a -- terminator before user arguments and have Run remove both the
injected test flag and separator. This was noted in the existing Copilot review summary.
[Medium] Do not corrupt the saved os.Args
reexec/reexectest/reexectest.go:59
origArgs := os.Args preserves only the slice header. The subsequent append reuses the same backing array because the result is shorter, overwriting the arguments that origArgs is meant
to restore. With child arguments, deferred cleanup restores a list with the test flag removed and the final argument duplicated.
Allocate a separate scrubbed slice before assigning it to os.Args. This is also covered by an existing review thread.
This package allows using the reexec functionality to execute child processes as part of a test.