github.com/c9s/go@v0.0.0-20180120015821-984e81f64e0c/src/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 t.Fatal(err) 76 } 77 defer master.Close() 78 slave, err := os.OpenFile(sname, os.O_RDWR, 0) 79 if err != nil { 80 t.Fatal(err) 81 } 82 defer slave.Close() 83 84 // Start an interactive shell. 85 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 86 defer cancel() 87 cmd := exec.CommandContext(ctx, bash, "--norc", "--noprofile", "-i") 88 cmd.Stdin = slave 89 cmd.Stdout = slave 90 cmd.Stderr = slave 91 cmd.SysProcAttr = &syscall.SysProcAttr{ 92 Setsid: true, 93 Setctty: true, 94 Ctty: int(slave.Fd()), 95 } 96 97 if err := cmd.Start(); err != nil { 98 t.Fatal(err) 99 } 100 101 if err := slave.Close(); err != nil { 102 t.Errorf("closing slave: %v", err) 103 } 104 105 progReady := make(chan bool) 106 sawPrompt := make(chan bool, 10) 107 const prompt = "prompt> " 108 109 // Read data from master in the background. 110 go func() { 111 input := bufio.NewReader(master) 112 var line, handled []byte 113 for { 114 b, err := input.ReadByte() 115 if err != nil { 116 if len(line) > 0 || len(handled) > 0 { 117 t.Logf("%q", append(handled, line...)) 118 } 119 if perr, ok := err.(*os.PathError); ok { 120 err = perr.Err 121 } 122 // EOF means master is closed. 123 // EIO means child process is done. 124 // "file already closed" means deferred close of master has happened. 125 if err != io.EOF && err != syscall.EIO && !strings.Contains(err.Error(), "file already closed") { 126 t.Logf("error reading from master: %v", err) 127 } 128 return 129 } 130 131 line = append(line, b) 132 133 if b == '\n' { 134 t.Logf("%q", append(handled, line...)) 135 line = nil 136 handled = nil 137 continue 138 } 139 140 if bytes.Contains(line, []byte(enteringRead)) { 141 close(progReady) 142 handled = append(handled, line...) 143 line = nil 144 } else if bytes.Contains(line, []byte(prompt)) && !bytes.Contains(line, []byte("PS1=")) { 145 sawPrompt <- true 146 handled = append(handled, line...) 147 line = nil 148 } 149 } 150 }() 151 152 // Set the bash prompt so that we can see it. 153 if _, err := master.Write([]byte("PS1='" + prompt + "'\n")); err != nil { 154 t.Fatalf("setting prompt: %v", err) 155 } 156 select { 157 case <-sawPrompt: 158 case <-time.After(wait): 159 t.Fatal("timed out waiting for shell prompt") 160 } 161 162 // Start a small program that reads from stdin 163 // (namely the code at the top of this function). 164 if _, err := master.Write([]byte("GO_TEST_TERMINAL_SIGNALS=1 " + os.Args[0] + " -test.run=TestTerminalSignal\n")); err != nil { 165 t.Fatal(err) 166 } 167 168 // Wait for the program to print that it is starting. 169 select { 170 case <-progReady: 171 case <-time.After(wait): 172 t.Fatal("timed out waiting for program to start") 173 } 174 175 // Give the program time to enter the read call. 176 // It doesn't matter much if we occasionally don't wait long enough; 177 // we won't be testing what we want to test, but the overall test 178 // will pass. 179 time.Sleep(pause) 180 181 // Send a ^Z to stop the program. 182 if _, err := master.Write([]byte{26}); err != nil { 183 t.Fatalf("writing ^Z to pty: %v", err) 184 } 185 186 // Wait for the program to stop and return to the shell. 187 select { 188 case <-sawPrompt: 189 case <-time.After(wait): 190 t.Fatal("timed out waiting for shell prompt") 191 } 192 193 // Restart the stopped program. 194 if _, err := master.Write([]byte("fg\n")); err != nil { 195 t.Fatalf("writing %q to pty: %v", "fg", err) 196 } 197 198 // Give the process time to restart. 199 // This is potentially racy: if the process does not restart 200 // quickly enough then the byte we send will go to bash rather 201 // than the program. Unfortunately there isn't anything we can 202 // look for to know that the program is running again. 203 // bash will print the program name, but that happens before it 204 // restarts the program. 205 time.Sleep(10 * pause) 206 207 // Write some data for the program to read, 208 // which should cause it to exit. 209 if _, err := master.Write([]byte{'\n'}); err != nil { 210 t.Fatalf("writing %q to pty: %v", "\n", err) 211 } 212 213 // Wait for the program to exit. 214 select { 215 case <-sawPrompt: 216 case <-time.After(wait): 217 t.Fatal("timed out waiting for shell prompt") 218 } 219 220 // Exit the shell with the program's exit status. 221 if _, err := master.Write([]byte("exit $?\n")); err != nil { 222 t.Fatalf("writing %q to pty: %v", "exit", err) 223 } 224 225 if err = cmd.Wait(); err != nil { 226 t.Errorf("subprogram failed: %v", err) 227 } 228 }