...

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