Source file src/cmd/go/terminal_test.go

     1  // Copyright 2016 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  package main_test
     6  
     7  import (
     8  	"errors"
     9  	"internal/testenv"
    10  	"internal/testpty"
    11  	"io"
    12  	"os"
    13  	"testing"
    14  
    15  	"golang.org/x/term"
    16  )
    17  
    18  func TestTerminalPassthrough(t *testing.T) {
    19  	t.Parallel()
    20  
    21  	// Check that if 'go test' is run with a terminal connected to stdin/stdout,
    22  	// then the go command passes that terminal down to the test binary
    23  	// invocation (rather than, e.g., putting a pipe in the way).
    24  	//
    25  	// See issue 18153.
    26  	testenv.MustHaveGoBuild(t)
    27  
    28  	// Start with a "self test" to make sure that if we *don't* pass in a
    29  	// terminal, the test can correctly detect that. (cmd/go doesn't guarantee
    30  	// that it won't add a terminal in the middle, but that would be pretty weird.)
    31  	t.Run("pipe", func(t *testing.T) {
    32  		t.Parallel()
    33  		r, w, err := os.Pipe()
    34  		if err != nil {
    35  			t.Fatalf("pipe failed: %s", err)
    36  		}
    37  		defer r.Close()
    38  		defer w.Close()
    39  		stdout, stderr := runTerminalPassthrough(t, r, w)
    40  		if stdout {
    41  			t.Errorf("stdout is unexpectedly a terminal")
    42  		}
    43  		if stderr {
    44  			t.Errorf("stderr is unexpectedly a terminal")
    45  		}
    46  	})
    47  
    48  	// Now test with a read PTY.
    49  	t.Run("pty", func(t *testing.T) {
    50  		t.Parallel()
    51  		r, processTTY, err := testpty.Open()
    52  		if errors.Is(err, testpty.ErrNotSupported) {
    53  			t.Skipf("%s", err)
    54  		} else if err != nil {
    55  			t.Fatalf("failed to open test PTY: %s", err)
    56  		}
    57  		defer r.Close()
    58  		w, err := os.OpenFile(processTTY, os.O_RDWR, 0)
    59  		if err != nil {
    60  			t.Fatal(err)
    61  		}
    62  		defer w.Close()
    63  		stdout, stderr := runTerminalPassthrough(t, r, w)
    64  		if !stdout {
    65  			t.Errorf("stdout is not a terminal")
    66  		}
    67  		if !stderr {
    68  			t.Errorf("stderr is not a terminal")
    69  		}
    70  	})
    71  }
    72  
    73  func runTerminalPassthrough(t *testing.T, r, w *os.File) (stdout, stderr bool) {
    74  	cmd := testenv.Command(t, testGo, "test", "-run=^$")
    75  	cmd.Env = append(cmd.Environ(), "GO_TEST_TERMINAL_PASSTHROUGH=1")
    76  	cmd.Stdout = w
    77  	cmd.Stderr = w
    78  
    79  	// The behavior of reading from a PTY after the child closes it is very
    80  	// strange: on Linux, Read returns EIO, and on at least some versions of
    81  	// macOS, unread output may be discarded (see https://go.dev/issue/57141).
    82  	//
    83  	// To avoid that situation, we keep the child process running until the
    84  	// parent has finished reading from the PTY, at which point we unblock the
    85  	// child by closing its stdin pipe.
    86  	stdin, err := cmd.StdinPipe()
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  
    91  	t.Logf("running %s", cmd)
    92  	err = cmd.Start()
    93  	if err != nil {
    94  		t.Fatalf("starting subprocess: %s", err)
    95  	}
    96  	w.Close()
    97  	t.Cleanup(func() {
    98  		stdin.Close()
    99  		if err := cmd.Wait(); err != nil {
   100  			t.Errorf("suprocess failed with: %s", err)
   101  		}
   102  	})
   103  
   104  	buf := make([]byte, 2)
   105  	n, err := io.ReadFull(r, buf)
   106  	if err != nil || !(buf[0] == '1' || buf[0] == 'X') || !(buf[1] == '2' || buf[1] == 'X') {
   107  		t.Logf("read error: %v", err)
   108  		t.Fatalf("expected 2 bytes matching `[1X][2X]`; got %q", buf[:n])
   109  	}
   110  	return buf[0] == '1', buf[1] == '2'
   111  }
   112  
   113  func init() {
   114  	if os.Getenv("GO_TEST_TERMINAL_PASSTHROUGH") == "" {
   115  		return
   116  	}
   117  
   118  	if term.IsTerminal(1) {
   119  		os.Stdout.WriteString("1")
   120  	} else {
   121  		os.Stdout.WriteString("X")
   122  	}
   123  	if term.IsTerminal(2) {
   124  		os.Stdout.WriteString("2")
   125  	} else {
   126  		os.Stdout.WriteString("X")
   127  	}
   128  
   129  	// Before exiting, wait for the parent process to read the PTY output,
   130  	// at which point it will close stdin.
   131  	io.Copy(io.Discard, os.Stdin)
   132  
   133  	os.Exit(0)
   134  }
   135  

View as plain text