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  }