From 8c2236e586105da24b07c6bcab233628ef42bbed Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 2 Apr 2025 21:17:09 +0200 Subject: [PATCH] internal/testlog: refactor test-case to be table-driven. adapt the test-logger to take T as an interface to enable log output capture in the unit test. --- internal/testlog/testlog.go | 17 +++++--- internal/testlog/testlog_test.go | 67 ++++++++++++++++++++++++++++---- tests/testdata | 2 +- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/internal/testlog/testlog.go b/internal/testlog/testlog.go index e58eb5e2a2..24015ef40a 100644 --- a/internal/testlog/testlog.go +++ b/internal/testlog/testlog.go @@ -21,23 +21,30 @@ import ( "bytes" "context" "fmt" + "github.com/ethereum/go-ethereum/log" "log/slog" "sync" - "testing" - - "github.com/ethereum/go-ethereum/log" ) const ( termTimeFormat = "01-02|15:04:05.000" ) +// T wraps methods from testing.T used by the test logger into an interface. +// It is specified so that unit tests can instantiate the logger with an +// implementation of T which can capture the output of logging statements +// from T.Logf, as this cannot be using testing.T. +type T interface { + Logf(format string, args ...any) + Helper() +} + // logger implements log.Logger such that all output goes to the unit test log via // t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test // helpers, so the file and line number in unit test output correspond to the call site // which emitted the log message. type logger struct { - t *testing.T + t T l log.Logger mu *sync.Mutex h *bufHandler @@ -78,7 +85,7 @@ func (h *bufHandler) WithGroup(_ string) slog.Handler { } // Logger returns a logger which logs to the unit test log of t. -func Logger(t *testing.T, level slog.Level) log.Logger { +func Logger(t T, level slog.Level) log.Logger { handler := bufHandler{ buf: []slog.Record{}, attrs: []slog.Attr{}, diff --git a/internal/testlog/testlog_test.go b/internal/testlog/testlog_test.go index af131bfe6a..8d461c55ed 100644 --- a/internal/testlog/testlog_test.go +++ b/internal/testlog/testlog_test.go @@ -1,16 +1,69 @@ package testlog import ( + "bytes" + "fmt" + "io" + "strings" "testing" "github.com/ethereum/go-ethereum/log" ) -func TestLogging(t *testing.T) { - l := Logger(t, log.LevelInfo) - subLogger := l.New("foobar", 123) - - l.Info("Visible") - subLogger.Info("Hide and seek") // this log is erroneously hidden in master, but fixed with this PR - l.Info("Also visible") +type mockT struct { + out io.Writer +} + +func (t *mockT) Helper() { + // noop for the purposes of unit tests +} + +func (t *mockT) Logf(format string, args ...any) { + // we could gate this operation in a mutex, but because testlogger + // only calls Logf with its internal mutex held, we just write output here + var lineBuf bytes.Buffer + if _, err := fmt.Fprintf(&lineBuf, fmt.Sprintf(format, args...)); err != nil { + panic(err) + } + sanitized := strings.Split(lineBuf.String(), "]")[1] + if _, err := t.out.Write([]byte(sanitized)); err != nil { + panic(err) + } +} + +func TestLogging(t *testing.T) { + tests := []struct { + name string + expected string + run func(t *mockT) + }{ + { + "SubLogger", + ` Visible + Hide and seek foobar=123 + Also visible +`, + func(t *mockT) { + l := Logger(t, log.LevelInfo) + subLogger := l.New("foobar", 123) + + l.Info("Visible") + subLogger.Info("Hide and seek") + l.Info("Also visible") + }, + }, + } + + for _, tc := range tests { + outp := bytes.Buffer{} + mock := mockT{&outp} + tc.run(&mock) + if outp.String() != tc.expected { + fmt.Println("mismatch") + fmt.Printf("'%s'\n", outp.String()) + fmt.Println("----") + fmt.Printf("'%s'\n", tc.expected) + } + } + } diff --git a/tests/testdata b/tests/testdata index 81862e4848..faf33b4714 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 81862e4848585a438d64f911a19b3825f0f4cd95 +Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2