From 814864597403c4aa18c37cd8d0aa28bc0d40996e Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Mon, 15 Dec 2025 13:55:37 -0600 Subject: [PATCH] Chore improve test coverage (#735) * Add array handling of IPAddr and IPPrefix Updated documentation, benchmark, and tests including catching up some missing. Cleaned up couple lint messages * Added cbor encoding for []net.IP and []net.IPNet * Fix binary_test Typos galore, thanks CI * Remove feral x character * Added IPAddrs and IPPrefixes tests for json encoder * Increase code coverage and test cleanup Added Type to the BenchmarkLogFieldType and BenchmarkContexdtFieldType test cases, also to log_test.go Moved the test-fixture data for them and also shared them with new event test for nil events. Added a couple strings for encodeByteTest for FF, CR, LF Added complete cbor testing for AppendInt*, AppendInts*, AppendUint*, and AppendUints* methods. Added test for float IsNaN and Inf(0) and Inf(-1). Added all min/max/smallest value tests for ints and floats Extended the Array test to include IPPrefix, MACAddr, Interface, and object marshaled. Better test of MarshalZerologObject. Added test for encoder AppendStrings and AppendStringers. * Remove merge detritus. * Remove code duplication when appending objects * Add missing Stringers support to Context * Add test cases for Err and Object in Array * Add missing benchmark for Events and Contexts Events: Any, Bytes, Hex, Float32, Floats32, Stringer, Stringers, Timestamp Context: Any, Bytes, Hex, Float32, Floats32, Stringers (also renamed Float,Floats to Float64,Floats64) * Add test of MarshalZeroLogObject of error * Build out Event test cases Test of nil Event - write - Array, Dict - Fields - Int8, Ints8, Int16, Ints16, Int32, Ints32, Int64, Ints64 - Uint8, Uints8, Uint16, Uints16, Uint32, Uints32, Uint64, Uints64 - RawCBOR, RawJSON, EmbedObject - Stringers - Caller, CallerSkip, Stack - Send, Msg, Msgf, MsgFunc - (renamed tests of Float, Floats to Float64, Floats64) Added test of MsgFunc * Add more tests - Dur (With) - Any (With, Fields, Fields map, Disabled) - Interface - Stack (Fields) Fix issue with testing a nil loggableError * Split out the With tests again type arrays Simplifies the "want" maintenance * Add missing test fixture data Bytes, Floats32, Ints8, Ints16, Ints32, Ints64, Uints8, Uints16, Uints32, Uints64, RawJSON, RawCBOR Stringers is now an array (not a single Stringer) (also renamed Float, Floats to Float64, Floats64) * Add examples for IPAddrs, IPPrefixes, Times Missing examples that increase coverage * Build out the complete ErrorMarshalFunc tests * Add AppendStrings to CBOR tests * Add CBOR AppendTimes, AppendDuration, AppendDurations tests * Add CBOR tests - BeginMarker, EndMarker, AppendArrayDim, AppendLineBreak - AppendObjectData - AppendIterface - AppendType - AppendHex - Empty arrays for - Bools - Floats32, Floats64 - Large arrays for - Bools, - Uints8, Uints16, Uints32, Uints64 - Floats32, Floats64 - IPAddrs, IPPrefixes - Small arrays for - Ints, Ints8, Ints16, Ints32, Ints64 - Uints, Uints32, Uints64 - Floats32, Floats64 * Add CBOR test for AppendKey * Added more string encoding tests Test escape character handling. * Fix test import cycle * Rename hex to hexCharacters to avoid namespace. * Remove leftover comment * Add test cases to share between CBOR and JSON tests * Switched CBOR to shared test cases * Add JSON test for AppendKey * Switch to shared test cases Added test for AppendStringer * Added JSON test - AppendNil - AppendBeginMarker, AppendEndMarker - AppendArrayStart, AppendArrayEnd - AppendArrayDelim - AppendLineBreak - AppendObjectData - AppendInterface - AppendBool, AppendBools * Add JSON time tests - AppendTime, AppendTimes - AppendTime (past/present) (integer/float) - AppendDuration, AppendDurations * Added JSON tests for plurals Split out the Int and Float tests into distinct files. Add - AppendInt - AppendInts8, AppendInts16, AppendInts32, AppendInts64, AppendInts - AppendUints8, AppendUints16, AppendUints32, AppendUints64, AppendUints - AppendFloats32, AppendFloats64 --- .gitignore | 2 + array.go | 12 +- array_test.go | 24 +- benchmark_test.go | 293 ++++----- context.go | 12 + ctx_test.go | 15 +- event_test.go | 238 +++++++ fields.go | 29 +- fixtures_test.go | 159 +++++ internal/cbor/base_test.go | 22 + internal/cbor/decoder_test.go | 117 ++-- internal/cbor/string_test.go | 58 ++ internal/cbor/time_test.go | 166 ++++- internal/cbor/types_test.go | 1146 +++++++++++++++++++++++++++------ internal/json/base_test.go | 30 + internal/json/bytes.go | 4 +- internal/json/bytes_test.go | 18 +- internal/json/float_test.go | 433 +++++++++++++ internal/json/int_test.go | 356 ++++++++++ internal/json/string.go | 4 +- internal/json/string_test.go | 105 ++- internal/json/time_test.go | 234 +++++++ internal/json/types_test.go | 545 ++++++---------- internal/testcases.go | 373 +++++++++++ log_example_test.go | 37 ++ log_test.go | 320 +++++++-- 26 files changed, 3784 insertions(+), 968 deletions(-) create mode 100644 fixtures_test.go create mode 100644 internal/cbor/base_test.go create mode 100644 internal/json/base_test.go create mode 100644 internal/json/float_test.go create mode 100644 internal/json/int_test.go create mode 100644 internal/json/time_test.go create mode 100644 internal/testcases.go diff --git a/.gitignore b/.gitignore index 8ebe58b..1a2fc21 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ _testmain.go *.exe *.test *.prof + +coverage.out diff --git a/array.go b/array.go index 4c9c988..d900517 100644 --- a/array.go +++ b/array.go @@ -59,11 +59,7 @@ func (a *Array) write(dst []byte) []byte { // Object marshals an object that implement the LogObjectMarshaler // interface and appends it to the array. func (a *Array) Object(obj LogObjectMarshaler) *Array { - e := Dict() - obj.MarshalZerologObject(e) - e.buf = enc.AppendEndMarker(e.buf) - a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) - putEvent(e) + a.buf = appendObject(enc.AppendArrayDelim(a.buf), obj) return a } @@ -95,11 +91,7 @@ func (a *Array) RawJSON(val []byte) *Array { func (a *Array) Err(err error) *Array { switch m := ErrorMarshalFunc(err).(type) { case LogObjectMarshaler: - e := newEvent(nil, 0) - e.buf = e.buf[:0] - e.appendObject(m) - a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) - putEvent(e) + a = a.Object(m) case error: if m == nil || isNilValue(m) { a.buf = enc.AppendNil(enc.AppendArrayDelim(a.buf)) diff --git a/array_test.go b/array_test.go index d37d8fb..8afa40d 100644 --- a/array_test.go +++ b/array_test.go @@ -1,6 +1,7 @@ package zerolog import ( + "fmt" "net" "testing" "time" @@ -27,12 +28,31 @@ func TestArray(t *testing.T) { RawJSON([]byte(`{"some":"json"}`)). Time(time.Time{}). IPAddr(net.IP{192, 168, 0, 10}). + IPPrefix(net.IPNet{IP: net.IP{127, 0, 0, 0}, Mask: net.CIDRMask(24, 32)}). + MACAddr(net.HardwareAddr{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}). + Interface(struct { + Pub string + Tag string `json:"tag"` + priv int + }{"A", "j", -5}). + Interface(logObjectMarshalerImpl{ + name: "ZOT", + age: 35, + }). Dur(0). Dict(Dict(). Str("bar", "baz"). Int("n", 1), - ) - want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},"0001-01-01T00:00:00Z","192.168.0.10",0,{"bar":"baz","n":1}]` + ). + Err(nil). + Err(fmt.Errorf("failure")). + Err(errorObjectMarshalerImpl{fmt.Errorf("oops")}). + Object(logObjectMarshalerImpl{ + name: "ZIT", + age: 22, + }) + + want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},"0001-01-01T00:00:00Z","192.168.0.10","127.0.0.0/24","01:23:45:67:89:ab",{"Pub":"A","tag":"j"},{"name":"zot","age":-35},0,{"bar":"baz","n":1},null,"failure",{"error":"OOPS"},{"name":"zit","age":-22}]` if got := decodeObjectToStr(a.write([]byte{})); got != want { t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want) } diff --git a/benchmark_test.go b/benchmark_test.go index 7e21677..c526551 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -1,10 +1,8 @@ package zerolog import ( - "context" "errors" "io" - "net" "testing" "time" ) @@ -86,22 +84,10 @@ func BenchmarkLogFields(b *testing.B) { }) } -type obj struct { - Pub string - Tag string `json:"tag"` - priv int -} - -func (o obj) MarshalZerologObject(e *Event) { - e.Str("Pub", o.Pub). - Str("Tag", o.Tag). - Int("priv", o.priv) -} - func BenchmarkLogArrayObject(b *testing.B) { - obj1 := obj{"a", "b", 2} - obj2 := obj{"c", "d", 3} - obj3 := obj{"e", "f", 4} + obj1 := fixtureObj{"a", "b", 2} + obj2 := fixtureObj{"c", "d", 3} + obj3 := fixtureObj{"e", "f", 4} logger := New(io.Discard) b.ResetTimer() b.ReportAllocs() @@ -115,138 +101,112 @@ func BenchmarkLogArrayObject(b *testing.B) { } func BenchmarkLogFieldType(b *testing.B) { - bools := []bool{true, false, true, false, true, false, true, false, true, false} - ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - floats := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - strings := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} - durations := []time.Duration{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - times := []time.Time{ - time.Unix(0, 0), - time.Unix(1, 0), - time.Unix(2, 0), - time.Unix(3, 0), - time.Unix(4, 0), - time.Unix(5, 0), - time.Unix(6, 0), - time.Unix(7, 0), - time.Unix(8, 0), - time.Unix(9, 0), - } - interfaces := []struct { - Pub string - Tag string `json:"tag"` - priv int - }{ - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - } - objects := []obj{ - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - } - ipAddrV4 := net.IP{192, 168, 0, 1} - ipAddrV6 := net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34} - ipAddrs := []net.IP{ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6} - ipPfxV4 := net.IPNet{IP: net.IP{192, 168, 0, 0}, Mask: net.CIDRMask(24, 32)} - ipPfxV6 := net.IPNet{IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x00}, Mask: net.CIDRMask(64, 128)} - ipPfxs := []net.IPNet{ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6} - macAddr := net.HardwareAddr{0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E} - - errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")} - ctx := context.Background() + fixtures := makeFieldFixtures() types := map[string]func(e *Event) *Event{ + "Any": func(e *Event) *Event { + return e.Any("k", fixtures.Interfaces[0]) + }, "Bool": func(e *Event) *Event { - return e.Bool("k", bools[0]) + return e.Bool("k", fixtures.Bools[0]) }, "Bools": func(e *Event) *Event { - return e.Bools("k", bools) + return e.Bools("k", fixtures.Bools) + }, + "Bytes": func(e *Event) *Event { + return e.Bytes("k", fixtures.Bytes) + }, + "Hex": func(e *Event) *Event { + return e.Hex("k", fixtures.Bytes) }, "Int": func(e *Event) *Event { - return e.Int("k", ints[0]) + return e.Int("k", fixtures.Ints[0]) }, "Ints": func(e *Event) *Event { - return e.Ints("k", ints) + return e.Ints("k", fixtures.Ints) }, - "Float": func(e *Event) *Event { - return e.Float64("k", floats[0]) + "Float32": func(e *Event) *Event { + return e.Float32("k", fixtures.Floats32[0]) }, - "Floats": func(e *Event) *Event { - return e.Floats64("k", floats) + "Floats32": func(e *Event) *Event { + return e.Floats32("k", fixtures.Floats32) + }, + "Float64": func(e *Event) *Event { + return e.Float64("k", fixtures.Floats64[0]) + }, + "Floats64": func(e *Event) *Event { + return e.Floats64("k", fixtures.Floats64) }, "Str": func(e *Event) *Event { - return e.Str("k", strings[0]) + return e.Str("k", fixtures.Strings[0]) }, "Strs": func(e *Event) *Event { - return e.Strs("k", strings) + return e.Strs("k", fixtures.Strings) + }, + "Stringer": func(e *Event) *Event { + return e.Stringer("k", fixtures.Stringers[0]) + }, + "Stringers": func(e *Event) *Event { + return e.Stringers("k", fixtures.Stringers) }, "Err": func(e *Event) *Event { - return e.Err(errs[0]) + return e.Err(fixtures.Errs[0]) }, "Errs": func(e *Event) *Event { - return e.Errs("k", errs) + return e.Errs("k", fixtures.Errs) }, "Ctx": func(e *Event) *Event { - return e.Ctx(ctx) + return e.Ctx(fixtures.Ctx) }, "Time": func(e *Event) *Event { - return e.Time("k", times[0]) + return e.Time("k", fixtures.Times[0]) }, "Times": func(e *Event) *Event { - return e.Times("k", times) + return e.Times("k", fixtures.Times) }, "Dur": func(e *Event) *Event { - return e.Dur("k", durations[0]) + return e.Dur("k", fixtures.Durations[0]) }, "Durs": func(e *Event) *Event { - return e.Durs("k", durations) + return e.Durs("k", fixtures.Durations) }, "Interface": func(e *Event) *Event { - return e.Interface("k", interfaces[0]) + return e.Interface("k", fixtures.Interfaces[0]) }, "Interfaces": func(e *Event) *Event { - return e.Interface("k", interfaces) + return e.Interface("k", fixtures.Interfaces) }, "Interface(Object)": func(e *Event) *Event { - return e.Interface("k", objects[0]) + return e.Interface("k", fixtures.Objects[0]) }, "Interface(Objects)": func(e *Event) *Event { - return e.Interface("k", objects) + return e.Interface("k", fixtures.Objects) }, "Object": func(e *Event) *Event { - return e.Object("k", objects[0]) + return e.Object("k", fixtures.Objects[0]) + }, + "Timestamp": func(e *Event) *Event { + return e.Timestamp() }, "IPAddr": func(e *Event) *Event { - return e.IPAddr("k", ipAddrs[0]) + return e.IPAddr("k", fixtures.IPAddrs[0]) }, "IPAddrs": func(e *Event) *Event { - return e.IPAddrs("k", ipAddrs) + return e.IPAddrs("k", fixtures.IPAddrs) }, "IPPrefix": func(e *Event) *Event { - return e.IPPrefix("k", ipPfxs[0]) + return e.IPPrefix("k", fixtures.IPPfxs[0]) }, "IPPrefixes": func(e *Event) *Event { - return e.IPPrefixes("k", ipPfxs) + return e.IPPrefixes("k", fixtures.IPPfxs) }, "MACAddr": func(e *Event) *Event { - return e.MACAddr("k", macAddr) + return e.MACAddr("k", fixtures.MACAddr) + }, + "Type": func(e *Event) *Event { + return e.Type("k", fixtures.Type) }, } + logger := New(io.Discard) b.ResetTimer() for name := range types { @@ -266,145 +226,112 @@ func BenchmarkContextFieldType(b *testing.B) { TimeFieldFormat = TimeFormatUnix defer func() { TimeFieldFormat = oldFormat }() - bools := []bool{true, false, true, false, true, false, true, false, true, false} - ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - floats := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - strings := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} - stringer := net.IP{127, 0, 0, 1} - durations := []time.Duration{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - times := []time.Time{ - time.Unix(0, 0), - time.Unix(1, 0), - time.Unix(2, 0), - time.Unix(3, 0), - time.Unix(4, 0), - time.Unix(5, 0), - time.Unix(6, 0), - time.Unix(7, 0), - time.Unix(8, 0), - time.Unix(9, 0), - } - interfaces := []struct { - Pub string - Tag string `json:"tag"` - priv int - }{ - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - } - objects := []obj{ - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - {"a", "a", 0}, - } - errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")} - ipAddrV4 := net.IP{192, 168, 0, 1} - ipAddrV6 := net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34} - ipAddrs := []net.IP{ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6} - ipPfxV4 := net.IPNet{IP: net.IP{192, 168, 0, 0}, Mask: net.CIDRMask(24, 32)} - ipPfxV6 := net.IPNet{IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x00}, Mask: net.CIDRMask(64, 128)} - ipPfxs := []net.IPNet{ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6} - macAddr := net.HardwareAddr{0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E} - - ctx := context.Background() + fixtures := makeFieldFixtures() types := map[string]func(c Context) Context{ + "Any": func(c Context) Context { + return c.Any("k", fixtures.Interfaces[0]) + }, "Bool": func(c Context) Context { - return c.Bool("k", bools[0]) + return c.Bool("k", fixtures.Bools[0]) }, "Bools": func(c Context) Context { - return c.Bools("k", bools) + return c.Bools("k", fixtures.Bools) + }, + "Bytes": func(c Context) Context { + return c.Bytes("k", fixtures.Bytes) + }, + "Hex": func(c Context) Context { + return c.Hex("k", fixtures.Bytes) }, "Int": func(c Context) Context { - return c.Int("k", ints[0]) + return c.Int("k", fixtures.Ints[0]) }, "Ints": func(c Context) Context { - return c.Ints("k", ints) + return c.Ints("k", fixtures.Ints) }, - "Float": func(c Context) Context { - return c.Float64("k", floats[0]) + "Float32": func(c Context) Context { + return c.Float32("k", fixtures.Floats32[0]) }, - "Floats": func(c Context) Context { - return c.Floats64("k", floats) + "Floats32": func(c Context) Context { + return c.Floats32("k", fixtures.Floats32) + }, + "Float64": func(c Context) Context { + return c.Float64("k", fixtures.Floats64[0]) + }, + "Floats64": func(c Context) Context { + return c.Floats64("k", fixtures.Floats64) }, "Str": func(c Context) Context { - return c.Str("k", strings[0]) + return c.Str("k", fixtures.Strings[0]) }, "Strs": func(c Context) Context { - return c.Strs("k", strings) + return c.Strs("k", fixtures.Strings) }, "Stringer": func(c Context) Context { - return c.Stringer("k", stringer) + return c.Stringer("k", fixtures.Stringers[0]) + }, + "Stringers": func(c Context) Context { + return c.Stringers("k", fixtures.Stringers) }, "Err": func(c Context) Context { - return c.Err(errs[0]) + return c.Err(fixtures.Errs[0]) }, "Errs": func(c Context) Context { - return c.Errs("k", errs) + return c.Errs("k", fixtures.Errs) }, "Ctx": func(c Context) Context { - return c.Ctx(ctx) + return c.Ctx(fixtures.Ctx) }, "Time": func(c Context) Context { - return c.Time("k", times[0]) + return c.Time("k", fixtures.Times[0]) }, "Times": func(c Context) Context { - return c.Times("k", times) + return c.Times("k", fixtures.Times) }, "Dur": func(c Context) Context { - return c.Dur("k", durations[0]) + return c.Dur("k", fixtures.Durations[0]) }, "Durs": func(c Context) Context { - return c.Durs("k", durations) + return c.Durs("k", fixtures.Durations) }, "Interface": func(c Context) Context { - return c.Interface("k", interfaces[0]) + return c.Interface("k", fixtures.Interfaces[0]) }, "Interfaces": func(c Context) Context { - return c.Interface("k", interfaces) + return c.Interface("k", fixtures.Interfaces) }, "Interface(Object)": func(c Context) Context { - return c.Interface("k", objects[0]) + return c.Interface("k", fixtures.Objects[0]) }, "Interface(Objects)": func(c Context) Context { - return c.Interface("k", objects) + return c.Interface("k", fixtures.Objects) }, "Object": func(c Context) Context { - return c.Object("k", objects[0]) + return c.Object("k", fixtures.Objects[0]) }, "Timestamp": func(c Context) Context { return c.Timestamp() }, "IPAddr": func(c Context) Context { - return c.IPAddr("k", ipAddrs[0]) + return c.IPAddr("k", fixtures.IPAddrs[0]) }, "IPAddrs": func(c Context) Context { - return c.IPAddrs("k", ipAddrs) + return c.IPAddrs("k", fixtures.IPAddrs) }, "IPPrefix": func(c Context) Context { - return c.IPPrefix("k", ipPfxs[0]) + return c.IPPrefix("k", fixtures.IPPfxs[0]) }, "IPPrefixes": func(c Context) Context { - return c.IPPrefixes("k", ipPfxs) + return c.IPPrefixes("k", fixtures.IPPfxs) }, "MACAddr": func(c Context) Context { - return c.MACAddr("k", macAddr) + return c.MACAddr("k", fixtures.MACAddr) + }, + "Type": func(c Context) Context { + return c.Type("k", fixtures.Type) }, } + logger := New(io.Discard) b.ResetTimer() for name := range types { diff --git a/context.go b/context.go index 8a1f207..fb9ff2a 100644 --- a/context.go +++ b/context.go @@ -96,6 +96,18 @@ func (c Context) Stringer(key string, val fmt.Stringer) Context { return c } +// Stringers adds the field key with vals as an array of strings by calling .String() on each entry +// to the logger context. +func (c Context) Stringers(key string, vals []fmt.Stringer) Context { + if vals != nil { + c.l.context = enc.AppendStringers(enc.AppendKey(c.l.context, key), vals) + return c + } + + c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), nil) + return c +} + // Bytes adds the field key with val as a []byte to the logger context. func (c Context) Bytes(key string, val []byte) Context { c.l.context = enc.AppendBytes(enc.AppendKey(c.l.context, key), val) diff --git a/ctx_test.go b/ctx_test.go index 6315cf7..a51d51d 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -5,6 +5,7 @@ import ( "context" "io" "reflect" + "strings" "testing" "github.com/rs/zerolog/internal/cbor" @@ -78,7 +79,15 @@ type logObjectMarshalerImpl struct { } func (t logObjectMarshalerImpl) MarshalZerologObject(e *Event) { - e.Str("name", "custom_value").Int("age", t.age) + e.Str("name", strings.ToLower(t.name)).Int("age", -t.age) +} + +type errorObjectMarshalerImpl struct { + error +} + +func (t errorObjectMarshalerImpl) MarshalZerologObject(e *Event) { + e.Str("error", strings.ToUpper(t.error.Error())) } func Test_InterfaceLogObjectMarshaler(t *testing.T) { @@ -89,13 +98,13 @@ func Test_InterfaceLogObjectMarshaler(t *testing.T) { log2 := Ctx(ctx) withLog := log2.With().Interface("obj", &logObjectMarshalerImpl{ - name: "foo", + name: "FOO", age: 29, }).Logger() withLog.Info().Msg("test") - if got, want := cbor.DecodeIfBinaryToString(buf.Bytes()), `{"level":"info","obj":{"name":"custom_value","age":29},"message":"test"}`+"\n"; got != want { + if got, want := cbor.DecodeIfBinaryToString(buf.Bytes()), `{"level":"info","obj":{"name":"foo","age":-29},"message":"test"}`+"\n"; got != want { t.Errorf("got %q, want %q", got, want) } } diff --git a/event_test.go b/event_test.go index e7cf425..b5d5751 100644 --- a/event_test.go +++ b/event_test.go @@ -38,6 +38,16 @@ func TestEvent_AnErr(t *testing.T) { } } +func TestEvent_writeWithNil(t *testing.T) { + var e *Event = nil + got := e.write() + + var want *Event = nil + if got != nil { + t.Errorf("Event.write() = %v, want %v", got, want) + } +} + func TestEvent_ObjectWithNil(t *testing.T) { var buf bytes.Buffer e := newEvent(LevelWriterAdapter{&buf}, DebugLevel) @@ -63,3 +73,231 @@ func TestEvent_EmbedObjectWithNil(t *testing.T) { t.Errorf("Event.EmbedObject() = %q, want %q", got, want) } } + +func TestEvent_WithNilEvent(t *testing.T) { + // coverage for nil Event receiver for all types + var e *Event = nil + + fixtures := makeFieldFixtures() + types := map[string]func() *Event{ + "Array": func() *Event { + arr := Arr() + return e.Array("k", arr) + }, + "Bool": func() *Event { + return e.Bool("k", fixtures.Bools[0]) + }, + "Bools": func() *Event { + return e.Bools("k", fixtures.Bools) + }, + "Fields": func() *Event { + return e.Fields(fixtures) + }, + "Int": func() *Event { + return e.Int("k", fixtures.Ints[0]) + }, + "Ints": func() *Event { + return e.Ints("k", fixtures.Ints) + }, + "Int8": func() *Event { + return e.Int8("k", fixtures.Ints8[0]) + }, + "Ints8": func() *Event { + return e.Ints8("k", fixtures.Ints8) + }, + "Int16": func() *Event { + return e.Int16("k", fixtures.Ints16[0]) + }, + "Ints16": func() *Event { + return e.Ints16("k", fixtures.Ints16) + }, + "Int32": func() *Event { + return e.Int32("k", fixtures.Ints32[0]) + }, + "Ints32": func() *Event { + return e.Ints32("k", fixtures.Ints32) + }, + "Int64": func() *Event { + return e.Int64("k", fixtures.Ints64[0]) + }, + "Ints64": func() *Event { + return e.Ints64("k", fixtures.Ints64) + }, + "Uint": func() *Event { + return e.Uint("k", fixtures.Uints[0]) + }, + "Uints": func() *Event { + return e.Uints("k", fixtures.Uints) + }, + "Uint8": func() *Event { + return e.Uint8("k", fixtures.Uints8[0]) + }, + "Uints8": func() *Event { + return e.Uints8("k", fixtures.Uints8) + }, + "Uint16": func() *Event { + return e.Uint16("k", fixtures.Uints16[0]) + }, + "Uints16": func() *Event { + return e.Uints16("k", fixtures.Uints16) + }, + "Uint32": func() *Event { + return e.Uint32("k", fixtures.Uints32[0]) + }, + "Uints32": func() *Event { + return e.Uints32("k", fixtures.Uints32) + }, + "Uint64": func() *Event { + return e.Uint64("k", fixtures.Uints64[0]) + }, + "Uints64": func() *Event { + return e.Uints64("k", fixtures.Uints64) + }, + "Float64": func() *Event { + return e.Float64("k", fixtures.Floats64[0]) + }, + "Floats64": func() *Event { + return e.Floats64("k", fixtures.Floats64) + }, + "Float32": func() *Event { + return e.Float32("k", fixtures.Floats32[0]) + }, + "Floats32": func() *Event { + return e.Floats32("k", fixtures.Floats32) + }, + "RawCBOR": func() *Event { + return e.RawCBOR("k", fixtures.RawCBOR) + }, + "RawJSON": func() *Event { + return e.RawJSON("k", fixtures.RawJSON) + }, + "Str": func() *Event { + return e.Str("k", fixtures.Strings[0]) + }, + "Strs": func() *Event { + return e.Strs("k", fixtures.Strings) + }, + "Stringers": func() *Event { + return e.Stringers("k", fixtures.Stringers) + }, + "Err": func() *Event { + return e.Err(fixtures.Errs[0]) + }, + "Errs": func() *Event { + return e.Errs("k", fixtures.Errs) + }, + "Ctx": func() *Event { + return e.Ctx(fixtures.Ctx) + }, + "Time": func() *Event { + return e.Time("k", fixtures.Times[0]) + }, + "Times": func() *Event { + return e.Times("k", fixtures.Times) + }, + "Dict": func() *Event { + d := Dict() + d.Str("greeting", "hello") + return e.Dict("k", d) + }, + "Dur": func() *Event { + return e.Dur("k", fixtures.Durations[0]) + }, + "Durs": func() *Event { + return e.Durs("k", fixtures.Durations) + }, + "Interface": func() *Event { + return e.Interface("k", fixtures.Interfaces[0]) + }, + "Interfaces": func() *Event { + return e.Interface("k", fixtures.Interfaces) + }, + "Interface(Object)": func() *Event { + return e.Interface("k", fixtures.Objects[0]) + }, + "Interface(Objects)": func() *Event { + return e.Interface("k", fixtures.Objects) + }, + "Object": func() *Event { + return e.Object("k", fixtures.Objects[0]) + }, + "EmbedObject": func() *Event { + return e.EmbedObject(fixtures.Objects[0]) + }, + "Timestamp": func() *Event { + return e.Timestamp() + }, + "IPAddr": func() *Event { + return e.IPAddr("k", fixtures.IPAddrs[0]) + }, + "IPAddrs": func() *Event { + return e.IPAddrs("k", fixtures.IPAddrs) + }, + "IPPrefix": func() *Event { + return e.IPPrefix("k", fixtures.IPPfxs[0]) + }, + "IPPrefixes": func() *Event { + return e.IPPrefixes("k", fixtures.IPPfxs) + }, + "MACAddr": func() *Event { + return e.MACAddr("k", fixtures.MACAddr) + }, + "Type": func() *Event { + return e.Type("k", fixtures.Type) + }, + "Caller": func() *Event { + return e.Caller(1) + }, + "CallerSkip": func() *Event { + return e.CallerSkipFrame(2) + }, + "Stack": func() *Event { + return e.Stack() + }, + } + + for name := range types { + f := types[name] + if got := f(); got != nil { + t.Errorf("Event.Bool() = %v, want %v", got, nil) + } + } + + e.Send() + e.Msg("nothing") + e.Msgf("what %s", "nothing") + + got := e.write() + if got != nil { + t.Errorf("Event.write() = %v, want %v", got, e) + } + + called := false + e.MsgFunc(func() string { + called = true + return "called" + }) + if called { + t.Errorf("Event.MsgFunc() should not be called on nil Event") + } +} + +func TestEvent_MsgFunc(t *testing.T) { + var buf bytes.Buffer + e := newEvent(LevelWriterAdapter{&buf}, DebugLevel) + + called := false + e.MsgFunc(func() string { + called = true + return "called" + }) + if !called { + t.Errorf("Event.MsgFunc() was not called on non-nil Event") + } + + want := `{"message":"called"}` + got := strings.TrimSpace(buf.String()) + if got != want { + t.Errorf("Event.MsgFunc() = %q, want %q", got, want) + } +} diff --git a/fields.go b/fields.go index 6e0cb70..c355079 100644 --- a/fields.go +++ b/fields.go @@ -34,6 +34,15 @@ func appendFields(dst []byte, fields interface{}, stack bool) []byte { return dst } +func appendObject(dst []byte, obj LogObjectMarshaler) []byte { + e := newEvent(nil, 0) + e.buf = e.buf[:0] // discard the beginning marker added by newEvent + e.appendObject(obj) + dst = append(dst, e.buf...) + putEvent(e) + return dst +} + func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte { for i, n := 0, len(kvList); i < n; i += 2 { key, val := kvList[i], kvList[i+1] @@ -43,11 +52,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte { continue } if val, ok := val.(LogObjectMarshaler); ok { - e := newEvent(nil, 0) - e.buf = e.buf[:0] - e.appendObject(val) - dst = append(dst, e.buf...) - putEvent(e) + dst = appendObject(dst, val) continue } switch val := val.(type) { @@ -58,11 +63,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte { case error: switch m := ErrorMarshalFunc(val).(type) { case LogObjectMarshaler: - e := newEvent(nil, 0) - e.buf = e.buf[:0] - e.appendObject(m) - dst = append(dst, e.buf...) - putEvent(e) + dst = appendObject(dst, m) case error: if m == nil || isNilValue(m) { dst = enc.AppendNil(dst) @@ -94,11 +95,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte { for i, err := range val { switch m := ErrorMarshalFunc(err).(type) { case LogObjectMarshaler: - e := newEvent(nil, 0) - e.buf = e.buf[:0] - e.appendObject(m) - dst = append(dst, e.buf...) - putEvent(e) + dst = appendObject(dst, m) case error: if m == nil || isNilValue(m) { dst = enc.AppendNil(dst) @@ -112,7 +109,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte { } if i < (len(val) - 1) { - enc.AppendArrayDelim(dst) + dst = enc.AppendArrayDelim(dst) } } dst = enc.AppendArrayEnd(dst) diff --git a/fixtures_test.go b/fixtures_test.go new file mode 100644 index 0000000..49580d8 --- /dev/null +++ b/fixtures_test.go @@ -0,0 +1,159 @@ +package zerolog + +import ( + "context" + "errors" + "fmt" + "net" + "reflect" + "time" +) + +type fixtureObj struct { + Pub string + Tag string `json:"tag"` + priv int +} + +func (o fixtureObj) MarshalZerologObject(e *Event) { + e.Str("Pub", o.Pub). + Str("Tag", o.Tag). + Int("priv", o.priv) +} + +type fieldFixtures struct { + Bools []bool + Bytes []byte + Ctx context.Context + Durations []time.Duration + Errs []error + Floats32 []float32 + Floats64 []float64 + Interfaces []struct { + Pub string + Tag string `json:"tag"` + priv int + } + Ints []int + Ints8 []int8 + Ints16 []int16 + Ints32 []int32 + Ints64 []int64 + Uints []uint + Uints8 []uint8 + Uints16 []uint16 + Uints32 []uint32 + Uints64 []uint64 + IPAddrs []net.IP + IPPfxs []net.IPNet + MACAddr net.HardwareAddr + Objects []fixtureObj + RawCBOR []byte + RawJSON []byte + Stringers []fmt.Stringer + Strings []string + Times []time.Time + Type reflect.Type +} + +func makeFieldFixtures() *fieldFixtures { + bools := []bool{true, false, true, false, true, false, true, false, true, false} + bytes := []byte(`abcdef`) + ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + ints8 := []int8{-8, 8} + ints16 := []int16{-16, 16} + ints32 := []int32{-32, 32} + ints64 := []int64{-64, 64} + uints := []uint{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + uints8 := []uint8{8, uint8(^uint8(0))} + uints16 := []uint16{16, uint16(^uint16(0))} + uints32 := []uint32{32, uint32(^uint32(0))} + uints64 := []uint64{64, uint64(^uint64(0))} + floats32 := []float32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + floats64 := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + strings := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} + durations := []time.Duration{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + times := []time.Time{ + time.Unix(0, 0), + time.Unix(1, 0), + time.Unix(2, 0), + time.Unix(3, 0), + time.Unix(4, 0), + time.Unix(5, 0), + time.Unix(6, 0), + time.Unix(7, 0), + time.Unix(8, 0), + time.Unix(9, 0), + } + interfaces := []struct { + Pub string + Tag string `json:"tag"` + priv int + }{ + {"A", "j", -5}, + {"B", "i", -4}, + {"C", "h", -3}, + {"D", "g", -2}, + {"E", "f", -1}, + {"F", "e", 0}, + {"G", "d", 1}, + {"H", "c", 2}, + {"I", "b", 3}, + {"J", "a", 4}, + } + objects := []fixtureObj{ + {"a", "z", 1}, + {"b", "y", 2}, + {"c", "x", 3}, + {"d", "w", 4}, + {"e", "v", 5}, + {"f", "u", 6}, + {"g", "t", 7}, + {"h", "s", 8}, + {"i", "r", 9}, + {"j", "q", 10}, + } + ipAddrV4 := net.IP{192, 168, 0, 1} + ipAddrV6 := net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34} + ipAddrs := []net.IP{ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6, ipAddrV4, ipAddrV6} + ipPfxV4 := net.IPNet{IP: net.IP{192, 168, 0, 0}, Mask: net.CIDRMask(24, 32)} + ipPfxV6 := net.IPNet{IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x00}, Mask: net.CIDRMask(64, 128)} + ipPfxs := []net.IPNet{ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6, ipPfxV4, ipPfxV6} + macAddr := net.HardwareAddr{0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E} + errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e"), nil, errorObjectMarshalerImpl{fmt.Errorf("oops")}} + ctx := context.Background() + stringers := []fmt.Stringer{ipAddrs[0], durations[0]} + rawJSON := []byte(`{"some":"json"}`) + rawCBOR := []byte{0xA1, 0x64, 0x73, 0x6F, 0x6D, 0x65, 0x64, 0x61, 0x74, 0x61} // {"some":"data"} + + return &fieldFixtures{ + Bools: bools, + Bytes: bytes, + Ctx: ctx, + Durations: durations, + Errs: errs, + Floats32: floats32, + Floats64: floats64, + Interfaces: interfaces, + Ints: ints, + Ints8: ints8, + Ints16: ints16, + Ints32: ints32, + Ints64: ints64, + Uints: uints, + Uints8: uints8, + Uints16: uints16, + Uints32: uints32, + Uints64: uints64, + IPAddrs: ipAddrs, + IPPfxs: ipPfxs, + MACAddr: macAddr, + Objects: objects, + RawCBOR: rawCBOR, + RawJSON: rawJSON, + Stringers: stringers, + Strings: strings, + Times: times, + Type: reflect.TypeOf(12345), + } +} diff --git a/internal/cbor/base_test.go b/internal/cbor/base_test.go new file mode 100644 index 0000000..88e8dc1 --- /dev/null +++ b/internal/cbor/base_test.go @@ -0,0 +1,22 @@ +package cbor + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestAppendKey(t *testing.T) { + want := make([]byte, 0) + want = append(want, 0xbf) // start string + want = append(want, 0x63) // length 3 + want = append(want, []byte("key")...) + + got := enc.AppendKey([]byte{}, "key") + if !bytes.Equal(got, want) { + t.Errorf("AppendKey(%v)\ngot: 0x%s\nwant: 0x%s", + "key", + hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} diff --git a/internal/cbor/decoder_test.go b/internal/cbor/decoder_test.go index 812e27f..20e7fc0 100644 --- a/internal/cbor/decoder_test.go +++ b/internal/cbor/decoder_test.go @@ -3,16 +3,19 @@ package cbor import ( "bytes" "encoding/hex" + "math" "testing" "time" + + "github.com/rs/zerolog/internal" ) func TestDecodeInteger(t *testing.T) { - for _, tc := range integerTestCases { - gotv := decodeInteger(getReader(tc.binary)) - if gotv != int64(tc.val) { + for _, tc := range internal.IntegerTestCases { + gotv := decodeInteger(getReader(tc.Binary)) + if gotv != int64(tc.Val) { t.Errorf("decodeInteger(0x%s)=0x%d, want: 0x%d", - hex.EncodeToString([]byte(tc.binary)), gotv, tc.val) + hex.EncodeToString([]byte(tc.Binary)), gotv, tc.Val) } } } @@ -28,11 +31,11 @@ func TestDecodeString(t *testing.T) { } func TestDecodeArray(t *testing.T) { - for _, tc := range integerArrayTestCases { + for _, tc := range internal.IntegerArrayTestCases { buf := bytes.NewBuffer([]byte{}) - array2Json(getReader(tc.binary), buf) - if buf.String() != tc.json { - t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.binary)), buf.String(), tc.json) + array2Json(getReader(tc.Binary), buf) + if buf.String() != tc.Json { + t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.Binary)), buf.String(), tc.Json) } } //Unspecified Length Array @@ -53,27 +56,27 @@ func TestDecodeArray(t *testing.T) { t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.out)), buf.String(), tc.out) } } - for _, tc := range booleanArrayTestCases { + for _, tc := range internal.BooleanArrayTestCases { buf := bytes.NewBuffer([]byte{}) - array2Json(getReader(tc.binary), buf) - if buf.String() != tc.json { - t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.binary)), buf.String(), tc.json) + array2Json(getReader(tc.Binary), buf) + if buf.String() != tc.Json { + t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.Binary)), buf.String(), tc.Json) } } //TODO add cases for arrays of other types } var infiniteMapDecodeTestCases = []struct { - bin []byte - json string + Bin []byte + Json string }{ {[]byte("\xbf\x64IETF\x20\xff"), "{\"IETF\":-1}"}, {[]byte("\xbf\x65Array\x84\x20\x00\x18\xc8\x14\xff"), "{\"Array\":[-1,0,200,20]}"}, } var mapDecodeTestCases = []struct { - bin []byte - json string + Bin []byte + Json string }{ {[]byte("\xa2\x64IETF\x20"), "{\"IETF\":-1}"}, {[]byte("\xa2\x65Array\x84\x20\x00\x18\xc8\x14"), "{\"Array\":[-1,0,200,20]}"}, @@ -82,90 +85,90 @@ var mapDecodeTestCases = []struct { func TestDecodeMap(t *testing.T) { for _, tc := range mapDecodeTestCases { buf := bytes.NewBuffer([]byte{}) - map2Json(getReader(string(tc.bin)), buf) - if buf.String() != tc.json { - t.Errorf("map2Json(0x%s)=%s, want: %s", hex.EncodeToString(tc.bin), buf.String(), tc.json) + map2Json(getReader(string(tc.Bin)), buf) + if buf.String() != tc.Json { + t.Errorf("map2Json(0x%s)=%s, want: %s", hex.EncodeToString(tc.Bin), buf.String(), tc.Json) } } for _, tc := range infiniteMapDecodeTestCases { buf := bytes.NewBuffer([]byte{}) - map2Json(getReader(string(tc.bin)), buf) - if buf.String() != tc.json { - t.Errorf("map2Json(0x%s)=%s, want: %s", hex.EncodeToString(tc.bin), buf.String(), tc.json) + map2Json(getReader(string(tc.Bin)), buf) + if buf.String() != tc.Json { + t.Errorf("map2Json(0x%s)=%s, want: %s", hex.EncodeToString(tc.Bin), buf.String(), tc.Json) } } } func TestDecodeBool(t *testing.T) { - for _, tc := range booleanTestCases { - got := decodeSimpleFloat(getReader(tc.binary)) - if string(got) != tc.json { - t.Errorf("decodeSimpleFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), string(got), tc.json) + for _, tc := range internal.BooleanTestCases { + got := decodeSimpleFloat(getReader(tc.Binary)) + if string(got) != tc.Json { + t.Errorf("decodeSimpleFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.Binary)), string(got), tc.Json) } } } func TestDecodeFloat(t *testing.T) { - for _, tc := range float32TestCases { - got, _ := decodeFloat(getReader(tc.binary)) - if got != float64(tc.val) { - t.Errorf("decodeFloat(0x%s)=%f, want:%f", hex.EncodeToString([]byte(tc.binary)), got, tc.val) + for _, tc := range internal.Float32TestCases { + got, _ := decodeFloat(getReader(tc.Binary)) + if got != float64(tc.Val) && math.IsNaN(got) != math.IsNaN(float64(tc.Val)) { + t.Errorf("decodeFloat(0x%s)=%f, want:%f", hex.EncodeToString([]byte(tc.Binary)), got, tc.Val) } } } func TestDecodeTimestamp(t *testing.T) { decodeTimeZone, _ = time.LoadLocation("UTC") - for _, tc := range timeIntegerTestcases { - tm := decodeTagData(getReader(tc.binary)) - if string(tm) != "\""+tc.rfcStr+"\"" { - t.Errorf("decodeFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), tm, tc.rfcStr) + for _, tc := range internal.TimeIntegerTestcases { + tm := decodeTagData(getReader(tc.Binary)) + if string(tm) != "\""+tc.RfcStr+"\"" { + t.Errorf("decodeFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.Binary)), tm, tc.RfcStr) } } - for _, tc := range timeFloatTestcases { - tm := decodeTagData(getReader(tc.out)) + for _, tc := range internal.TimeFloatTestcases { + tm := decodeTagData(getReader(tc.Out)) //Since we convert to float and back - it may be slightly off - so //we cannot check for exact equality instead, we'll check it is //very close to each other Less than a Microsecond (lets not yet do nanosec) got, _ := time.Parse(string(tm), string(tm)) - want, _ := time.Parse(tc.rfcStr, tc.rfcStr) + want, _ := time.Parse(tc.RfcStr, tc.RfcStr) if got.Sub(want) > time.Microsecond { - t.Errorf("decodeFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.out)), tm, tc.rfcStr) + t.Errorf("decodeFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.Out)), tm, tc.RfcStr) } } } func TestDecodeNetworkAddr(t *testing.T) { - for _, tc := range ipAddrTestCases { - d1 := decodeTagData(getReader(tc.binary)) - if string(d1) != tc.text { - t.Errorf("decodeNetworkAddr(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), d1, tc.text) + for _, tc := range internal.IpAddrTestCases { + d1 := decodeTagData(getReader(tc.Binary)) + if string(d1) != tc.Text { + t.Errorf("decodeNetworkAddr(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.Binary)), d1, tc.Text) } } } func TestDecodeMACAddr(t *testing.T) { - for _, tc := range macAddrTestCases { - d1 := decodeTagData(getReader(tc.binary)) - if string(d1) != tc.text { - t.Errorf("decodeNetworkAddr(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), d1, tc.text) + for _, tc := range internal.MacAddrTestCases { + d1 := decodeTagData(getReader(tc.Binary)) + if string(d1) != tc.Text { + t.Errorf("decodeNetworkAddr(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.Binary)), d1, tc.Text) } } } func TestDecodeIPPrefix(t *testing.T) { - for _, tc := range IPPrefixTestCases { - d1 := decodeTagData(getReader(tc.binary)) - if string(d1) != tc.text { - t.Errorf("decodeIPPrefix(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), d1, tc.text) + for _, tc := range internal.IPPrefixTestCases { + d1 := decodeTagData(getReader(tc.Binary)) + if string(d1) != tc.Text { + t.Errorf("decodeIPPrefix(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.Binary)), d1, tc.Text) } } } var compositeCborTestCases = []struct { - binary []byte - json string + Binary []byte + Json string }{ {[]byte("\xbf\x64IETF\x20\x65Array\x9f\x20\x00\x18\xc8\x14\xff\xff"), "{\"IETF\":-1,\"Array\":[-1,0,200,20]}\n"}, {[]byte("\xbf\x64IETF\x64YES!\x65Array\x9f\x20\x00\x18\xc8\x14\xff\xff"), "{\"IETF\":\"YES!\",\"Array\":[-1,0,200,20]}\n"}, @@ -174,15 +177,15 @@ var compositeCborTestCases = []struct { func TestDecodeCbor2Json(t *testing.T) { for _, tc := range compositeCborTestCases { buf := bytes.NewBuffer([]byte{}) - err := Cbor2JsonManyObjects(getReader(string(tc.binary)), buf) - if buf.String() != tc.json || err != nil { - t.Errorf("cbor2JsonManyObjects(0x%s)=%s, want: %s, err:%s", hex.EncodeToString(tc.binary), buf.String(), tc.json, err.Error()) + err := Cbor2JsonManyObjects(getReader(string(tc.Binary)), buf) + if buf.String() != tc.Json || err != nil { + t.Errorf("cbor2JsonManyObjects(0x%s)=%s, want: %s, err:%s", hex.EncodeToString(tc.Binary), buf.String(), tc.Json, err.Error()) } } } var negativeCborTestCases = []struct { - binary []byte + Binary []byte errStr string }{ {[]byte("\xb9\x64IETF\x20\x65Array\x9f\x20\x00\x18\xc8\x14"), "Tried to Read 18 Bytes.. But hit end of file"}, @@ -197,7 +200,7 @@ var negativeCborTestCases = []struct { func TestDecodeNegativeCbor2Json(t *testing.T) { for _, tc := range negativeCborTestCases { buf := bytes.NewBuffer([]byte{}) - err := Cbor2JsonManyObjects(getReader(string(tc.binary)), buf) + err := Cbor2JsonManyObjects(getReader(string(tc.Binary)), buf) if err == nil || err.Error() != tc.errStr { t.Errorf("Expected error got:%s, want:%s", err, tc.errStr) } diff --git a/internal/cbor/string_test.go b/internal/cbor/string_test.go index 0e3e9ab..439f1ae 100644 --- a/internal/cbor/string_test.go +++ b/internal/cbor/string_test.go @@ -2,6 +2,7 @@ package cbor import ( "bytes" + "encoding/hex" "testing" ) @@ -12,6 +13,13 @@ var encodeStringTests = []struct { }{ {"", "\x60", ""}, {"\\", "\x61\x5c", "\\\\"}, + {"\"", "\x61\x22", "\\\""}, + {"\b", "\x61\x08", "\\b"}, + {"\f", "\x61\x0c", "\\f"}, + {"\n", "\x61\x0a", "\\n"}, + {"\r", "\x61\x0d", "\\r"}, + {"\t", "\x61\x09", "\\t"}, + {"Hi\t", "\x63Hi\x09", "Hi\\t"}, {"\x00", "\x61\x00", "\\u0000"}, {"\x01", "\x61\x01", "\\u0001"}, {"\x02", "\x61\x02", "\\u0002"}, @@ -44,6 +52,9 @@ var encodeByteTests = []struct { {[]byte("\x02"), "\x41\x02"}, {[]byte("\x03"), "\x41\x03"}, {[]byte("\x04"), "\x41\x04"}, + {[]byte("\f"), "\x41\x0C"}, + {[]byte("\n"), "\x41\x0A"}, + {[]byte("\r"), "\x41\x0D"}, {[]byte("*"), "\x41*"}, {[]byte("a"), "\x41a"}, {[]byte("IETF"), "\x44IETF"}, @@ -77,6 +88,53 @@ func TestAppendString(t *testing.T) { t.Errorf("appendString(%q) = %#q, want %#q", inp, got, want) } } +func TestAppendStrings(t *testing.T) { + array := []string{} + for _, tt := range encodeStringTests { + array = append(array, tt.plain) + } + want := make([]byte, 0) + want = append(want, 0x94) // start array length 24 + for _, tt := range encodeStringTests { + want = append(want, []byte(tt.binary)...) + } + + got := enc.AppendStrings([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendStrings(%v)\ngot: 0x%s\nwant: 0x%s", + array, + hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]string, 0) + want = make([]byte, 0) + want = append(want, 0x80) // start an empty string array + got = enc.AppendStrings([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendStrings(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now large array case + array = make([]string, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a large array + want = append(want, 0x18) // of length 24 + for i := 0; i < len(array); i++ { + array[i] = "test" + want = append(want, []byte("\x64test")...) + } + got = enc.AppendStrings([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendStrings(%v)\ngot: 0x%s\nwant: 0x%s", + array, + hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} func TestAppendBytes(t *testing.T) { for _, tt := range encodeByteTests { diff --git a/internal/cbor/time_test.go b/internal/cbor/time_test.go index d285e35..3c46ec7 100644 --- a/internal/cbor/time_test.go +++ b/internal/cbor/time_test.go @@ -1,11 +1,14 @@ package cbor import ( + "bytes" "encoding/hex" "fmt" "math" "testing" "time" + + "github.com/rs/zerolog/internal" ) func TestAppendTimeNow(t *testing.T) { @@ -13,7 +16,7 @@ func TestAppendTimeNow(t *testing.T) { s := enc.AppendTime([]byte{}, tm, "unused") got := string(s) - tm1 := float64(tm.Unix()) + float64(tm.Nanosecond())*1E-9 + tm1 := float64(tm.Unix()) + float64(tm.Nanosecond())*1e-9 tm2 := math.Float64bits(tm1) var tm3 [8]byte for i := uint(0); i < 8; i++ { @@ -27,55 +30,162 @@ func TestAppendTimeNow(t *testing.T) { } } -var timeIntegerTestcases = []struct { - txt string - binary string - rfcStr string -}{ - {"2013-02-03T19:54:00-08:00", "\xc1\x1a\x51\x0f\x30\xd8", "2013-02-04T03:54:00Z"}, - {"1950-02-03T19:54:00-08:00", "\xc1\x3a\x25\x71\x93\xa7", "1950-02-04T03:54:00Z"}, -} - func TestAppendTimePastPresentInteger(t *testing.T) { - for _, tt := range timeIntegerTestcases { - tin, err := time.Parse(time.RFC3339, tt.txt) + for _, tt := range internal.TimeIntegerTestcases { + tin, err := time.Parse(time.RFC3339, tt.Txt) if err != nil { - fmt.Println("Cannot parse input", tt.txt, ".. Skipping!", err) + fmt.Println("Cannot parse input", tt.Txt, ".. Skipping!", err) continue } b := enc.AppendTime([]byte{}, tin, "unused") - if got, want := string(b), tt.binary; got != want { - t.Errorf("appendString(%s) = 0x%s, want 0x%s", tt.txt, + if got, want := string(b), tt.Binary; got != want { + t.Errorf("appendString(%s) = 0x%s, want 0x%s", tt.Txt, hex.EncodeToString(b), hex.EncodeToString([]byte(want))) } } } -var timeFloatTestcases = []struct { - rfcStr string - out string -}{ - {"2006-01-02T15:04:05.999999-08:00", "\xc1\xfb\x41\xd0\xee\x6c\x59\x7f\xff\xfc"}, - {"1956-01-02T15:04:05.999999-08:00", "\xc1\xfb\xc1\xba\x53\x81\x1a\x00\x00\x11"}, -} - func TestAppendTimePastPresentFloat(t *testing.T) { const timeFloatFmt = "2006-01-02T15:04:05.999999-07:00" - for _, tt := range timeFloatTestcases { - tin, err := time.Parse(timeFloatFmt, tt.rfcStr) + for _, tt := range internal.TimeFloatTestcases { + tin, err := time.Parse(timeFloatFmt, tt.RfcStr) if err != nil { - fmt.Println("Cannot parse input", tt.rfcStr, ".. Skipping!") + fmt.Println("Cannot parse input", tt.RfcStr, ".. Skipping!") continue } b := enc.AppendTime([]byte{}, tin, "unused") - if got, want := string(b), tt.out; got != want { - t.Errorf("appendString(%s) = 0x%s, want 0x%s", tt.rfcStr, + if got, want := string(b), tt.Out; got != want { + t.Errorf("appendString(%s) = 0x%s, want 0x%s", tt.RfcStr, hex.EncodeToString(b), hex.EncodeToString([]byte(want))) } } } +func TestAppendTimes(t *testing.T) { + const timeFloatFmt = "2006-01-02T15:04:05.999999-07:00" + array := make([]time.Time, len(internal.TimeFloatTestcases)) + want := make([]byte, 0) + want = append(want, 0x82) // start small array + for i, tt := range internal.TimeFloatTestcases { + array[i], _ = time.Parse(timeFloatFmt, tt.RfcStr) + want = append(want, []byte(tt.Out)...) + } + + got := enc.AppendTimes([]byte{}, array, "unused") + if !bytes.Equal(got, want) { + t.Errorf("AppendTimes(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]time.Time, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendTimes([]byte{}, array, "unused") + if !bytes.Equal(got, want) { + t.Errorf("AppendTimes(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now large array case + testtime, _ := time.Parse(timeFloatFmt, internal.TimeFloatTestcases[0].RfcStr) + outbytes := internal.TimeFloatTestcases[0].Out + array = make([]time.Time, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a large array + want = append(want, 0x18) // of length 24 + for i := 0; i < len(array); i++ { + array[i] = testtime + want = append(want, []byte(outbytes)...) + } + got = enc.AppendTimes([]byte{}, array, "unused") + if !bytes.Equal(got, want) { + t.Errorf("AppendTimes(%v)\ngot: 0x%s\nwant: 0x%s", + array, + hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendDurationFloat(t *testing.T) { + for _, tt := range internal.DurTestcases { + dur := tt.Duration + want := []byte{} + want = append(want, []byte(tt.FloatOut)...) + got := enc.AppendDuration([]byte{}, dur, time.Microsecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDuration(%v)=\ngot: 0x%s\nwant: 0x%s", + dur, + hex.EncodeToString(got), + hex.EncodeToString(want)) + } + } +} +func TestAppendDurationInteger(t *testing.T) { + for _, tt := range internal.DurTestcases { + dur := tt.Duration + want := []byte{} + want = append(want, []byte(tt.IntegerOut)...) + got := enc.AppendDuration([]byte{}, dur, time.Microsecond, true, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDuration(%v)=\ngot: 0x%s\nwant: 0x%s", + dur, + hex.EncodeToString(got), + hex.EncodeToString(want)) + } + } +} +func TestAppendDurations(t *testing.T) { + array := make([]time.Duration, len(internal.DurTestcases)) + want := make([]byte, 0) + want = append(want, 0x83) // start 3 element array + for i, tt := range internal.DurTestcases { + array[i] = tt.Duration + want = append(want, []byte(tt.FloatOut)...) + } + + got := enc.AppendDurations([]byte{}, array, time.Microsecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDurations(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]time.Duration, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendDurations([]byte{}, array, time.Microsecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDurations(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now large array case + testtime := internal.DurTestcases[0].Duration + outbytes := internal.DurTestcases[0].FloatOut + array = make([]time.Duration, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a large array + want = append(want, 0x18) // of length 24 + for i := 0; i < len(array); i++ { + array[i] = testtime + want = append(want, []byte(outbytes)...) + } + got = enc.AppendDurations([]byte{}, array, time.Microsecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDurations(%v)\ngot: 0x%s\nwant: 0x%s", + array, + hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} func BenchmarkAppendTime(b *testing.B) { tests := map[string]string{ diff --git a/internal/cbor/types_test.go b/internal/cbor/types_test.go index 6320a7e..2611475 100644 --- a/internal/cbor/types_test.go +++ b/internal/cbor/types_test.go @@ -1,9 +1,14 @@ package cbor import ( + "bytes" "encoding/hex" + "errors" + "math" "net" "testing" + + "github.com/rs/zerolog/internal" ) var enc = Encoder{} @@ -13,283 +18,1024 @@ func TestAppendNil(t *testing.T) { got := string(s) want := "\xf6" if got != want { - t.Errorf("appendNull() = 0x%s, want: 0x%s", hex.EncodeToString(s), + t.Errorf("AppendNil() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } +} +func TestAppendBeginMarker(t *testing.T) { + s := enc.AppendBeginMarker([]byte{}) + got := string(s) + want := "\xbf" + if got != want { + t.Errorf("AppendBeginMarker() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } +} +func TestAppendEndMarker(t *testing.T) { + s := enc.AppendEndMarker([]byte{}) + got := string(s) + want := "\xff" + if got != want { + t.Errorf("AppendEndMarker() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } +} +func TestAppendObjectData(t *testing.T) { + data := []byte{0xbf, 0x02, 0x03} + s := enc.AppendObjectData([]byte{}, data) + got := string(s) + want := "\x02\x03" // the begin marker is not copied + if got != want { + t.Errorf("AppendObjectData() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } +} +func TestAppendArrayDelim(t *testing.T) { + s := enc.AppendArrayDelim([]byte{}) + got := string(s) + want := "" + if got != want { + t.Errorf("AppendArrayDelim() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } +} +func TestAppendLineBreak(t *testing.T) { + s := enc.AppendLineBreak([]byte{}) + got := string(s) + want := "" + if got != want { + t.Errorf("AppendLineBreak() = 0x%s, want: 0x%s", hex.EncodeToString(s), hex.EncodeToString([]byte(want))) } } -var booleanTestCases = []struct { - val bool - binary string - json string -}{ - {true, "\xf5", "true"}, - {false, "\xf4", "false"}, +func TestAppendInterface(t *testing.T) { + oldJSONMarshalFunc := JSONMarshalFunc + defer func() { + JSONMarshalFunc = oldJSONMarshalFunc + }() + + JSONMarshalFunc = func(v interface{}) ([]byte, error) { + return internal.InterfaceMarshalFunc(v) + } + + var i int = 17 + got := enc.AppendInterface([]byte{}, i) + want := make([]byte, 0) + want = append(want, 0xd9, 0x01) // start an array + want = append(want, 0x06, 0x42) // of type interface, two characters + want = append(want, 0x31, 0x37) // with literal int 17 + if !bytes.Equal(got, want) { + t.Errorf("AppendInterface\ngot: 0x%s\nwant: 0x%s", + hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + JSONMarshalFunc = func(v interface{}) ([]byte, error) { + return nil, errors.New("test") + } + + got = enc.AppendInterface([]byte{}, nil) + want = make([]byte, 0) + want = append(want, 0x76) // string + want = append(want, []byte("marshaling error: test")...) // of type interface, two characters + if !bytes.Equal(got, want) { + t.Errorf("AppendInterface\ngot: 0x%s\nwant: 0x%s", + hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendType(t *testing.T) { + s := enc.AppendType([]byte{}, "") + got := string(s) + want := "\x66string" + if got != want { + t.Errorf("AppendType() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } + + s = enc.AppendType([]byte{}, nil) + got = string(s) + want = "\x65" + if got != want { + t.Errorf("AppendType() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } + + var n *int = nil + s = enc.AppendType([]byte{}, n) + got = string(s) + want = "\x64*int" + if got != want { + t.Errorf("AppendType() = 0x%s, want: 0x%s", hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } } func TestAppendBool(t *testing.T) { - for _, tc := range booleanTestCases { - s := enc.AppendBool([]byte{}, tc.val) + for _, tc := range internal.BooleanTestCases { + s := enc.AppendBool([]byte{}, tc.Val) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendBool(%s)=0x%s, want: 0x%s", - tc.json, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Json, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } } -var booleanArrayTestCases = []struct { - val []bool - binary string - json string -}{ - {[]bool{}, "\x9f\xff", "[]"}, - {[]bool{false}, "\x81\xf4", "[false]"}, - {[]bool{true, false, true}, "\x83\xf5\xf4\xf5", "[true,false,true]"}, - {[]bool{true, false, false, true, false, true}, "\x86\xf5\xf4\xf4\xf5\xf4\xf5", "[true,false,false,true,false,true]"}, -} - func TestAppendBoolArray(t *testing.T) { - for _, tc := range booleanArrayTestCases { - s := enc.AppendBools([]byte{}, tc.val) + for _, tc := range internal.BooleanArrayTestCases { + s := enc.AppendBools([]byte{}, tc.Val) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendBools(%s)=0x%s, want: 0x%s", - tc.json, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Json, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } + + // now empty array case + array := make([]bool, 0) + want := make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got := enc.AppendBools([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendBools(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a large array case + array = make([]bool, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a large array + want = append(want, 0x18) // for 24 elements + for i := 0; i < 24; i++ { + array[i] = bool(i%2 == 1) + want = append(want, 0xf4|byte(i&0x01)) // 0xf4 is false, 0xf5 is true + } + got = enc.AppendBools([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendBools(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendInt8(t *testing.T) { + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt8) || (tc.Val > math.MaxInt8) { + continue + } + s := enc.AppendInt8([]byte{}, int8(tc.Val)) + got := string(s) + if got != tc.Binary { + t.Errorf("AppendInt8(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } } -var integerTestCases = []struct { - val int - binary string -}{ - // Value included in the type. - {0, "\x00"}, - {1, "\x01"}, - {2, "\x02"}, - {3, "\x03"}, - {8, "\x08"}, - {9, "\x09"}, - {10, "\x0a"}, - {22, "\x16"}, - {23, "\x17"}, - // Value in 1 byte. - {24, "\x18\x18"}, - {25, "\x18\x19"}, - {26, "\x18\x1a"}, - {100, "\x18\x64"}, - {254, "\x18\xfe"}, - {255, "\x18\xff"}, - // Value in 2 bytes. - {256, "\x19\x01\x00"}, - {257, "\x19\x01\x01"}, - {1000, "\x19\x03\xe8"}, - {0xFFFF, "\x19\xff\xff"}, - // Value in 4 bytes. - {0x10000, "\x1a\x00\x01\x00\x00"}, - {0x7FFFFFFE, "\x1a\x7f\xff\xff\xfe"}, - {1000000, "\x1a\x00\x0f\x42\x40"}, - // Negative number test cases. - // Value included in the type. - {-1, "\x20"}, - {-2, "\x21"}, - {-3, "\x22"}, - {-10, "\x29"}, - {-21, "\x34"}, - {-22, "\x35"}, - {-23, "\x36"}, - {-24, "\x37"}, - // Value in 1 byte. - {-25, "\x38\x18"}, - {-26, "\x38\x19"}, - {-100, "\x38\x63"}, - {-254, "\x38\xfd"}, - {-255, "\x38\xfe"}, - {-256, "\x38\xff"}, - // Value in 2 bytes. - {-257, "\x39\x01\x00"}, - {-258, "\x39\x01\x01"}, - {-1000, "\x39\x03\xe7"}, - // Value in 4 bytes. - {-0x10001, "\x3a\x00\x01\x00\x00"}, - {-0x7FFFFFFE, "\x3a\x7f\xff\xff\xfd"}, - {-1000000, "\x3a\x00\x0f\x42\x3f"}, +func TestAppendInts8(t *testing.T) { + array := make([]int8, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x1b) // for signed 8-bit elements + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt8) || (tc.Val > math.MaxInt8) { + continue + } + array = append(array, int8(tc.Val)) + want = append(want, tc.Binary...) + } + + got := enc.AppendInts8([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts8(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]int8, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendInts8([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts8(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now small array case + array = make([]int8, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a small array + for i := 0; i < 21; i++ { + array[i] = int8(i) + want = append(want, byte(i)) + } + got = enc.AppendInts8([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts8(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendInt16(t *testing.T) { + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt16) || (tc.Val > math.MaxInt16) { + continue + } + s := enc.AppendInt16([]byte{}, int16(tc.Val)) + got := string(s) + if got != tc.Binary { + t.Errorf("AppendInt16(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } +} + +func TestAppendInts16(t *testing.T) { + array := make([]int16, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x28) // for signed 16-bit elements + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt16) || (tc.Val > math.MaxInt16) { + continue + } + array = append(array, int16(tc.Val)) + want = append(want, tc.Binary...) + } + + got := enc.AppendInts16([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts16(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]int16, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendInts16([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts16(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a small array case + array = make([]int16, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a smaller array + for i := 0; i < 21; i++ { + array[i] = int16(i) + want = append(want, byte(i)) + } + got = enc.AppendInts16([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts16(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendInt32(t *testing.T) { + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt32) || (tc.Val > math.MaxInt32) { + continue + } + s := enc.AppendInt32([]byte{}, int32(tc.Val)) + got := string(s) + if got != tc.Binary { + t.Errorf("AppendInt32(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } +} + +func TestAppendInts32(t *testing.T) { + array := make([]int32, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x31) // for signed 32-bit elements + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt32) || (tc.Val > math.MaxInt32) { + continue + } + array = append(array, int32(tc.Val)) + want = append(want, tc.Binary...) + } + + got := enc.AppendInts32([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]int32, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendInts32([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a small array case + array = make([]int32, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a smaller array + for i := 0; i < 21; i++ { + array[i] = int32(i) + want = append(want, byte(i)) + } + got = enc.AppendInts32([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendInt64(t *testing.T) { + for _, tc := range internal.IntegerTestCases { + s := enc.AppendInt64([]byte{}, int64(tc.Val)) + got := string(s) + if got != tc.Binary { + t.Errorf("AppendInt64(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } +} + +func TestAppendInts64(t *testing.T) { + array := make([]int64, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x33) // for signed 64-bit elements + for _, tc := range internal.IntegerTestCases { + array = append(array, int64(tc.Val)) + want = append(want, tc.Binary...) + } + + got := enc.AppendInts64([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]int64, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendInts64([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a small array case + array = make([]int64, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a smaller array + for i := 0; i < 21; i++ { + array[i] = int64(i) + want = append(want, byte(i)) + } + got = enc.AppendInts64([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } } func TestAppendInt(t *testing.T) { - for _, tc := range integerTestCases { - s := enc.AppendInt([]byte{}, tc.val) + for _, tc := range internal.IntegerTestCases { + s := enc.AppendInt([]byte{}, tc.Val) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendInt(0x%x)=0x%s, want: 0x%s", - tc.val, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } } -var integerArrayTestCases = []struct { - val []int - binary string - json string -}{ - {[]int{}, "\x9f\xff", "[]"}, - {[]int{32768}, "\x81\x19\x80\x00", "[32768]"}, - {[]int{-1, 0, 200, 20}, "\x84\x20\x00\x18\xc8\x14", "[-1,0,200,20]"}, - {[]int{-200, -10, 200, 400}, "\x84\x38\xc7\x29\x18\xc8\x19\x01\x90", "[-200,-10,200,400]"}, - {[]int{1, 2, 3}, "\x83\x01\x02\x03", "[1,2,3]"}, - {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}, - "\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19", - "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]"}, +func TestAppendInts(t *testing.T) { + array := make([]int, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x33) // for signed int elements + for _, tc := range internal.IntegerTestCases { + array = append(array, int(tc.Val)) + want = append(want, tc.Binary...) + } + + got := enc.AppendInts([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]int, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendInts([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a small array case + array = make([]int, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a smaller array + for i := 0; i < 21; i++ { + array[i] = int(i) + want = append(want, byte(i)) + } + got = enc.AppendInts([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendUint8(t *testing.T) { + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint8 { + continue + } + s := enc.AppendUint8([]byte{}, uint8(tc.Val)) + got := string(s) + if got != tc.Binary { + t.Errorf("AppendUint8(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } +} + +func TestAppendUints8(t *testing.T) { + array := make([]uint8, 0) + want := make([]byte, 0) + want = append(want, 0x91) // start array for unsigned 8-bit elements + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint8 { + continue + } + array = append(array, uint8(tc.Val)) + want = append(want, tc.Binary...) + } + + got := enc.AppendUints8([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints8(%v)=0x%s, want: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]uint8, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendUints8([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints8(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a large array case + array = make([]uint8, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a large array + want = append(want, 0x18) // for 24 elements + for i := 0; i < 24; i++ { + array[i] = uint8(i) + want = append(want, byte(i)) + } + got = enc.AppendUints8([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints8(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendUint16(t *testing.T) { + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint16 { + continue + } + s := enc.AppendUint16([]byte{}, uint16(tc.Val)) + got := string(s) + if got != tc.Binary { + t.Errorf("AppendUint16(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + + } +} + +func TestAppendUints16(t *testing.T) { + array := make([]uint16, 0) + want := make([]byte, 0) + want = append(want, 0x97) // start array for unsigned 16-bit elements + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint16 { + continue + } + array = append(array, uint16(tc.Val)) + want = append(want, tc.Binary...) + } + + got := enc.AppendUints16([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints16(%v)=0x%s, want: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]uint16, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendUints16([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints8(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a large array case + array = make([]uint16, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a larger array + want = append(want, 0x18) // for 24 elements + for i := 0; i < 24; i++ { + array[i] = uint16(i) + want = append(want, byte(i)) + } + got = enc.AppendUints16([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints16(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendUint32(t *testing.T) { + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint32 { + continue + } + s := enc.AppendUint32([]byte{}, uint32(tc.Val)) + got := string(s) + want := tc.Bigbinary + if got != want { + t.Errorf("AppendUint32(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } + } +} + +func TestAppendUints32(t *testing.T) { + array := make([]uint32, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x1f) // for unsigned 32-bit elements + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint32 { + continue + } + array = append(array, uint32(tc.Val)) + want = append(want, tc.Bigbinary...) + } + + got := enc.AppendUints32([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]uint32, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendUints32([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a small array case + array = make([]uint32, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a smaller array + for i := 0; i < 21; i++ { + array[i] = uint32(i) + want = append(want, byte(i)) + } + got = enc.AppendUints32([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendUint64(t *testing.T) { + for _, tc := range internal.UnsignedIntegerTestCases { + s := enc.AppendUint64([]byte{}, uint64(tc.Val)) + got := string(s) + want := tc.Bigbinary + if got != want { + t.Errorf("AppendUint64(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } + } +} + +func TestAppendUints64(t *testing.T) { + array := make([]uint64, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x21) // for unsigned 64-bit elements + for _, tc := range internal.UnsignedIntegerTestCases { + array = append(array, uint64(tc.Val)) + want = append(want, tc.Bigbinary...) + } + + got := enc.AppendUints64([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]uint64, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendUints64([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a small array case + array = make([]uint64, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a smaller array + for i := 0; i < 21; i++ { + array[i] = uint64(i) + want = append(want, byte(i)) + } + got = enc.AppendUints64([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendUint(t *testing.T) { + for _, tc := range internal.UnsignedIntegerTestCases { + s := enc.AppendUint([]byte{}, tc.Val) + got := string(s) + want := tc.Bigbinary + if tc.Val == math.MaxUint64 { + want = "\x20" // this is special case for uint max value when using AppendUint + } + if got != want { + t.Errorf("AppendUint(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) + } + } +} + +func TestAppendUints(t *testing.T) { + array := make([]uint, 0) + want := make([]byte, 0) + want = append(want, 0x98) // start array + want = append(want, 0x21) // for unsigned int elements + for _, tc := range internal.UnsignedIntegerTestCases { + array = append(array, uint(tc.Val)) + expected := tc.Bigbinary + if tc.Val == math.MaxUint64 { + expected = "\x20" // this is special case for uint max value when using AppendUint + } + want = append(want, expected...) + } + + got := enc.AppendUints([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]uint, 0) + want = make([]byte, 0) + want = append(want, 0x9f) // start and end array + want = append(want, 0xff) // for empty array + got = enc.AppendUints([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a small array case + array = make([]uint, 21) + want = make([]byte, 0) + want = append(want, 0x95) // start a smaller array + for i := 0; i < 21; i++ { + array[i] = uint(i) + want = append(want, byte(i)) + } + got = enc.AppendUints([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } } func TestAppendIntArray(t *testing.T) { - for _, tc := range integerArrayTestCases { - s := enc.AppendInts([]byte{}, tc.val) + for _, tc := range internal.IntegerArrayTestCases { + s := enc.AppendInts([]byte{}, tc.Val) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendInts(%s)=0x%s, want: 0x%s", - tc.json, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Json, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } } -var float32TestCases = []struct { - val float32 - binary string -}{ - {0.0, "\xfa\x00\x00\x00\x00"}, - {-0.0, "\xfa\x00\x00\x00\x00"}, - {1.0, "\xfa\x3f\x80\x00\x00"}, - {1.5, "\xfa\x3f\xc0\x00\x00"}, - {65504.0, "\xfa\x47\x7f\xe0\x00"}, - {-4.0, "\xfa\xc0\x80\x00\x00"}, - {0.00006103515625, "\xfa\x38\x80\x00\x00"}, -} - func TestAppendFloat32(t *testing.T) { - for _, tc := range float32TestCases { - s := enc.AppendFloat32([]byte{}, tc.val, -1) + for _, tc := range internal.Float32TestCases { + s := enc.AppendFloat32([]byte{}, tc.Val, -1) got := string(s) - if got != tc.binary { - t.Errorf("AppendFloat32(%f)=0x%s, want: 0x%s", - tc.val, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + want := tc.Binary + if got != want { + t.Errorf("AppendFloat32(0x%x)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(want))) } } } -var ipAddrTestCases = []struct { - ipaddr net.IP - text string // ASCII representation of ipaddr - binary string // CBOR representation of ipaddr -}{ - {net.IP{10, 0, 0, 1}, "\"10.0.0.1\"", "\xd9\x01\x04\x44\x0a\x00\x00\x01"}, - {net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, - "\"2001:db8:85a3::8a2e:370:7334\"", - "\xd9\x01\x04\x50\x20\x01\x0d\xb8\x85\xa3\x00\x00\x00\x00\x8a\x2e\x03\x70\x73\x34"}, +func TestAppendFloats32(t *testing.T) { + array := []float32{1.0, 1.5} + want := make([]byte, 0) + want = append(want, 0x82) // start array for float elements + want = append(want, 0xfa, 0x3f, 0x80, 0x00, 0x00) // 32 bit 1.0 + want = append(want, 0xfa, 0x3f, 0xc0, 0x00, 0x00) // 32 bit 1.5 + + got := enc.AppendFloats32([]byte{}, array, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats32(%v)=0x%s, want: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = []float32{} + want = make([]byte, 0) + want = append(want, 0x9f, 0xff) // start and end array + got = enc.AppendFloats32([]byte{}, array, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a large array case + array = make([]float32, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a larger array + want = append(want, 0x18) // for 24 elements + for i := 0; i < 24; i++ { + want = append(want, 0xfa, 0x00, 0x00, 0x00, 0x00) + } + got = enc.AppendFloats32([]byte{}, array, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats32(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } +} + +func TestAppendFloat64(t *testing.T) { + for _, tc := range internal.Float64TestCases { + s := enc.AppendFloat64([]byte{}, tc.Val, -1) + got := string(s) + if got != tc.Binary && ((got == "NaN") != math.IsNaN(tc.Val)) { + t.Errorf("AppendFloat64(%f)=0x%s, want: 0x%s", + tc.Val, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } +} + +func TestAppendFloats64(t *testing.T) { + array := []float64{1.0, 1.5} + want := make([]byte, 0) + want = append(want, 0x82) // start array for float elements + want = append(want, 0xfb, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 64 bit 1.0 + want = append(want, 0xfb, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 64 bit 1.5 + + got := enc.AppendFloats64([]byte{}, array, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats64(%v)=0x%s, want: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = []float64{} + want = make([]byte, 0) + want = append(want, 0x9f, 0xff) // start and end array + got = enc.AppendFloats64([]byte{}, array, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a large array case + array = make([]float64, 24) + want = make([]byte, 0) + want = append(want, 0x98) // start a larger array + want = append(want, 0x18) // for 24 elements + for i := 0; i < 24; i++ { + want = append(want, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + } + got = enc.AppendFloats64([]byte{}, array, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats64(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } } func TestAppendNetworkAddr(t *testing.T) { - for _, tc := range ipAddrTestCases { - s := enc.AppendIPAddr([]byte{}, tc.ipaddr) + for _, tc := range internal.IpAddrTestCases { + s := enc.AppendIPAddr([]byte{}, tc.Ipaddr) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendIPAddr(%s)=0x%s, want: 0x%s", - tc.ipaddr, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Ipaddr, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } } -var IPAddrArrayTestCases = []struct { - val []net.IP - binary string - json string -}{ - {[]net.IP{}, "\x9f\xff", "[]"}, - {[]net.IP{{127, 0, 0, 0}}, "\x81\xd9\x01\x04\x44\x7f\x00\x00\x00", "[127.0.0.0]"}, - {[]net.IP{{0, 0, 0, 0}, {192, 168, 0, 100}}, "\x82\xd9\x01\x04\x44\x00\x00\x00\x00\xd9\x01\x04\x44\xc0\xa8\x00\x64", "[0.0.0.0,192.168.0.100]"}, -} - func TestAppendIPAddrArray(t *testing.T) { - for _, tc := range IPAddrArrayTestCases { - s := enc.AppendIPAddrs([]byte{}, tc.val) + for _, tc := range internal.IPAddrArrayTestCases { + s := enc.AppendIPAddrs([]byte{}, tc.Val) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendIPAddr(%s)=0x%s, want: 0x%s", - tc.json, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Json, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } -} -var macAddrTestCases = []struct { - macaddr net.HardwareAddr - text string // ASCII representation of macaddr - binary string // CBOR representation of macaddr -}{ - {net.HardwareAddr{0x12, 0x34, 0x56, 0x78, 0x90, 0xab}, "\"12:34:56:78:90:ab\"", "\xd9\x01\x04\x46\x12\x34\x56\x78\x90\xab"}, - {net.HardwareAddr{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3}, "\"20:01:0d:b8:85:a3\"", "\xd9\x01\x04\x46\x20\x01\x0d\xb8\x85\xa3"}, + // now a large array case + array := make([]net.IP, 24) + want := make([]byte, 0) + want = append(want, 0x98) // start a larger array + want = append(want, 0x18) // for 24 elements + for i := 0; i < 24; i++ { + array[i] = net.IP{0, 0, 0, byte(i)} + want = append(want, 0xd9, 0x01, 0x04, 0x44, 0x00, 0x00, 0x00, byte(i)) + } + got := enc.AppendIPAddrs([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendIPAddrs(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } } func TestAppendMACAddr(t *testing.T) { - for _, tc := range macAddrTestCases { - s := enc.AppendMACAddr([]byte{}, tc.macaddr) + for _, tc := range internal.MacAddrTestCases { + s := enc.AppendMACAddr([]byte{}, tc.Macaddr) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendMACAddr(%s)=0x%s, want: 0x%s", - tc.macaddr.String(), hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Macaddr.String(), hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } } -var IPPrefixTestCases = []struct { - pfx net.IPNet - text string // ASCII representation of pfx - binary string // CBOR representation of pfx -}{ - {net.IPNet{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}, "\"0.0.0.0/0\"", "\xd9\x01\x05\xa1\x44\x00\x00\x00\x00\x00"}, - {net.IPNet{IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}, "\"192.168.0.100/24\"", - "\xd9\x01\x05\xa1\x44\xc0\xa8\x00\x64\x18\x18"}, -} - func TestAppendIPPrefix(t *testing.T) { - for _, tc := range IPPrefixTestCases { - s := enc.AppendIPPrefix([]byte{}, tc.pfx) + for _, tc := range internal.IPPrefixTestCases { + s := enc.AppendIPPrefix([]byte{}, tc.Pfx) got := string(s) - if got != tc.binary { + if got != tc.Binary { t.Errorf("AppendIPPrefix(%s)=0x%s, want: 0x%s", - tc.pfx.String(), hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) + tc.Pfx.String(), hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) } } } -var IPPrefixArrayTestCases = []struct { - val []net.IPNet - binary string - json string -}{ - {[]net.IPNet{}, "\x9f\xff", "[]"}, - {[]net.IPNet{{IP: net.IP{127, 0, 0, 0}, Mask: net.CIDRMask(24, 32)}}, "\x81\xd9\x01\x05\xa1\x44\x7f\x00\x00\x00\x18\x18", "[127.0.0.0/24]"}, - {[]net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}, {IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}}, "\x82\xd9\x01\x05\xa1\x44\x00\x00\x00\x00\x00\xd9\x01\x05\xa1\x44\xc0\xa8\x00\x64\x18\x18", "[0.0.0.0/0,192.168.0.100/24]"}, +func TestAppendIPPrefixArray(t *testing.T) { + for _, tc := range internal.IPPrefixArrayTestCases { + s := enc.AppendIPPrefixes([]byte{}, tc.Val) + got := string(s) + if got != tc.Binary { + t.Errorf("AppendIPPrefix(%s)=0x%s, want: 0x%s", + tc.Json, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } + + // now a large array case + array := make([]net.IPNet, 24) + want := make([]byte, 0) + want = append(want, 0x98) // start a larger array + want = append(want, 0x18) // for 24 elements + for i := 0; i < 24; i++ { + array[i] = net.IPNet{IP: net.IP{0, 0, 0, byte(i)}, Mask: net.CIDRMask(24, 32)} + want = append(want, 0xd9, 0x01, 0x05, 0xa1, 0x44, 0x00, 0x00, 0x00, byte(i), 0x18, 0x18) + } + got := enc.AppendIPPrefixes([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendIPPrefixes(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } } -func TestAppendIPPrefixArray(t *testing.T) { - for _, tc := range IPPrefixArrayTestCases { - s := enc.AppendIPPrefixes([]byte{}, tc.val) - got := string(s) - if got != tc.binary { - t.Errorf("AppendIPPrefix(%s)=0x%s, want: 0x%s", - tc.json, hex.EncodeToString(s), - hex.EncodeToString([]byte(tc.binary))) - } +func TestAppendHex(t *testing.T) { + array := []byte{0x01, 0x02} + want := make([]byte, 0) + want = append(want, 0xd9, 0x01) // start array + want = append(want, 0x07, 0x42) // array of two elements + want = append(want, 0x01, 0x02) // 0x01, 0x02 + got := enc.AppendHex([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendHex(%v)=0x%s, want: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now empty array case + array = make([]byte, 0) + want = make([]byte, 0) + want = append(want, 0xd9, 0x01) // start an array + want = append(want, 0x07, 0x40) // array of zero elements + got = enc.AppendHex([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendHex(%v)\ngot: 0x%s\nwant: 0x%s", + array, hex.EncodeToString(got), + hex.EncodeToString(want)) } } diff --git a/internal/json/base_test.go b/internal/json/base_test.go new file mode 100644 index 0000000..0a2e37a --- /dev/null +++ b/internal/json/base_test.go @@ -0,0 +1,30 @@ +package json + +import ( + "bytes" + "testing" +) + +func TestAppendKey(t *testing.T) { + want := make([]byte, 0) + want = append(want, []byte("{\"key\":")...) + + got := enc.AppendKey([]byte("{"), "key") // test with empty object + if !bytes.Equal(got, want) { + t.Errorf("AppendKey(%v)\ngot: %s\nwant: %s", + "key", + string(got), + string(want)) + } + + want = make([]byte, 0) + want = append(want, []byte("},\"key\":")...) // test with non-empty object + + got = enc.AppendKey([]byte("}"), "key") + if !bytes.Equal(got, want) { + t.Errorf("AppendKey(%v)\ngot: %s\nwant: %s", + "key", + string(got), + string(want)) + } +} diff --git a/internal/json/bytes.go b/internal/json/bytes.go index de64120..2c624eb 100644 --- a/internal/json/bytes.go +++ b/internal/json/bytes.go @@ -23,7 +23,7 @@ func (Encoder) AppendBytes(dst, s []byte) []byte { func (Encoder) AppendHex(dst, s []byte) []byte { dst = append(dst, '"') for _, v := range s { - dst = append(dst, hex[v>>4], hex[v&0x0f]) + dst = append(dst, hexCharacters[v>>4], hexCharacters[v&0x0f]) } return append(dst, '"') } @@ -73,7 +73,7 @@ func appendBytesComplex(dst, s []byte, i int) []byte { case '\t': dst = append(dst, '\\', 't') default: - dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) + dst = append(dst, '\\', 'u', '0', '0', hexCharacters[b>>4], hexCharacters[b&0xF]) } i++ start = i diff --git a/internal/json/bytes_test.go b/internal/json/bytes_test.go index d1a370a..bd326b5 100644 --- a/internal/json/bytes_test.go +++ b/internal/json/bytes_test.go @@ -3,24 +3,26 @@ package json import ( "testing" "unicode" + + "github.com/rs/zerolog/internal" ) var enc = Encoder{} func TestAppendBytes(t *testing.T) { - for _, tt := range encodeStringTests { - b := enc.AppendBytes([]byte{}, []byte(tt.in)) - if got, want := string(b), tt.out; got != want { - t.Errorf("appendBytes(%q) = %#q, want %#q", tt.in, got, want) + for _, tt := range internal.EncodeStringTests { + b := enc.AppendBytes([]byte{}, []byte(tt.In)) + if got, want := string(b), tt.Out; got != want { + t.Errorf("appendBytes(%q) = %#q, want %#q", tt.In, got, want) } } } func TestAppendHex(t *testing.T) { - for _, tt := range encodeHexTests { - b := enc.AppendHex([]byte{}, []byte{tt.in}) - if got, want := string(b), tt.out; got != want { - t.Errorf("appendHex(%x) = %s, want %s", tt.in, got, want) + for _, tt := range internal.EncodeHexTests { + b := enc.AppendHex([]byte{}, []byte{tt.In}) + if got, want := string(b), tt.Out; got != want { + t.Errorf("appendHex(%x) = %s, want %s", tt.In, got, want) } } } diff --git a/internal/json/float_test.go b/internal/json/float_test.go new file mode 100644 index 0000000..c730ae4 --- /dev/null +++ b/internal/json/float_test.go @@ -0,0 +1,433 @@ +package json + +import ( + "bytes" + "encoding/json" + "fmt" + "math" + "math/rand" + "testing" + + "github.com/rs/zerolog/internal" +) + +var float64Tests = []struct { + Name string + Val float64 + Want string +}{ + { + Name: "Positive integer", + Val: 1234.0, + Want: "1234", + }, + { + Name: "Negative integer", + Val: -5678.0, + Want: "-5678", + }, + { + Name: "Positive decimal", + Val: 12.3456, + Want: "12.3456", + }, + { + Name: "Negative decimal", + Val: -78.9012, + Want: "-78.9012", + }, + { + Name: "Large positive number", + Val: 123456789.0, + Want: "123456789", + }, + { + Name: "Large negative number", + Val: -987654321.0, + Want: "-987654321", + }, + { + Name: "Zero", + Val: 0.0, + Want: "0", + }, + { + Name: "Smallest positive value", + Val: math.SmallestNonzeroFloat64, + Want: "5e-324", + }, + { + Name: "Largest positive value", + Val: math.MaxFloat64, + Want: "1.7976931348623157e+308", + }, + { + Name: "Smallest negative value", + Val: -math.SmallestNonzeroFloat64, + Want: "-5e-324", + }, + { + Name: "Largest negative value", + Val: -math.MaxFloat64, + Want: "-1.7976931348623157e+308", + }, + { + Name: "NaN", + Val: math.NaN(), + Want: `"NaN"`, + }, + { + Name: "+Inf", + Val: math.Inf(1), + Want: `"+Inf"`, + }, + { + Name: "-Inf", + Val: math.Inf(-1), + Want: `"-Inf"`, + }, + { + Name: "Clean up e-09 to e-9 case 1", + Val: 1e-9, + Want: "1e-9", + }, + { + Name: "Clean up e-09 to e-9 case 2", + Val: -2.236734e-9, + Want: "-2.236734e-9", + }, +} + +func TestEncoder_AppendFloat64(t *testing.T) { + for _, tc := range float64Tests { + t.Run(tc.Name, func(t *testing.T) { + var b []byte + b = (Encoder{}).AppendFloat64(b, tc.Val, -1) + if s := string(b); tc.Want != s { + t.Errorf("%q", s) + } + }) + } +} + +func FuzzEncoder_AppendFloat64(f *testing.F) { + for _, tc := range float64Tests { + f.Add(tc.Val) + } + f.Fuzz(func(t *testing.T, val float64) { + actual := (Encoder{}).AppendFloat64(nil, val, -1) + if len(actual) == 0 { + t.Fatal("empty buffer") + } + + if actual[0] == '"' { + switch string(actual) { + case `"NaN"`: + if !math.IsNaN(val) { + t.Fatalf("expected %v got NaN", val) + } + case `"+Inf"`: + if !math.IsInf(val, 1) { + t.Fatalf("expected %v got +Inf", val) + } + case `"-Inf"`: + if !math.IsInf(val, -1) { + t.Fatalf("expected %v got -Inf", val) + } + default: + t.Fatalf("unexpected string: %s", actual) + } + return + } + + if expected, err := json.Marshal(val); err != nil { + t.Error(err) + } else if string(actual) != string(expected) { + t.Errorf("expected %s, got %s", expected, actual) + } + + var parsed float64 + if err := json.Unmarshal(actual, &parsed); err != nil { + t.Fatal(err) + } + + if parsed != val { + t.Fatalf("expected %v, got %v", val, parsed) + } + }) +} + +var float32Tests = []struct { + Name string + Val float32 + Want string +}{ + { + Name: "Positive integer", + Val: 1234.0, + Want: "1234", + }, + { + Name: "Negative integer", + Val: -5678.0, + Want: "-5678", + }, + { + Name: "Positive decimal", + Val: 12.3456, + Want: "12.3456", + }, + { + Name: "Negative decimal", + Val: -78.9012, + Want: "-78.9012", + }, + { + Name: "Large positive number", + Val: 123456789.0, + Want: "123456790", + }, + { + Name: "Large negative number", + Val: -987654321.0, + Want: "-987654340", + }, + { + Name: "Zero", + Val: 0.0, + Want: "0", + }, + { + Name: "Smallest positive value", + Val: math.SmallestNonzeroFloat32, + Want: "1e-45", + }, + { + Name: "Largest positive value", + Val: math.MaxFloat32, + Want: "3.4028235e+38", + }, + { + Name: "Smallest negative value", + Val: -math.SmallestNonzeroFloat32, + Want: "-1e-45", + }, + { + Name: "Largest negative value", + Val: -math.MaxFloat32, + Want: "-3.4028235e+38", + }, + { + Name: "NaN", + Val: float32(math.NaN()), + Want: `"NaN"`, + }, + { + Name: "+Inf", + Val: float32(math.Inf(1)), + Want: `"+Inf"`, + }, + { + Name: "-Inf", + Val: float32(math.Inf(-1)), + Want: `"-Inf"`, + }, + { + Name: "Clean up e-09 to e-9 case 1", + Val: 1e-9, + Want: "1e-9", + }, + { + Name: "Clean up e-09 to e-9 case 2", + Val: -2.236734e-9, + Want: "-2.236734e-9", + }, +} + +func TestEncoder_AppendFloat32(t *testing.T) { + for _, tc := range float32Tests { + t.Run(tc.Name, func(t *testing.T) { + var b []byte + b = (Encoder{}).AppendFloat32(b, tc.Val, -1) + if s := string(b); tc.Want != s { + t.Errorf("%q", s) + } + }) + } +} + +func FuzzEncoder_AppendFloat32(f *testing.F) { + for _, tc := range float32Tests { + f.Add(tc.Val) + } + f.Fuzz(func(t *testing.T, val float32) { + actual := (Encoder{}).AppendFloat32(nil, val, -1) + if len(actual) == 0 { + t.Fatal("empty buffer") + } + + if actual[0] == '"' { + val := float64(val) + switch string(actual) { + case `"NaN"`: + if !math.IsNaN(val) { + t.Fatalf("expected %v got NaN", val) + } + case `"+Inf"`: + if !math.IsInf(val, 1) { + t.Fatalf("expected %v got +Inf", val) + } + case `"-Inf"`: + if !math.IsInf(val, -1) { + t.Fatalf("expected %v got -Inf", val) + } + default: + t.Fatalf("unexpected string: %s", actual) + } + return + } + + if expected, err := json.Marshal(val); err != nil { + t.Error(err) + } else if string(actual) != string(expected) { + t.Errorf("expected %s, got %s", expected, actual) + } + + var parsed float32 + if err := json.Unmarshal(actual, &parsed); err != nil { + t.Fatal(err) + } + + if parsed != val { + t.Fatalf("expected %v, got %v", val, parsed) + } + }) +} + +func generateFloat32s(n int) []float32 { + floats := make([]float32, n) + for i := 0; i < n; i++ { + floats[i] = rand.Float32() + } + return floats +} + +func generateFloat64s(n int) []float64 { + floats := make([]float64, n) + for i := 0; i < n; i++ { + floats[i] = rand.Float64() + } + return floats +} + +func TestAppendFloats32(t *testing.T) { + doOne := func(vals []float32) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + if math.IsNaN(float64(val)) { + want = append(want, []byte(`"NaN"`)...) + } else if math.IsInf(float64(val), 1) { + want = append(want, []byte(`"+Inf"`)...) + } else if math.IsInf(float64(val), -1) { + want = append(want, []byte(`"-Inf"`)...) + } else { + want = append(want, []byte(fmt.Sprintf("%v", float32(val)))...) + } + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendFloats32([]byte{}, vals, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats32(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]float32, 0) + for _, tc := range internal.Float32TestCases { + if tc.Val > 0 && tc.Val < 1e-4 { + continue // we want to ignore very small numbers for this test + } + array = append(array, float32(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} + +func TestAppendFloats64(t *testing.T) { + doOne := func(vals []float64) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + if math.IsNaN(val) { + want = append(want, []byte(`"NaN"`)...) + } else if math.IsInf(val, 1) { + want = append(want, []byte(`"+Inf"`)...) + } else if math.IsInf(val, -1) { + want = append(want, []byte(`"-Inf"`)...) + } else { + want = append(want, []byte(fmt.Sprintf("%v", val))...) + } + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendFloats64([]byte{}, vals, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendFloats64(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]float64, 0) + for _, tc := range internal.Float64TestCases { + if tc.Val > 0 && tc.Val < 1e-4 { + continue // we want to ignore very small numbers for this test + } + array = append(array, tc.Val) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} + +// this is really just for the memory allocation characteristics +func BenchmarkEncoder_AppendFloat32(b *testing.B) { + floats := append(generateFloat32s(5000), float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1))) + dst := make([]byte, 0, 128) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, f := range floats { + dst = (Encoder{}).AppendFloat32(dst[:0], f, -1) + } + } +} + +// this is really just for the memory allocation characteristics +func BenchmarkEncoder_AppendFloat64(b *testing.B) { + floats := append(generateFloat64s(5000), math.NaN(), math.Inf(1), math.Inf(-1)) + dst := make([]byte, 0, 128) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, f := range floats { + dst = (Encoder{}).AppendFloat64(dst[:0], f, -1) + } + } +} diff --git a/internal/json/int_test.go b/internal/json/int_test.go new file mode 100644 index 0000000..52120ad --- /dev/null +++ b/internal/json/int_test.go @@ -0,0 +1,356 @@ +package json + +import ( + "bytes" + "fmt" + "math" + "testing" + + "github.com/rs/zerolog/internal" +) + +func TestAppendInts8(t *testing.T) { + doOne := func(vals []int8) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", int8(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendInts8([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts8(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]int8, 0) + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt8) || (tc.Val > math.MaxInt8) { + continue + } + array = append(array, int8(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} +func TestAppendUints8(t *testing.T) { + doOne := func(vals []uint8) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%v", uint8(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendUints8([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints8(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]uint8, 0) + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint8 { + continue + } + array = append(array, uint8(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} +func TestAppendInts16(t *testing.T) { + doOne := func(vals []int16) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", int16(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendInts16([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts16(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]int16, 0) + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt16) || (tc.Val > math.MaxInt16) { + continue + } + array = append(array, int16(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} +func TestAppendUints16(t *testing.T) { + doOne := func(vals []uint16) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", uint16(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendUints16([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints16(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]uint16, 0) + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint16 { + continue + } + array = append(array, uint16(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} +func TestAppendInts32(t *testing.T) { + doOne := func(vals []int32) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", int32(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendInts32([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts32(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]int32, 0) + for _, tc := range internal.IntegerTestCases { + if (tc.Val < math.MinInt32) || (tc.Val > math.MaxInt32) { + continue + } + array = append(array, int32(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} +func TestAppendUints32(t *testing.T) { + doOne := func(vals []uint32) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", uint32(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendUints32([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints32(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]uint32, 0) + for _, tc := range internal.UnsignedIntegerTestCases { + if tc.Val > math.MaxUint32 { + continue + } + array = append(array, uint32(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} + +func TestAppendInt64(t *testing.T) { + doOne := func(vals []int64) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", int64(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendInts64([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts64(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]int64, 0) + for _, tc := range internal.IntegerTestCases { + array = append(array, int64(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} +func TestAppendUints64(t *testing.T) { + doOne := func(vals []uint64) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", uint64(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendUints64([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints64(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]uint64, 0) + for _, tc := range internal.UnsignedIntegerTestCases { + array = append(array, uint64(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} + +func TestAppendInt(t *testing.T) { + for _, tc := range internal.IntegerTestCases { + want := []byte(fmt.Sprintf("%d", tc.Val)) + got := enc.AppendInt([]byte{}, tc.Val) + if !bytes.Equal(got, want) { + t.Errorf("AppendInt(0x%x)\ngot: %s\nwant: %s", + tc.Val, + string(got), + string(want)) + } + } +} +func TestAppendUint(t *testing.T) { + for _, tc := range internal.UnsignedIntegerTestCases { + want := []byte(fmt.Sprintf("%d", tc.Val)) + got := enc.AppendUint([]byte{}, tc.Val) + if !bytes.Equal(got, want) { + t.Errorf("AppendUint(0x%x)\ngot: %s\nwant: %s", + tc.Val, + string(got), + string(want)) + } + } +} + +func TestAppendInts(t *testing.T) { + doOne := func(vals []int) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", int(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendInts([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendInts(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]int, 0) + for _, tc := range internal.IntegerTestCases { + array = append(array, int(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} +func TestAppendUints(t *testing.T) { + doOne := func(vals []uint) { + want := make([]byte, 0) + want = append(want, '[') + for i, val := range vals { + want = append(want, []byte(fmt.Sprintf("%d", uint(val)))...) + if i < len(vals)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendUints([]byte{}, vals) + if !bytes.Equal(got, want) { + t.Errorf("AppendUints(%v)\ngot: %s\nwant: %s", + vals, + string(got), + string(want)) + } + } + + array := make([]uint, 0) + for _, tc := range internal.UnsignedIntegerTestCases { + array = append(array, uint(tc.Val)) + } + + doOne(array) + doOne(array[:1]) // single element + doOne(array[:0]) // edge case of zero length +} diff --git a/internal/json/string.go b/internal/json/string.go index 275fa62..d641983 100644 --- a/internal/json/string.go +++ b/internal/json/string.go @@ -5,7 +5,7 @@ import ( "unicode/utf8" ) -const hex = "0123456789abcdef" +const hexCharacters = "0123456789abcdef" var noEscapeTable = [256]bool{} @@ -137,7 +137,7 @@ func appendStringComplex(dst []byte, s string, i int) []byte { case '\t': dst = append(dst, '\\', 't') default: - dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) + dst = append(dst, '\\', 'u', '0', '0', hexCharacters[b>>4], hexCharacters[b&0xF]) } i++ start = i diff --git a/internal/json/string_test.go b/internal/json/string_test.go index 10c1313..ae8524d 100644 --- a/internal/json/string_test.go +++ b/internal/json/string_test.go @@ -2,72 +2,51 @@ package json import ( "testing" + + "github.com/rs/zerolog/internal" ) -var encodeStringTests = []struct { - in string - out string -}{ - {"", `""`}, - {"\\", `"\\"`}, - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\b"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\f"`}, - {"\x0d", `"\r"`}, - {"\x0e", `"\u000e"`}, - {"\x0f", `"\u000f"`}, - {"\x10", `"\u0010"`}, - {"\x11", `"\u0011"`}, - {"\x12", `"\u0012"`}, - {"\x13", `"\u0013"`}, - {"\x14", `"\u0014"`}, - {"\x15", `"\u0015"`}, - {"\x16", `"\u0016"`}, - {"\x17", `"\u0017"`}, - {"\x18", `"\u0018"`}, - {"\x19", `"\u0019"`}, - {"\x1a", `"\u001a"`}, - {"\x1b", `"\u001b"`}, - {"\x1c", `"\u001c"`}, - {"\x1d", `"\u001d"`}, - {"\x1e", `"\u001e"`}, - {"\x1f", `"\u001f"`}, - {"✭", `"✭"`}, - {"foo\xc2\x7fbar", `"foo\ufffd\u007fbar"`}, // invalid sequence - {"ascii", `"ascii"`}, - {"\"a", `"\"a"`}, - {"\x1fa", `"\u001fa"`}, - {"foo\"bar\"baz", `"foo\"bar\"baz"`}, - {"\x1ffoo\x1fbar\x1fbaz", `"\u001ffoo\u001fbar\u001fbaz"`}, - {"emoji \u2764\ufe0f!", `"emoji ❤️!"`}, -} - -var encodeHexTests = []struct { - in byte - out string -}{ - {0x00, `"00"`}, - {0x0f, `"0f"`}, - {0x10, `"10"`}, - {0xf0, `"f0"`}, - {0xff, `"ff"`}, -} - func TestAppendString(t *testing.T) { - for _, tt := range encodeStringTests { - b := enc.AppendString([]byte{}, tt.in) - if got, want := string(b), tt.out; got != want { - t.Errorf("appendString(%q) = %#q, want %#q", tt.in, got, want) + for _, tt := range internal.EncodeStringTests { + b := enc.AppendString([]byte{}, tt.In) + if got, want := string(b), tt.Out; got != want { + t.Errorf("appendString(%q) = %#q, want %#q", tt.In, got, want) + } + } +} + +func TestAppendStrings(t *testing.T) { + for _, tt := range internal.EncodeStringsTests { + b := enc.AppendStrings([]byte{}, tt.In) + if got, want := string(b), tt.Out; got != want { + t.Errorf("appendStrings(%q) = %#q, want %#q", tt.In, got, want) + } + } +} + +func TestAppendStringer(t *testing.T) { + oldJSONMarshalFunc := JSONMarshalFunc + defer func() { + JSONMarshalFunc = oldJSONMarshalFunc + }() + + JSONMarshalFunc = func(v interface{}) ([]byte, error) { + return internal.InterfaceMarshalFunc(v) + } + + for _, tt := range internal.EncodeStringerTests { + b := enc.AppendStringer([]byte{}, tt.In) + if got, want := string(b), tt.Out; got != want { + t.Errorf("AppendStringer(%q)\ngot: %#q, want: %#q", tt.In, got, want) + } + } +} + +func TestAppendStringers(t *testing.T) { + for _, tt := range internal.EncodeStringersTests { + b := enc.AppendStringers([]byte{}, tt.In) + if got, want := string(b), tt.Out; got != want { + t.Errorf("appendStrings(%q) = %#q, want %#q", tt.In, got, want) } } } diff --git a/internal/json/time_test.go b/internal/json/time_test.go new file mode 100644 index 0000000..ae53924 --- /dev/null +++ b/internal/json/time_test.go @@ -0,0 +1,234 @@ +package json + +import ( + "bytes" + "fmt" + "testing" + "time" + + "github.com/rs/zerolog/internal" +) + +func TestAppendTimeNow(t *testing.T) { + tm := time.Now() + got := enc.AppendTime([]byte{}, tm, time.RFC3339) + want := tm.AppendFormat([]byte{'"'}, time.RFC3339) + want = append(want, '"') + if !bytes.Equal(got, want) { + t.Errorf("AppendTime(%s)\ngot: %s\nwant: %s", + "time.Now()", + string(got), + string(want)) + } +} + +func TestAppendTimePastPresentInteger(t *testing.T) { + for _, tt := range internal.TimeIntegerTestcases { + tin, err := time.Parse(time.RFC3339, tt.Txt) + if err != nil { + fmt.Println("Cannot parse input", tt.Txt, ".. Skipping!", err) + continue + } + + got := enc.AppendTime([]byte{}, tin, timeFormatUnix) + want := []byte(fmt.Sprintf("%d", tt.UnixInt)) + if !bytes.Equal(got, want) { + t.Errorf("appendString(%s)\ngot: %s\nwant: %s", + tt.Txt, + string(got), + string(want)) + } + got = enc.AppendTime([]byte{}, tin, timeFormatUnixMs) + want = []byte(fmt.Sprintf("%d", tt.UnixInt*1000)) + if !bytes.Equal(got, want) { + t.Errorf("appendString(%s)\ngot: %s\nwant: %s", + tt.Txt, + string(got), + string(want)) + } + got = enc.AppendTime([]byte{}, tin, timeFormatUnixMicro) + want = []byte(fmt.Sprintf("%d", tt.UnixInt*1000000)) + if !bytes.Equal(got, want) { + t.Errorf("appendString(%s)\ngot: %s\nwant: %s", + tt.Txt, + string(got), + string(want)) + } + got = enc.AppendTime([]byte{}, tin, timeFormatUnixNano) + want = []byte(fmt.Sprintf("%d", tt.UnixInt*1000000000)) + if !bytes.Equal(got, want) { + t.Errorf("appendString(%s)\ngot: %s\nwant: %s", + tt.Txt, + string(got), + string(want)) + } + } +} + +func TestAppendTimePastPresentFloat(t *testing.T) { + const timeFloatFmt = "2006-01-02T15:04:05.999999-07:00" + for _, tt := range internal.TimeFloatTestcases { + tin, err := time.Parse(timeFloatFmt, tt.RfcStr) + if err != nil { + fmt.Println("Cannot parse input", tt.RfcStr, ".. Skipping!") + continue + } + got := enc.AppendTime([]byte{}, tin, timeFormatUnix) + want := []byte(fmt.Sprintf("%d", tt.UnixInt)) + if !bytes.Equal(got, want) { + t.Errorf("appendString(%s)\ngot: %s\nwant: %s", + tt.RfcStr, + string(got), + string(want)) + } + } +} +func TestAppendTimes(t *testing.T) { + doOne := func(multiplier int, format string) { + array := make([]time.Time, 0) + want := append([]byte{}, '[') + want = append(want, ']') + got := enc.AppendTimes([]byte{}, array, format) + if !bytes.Equal(got, want) { + t.Errorf("AppendTimes(%v)\ngot: %s\nwant: %s", + array, + string(got), + string(want)) + } + + array = make([]time.Time, len(internal.TimeIntegerTestcases)) + want = append([]byte{}, '[') + for i, tt := range internal.TimeIntegerTestcases { + if tin, err := time.Parse(time.RFC3339, tt.RfcStr); err != nil { + fmt.Println("Cannot parse input", tt.RfcStr, ".. Skipping!") + continue + } else { + array[i] = tin + } + if multiplier == 0 { + want = append(want, '"') + formatted := array[i].Format(format) + want = append(want, []byte(fmt.Sprintf("%v", formatted))...) + want = append(want, '"') + } else { + scaled := tt.UnixInt * multiplier + want = append(want, []byte(fmt.Sprintf("%d", scaled))...) + } + if i < len(internal.TimeIntegerTestcases)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got = enc.AppendTimes([]byte{}, array, format) + if !bytes.Equal(got, want) { + t.Errorf("AppendTimes(%v) %d %s\ngot: %s\nwant: %s", + array, multiplier, format, + string(got), + string(want)) + } + } + + doOne(0, time.RFC3339) + doOne(1, timeFormatUnix) + doOne(1000, timeFormatUnixMs) + doOne(1000000, timeFormatUnixMicro) + doOne(1000000000, timeFormatUnixNano) +} + +func TestAppendDurationFloat(t *testing.T) { + for _, tt := range internal.DurTestcases { + dur := tt.Duration + want := []byte{} + want = append(want, []byte(fmt.Sprintf("%v", dur.Microseconds()))...) + got := enc.AppendDuration([]byte{}, dur, time.Microsecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDuration(%v)=\ngot: %s\nwant: %s", + dur, + string(got), + string(want)) + } + + want = []byte{} + fraction := float64(dur) / float64(time.Millisecond) + want = append(want, []byte(fmt.Sprintf("%v", fraction))...) + got = enc.AppendDuration([]byte{}, dur, time.Millisecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDuration(%v)=\ngot: %s\nwant: %s", + dur, + string(got), + string(want)) + } + } +} +func TestAppendDurationInteger(t *testing.T) { + for _, tt := range internal.DurTestcases { + dur := tt.Duration + want := []byte{} + whole := int(dur) / int(time.Microsecond) + want = append(want, []byte(fmt.Sprintf("%v", whole))...) + got := enc.AppendDuration([]byte{}, dur, time.Microsecond, true, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDuration(%v)=\ngot: %s\nwant: %s", + dur, + string(got), + string(want)) + } + } +} +func TestAppendDurations(t *testing.T) { + array := make([]time.Duration, len(internal.DurTestcases)) + want := make([]byte, 0) + want = append(want, '[') + for i, tt := range internal.DurTestcases { + array[i] = tt.Duration + whole := int(tt.Duration) / int(time.Microsecond) + want = append(want, []byte(fmt.Sprintf("%v", whole))...) + if i < len(internal.DurTestcases)-1 { + want = append(want, ',') + } + } + want = append(want, ']') + + got := enc.AppendDurations([]byte{}, array, time.Microsecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDurations(%v)\ngot: %s\nwant: %s", + array, + string(got), + string(want)) + } + + // now empty array case + array = make([]time.Duration, 0) + want = make([]byte, 0) + want = append(want, '[') + want = append(want, ']') + got = enc.AppendDurations([]byte{}, array, time.Microsecond, false, -1) + if !bytes.Equal(got, want) { + t.Errorf("AppendDurations(%v)\ngot: %s\nwant: %s", + array, + string(got), + string(want)) + } +} + +func BenchmarkAppendTime(b *testing.B) { + tests := map[string]string{ + "Integer": "Feb 3, 2013 at 7:54pm (PST)", + "Float": "2006-01-02T15:04:05.999999-08:00", + } + const timeFloatFmt = "2006-01-02T15:04:05.999999-07:00" + + for name, str := range tests { + t, err := time.Parse(time.RFC3339, str) + if err != nil { + t, _ = time.Parse(timeFloatFmt, str) + } + b.Run(name, func(b *testing.B) { + buf := make([]byte, 0, 100) + for i := 0; i < b.N; i++ { + _ = enc.AppendTime(buf, t, "unused") + } + }) + } +} diff --git a/internal/json/types_test.go b/internal/json/types_test.go index 0914a3d..abdd68a 100644 --- a/internal/json/types_test.go +++ b/internal/json/types_test.go @@ -1,14 +1,141 @@ package json import ( + "bytes" + "encoding/hex" "encoding/json" + "errors" "math" - "math/rand" "net" "reflect" "testing" + + "github.com/rs/zerolog/internal" ) +func TestAppendNil(t *testing.T) { + got := enc.AppendNil([]byte{}) + want := []byte(`null`) + if !bytes.Equal(got, want) { + t.Errorf("AppendNil() = %s, want: %s", + string(got), + string(want)) + } +} +func TestAppendBeginMarker(t *testing.T) { + got := enc.AppendBeginMarker([]byte{}) + want := []byte(`{`) + if !bytes.Equal(got, want) { + t.Errorf("AppendBeginMarker()\ngot: %s, want: %s", + string(got), + string(want)) + } +} +func TestAppendEndMarker(t *testing.T) { + got := enc.AppendEndMarker([]byte{}) + want := []byte(`}`) + if !bytes.Equal(got, want) { + t.Errorf("AppendEndMarker()\ngot: %s, want: %s", + string(got), + string(want)) + } +} +func TestAppendArrayStart(t *testing.T) { + got := enc.AppendArrayStart([]byte{}) + want := []byte("[") + if !bytes.Equal(got, want) { + t.Errorf("AppendArrayStart() = %s, want: %s", + string(got), + string(want)) + } +} +func TestAppendArrayEnd(t *testing.T) { + got := enc.AppendArrayEnd([]byte{}) + want := []byte("]") + if !bytes.Equal(got, want) { + t.Errorf("AppendArrayEnd() = %s, want: %s", + string(got), + string(want)) + } +} +func TestAppendArrayDelim(t *testing.T) { + got := enc.AppendArrayDelim([]byte{}) + want := []byte("") + if !bytes.Equal(got, want) { + t.Errorf("AppendArrayDelim() = 0x%s, want: 0x%s", + string(got), + string(want)) + } + + got = enc.AppendArrayDelim([]byte("a")) + want = []byte("a,") + if !bytes.Equal(got, want) { + t.Errorf("AppendArrayDelim() = 0x%s, want: 0x%s", + string(got), + string(want)) + } +} +func TestAppendLineBreak(t *testing.T) { + got := enc.AppendLineBreak([]byte{}) + want := []byte("\n") + if !bytes.Equal(got, want) { + t.Errorf("AppendLineBreak() = 0x%s, want: 0x%s", + string(got), + string(want)) + } +} + +// inline copy from globals.go of InterfaceMarshalFunc used in tests to avoid import cycle +func interfaceMarshalFunc(v interface{}) ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(false) + err := encoder.Encode(v) + if err != nil { + return nil, err + } + b := buf.Bytes() + if len(b) > 0 { + // Remove trailing \n which is added by Encode. + return b[:len(b)-1], nil + } + return b, nil +} + +func TestAppendInterface(t *testing.T) { + oldJSONMarshalFunc := JSONMarshalFunc + defer func() { + JSONMarshalFunc = oldJSONMarshalFunc + }() + + JSONMarshalFunc = func(v interface{}) ([]byte, error) { + return interfaceMarshalFunc(v) + } + + var i int = 17 + got := enc.AppendInterface([]byte{}, i) + want := make([]byte, 0) + want = append(want, []byte("17")...) // of type interface, two characters + if !bytes.Equal(got, want) { + t.Errorf("AppendInterface\ngot: 0x%s\nwant: 0x%s", + string(got), + string(want)) + } + + JSONMarshalFunc = func(v interface{}) ([]byte, error) { + return nil, errors.New("test") + } + + got = enc.AppendInterface([]byte{}, nil) + want = make([]byte, 0) + want = append(want, []byte("\"marshaling error: test\"")...) // of type interface, two characters + if !bytes.Equal(got, want) { + t.Errorf("AppendInterface\ngot: 0x%s\nwant: 0x%s", + string(got), + string(want)) + } +} + func TestAppendType(t *testing.T) { w := map[string]func(interface{}) []byte{ "AppendInt": func(v interface{}) []byte { return enc.AppendInt([]byte{}, v.(int)) }, @@ -70,7 +197,7 @@ func TestAppendType(t *testing.T) { } } -func Test_appendMAC(t *testing.T) { +func TestAppendMAC(t *testing.T) { MACtests := []struct { input string want []byte @@ -88,7 +215,70 @@ func Test_appendMAC(t *testing.T) { } } -func Test_appendIP(t *testing.T) { +func TestAppendBool(t *testing.T) { + for _, tc := range internal.BooleanTestCases { + s := enc.AppendBool([]byte{}, tc.Val) + got := string(s) + if got != tc.Json { + t.Errorf("AppendBool(%s)=0x%s, want: 0x%s", + tc.Json, + string(s), + string([]byte(tc.Binary))) + } + } +} + +func TestAppendBoolArray(t *testing.T) { + for _, tc := range internal.BooleanArrayTestCases { + s := enc.AppendBools([]byte{}, tc.Val) + got := string(s) + if got != tc.Json { + t.Errorf("AppendBools(%s)=0x%s, want: 0x%s", + tc.Json, + hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.Binary))) + } + } + + // now empty array case + array := make([]bool, 0) + want := make([]byte, 0) + want = append(want, []byte("[]")...) // start and end array + got := enc.AppendBools([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendBools(%v)\ngot: 0x%s\nwant: 0x%s", + array, + hex.EncodeToString(got), + hex.EncodeToString(want)) + } + + // now a large array case + array = make([]bool, 24) + want = make([]byte, 0) + want = append(want, []byte("[")...) // start a large array + for i := 0; i < 24; i++ { + array[i] = bool(i%2 == 1) + if array[i] { + want = append(want, []byte("true")...) + } else { + want = append(want, []byte("false")...) + } + if (i + 1) < 24 { + want = append(want, []byte(",")...) + } + } + want = append(want, []byte("]")...) // end a large array + + got = enc.AppendBools([]byte{}, array) + if !bytes.Equal(got, want) { + t.Errorf("AppendBools(%v)\ngot: 0x%s\nwant: 0x%s", + array, + string(got), + string(want)) + } +} + +func TestAppendIP(t *testing.T) { IPv4tests := []struct { input net.IP want []byte @@ -130,7 +320,7 @@ var IPAddrArrayTestCases = []struct { {[]net.IP{{0, 0, 0, 0}, {192, 168, 0, 100}}, []byte(`["0.0.0.0","192.168.0.100"]`)}, } -func Test_appendIPAddr_array(t *testing.T) { +func TestAppendIPAddrs(t *testing.T) { for _, tt := range IPAddrArrayTestCases { t.Run("IPAddrs", func(t *testing.T) { if got := enc.AppendIPAddrs([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { @@ -149,7 +339,7 @@ var IPPrefixArrayTestCases = []struct { {[]net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}, {IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}}, []byte(`["0.0.0.0/0","192.168.0.100/24"]`)}, } -func Test_appendIPPrefix_array(t *testing.T) { +func TestAppendIPPrefixes(t *testing.T) { for _, tt := range IPPrefixArrayTestCases { t.Run("IPPrefixes", func(t *testing.T) { if got := enc.AppendIPPrefixes([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { @@ -159,7 +349,7 @@ func Test_appendIPPrefix_array(t *testing.T) { } } -func Test_appendIPPrefix(t *testing.T) { +func TestAppendIPPrefix(t *testing.T) { IPv4Prefixtests := []struct { input net.IPNet want []byte @@ -193,7 +383,7 @@ func Test_appendIPPrefix(t *testing.T) { } } -func Test_appendMac(t *testing.T) { +func TestAppendMACAddr(t *testing.T) { MACtests := []struct { input net.HardwareAddr want []byte @@ -211,7 +401,7 @@ func Test_appendMac(t *testing.T) { } } -func Test_appendType(t *testing.T) { +func TestAppendType2(t *testing.T) { typeTests := []struct { label string input interface{} @@ -233,7 +423,7 @@ func Test_appendType(t *testing.T) { } } -func Test_appendObjectData(t *testing.T) { +func TestAppendObjectData(t *testing.T) { tests := []struct { dst []byte obj []byte @@ -252,340 +442,3 @@ func Test_appendObjectData(t *testing.T) { }) } } - -var float64Tests = []struct { - Name string - Val float64 - Want string -}{ - { - Name: "Positive integer", - Val: 1234.0, - Want: "1234", - }, - { - Name: "Negative integer", - Val: -5678.0, - Want: "-5678", - }, - { - Name: "Positive decimal", - Val: 12.3456, - Want: "12.3456", - }, - { - Name: "Negative decimal", - Val: -78.9012, - Want: "-78.9012", - }, - { - Name: "Large positive number", - Val: 123456789.0, - Want: "123456789", - }, - { - Name: "Large negative number", - Val: -987654321.0, - Want: "-987654321", - }, - { - Name: "Zero", - Val: 0.0, - Want: "0", - }, - { - Name: "Smallest positive value", - Val: math.SmallestNonzeroFloat64, - Want: "5e-324", - }, - { - Name: "Largest positive value", - Val: math.MaxFloat64, - Want: "1.7976931348623157e+308", - }, - { - Name: "Smallest negative value", - Val: -math.SmallestNonzeroFloat64, - Want: "-5e-324", - }, - { - Name: "Largest negative value", - Val: -math.MaxFloat64, - Want: "-1.7976931348623157e+308", - }, - { - Name: "NaN", - Val: math.NaN(), - Want: `"NaN"`, - }, - { - Name: "+Inf", - Val: math.Inf(1), - Want: `"+Inf"`, - }, - { - Name: "-Inf", - Val: math.Inf(-1), - Want: `"-Inf"`, - }, - { - Name: "Clean up e-09 to e-9 case 1", - Val: 1e-9, - Want: "1e-9", - }, - { - Name: "Clean up e-09 to e-9 case 2", - Val: -2.236734e-9, - Want: "-2.236734e-9", - }, -} - -func TestEncoder_AppendFloat64(t *testing.T) { - for _, tc := range float64Tests { - t.Run(tc.Name, func(t *testing.T) { - var b []byte - b = (Encoder{}).AppendFloat64(b, tc.Val, -1) - if s := string(b); tc.Want != s { - t.Errorf("%q", s) - } - }) - } -} - -func FuzzEncoder_AppendFloat64(f *testing.F) { - for _, tc := range float64Tests { - f.Add(tc.Val) - } - f.Fuzz(func(t *testing.T, val float64) { - actual := (Encoder{}).AppendFloat64(nil, val, -1) - if len(actual) == 0 { - t.Fatal("empty buffer") - } - - if actual[0] == '"' { - switch string(actual) { - case `"NaN"`: - if !math.IsNaN(val) { - t.Fatalf("expected %v got NaN", val) - } - case `"+Inf"`: - if !math.IsInf(val, 1) { - t.Fatalf("expected %v got +Inf", val) - } - case `"-Inf"`: - if !math.IsInf(val, -1) { - t.Fatalf("expected %v got -Inf", val) - } - default: - t.Fatalf("unexpected string: %s", actual) - } - return - } - - if expected, err := json.Marshal(val); err != nil { - t.Error(err) - } else if string(actual) != string(expected) { - t.Errorf("expected %s, got %s", expected, actual) - } - - var parsed float64 - if err := json.Unmarshal(actual, &parsed); err != nil { - t.Fatal(err) - } - - if parsed != val { - t.Fatalf("expected %v, got %v", val, parsed) - } - }) -} - -var float32Tests = []struct { - Name string - Val float32 - Want string -}{ - { - Name: "Positive integer", - Val: 1234.0, - Want: "1234", - }, - { - Name: "Negative integer", - Val: -5678.0, - Want: "-5678", - }, - { - Name: "Positive decimal", - Val: 12.3456, - Want: "12.3456", - }, - { - Name: "Negative decimal", - Val: -78.9012, - Want: "-78.9012", - }, - { - Name: "Large positive number", - Val: 123456789.0, - Want: "123456790", - }, - { - Name: "Large negative number", - Val: -987654321.0, - Want: "-987654340", - }, - { - Name: "Zero", - Val: 0.0, - Want: "0", - }, - { - Name: "Smallest positive value", - Val: math.SmallestNonzeroFloat32, - Want: "1e-45", - }, - { - Name: "Largest positive value", - Val: math.MaxFloat32, - Want: "3.4028235e+38", - }, - { - Name: "Smallest negative value", - Val: -math.SmallestNonzeroFloat32, - Want: "-1e-45", - }, - { - Name: "Largest negative value", - Val: -math.MaxFloat32, - Want: "-3.4028235e+38", - }, - { - Name: "NaN", - Val: float32(math.NaN()), - Want: `"NaN"`, - }, - { - Name: "+Inf", - Val: float32(math.Inf(1)), - Want: `"+Inf"`, - }, - { - Name: "-Inf", - Val: float32(math.Inf(-1)), - Want: `"-Inf"`, - }, - { - Name: "Clean up e-09 to e-9 case 1", - Val: 1e-9, - Want: "1e-9", - }, - { - Name: "Clean up e-09 to e-9 case 2", - Val: -2.236734e-9, - Want: "-2.236734e-9", - }, -} - -func TestEncoder_AppendFloat32(t *testing.T) { - for _, tc := range float32Tests { - t.Run(tc.Name, func(t *testing.T) { - var b []byte - b = (Encoder{}).AppendFloat32(b, tc.Val, -1) - if s := string(b); tc.Want != s { - t.Errorf("%q", s) - } - }) - } -} - -func FuzzEncoder_AppendFloat32(f *testing.F) { - for _, tc := range float32Tests { - f.Add(tc.Val) - } - f.Fuzz(func(t *testing.T, val float32) { - actual := (Encoder{}).AppendFloat32(nil, val, -1) - if len(actual) == 0 { - t.Fatal("empty buffer") - } - - if actual[0] == '"' { - val := float64(val) - switch string(actual) { - case `"NaN"`: - if !math.IsNaN(val) { - t.Fatalf("expected %v got NaN", val) - } - case `"+Inf"`: - if !math.IsInf(val, 1) { - t.Fatalf("expected %v got +Inf", val) - } - case `"-Inf"`: - if !math.IsInf(val, -1) { - t.Fatalf("expected %v got -Inf", val) - } - default: - t.Fatalf("unexpected string: %s", actual) - } - return - } - - if expected, err := json.Marshal(val); err != nil { - t.Error(err) - } else if string(actual) != string(expected) { - t.Errorf("expected %s, got %s", expected, actual) - } - - var parsed float32 - if err := json.Unmarshal(actual, &parsed); err != nil { - t.Fatal(err) - } - - if parsed != val { - t.Fatalf("expected %v, got %v", val, parsed) - } - }) -} - -func generateFloat32s(n int) []float32 { - floats := make([]float32, n) - for i := 0; i < n; i++ { - floats[i] = rand.Float32() - } - return floats -} - -func generateFloat64s(n int) []float64 { - floats := make([]float64, n) - for i := 0; i < n; i++ { - floats[i] = rand.Float64() - } - return floats -} - -// this is really just for the memory allocation characteristics -func BenchmarkEncoder_AppendFloat32(b *testing.B) { - floats := append(generateFloat32s(5000), float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1))) - dst := make([]byte, 0, 128) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for _, f := range floats { - dst = (Encoder{}).AppendFloat32(dst[:0], f, -1) - } - } -} - -// this is really just for the memory allocation characteristics -func BenchmarkEncoder_AppendFloat64(b *testing.B) { - floats := append(generateFloat64s(5000), math.NaN(), math.Inf(1), math.Inf(-1)) - dst := make([]byte, 0, 128) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for _, f := range floats { - dst = (Encoder{}).AppendFloat64(dst[:0], f, -1) - } - } -} diff --git a/internal/testcases.go b/internal/testcases.go new file mode 100644 index 0000000..ccfeb2b --- /dev/null +++ b/internal/testcases.go @@ -0,0 +1,373 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "math" + "net" + "time" +) + +var BooleanTestCases = []struct { + Val bool + Binary string + Json string +}{ + {true, "\xf5", "true"}, + {false, "\xf4", "false"}, +} + +var BooleanArrayTestCases = []struct { + Val []bool + Binary string + Json string +}{ + {[]bool{}, "\x9f\xff", "[]"}, + {[]bool{false}, "\x81\xf4", "[false]"}, + {[]bool{true, false, true}, "\x83\xf5\xf4\xf5", "[true,false,true]"}, + {[]bool{true, false, false, true, false, true}, "\x86\xf5\xf4\xf4\xf5\xf4\xf5", "[true,false,false,true,false,true]"}, +} + +var IntegerTestCases = []struct { + Val int + Binary string +}{ + // Value included in the type. + {0, "\x00"}, + {1, "\x01"}, + {2, "\x02"}, + {3, "\x03"}, + {8, "\x08"}, + {9, "\x09"}, + {10, "\x0a"}, + {22, "\x16"}, + {23, "\x17"}, + + // Value in 1 byte. + {24, "\x18\x18"}, + {25, "\x18\x19"}, + {26, "\x18\x1a"}, + {127, "\x18\x7f"}, + {254, "\x18\xfe"}, + {255, "\x18\xff"}, + + // Value in 2 bytes. + {256, "\x19\x01\x00"}, + {257, "\x19\x01\x01"}, + {1000, "\x19\x03\xe8"}, + {0xFFFF, "\x19\xff\xff"}, + + // Value in 4 bytes. + {0x10000, "\x1a\x00\x01\x00\x00"}, + {0x7FFFFFFE, "\x1a\x7f\xff\xff\xfe"}, + {1000000, "\x1a\x00\x0f\x42\x40"}, + + // Negative number test cases. + // Value included in the type. + {-1, "\x20"}, + {-2, "\x21"}, + {-3, "\x22"}, + {-10, "\x29"}, + {-21, "\x34"}, + {-22, "\x35"}, + {-23, "\x36"}, + {-24, "\x37"}, + + // Value in 1 byte. + {-25, "\x38\x18"}, + {-26, "\x38\x19"}, + {-100, "\x38\x63"}, + {-128, "\x38\x7f"}, + {-254, "\x38\xfd"}, + {-255, "\x38\xfe"}, + {-256, "\x38\xff"}, + + // Value in 2 bytes. + {-257, "\x39\x01\x00"}, + {-258, "\x39\x01\x01"}, + {-1000, "\x39\x03\xe7"}, + + // Value in 4 bytes. + {-0x10001, "\x3a\x00\x01\x00\x00"}, + {-0x7FFFFFFE, "\x3a\x7f\xff\xff\xfd"}, + {-1000000, "\x3a\x00\x0f\x42\x3f"}, + + //Constants + {math.MaxInt8, "\x18\x7f"}, + {math.MinInt8, "\x38\x7f"}, + {math.MaxInt16, "\x19\x7f\xff"}, + {math.MinInt16, "\x39\x7f\xff"}, + {math.MaxInt32, "\x1a\x7f\xff\xff\xff"}, + {math.MinInt32, "\x3a\x7f\xff\xff\xff"}, + {math.MaxInt64, "\x1b\x7f\xff\xff\xff\xff\xff\xff\xff"}, + {math.MinInt64, "\x3b\x7f\xff\xff\xff\xff\xff\xff\xff"}, +} + +type UnsignedIntTestCase struct { + Val uint + Binary string + Bigbinary string +} + +var AdditionalUnsignedIntegerTestCases = []UnsignedIntTestCase{ + {0x7FFFFFFF, "\x18\xff", "\x1a\x7f\xff\xff\xff"}, + {0x80000000, "\x19\xff\xff", "\x1a\x80\x00\x00\x00"}, + {1000000, "\x1b\x80\x00\x00\x00\x00\x00\x00\x00", "\x1a\x00\x0f\x42\x40"}, + + //Constants + {math.MaxUint8, "\x18\xff", "\x18\xff"}, + {math.MaxUint16, "\x19\xff\xff", "\x19\xff\xff"}, + {math.MaxUint32, "\x1a\xff\xff\xff\xff", "\x1a\xff\xff\xff\xff"}, + {math.MaxUint64, "\x1b\xff\xff\xff\xff\xff\xff\xff\xff", "\x1b\xff\xff\xff\xff\xff\xff\xff\xff"}, +} + +func unsignedIntegerTestCases() []UnsignedIntTestCase { + size := len(IntegerTestCases) + len(AdditionalUnsignedIntegerTestCases) + cases := make([]UnsignedIntTestCase, 0, size) + cases = append(cases, AdditionalUnsignedIntegerTestCases...) + for _, itc := range IntegerTestCases { + if itc.Val < 0 { + continue + } + cases = append(cases, UnsignedIntTestCase{Val: uint(itc.Val), Binary: itc.Binary, Bigbinary: itc.Binary}) + } + return cases +} + +var UnsignedIntegerTestCases = unsignedIntegerTestCases() + +var Float32TestCases = []struct { + Val float32 + Binary string +}{ + {0.0, "\xfa\x00\x00\x00\x00"}, + {-0.0, "\xfa\x00\x00\x00\x00"}, + {1.0, "\xfa\x3f\x80\x00\x00"}, + {1.5, "\xfa\x3f\xc0\x00\x00"}, + {65504.0, "\xfa\x47\x7f\xe0\x00"}, + {-4.0, "\xfa\xc0\x80\x00\x00"}, + {0.00006103515625, "\xfa\x38\x80\x00\x00"}, + {float32(math.Inf(0)), "\xfa\x7f\x80\x00\x00"}, + {float32(math.Inf(-1)), "\xfa\xff\x80\x00\x00"}, + {float32(math.NaN()), "\xfa\x7f\xc0\x00\x00"}, + {math.SmallestNonzeroFloat32, "\xfa\x00\x00\x00\x01"}, + {math.MaxFloat32, "\xfa\x7f\x7f\xff\xff"}, +} + +var Float64TestCases = []struct { + Val float64 + Binary string +}{ + {0.0, "\xfa\x00\x00\x00\x00"}, + {-0.0, "\xfa\x00\x00\x00\x00"}, + {1.0, "\xfa\x3f\x80\x00\x00"}, + {1.5, "\xfa\x3f\xc0\x00\x00"}, + {65504.0, "\xfa\x47\x7f\xe0\x00"}, + {-4.0, "\xfa\xc0\x80\x00\x00"}, + {0.00006103515625, "\xfa\x38\x80\x00\x00"}, + {math.Inf(0), "\xfa\x7f\x80\x00\x00\x00\x00\x00\x00"}, + {math.Inf(-1), "\xfa\xff\x80\x00\x00\x00\x00\x00\x00"}, + {math.NaN(), "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"}, + {math.SmallestNonzeroFloat64, "\xfa\x00\x00\x00\x00\x00\x00\x00\x01"}, + {math.MaxFloat64, "\xfa\x7f\x7f\xff\xff"}, +} + +var IntegerArrayTestCases = []struct { + Val []int + Binary string + Json string +}{ + {[]int{}, "\x9f\xff", "[]"}, + {[]int{32768}, "\x81\x19\x80\x00", "[32768]"}, + {[]int{-1, 0, 200, 20}, "\x84\x20\x00\x18\xc8\x14", "[-1,0,200,20]"}, + {[]int{-200, -10, 200, 400}, "\x84\x38\xc7\x29\x18\xc8\x19\x01\x90", "[-200,-10,200,400]"}, + {[]int{1, 2, 3}, "\x83\x01\x02\x03", "[1,2,3]"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}, + "\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19", + "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]"}, +} + +var IpAddrTestCases = []struct { + Ipaddr net.IP + Text string + Binary string +}{ + {net.IP{10, 0, 0, 1}, "\"10.0.0.1\"", "\xd9\x01\x04\x44\x0a\x00\x00\x01"}, + {net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, + "\"2001:db8:85a3::8a2e:370:7334\"", + "\xd9\x01\x04\x50\x20\x01\x0d\xb8\x85\xa3\x00\x00\x00\x00\x8a\x2e\x03\x70\x73\x34"}, +} + +var IPAddrArrayTestCases = []struct { + Val []net.IP + Binary string + Json string +}{ + {[]net.IP{}, "\x9f\xff", "[]"}, + {[]net.IP{{127, 0, 0, 0}}, "\x81\xd9\x01\x04\x44\x7f\x00\x00\x00", "[127.0.0.0]"}, + {[]net.IP{{0, 0, 0, 0}, {192, 168, 0, 100}}, "\x82\xd9\x01\x04\x44\x00\x00\x00\x00\xd9\x01\x04\x44\xc0\xa8\x00\x64", "[0.0.0.0,192.168.0.100]"}, +} + +var IPPrefixTestCases = []struct { + Pfx net.IPNet + Text string // ASCII representation of pfx + Binary string // CBOR representation of pfx +}{ + {net.IPNet{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}, "\"0.0.0.0/0\"", "\xd9\x01\x05\xa1\x44\x00\x00\x00\x00\x00"}, + {net.IPNet{IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}, "\"192.168.0.100/24\"", + "\xd9\x01\x05\xa1\x44\xc0\xa8\x00\x64\x18\x18"}, +} + +var IPPrefixArrayTestCases = []struct { + Val []net.IPNet + Binary string + Json string +}{ + {[]net.IPNet{}, "\x9f\xff", "[]"}, + {[]net.IPNet{{IP: net.IP{127, 0, 0, 0}, Mask: net.CIDRMask(24, 32)}}, "\x81\xd9\x01\x05\xa1\x44\x7f\x00\x00\x00\x18\x18", "[127.0.0.0/24]"}, + {[]net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}, {IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}}, "\x82\xd9\x01\x05\xa1\x44\x00\x00\x00\x00\x00\xd9\x01\x05\xa1\x44\xc0\xa8\x00\x64\x18\x18", "[0.0.0.0/0,192.168.0.100/24]"}, +} + +var MacAddrTestCases = []struct { + Macaddr net.HardwareAddr + Text string // ASCII representation of macaddr + Binary string // CBOR representation of macaddr +}{ + {net.HardwareAddr{0x12, 0x34, 0x56, 0x78, 0x90, 0xab}, "\"12:34:56:78:90:ab\"", "\xd9\x01\x04\x46\x12\x34\x56\x78\x90\xab"}, + {net.HardwareAddr{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3}, "\"20:01:0d:b8:85:a3\"", "\xd9\x01\x04\x46\x20\x01\x0d\xb8\x85\xa3"}, +} + +var EncodeHexTests = []struct { + In byte + Out string +}{ + {0x00, `"00"`}, + {0x0f, `"0f"`}, + {0x10, `"10"`}, + {0xf0, `"f0"`}, + {0xff, `"ff"`}, +} + +var EncodeStringTests = []struct { + In string + Out string +}{ + {"", `""`}, + {"\\", `"\\"`}, + {"\x00", `"\u0000"`}, + {"\x01", `"\u0001"`}, + {"\x02", `"\u0002"`}, + {"\x03", `"\u0003"`}, + {"\x04", `"\u0004"`}, + {"\x05", `"\u0005"`}, + {"\x06", `"\u0006"`}, + {"\x07", `"\u0007"`}, + {"\x08", `"\b"`}, + {"\x09", `"\t"`}, + {"\x0a", `"\n"`}, + {"\x0b", `"\u000b"`}, + {"\x0c", `"\f"`}, + {"\x0d", `"\r"`}, + {"\x0e", `"\u000e"`}, + {"\x0f", `"\u000f"`}, + {"\x10", `"\u0010"`}, + {"\x11", `"\u0011"`}, + {"\x12", `"\u0012"`}, + {"\x13", `"\u0013"`}, + {"\x14", `"\u0014"`}, + {"\x15", `"\u0015"`}, + {"\x16", `"\u0016"`}, + {"\x17", `"\u0017"`}, + {"\x18", `"\u0018"`}, + {"\x19", `"\u0019"`}, + {"\x1a", `"\u001a"`}, + {"\x1b", `"\u001b"`}, + {"\x1c", `"\u001c"`}, + {"\x1d", `"\u001d"`}, + {"\x1e", `"\u001e"`}, + {"\x1f", `"\u001f"`}, + {"✭", `"✭"`}, + {"foo\xc2\x7fbar", `"foo\ufffd\u007fbar"`}, // invalid sequence + {"ascii", `"ascii"`}, + {"\"a", `"\"a"`}, + {"\x1fa", `"\u001fa"`}, + {"foo\"bar\"baz", `"foo\"bar\"baz"`}, + {"\x1ffoo\x1fbar\x1fbaz", `"\u001ffoo\u001fbar\u001fbaz"`}, + {"emoji \u2764\ufe0f!", `"emoji ❤️!"`}, +} + +var EncodeStringsTests = []struct { + In []string + Out string +}{ + {nil, `[]`}, + {[]string{}, `[]`}, + {[]string{"A"}, `["A"]`}, + {[]string{"A", "B"}, `["A","B"]`}, +} + +var EncodeStringerTests = []struct { + In fmt.Stringer + Out string +}{ + {nil, `null`}, + {fmt.Stringer(nil), `null`}, + {net.IPv4bcast, `"255.255.255.255"`}, +} + +var EncodeStringersTests = []struct { + In []fmt.Stringer + Out string +}{ + {nil, `[]`}, + {[]fmt.Stringer{}, `[]`}, + {[]fmt.Stringer{net.IPv4bcast}, `["255.255.255.255"]`}, + {[]fmt.Stringer{net.IPv4allsys, net.IPv4allrouter}, `["224.0.0.1","224.0.0.2"]`}, +} + +var TimeIntegerTestcases = []struct { + Txt string + Binary string + RfcStr string + UnixInt int +}{ + {"2013-02-03T19:54:00-08:00", "\xc1\x1a\x51\x0f\x30\xd8", "2013-02-04T03:54:00Z", 1359950040}, + {"1950-02-03T19:54:00-08:00", "\xc1\x3a\x25\x71\x93\xa7", "1950-02-04T03:54:00Z", -628200360}, +} + +var TimeFloatTestcases = []struct { + RfcStr string + Out string + UnixInt int +}{ + {"2006-01-02T15:04:05.999999-08:00", "\xc1\xfb\x41\xd0\xee\x6c\x59\x7f\xff\xfc", 1136243045}, + {"1956-01-02T15:04:05.999999-08:00", "\xc1\xfb\xc1\xba\x53\x81\x1a\x00\x00\x11", -441680155}, +} + +var DurTestcases = []struct { + Duration time.Duration + FloatOut string + IntegerOut string +}{ + {1000, "\xfb\x3f\xf0\x00\x00\x00\x00\x00\x00", "\x01"}, + {2000, "\xfb\x40\x00\x00\x00\x00\x00\x00\x00", "\x02"}, + {200000, "\xfb\x40\x69\x00\x00\x00\x00\x00\x00", "\x18\xc8"}, +} + +// inline copy from globals.go of InterfaceMarshalFunc used in tests to avoid import cycle +func InterfaceMarshalFunc(v interface{}) ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(false) + err := encoder.Encode(v) + if err != nil { + return nil, err + } + b := buf.Bytes() + if len(b) > 0 { + // Remove trailing \n which is added by Encode. + return b[:len(b)-1], nil + } + return b, nil +} diff --git a/log_example_test.go b/log_example_test.go index c48a1e3..219f691 100644 --- a/log_example_test.go +++ b/log_example_test.go @@ -507,6 +507,17 @@ func ExampleContext_IPAddr() { // Output: {"HostIP":"192.168.0.100","message":"hello world"} } +func ExampleContext_IPAddrs() { + hostIP := net.IP{192, 168, 0, 100} + log := zerolog.New(os.Stdout).With(). + IPAddrs("HostIP", []net.IP{hostIP}). + Logger() + + log.Log().Msg("hello world") + + // Output: {"HostIP":["192.168.0.100"],"message":"hello world"} +} + func ExampleContext_IPPrefix() { route := net.IPNet{IP: net.IP{192, 168, 0, 0}, Mask: net.CIDRMask(24, 32)} log := zerolog.New(os.Stdout).With(). @@ -518,6 +529,17 @@ func ExampleContext_IPPrefix() { // Output: {"Route":"192.168.0.0/24","message":"hello world"} } +func ExampleContext_IPPrefixes() { + route := net.IPNet{IP: net.IP{192, 168, 0, 0}, Mask: net.CIDRMask(24, 32)} + log := zerolog.New(os.Stdout).With(). + IPPrefixes("Route", []net.IPNet{route}). + Logger() + + log.Log().Msg("hello world") + + // Output: {"Route":["192.168.0.0/24"],"message":"hello world"} +} + func ExampleContext_MACAddr() { mac := net.HardwareAddr{0x00, 0x14, 0x22, 0x01, 0x23, 0x45} log := zerolog.New(os.Stdout).With(). @@ -560,3 +582,18 @@ func ExampleContext_Fields_slice() { // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} } + +func ExampleContext_Times() { + t1 := time.Time{} + t2 := t1.Add(time.Second * 10) + t := []time.Time{t1, t2} + + log := zerolog.New(os.Stdout).With(). + Str("foo", "bar"). + Times("times", t). + Logger() + + log.Log().Msg("hello world") + + // Output: {"foo":"bar","times":["0001-01-01T00:00:00Z","0001-01-01T00:00:10Z"],"message":"hello world"} +} diff --git a/log_test.go b/log_test.go index b3faa0d..3c4ffa8 100644 --- a/log_test.go +++ b/log_test.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "math" "net" "reflect" "runtime" @@ -121,17 +122,26 @@ func TestWith(t *testing.T) { Float32("float32", 11.101). Float64("float64", 12.30303). Time("time", time.Time{}). - Ctx(context.Background()) + Dur("dur", 3). + Ctx(context.Background()). + Any("any", "test"). + Interface("interface", struct { + Pub string + Tag string `json:"tag"` + }{"a", "b"}). + Type("type", math.Phi) _, file, line, _ := runtime.Caller(0) caller := fmt.Sprintf("%s:%d", file, line+3) log := ctx.Caller().Logger() log.Log().Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), + `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","dur":0.000003,"any":"test","interface":{"Pub":"a","tag":"b"},"type":"float64","caller":"`+caller+`"}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } // Validate CallerWithSkipFrameCount. out.Reset() + ctx = New(out).With() _, file, line, _ = runtime.Caller(0) caller = fmt.Sprintf("%s:%d", file, line+5) log = ctx.CallerWithSkipFrameCount(3).Logger() @@ -140,7 +150,38 @@ func TestWith(t *testing.T) { }() // The above line is a little contrived, but the line above should be the line due // to the extra frame skip. - if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`"}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + +func TestWithPlurals(t *testing.T) { + out := &bytes.Buffer{} + ctx := New(out).With(). + Strs("strings", []string{"foo", "bar"}). + Strs("strings_nil", nil). + Stringers("stringers", []fmt.Stringer{net.IP{127, 0, 0, 1}, nil}). + Stringers("stringers_nil", nil). + Errs("errs", []error{errors.New("some error"), errors.New("some other error")}). + Bools("bool", []bool{true, false}). + Ints("int", []int{1, 2}). + Ints8("int8", []int8{2, 3}). + Ints16("int16", []int16{3, 4}). + Ints32("int32", []int32{4, 5}). + Ints64("int64", []int64{5, 6}). + Uints("uint", []uint{6, 7}). + Uints8("uint8", []uint8{7, 8}). + Uints16("uint16", []uint16{8, 9}). + Uints32("uint32", []uint32{9, 10}). + Uints64("uint64", []uint64{10, 11}). + Floats32("float32", []float32{1.1, 2.2}). + Floats64("float64", []float64{2.2, 3.3}). + Times("time", []time.Time{time.Time{}.AddDate(0, 1, 2), time.Time{}.AddDate(4, 5, 6)}). + Durs("dur", []time.Duration{1 * time.Second, 2 * time.Second}) + log := ctx.Logger() + log.Log().Msg("") + if got, want := decodeIfBinaryToString(out.Bytes()), + `{"strings":["foo","bar"],"strings_nil":[],"stringers":["127.0.0.1",null],"stringers_nil":null,"errs":["some error","some other error"],"bool":[true,false],"int":[1,2],"int8":[2,3],"int16":[3,4],"int32":[4,5],"int64":[5,6],"uint":[6,7],"uint8":[7,8],"uint16":[8,9],"uint32":[9,10],"uint64":[10,11],"float32":[1.1,2.2],"float64":[2.2,3.3],"time":["0001-02-03T00:00:00Z","0005-06-07T00:00:00Z"],"dur":[1000,2000]}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -156,6 +197,7 @@ func TestWithReset(t *testing.T) { Hex("hex", []byte{0x12, 0xef}). Uint64("uint64", 10). Float64("float64", 12.30303). + Stack(). Ctx(context.Background()) log := ctx.Logger() log.Log().Msg("") @@ -188,9 +230,10 @@ func TestFieldsMap(t *testing.T) { "ipv6": net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, "dur": 1 * time.Second, "time": time.Time{}, - "obj": obj{"a", "b", 1}, + "obj": fixtureObj{"a", "b", 1}, + "any": struct{ A string }{"test"}, }).Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":true,"bytes":"bar","dur":1000,"error":"some error","float32":11,"float64":12,"int":1,"int16":3,"int32":4,"int64":5,"int8":2,"ipv6":"2001:db8:85a3::8a2e:370:7334","nil":null,"obj":{"Pub":"a","Tag":"b","priv":1},"string":"foo","time":"0001-01-01T00:00:00Z","uint":6,"uint16":8,"uint32":9,"uint64":10,"uint8":7}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"any":{"A":"test"},"bool":true,"bytes":"bar","dur":1000,"error":"some error","float32":11,"float64":12,"int":1,"int16":3,"int32":4,"int64":5,"int8":2,"ipv6":"2001:db8:85a3::8a2e:370:7334","nil":null,"obj":{"Pub":"a","Tag":"b","priv":1},"string":"foo","time":"0001-01-01T00:00:00Z","uint":6,"uint16":8,"uint32":9,"uint64":10,"uint8":7}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -291,7 +334,7 @@ func TestFieldsSlice(t *testing.T) { "ipv6", net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, "dur", 1 * time.Second, "time", time.Time{}, - "obj", obj{"a", "b", 1}, + "obj", fixtureObj{"a", "b", 1}, }).Msg("") if got, want := decodeIfBinaryToString(out.Bytes()), `{"nil":null,"string":"foo","bytes":"bar","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"ipv6":"2001:db8:85a3::8a2e:370:7334","dur":1000,"time":"0001-01-01T00:00:00Z","obj":{"Pub":"a","Tag":"b","priv":1}}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) @@ -318,7 +361,7 @@ func TestFieldsNotMapSlice(t *testing.T) { out := &bytes.Buffer{} log := New(out) log.Log(). - Fields(obj{"a", "b", 1}). + Fields(fixtureObj{"a", "b", 1}). Fields("string"). Fields(1). Msg("") @@ -367,8 +410,12 @@ func TestFields(t *testing.T) { Time("time", time.Time{}). TimeDiff("diff", now, now.Add(-10*time.Second)). Ctx(context.Background()). + Type("type", "hello"). + Any("any", struct{ A string }{"test"}). + Any("logobject", fixtureObj{"a", "z", 1}). + Stack(). Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"cbor":"data:application/cbor;base64,gwGCAgOCBAU=","func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"ipv4":"192.168.0.100","ipv6":"2001:db8:85a3::8a2e:370:7334","mac":"00:14:22:01:23:45","pfxv4":"192.168.0.100/24","pfxv6":"2001:db8:85a3::8a2e:370:7334/64","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"cbor":"data:application/cbor;base64,gwGCAgOCBAU=","func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"ipv4":"192.168.0.100","ipv6":"2001:db8:85a3::8a2e:370:7334","mac":"00:14:22:01:23:45","pfxv4":"192.168.0.100/24","pfxv6":"2001:db8:85a3::8a2e:370:7334/64","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000,"type":"string","any":{"A":"test"},"logobject":{"Pub":"a","Tag":"z","priv":1}}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -495,6 +542,8 @@ func TestFieldsDisabled(t *testing.T) { IPPrefix("ip", net.IPNet{IP: net.IP{127, 0, 0, 1}, Mask: net.CIDRMask(24, 32)}). MACAddr("mac", net.HardwareAddr{0x00, 0x14, 0x22, 0x01, 0x23, 0x45}). Ctx(context.Background()). + Any("any", struct{ A string }{"test"}). + Interface("interface", fixtureObj{"a", "z", 1}). Msg("") if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) @@ -757,56 +806,231 @@ type loggableError struct { } func (l loggableError) MarshalZerologObject(e *Event) { + if l.error == nil { + return + } e.Str("message", l.error.Error()+": loggableError") } -func TestErrorMarshalFunc(t *testing.T) { - out := &bytes.Buffer{} - log := New(out) +type nonLoggable struct { + where string + how int + what string +} +func TestErrorMarshalFunc_None(t *testing.T) { // test default behaviour - log.Log().Err(errors.New("err")).Msg("msg") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err","message":"msg"}`+"\n"; got != want { - t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) - } - out.Reset() - - log.Log().Err(loggableError{errors.New("err")}).Msg("msg") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":{"message":"err: loggableError"},"message":"msg"}`+"\n"; got != want { - t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) - } - out.Reset() - - // test overriding the ErrorMarshalFunc - originalErrorMarshalFunc := ErrorMarshalFunc - defer func() { - ErrorMarshalFunc = originalErrorMarshalFunc - }() - - ErrorMarshalFunc = func(err error) interface{} { + testErrorMarshalFunc(t, nil, []string{ + "default", + `{"message":"msg"}`, + `{"error":"err","message":"msg"}`, + `{"error":{"message":"err: loggableError"},"message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":["foo","bar"],"message":"msg"}`, + }) +} +func TestErrorMarshalFunc_AppendedString(t *testing.T) { + // test returning an appended string from ErrorMarshalFunc + testErrorMarshalFunc(t, func(err error) interface{} { + if err == nil { + return nil + } return err.Error() + ": marshaled string" - } - log.Log().Err(errors.New("err")).Msg("msg") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err: marshaled string","message":"msg"}`+"\n"; got != want { - t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) - } + }, []string{ + "appended string", + `{"message":"msg"}`, + `{"error":"err: marshaled string","message":"msg"}`, + `{"error":"err: marshaled string","message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":["foo: marshaled string","bar: marshaled string"],"message":"msg"}`, + }) +} +func TestErrorMarshalFunc_NilError(t *testing.T) { + // test returning an nil error from ErrorMarshalFunc + testErrorMarshalFunc(t, func(err error) interface{} { + return error(nil) + }, []string{ + "nil error", + `{"message":"msg"}`, + `{"message":"msg"}`, + `{"message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":[null,null],"message":"msg"}`, + }) +} - out.Reset() - ErrorMarshalFunc = func(err error) interface{} { - return errors.New(err.Error() + ": new error") - } - log.Log().Err(errors.New("err")).Msg("msg") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err: new error","message":"msg"}`+"\n"; got != want { - t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) - } +func TestErrorMarshalFunc_UntypedNil(t *testing.T) { + // test returning an untyped nil from ErrorMarshalFunc + testErrorMarshalFunc(t, func(err error) interface{} { + return nil + }, []string{ + "untyped nil", + `{"message":"msg"}`, + `{"message":"msg"}`, + `{"message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":[null,null],"message":"msg"}`, + }) +} - out.Reset() - ErrorMarshalFunc = func(err error) interface{} { +type wrappedError struct { + error + msg string +} + +func (w wrappedError) Error() string { + if w.error == nil { + return w.msg + } + return w.error.Error() + ": " + w.msg +} + +func TestErrorMarshalFunc_WrappedError(t *testing.T) { + // test returning an wrapped error from ErrorMarshalFunc + testErrorMarshalFunc(t, func(err error) interface{} { + if err == nil { + return nil + } else if we, ok := err.(wrappedError); ok { + return we + } else { + return wrappedError{err, "addendum"} + } + }, []string{ + "wrapped error", + `{"message":"msg"}`, + `{"error":"err: addendum","message":"msg"}`, + `{"error":"err: addendum","message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":["foo: addendum","bar: addendum"],"message":"msg"}`, + }) +} + +func TestErrorMarshalFunc_LoggableType(t *testing.T) { + // test returning a loggable type from ErrorMarshalFunc + testErrorMarshalFunc(t, func(err error) interface{} { return loggableError{err} + }, []string{ + "loggable type", + `{"error":{},"message":"msg"}`, + `{"error":{"message":"err: loggableError"},"message":"msg"}`, + `{"error":{"message":"err: loggableError"},"message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":[{"message":"foo: loggableError"},{"message":"bar: loggableError"}],"message":"msg"}`, + }) +} +func TestErrorMarshalFunc_String(t *testing.T) { + // test returning a string type from ErrorMarshalFunc + testErrorMarshalFunc(t, func(err error) interface{} { + return "i'm an err" // return just a string + }, []string{ + "string type", + `{"error":"i'm an err","message":"msg"}`, + `{"error":"i'm an err","message":"msg"}`, + `{"error":"i'm an err","message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":["i'm an err","i'm an err"],"message":"msg"}`, + }) +} + +func TestErrorMarshalFunc_NonLoggableType(t *testing.T) { + // test returning a non-loggable type from ErrorMarshalFunc + testErrorMarshalFunc(t, func(err error) interface{} { + if err == nil { + return nil + } + return nonLoggable{where: "here", how: 42, what: err.Error()} + }, []string{ + "non-loggable type", + `{"message":"msg"}`, + `{"error":{},"message":"msg"}`, + `{"error":{},"message":"msg"}`, + `{"errs":[],"message":"msg"}`, + `{"errs":[{},{}],"message":"msg"}`, + }) +} + +func testErrorMarshalFunc(t *testing.T, errFunc func(err error) interface{}, wants []string) { + if errFunc != nil { + originalErrorMarshalFunc := ErrorMarshalFunc + defer func() { + ErrorMarshalFunc = originalErrorMarshalFunc + }() + + ErrorMarshalFunc = errFunc } - log.Log().Err(errors.New("err")).Msg("msg") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":{"message":"err: loggableError"},"message":"msg"}`+"\n"; got != want { - t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + + testcase := wants[0] + var err error + + err = nil + testContextErrorMarshalFunc(t, testcase+" / ctx.Err(nil)", wants[1], func(ctx Context) Context { + ctx = ctx.Err(err) + return ctx + }) + testEventErrorMarshalFunc(t, testcase+" / e.Err(nil)", wants[1], func(e *Event) *Event { + e.Err(err) + return e + }) + + err = errors.New("err") + testContextErrorMarshalFunc(t, testcase+" / ctx.Err(error)", wants[2], func(ctx Context) Context { + ctx = ctx.Err(err) + return ctx + }) + testEventErrorMarshalFunc(t, testcase+" / e.Err(error)", wants[2], func(e *Event) *Event { + e.Err(err) + return e + }) + + err = loggableError{errors.New("err")} + testContextErrorMarshalFunc(t, testcase+" / ctx.Err(loggable)", wants[3], func(ctx Context) Context { + ctx = ctx.Err(err) + return ctx + }) + testEventErrorMarshalFunc(t, testcase+" / e.Err(loggable)", wants[3], func(e *Event) *Event { + e.Err(err) + return e + }) + + var errs []error = nil + testContextErrorMarshalFunc(t, testcase+" / ctx.Errs(nil)", wants[4], func(ctx Context) Context { + ctx = ctx.Errs("errs", errs) + return ctx + }) + testEventErrorMarshalFunc(t, testcase+" / e.Errs(nil)", wants[4], func(e *Event) *Event { + e.Errs("errs", errs) + return e + }) + + errs = []error{errors.New("foo"), errors.New("bar")} + testContextErrorMarshalFunc(t, testcase+" / ctx.Errs([])", wants[5], func(ctx Context) Context { + ctx = ctx.Errs("errs", []error{errors.New("foo"), errors.New("bar")}) + return ctx + }) + testEventErrorMarshalFunc(t, testcase+" / e.Errs([])", wants[5], func(e *Event) *Event { + e.Errs("errs", errs) + return e + }) +} + +func testEventErrorMarshalFunc(t *testing.T, testcase string, wants string, test func(e *Event) *Event) { + out := &bytes.Buffer{} + logger := New(out) + test(logger.Log()).Msg("msg") + if got, want := decodeIfBinaryToString(out.Bytes()), wants+"\n"; got != want { + t.Errorf("%s output:\ngot: %v\nwant: %v", testcase, got, want) + } +} + +func testContextErrorMarshalFunc(t *testing.T, testcase string, wants string, test func(ctx Context) Context) { + out := &bytes.Buffer{} + ctx := New(out).With() + + ctx = test(ctx) + logger := ctx.Logger() + logger.Log().Msg("msg") + if got, want := decodeIfBinaryToString(out.Bytes()), wants+"\n"; got != want { + t.Errorf("%s output:\ngot: %v\nwant: %v", testcase, got, want) } }