diff --git a/engine.go b/engine.go index 3f8b613..f413baf 100644 --- a/engine.go +++ b/engine.go @@ -12,6 +12,7 @@ import ( "encoding/binary" "flag" "fmt" + "io" "log" "os" "path/filepath" @@ -778,6 +779,31 @@ func (t *T) Log(args ...any) { } } +// Output returns a Writer that writes to the same test output stream as T.Log. +// The output is indented like T.Log lines, but Output does not +// add source locations or newlines. The output is internally line +// buffered, and a call to T.Log or the end of the test will implicitly +// flush the buffer, followed by a newline. After a test function and all its +// parents return, neither Output nor the Write method may be called. +// +// Only available on Go >= 1.25 +func (t *T) Output() io.Writer { + t.Helper() + + if t.rawLog != nil { + return t.rawLog.Writer() + } else if t.tbLog { + if tout, ok := t.tb.(interface{ Output() io.Writer }); ok { + return tout.Output() + } else { + t.Fatal("[rapid] Output requires Go 1.25 or newer") + return nil + } + } else { + return io.Discard + } +} + // Skipf is equivalent to [T.Logf] followed by [T.SkipNow]. func (t *T) Skipf(format string, args ...any) { if t.tbLog { diff --git a/engine_test.go b/engine_test.go index fcee09b..c92d1e6 100644 --- a/engine_test.go +++ b/engine_test.go @@ -7,6 +7,7 @@ package rapid import ( + "bytes" "context" "errors" "reflect" @@ -251,6 +252,20 @@ func TestCheckCleanupContextCreatedInCleanup(t *testing.T) { }) } +func TestOutputRawLog(t *testing.T) { + t.Parallel() + + msg := []byte("Hello World") + + out := captureTestOutput(t, func(t *T) { + t.Output().Write(msg) + }, nil) + + if !bytes.Contains(out, msg) { + t.Errorf("expected output to contain %q, got: %q", msg, out) + } +} + // ignoreErrorsTB is a TB that ignores all errors posted to it. type ignoreErrorsTB struct{ TB }