Files
pinniped/internal/plog/testing.go

163 lines
4.2 KiB
Go

// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package plog
import (
"bytes"
"context"
"io"
"math"
"strings"
"testing"
"time"
"github.com/go-logr/logr"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"k8s.io/utils/clock"
clocktesting "k8s.io/utils/clock/testing"
)
// contextKey type is unexported to prevent collisions.
type contextKey int
const testOverridesContextKey contextKey = iota
type testOverrides struct {
t *testing.T
w io.Writer
f func(*zap.Config)
fakeClock *clocktesting.FakeClock
opts []zap.Option
}
// AddZapOverridesToContext adds Zap (and klog/textlogger) overrides to the context.
// This is done so that production code can read these values for test overrides.
// Do not pass zap.WithClock in opts since that will be constructed for you from fakeClock.
func AddZapOverridesToContext(
ctx context.Context,
t *testing.T,
w io.Writer,
f func(*zap.Config),
fakeClock *clocktesting.FakeClock,
opts ...zap.Option,
) context.Context {
t.Helper() // discourage use outside of tests
require.NotNil(t, fakeClock, "fakeClock is required")
opts = append(opts, zap.WithClock(ZapClock(fakeClock)))
overrides := &testOverrides{
t: t,
w: w,
f: f,
fakeClock: fakeClock,
opts: opts,
}
return context.WithValue(ctx, testOverridesContextKey, overrides)
}
func TestLogger(t *testing.T) (Logger, *bytes.Buffer) {
t.Helper()
var log bytes.Buffer
return New().withLogrMod(func(l logr.Logger) logr.Logger {
return l.WithSink(testZapr(t, &log, "json").GetSink())
}),
&log
}
func TestAuditLogger(t *testing.T) (AuditLogger, *bytes.Buffer) {
return TestAuditLoggerWithConfig(t, AuditLogConfig{LogUsernamesAndGroupNames: true})
}
func TestAuditLoggerWithConfig(t *testing.T, cfg AuditLogConfig) (AuditLogger, *bytes.Buffer) {
t.Helper()
underlyingLogger, logBuf := TestLogger(t)
return &auditLogger{logger: underlyingLogger, cfg: cfg}, logBuf
}
func TestConsoleLogger(t *testing.T, w io.Writer) Logger {
t.Helper()
return New().withLogrMod(func(l logr.Logger) logr.Logger {
return l.WithSink(testZapr(t, w, "console").GetSink())
})
}
func testZapr(t *testing.T, w io.Writer, encoding string) logr.Logger {
t.Helper()
now, err := time.Parse(time.RFC3339Nano, "2099-08-08T13:57:36.123456789Z")
require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
ctx = AddZapOverridesToContext(ctx, t, w,
func(config *zap.Config) {
config.Level = zap.NewAtomicLevelAt(math.MinInt8) // log everything during tests
// make test assertions less painful to write while keeping them as close to the real thing as possible
config.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
trimmed := caller.TrimmedPath()
if idx := strings.LastIndexByte(trimmed, ':'); idx != -1 {
trimmed = trimmed[:idx+1] + "<line>"
}
if encoding != "console" {
trimmed += funcEncoder(caller)
}
enc.AppendString(trimmed)
}
},
clocktesting.NewFakeClock(now), // have the clock be static during tests
zap.AddStacktrace(nopLevelEnabler{}), // do not log stacktraces
)
// there is no buffering so we can ignore flush
zl, _, err := newLogr(ctx, encoding, 0)
require.NoError(t, err)
return zl
}
var _ zapcore.Clock = &clockAdapter{}
type clockAdapter struct {
clock clock.Clock
}
func (c *clockAdapter) Now() time.Time {
return c.clock.Now()
}
func (c *clockAdapter) NewTicker(duration time.Duration) *time.Ticker {
return &time.Ticker{C: c.clock.Tick(duration)}
}
func ZapClock(c clock.Clock) zapcore.Clock {
return &clockAdapter{clock: c}
}
var _ zap.Sink = nopCloserSink{}
type nopCloserSink struct{ zapcore.WriteSyncer }
func (nopCloserSink) Close() error { return nil }
// newSink returns a wrapper around the input writer that is safe for concurrent use.
func newSink(w io.Writer) zap.Sink {
return nopCloserSink{WriteSyncer: zapcore.Lock(zapcore.AddSync(w))}
}
var _ zapcore.LevelEnabler = nopLevelEnabler{}
type nopLevelEnabler struct{}
func (nopLevelEnabler) Enabled(_ zapcore.Level) bool { return false }