blob: f2de0a67ef793eec3a0de8852f75c4d9e5a42016 [file] [log] [blame]
# Regression test for https://go.dev/issue/24050:
# a test that exits with an I/O stream held open
# should fail after a reasonable delay, not wait forever.
# (As of the time of writing, that delay is 10% of the timeout,
# but this test does not depend on its specific value.)
[short] skip 'runs a test that hangs until its WaitDelay expires'
! go test -v -timeout=1m .
# After the test process itself prints PASS and exits,
# the kernel closes its stdin pipe to to the orphaned subprocess.
# At that point, we expect the subprocess to print 'stdin closed'
# and periodically log to stderr until the WaitDelay expires.
#
# Once the WaitDelay expires, the copying goroutine for 'go test' stops and
# closes the read side of the stderr pipe, and the subprocess will eventually
# exit due to a failed write to that pipe.
stdout '^--- PASS: TestOrphanCmd .*\nPASS\nstdin closed'
stdout '^\*\*\* Test I/O incomplete \d+.* after exiting\.\nexec: WaitDelay expired before I/O complete\nFAIL\s+example\s+\d+(\.\d+)?s'
-- go.mod --
module example
go 1.20
-- main_test.go --
package main
import (
"fmt"
"io"
"os"
"os/exec"
"testing"
"time"
)
func TestMain(m *testing.M) {
if os.Getenv("TEST_TIMEOUT_HANG") == "1" {
io.Copy(io.Discard, os.Stdin)
if _, err := os.Stderr.WriteString("stdin closed\n"); err != nil {
os.Exit(1)
}
ticker := time.NewTicker(100 * time.Millisecond)
for t := range ticker.C {
_, err := fmt.Fprintf(os.Stderr, "still alive at %v\n", t)
if err != nil {
os.Exit(1)
}
}
}
m.Run()
}
func TestOrphanCmd(t *testing.T) {
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(exe)
cmd.Env = append(cmd.Environ(), "TEST_TIMEOUT_HANG=1")
// Hold stdin open until this (parent) process exits.
if _, err := cmd.StdinPipe(); err != nil {
t.Fatal(err)
}
// Forward stderr to the subprocess so that it can hold the stream open.
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
t.Logf("started %v", cmd)
// Intentionally leak cmd when the test completes.
// This will allow the test process itself to exit, but (at least on Unix
// platforms) will keep the parent process's stderr stream open.
go func() {
if err := cmd.Wait(); err != nil {
os.Exit(3)
}
}()
}