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  }