diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go index 8a237f446374..a4da430e0da3 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go @@ -9,6 +9,24 @@ import ( "golang.org/x/sys/unix" ) +// dupStdout creates a clone of stdout's file descriptor that can be used +// to write to the original terminal even after stdout has been redirected. +// Returns nil if the clone cannot be created. +func dupStdout() *os.File { + stdoutCloneFD, err := unix.Dup(1) + if err != nil { + return nil + } + + // Set FD_CLOEXEC to prevent leaking into child processes + flags, err := unix.FcntlInt(uintptr(stdoutCloneFD), unix.F_GETFD, 0) + if err == nil { + unix.FcntlInt(uintptr(stdoutCloneFD), unix.F_SETFD, flags|unix.FD_CLOEXEC) + } + + return os.NewFile(uintptr(stdoutCloneFD), "stdout-clone-for-forwarding") +} + func NewOutputInterceptor() OutputInterceptor { return &genericOutputInterceptor{ interceptedContent: make(chan string), diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_wasm.go b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_wasm.go index 4c374935b8a5..3cffcb534027 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_wasm.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_wasm.go @@ -2,6 +2,13 @@ package internal +import "os" + +// dupStdout returns nil on WASM since output interception is not supported. +func dupStdout() *os.File { + return nil +} + func NewOutputInterceptor() OutputInterceptor { return &NoopOutputInterceptor{} } diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go index 30c2851a8188..710cbe04ae9c 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go @@ -2,6 +2,15 @@ package internal +import "os" + +// dupStdout returns the current os.Stdout. On Windows, the output interceptor +// uses osGlobalReassigning which only changes the Go variable, so capturing +// the current os.Stdout before interception starts is sufficient. +func dupStdout() *os.File { + return os.Stdout +} + func NewOutputInterceptor() OutputInterceptor { return NewOSGlobalReassigningOutputInterceptor() } diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/suite_patch.go b/vendor/github.com/onsi/ginkgo/v2/internal/suite_patch.go index 29eae0283249..c6d1f9acc013 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/suite_patch.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/suite_patch.go @@ -1,6 +1,8 @@ package internal import ( + "io" + "os" "time" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" @@ -8,6 +10,70 @@ import ( "github.com/onsi/ginkgo/v2/types" ) +// ForwardingOutputInterceptor wraps a real OutputInterceptor but forwards +// captured output to stdout in real-time. This allows stdout/stderr to be +// captured in test results while still showing output during test execution. +type ForwardingOutputInterceptor struct { + interceptor OutputInterceptor + stdoutClone *os.File +} + +// NewForwardingOutputInterceptor creates an output interceptor that captures +// stdout/stderr while also forwarding it to a clone of stdout in real-time. +// The stdout clone is created using dupStdout() (platform-specific) before +// the interceptor redirects output, ensuring output is always forwarded to +// the original terminal. +func NewForwardingOutputInterceptor() *ForwardingOutputInterceptor { + // Create a clone of stdout BEFORE the interceptor can redirect it. + // This ensures we always have a handle to the original terminal for + // forwarding output. The dupStdout() function is platform-specific. + stdoutClone := dupStdout() + if stdoutClone == nil { + // If we can't dup stdout, fall back to NoopOutputInterceptor behavior + return &ForwardingOutputInterceptor{ + interceptor: NoopOutputInterceptor{}, + stdoutClone: nil, + } + } + + return &ForwardingOutputInterceptor{ + interceptor: NewOutputInterceptor(), + stdoutClone: stdoutClone, + } +} + +func (f *ForwardingOutputInterceptor) StartInterceptingOutput() { + if f.stdoutClone != nil { + f.interceptor.StartInterceptingOutputAndForwardTo(f.stdoutClone) + } else { + f.interceptor.StartInterceptingOutput() + } +} + +func (f *ForwardingOutputInterceptor) StartInterceptingOutputAndForwardTo(w io.Writer) { + f.interceptor.StartInterceptingOutputAndForwardTo(w) +} + +func (f *ForwardingOutputInterceptor) StopInterceptingAndReturnOutput() string { + return f.interceptor.StopInterceptingAndReturnOutput() +} + +func (f *ForwardingOutputInterceptor) PauseIntercepting() { + f.interceptor.PauseIntercepting() +} + +func (f *ForwardingOutputInterceptor) ResumeIntercepting() { + f.interceptor.ResumeIntercepting() +} + +func (f *ForwardingOutputInterceptor) Shutdown() { + f.interceptor.Shutdown() + if f.stdoutClone != nil { + f.stdoutClone.Close() + f.stdoutClone = nil + } +} + type AnnotateFunc func(testName string, test types.TestSpec) func (suite *Suite) SetAnnotateFn(fn AnnotateFunc) { @@ -58,7 +124,7 @@ func (suite *Suite) RunSpec(spec types.TestSpec, suiteLabels Labels, suiteDescri suite.failer = failer suite.reporter = reporters.NewDefaultReporter(reporterConfig, writer) suite.writer = writer - suite.outputInterceptor = NoopOutputInterceptor{} + suite.outputInterceptor = NewForwardingOutputInterceptor() if suite.config.Timeout > 0 { suite.deadline = time.Now().Add(suiteConfig.Timeout) }