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 }