Skip to content

Commit 3734877

Browse files
authored
feat: custom mock logger (#21)
Adds a `WithLogger` method to the Mock clock that allows customising the logger used for debug output, instead of always using `testing.TB`. The main motivation is that Quartz can be verbose.
1 parent b71761c commit 3734877

File tree

2 files changed

+81
-8
lines changed

2 files changed

+81
-8
lines changed

mock.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
// during a test, triggering any timers or tickers automatically.
1515
type Mock struct {
1616
tb testing.TB
17+
logger Logger
1718
mu sync.Mutex
1819
testOver bool
1920

@@ -199,7 +200,7 @@ func (m *Mock) matchCallLocked(c *apiCall) {
199200
}
200201
}
201202
if !m.testOver {
202-
m.tb.Logf("Mock Clock - %s call, matched %d traps", c, len(traps))
203+
m.logger.Logf("Mock Clock - %s call, matched %d traps", c, len(traps))
203204
}
204205
if len(traps) == 0 {
205206
return
@@ -265,7 +266,7 @@ func (m *Mock) Advance(d time.Duration) AdvanceWaiter {
265266
w := AdvanceWaiter{tb: m.tb, ch: make(chan struct{})}
266267
m.mu.Lock()
267268
if !m.testOver {
268-
m.tb.Logf("Mock Clock - Advance(%s)", d)
269+
m.logger.Logf("Mock Clock - Advance(%s)", d)
269270
}
270271
fin := m.cur.Add(d)
271272
// nextTime.IsZero implies no events scheduled.
@@ -315,7 +316,7 @@ func (m *Mock) Set(t time.Time) AdvanceWaiter {
315316
w := AdvanceWaiter{tb: m.tb, ch: make(chan struct{})}
316317
m.mu.Lock()
317318
if !m.testOver {
318-
m.tb.Logf("Mock Clock - Set(%s)", t)
319+
m.logger.Logf("Mock Clock - Set(%s)", t)
319320
}
320321
if t.Before(m.cur) {
321322
defer close(w.ch)
@@ -354,7 +355,7 @@ func (m *Mock) Set(t time.Time) AdvanceWaiter {
354355
func (m *Mock) AdvanceNext() (time.Duration, AdvanceWaiter) {
355356
m.mu.Lock()
356357
if !m.testOver {
357-
m.tb.Logf("Mock Clock - AdvanceNext()")
358+
m.logger.Logf("Mock Clock - AdvanceNext()")
358359
}
359360
m.tb.Helper()
360361
w := AdvanceWaiter{tb: m.tb, ch: make(chan struct{})}
@@ -445,7 +446,7 @@ func (m *Mock) newTrap(fn clockFunction, tags []string) *Trap {
445446
m.mu.Lock()
446447
defer m.mu.Unlock()
447448
if !m.testOver {
448-
m.tb.Logf("Mock Clock - Trap %s(..., %v)", fn, tags)
449+
m.logger.Logf("Mock Clock - Trap %s(..., %v)", fn, tags)
449450
}
450451
tr := &Trap{
451452
fn: fn,
@@ -458,6 +459,18 @@ func (m *Mock) newTrap(fn clockFunction, tags []string) *Trap {
458459
return tr
459460
}
460461

462+
// WithLogger replaces the default testing logger with a custom one.
463+
//
464+
// This can be used to discard log messages with:
465+
//
466+
// quartz.NewMock(t).WithLogger(quartz.NoOpLogger)
467+
func (m *Mock) WithLogger(l Logger) *Mock {
468+
m.mu.Lock()
469+
defer m.mu.Unlock()
470+
m.logger = l
471+
return m
472+
}
473+
461474
// NewMock creates a new Mock with the time set to midnight UTC on Jan 1, 2024.
462475
// You may re-set the time earlier than this, but only before timers or tickers
463476
// are created.
@@ -467,14 +480,15 @@ func NewMock(tb testing.TB) *Mock {
467480
panic(err)
468481
}
469482
m := &Mock{
470-
tb: tb,
471-
cur: cur,
483+
tb: tb,
484+
logger: tb,
485+
cur: cur,
472486
}
473487
tb.Cleanup(func() {
474488
m.mu.Lock()
475489
defer m.mu.Unlock()
476490
m.testOver = true
477-
tb.Logf("Mock Clock - test cleanup; will no longer log clock events")
491+
m.logger.Logf("Mock Clock - test cleanup; will no longer log clock events")
478492
})
479493
return m
480494
}
@@ -806,3 +820,16 @@ func (t *Trap) MustWait(ctx context.Context) *Call {
806820
}
807821
return c
808822
}
823+
824+
type Logger interface {
825+
Log(args ...any)
826+
Logf(format string, args ...any)
827+
}
828+
829+
// NoOpLogger is a Logger that discards all log messages.
830+
var NoOpLogger Logger = noOpLogger{}
831+
832+
type noOpLogger struct{}
833+
834+
func (noOpLogger) Log(args ...any) {}
835+
func (noOpLogger) Logf(format string, args ...any) {}

mock_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package quartz_test
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"testing"
78
"time"
89

@@ -407,6 +408,39 @@ func Test_UnreleasedCalls(t *testing.T) {
407408
})
408409
}
409410

411+
func Test_WithLogger(t *testing.T) {
412+
t.Parallel()
413+
414+
tl := &testLogger{}
415+
mClock := quartz.NewMock(t).WithLogger(tl)
416+
mClock.Now("test", "Test_WithLogger")
417+
if len(tl.calls) != 1 {
418+
t.Fatalf("expected 1 call, got %d", len(tl.calls))
419+
}
420+
expectLogLine := "Mock Clock - Now([test Test_WithLogger]) call, matched 0 traps"
421+
if tl.calls[0] != expectLogLine {
422+
t.Fatalf("expected log line %q, got %q", expectLogLine, tl.calls[0])
423+
}
424+
425+
mClock.NewTimer(time.Second, "timer")
426+
if len(tl.calls) != 2 {
427+
t.Fatalf("expected 2 calls, got %d", len(tl.calls))
428+
}
429+
expectLogLine = "Mock Clock - NewTimer(1s, [timer]) call, matched 0 traps"
430+
if tl.calls[1] != expectLogLine {
431+
t.Fatalf("expected log line %q, got %q", expectLogLine, tl.calls[1])
432+
}
433+
434+
mClock.Advance(500 * time.Millisecond)
435+
if len(tl.calls) != 3 {
436+
t.Fatalf("expected 3 calls, got %d", len(tl.calls))
437+
}
438+
expectLogLine = "Mock Clock - Advance(500ms)"
439+
if tl.calls[2] != expectLogLine {
440+
t.Fatalf("expected log line %q, got %q", expectLogLine, tl.calls[2])
441+
}
442+
}
443+
410444
type captureFailTB struct {
411445
failed bool
412446
testing.TB
@@ -455,3 +489,15 @@ func tRunFail(t testing.TB, f func(t testing.TB)) {
455489
t.Fatal("want test to fail")
456490
}
457491
}
492+
493+
type testLogger struct {
494+
calls []string
495+
}
496+
497+
func (l *testLogger) Log(args ...any) {
498+
l.calls = append(l.calls, fmt.Sprint(args...))
499+
}
500+
501+
func (l *testLogger) Logf(format string, args ...any) {
502+
l.calls = append(l.calls, fmt.Sprintf(format, args...))
503+
}

0 commit comments

Comments
 (0)