...

Text file src/cmd/go/testdata/script/test_timeout_stdin.txt

Documentation: cmd/go/testdata/script

     1# Regression test for https://go.dev/issue/24050:
     2# a test that exits with an I/O stream held open
     3# should fail after a reasonable delay, not wait forever.
     4# (As of the time of writing, that delay is 10% of the timeout,
     5# but this test does not depend on its specific value.)
     6
     7[short] skip 'runs a test that hangs until its WaitDelay expires'
     8
     9! go test -v -timeout=1m .
    10
    11	# After the test process itself prints PASS and exits,
    12	# the kernel closes its stdin pipe to to the orphaned subprocess.
    13	# At that point, we expect the subprocess to print 'stdin closed'
    14	# and periodically log to stderr until the WaitDelay expires.
    15	#
    16	# Once the WaitDelay expires, the copying goroutine for 'go test' stops and
    17	# closes the read side of the stderr pipe, and the subprocess will eventually
    18	# exit due to a failed write to that pipe.
    19
    20stdout '^--- PASS: TestOrphanCmd .*\nPASS\nstdin closed'
    21stdout '^\*\*\* Test I/O incomplete \d+.* after exiting\.\nexec: WaitDelay expired before I/O complete\nFAIL\s+example\s+\d+(\.\d+)?s'
    22
    23-- go.mod --
    24module example
    25
    26go 1.20
    27-- main_test.go --
    28package main
    29
    30import (
    31	"fmt"
    32	"io"
    33	"os"
    34	"os/exec"
    35	"testing"
    36	"time"
    37)
    38
    39func TestMain(m *testing.M) {
    40	if os.Getenv("TEST_TIMEOUT_HANG") == "1" {
    41		io.Copy(io.Discard, os.Stdin)
    42		if _, err := os.Stderr.WriteString("stdin closed\n"); err != nil {
    43			os.Exit(1)
    44		}
    45
    46		ticker := time.NewTicker(100 * time.Millisecond)
    47		for t := range ticker.C {
    48			_, err := fmt.Fprintf(os.Stderr, "still alive at %v\n", t)
    49			if err != nil {
    50				os.Exit(1)
    51			}
    52		}
    53	}
    54
    55	m.Run()
    56}
    57
    58func TestOrphanCmd(t *testing.T) {
    59	exe, err := os.Executable()
    60	if err != nil {
    61		t.Fatal(err)
    62	}
    63
    64	cmd := exec.Command(exe)
    65	cmd.Env = append(cmd.Environ(), "TEST_TIMEOUT_HANG=1")
    66
    67	// Hold stdin open until this (parent) process exits.
    68	if _, err := cmd.StdinPipe(); err != nil {
    69		t.Fatal(err)
    70	}
    71
    72	// Forward stderr to the subprocess so that it can hold the stream open.
    73	cmd.Stderr = os.Stderr
    74
    75	if err := cmd.Start(); err != nil {
    76		t.Fatal(err)
    77	}
    78	t.Logf("started %v", cmd)
    79
    80	// Intentionally leak cmd when the test completes.
    81	// This will allow the test process itself to exit, but (at least on Unix
    82	// platforms) will keep the parent process's stderr stream open.
    83	go func() {
    84		if err := cmd.Wait(); err != nil {
    85			os.Exit(3)
    86		}
    87	}()
    88}

View as plain text