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