| // Copyright 2020 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build goexperiment.jsonv2 | |
| package json_test | |
| import ( | |
| "bytes" | |
| "cmp" | |
| "fmt" | |
| "io" | |
| "os" | |
| "path" | |
| "reflect" | |
| "strings" | |
| "testing" | |
| "testing/iotest" | |
| "time" | |
| jsonv1 "encoding/json" | |
| jsonv1in2 "encoding/json" | |
| "encoding/json/internal/jsontest" | |
| "encoding/json/jsontext" | |
| jsonv2 "encoding/json/v2" | |
| ) | |
| // benchVersion is the version to benchmark (either "v1", "v1in2", or "v2"). | |
| var benchVersion = cmp.Or(os.Getenv("BENCHMARK_VERSION"), "v2") | |
| var jsonFuncs = func() (funcs struct { | |
| marshal func(any) ([]byte, error) | |
| unmarshal func([]byte, any) error | |
| encodeValue func(w io.Writer, b []byte) error | |
| encodeTokens func(w io.Writer, toks []jsontext.Token) error | |
| decodeValue func(r io.Reader) error | |
| decodeTokens func(r io.Reader) error | |
| }) { | |
| ignoreEOF := func(err error) error { | |
| if err == io.EOF { | |
| err = nil | |
| } | |
| return err | |
| } | |
| switch benchVersion { | |
| case "v1": | |
| funcs.marshal = jsonv1.Marshal | |
| funcs.unmarshal = jsonv1.Unmarshal | |
| funcs.encodeValue = func(w io.Writer, b []byte) error { | |
| return jsonv1.NewEncoder(w).Encode(jsonv1.RawMessage(b)) | |
| } | |
| funcs.decodeValue = func(r io.Reader) error { | |
| var v jsonv1.RawMessage | |
| return jsonv1.NewDecoder(r).Decode(&v) | |
| } | |
| funcs.decodeTokens = func(r io.Reader) error { | |
| d := jsonv1.NewDecoder(r) | |
| for { | |
| if _, err := d.Token(); err != nil { | |
| return ignoreEOF(err) | |
| } | |
| } | |
| } | |
| case "v1in2": | |
| funcs.marshal = jsonv1in2.Marshal | |
| funcs.unmarshal = jsonv1in2.Unmarshal | |
| funcs.encodeValue = func(w io.Writer, b []byte) error { | |
| return jsonv1in2.NewEncoder(w).Encode(jsonv1in2.RawMessage(b)) | |
| } | |
| funcs.decodeValue = func(r io.Reader) error { | |
| var v jsonv1in2.RawMessage | |
| return jsonv1in2.NewDecoder(r).Decode(&v) | |
| } | |
| funcs.decodeTokens = func(r io.Reader) error { | |
| d := jsonv1in2.NewDecoder(r) | |
| for { | |
| if _, err := d.Token(); err != nil { | |
| return ignoreEOF(err) | |
| } | |
| } | |
| } | |
| case "v2": | |
| funcs.marshal = func(v any) ([]byte, error) { return jsonv2.Marshal(v) } | |
| funcs.unmarshal = func(b []byte, v any) error { return jsonv2.Unmarshal(b, v) } | |
| funcs.encodeValue = func(w io.Writer, b []byte) error { | |
| return jsontext.NewEncoder(w).WriteValue(b) | |
| } | |
| funcs.encodeTokens = func(w io.Writer, toks []jsontext.Token) error { | |
| e := jsontext.NewEncoder(w) | |
| for _, tok := range toks { | |
| if err := e.WriteToken(tok); err != nil { | |
| return err | |
| } | |
| } | |
| return nil | |
| } | |
| funcs.decodeValue = func(r io.Reader) error { | |
| _, err := jsontext.NewDecoder(r).ReadValue() | |
| return err | |
| } | |
| funcs.decodeTokens = func(r io.Reader) error { | |
| d := jsontext.NewDecoder(r) | |
| for { | |
| if _, err := d.ReadToken(); err != nil { | |
| return ignoreEOF(err) | |
| } | |
| } | |
| } | |
| default: | |
| panic("unknown version: " + benchVersion) | |
| } | |
| return | |
| }() | |
| // bytesBuffer is identical to bytes.Buffer, | |
| // but a different type to avoid any optimizations for bytes.Buffer. | |
| type bytesBuffer struct{ *bytes.Buffer } | |
| func addr[T any](v T) *T { | |
| return &v | |
| } | |
| func len64[Bytes ~[]byte | ~string](in Bytes) int64 { | |
| return int64(len(in)) | |
| } | |
| var arshalTestdata = []struct { | |
| name string | |
| raw []byte | |
| val any | |
| new func() any | |
| skipV1 bool | |
| }{{ | |
| name: "Bool", | |
| raw: []byte("true"), | |
| val: addr(true), | |
| new: func() any { return new(bool) }, | |
| }, { | |
| name: "String", | |
| raw: []byte(`"hello, world!"`), | |
| val: addr("hello, world!"), | |
| new: func() any { return new(string) }, | |
| }, { | |
| name: "Int", | |
| raw: []byte("-1234"), | |
| val: addr(int64(-1234)), | |
| new: func() any { return new(int64) }, | |
| }, { | |
| name: "Uint", | |
| raw: []byte("1234"), | |
| val: addr(uint64(1234)), | |
| new: func() any { return new(uint64) }, | |
| }, { | |
| name: "Float", | |
| raw: []byte("12.34"), | |
| val: addr(float64(12.34)), | |
| new: func() any { return new(float64) }, | |
| }, { | |
| name: "Map/ManyEmpty", | |
| raw: []byte(`[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]`), | |
| val: addr(func() (out []map[string]string) { | |
| for range 100 { | |
| out = append(out, map[string]string{}) | |
| } | |
| return out | |
| }()), | |
| new: func() any { return new([]map[string]string) }, | |
| }, { | |
| name: "Map/OneLarge", | |
| raw: []byte(`{"A":"A","B":"B","C":"C","D":"D","E":"E","F":"F","G":"G","H":"H","I":"I","J":"J","K":"K","L":"L","M":"M","N":"N","O":"O","P":"P","Q":"Q","R":"R","S":"S","T":"T","U":"U","V":"V","W":"W","X":"X","Y":"Y","Z":"Z"}`), | |
| val: addr(map[string]string{"A": "A", "B": "B", "C": "C", "D": "D", "E": "E", "F": "F", "G": "G", "H": "H", "I": "I", "J": "J", "K": "K", "L": "L", "M": "M", "N": "N", "O": "O", "P": "P", "Q": "Q", "R": "R", "S": "S", "T": "T", "U": "U", "V": "V", "W": "W", "X": "X", "Y": "Y", "Z": "Z"}), | |
| new: func() any { return new(map[string]string) }, | |
| }, { | |
| name: "Map/ManySmall", | |
| raw: []byte(`{"A":{"K":"V"},"B":{"K":"V"},"C":{"K":"V"},"D":{"K":"V"},"E":{"K":"V"},"F":{"K":"V"},"G":{"K":"V"},"H":{"K":"V"},"I":{"K":"V"},"J":{"K":"V"},"K":{"K":"V"},"L":{"K":"V"},"M":{"K":"V"},"N":{"K":"V"},"O":{"K":"V"},"P":{"K":"V"},"Q":{"K":"V"},"R":{"K":"V"},"S":{"K":"V"},"T":{"K":"V"},"U":{"K":"V"},"V":{"K":"V"},"W":{"K":"V"},"X":{"K":"V"},"Y":{"K":"V"},"Z":{"K":"V"}}`), | |
| val: addr(map[string]map[string]string{"A": {"K": "V"}, "B": {"K": "V"}, "C": {"K": "V"}, "D": {"K": "V"}, "E": {"K": "V"}, "F": {"K": "V"}, "G": {"K": "V"}, "H": {"K": "V"}, "I": {"K": "V"}, "J": {"K": "V"}, "K": {"K": "V"}, "L": {"K": "V"}, "M": {"K": "V"}, "N": {"K": "V"}, "O": {"K": "V"}, "P": {"K": "V"}, "Q": {"K": "V"}, "R": {"K": "V"}, "S": {"K": "V"}, "T": {"K": "V"}, "U": {"K": "V"}, "V": {"K": "V"}, "W": {"K": "V"}, "X": {"K": "V"}, "Y": {"K": "V"}, "Z": {"K": "V"}}), | |
| new: func() any { return new(map[string]map[string]string) }, | |
| }, { | |
| name: "Struct/ManyEmpty", | |
| raw: []byte(`[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]`), | |
| val: addr(make([]struct{}, 100)), | |
| new: func() any { | |
| return new([]struct{}) | |
| }, | |
| }, { | |
| name: "Struct/OneLarge", | |
| raw: []byte(`{"A":"A","B":"B","C":"C","D":"D","E":"E","F":"F","G":"G","H":"H","I":"I","J":"J","K":"K","L":"L","M":"M","N":"N","O":"O","P":"P","Q":"Q","R":"R","S":"S","T":"T","U":"U","V":"V","W":"W","X":"X","Y":"Y","Z":"Z"}`), | |
| val: addr(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z string }{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}), | |
| new: func() any { | |
| return new(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z string }) | |
| }, | |
| }, { | |
| name: "Struct/ManySmall", | |
| raw: []byte(`{"A":{"K":"V"},"B":{"K":"V"},"C":{"K":"V"},"D":{"K":"V"},"E":{"K":"V"},"F":{"K":"V"},"G":{"K":"V"},"H":{"K":"V"},"I":{"K":"V"},"J":{"K":"V"},"K":{"K":"V"},"L":{"K":"V"},"M":{"K":"V"},"N":{"K":"V"},"O":{"K":"V"},"P":{"K":"V"},"Q":{"K":"V"},"R":{"K":"V"},"S":{"K":"V"},"T":{"K":"V"},"U":{"K":"V"},"V":{"K":"V"},"W":{"K":"V"},"X":{"K":"V"},"Y":{"K":"V"},"Z":{"K":"V"}}`), | |
| val: func() any { | |
| V := struct{ K string }{"V"} | |
| return addr(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z struct{ K string } }{ | |
| V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, | |
| }) | |
| }(), | |
| new: func() any { | |
| return new(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z struct{ K string } }) | |
| }, | |
| }, { | |
| name: "Slice/ManyEmpty", | |
| raw: []byte(`[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]`), | |
| val: addr(func() (out [][]string) { | |
| for range 100 { | |
| out = append(out, []string{}) | |
| } | |
| return out | |
| }()), | |
| new: func() any { return new([][]string) }, | |
| }, { | |
| name: "Slice/OneLarge", | |
| raw: []byte(`["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]`), | |
| val: addr([]string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}), | |
| new: func() any { return new([]string) }, | |
| }, { | |
| name: "Slice/ManySmall", | |
| raw: []byte(`[["A"],["B"],["C"],["D"],["E"],["F"],["G"],["H"],["I"],["J"],["K"],["L"],["M"],["N"],["O"],["P"],["Q"],["R"],["S"],["T"],["U"],["V"],["W"],["X"],["Y"],["Z"]]`), | |
| val: addr([][]string{{"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"G"}, {"H"}, {"I"}, {"J"}, {"K"}, {"L"}, {"M"}, {"N"}, {"O"}, {"P"}, {"Q"}, {"R"}, {"S"}, {"T"}, {"U"}, {"V"}, {"W"}, {"X"}, {"Y"}, {"Z"}}), | |
| new: func() any { return new([][]string) }, | |
| }, { | |
| name: "Array/OneLarge", | |
| raw: []byte(`["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]`), | |
| val: addr([26]string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}), | |
| new: func() any { return new([26]string) }, | |
| }, { | |
| name: "Array/ManySmall", | |
| raw: []byte(`[["A"],["B"],["C"],["D"],["E"],["F"],["G"],["H"],["I"],["J"],["K"],["L"],["M"],["N"],["O"],["P"],["Q"],["R"],["S"],["T"],["U"],["V"],["W"],["X"],["Y"],["Z"]]`), | |
| val: addr([26][1]string{{"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"G"}, {"H"}, {"I"}, {"J"}, {"K"}, {"L"}, {"M"}, {"N"}, {"O"}, {"P"}, {"Q"}, {"R"}, {"S"}, {"T"}, {"U"}, {"V"}, {"W"}, {"X"}, {"Y"}, {"Z"}}), | |
| new: func() any { return new([26][1]string) }, | |
| }, { | |
| name: "Bytes/Slice", | |
| raw: []byte(`"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="`), | |
| val: addr([]byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}), | |
| new: func() any { return new([]byte) }, | |
| }, { | |
| name: "Bytes/Array", | |
| raw: []byte(`"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="`), | |
| val: addr([32]byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}), | |
| new: func() any { return new([32]byte) }, | |
| skipV1: true, | |
| }, { | |
| name: "Pointer", | |
| raw: []byte("true"), | |
| val: addr(addr(addr(addr(addr(addr(addr(addr(addr(addr(addr(true))))))))))), | |
| new: func() any { return new(**********bool) }, | |
| }, { | |
| name: "TextArshal", | |
| raw: []byte(`"method"`), | |
| val: new(textArshaler), | |
| new: func() any { return new(textArshaler) }, | |
| }, { | |
| name: "JSONArshalV1", | |
| raw: []byte(`"method"`), | |
| val: new(jsonArshalerV1), | |
| new: func() any { return new(jsonArshalerV1) }, | |
| }, { | |
| name: "JSONArshalV2", | |
| raw: []byte(`"method"`), | |
| val: new(jsonArshalerV2), | |
| new: func() any { return new(jsonArshalerV2) }, | |
| skipV1: true, | |
| }, { | |
| /* TODO(https://go.dev/issue/71631): Re-enable this test case. | |
| name: "Duration", | |
| raw: []byte(`"1h1m1s"`), | |
| val: addr(time.Hour + time.Minute + time.Second), | |
| new: func() any { return new(time.Duration) }, | |
| skipV1: true, | |
| }, { */ | |
| name: "Time", | |
| raw: []byte(`"2006-01-02T22:04:05Z"`), | |
| val: addr(time.Unix(1136239445, 0).UTC()), | |
| new: func() any { return new(time.Time) }, | |
| }} | |
| type textArshaler struct{ _ [4]int } | |
| func (textArshaler) MarshalText() ([]byte, error) { | |
| return []byte("method"), nil | |
| } | |
| func (*textArshaler) UnmarshalText(b []byte) error { | |
| if string(b) != "method" { | |
| return fmt.Errorf("UnmarshalText: got %q, want %q", b, "method") | |
| } | |
| return nil | |
| } | |
| type jsonArshalerV1 struct{ _ [4]int } | |
| func (jsonArshalerV1) MarshalJSON() ([]byte, error) { | |
| return []byte(`"method"`), nil | |
| } | |
| func (*jsonArshalerV1) UnmarshalJSON(b []byte) error { | |
| if string(b) != `"method"` { | |
| return fmt.Errorf("UnmarshalJSON: got %q, want %q", b, `"method"`) | |
| } | |
| return nil | |
| } | |
| type jsonArshalerV2 struct{ _ [4]int } | |
| func (jsonArshalerV2) MarshalJSONTo(enc *jsontext.Encoder) error { | |
| return enc.WriteToken(jsontext.String("method")) | |
| } | |
| func (*jsonArshalerV2) UnmarshalJSONFrom(dec *jsontext.Decoder) error { | |
| b, err := dec.ReadValue() | |
| if string(b) != `"method"` { | |
| return fmt.Errorf("UnmarshalJSONFrom: got %q, want %q", b, `"method"`) | |
| } | |
| return err | |
| } | |
| func TestBenchmarkUnmarshal(t *testing.T) { runUnmarshal(t) } | |
| func BenchmarkUnmarshal(b *testing.B) { runUnmarshal(b) } | |
| func runUnmarshal(tb testing.TB) { | |
| for _, tt := range arshalTestdata { | |
| if tt.skipV1 && strings.HasPrefix(benchVersion, "v1") { | |
| runTestOrBench(tb, tt.name, 0, func(tb testing.TB) { tb.Skip("not supported in v1") }) | |
| return | |
| } | |
| // Setup the unmarshal operation. | |
| var val any | |
| run := func(tb testing.TB) { | |
| val = tt.new() | |
| if err := jsonFuncs.unmarshal(tt.raw, val); err != nil { | |
| tb.Fatalf("Unmarshal error: %v", err) | |
| } | |
| } | |
| // Verify the results. | |
| if _, ok := tb.(*testing.T); ok { | |
| run0 := run | |
| run = func(tb testing.TB) { | |
| run0(tb) | |
| if !reflect.DeepEqual(val, tt.val) { | |
| tb.Fatalf("Unmarshal output mismatch:\ngot %v\nwant %v", val, tt.val) | |
| } | |
| } | |
| } | |
| runTestOrBench(tb, tt.name, len64(tt.raw), run) | |
| } | |
| } | |
| func TestBenchmarkMarshal(t *testing.T) { runMarshal(t) } | |
| func BenchmarkMarshal(b *testing.B) { runMarshal(b) } | |
| func runMarshal(tb testing.TB) { | |
| for _, tt := range arshalTestdata { | |
| if tt.skipV1 && strings.HasPrefix(benchVersion, "v1") { | |
| runTestOrBench(tb, tt.name, 0, func(tb testing.TB) { tb.Skip("not supported in v1") }) | |
| return | |
| } | |
| // Setup the marshal operation. | |
| var raw []byte | |
| run := func(tb testing.TB) { | |
| var err error | |
| raw, err = jsonFuncs.marshal(tt.val) | |
| if err != nil { | |
| tb.Fatalf("Marshal error: %v", err) | |
| } | |
| } | |
| // Verify the results. | |
| if _, ok := tb.(*testing.T); ok { | |
| run0 := run | |
| run = func(tb testing.TB) { | |
| run0(tb) | |
| if !bytes.Equal(raw, tt.raw) { | |
| // Map marshaling in v2 is non-deterministic. | |
| byteHistogram := func(b []byte) (h [256]int) { | |
| for _, c := range b { | |
| h[c]++ | |
| } | |
| return h | |
| } | |
| if !(strings.HasPrefix(tt.name, "Map/") && byteHistogram(raw) == byteHistogram(tt.raw)) { | |
| tb.Fatalf("Marshal output mismatch:\ngot %s\nwant %s", raw, tt.raw) | |
| } | |
| } | |
| } | |
| } | |
| runTestOrBench(tb, tt.name, len64(tt.raw), run) | |
| } | |
| } | |
| func TestBenchmarkTestdata(t *testing.T) { runAllTestdata(t) } | |
| func BenchmarkTestdata(b *testing.B) { runAllTestdata(b) } | |
| func runAllTestdata(tb testing.TB) { | |
| for _, td := range jsontest.Data { | |
| for _, arshalName := range []string{"Marshal", "Unmarshal"} { | |
| for _, typeName := range []string{"Concrete", "Interface"} { | |
| newValue := func() any { return new(any) } | |
| if typeName == "Concrete" { | |
| if td.New == nil { | |
| continue | |
| } | |
| newValue = td.New | |
| } | |
| value := mustUnmarshalValue(tb, td.Data(), newValue) | |
| name := path.Join(td.Name, arshalName, typeName) | |
| runTestOrBench(tb, name, int64(len(td.Data())), func(tb testing.TB) { | |
| runArshal(tb, arshalName, newValue, td.Data(), value) | |
| }) | |
| } | |
| } | |
| tokens := mustDecodeTokens(tb, td.Data()) | |
| buffer := make([]byte, 0, 2*len(td.Data())) | |
| for _, codeName := range []string{"Encode", "Decode"} { | |
| for _, typeName := range []string{"Token", "Value"} { | |
| for _, modeName := range []string{"Streaming", "Buffered"} { | |
| name := path.Join(td.Name, codeName, typeName, modeName) | |
| runTestOrBench(tb, name, int64(len(td.Data())), func(tb testing.TB) { | |
| runCode(tb, codeName, typeName, modeName, buffer, td.Data(), tokens) | |
| }) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| func mustUnmarshalValue(t testing.TB, data []byte, newValue func() any) (value any) { | |
| value = newValue() | |
| if err := jsonv2.Unmarshal(data, value); err != nil { | |
| t.Fatalf("Unmarshal error: %v", err) | |
| } | |
| return value | |
| } | |
| func runArshal(t testing.TB, arshalName string, newValue func() any, data []byte, value any) { | |
| switch arshalName { | |
| case "Marshal": | |
| if _, err := jsonFuncs.marshal(value); err != nil { | |
| t.Fatalf("Marshal error: %v", err) | |
| } | |
| case "Unmarshal": | |
| if err := jsonFuncs.unmarshal(data, newValue()); err != nil { | |
| t.Fatalf("Unmarshal error: %v", err) | |
| } | |
| } | |
| } | |
| func mustDecodeTokens(t testing.TB, data []byte) []jsontext.Token { | |
| var tokens []jsontext.Token | |
| dec := jsontext.NewDecoder(bytes.NewReader(data)) | |
| for { | |
| tok, err := dec.ReadToken() | |
| if err != nil { | |
| if err == io.EOF { | |
| break | |
| } | |
| t.Fatalf("Decoder.ReadToken error: %v", err) | |
| } | |
| // Prefer exact representation for JSON strings and numbers | |
| // since this more closely matches common use cases. | |
| switch tok.Kind() { | |
| case '"': | |
| tokens = append(tokens, jsontext.String(tok.String())) | |
| case '0': | |
| f, _ := tok.Float() | |
| tokens = append(tokens, jsontext.Float(f)) | |
| default: | |
| tokens = append(tokens, tok.Clone()) | |
| } | |
| } | |
| return tokens | |
| } | |
| func runCode(t testing.TB, codeName, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) { | |
| switch codeName { | |
| case "Encode": | |
| runEncode(t, typeName, modeName, buffer, data, tokens) | |
| case "Decode": | |
| runDecode(t, typeName, modeName, buffer, data, tokens) | |
| } | |
| } | |
| func runEncode(t testing.TB, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) { | |
| if strings.HasPrefix(benchVersion, "v1") { | |
| switch { | |
| case modeName == "Buffered": | |
| t.Skip("no support for direct buffered output in v1; see https://go.dev/issue/7872") | |
| case typeName == "Token": | |
| t.Skip("no support for encoding tokens in v1; see https://go.dev/issue/40127") | |
| } | |
| } | |
| var w io.Writer | |
| switch modeName { | |
| case "Streaming": | |
| w = bytesBuffer{bytes.NewBuffer(buffer[:0])} | |
| case "Buffered": | |
| w = bytes.NewBuffer(buffer[:0]) | |
| } | |
| switch typeName { | |
| case "Token": | |
| if err := jsonFuncs.encodeTokens(w, tokens); err != nil { | |
| t.Fatalf("Encoder.WriteToken error: %v", err) | |
| } | |
| case "Value": | |
| if err := jsonFuncs.encodeValue(w, data); err != nil { | |
| t.Fatalf("Encoder.WriteValue error: %v", err) | |
| } | |
| } | |
| } | |
| func runDecode(t testing.TB, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) { | |
| if strings.HasPrefix(benchVersion, "v1") && modeName == "Buffered" { | |
| t.Skip("no support for direct buffered input in v1; see https://go.dev/issue/11046") | |
| } | |
| var r io.Reader | |
| switch modeName { | |
| case "Streaming": | |
| r = bytesBuffer{bytes.NewBuffer(data)} | |
| case "Buffered": | |
| r = bytes.NewBuffer(data) | |
| } | |
| switch typeName { | |
| case "Token": | |
| if err := jsonFuncs.decodeTokens(r); err != nil { | |
| t.Fatalf("Decoder.ReadToken error: %v", err) | |
| } | |
| case "Value": | |
| if err := jsonFuncs.decodeValue(r); err != nil { | |
| t.Fatalf("Decoder.ReadValue error: %v", err) | |
| } | |
| } | |
| } | |
| var ws = strings.Repeat(" ", 4<<10) | |
| var slowStreamingDecodeTestdata = []struct { | |
| name string | |
| data []byte | |
| }{ | |
| {"LargeString", []byte(`"` + strings.Repeat(" ", 4<<10) + `"`)}, | |
| {"LargeNumber", []byte("0." + strings.Repeat("0", 4<<10))}, | |
| {"LargeWhitespace/Null", []byte(ws + "null" + ws)}, | |
| {"LargeWhitespace/Object", []byte(ws + "{" + ws + `"name1"` + ws + ":" + ws + `"value"` + ws + "," + ws + `"name2"` + ws + ":" + ws + `"value"` + ws + "}" + ws)}, | |
| {"LargeWhitespace/Array", []byte(ws + "[" + ws + `"value"` + ws + "," + ws + `"value"` + ws + "]" + ws)}, | |
| } | |
| func TestBenchmarkSlowStreamingDecode(t *testing.T) { runAllSlowStreamingDecode(t) } | |
| func BenchmarkSlowStreamingDecode(b *testing.B) { runAllSlowStreamingDecode(b) } | |
| func runAllSlowStreamingDecode(tb testing.TB) { | |
| for _, td := range slowStreamingDecodeTestdata { | |
| for _, typeName := range []string{"Token", "Value"} { | |
| name := path.Join(td.name, typeName) | |
| runTestOrBench(tb, name, len64(td.data), func(tb testing.TB) { | |
| runSlowStreamingDecode(tb, typeName, td.data) | |
| }) | |
| } | |
| } | |
| } | |
| // runSlowStreamingDecode tests a streaming Decoder operating on | |
| // a slow io.Reader that only returns 1 byte at a time, | |
| // which tends to exercise pathological behavior. | |
| func runSlowStreamingDecode(t testing.TB, typeName string, data []byte) { | |
| r := iotest.OneByteReader(bytes.NewReader(data)) | |
| switch typeName { | |
| case "Token": | |
| if err := jsonFuncs.decodeTokens(r); err != nil { | |
| t.Fatalf("Decoder.ReadToken error: %v", err) | |
| } | |
| case "Value": | |
| if err := jsonFuncs.decodeValue(r); err != nil { | |
| t.Fatalf("Decoder.ReadValue error: %v", err) | |
| } | |
| } | |
| } | |
| func TestBenchmarkTextValue(t *testing.T) { runValue(t) } | |
| func BenchmarkTextValue(b *testing.B) { runValue(b) } | |
| func runValue(tb testing.TB) { | |
| if testing.Short() { | |
| tb.Skip() // CitmCatalog is not loaded in short mode | |
| } | |
| var data []byte | |
| for _, ts := range jsontest.Data { | |
| if ts.Name == "CitmCatalog" { | |
| data = ts.Data() | |
| } | |
| } | |
| runTestOrBench(tb, "IsValid", len64(data), func(tb testing.TB) { | |
| jsontext.Value(data).IsValid() | |
| }) | |
| methods := []struct { | |
| name string | |
| format func(*jsontext.Value, ...jsontext.Options) error | |
| }{ | |
| {"Compact", (*jsontext.Value).Compact}, | |
| {"Indent", (*jsontext.Value).Indent}, | |
| {"Canonicalize", (*jsontext.Value).Canonicalize}, | |
| } | |
| var v jsontext.Value | |
| for _, method := range methods { | |
| runTestOrBench(tb, method.name, len64(data), func(tb testing.TB) { | |
| v = append(v[:0], data...) // reset with original input | |
| if err := method.format(&v); err != nil { | |
| tb.Errorf("jsontext.Value.%v error: %v", method.name, err) | |
| } | |
| }) | |
| v = append(v[:0], data...) | |
| method.format(&v) | |
| runTestOrBench(tb, method.name+"/Noop", len64(data), func(tb testing.TB) { | |
| if err := method.format(&v); err != nil { | |
| tb.Errorf("jsontext.Value.%v error: %v", method.name, err) | |
| } | |
| }) | |
| } | |
| } | |
| func runTestOrBench(tb testing.TB, name string, numBytes int64, run func(tb testing.TB)) { | |
| switch tb := tb.(type) { | |
| case *testing.T: | |
| tb.Run(name, func(t *testing.T) { | |
| run(t) | |
| }) | |
| case *testing.B: | |
| tb.Run(name, func(b *testing.B) { | |
| b.ResetTimer() | |
| b.ReportAllocs() | |
| b.SetBytes(numBytes) | |
| for range b.N { | |
| run(b) | |
| } | |
| }) | |
| } | |
| } |