|  | // Copyright 2017 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // +build darwin dragonfly freebsd linux,!android netbsd openbsd | 
|  | // +build cgo | 
|  |  | 
|  | // Note that this test does not work on Solaris: issue #22849. | 
|  | // Don't run the test on Android because at least some versions of the | 
|  | // C library do not define the posix_openpt function. | 
|  |  | 
|  | package signal_test | 
|  |  | 
|  | import ( | 
|  | "bufio" | 
|  | "bytes" | 
|  | "context" | 
|  | "fmt" | 
|  | "io" | 
|  | "os" | 
|  | "os/exec" | 
|  | "os/signal/internal/pty" | 
|  | "strconv" | 
|  | "strings" | 
|  | "sync" | 
|  | "syscall" | 
|  | "testing" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | func TestTerminalSignal(t *testing.T) { | 
|  | const enteringRead = "test program entering read" | 
|  | if os.Getenv("GO_TEST_TERMINAL_SIGNALS") != "" { | 
|  | var b [1]byte | 
|  | fmt.Println(enteringRead) | 
|  | n, err := os.Stdin.Read(b[:]) | 
|  | if n == 1 { | 
|  | if b[0] == '\n' { | 
|  | // This is what we expect | 
|  | fmt.Println("read newline") | 
|  | } else { | 
|  | fmt.Printf("read 1 byte: %q\n", b) | 
|  | } | 
|  | } else { | 
|  | fmt.Printf("read %d bytes\n", n) | 
|  | } | 
|  | if err != nil { | 
|  | fmt.Println(err) | 
|  | os.Exit(1) | 
|  | } | 
|  | os.Exit(0) | 
|  | } | 
|  |  | 
|  | t.Parallel() | 
|  |  | 
|  | // The test requires a shell that uses job control. | 
|  | bash, err := exec.LookPath("bash") | 
|  | if err != nil { | 
|  | t.Skipf("could not find bash: %v", err) | 
|  | } | 
|  |  | 
|  | scale := 1 | 
|  | if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { | 
|  | if sc, err := strconv.Atoi(s); err == nil { | 
|  | scale = sc | 
|  | } | 
|  | } | 
|  | pause := time.Duration(scale) * 10 * time.Millisecond | 
|  | wait := time.Duration(scale) * 5 * time.Second | 
|  |  | 
|  | // The test only fails when using a "slow device," in this | 
|  | // case a pseudo-terminal. | 
|  |  | 
|  | master, sname, err := pty.Open() | 
|  | if err != nil { | 
|  | ptyErr := err.(*pty.PtyError) | 
|  | if ptyErr.FuncName == "posix_openpt" && ptyErr.Errno == syscall.EACCES { | 
|  | t.Skip("posix_openpt failed with EACCES, assuming chroot and skipping") | 
|  | } | 
|  | t.Fatal(err) | 
|  | } | 
|  | defer master.Close() | 
|  | slave, err := os.OpenFile(sname, os.O_RDWR, 0) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | defer slave.Close() | 
|  |  | 
|  | // Start an interactive shell. | 
|  | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | 
|  | defer cancel() | 
|  | cmd := exec.CommandContext(ctx, bash, "--norc", "--noprofile", "-i") | 
|  | // Clear HISTFILE so that we don't read or clobber the user's bash history. | 
|  | cmd.Env = append(os.Environ(), "HISTFILE=") | 
|  | cmd.Stdin = slave | 
|  | cmd.Stdout = slave | 
|  | cmd.Stderr = slave | 
|  | cmd.SysProcAttr = &syscall.SysProcAttr{ | 
|  | Setsid:  true, | 
|  | Setctty: true, | 
|  | Ctty:    int(slave.Fd()), | 
|  | } | 
|  |  | 
|  | if err := cmd.Start(); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | if err := slave.Close(); err != nil { | 
|  | t.Errorf("closing slave: %v", err) | 
|  | } | 
|  |  | 
|  | progReady := make(chan bool) | 
|  | sawPrompt := make(chan bool, 10) | 
|  | const prompt = "prompt> " | 
|  |  | 
|  | // Read data from master in the background. | 
|  | var wg sync.WaitGroup | 
|  | wg.Add(1) | 
|  | defer wg.Wait() | 
|  | go func() { | 
|  | defer wg.Done() | 
|  | input := bufio.NewReader(master) | 
|  | var line, handled []byte | 
|  | for { | 
|  | b, err := input.ReadByte() | 
|  | if err != nil { | 
|  | if len(line) > 0 || len(handled) > 0 { | 
|  | t.Logf("%q", append(handled, line...)) | 
|  | } | 
|  | if perr, ok := err.(*os.PathError); ok { | 
|  | err = perr.Err | 
|  | } | 
|  | // EOF means master is closed. | 
|  | // EIO means child process is done. | 
|  | // "file already closed" means deferred close of master has happened. | 
|  | if err != io.EOF && err != syscall.EIO && !strings.Contains(err.Error(), "file already closed") { | 
|  | t.Logf("error reading from master: %v", err) | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | line = append(line, b) | 
|  |  | 
|  | if b == '\n' { | 
|  | t.Logf("%q", append(handled, line...)) | 
|  | line = nil | 
|  | handled = nil | 
|  | continue | 
|  | } | 
|  |  | 
|  | if bytes.Contains(line, []byte(enteringRead)) { | 
|  | close(progReady) | 
|  | handled = append(handled, line...) | 
|  | line = nil | 
|  | } else if bytes.Contains(line, []byte(prompt)) && !bytes.Contains(line, []byte("PS1=")) { | 
|  | sawPrompt <- true | 
|  | handled = append(handled, line...) | 
|  | line = nil | 
|  | } | 
|  | } | 
|  | }() | 
|  |  | 
|  | // Set the bash prompt so that we can see it. | 
|  | if _, err := master.Write([]byte("PS1='" + prompt + "'\n")); err != nil { | 
|  | t.Fatalf("setting prompt: %v", err) | 
|  | } | 
|  | select { | 
|  | case <-sawPrompt: | 
|  | case <-time.After(wait): | 
|  | t.Fatal("timed out waiting for shell prompt") | 
|  | } | 
|  |  | 
|  | // Start a small program that reads from stdin | 
|  | // (namely the code at the top of this function). | 
|  | if _, err := master.Write([]byte("GO_TEST_TERMINAL_SIGNALS=1 " + os.Args[0] + " -test.run=TestTerminalSignal\n")); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | // Wait for the program to print that it is starting. | 
|  | select { | 
|  | case <-progReady: | 
|  | case <-time.After(wait): | 
|  | t.Fatal("timed out waiting for program to start") | 
|  | } | 
|  |  | 
|  | // Give the program time to enter the read call. | 
|  | // It doesn't matter much if we occasionally don't wait long enough; | 
|  | // we won't be testing what we want to test, but the overall test | 
|  | // will pass. | 
|  | time.Sleep(pause) | 
|  |  | 
|  | // Send a ^Z to stop the program. | 
|  | if _, err := master.Write([]byte{26}); err != nil { | 
|  | t.Fatalf("writing ^Z to pty: %v", err) | 
|  | } | 
|  |  | 
|  | // Wait for the program to stop and return to the shell. | 
|  | select { | 
|  | case <-sawPrompt: | 
|  | case <-time.After(wait): | 
|  | t.Fatal("timed out waiting for shell prompt") | 
|  | } | 
|  |  | 
|  | // Restart the stopped program. | 
|  | if _, err := master.Write([]byte("fg\n")); err != nil { | 
|  | t.Fatalf("writing %q to pty: %v", "fg", err) | 
|  | } | 
|  |  | 
|  | // Give the process time to restart. | 
|  | // This is potentially racy: if the process does not restart | 
|  | // quickly enough then the byte we send will go to bash rather | 
|  | // than the program. Unfortunately there isn't anything we can | 
|  | // look for to know that the program is running again. | 
|  | // bash will print the program name, but that happens before it | 
|  | // restarts the program. | 
|  | time.Sleep(10 * pause) | 
|  |  | 
|  | // Write some data for the program to read, | 
|  | // which should cause it to exit. | 
|  | if _, err := master.Write([]byte{'\n'}); err != nil { | 
|  | t.Fatalf("writing %q to pty: %v", "\n", err) | 
|  | } | 
|  |  | 
|  | // Wait for the program to exit. | 
|  | select { | 
|  | case <-sawPrompt: | 
|  | case <-time.After(wait): | 
|  | t.Fatal("timed out waiting for shell prompt") | 
|  | } | 
|  |  | 
|  | // Exit the shell with the program's exit status. | 
|  | if _, err := master.Write([]byte("exit $?\n")); err != nil { | 
|  | t.Fatalf("writing %q to pty: %v", "exit", err) | 
|  | } | 
|  |  | 
|  | if err = cmd.Wait(); err != nil { | 
|  | t.Errorf("subprogram failed: %v", err) | 
|  | } | 
|  | } |