...

Source file src/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go

Documentation: github.com/onsi/ginkgo/v2/internal

     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  	// Important, set the fds to FD_CLOEXEC to prevent them leaking into childs
    30  	// https://github.com/onsi/ginkgo/issues/1191
    31  	flags, err := unix.FcntlInt(uintptr(stdoutCloneFD), unix.F_GETFD, 0)
    32  	if err == nil {
    33  		unix.FcntlInt(uintptr(stdoutCloneFD), unix.F_SETFD, flags|unix.FD_CLOEXEC)
    34  	}
    35  	flags, err = unix.FcntlInt(uintptr(stderrCloneFD), unix.F_GETFD, 0)
    36  	if err == nil {
    37  		unix.FcntlInt(uintptr(stderrCloneFD), unix.F_SETFD, flags|unix.FD_CLOEXEC)
    38  	}
    39  
    40  	// And then wrap the clone file descriptors in files.
    41  	// One benefit of this (that we don't use yet) is that we can actually write
    42  	// to these files to emit output to the console even though we're intercepting output
    43  	stdoutClone := os.NewFile(uintptr(stdoutCloneFD), "stdout-clone")
    44  	stderrClone := os.NewFile(uintptr(stderrCloneFD), "stderr-clone")
    45  
    46  	//these clones remain alive throughout the lifecycle of the suite and don't need to be recreated
    47  	//this speeds things up a bit, actually.
    48  	return stdoutClone, stderrClone
    49  }
    50  
    51  func (impl *dupSyscallOutputInterceptorImpl) ConnectPipeToStdoutStderr(pipeWriter *os.File) {
    52  	// 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)
    53  	// to the write end of the pipe.
    54  	// 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
    55  	// This effectively shunts data written to stdout and stderr to the write end of our pipe
    56  	unix.Dup2(int(pipeWriter.Fd()), 1)
    57  	unix.Dup2(int(pipeWriter.Fd()), 2)
    58  }
    59  
    60  func (impl *dupSyscallOutputInterceptorImpl) RestoreStdoutStderrFromClones(stdoutClone *os.File, stderrClone *os.File) {
    61  	// To restore stdour/stderr from the clones we have the 1 and 2 file descriptors
    62  	// point to the original file descriptions that we saved off in the clones.
    63  	// This has the added benefit of closing the connection between these descriptors and the write end of the pipe
    64  	// which is important to cause the io.Copy on the pipe.Reader to end.
    65  	unix.Dup2(int(stdoutClone.Fd()), 1)
    66  	unix.Dup2(int(stderrClone.Fd()), 2)
    67  }
    68  
    69  func (impl *dupSyscallOutputInterceptorImpl) ShutdownClones(stdoutClone *os.File, stderrClone *os.File) {
    70  	// We're done with the clones so we can close them to clean up after ourselves
    71  	stdoutClone.Close()
    72  	stderrClone.Close()
    73  }
    74  

View as plain text