From 752ef404c47039d48f054dba250b658b9332f957 Mon Sep 17 00:00:00 2001 From: Adam Luzsi Date: Mon, 14 Oct 2024 20:13:35 +0200 Subject: [PATCH] add support to pretty print into a file Sometimes like during Fuzz testing, stdout is supressed, make it difficult to debug. --- pp/Diff_test.go | 30 ++++++++++++++++++---- pp/PP.go | 2 -- pp/PP_test.go | 18 ++++++++----- pp/README.md | 17 +++++++++--- pp/default.go | 34 ++++++++++++++++++++++++ pp/default_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 pp/default.go create mode 100644 pp/default_test.go diff --git a/pp/Diff_test.go b/pp/Diff_test.go index 4ee97a4..9b78170 100644 --- a/pp/Diff_test.go +++ b/pp/Diff_test.go @@ -27,7 +27,7 @@ func TestDiff_smoke(t *testing.T) { exp := strings.TrimSpace(DiffOutput) got := strings.TrimSpace(buf.String()) - mustEqual(t, exp, got) + assertEqual(t, exp, got) } func TestDiffFormat_smoke(t *testing.T) { @@ -35,7 +35,7 @@ func TestDiffFormat_smoke(t *testing.T) { v1 := X{A: 1, B: 2} v2 := X{A: 2, B: 2} tr := strings.TrimSpace - mustEqual(t, tr(DiffOutput), tr(DiffFormat(v1, v2))) + assertEqual(t, tr(DiffOutput), tr(DiffFormat(v1, v2))) } const DiffStringA = ` @@ -76,7 +76,7 @@ func TestPrettyPrinter_DiffString_smoke(t *testing.T) { exp := tr(DiffStringOut) act := tr(got) t.Logf("\n\nexpected:\n%s\n\nactual:\n%s", exp, act) - mustEqual(t, exp, act) + assertEqual(t, exp, act) }) tr := func(str string) string { var strs []string @@ -140,14 +140,34 @@ func TestPrettyPrinter_DiffString_smoke(t *testing.T) { tc := tc t.Run(tc.Desc, func(t *testing.T) { diff := DiffString(tr(tc.A), tr(tc.B)) - mustEqual(t, tr(tc.Diff), tr(diff)) + assertEqual(t, tr(tc.Diff), tr(diff)) }) } } -func mustEqual(tb testing.TB, exp string, act string) { +func assertEqual(tb testing.TB, exp string, act string) { tb.Helper() if act != exp { tb.Fatalf("exp and got not equal: \n\nexpected:\n%s\n\nactual:\n%s\n", exp, act) } } + +func assertNoError(tb testing.TB, err error) { + if err != nil { + tb.Fatalf("expected no error but got: %s", err.Error()) + } +} + +func assertNotEmpty[T any](tb testing.TB, vs []T) { + tb.Helper() + if len(vs) == 0 { + tb.Fatal("expected that slice is not empty") + } +} + +func assertEmpty[T any](tb testing.TB, vs []T) { + tb.Helper() + if len(vs) != 0 { + tb.Fatalf("expected that slice is empty, but got %d elements in it", len(vs)) + } +} diff --git a/pp/PP.go b/pp/PP.go index d8fcf3d..9d623a6 100644 --- a/pp/PP.go +++ b/pp/PP.go @@ -3,7 +3,6 @@ package pp import ( "fmt" "io" - "os" "runtime" "strings" "sync" @@ -11,7 +10,6 @@ import ( "go.llib.dev/testcase/internal/caller" ) -var defaultWriter io.Writer = os.Stderr var l sync.Mutex func PP(vs ...any) { diff --git a/pp/PP_test.go b/pp/PP_test.go index 0be8f09..f3a2b96 100644 --- a/pp/PP_test.go +++ b/pp/PP_test.go @@ -25,15 +25,11 @@ func TestFPP(t *testing.T) { act := buf.String() - mustEqual(t, exp, act) + assertEqual(t, exp, act) } func TestPP(t *testing.T) { - ogw := defaultWriter - defer func() { defaultWriter = ogw }() - - buf := &bytes.Buffer{} - defaultWriter = buf + buf := stubDefaultWriter(t) v1 := time.Date(2022, time.July, 26, 17, 36, 19, 882377000, time.UTC) v2 := "bar" @@ -45,5 +41,13 @@ func TestPP(t *testing.T) { filepath.Base(file), line+1) act := buf.String() - mustEqual(t, exp, act) + assertEqual(t, exp, act) +} + +func stubDefaultWriter(tb testing.TB) *bytes.Buffer { + ogw := defaultWriter + tb.Cleanup(func() { defaultWriter = ogw }) + buf := &bytes.Buffer{} + defaultWriter = buf + return buf } diff --git a/pp/README.md b/pp/README.md index 369d189..11d095f 100644 --- a/pp/README.md +++ b/pp/README.md @@ -2,9 +2,10 @@ - [Pretty Print (PP)](#pretty-print-pp) - - [usage](#usage) - - [PP / Format](#pp--format) - - [Diff](#diff) + - [usage](#usage) + - [PP / Format](#pp--format) + - [Diff](#diff) + - [printing into a file](#printing-into-a-file) @@ -80,9 +81,17 @@ func main(t *testing.T) { > output in GNU diff side-by-side style -``` +```go pp_test.ExampleStruct{ pp_test.ExampleStruct{ A: "The Answer", | A: "The Question", B: 42, B: 42, } ``` + +## printing into a file + +If STDOUT is supressed, you can also instruct PP to print into a file by setting the output file path in the `PP` environment variable. + +```shell +export PP="out.txt" +``` diff --git a/pp/default.go b/pp/default.go new file mode 100644 index 0000000..4dc4b35 --- /dev/null +++ b/pp/default.go @@ -0,0 +1,34 @@ +package pp + +import ( + "io" + "os" +) + +func init() { + initDefaultWriter() +} + +var defaultWriter io.Writer = os.Stderr + +func initDefaultWriter() { + fpath, ok := os.LookupEnv("PP") + if !ok { + return + } + if fpath == "" { + return + } + stat, err := os.Stat(fpath) + if err != nil && !os.IsNotExist(err) { + return + } + if stat != nil && stat.IsDir() { + return + } + out, err := os.OpenFile(fpath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + panic(err) + } + defaultWriter = out +} diff --git a/pp/default_test.go b/pp/default_test.go new file mode 100644 index 0000000..aa69bd8 --- /dev/null +++ b/pp/default_test.go @@ -0,0 +1,64 @@ +package pp + +import ( + "io" + "os" + "path/filepath" + "testing" + + "go.llib.dev/testcase/internal/env" +) + +func Test_envPP(t *testing.T) { + defer initDefaultWriter() + + t.Run("when not supplied", func(t *testing.T) { + env.UnsetEnv(t, "PP") + buf := stubDefaultWriter(t) + initDefaultWriter() + PP("OK") + assertNotEmpty(t, buf.Bytes()) + }) + + t.Run("when provided but empty", func(t *testing.T) { + env.SetEnv(t, "PP", "") + buf := stubDefaultWriter(t) + initDefaultWriter() + PP("OK") + assertNotEmpty(t, buf.Bytes()) + }) + + t.Run("when provided but not a valid file path", func(t *testing.T) { + env.SetEnv(t, "PP", ".") + buf := stubDefaultWriter(t) + initDefaultWriter() + PP("OK") + assertNotEmpty(t, buf.Bytes()) + }) + + t.Run("when existing file provided", func(t *testing.T) { + f, err := os.CreateTemp(t.TempDir(), "") + assertNoError(t, err) + env.SetEnv(t, "PP", f.Name()) + buf := stubDefaultWriter(t) + initDefaultWriter() + PP("OK") + assertEmpty(t, buf.Bytes()) + bs, err := io.ReadAll(f) + assertNoError(t, err) + assertNotEmpty(t, bs) + }) + + t.Run("when non existing file provided", func(t *testing.T) { + fpath := filepath.Join(t.TempDir(), "test.txt") + env.SetEnv(t, "PP", fpath) + buf := stubDefaultWriter(t) + initDefaultWriter() + PP("OK") + assertEmpty(t, buf.Bytes()) + bs, err := os.ReadFile(fpath) + assertNoError(t, err) + assertNotEmpty(t, bs) + }) + +}