...

Source file src/go.uber.org/zap/zapcore/json_encoder_impl_test.go

Documentation: go.uber.org/zap/zapcore

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package zapcore
    22  
    23  import (
    24  	"encoding/json"
    25  	"errors"
    26  	"math"
    27  	"math/rand"
    28  	"reflect"
    29  	"testing"
    30  	"testing/quick"
    31  	"time"
    32  	"unicode/utf8"
    33  
    34  	"go.uber.org/zap/buffer"
    35  	"go.uber.org/zap/internal/bufferpool"
    36  
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  	"go.uber.org/multierr"
    40  )
    41  
    42  var _defaultEncoderConfig = EncoderConfig{
    43  	EncodeTime:     EpochTimeEncoder,
    44  	EncodeDuration: SecondsDurationEncoder,
    45  }
    46  
    47  func TestJSONClone(t *testing.T) {
    48  	// The parent encoder is created with plenty of excess capacity.
    49  	parent := &jsonEncoder{buf: bufferpool.Get()}
    50  	clone := parent.Clone()
    51  
    52  	// Adding to the parent shouldn't affect the clone, and vice versa.
    53  	parent.AddString("foo", "bar")
    54  	clone.AddString("baz", "bing")
    55  
    56  	assertJSON(t, `"foo":"bar"`, parent)
    57  	assertJSON(t, `"baz":"bing"`, clone.(*jsonEncoder))
    58  }
    59  
    60  func TestJSONEscaping(t *testing.T) {
    61  	enc := &jsonEncoder{buf: bufferpool.Get()}
    62  	// Test all the edge cases of JSON escaping directly.
    63  	cases := map[string]string{
    64  		// ASCII.
    65  		`foo`: `foo`,
    66  		// Special-cased characters.
    67  		`"`: `\"`,
    68  		`\`: `\\`,
    69  		// Special-cased characters within everyday ASCII.
    70  		`foo"foo`: `foo\"foo`,
    71  		"foo\n":   `foo\n`,
    72  		// Special-cased control characters.
    73  		"\n": `\n`,
    74  		"\r": `\r`,
    75  		"\t": `\t`,
    76  		// \b and \f are sometimes backslash-escaped, but this representation is also
    77  		// conformant.
    78  		"\b": `\u0008`,
    79  		"\f": `\u000c`,
    80  		// The standard lib special-cases angle brackets and ampersands by default,
    81  		// because it wants to protect users from browser exploits. In a logging
    82  		// context, we shouldn't special-case these characters.
    83  		"<": "<",
    84  		">": ">",
    85  		"&": "&",
    86  		// ASCII bell - not special-cased.
    87  		string(byte(0x07)): `\u0007`,
    88  		// Astral-plane unicode.
    89  		`☃`: `☃`,
    90  		// Decodes to (RuneError, 1)
    91  		"\xed\xa0\x80":    `\ufffd\ufffd\ufffd`,
    92  		"foo\xed\xa0\x80": `foo\ufffd\ufffd\ufffd`,
    93  	}
    94  
    95  	t.Run("String", func(t *testing.T) {
    96  		for input, output := range cases {
    97  			enc.truncate()
    98  			enc.safeAddString(input)
    99  			assertJSON(t, output, enc)
   100  		}
   101  	})
   102  
   103  	t.Run("ByteString", func(t *testing.T) {
   104  		for input, output := range cases {
   105  			enc.truncate()
   106  			enc.safeAddByteString([]byte(input))
   107  			assertJSON(t, output, enc)
   108  		}
   109  	})
   110  }
   111  
   112  func TestJSONEncoderObjectFields(t *testing.T) {
   113  	tests := []struct {
   114  		desc     string
   115  		expected string
   116  		f        func(Encoder)
   117  	}{
   118  		{"binary", `"k":"YWIxMg=="`, func(e Encoder) { e.AddBinary("k", []byte("ab12")) }},
   119  		{"bool", `"k\\":true`, func(e Encoder) { e.AddBool(`k\`, true) }}, // test key escaping once
   120  		{"bool", `"k":true`, func(e Encoder) { e.AddBool("k", true) }},
   121  		{"bool", `"k":false`, func(e Encoder) { e.AddBool("k", false) }},
   122  		{"byteString", `"k":"v\\"`, func(e Encoder) { e.AddByteString(`k`, []byte(`v\`)) }},
   123  		{"byteString", `"k":"v"`, func(e Encoder) { e.AddByteString("k", []byte("v")) }},
   124  		{"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", []byte{}) }},
   125  		{"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", nil) }},
   126  		{"complex128", `"k":"1+2i"`, func(e Encoder) { e.AddComplex128("k", 1+2i) }},
   127  		{"complex128/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex128("k", 1-2i) }},
   128  		{"complex64", `"k":"1+2i"`, func(e Encoder) { e.AddComplex64("k", 1+2i) }},
   129  		{"complex64/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex64("k", 1-2i) }},
   130  		{"complex64", `"k":"2.71+3.14i"`, func(e Encoder) { e.AddComplex64("k", 2.71+3.14i) }},
   131  		{"duration", `"k":0.000000001`, func(e Encoder) { e.AddDuration("k", 1) }},
   132  		{"duration/negative", `"k":-0.000000001`, func(e Encoder) { e.AddDuration("k", -1) }},
   133  		{"float64", `"k":1`, func(e Encoder) { e.AddFloat64("k", 1.0) }},
   134  		{"float64", `"k":10000000000`, func(e Encoder) { e.AddFloat64("k", 1e10) }},
   135  		{"float64", `"k":"NaN"`, func(e Encoder) { e.AddFloat64("k", math.NaN()) }},
   136  		{"float64", `"k":"+Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(1)) }},
   137  		{"float64", `"k":"-Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(-1)) }},
   138  		{"float64/pi", `"k":3.141592653589793`, func(e Encoder) { e.AddFloat64("k", math.Pi) }},
   139  		{"float32", `"k":1`, func(e Encoder) { e.AddFloat32("k", 1.0) }},
   140  		{"float32", `"k":2.71`, func(e Encoder) { e.AddFloat32("k", 2.71) }},
   141  		{"float32", `"k":0.1`, func(e Encoder) { e.AddFloat32("k", 0.1) }},
   142  		{"float32", `"k":10000000000`, func(e Encoder) { e.AddFloat32("k", 1e10) }},
   143  		{"float32", `"k":"NaN"`, func(e Encoder) { e.AddFloat32("k", float32(math.NaN())) }},
   144  		{"float32", `"k":"+Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(1))) }},
   145  		{"float32", `"k":"-Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(-1))) }},
   146  		{"float32/pi", `"k":3.1415927`, func(e Encoder) { e.AddFloat32("k", math.Pi) }},
   147  		{"int", `"k":42`, func(e Encoder) { e.AddInt("k", 42) }},
   148  		{"int64", `"k":42`, func(e Encoder) { e.AddInt64("k", 42) }},
   149  		{"int64/min", `"k":-9223372036854775808`, func(e Encoder) { e.AddInt64("k", math.MinInt64) }},
   150  		{"int64/max", `"k":9223372036854775807`, func(e Encoder) { e.AddInt64("k", math.MaxInt64) }},
   151  		{"int32", `"k":42`, func(e Encoder) { e.AddInt32("k", 42) }},
   152  		{"int32/min", `"k":-2147483648`, func(e Encoder) { e.AddInt32("k", math.MinInt32) }},
   153  		{"int32/max", `"k":2147483647`, func(e Encoder) { e.AddInt32("k", math.MaxInt32) }},
   154  		{"int16", `"k":42`, func(e Encoder) { e.AddInt16("k", 42) }},
   155  		{"int16/min", `"k":-32768`, func(e Encoder) { e.AddInt16("k", math.MinInt16) }},
   156  		{"int16/max", `"k":32767`, func(e Encoder) { e.AddInt16("k", math.MaxInt16) }},
   157  		{"int8", `"k":42`, func(e Encoder) { e.AddInt8("k", 42) }},
   158  		{"int8/min", `"k":-128`, func(e Encoder) { e.AddInt8("k", math.MinInt8) }},
   159  		{"int8/max", `"k":127`, func(e Encoder) { e.AddInt8("k", math.MaxInt8) }},
   160  		{"string", `"k":"v\\"`, func(e Encoder) { e.AddString(`k`, `v\`) }},
   161  		{"string", `"k":"v"`, func(e Encoder) { e.AddString("k", "v") }},
   162  		{"string", `"k":""`, func(e Encoder) { e.AddString("k", "") }},
   163  		{"time", `"k":1`, func(e Encoder) { e.AddTime("k", time.Unix(1, 0)) }},
   164  		{"uint", `"k":42`, func(e Encoder) { e.AddUint("k", 42) }},
   165  		{"uint64", `"k":42`, func(e Encoder) { e.AddUint64("k", 42) }},
   166  		{"uint64/max", `"k":18446744073709551615`, func(e Encoder) { e.AddUint64("k", math.MaxUint64) }},
   167  		{"uint32", `"k":42`, func(e Encoder) { e.AddUint32("k", 42) }},
   168  		{"uint32/max", `"k":4294967295`, func(e Encoder) { e.AddUint32("k", math.MaxUint32) }},
   169  		{"uint16", `"k":42`, func(e Encoder) { e.AddUint16("k", 42) }},
   170  		{"uint16/max", `"k":65535`, func(e Encoder) { e.AddUint16("k", math.MaxUint16) }},
   171  		{"uint8", `"k":42`, func(e Encoder) { e.AddUint8("k", 42) }},
   172  		{"uint8/max", `"k":255`, func(e Encoder) { e.AddUint8("k", math.MaxUint8) }},
   173  		{"uintptr", `"k":42`, func(e Encoder) { e.AddUintptr("k", 42) }},
   174  		{
   175  			desc:     "object (success)",
   176  			expected: `"k":{"loggable":"yes"}`,
   177  			f: func(e Encoder) {
   178  				assert.NoError(t, e.AddObject("k", loggable{true}), "Unexpected error calling MarshalLogObject.")
   179  			},
   180  		},
   181  		{
   182  			desc:     "object (error)",
   183  			expected: `"k":{}`,
   184  			f: func(e Encoder) {
   185  				assert.Error(t, e.AddObject("k", loggable{false}), "Expected an error calling MarshalLogObject.")
   186  			},
   187  		},
   188  		{
   189  			desc:     "object (with nested array)",
   190  			expected: `"turducken":{"ducks":[{"in":"chicken"},{"in":"chicken"}]}`,
   191  			f: func(e Encoder) {
   192  				assert.NoError(
   193  					t,
   194  					e.AddObject("turducken", turducken{}),
   195  					"Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.",
   196  				)
   197  			},
   198  		},
   199  		{
   200  			desc:     "array (with nested object)",
   201  			expected: `"turduckens":[{"ducks":[{"in":"chicken"},{"in":"chicken"}]},{"ducks":[{"in":"chicken"},{"in":"chicken"}]}]`,
   202  			f: func(e Encoder) {
   203  				assert.NoError(
   204  					t,
   205  					e.AddArray("turduckens", turduckens(2)),
   206  					"Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.",
   207  				)
   208  			},
   209  		},
   210  		{
   211  			desc:     "array (success)",
   212  			expected: `"k":[true]`,
   213  			f: func(e Encoder) {
   214  				assert.NoError(t, e.AddArray(`k`, loggable{true}), "Unexpected error calling MarshalLogArray.")
   215  			},
   216  		},
   217  		{
   218  			desc:     "array (error)",
   219  			expected: `"k":[]`,
   220  			f: func(e Encoder) {
   221  				assert.Error(t, e.AddArray("k", loggable{false}), "Expected an error calling MarshalLogArray.")
   222  			},
   223  		},
   224  		{
   225  			desc:     "reflect (success)",
   226  			expected: `"k":{"escape":"<&>","loggable":"yes"}`,
   227  			f: func(e Encoder) {
   228  				assert.NoError(t, e.AddReflected("k", map[string]string{"escape": "<&>", "loggable": "yes"}), "Unexpected error JSON-serializing a map.")
   229  			},
   230  		},
   231  		{
   232  			desc:     "reflect (failure)",
   233  			expected: "",
   234  			f: func(e Encoder) {
   235  				assert.Error(t, e.AddReflected("k", noJSON{}), "Unexpected success JSON-serializing a noJSON.")
   236  			},
   237  		},
   238  		{
   239  			desc: "namespace",
   240  			// EncodeEntry is responsible for closing all open namespaces.
   241  			expected: `"outermost":{"outer":{"foo":1,"inner":{"foo":2,"innermost":{`,
   242  			f: func(e Encoder) {
   243  				e.OpenNamespace("outermost")
   244  				e.OpenNamespace("outer")
   245  				e.AddInt("foo", 1)
   246  				e.OpenNamespace("inner")
   247  				e.AddInt("foo", 2)
   248  				e.OpenNamespace("innermost")
   249  			},
   250  		},
   251  		{
   252  			desc:     "object (no nested namespace)",
   253  			expected: `"obj":{"obj-out":"obj-outside-namespace"},"not-obj":"should-be-outside-obj"`,
   254  			f: func(e Encoder) {
   255  				assert.NoError(t, e.AddObject("obj", maybeNamespace{false}))
   256  				e.AddString("not-obj", "should-be-outside-obj")
   257  			},
   258  		},
   259  		{
   260  			desc:     "object (with nested namespace)",
   261  			expected: `"obj":{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"not-obj":"should-be-outside-obj"`,
   262  			f: func(e Encoder) {
   263  				assert.NoError(t, e.AddObject("obj", maybeNamespace{true}))
   264  				e.AddString("not-obj", "should-be-outside-obj")
   265  			},
   266  		},
   267  		{
   268  			desc:     "multiple open namespaces",
   269  			expected: `"k":{"foo":1,"middle":{"foo":2,"inner":{"foo":3}}}`,
   270  			f: func(e Encoder) {
   271  				err := e.AddObject("k", ObjectMarshalerFunc(func(enc ObjectEncoder) error {
   272  					e.AddInt("foo", 1)
   273  					e.OpenNamespace("middle")
   274  					e.AddInt("foo", 2)
   275  					e.OpenNamespace("inner")
   276  					e.AddInt("foo", 3)
   277  					return nil
   278  				}))
   279  				assert.NoError(t, err)
   280  			},
   281  		},
   282  	}
   283  
   284  	for _, tt := range tests {
   285  		t.Run(tt.desc, func(t *testing.T) {
   286  			assertOutput(t, _defaultEncoderConfig, tt.expected, tt.f)
   287  		})
   288  	}
   289  }
   290  
   291  func TestJSONEncoderTimeFormats(t *testing.T) {
   292  	date := time.Date(2000, time.January, 2, 3, 4, 5, 6, time.UTC)
   293  
   294  	f := func(e Encoder) {
   295  		e.AddTime("k", date)
   296  		err := e.AddArray("a", ArrayMarshalerFunc(func(enc ArrayEncoder) error {
   297  			enc.AppendTime(date)
   298  			return nil
   299  		}))
   300  		assert.NoError(t, err)
   301  	}
   302  	tests := []struct {
   303  		desc     string
   304  		cfg      EncoderConfig
   305  		expected string
   306  	}{
   307  		{
   308  			desc: "time.Time ISO8601",
   309  			cfg: EncoderConfig{
   310  				EncodeDuration: NanosDurationEncoder,
   311  				EncodeTime:     ISO8601TimeEncoder,
   312  			},
   313  			expected: `"k":"2000-01-02T03:04:05.000Z","a":["2000-01-02T03:04:05.000Z"]`,
   314  		},
   315  		{
   316  			desc: "time.Time RFC3339",
   317  			cfg: EncoderConfig{
   318  				EncodeDuration: NanosDurationEncoder,
   319  				EncodeTime:     RFC3339TimeEncoder,
   320  			},
   321  			expected: `"k":"2000-01-02T03:04:05Z","a":["2000-01-02T03:04:05Z"]`,
   322  		},
   323  		{
   324  			desc: "time.Time RFC3339Nano",
   325  			cfg: EncoderConfig{
   326  				EncodeDuration: NanosDurationEncoder,
   327  				EncodeTime:     RFC3339NanoTimeEncoder,
   328  			},
   329  			expected: `"k":"2000-01-02T03:04:05.000000006Z","a":["2000-01-02T03:04:05.000000006Z"]`,
   330  		},
   331  	}
   332  
   333  	for _, tt := range tests {
   334  		t.Run(tt.desc, func(t *testing.T) {
   335  			assertOutput(t, tt.cfg, tt.expected, f)
   336  		})
   337  	}
   338  }
   339  
   340  func TestJSONEncoderArrays(t *testing.T) {
   341  	tests := []struct {
   342  		desc     string
   343  		expected string // expect f to be called twice
   344  		f        func(ArrayEncoder)
   345  	}{
   346  		{"bool", `[true,true]`, func(e ArrayEncoder) { e.AppendBool(true) }},
   347  		{"byteString", `["k","k"]`, func(e ArrayEncoder) { e.AppendByteString([]byte("k")) }},
   348  		{"byteString", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendByteString([]byte(`k\`)) }},
   349  		{"complex128", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex128(1 + 2i) }},
   350  		{"complex64", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex64(1 + 2i) }},
   351  		{"durations", `[0.000000002,0.000000002]`, func(e ArrayEncoder) { e.AppendDuration(2) }},
   352  		{"float64", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat64(3.14) }},
   353  		{"float32", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat32(3.14) }},
   354  		{"int", `[42,42]`, func(e ArrayEncoder) { e.AppendInt(42) }},
   355  		{"int64", `[42,42]`, func(e ArrayEncoder) { e.AppendInt64(42) }},
   356  		{"int32", `[42,42]`, func(e ArrayEncoder) { e.AppendInt32(42) }},
   357  		{"int16", `[42,42]`, func(e ArrayEncoder) { e.AppendInt16(42) }},
   358  		{"int8", `[42,42]`, func(e ArrayEncoder) { e.AppendInt8(42) }},
   359  		{"string", `["k","k"]`, func(e ArrayEncoder) { e.AppendString("k") }},
   360  		{"string", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendString(`k\`) }},
   361  		{"times", `[1,1]`, func(e ArrayEncoder) { e.AppendTime(time.Unix(1, 0)) }},
   362  		{"uint", `[42,42]`, func(e ArrayEncoder) { e.AppendUint(42) }},
   363  		{"uint64", `[42,42]`, func(e ArrayEncoder) { e.AppendUint64(42) }},
   364  		{"uint32", `[42,42]`, func(e ArrayEncoder) { e.AppendUint32(42) }},
   365  		{"uint16", `[42,42]`, func(e ArrayEncoder) { e.AppendUint16(42) }},
   366  		{"uint8", `[42,42]`, func(e ArrayEncoder) { e.AppendUint8(42) }},
   367  		{"uintptr", `[42,42]`, func(e ArrayEncoder) { e.AppendUintptr(42) }},
   368  		{
   369  			desc:     "arrays (success)",
   370  			expected: `[[true],[true]]`,
   371  			f: func(arr ArrayEncoder) {
   372  				assert.NoError(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error {
   373  					inner.AppendBool(true)
   374  					return nil
   375  				})), "Unexpected error appending an array.")
   376  			},
   377  		},
   378  		{
   379  			desc:     "arrays (error)",
   380  			expected: `[[true],[true]]`,
   381  			f: func(arr ArrayEncoder) {
   382  				assert.Error(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error {
   383  					inner.AppendBool(true)
   384  					return errors.New("fail")
   385  				})), "Expected an error appending an array.")
   386  			},
   387  		},
   388  		{
   389  			desc:     "objects (success)",
   390  			expected: `[{"loggable":"yes"},{"loggable":"yes"}]`,
   391  			f: func(arr ArrayEncoder) {
   392  				assert.NoError(t, arr.AppendObject(loggable{true}), "Unexpected error appending an object.")
   393  			},
   394  		},
   395  		{
   396  			desc:     "objects (error)",
   397  			expected: `[{},{}]`,
   398  			f: func(arr ArrayEncoder) {
   399  				assert.Error(t, arr.AppendObject(loggable{false}), "Expected an error appending an object.")
   400  			},
   401  		},
   402  		{
   403  			desc:     "reflect (success)",
   404  			expected: `[{"foo":5},{"foo":5}]`,
   405  			f: func(arr ArrayEncoder) {
   406  				assert.NoError(
   407  					t,
   408  					arr.AppendReflected(map[string]int{"foo": 5}),
   409  					"Unexpected an error appending an object with reflection.",
   410  				)
   411  			},
   412  		},
   413  		{
   414  			desc:     "reflect (error)",
   415  			expected: `[]`,
   416  			f: func(arr ArrayEncoder) {
   417  				assert.Error(
   418  					t,
   419  					arr.AppendReflected(noJSON{}),
   420  					"Unexpected an error appending an object with reflection.",
   421  				)
   422  			},
   423  		},
   424  		{
   425  			desc:     "object (no nested namespace) then string",
   426  			expected: `[{"obj-out":"obj-outside-namespace"},"should-be-outside-obj",{"obj-out":"obj-outside-namespace"},"should-be-outside-obj"]`,
   427  			f: func(arr ArrayEncoder) {
   428  				assert.NoError(t, arr.AppendObject(maybeNamespace{false}))
   429  				arr.AppendString("should-be-outside-obj")
   430  			},
   431  		},
   432  		{
   433  			desc:     "object (with nested namespace) then string",
   434  			expected: `[{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj",{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj"]`,
   435  			f: func(arr ArrayEncoder) {
   436  				assert.NoError(t, arr.AppendObject(maybeNamespace{true}))
   437  				arr.AppendString("should-be-outside-obj")
   438  			},
   439  		},
   440  	}
   441  
   442  	for _, tt := range tests {
   443  		t.Run(tt.desc, func(t *testing.T) {
   444  			f := func(enc Encoder) error {
   445  				return enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error {
   446  					tt.f(arr)
   447  					tt.f(arr)
   448  					return nil
   449  				}))
   450  			}
   451  			assertOutput(t, _defaultEncoderConfig, `"array":`+tt.expected, func(enc Encoder) {
   452  				err := f(enc)
   453  				assert.NoError(t, err, "Unexpected error adding array to JSON encoder.")
   454  			})
   455  		})
   456  	}
   457  }
   458  
   459  func TestJSONEncoderTimeArrays(t *testing.T) {
   460  	times := []time.Time{
   461  		time.Unix(1008720000, 0).UTC(), // 2001-12-19
   462  		time.Unix(1040169600, 0).UTC(), // 2002-12-18
   463  		time.Unix(1071619200, 0).UTC(), // 2003-12-17
   464  	}
   465  
   466  	tests := []struct {
   467  		desc    string
   468  		encoder TimeEncoder
   469  		want    string
   470  	}{
   471  		{
   472  			desc:    "epoch",
   473  			encoder: EpochTimeEncoder,
   474  			want:    `[1008720000,1040169600,1071619200]`,
   475  		},
   476  		{
   477  			desc:    "epoch millis",
   478  			encoder: EpochMillisTimeEncoder,
   479  			want:    `[1008720000000,1040169600000,1071619200000]`,
   480  		},
   481  		{
   482  			desc:    "iso8601",
   483  			encoder: ISO8601TimeEncoder,
   484  			want:    `["2001-12-19T00:00:00.000Z","2002-12-18T00:00:00.000Z","2003-12-17T00:00:00.000Z"]`,
   485  		},
   486  		{
   487  			desc:    "rfc3339",
   488  			encoder: RFC3339TimeEncoder,
   489  			want:    `["2001-12-19T00:00:00Z","2002-12-18T00:00:00Z","2003-12-17T00:00:00Z"]`,
   490  		},
   491  	}
   492  
   493  	for _, tt := range tests {
   494  		t.Run(tt.desc, func(t *testing.T) {
   495  			cfg := _defaultEncoderConfig
   496  			cfg.EncodeTime = tt.encoder
   497  
   498  			enc := &jsonEncoder{buf: bufferpool.Get(), EncoderConfig: &cfg}
   499  			err := enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error {
   500  				for _, time := range times {
   501  					arr.AppendTime(time)
   502  				}
   503  				return nil
   504  			}))
   505  			assert.NoError(t, err)
   506  			assert.Equal(t, `"array":`+tt.want, enc.buf.String())
   507  		})
   508  	}
   509  }
   510  
   511  func assertJSON(t *testing.T, expected string, enc *jsonEncoder) {
   512  	assert.Equal(t, expected, enc.buf.String(), "Encoded JSON didn't match expectations.")
   513  }
   514  
   515  func assertOutput(t testing.TB, cfg EncoderConfig, expected string, f func(Encoder)) {
   516  	enc := NewJSONEncoder(cfg).(*jsonEncoder)
   517  	f(enc)
   518  	assert.Equal(t, expected, enc.buf.String(), "Unexpected encoder output after adding.")
   519  
   520  	enc.truncate()
   521  	enc.AddString("foo", "bar")
   522  	f(enc)
   523  	expectedPrefix := `"foo":"bar"`
   524  	if expected != "" {
   525  		// If we expect output, it should be comma-separated from the previous
   526  		// field.
   527  		expectedPrefix += ","
   528  	}
   529  	assert.Equal(t, expectedPrefix+expected, enc.buf.String(), "Unexpected encoder output after adding as a second field.")
   530  }
   531  
   532  // Nested Array- and ObjectMarshalers.
   533  type turducken struct{}
   534  
   535  func (t turducken) MarshalLogObject(enc ObjectEncoder) error {
   536  	return enc.AddArray("ducks", ArrayMarshalerFunc(func(arr ArrayEncoder) error {
   537  		for i := 0; i < 2; i++ {
   538  			err := arr.AppendObject(ObjectMarshalerFunc(func(inner ObjectEncoder) error {
   539  				inner.AddString("in", "chicken")
   540  				return nil
   541  			}))
   542  			if err != nil {
   543  				return err
   544  			}
   545  		}
   546  		return nil
   547  	}))
   548  }
   549  
   550  type turduckens int
   551  
   552  func (t turduckens) MarshalLogArray(enc ArrayEncoder) error {
   553  	var err error
   554  	tur := turducken{}
   555  	for i := 0; i < int(t); i++ {
   556  		err = multierr.Append(err, enc.AppendObject(tur))
   557  	}
   558  	return err
   559  }
   560  
   561  type loggable struct{ bool }
   562  
   563  func (l loggable) MarshalLogObject(enc ObjectEncoder) error {
   564  	if !l.bool {
   565  		return errors.New("can't marshal")
   566  	}
   567  	enc.AddString("loggable", "yes")
   568  	return nil
   569  }
   570  
   571  func (l loggable) MarshalLogArray(enc ArrayEncoder) error {
   572  	if !l.bool {
   573  		return errors.New("can't marshal")
   574  	}
   575  	enc.AppendBool(true)
   576  	return nil
   577  }
   578  
   579  // maybeNamespace is an ObjectMarshaler that sometimes opens a namespace
   580  type maybeNamespace struct{ bool }
   581  
   582  func (m maybeNamespace) MarshalLogObject(enc ObjectEncoder) error {
   583  	enc.AddString("obj-out", "obj-outside-namespace")
   584  	if m.bool {
   585  		enc.OpenNamespace("obj-namespace")
   586  		enc.AddString("obj-in", "obj-inside-namespace")
   587  	}
   588  	return nil
   589  }
   590  
   591  type noJSON struct{}
   592  
   593  func (nj noJSON) MarshalJSON() ([]byte, error) {
   594  	return nil, errors.New("no")
   595  }
   596  
   597  func zapEncode(encode func(*jsonEncoder, string)) func(s string) []byte {
   598  	return func(s string) []byte {
   599  		enc := &jsonEncoder{buf: bufferpool.Get()}
   600  		// Escape and quote a string using our encoder.
   601  		var ret []byte
   602  		encode(enc, s)
   603  		ret = make([]byte, 0, enc.buf.Len()+2)
   604  		ret = append(ret, '"')
   605  		ret = append(ret, enc.buf.Bytes()...)
   606  		ret = append(ret, '"')
   607  		return ret
   608  	}
   609  }
   610  
   611  func roundTripsCorrectly(encode func(string) []byte, original string) bool {
   612  	// Encode using our encoder, decode using the standard library, and assert
   613  	// that we haven't lost any information.
   614  	encoded := encode(original)
   615  
   616  	var decoded string
   617  	err := json.Unmarshal(encoded, &decoded)
   618  	if err != nil {
   619  		return false
   620  	}
   621  	return original == decoded
   622  }
   623  
   624  func roundTripsCorrectlyString(original string) bool {
   625  	return roundTripsCorrectly(zapEncode((*jsonEncoder).safeAddString), original)
   626  }
   627  
   628  func roundTripsCorrectlyByteString(original string) bool {
   629  	return roundTripsCorrectly(
   630  		zapEncode(func(enc *jsonEncoder, s string) {
   631  			enc.safeAddByteString([]byte(s))
   632  		}),
   633  		original)
   634  }
   635  
   636  type ASCII string
   637  
   638  func (s ASCII) Generate(r *rand.Rand, size int) reflect.Value {
   639  	bs := make([]byte, size)
   640  	for i := range bs {
   641  		bs[i] = byte(r.Intn(128))
   642  	}
   643  	a := ASCII(bs)
   644  	return reflect.ValueOf(a)
   645  }
   646  
   647  func asciiRoundTripsCorrectlyString(s ASCII) bool {
   648  	return roundTripsCorrectlyString(string(s))
   649  }
   650  
   651  func asciiRoundTripsCorrectlyByteString(s ASCII) bool {
   652  	return roundTripsCorrectlyByteString(string(s))
   653  }
   654  
   655  func TestJSONQuick(t *testing.T) {
   656  	check := func(f interface{}) {
   657  		err := quick.Check(f, &quick.Config{MaxCountScale: 100.0})
   658  		assert.NoError(t, err)
   659  	}
   660  	// Test the full range of UTF-8 strings.
   661  	check(roundTripsCorrectlyString)
   662  	check(roundTripsCorrectlyByteString)
   663  
   664  	// Focus on ASCII strings.
   665  	check(asciiRoundTripsCorrectlyString)
   666  	check(asciiRoundTripsCorrectlyByteString)
   667  }
   668  
   669  var _stringLikeCorpus = []string{
   670  	"",
   671  	"foo",
   672  	"bar",
   673  	"a\nb",
   674  	"a\tb",
   675  	"a\\b",
   676  	`a"b`,
   677  }
   678  
   679  func FuzzSafeAppendStringLike_bytes(f *testing.F) {
   680  	for _, s := range _stringLikeCorpus {
   681  		f.Add([]byte(s))
   682  	}
   683  	f.Fuzz(func(t *testing.T, b []byte) {
   684  		if !utf8.Valid(b) {
   685  			t.Skip()
   686  		}
   687  
   688  		fuzzSafeAppendStringLike(t, string(b), func(buf *buffer.Buffer) {
   689  			safeAppendStringLike(
   690  				(*buffer.Buffer).AppendBytes,
   691  				utf8.DecodeRune,
   692  				buf,
   693  				b,
   694  			)
   695  		})
   696  	})
   697  }
   698  
   699  func FuzzSafeAppendStringLike_string(f *testing.F) {
   700  	for _, s := range _stringLikeCorpus {
   701  		f.Add(s)
   702  	}
   703  	f.Fuzz(func(t *testing.T, s string) {
   704  		if !utf8.ValidString(s) {
   705  			t.Skip()
   706  		}
   707  
   708  		fuzzSafeAppendStringLike(t, s, func(buf *buffer.Buffer) {
   709  			safeAppendStringLike(
   710  				(*buffer.Buffer).AppendString,
   711  				utf8.DecodeRuneInString,
   712  				buf,
   713  				s,
   714  			)
   715  		})
   716  	})
   717  }
   718  
   719  func fuzzSafeAppendStringLike(
   720  	t *testing.T,
   721  	want string,
   722  	writeString func(*buffer.Buffer),
   723  ) {
   724  	t.Helper()
   725  
   726  	buf := bufferpool.Get()
   727  	defer buf.Free()
   728  
   729  	buf.AppendByte('"')
   730  	writeString(buf)
   731  	buf.AppendByte('"')
   732  
   733  	var got string
   734  	require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
   735  	assert.Equal(t, want, got)
   736  }
   737  

View as plain text