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