github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/internal/output_interceptor_unix.go (about)

     1  //go:build freebsd || openbsd || netbsd || dragonfly || darwin || linux || solaris
     2  // +build freebsd openbsd netbsd dragonfly darwin linux solaris
     3  
     4  package internal
     5  
     6  import (
     7  	"os"
     8  
     9  	"golang.org/x/sys/unix"
    10  )
    11  
    12  func NewOutputInterceptor() OutputInterceptor {
    13  	return &genericOutputInterceptor{
    14  		interceptedContent: make(chan string),
    15  		pipeChannel:        make(chan pipePair),
    16  		shutdown:           make(chan interface{}),
    17  		implementation:     &dupSyscallOutputInterceptorImpl{},
    18  	}
    19  }
    20  
    21  type dupSyscallOutputInterceptorImpl struct{}
    22  
    23  func (impl *dupSyscallOutputInterceptorImpl) CreateStdoutStderrClones() (*os.File, *os.File) {
    24  	// To clone stdout and stderr we:
    25  	// First, create two clone file descriptors that point to the stdout and stderr file descriptions
    26  	stdoutCloneFD, _ := unix.Dup(1)
    27  	stderrCloneFD, _ := unix.Dup(2)
    28  
    29  	// And then wrap the clone file descriptors in files.
    30  	// One benefit of this (that we don't use yet) is that we can actually write
    31  	// to these files to emit output to the console evne though we're intercepting output
    32  	stdoutClone := os.NewFile(uintptr(stdoutCloneFD), "stdout-clone")
    33  	stderrClone := os.NewFile(uintptr(stderrCloneFD), "stderr-clone")
    34  
    35  	//these clones remain alive throughout the lifecycle of the suite and don't need to be recreated
    36  	//this speeds things up a bit, actually.
    37  	return stdoutClone, stderrClone
    38  }
    39  
    40  func (impl *dupSyscallOutputInterceptorImpl) ConnectPipeToStdoutStderr(pipeWriter *os.File) {
    41  	// To redirect output to our pipe we need to point the 1 and 2 file descriptors (which is how the world tries to log things)
    42  	// to the write end of the pipe.
    43  	// We do this with Dup2 (possibly Dup3 on some architectures) to have file descriptors 1 and 2 point to the same file description as the pipeWriter
    44  	// This effectively shunts data written to stdout and stderr to the write end of our pipe
    45  	unix.Dup2(int(pipeWriter.Fd()), 1)
    46  	unix.Dup2(int(pipeWriter.Fd()), 2)
    47  }
    48  
    49  func (impl *dupSyscallOutputInterceptorImpl) RestoreStdoutStderrFromClones(stdoutClone *os.File, stderrClone *os.File) {
    50  	// To restore stdour/stderr from the clones we have the 1 and 2 file descriptors
    51  	// point to the original file descriptions that we saved off in the clones.
    52  	// This has the added benefit of closing the connection between these descriptors and the write end of the pipe
    53  	// which is important to cause the io.Copy on the pipe.Reader to end.
    54  	unix.Dup2(int(stdoutClone.Fd()), 1)
    55  	unix.Dup2(int(stderrClone.Fd()), 2)
    56  }
    57  
    58  func (impl *dupSyscallOutputInterceptorImpl) ShutdownClones(stdoutClone *os.File, stderrClone *os.File) {
    59  	// We're done with the clones so we can close them to clean up after ourselves
    60  	stdoutClone.Close()
    61  	stderrClone.Close()
    62  }