Black Lives Matter. Support the Equal Justice Initiative.

Source file src/os/signal/signal_cgo_test.go

Documentation: os/signal

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

View as plain text