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.

This commit is contained in:
Jared Wasinger 2025-04-02 21:17:09 +02:00
parent 8f1a56bb4e
commit 8c2236e586
3 changed files with 73 additions and 13 deletions

View file

@ -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{},

View file

@ -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)
}
}
}

@ -1 +1 @@
Subproject commit 81862e4848585a438d64f911a19b3825f0f4cd95
Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2