Files
goutils/tee/tee_test.go
Kyle Isom e3a6355edb tee: add tests; linter fixes.
Additionally, disable reassign in testing files.
2025-11-15 20:18:09 -08:00

198 lines
4.4 KiB
Go

package tee_test
import (
"io"
"os"
"path/filepath"
"testing"
tee "git.wntrmute.dev/kyle/goutils/tee"
)
// captureStdout redirects os.Stdout for the duration of fn and returns what was written.
func captureStdout(t *testing.T, fn func()) string {
t.Helper()
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("pipe: %v", err)
}
old := os.Stdout
os.Stdout = w
defer func() { os.Stdout = old }()
fn()
// Close writer to unblock reader and restore stdout
_ = w.Close()
b, _ := io.ReadAll(r)
_ = r.Close()
return string(b)
}
func TestNewOutEmpty_WritesToStdoutOnly(t *testing.T) {
teeInst, err := tee.NewOut("")
if err != nil {
t.Fatalf("NewOut: %v", err)
}
out := captureStdout(t, func() {
var n int
if n, err = teeInst.Write([]byte("abc")); err != nil || n != 3 {
t.Fatalf("Write got n=%d err=%v", n, err)
}
if n, err = teeInst.Printf("-%d-", 7); err != nil || n != len("-7-") {
t.Fatalf("Printf got n=%d err=%v", n, err)
}
})
if out != "abc-7-" {
t.Fatalf("stdout = %q, want %q", out, "abc-7-")
}
}
func TestNewOutWithFile_WritesToBoth(t *testing.T) {
dir := t.TempDir()
logPath := filepath.Join(dir, "log.txt")
teeInst, err := tee.NewOut(logPath)
if err != nil {
t.Fatalf("NewOut: %v", err)
}
defer func() { _ = teeInst.Close() }()
out := captureStdout(t, func() {
if _, err = teeInst.Write([]byte("x")); err != nil {
t.Fatalf("Write: %v", err)
}
if _, err = teeInst.Printf("%s", "y"); err != nil {
t.Fatalf("Printf: %v", err)
}
})
if out != "xy" {
t.Fatalf("stdout = %q, want %q", out, "xy")
}
// Close to flush and release the file before reading
if err = teeInst.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
data, err := os.ReadFile(logPath)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if string(data) != "xy" {
t.Fatalf("file content = %q, want %q", string(data), "xy")
}
}
func TestVPrintf_VerboseToggle(t *testing.T) {
teeInst := &tee.Tee{} // stdout only
out := captureStdout(t, func() {
if n, err := teeInst.VPrintf("hello"); err != nil || n != 0 {
t.Fatalf("VPrintf (quiet) got n=%d err=%v", n, err)
}
})
if out != "" {
t.Fatalf("stdout = %q, want empty when not verbose", out)
}
teeInst.Verbose = true
out = captureStdout(t, func() {
if n, err := teeInst.VPrintf("%s", "hello"); err != nil || n != len("hello") {
t.Fatalf("VPrintf (verbose) got n=%d err=%v", n, err)
}
})
if out != "hello" {
t.Fatalf("stdout = %q, want %q", out, "hello")
}
}
func TestWrite_StdoutErrorDoesNotWriteToFile(t *testing.T) {
dir := t.TempDir()
logPath := filepath.Join(dir, "log.txt")
teeInst, err := tee.NewOut(logPath)
if err != nil {
t.Fatalf("NewOut: %v", err)
}
defer func() { _ = teeInst.Close() }()
// Replace stdout with a closed pipe writer to force write error.
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("pipe: %v", err)
}
old := os.Stdout
os.Stdout = w
_ = w.Close() // immediately close to cause EPIPE on write
defer func() {
os.Stdout = old
_ = r.Close()
}()
var n int
if n, err = teeInst.Write([]byte("abc")); err == nil {
t.Fatalf("expected error writing to closed stdout, got n=%d err=nil", n)
}
// Ensure file remained empty because stdout write failed first.
_ = teeInst.Close()
data, err := os.ReadFile(logPath)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if len(data) != 0 {
t.Fatalf("file content = %q, want empty due to stdout failure", string(data))
}
}
func TestGlobal_OpenPrintfVPrintfClose(t *testing.T) {
// Ensure a clean slate for global tee
_ = tee.Close()
tee.SetVerbose(false)
dir := t.TempDir()
logPath := filepath.Join(dir, "glog.txt")
if err := tee.Open(logPath); err != nil {
t.Fatalf("Open: %v", err)
}
out := captureStdout(t, func() {
if _, err := tee.Printf("A"); err != nil {
t.Fatalf("Printf: %v", err)
}
// Not verbose yet, should not print
if n, err := tee.VPrintf("B"); err != nil || n != 0 {
t.Fatalf("VPrintf (quiet) n=%d err=%v", n, err)
}
tee.SetVerbose(true)
if _, err := tee.VPrintf("C%d", 1); err != nil {
t.Fatalf("VPrintf (verbose): %v", err)
}
})
if out != "AC1" {
t.Fatalf("stdout = %q, want %q", out, "AC1")
}
if err := tee.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
data, err := os.ReadFile(logPath)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if string(data) != "AC1" {
t.Fatalf("file content = %q, want %q", string(data), "AC1")
}
// Reset global tee for other tests/packages
_ = tee.Close()
tee.SetVerbose(false)
}