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