1
0
mirror of https://github.com/rs/zerolog synced 2026-06-08 17:13:30 +00:00
Files
rs-zerolog/sampler_test.go
T
Marc Brooks f6fbd330be Test coverage improvements (#748)
* Test coverage improvements

Implements #397 and #591, might help with #473
Test coverage for core is 97.6% with only real fringe cases remaining.
Improve `Fields` `isNilValue()` portability.
Added a new global handler `FatalExitFunc` to allow intercepting `Fatal()` messages.
Fixed CBOR float constants (removed the CBOR prefix)

Added tests for:
  - global `FatalExitFunc` to allow intercepting Fatal messages (both for testing and public use).
  - `Logger`
    -  `DisableSampling`
    -  `.With()` copying existing context if present.
    -  `.With().Fields()` all forms of `ErrorStackMarshaler` returns.
    - `.WithLevel()` for `FatalLevel`, `PanicLevel`, and `DisabledLevel`.
    - `.Err()` with `nil` and non-`nil` `error` and test the resulting log level.
    - `.should()` covering `nil` writer.
    - `.Output()` gets context values.
    - `.UpdateContext()` on a disabled logger doesn't panic and is a nop.
    - `.With()` all forms of `ErrorStackMarshaler` returns.
  - with a `nil`writer.
  - `.Hook()` passing no hooks.
- `Array`
  - `.MarshalZerologArray` is a nop that won't panic.
- `Context`
  - ` .Err()` and `.AnErr()` for `nil` errors and all forms of `ErrorStackMarshaler` returns.
  - `Event`
    - `.Caller()` to ensure we don't panic or add invalid information if `runtime.Caller()` fails
    -` .Err()` and `.AnErr()` for `nil` errors and all forms of `ErrorStackMarshaler` returns.
  - `Fields`
    - ` .appendFields()`  all forms of `ErrorStackMarshaler` returns.
  - `HookLevel`
    - `.Run()` methods.
  - `LevelSampler`
    - `.Sample() methods.
  - `Syslog`
    - `.Write()`, `.WriteLevel()`, and `.Close()` methods.
    - `.WriteLevel()` with an `InvalidLevel`.
  - `Writer`
    - `.Write()` short write and error cases.
    - `MultiLevelWriter` `.WriteLevel()` and for `.Write()` error and  `.Close()` cases.
  - test of unmarshalling a level byte returns correct error.
  - CBOR decodeStream
    - `.decodeFloat()`, `.binaryFmt()`, `.DecodeIfBinaryToString()`, `.DecodeObjectToStr()`, `.DecodeIfBinaryToBytes()`, `.decodeTagData()`, and `.decodeSimpleFloat()`
    - handling of invalid UTF-8 sequences
    - handling of UTC times.
    - handling of timestamps
    - handling of various map lengths

Restructure `Event` `.caller()` so we test for ok and eliminate untestable coverage hole.

Restructure `Context` `.Err()` when the `ErrorStackMarshaler` returns a `nil` so there's code to cover.

Inverted logic for`Event` `.Caller()`'s call to `runtime.Caller()` for simpler testing.

Inverted logic for `Array` `.putArray()` and `Event` `.putEvent()` so there isn't uncoverable code.

Restructure `Field` `.appendFieldList()` to early return when `ErrorStackMarshaler` returns a `nil`

Added comments for things we can't get coverage on.

Did a go fmt ./...

Coverage of core is now 100% on `Array`, `Context`, `Ctx`, `Event`, `Field`, `Hook`, and `Syslog`.

Coverage of `Globals`, `Log`, `Sampler`, and `Writer` is almost all except some real edge-cases.

JSON encoder coverage is 100%

CBOR encoder coverage is 96.3% with base, cbor, string, time and types at 100% and decode_stream (which is lacks coverage on some panic states, and two incorrect coverage-tool lapses)

* Fix CBOR tests for StackMarshaler

Forgot to use the `decodeIfBinaryToString()`
2026-01-12 15:03:52 +00:00

183 lines
3.7 KiB
Go

//go:build !binary_log
// +build !binary_log
package zerolog
import (
"testing"
"time"
)
var samplers = []struct {
name string
sampler func() Sampler
total int
wantMin int
wantMax int
}{
{
"BasicSampler_1",
func() Sampler {
return &BasicSampler{N: 1}
},
100, 100, 100,
},
{
"BasicSampler_5",
func() Sampler {
return &BasicSampler{N: 5}
},
100, 20, 20,
},
{
"BasicSampler_0",
func() Sampler {
return &BasicSampler{N: 0}
},
100, 0, 0,
},
{
"RandomSampler",
func() Sampler {
return RandomSampler(5)
},
100, 10, 30,
},
{
"RandomSampler_0",
func() Sampler {
return RandomSampler(0)
},
100, 0, 0,
},
{
"BurstSampler",
func() Sampler {
return &BurstSampler{Burst: 20, Period: time.Second}
},
100, 20, 20,
},
{
"BurstSampler_0",
func() Sampler {
return &BurstSampler{Burst: 0, Period: time.Second}
},
100, 0, 0,
},
{
"BurstSamplerNext",
func() Sampler {
return &BurstSampler{Burst: 20, Period: time.Second, NextSampler: &BasicSampler{N: 5}}
},
120, 40, 40,
},
}
func TestSamplers(t *testing.T) {
for i := range samplers {
s := samplers[i]
t.Run(s.name, func(t *testing.T) {
sampler := s.sampler()
got := 0
for t := s.total; t > 0; t-- {
if sampler.Sample(0) {
got++
}
}
if got < s.wantMin || got > s.wantMax {
t.Errorf("%s.Sample(0) == true %d on %d, want [%d, %d]", s.name, got, s.total, s.wantMin, s.wantMax)
}
})
}
}
func BenchmarkSamplers(b *testing.B) {
for i := range samplers {
s := samplers[i]
b.Run(s.name, func(b *testing.B) {
sampler := s.sampler()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
sampler.Sample(0)
}
})
})
}
}
func TestBurst(t *testing.T) {
sampler := &BurstSampler{Burst: 1, Period: time.Second}
t0 := time.Now()
now := t0
mockedTime := func() time.Time {
return now
}
TimestampFunc = mockedTime
defer func() { TimestampFunc = time.Now }()
scenario := []struct {
tm time.Time
want bool
}{
{t0, true},
{t0.Add(time.Second - time.Nanosecond), false},
{t0.Add(time.Second), true},
{t0.Add(time.Second + time.Nanosecond), false},
}
for i, step := range scenario {
now = step.tm
got := sampler.Sample(NoLevel)
if got != step.want {
t.Errorf("step %d (t=%s): expect %t got %t", i, step.tm, step.want, got)
}
}
}
func TestLevelSampler(t *testing.T) {
// Create mock samplers that return true for specific levels
traceSampler := &BasicSampler{N: 1} // Always sample
debugSampler := &BasicSampler{N: 0} // Never sample
infoSampler := &BasicSampler{N: 1} // Always sample
warnSampler := &BasicSampler{N: 0} // Never sample
errorSampler := &BasicSampler{N: 1} // Always sample
sampler := LevelSampler{
TraceSampler: traceSampler,
DebugSampler: debugSampler,
InfoSampler: infoSampler,
WarnSampler: warnSampler,
ErrorSampler: errorSampler,
}
// Test each level
if !sampler.Sample(TraceLevel) {
t.Error("TraceLevel should be sampled")
}
if sampler.Sample(DebugLevel) {
t.Error("DebugLevel should not be sampled")
}
if !sampler.Sample(InfoLevel) {
t.Error("InfoLevel should be sampled")
}
if sampler.Sample(WarnLevel) {
t.Error("WarnLevel should not be sampled")
}
if !sampler.Sample(ErrorLevel) {
t.Error("ErrorLevel should be sampled")
}
// Test levels not covered by the LevelSampler sampler (FatalLevel, PanicLevel, NoLevel) - should return true
if !sampler.Sample(FatalLevel) {
t.Error("FatalLevel should return true when no sampler is set")
}
if !sampler.Sample(PanicLevel) {
t.Error("PanicLevel should return true when no sampler is set")
}
if !sampler.Sample(NoLevel) {
t.Error("NoLevel should return true when no sampler is set")
}
}