github.com/goshafaq/sonic@v0.0.0-20231026082336-871835fb94c6/encoder/encoder_test.go (about) 1 /* 2 * Copyright 2021 ByteDance Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package encoder 18 19 import ( 20 "bytes" 21 "encoding" 22 "encoding/json" 23 "runtime" 24 "runtime/debug" 25 "strconv" 26 "sync" 27 "testing" 28 "time" 29 30 "github.com/goshafaq/sonic/internal/rt" 31 "github.com/stretchr/testify/require" 32 ) 33 34 func TestMain(m *testing.M) { 35 go func() { 36 if !debugAsyncGC { 37 return 38 } 39 println("Begin GC looping...") 40 for { 41 runtime.GC() 42 debug.FreeOSMemory() 43 } 44 println("stop GC looping!") 45 }() 46 time.Sleep(time.Millisecond) 47 m.Run() 48 } 49 50 func TestGC(t *testing.T) { 51 if debugSyncGC { 52 return 53 } 54 out, err := Encode(_GenericValue, 0) 55 if err != nil { 56 t.Fatal(err) 57 } 58 n := len(out) 59 wg := &sync.WaitGroup{} 60 N := 10000 61 for i := 0; i < N; i++ { 62 wg.Add(1) 63 go func(wg *sync.WaitGroup, size int) { 64 defer wg.Done() 65 out, err := Encode(_GenericValue, 0) 66 if err != nil { 67 t.Error(err) 68 return 69 } 70 if len(out) != size { 71 t.Error(len(out), size) 72 return 73 } 74 runtime.GC() 75 }(wg, n) 76 } 77 wg.Wait() 78 } 79 80 type sample struct { 81 M map[string]interface{} 82 S []interface{} 83 A [0]interface{} 84 MP *map[string]interface{} 85 SP *[]interface{} 86 AP *[0]interface{} 87 } 88 89 func BenchmarkOptionSliceOrMapNoNull(b *testing.B) { 90 b.Run("true", func(b *testing.B) { 91 obj := sample{} 92 _, err := Encode(obj, NoNullSliceOrMap) 93 if err != nil { 94 b.Fatal(err) 95 } 96 b.ResetTimer() 97 for i := 0; i < b.N; i++ { 98 _, _ = Encode(obj, NoNullSliceOrMap) 99 } 100 }) 101 102 b.Run("false", func(b *testing.B) { 103 obj2 := sample{} 104 _, err := Encode(obj2, 0) 105 if err != nil { 106 b.Fatal(err) 107 } 108 for i := 0; i < b.N; i++ { 109 _, _ = Encode(obj2, 0) 110 } 111 }) 112 } 113 114 func runEncoderTest(t *testing.T, fn func(string) string, exp string, arg string) { 115 require.Equal(t, exp, fn(arg)) 116 } 117 118 func TestEncoder_String(t *testing.T) { 119 runEncoderTest(t, Quote, `""`, "") 120 runEncoderTest(t, Quote, `"hello, world"`, "hello, world") 121 runEncoderTest(t, Quote, `"hello啊啊啊aa"`, "hello啊啊啊aa") 122 runEncoderTest(t, Quote, `"hello\\\"world"`, "hello\\\"world") 123 runEncoderTest(t, Quote, `"hello\n\tworld"`, "hello\n\tworld") 124 runEncoderTest(t, Quote, `"hello\u0000\u0001world"`, "hello\x00\x01world") 125 runEncoderTest(t, Quote, `"hello\u0000\u0001world"`, "hello\x00\x01world") 126 runEncoderTest(t, Quote, `"Cartoonist, Illustrator, and T-Shirt connoisseur"`, "Cartoonist, Illustrator, and T-Shirt connoisseur") 127 } 128 129 type StringStruct struct { 130 X *int `json:"x,string,omitempty"` 131 Y []int `json:"y"` 132 Z json.Number `json:"z,string"` 133 W string `json:"w,string"` 134 } 135 136 func TestEncoder_FieldStringize(t *testing.T) { 137 x := 12345 138 v := StringStruct{X: &x, Y: []int{1, 2, 3}, Z: "4567456", W: "asdf"} 139 r, e := Encode(v, 0) 140 require.NoError(t, e) 141 println(string(r)) 142 } 143 144 func TestEncodeErrorAndScratchBuf(t *testing.T) { 145 var obj = map[string]interface{}{ 146 "a": json.RawMessage(" [} "), 147 } 148 buf := make([]byte, 0, 10) 149 _ = EncodeInto(&buf, obj, 0) 150 if len(buf) < 0 || len(buf) > 10 { 151 t.Fatal() 152 } 153 } 154 155 type MarshalerImpl struct { 156 X int 157 } 158 159 func (self *MarshalerImpl) MarshalJSON() ([]byte, error) { 160 ret := []byte(strconv.Itoa(self.X)) 161 return append(ret, " "...), nil 162 } 163 164 type MarshalerStruct struct { 165 V MarshalerImpl 166 } 167 168 type MarshalerErrorStruct struct { 169 V MarshalerImpl 170 } 171 172 func (self *MarshalerErrorStruct) MarshalJSON() ([]byte, error) { 173 return []byte(`[""] {`), nil 174 } 175 176 type RawMessageStruct struct { 177 X json.RawMessage 178 } 179 180 type TextMarshalerImpl struct { 181 X string 182 } 183 184 func (self *TextMarshalerImpl) MarshalText() ([]byte, error) { 185 return []byte(self.X), nil 186 } 187 188 type TextMarshalerImplV struct { 189 X string 190 } 191 192 func (self TextMarshalerImplV) MarshalText() ([]byte, error) { 193 return []byte(self.X), nil 194 } 195 196 type TextMarshalerStruct struct { 197 V TextMarshalerImpl 198 } 199 200 func TestTextMarshalTextKey_SortKeys(t *testing.T) { 201 v := map[*TextMarshalerImpl]string{ 202 {"b"}: "b", 203 {"c"}: "c", 204 {"a"}: "a", 205 } 206 ret, err := Encode(v, SortMapKeys) 207 require.NoError(t, err) 208 require.Equal(t, `{"a":"a","b":"b","c":"c"}`, string(ret)) 209 210 v2 := map[TextMarshalerImplV]string{ 211 {"b"}: "b", 212 {"c"}: "c", 213 {"a"}: "a", 214 } 215 ret, err = Encode(v2, SortMapKeys) 216 require.NoError(t, err) 217 require.Equal(t, `{"a":"a","b":"b","c":"c"}`, string(ret)) 218 219 v3 := map[encoding.TextMarshaler]string{ 220 TextMarshalerImplV{"b"}: "b", 221 &TextMarshalerImpl{"c"}: "c", 222 TextMarshalerImplV{"a"}: "a", 223 } 224 ret, err = Encode(v3, SortMapKeys) 225 require.NoError(t, err) 226 require.Equal(t, `{"a":"a","b":"b","c":"c"}`, string(ret)) 227 } 228 229 func TestEncoder_EscapeHTML(t *testing.T) { 230 // test data from libfuzzer 231 test := []string{ 232 "&&&&&&&&&&&&&&&&&&&&&&&\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", 233 "{\"\"\u2028\x94\xe2\x00\x00\x00\x00\x00\x00\x00\x00\u2028\x80\u2028\x80\u2028\xe2\u2028\x8a\u2028⑀\xa8\x8a\xa8\xe2\u2028\xe2\u2028\xe2\u2028\xe2\u2000\x8d\xe2\u2028\xe2\u2028\xe2\xe2\xa8\"}", 234 } 235 for _, s := range test { 236 data := []byte(s) 237 sdst := HTMLEscape(nil, data) 238 var dst bytes.Buffer 239 json.HTMLEscape(&dst, data) 240 require.Equal(t, string(sdst), dst.String()) 241 } 242 } 243 244 func TestEncoder_Marshal_EscapeHTML_LargeJson(t *testing.T) { 245 buf1, err1 := Encode(&_BindingValue, SortMapKeys|EscapeHTML) 246 require.NoError(t, err1) 247 buf2, err2 := json.Marshal(&_BindingValue) 248 require.NoError(t, err2) 249 require.Equal(t, buf1, buf2) 250 } 251 252 var _GenericValue interface{} 253 var _BindingValue TwitterStruct 254 255 func init() { 256 _ = json.Unmarshal([]byte(TwitterJson), &_GenericValue) 257 _ = json.Unmarshal([]byte(TwitterJson), &_BindingValue) 258 } 259 260 func TestEncoder_Generic(t *testing.T) { 261 v, e := Encode(_GenericValue, 0) 262 require.NoError(t, e) 263 println(string(v)) 264 } 265 266 func TestEncoder_Binding(t *testing.T) { 267 v, e := Encode(_BindingValue, 0) 268 require.NoError(t, e) 269 println(string(v)) 270 } 271 272 func TestEncoder_MapSortKey(t *testing.T) { 273 m := map[string]string{ 274 "C": "third", 275 "D": "forth", 276 "A": "first", 277 "F": "sixth", 278 "E": "fifth", 279 "B": "second", 280 } 281 v, e := Encode(m, SortMapKeys) 282 require.NoError(t, e) 283 require.Equal(t, `{"A":"first","B":"second","C":"third","D":"forth","E":"fifth","F":"sixth"}`, string(v)) 284 } 285 286 func BenchmarkEncoder_Generic_Sonic(b *testing.B) { 287 _, _ = Encode(_GenericValue, SortMapKeys|EscapeHTML|CompactMarshaler) 288 b.SetBytes(int64(len(TwitterJson))) 289 b.ResetTimer() 290 for i := 0; i < b.N; i++ { 291 _, _ = Encode(_GenericValue, SortMapKeys|EscapeHTML|CompactMarshaler) 292 } 293 } 294 295 func BenchmarkEncoder_Generic_Sonic_Fast(b *testing.B) { 296 _, _ = Encode(_GenericValue, 0) 297 b.SetBytes(int64(len(TwitterJson))) 298 b.ResetTimer() 299 for i := 0; i < b.N; i++ { 300 _, _ = Encode(_GenericValue, 0) 301 } 302 } 303 304 func BenchmarkEncoder_Generic_StdLib(b *testing.B) { 305 _, _ = json.Marshal(_GenericValue) 306 b.SetBytes(int64(len(TwitterJson))) 307 b.ResetTimer() 308 for i := 0; i < b.N; i++ { 309 _, _ = json.Marshal(_GenericValue) 310 } 311 } 312 313 func BenchmarkEncoder_Binding_Sonic(b *testing.B) { 314 _, _ = Encode(&_BindingValue, SortMapKeys|EscapeHTML|CompactMarshaler) 315 b.SetBytes(int64(len(TwitterJson))) 316 b.ResetTimer() 317 for i := 0; i < b.N; i++ { 318 _, _ = Encode(&_BindingValue, SortMapKeys|EscapeHTML|CompactMarshaler) 319 } 320 } 321 322 func BenchmarkEncoder_Binding_Sonic_Fast(b *testing.B) { 323 _, _ = Encode(&_BindingValue, NoQuoteTextMarshaler) 324 b.SetBytes(int64(len(TwitterJson))) 325 b.ResetTimer() 326 for i := 0; i < b.N; i++ { 327 _, _ = Encode(&_BindingValue, NoQuoteTextMarshaler) 328 } 329 } 330 331 func BenchmarkEncoder_Binding_StdLib(b *testing.B) { 332 _, _ = json.Marshal(&_BindingValue) 333 b.SetBytes(int64(len(TwitterJson))) 334 b.ResetTimer() 335 for i := 0; i < b.N; i++ { 336 _, _ = json.Marshal(&_BindingValue) 337 } 338 } 339 340 func BenchmarkEncoder_Parallel_Generic_Sonic(b *testing.B) { 341 _, _ = Encode(_GenericValue, SortMapKeys|EscapeHTML|CompactMarshaler) 342 b.SetBytes(int64(len(TwitterJson))) 343 b.ResetTimer() 344 b.RunParallel(func(pb *testing.PB) { 345 for pb.Next() { 346 _, _ = Encode(_GenericValue, SortMapKeys|EscapeHTML|CompactMarshaler) 347 } 348 }) 349 } 350 351 func BenchmarkEncoder_Parallel_Generic_Sonic_Fast(b *testing.B) { 352 _, _ = Encode(_GenericValue, NoQuoteTextMarshaler) 353 b.SetBytes(int64(len(TwitterJson))) 354 b.ResetTimer() 355 b.RunParallel(func(pb *testing.PB) { 356 for pb.Next() { 357 _, _ = Encode(_GenericValue, NoQuoteTextMarshaler) 358 } 359 }) 360 } 361 362 func BenchmarkEncoder_Parallel_Generic_StdLib(b *testing.B) { 363 _, _ = json.Marshal(_GenericValue) 364 b.SetBytes(int64(len(TwitterJson))) 365 b.ResetTimer() 366 b.RunParallel(func(pb *testing.PB) { 367 for pb.Next() { 368 _, _ = json.Marshal(_GenericValue) 369 } 370 }) 371 } 372 373 func BenchmarkEncoder_Parallel_Binding_Sonic(b *testing.B) { 374 _, _ = Encode(&_BindingValue, SortMapKeys|EscapeHTML|CompactMarshaler) 375 b.SetBytes(int64(len(TwitterJson))) 376 b.ResetTimer() 377 b.RunParallel(func(pb *testing.PB) { 378 for pb.Next() { 379 _, _ = Encode(&_BindingValue, SortMapKeys|EscapeHTML|CompactMarshaler) 380 } 381 }) 382 } 383 384 func BenchmarkEncoder_Parallel_Binding_Sonic_Fast(b *testing.B) { 385 _, _ = Encode(&_BindingValue, NoQuoteTextMarshaler) 386 b.SetBytes(int64(len(TwitterJson))) 387 b.ResetTimer() 388 b.RunParallel(func(pb *testing.PB) { 389 for pb.Next() { 390 _, _ = Encode(&_BindingValue, NoQuoteTextMarshaler) 391 } 392 }) 393 } 394 395 func BenchmarkEncoder_Parallel_Binding_StdLib(b *testing.B) { 396 _, _ = json.Marshal(&_BindingValue) 397 b.SetBytes(int64(len(TwitterJson))) 398 b.ResetTimer() 399 b.RunParallel(func(pb *testing.PB) { 400 for pb.Next() { 401 _, _ = json.Marshal(&_BindingValue) 402 } 403 }) 404 } 405 406 func BenchmarkHTMLEscape_Sonic(b *testing.B) { 407 jsonByte := []byte(TwitterJson) 408 b.SetBytes(int64(len(TwitterJson))) 409 b.ResetTimer() 410 var buf []byte 411 for i := 0; i < b.N; i++ { 412 buf = HTMLEscape(nil, jsonByte) 413 } 414 _ = buf 415 } 416 417 func BenchmarkHTMLEscape_StdLib(b *testing.B) { 418 jsonByte := []byte(TwitterJson) 419 b.SetBytes(int64(len(TwitterJson))) 420 b.ResetTimer() 421 var buf []byte 422 for i := 0; i < b.N; i++ { 423 out := bytes.NewBuffer(make([]byte, 0, len(TwitterJson)*6/5)) 424 json.HTMLEscape(out, jsonByte) 425 buf = out.Bytes() 426 } 427 _ = buf 428 } 429 430 func BenchmarkValidate_Sonic(b *testing.B) { 431 var data = rt.Str2Mem(TwitterJson) 432 ok, s := Valid(data) 433 if !ok { 434 b.Fatal(s) 435 } 436 b.SetBytes(int64(len(TwitterJson))) 437 b.ResetTimer() 438 for i := 0; i < b.N; i++ { 439 _, _ = Valid(data) 440 } 441 } 442 443 func BenchmarkValidate_Std(b *testing.B) { 444 var data = rt.Str2Mem(TwitterJson) 445 if !json.Valid(data) { 446 b.Fatal() 447 } 448 b.SetBytes(int64(len(TwitterJson))) 449 b.ResetTimer() 450 for i := 0; i < b.N; i++ { 451 _ = json.Valid(data) 452 } 453 } 454 455 func BenchmarkCompact_Std(b *testing.B) { 456 var data = rt.Str2Mem(TwitterJson) 457 var dst = bytes.NewBuffer(nil) 458 if err := json.Compact(dst, data); err != nil { 459 b.Fatal(err) 460 } 461 b.SetBytes(int64(len(TwitterJson))) 462 b.ResetTimer() 463 for i := 0; i < b.N; i++ { 464 dst.Reset() 465 _ = json.Compact(dst, data) 466 } 467 } 468 469 type f64Bench struct { 470 name string 471 float float64 472 } 473 474 func BenchmarkEncode_Float64(b *testing.B) { 475 var bench = []f64Bench{ 476 {"Zero", 0}, 477 {"ShortDecimal", 1000}, 478 {"Decimal", 33909}, 479 {"Float", 339.7784}, 480 {"Exp", -5.09e75}, 481 {"NegExp", -5.11e-95}, 482 {"LongExp", 1.234567890123456e-78}, 483 {"Big", 123456789123456789123456789}, 484 } 485 maxUint := "18446744073709551615" 486 for i := 1; i <= len(maxUint); i++ { 487 name := strconv.FormatInt(int64(i), 10) + "-Digs" 488 num, _ := strconv.ParseUint(string(maxUint[:i]), 10, 64) 489 bench = append(bench, f64Bench{name, float64(num)}) 490 } 491 for _, c := range bench { 492 libs := []struct { 493 name string 494 test func(*testing.B) 495 }{{ 496 name: "StdLib", 497 test: func(b *testing.B) { 498 _, _ = json.Marshal(c.float) 499 for i := 0; i < b.N; i++ { 500 _, _ = json.Marshal(c.float) 501 } 502 }, 503 }, { 504 name: "Sonic", 505 test: func(b *testing.B) { 506 _, _ = Encode(c.float, 0) 507 for i := 0; i < b.N; i++ { 508 _, _ = Encode(c.float, 0) 509 } 510 }, 511 }} 512 for _, lib := range libs { 513 name := lib.name + "_" + c.name 514 b.Run(name, lib.test) 515 } 516 } 517 } 518 519 type f32Bench struct { 520 name string 521 float float32 522 } 523 524 func BenchmarkEncode_Float32(b *testing.B) { 525 var bench = []f32Bench{ 526 {"Zero", 0}, 527 {"ShortDecimal", 1000}, 528 {"Decimal", 33909}, 529 {"ExactFraction", 3.375}, 530 {"Point", 339.7784}, 531 {"Exp", -5.09e25}, 532 {"NegExp", -5.11e-25}, 533 {"Shortest", 1.234567e-8}, 534 } 535 536 maxUint := "18446744073709551615" 537 for i := 1; i <= len(maxUint); i++ { 538 name := strconv.FormatInt(int64(i), 10) + "-Digs" 539 num, _ := strconv.ParseUint(string(maxUint[:i]), 10, 64) 540 bench = append(bench, f32Bench{name, float32(num)}) 541 } 542 for _, c := range bench { 543 libs := []struct { 544 name string 545 test func(*testing.B) 546 }{{ 547 name: "StdLib", 548 test: func(b *testing.B) { 549 _, _ = json.Marshal(c.float) 550 for i := 0; i < b.N; i++ { 551 _, _ = json.Marshal(c.float) 552 } 553 }, 554 }, { 555 name: "Sonic", 556 test: func(b *testing.B) { 557 _, _ = Encode(c.float, 0) 558 for i := 0; i < b.N; i++ { 559 _, _ = Encode(c.float, 0) 560 } 561 }, 562 }} 563 for _, lib := range libs { 564 name := lib.name + "_" + c.name 565 b.Run(name, lib.test) 566 } 567 } 568 }