github.com/Laisky/zap@v1.27.0/zapcore/json_encoder_impl_test.go (about) 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 "github.com/Laisky/zap/buffer" 35 "github.com/Laisky/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 }