Skip to content

Commit

Permalink
testr: use an interface to make it work with *testing.B and *testing.…
Browse files Browse the repository at this point in the history
…F in addition to *testing.T
  • Loading branch information
jeandeaual committed Jul 10, 2022
1 parent 47e013c commit 4f1beee
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 13 deletions.
99 changes: 87 additions & 12 deletions testr/testr.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ func NewWithOptions(t *testing.T, opts Options) logr.Logger {
return logr.New(l)
}

// TestingT is an interface wrapper around testing.T, testing.B and testing.F.
type TestingT interface {
Helper()
Log(args ...interface{})
}

// NewWithInterface returns a logr.Logger that prints through a
// TestingT object.
// In contrast to the simpler New, output formatting can be configured.
func NewWithInterface(t TestingT, opts Options) logr.Logger {
l := &testloggerInterface{
Formatter: funcr.NewFormatter(funcr.Options{
LogTimestamp: opts.LogTimestamp,
Verbosity: opts.Verbosity,
}),
t: t,
}
return logr.New(l)
}

// Underlier exposes access to the underlying testing.T instance. Since
// callers only have a logr.Logger, they have to know which
// implementation is in use, so this interface is less of an
Expand All @@ -67,6 +87,34 @@ type Underlier interface {
GetUnderlying() *testing.T
}

// UnderlierInterface exposes access to the underlying TestingT instance. Since
// callers only have a logr.Logger, they have to know which
// implementation is in use, so this interface is less of an
// abstraction and more of a way to test type conversion.
type UnderlierInterface interface {
GetUnderlying() TestingT
}

// Info logging implementation shared between testLogger and testLoggerInterface.
func logInfo(t TestingT, formatInfo func(int, string, []interface{}) (string, string), level int, msg string, kvList ...interface{}) {
prefix, args := formatInfo(level, msg, kvList)
t.Helper()
if prefix != "" {
args = prefix + ": " + args
}
t.Log(args)
}

// Error logging implementation shared between testLogger and testLoggerInterface.
func logError(t TestingT, formatError func(error, string, []interface{}) (string, string), err error, msg string, kvList ...interface{}) {
prefix, args := formatError(err, msg, kvList)
t.Helper()
if prefix != "" {
args = prefix + ": " + args
}
t.Log(args)
}

type testlogger struct {
funcr.Formatter
t *testing.T
Expand All @@ -87,30 +135,57 @@ func (l testlogger) GetCallStackHelper() func() {
}

func (l testlogger) Info(level int, msg string, kvList ...interface{}) {
prefix, args := l.FormatInfo(level, msg, kvList)
l.t.Helper()
if prefix != "" {
l.t.Logf("%s: %s", prefix, args)
} else {
l.t.Log(args)
}
logInfo(l.t, l.FormatInfo, level, msg, kvList...)
}

func (l testlogger) Error(err error, msg string, kvList ...interface{}) {
prefix, args := l.FormatError(err, msg, kvList)
l.t.Helper()
if prefix != "" {
l.t.Logf("%s: %s", prefix, args)
} else {
l.t.Log(args)
}
logError(l.t, l.FormatError, err, msg, kvList...)
}

func (l testlogger) GetUnderlying() *testing.T {
return l.t
}

type testloggerInterface struct {
funcr.Formatter
t TestingT
}

func (l testloggerInterface) WithName(name string) logr.LogSink {
l.Formatter.AddName(name)
return &l
}

func (l testloggerInterface) WithValues(kvList ...interface{}) logr.LogSink {
l.Formatter.AddValues(kvList)
return &l
}

func (l testloggerInterface) GetCallStackHelper() func() {
return l.t.Helper
}

func (l testloggerInterface) Info(level int, msg string, kvList ...interface{}) {
l.t.Helper()
logInfo(l.t, l.FormatInfo, level, msg, kvList...)
}

func (l testloggerInterface) Error(err error, msg string, kvList ...interface{}) {
l.t.Helper()
logError(l.t, l.FormatError, err, msg, kvList...)
}

func (l testloggerInterface) GetUnderlying() TestingT {
return l.t
}

// Assert conformance to the interfaces.
var _ logr.LogSink = &testlogger{}
var _ logr.CallStackHelperLogSink = &testlogger{}
var _ Underlier = &testlogger{}

var _ logr.LogSink = &testloggerInterface{}
var _ logr.CallStackHelperLogSink = &testloggerInterface{}
var _ UnderlierInterface = &testloggerInterface{}
34 changes: 33 additions & 1 deletion testr/testr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,46 @@ func TestLogger(t *testing.T) {
log.V(0).Info("V(0).info")
log.V(1).Info("v(1).info")
log.Error(fmt.Errorf("error"), "error")
log.WithName("testing").Info("with prefix")
log.WithName("testing").WithValues("value", "test").Info("with prefix")
log.WithName("testing").Error(fmt.Errorf("error"), "with prefix")
Helper(log, "hello world")

log = NewWithOptions(t, Options{
LogTimestamp: true,
Verbosity: 1,
})
log.V(1).Info("v(1).info with options")

underlier, ok := log.GetSink().(Underlier)
if !ok {
t.Error("couldn't get underlier")
}
if t != underlier.GetUnderlying() {
t.Error("invalid underlier")
}
}

func TestLoggerInterface(t *testing.T) {
log := NewWithInterface(t, Options{})
log.Info("info")
log.V(0).Info("V(0).info")
log.V(1).Info("v(1).info")
log.Error(fmt.Errorf("error"), "error")
log.WithName("testing").WithValues("value", "test").Info("with prefix")
log.WithName("testing").Error(fmt.Errorf("error"), "with prefix")
Helper(log, "hello world")

underlier, ok := log.GetSink().(UnderlierInterface)
if !ok {
t.Fatal("couldn't get underlier")
}
underlierT, ok := underlier.GetUnderlying().(*testing.T)
if !ok {
t.Fatal("couldn't get underlying *testing.T")
}
if t != underlierT {
t.Error("invalid underlier")
}
}

func Helper(log logr.Logger, msg string) {
Expand Down

0 comments on commit 4f1beee

Please sign in to comment.