mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			235 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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"
 | |
| 	"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.
 | |
| 	go func() {
 | |
| 		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)
 | |
| 	}
 | |
| }
 |