From ec48a92065f2324fe21e0b9156067c0e53fe843a Mon Sep 17 00:00:00 2001 From: riccardom Date: Thu, 1 Oct 2026 13:28:34 +0200 Subject: [PATCH] [client] Cover the cert proof helper path the deadline actually cuts short The existing case has the helper exit on its own, which reports ErrWaitDelay. The daemon meets the other shape: the process it launches is a launcher, the work happens in a grandchild, and the deadline kills the launcher while the grandchild holds the inherited pipe. Wait then reports the kill and the wait delay error is dropped, so the two paths through Cmd.Wait differ and only one of them was exercised. --- client/internal/certproof/helper_run_test.go | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/client/internal/certproof/helper_run_test.go b/client/internal/certproof/helper_run_test.go index 8a893f903..b3c07cfcd 100644 --- a/client/internal/certproof/helper_run_test.go +++ b/client/internal/certproof/helper_run_test.go @@ -3,6 +3,7 @@ package certproof import ( + "context" "encoding/json" "fmt" "os/exec" @@ -89,6 +90,32 @@ func TestRunHelperCmd_ReturnsWhenAGrandchildHoldsTheOutputPipe(t *testing.T) { } } +func TestRunHelperCmd_ReturnsWhenTheDeadlineKillsTheLauncher(t *testing.T) { + req := HelperRequest{Challenges: []HelperChallenge{{Nonce: []byte("asked")}}} + + // The shape the daemon actually meets on macOS: the process it launches is only a + // launcher, the work happens in a grandchild, and the deadline reaps the launcher + // while the grandchild keeps the stdout it inherited open. Unlike the case above + // the process is killed, so Wait reports that rather than ErrWaitDelay. + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer cancel() + cmd := exec.CommandContext(ctx, "/bin/sh", "-c", "cat >/dev/null; sleep 10 & sleep 10") + + done := make(chan error, 1) + go func() { + proofs, err := runHelperCmd(cmd, req) + assert.Empty(t, proofs, "a run the deadline cut short has nothing trustworthy to report") + done <- err + }() + + select { + case err := <-done: + require.Error(t, err, "the run was cut short, so it must not be reported as a success") + case <-time.After(5 * time.Second): + t.Fatal("runHelperCmd never returned after the deadline killed the launcher, so the collector's busy latch would stay set for the life of the daemon") + } +} + func TestRunHelperCmd_RejectsGarbage(t *testing.T) { req := HelperRequest{Challenges: []HelperChallenge{{Nonce: []byte("asked")}}}