Source file src/encoding/json/v2/bench_test.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package json_test
     8  
     9  import (
    10  	"bytes"
    11  	"cmp"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"path"
    16  	"reflect"
    17  	"strings"
    18  	"testing"
    19  	"testing/iotest"
    20  	"time"
    21  
    22  	jsonv1 "encoding/json"
    23  
    24  	jsonv1in2 "encoding/json"
    25  	"encoding/json/internal/jsontest"
    26  	"encoding/json/jsontext"
    27  	jsonv2 "encoding/json/v2"
    28  )
    29  
    30  // benchVersion is the version to benchmark (either "v1", "v1in2", or "v2").
    31  var benchVersion = cmp.Or(os.Getenv("BENCHMARK_VERSION"), "v2")
    32  
    33  var jsonFuncs = func() (funcs struct {
    34  	marshal      func(any) ([]byte, error)
    35  	unmarshal    func([]byte, any) error
    36  	encodeValue  func(w io.Writer, b []byte) error
    37  	encodeTokens func(w io.Writer, toks []jsontext.Token) error
    38  	decodeValue  func(r io.Reader) error
    39  	decodeTokens func(r io.Reader) error
    40  }) {
    41  	ignoreEOF := func(err error) error {
    42  		if err == io.EOF {
    43  			err = nil
    44  		}
    45  		return err
    46  	}
    47  
    48  	switch benchVersion {
    49  	case "v1":
    50  		funcs.marshal = jsonv1.Marshal
    51  		funcs.unmarshal = jsonv1.Unmarshal
    52  		funcs.encodeValue = func(w io.Writer, b []byte) error {
    53  			return jsonv1.NewEncoder(w).Encode(jsonv1.RawMessage(b))
    54  		}
    55  		funcs.decodeValue = func(r io.Reader) error {
    56  			var v jsonv1.RawMessage
    57  			return jsonv1.NewDecoder(r).Decode(&v)
    58  		}
    59  		funcs.decodeTokens = func(r io.Reader) error {
    60  			d := jsonv1.NewDecoder(r)
    61  			for {
    62  				if _, err := d.Token(); err != nil {
    63  					return ignoreEOF(err)
    64  				}
    65  			}
    66  		}
    67  	case "v1in2":
    68  		funcs.marshal = jsonv1in2.Marshal
    69  		funcs.unmarshal = jsonv1in2.Unmarshal
    70  		funcs.encodeValue = func(w io.Writer, b []byte) error {
    71  			return jsonv1in2.NewEncoder(w).Encode(jsonv1in2.RawMessage(b))
    72  		}
    73  		funcs.decodeValue = func(r io.Reader) error {
    74  			var v jsonv1in2.RawMessage
    75  			return jsonv1in2.NewDecoder(r).Decode(&v)
    76  		}
    77  		funcs.decodeTokens = func(r io.Reader) error {
    78  			d := jsonv1in2.NewDecoder(r)
    79  			for {
    80  				if _, err := d.Token(); err != nil {
    81  					return ignoreEOF(err)
    82  				}
    83  			}
    84  		}
    85  	case "v2":
    86  		funcs.marshal = func(v any) ([]byte, error) { return jsonv2.Marshal(v) }
    87  		funcs.unmarshal = func(b []byte, v any) error { return jsonv2.Unmarshal(b, v) }
    88  		funcs.encodeValue = func(w io.Writer, b []byte) error {
    89  			return jsontext.NewEncoder(w).WriteValue(b)
    90  		}
    91  		funcs.encodeTokens = func(w io.Writer, toks []jsontext.Token) error {
    92  			e := jsontext.NewEncoder(w)
    93  			for _, tok := range toks {
    94  				if err := e.WriteToken(tok); err != nil {
    95  					return err
    96  				}
    97  			}
    98  			return nil
    99  		}
   100  		funcs.decodeValue = func(r io.Reader) error {
   101  			_, err := jsontext.NewDecoder(r).ReadValue()
   102  			return err
   103  		}
   104  		funcs.decodeTokens = func(r io.Reader) error {
   105  			d := jsontext.NewDecoder(r)
   106  			for {
   107  				if _, err := d.ReadToken(); err != nil {
   108  					return ignoreEOF(err)
   109  				}
   110  			}
   111  		}
   112  	default:
   113  		panic("unknown version: " + benchVersion)
   114  	}
   115  	return
   116  }()
   117  
   118  // bytesBuffer is identical to bytes.Buffer,
   119  // but a different type to avoid any optimizations for bytes.Buffer.
   120  type bytesBuffer struct{ *bytes.Buffer }
   121  
   122  func addr[T any](v T) *T {
   123  	return &v
   124  }
   125  
   126  func len64[Bytes ~[]byte | ~string](in Bytes) int64 {
   127  	return int64(len(in))
   128  }
   129  
   130  var arshalTestdata = []struct {
   131  	name   string
   132  	raw    []byte
   133  	val    any
   134  	new    func() any
   135  	skipV1 bool
   136  }{{
   137  	name: "Bool",
   138  	raw:  []byte("true"),
   139  	val:  addr(true),
   140  	new:  func() any { return new(bool) },
   141  }, {
   142  	name: "String",
   143  	raw:  []byte(`"hello, world!"`),
   144  	val:  addr("hello, world!"),
   145  	new:  func() any { return new(string) },
   146  }, {
   147  	name: "Int",
   148  	raw:  []byte("-1234"),
   149  	val:  addr(int64(-1234)),
   150  	new:  func() any { return new(int64) },
   151  }, {
   152  	name: "Uint",
   153  	raw:  []byte("1234"),
   154  	val:  addr(uint64(1234)),
   155  	new:  func() any { return new(uint64) },
   156  }, {
   157  	name: "Float",
   158  	raw:  []byte("12.34"),
   159  	val:  addr(float64(12.34)),
   160  	new:  func() any { return new(float64) },
   161  }, {
   162  	name: "Map/ManyEmpty",
   163  	raw:  []byte(`[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]`),
   164  	val: addr(func() (out []map[string]string) {
   165  		for range 100 {
   166  			out = append(out, map[string]string{})
   167  		}
   168  		return out
   169  	}()),
   170  	new: func() any { return new([]map[string]string) },
   171  }, {
   172  	name: "Map/OneLarge",
   173  	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"}`),
   174  	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"}),
   175  	new:  func() any { return new(map[string]string) },
   176  }, {
   177  	name: "Map/ManySmall",
   178  	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"}}`),
   179  	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"}}),
   180  	new:  func() any { return new(map[string]map[string]string) },
   181  }, {
   182  	name: "Struct/ManyEmpty",
   183  	raw:  []byte(`[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]`),
   184  	val:  addr(make([]struct{}, 100)),
   185  	new: func() any {
   186  		return new([]struct{})
   187  	},
   188  }, {
   189  	name: "Struct/OneLarge",
   190  	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"}`),
   191  	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"}),
   192  	new: func() any {
   193  		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 })
   194  	},
   195  }, {
   196  	name: "Struct/ManySmall",
   197  	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"}}`),
   198  	val: func() any {
   199  		V := struct{ K string }{"V"}
   200  		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 } }{
   201  			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,
   202  		})
   203  	}(),
   204  	new: func() any {
   205  		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 } })
   206  	},
   207  }, {
   208  	name: "Slice/ManyEmpty",
   209  	raw:  []byte(`[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]`),
   210  	val: addr(func() (out [][]string) {
   211  		for range 100 {
   212  			out = append(out, []string{})
   213  		}
   214  		return out
   215  	}()),
   216  	new: func() any { return new([][]string) },
   217  }, {
   218  	name: "Slice/OneLarge",
   219  	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"]`),
   220  	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"}),
   221  	new:  func() any { return new([]string) },
   222  }, {
   223  	name: "Slice/ManySmall",
   224  	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"]]`),
   225  	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"}}),
   226  	new:  func() any { return new([][]string) },
   227  }, {
   228  	name: "Array/OneLarge",
   229  	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"]`),
   230  	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"}),
   231  	new:  func() any { return new([26]string) },
   232  }, {
   233  	name: "Array/ManySmall",
   234  	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"]]`),
   235  	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"}}),
   236  	new:  func() any { return new([26][1]string) },
   237  }, {
   238  	name: "Bytes/Slice",
   239  	raw:  []byte(`"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="`),
   240  	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}),
   241  	new:  func() any { return new([]byte) },
   242  }, {
   243  	name:   "Bytes/Array",
   244  	raw:    []byte(`"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="`),
   245  	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}),
   246  	new:    func() any { return new([32]byte) },
   247  	skipV1: true,
   248  }, {
   249  	name: "Pointer",
   250  	raw:  []byte("true"),
   251  	val:  addr(addr(addr(addr(addr(addr(addr(addr(addr(addr(addr(true))))))))))),
   252  	new:  func() any { return new(**********bool) },
   253  }, {
   254  	name: "TextArshal",
   255  	raw:  []byte(`"method"`),
   256  	val:  new(textArshaler),
   257  	new:  func() any { return new(textArshaler) },
   258  }, {
   259  	name: "JSONArshalV1",
   260  	raw:  []byte(`"method"`),
   261  	val:  new(jsonArshalerV1),
   262  	new:  func() any { return new(jsonArshalerV1) },
   263  }, {
   264  	name:   "JSONArshalV2",
   265  	raw:    []byte(`"method"`),
   266  	val:    new(jsonArshalerV2),
   267  	new:    func() any { return new(jsonArshalerV2) },
   268  	skipV1: true,
   269  }, {
   270  	/* TODO(https://go.dev/issue/71631): Re-enable this test case.
   271  	name:   "Duration",
   272  	raw:    []byte(`"1h1m1s"`),
   273  	val:    addr(time.Hour + time.Minute + time.Second),
   274  	new:    func() any { return new(time.Duration) },
   275  	skipV1: true,
   276  	}, { */
   277  	name: "Time",
   278  	raw:  []byte(`"2006-01-02T22:04:05Z"`),
   279  	val:  addr(time.Unix(1136239445, 0).UTC()),
   280  	new:  func() any { return new(time.Time) },
   281  }}
   282  
   283  type textArshaler struct{ _ [4]int }
   284  
   285  func (textArshaler) MarshalText() ([]byte, error) {
   286  	return []byte("method"), nil
   287  }
   288  func (*textArshaler) UnmarshalText(b []byte) error {
   289  	if string(b) != "method" {
   290  		return fmt.Errorf("UnmarshalText: got %q, want %q", b, "method")
   291  	}
   292  	return nil
   293  }
   294  
   295  type jsonArshalerV1 struct{ _ [4]int }
   296  
   297  func (jsonArshalerV1) MarshalJSON() ([]byte, error) {
   298  	return []byte(`"method"`), nil
   299  }
   300  func (*jsonArshalerV1) UnmarshalJSON(b []byte) error {
   301  	if string(b) != `"method"` {
   302  		return fmt.Errorf("UnmarshalJSON: got %q, want %q", b, `"method"`)
   303  	}
   304  	return nil
   305  }
   306  
   307  type jsonArshalerV2 struct{ _ [4]int }
   308  
   309  func (jsonArshalerV2) MarshalJSONTo(enc *jsontext.Encoder) error {
   310  	return enc.WriteToken(jsontext.String("method"))
   311  }
   312  func (*jsonArshalerV2) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
   313  	b, err := dec.ReadValue()
   314  	if string(b) != `"method"` {
   315  		return fmt.Errorf("UnmarshalJSONFrom: got %q, want %q", b, `"method"`)
   316  	}
   317  	return err
   318  }
   319  
   320  func TestBenchmarkUnmarshal(t *testing.T) { runUnmarshal(t) }
   321  func BenchmarkUnmarshal(b *testing.B)     { runUnmarshal(b) }
   322  
   323  func runUnmarshal(tb testing.TB) {
   324  	for _, tt := range arshalTestdata {
   325  		if tt.skipV1 && strings.HasPrefix(benchVersion, "v1") {
   326  			runTestOrBench(tb, tt.name, 0, func(tb testing.TB) { tb.Skip("not supported in v1") })
   327  			return
   328  		}
   329  
   330  		// Setup the unmarshal operation.
   331  		var val any
   332  		run := func(tb testing.TB) {
   333  			val = tt.new()
   334  			if err := jsonFuncs.unmarshal(tt.raw, val); err != nil {
   335  				tb.Fatalf("Unmarshal error: %v", err)
   336  			}
   337  		}
   338  
   339  		// Verify the results.
   340  		if _, ok := tb.(*testing.T); ok {
   341  			run0 := run
   342  			run = func(tb testing.TB) {
   343  				run0(tb)
   344  				if !reflect.DeepEqual(val, tt.val) {
   345  					tb.Fatalf("Unmarshal output mismatch:\ngot  %v\nwant %v", val, tt.val)
   346  				}
   347  			}
   348  		}
   349  
   350  		runTestOrBench(tb, tt.name, len64(tt.raw), run)
   351  	}
   352  }
   353  
   354  func TestBenchmarkMarshal(t *testing.T) { runMarshal(t) }
   355  func BenchmarkMarshal(b *testing.B)     { runMarshal(b) }
   356  
   357  func runMarshal(tb testing.TB) {
   358  	for _, tt := range arshalTestdata {
   359  		if tt.skipV1 && strings.HasPrefix(benchVersion, "v1") {
   360  			runTestOrBench(tb, tt.name, 0, func(tb testing.TB) { tb.Skip("not supported in v1") })
   361  			return
   362  		}
   363  
   364  		// Setup the marshal operation.
   365  		var raw []byte
   366  		run := func(tb testing.TB) {
   367  			var err error
   368  			raw, err = jsonFuncs.marshal(tt.val)
   369  			if err != nil {
   370  				tb.Fatalf("Marshal error: %v", err)
   371  			}
   372  		}
   373  
   374  		// Verify the results.
   375  		if _, ok := tb.(*testing.T); ok {
   376  			run0 := run
   377  			run = func(tb testing.TB) {
   378  				run0(tb)
   379  				if !bytes.Equal(raw, tt.raw) {
   380  					// Map marshaling in v2 is non-deterministic.
   381  					byteHistogram := func(b []byte) (h [256]int) {
   382  						for _, c := range b {
   383  							h[c]++
   384  						}
   385  						return h
   386  					}
   387  					if !(strings.HasPrefix(tt.name, "Map/") && byteHistogram(raw) == byteHistogram(tt.raw)) {
   388  						tb.Fatalf("Marshal output mismatch:\ngot  %s\nwant %s", raw, tt.raw)
   389  					}
   390  				}
   391  			}
   392  		}
   393  
   394  		runTestOrBench(tb, tt.name, len64(tt.raw), run)
   395  	}
   396  }
   397  
   398  func TestBenchmarkTestdata(t *testing.T) { runAllTestdata(t) }
   399  func BenchmarkTestdata(b *testing.B)     { runAllTestdata(b) }
   400  
   401  func runAllTestdata(tb testing.TB) {
   402  	for _, td := range jsontest.Data {
   403  		for _, arshalName := range []string{"Marshal", "Unmarshal"} {
   404  			for _, typeName := range []string{"Concrete", "Interface"} {
   405  				newValue := func() any { return new(any) }
   406  				if typeName == "Concrete" {
   407  					if td.New == nil {
   408  						continue
   409  					}
   410  					newValue = td.New
   411  				}
   412  				value := mustUnmarshalValue(tb, td.Data(), newValue)
   413  				name := path.Join(td.Name, arshalName, typeName)
   414  				runTestOrBench(tb, name, int64(len(td.Data())), func(tb testing.TB) {
   415  					runArshal(tb, arshalName, newValue, td.Data(), value)
   416  				})
   417  			}
   418  		}
   419  
   420  		tokens := mustDecodeTokens(tb, td.Data())
   421  		buffer := make([]byte, 0, 2*len(td.Data()))
   422  		for _, codeName := range []string{"Encode", "Decode"} {
   423  			for _, typeName := range []string{"Token", "Value"} {
   424  				for _, modeName := range []string{"Streaming", "Buffered"} {
   425  					name := path.Join(td.Name, codeName, typeName, modeName)
   426  					runTestOrBench(tb, name, int64(len(td.Data())), func(tb testing.TB) {
   427  						runCode(tb, codeName, typeName, modeName, buffer, td.Data(), tokens)
   428  					})
   429  				}
   430  			}
   431  		}
   432  	}
   433  }
   434  
   435  func mustUnmarshalValue(t testing.TB, data []byte, newValue func() any) (value any) {
   436  	value = newValue()
   437  	if err := jsonv2.Unmarshal(data, value); err != nil {
   438  		t.Fatalf("Unmarshal error: %v", err)
   439  	}
   440  	return value
   441  }
   442  
   443  func runArshal(t testing.TB, arshalName string, newValue func() any, data []byte, value any) {
   444  	switch arshalName {
   445  	case "Marshal":
   446  		if _, err := jsonFuncs.marshal(value); err != nil {
   447  			t.Fatalf("Marshal error: %v", err)
   448  		}
   449  	case "Unmarshal":
   450  		if err := jsonFuncs.unmarshal(data, newValue()); err != nil {
   451  			t.Fatalf("Unmarshal error: %v", err)
   452  		}
   453  	}
   454  }
   455  
   456  func mustDecodeTokens(t testing.TB, data []byte) []jsontext.Token {
   457  	var tokens []jsontext.Token
   458  	dec := jsontext.NewDecoder(bytes.NewReader(data))
   459  	for {
   460  		tok, err := dec.ReadToken()
   461  		if err != nil {
   462  			if err == io.EOF {
   463  				break
   464  			}
   465  			t.Fatalf("Decoder.ReadToken error: %v", err)
   466  		}
   467  
   468  		// Prefer exact representation for JSON strings and numbers
   469  		// since this more closely matches common use cases.
   470  		switch tok.Kind() {
   471  		case '"':
   472  			tokens = append(tokens, jsontext.String(tok.String()))
   473  		case '0':
   474  			f, _ := tok.Float()
   475  			tokens = append(tokens, jsontext.Float(f))
   476  		default:
   477  			tokens = append(tokens, tok.Clone())
   478  		}
   479  	}
   480  	return tokens
   481  }
   482  
   483  func runCode(t testing.TB, codeName, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) {
   484  	switch codeName {
   485  	case "Encode":
   486  		runEncode(t, typeName, modeName, buffer, data, tokens)
   487  	case "Decode":
   488  		runDecode(t, typeName, modeName, buffer, data, tokens)
   489  	}
   490  }
   491  
   492  func runEncode(t testing.TB, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) {
   493  	if strings.HasPrefix(benchVersion, "v1") {
   494  		switch {
   495  		case modeName == "Buffered":
   496  			t.Skip("no support for direct buffered output in v1; see https://go.dev/issue/7872")
   497  		case typeName == "Token":
   498  			t.Skip("no support for encoding tokens in v1; see https://go.dev/issue/40127")
   499  		}
   500  	}
   501  
   502  	var w io.Writer
   503  	switch modeName {
   504  	case "Streaming":
   505  		w = bytesBuffer{bytes.NewBuffer(buffer[:0])}
   506  	case "Buffered":
   507  		w = bytes.NewBuffer(buffer[:0])
   508  	}
   509  	switch typeName {
   510  	case "Token":
   511  		if err := jsonFuncs.encodeTokens(w, tokens); err != nil {
   512  			t.Fatalf("Encoder.WriteToken error: %v", err)
   513  		}
   514  	case "Value":
   515  		if err := jsonFuncs.encodeValue(w, data); err != nil {
   516  			t.Fatalf("Encoder.WriteValue error: %v", err)
   517  		}
   518  	}
   519  }
   520  
   521  func runDecode(t testing.TB, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) {
   522  	if strings.HasPrefix(benchVersion, "v1") && modeName == "Buffered" {
   523  		t.Skip("no support for direct buffered input in v1; see https://go.dev/issue/11046")
   524  	}
   525  
   526  	var r io.Reader
   527  	switch modeName {
   528  	case "Streaming":
   529  		r = bytesBuffer{bytes.NewBuffer(data)}
   530  	case "Buffered":
   531  		r = bytes.NewBuffer(data)
   532  	}
   533  	switch typeName {
   534  	case "Token":
   535  		if err := jsonFuncs.decodeTokens(r); err != nil {
   536  			t.Fatalf("Decoder.ReadToken error: %v", err)
   537  		}
   538  	case "Value":
   539  		if err := jsonFuncs.decodeValue(r); err != nil {
   540  			t.Fatalf("Decoder.ReadValue error: %v", err)
   541  		}
   542  	}
   543  }
   544  
   545  var ws = strings.Repeat(" ", 4<<10)
   546  var slowStreamingDecodeTestdata = []struct {
   547  	name string
   548  	data []byte
   549  }{
   550  	{"LargeString", []byte(`"` + strings.Repeat(" ", 4<<10) + `"`)},
   551  	{"LargeNumber", []byte("0." + strings.Repeat("0", 4<<10))},
   552  	{"LargeWhitespace/Null", []byte(ws + "null" + ws)},
   553  	{"LargeWhitespace/Object", []byte(ws + "{" + ws + `"name1"` + ws + ":" + ws + `"value"` + ws + "," + ws + `"name2"` + ws + ":" + ws + `"value"` + ws + "}" + ws)},
   554  	{"LargeWhitespace/Array", []byte(ws + "[" + ws + `"value"` + ws + "," + ws + `"value"` + ws + "]" + ws)},
   555  }
   556  
   557  func TestBenchmarkSlowStreamingDecode(t *testing.T) { runAllSlowStreamingDecode(t) }
   558  func BenchmarkSlowStreamingDecode(b *testing.B)     { runAllSlowStreamingDecode(b) }
   559  
   560  func runAllSlowStreamingDecode(tb testing.TB) {
   561  	for _, td := range slowStreamingDecodeTestdata {
   562  		for _, typeName := range []string{"Token", "Value"} {
   563  			name := path.Join(td.name, typeName)
   564  			runTestOrBench(tb, name, len64(td.data), func(tb testing.TB) {
   565  				runSlowStreamingDecode(tb, typeName, td.data)
   566  			})
   567  		}
   568  	}
   569  }
   570  
   571  // runSlowStreamingDecode tests a streaming Decoder operating on
   572  // a slow io.Reader that only returns 1 byte at a time,
   573  // which tends to exercise pathological behavior.
   574  func runSlowStreamingDecode(t testing.TB, typeName string, data []byte) {
   575  	r := iotest.OneByteReader(bytes.NewReader(data))
   576  	switch typeName {
   577  	case "Token":
   578  		if err := jsonFuncs.decodeTokens(r); err != nil {
   579  			t.Fatalf("Decoder.ReadToken error: %v", err)
   580  		}
   581  	case "Value":
   582  		if err := jsonFuncs.decodeValue(r); err != nil {
   583  			t.Fatalf("Decoder.ReadValue error: %v", err)
   584  		}
   585  	}
   586  }
   587  
   588  func TestBenchmarkTextValue(t *testing.T) { runValue(t) }
   589  func BenchmarkTextValue(b *testing.B)     { runValue(b) }
   590  
   591  func runValue(tb testing.TB) {
   592  	if testing.Short() {
   593  		tb.Skip() // CitmCatalog is not loaded in short mode
   594  	}
   595  	var data []byte
   596  	for _, ts := range jsontest.Data {
   597  		if ts.Name == "CitmCatalog" {
   598  			data = ts.Data()
   599  		}
   600  	}
   601  
   602  	runTestOrBench(tb, "IsValid", len64(data), func(tb testing.TB) {
   603  		jsontext.Value(data).IsValid()
   604  	})
   605  
   606  	methods := []struct {
   607  		name   string
   608  		format func(*jsontext.Value, ...jsontext.Options) error
   609  	}{
   610  		{"Compact", (*jsontext.Value).Compact},
   611  		{"Indent", (*jsontext.Value).Indent},
   612  		{"Canonicalize", (*jsontext.Value).Canonicalize},
   613  	}
   614  
   615  	var v jsontext.Value
   616  	for _, method := range methods {
   617  		runTestOrBench(tb, method.name, len64(data), func(tb testing.TB) {
   618  			v = append(v[:0], data...) // reset with original input
   619  			if err := method.format(&v); err != nil {
   620  				tb.Errorf("jsontext.Value.%v error: %v", method.name, err)
   621  			}
   622  		})
   623  		v = append(v[:0], data...)
   624  		method.format(&v)
   625  		runTestOrBench(tb, method.name+"/Noop", len64(data), func(tb testing.TB) {
   626  			if err := method.format(&v); err != nil {
   627  				tb.Errorf("jsontext.Value.%v error: %v", method.name, err)
   628  			}
   629  		})
   630  	}
   631  }
   632  
   633  func runTestOrBench(tb testing.TB, name string, numBytes int64, run func(tb testing.TB)) {
   634  	switch tb := tb.(type) {
   635  	case *testing.T:
   636  		tb.Run(name, func(t *testing.T) {
   637  			run(t)
   638  		})
   639  	case *testing.B:
   640  		tb.Run(name, func(b *testing.B) {
   641  			b.ResetTimer()
   642  			b.ReportAllocs()
   643  			b.SetBytes(numBytes)
   644  			for range b.N {
   645  				run(b)
   646  			}
   647  		})
   648  	}
   649  }
   650  

View as plain text