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

     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  
    20  stdout '^--- PASS: TestOrphanCmd .*\nPASS\nstdin closed'
    21  stdout '^\*\*\* 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 --
    24  module example
    25  
    26  go 1.20
    27  -- main_test.go --
    28  package main
    29  
    30  import (
    31  	"fmt"
    32  	"io"
    33  	"os"
    34  	"os/exec"
    35  	"testing"
    36  	"time"
    37  )
    38  
    39  func 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  
    58  func 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  }
    89  

View as plain text