github.com/comwrg/go/src@v0.0.0-20220319063731-c238d0440370/os/signal/signal_cgo_test.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build (darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd) && cgo 6 // +build darwin dragonfly freebsd linux,!android netbsd openbsd 7 // +build cgo 8 9 // Note that this test does not work on Solaris: issue #22849. 10 // Don't run the test on Android because at least some versions of the 11 // C library do not define the posix_openpt function. 12 13 package signal_test 14 15 import ( 16 "bufio" 17 "bytes" 18 "context" 19 "fmt" 20 "io" 21 "io/fs" 22 "os" 23 "os/exec" 24 ptypkg "os/signal/internal/pty" 25 "strconv" 26 "strings" 27 "sync" 28 "syscall" 29 "testing" 30 "time" 31 ) 32 33 func TestTerminalSignal(t *testing.T) { 34 const enteringRead = "test program entering read" 35 if os.Getenv("GO_TEST_TERMINAL_SIGNALS") != "" { 36 var b [1]byte 37 fmt.Println(enteringRead) 38 n, err := os.Stdin.Read(b[:]) 39 if n == 1 { 40 if b[0] == '\n' { 41 // This is what we expect 42 fmt.Println("read newline") 43 } else { 44 fmt.Printf("read 1 byte: %q\n", b) 45 } 46 } else { 47 fmt.Printf("read %d bytes\n", n) 48 } 49 if err != nil { 50 fmt.Println(err) 51 os.Exit(1) 52 } 53 os.Exit(0) 54 } 55 56 t.Parallel() 57 58 // The test requires a shell that uses job control. 59 bash, err := exec.LookPath("bash") 60 if err != nil { 61 t.Skipf("could not find bash: %v", err) 62 } 63 64 scale := 1 65 if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { 66 if sc, err := strconv.Atoi(s); err == nil { 67 scale = sc 68 } 69 } 70 pause := time.Duration(scale) * 10 * time.Millisecond 71 wait := time.Duration(scale) * 5 * time.Second 72 73 // The test only fails when using a "slow device," in this 74 // case a pseudo-terminal. 75 76 pty, procTTYName, err := ptypkg.Open() 77 if err != nil { 78 ptyErr := err.(*ptypkg.PtyError) 79 if ptyErr.FuncName == "posix_openpt" && ptyErr.Errno == syscall.EACCES { 80 t.Skip("posix_openpt failed with EACCES, assuming chroot and skipping") 81 } 82 t.Fatal(err) 83 } 84 defer pty.Close() 85 procTTY, err := os.OpenFile(procTTYName, os.O_RDWR, 0) 86 if err != nil { 87 t.Fatal(err) 88 } 89 defer procTTY.Close() 90 91 // Start an interactive shell. 92 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 93 defer cancel() 94 cmd := exec.CommandContext(ctx, bash, "--norc", "--noprofile", "-i") 95 // Clear HISTFILE so that we don't read or clobber the user's bash history. 96 cmd.Env = append(os.Environ(), "HISTFILE=") 97 cmd.Stdin = procTTY 98 cmd.Stdout = procTTY 99 cmd.Stderr = procTTY 100 cmd.SysProcAttr = &syscall.SysProcAttr{ 101 Setsid: true, 102 Setctty: true, 103 Ctty: 0, 104 } 105 106 if err := cmd.Start(); err != nil { 107 t.Fatal(err) 108 } 109 110 if err := procTTY.Close(); err != nil { 111 t.Errorf("closing procTTY: %v", err) 112 } 113 114 progReady := make(chan bool) 115 sawPrompt := make(chan bool, 10) 116 const prompt = "prompt> " 117 118 // Read data from pty in the background. 119 var wg sync.WaitGroup 120 wg.Add(1) 121 defer wg.Wait() 122 go func() { 123 defer wg.Done() 124 input := bufio.NewReader(pty) 125 var line, handled []byte 126 for { 127 b, err := input.ReadByte() 128 if err != nil { 129 if len(line) > 0 || len(handled) > 0 { 130 t.Logf("%q", append(handled, line...)) 131 } 132 if perr, ok := err.(*fs.PathError); ok { 133 err = perr.Err 134 } 135 // EOF means pty is closed. 136 // EIO means child process is done. 137 // "file already closed" means deferred close of pty has happened. 138 if err != io.EOF && err != syscall.EIO && !strings.Contains(err.Error(), "file already closed") { 139 t.Logf("error reading from pty: %v", err) 140 } 141 return 142 } 143 144 line = append(line, b) 145 146 if b == '\n' { 147 t.Logf("%q", append(handled, line...)) 148 line = nil 149 handled = nil 150 continue 151 } 152 153 if bytes.Contains(line, []byte(enteringRead)) { 154 close(progReady) 155 handled = append(handled, line...) 156 line = nil 157 } else if bytes.Contains(line, []byte(prompt)) && !bytes.Contains(line, []byte("PS1=")) { 158 sawPrompt <- true 159 handled = append(handled, line...) 160 line = nil 161 } 162 } 163 }() 164 165 // Set the bash prompt so that we can see it. 166 if _, err := pty.Write([]byte("PS1='" + prompt + "'\n")); err != nil { 167 t.Fatalf("setting prompt: %v", err) 168 } 169 select { 170 case <-sawPrompt: 171 case <-time.After(wait): 172 t.Fatal("timed out waiting for shell prompt") 173 } 174 175 // Start a small program that reads from stdin 176 // (namely the code at the top of this function). 177 if _, err := pty.Write([]byte("GO_TEST_TERMINAL_SIGNALS=1 " + os.Args[0] + " -test.run=TestTerminalSignal\n")); err != nil { 178 t.Fatal(err) 179 } 180 181 // Wait for the program to print that it is starting. 182 select { 183 case <-progReady: 184 case <-time.After(wait): 185 t.Fatal("timed out waiting for program to start") 186 } 187 188 // Give the program time to enter the read call. 189 // It doesn't matter much if we occasionally don't wait long enough; 190 // we won't be testing what we want to test, but the overall test 191 // will pass. 192 time.Sleep(pause) 193 194 // Send a ^Z to stop the program. 195 if _, err := pty.Write([]byte{26}); err != nil { 196 t.Fatalf("writing ^Z to pty: %v", err) 197 } 198 199 // Wait for the program to stop and return to the shell. 200 select { 201 case <-sawPrompt: 202 case <-time.After(wait): 203 t.Fatal("timed out waiting for shell prompt") 204 } 205 206 // Restart the stopped program. 207 if _, err := pty.Write([]byte("fg\n")); err != nil { 208 t.Fatalf("writing %q to pty: %v", "fg", err) 209 } 210 211 // Give the process time to restart. 212 // This is potentially racy: if the process does not restart 213 // quickly enough then the byte we send will go to bash rather 214 // than the program. Unfortunately there isn't anything we can 215 // look for to know that the program is running again. 216 // bash will print the program name, but that happens before it 217 // restarts the program. 218 time.Sleep(10 * pause) 219 220 // Write some data for the program to read, 221 // which should cause it to exit. 222 if _, err := pty.Write([]byte{'\n'}); err != nil { 223 t.Fatalf("writing %q to pty: %v", "\n", err) 224 } 225 226 // Wait for the program to exit. 227 select { 228 case <-sawPrompt: 229 case <-time.After(wait): 230 t.Fatal("timed out waiting for shell prompt") 231 } 232 233 // Exit the shell with the program's exit status. 234 if _, err := pty.Write([]byte("exit $?\n")); err != nil { 235 t.Fatalf("writing %q to pty: %v", "exit", err) 236 } 237 238 if err = cmd.Wait(); err != nil { 239 t.Errorf("subprogram failed: %v", err) 240 } 241 }