summaryrefslogtreecommitdiffstats
path: root/src/os/signal/signal_cgo_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/os/signal/signal_cgo_test.go')
-rw-r--r--src/os/signal/signal_cgo_test.go239
1 files changed, 239 insertions, 0 deletions
diff --git a/src/os/signal/signal_cgo_test.go b/src/os/signal/signal_cgo_test.go
new file mode 100644
index 0000000..67bad66
--- /dev/null
+++ b/src/os/signal/signal_cgo_test.go
@@ -0,0 +1,239 @@
+// 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.
+
+//go:build (darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd) && 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"
+ "io/fs"
+ "os"
+ "os/exec"
+ ptypkg "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.
+
+ pty, procTTYName, err := ptypkg.Open()
+ if err != nil {
+ ptyErr := err.(*ptypkg.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 pty.Close()
+ procTTY, err := os.OpenFile(procTTYName, os.O_RDWR, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer procTTY.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 = procTTY
+ cmd.Stdout = procTTY
+ cmd.Stderr = procTTY
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setsid: true,
+ Setctty: true,
+ Ctty: 0,
+ }
+
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := procTTY.Close(); err != nil {
+ t.Errorf("closing procTTY: %v", err)
+ }
+
+ progReady := make(chan bool)
+ sawPrompt := make(chan bool, 10)
+ const prompt = "prompt> "
+
+ // Read data from pty in the background.
+ var wg sync.WaitGroup
+ wg.Add(1)
+ defer wg.Wait()
+ go func() {
+ defer wg.Done()
+ input := bufio.NewReader(pty)
+ 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.(*fs.PathError); ok {
+ err = perr.Err
+ }
+ // EOF means pty is closed.
+ // EIO means child process is done.
+ // "file already closed" means deferred close of pty has happened.
+ if err != io.EOF && err != syscall.EIO && !strings.Contains(err.Error(), "file already closed") {
+ t.Logf("error reading from pty: %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 := pty.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 := pty.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 := pty.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 := pty.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 := pty.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 := pty.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)
+ }
+}