Source file src/encoding/json/jsontext/encode_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 jsontext
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"io"
    13  	"path"
    14  	"slices"
    15  	"testing"
    16  
    17  	"encoding/json/internal/jsonflags"
    18  	"encoding/json/internal/jsontest"
    19  	"encoding/json/internal/jsonwire"
    20  )
    21  
    22  // TestEncoder tests whether we can produce JSON with either tokens or raw values.
    23  func TestEncoder(t *testing.T) {
    24  	for _, td := range coderTestdata {
    25  		for _, formatName := range []string{"Compact", "Indented"} {
    26  			for _, typeName := range []string{"Token", "Value", "TokenDelims"} {
    27  				t.Run(path.Join(td.name.Name, typeName, formatName), func(t *testing.T) {
    28  					testEncoder(t, td.name.Where, formatName, typeName, td)
    29  				})
    30  			}
    31  		}
    32  	}
    33  }
    34  func testEncoder(t *testing.T, where jsontest.CasePos, formatName, typeName string, td coderTestdataEntry) {
    35  	var want string
    36  	var opts []Options
    37  	dst := new(bytes.Buffer)
    38  	opts = append(opts, jsonflags.OmitTopLevelNewline|1)
    39  	want = td.outCompacted
    40  	switch formatName {
    41  	case "Indented":
    42  		opts = append(opts, Multiline(true))
    43  		opts = append(opts, WithIndentPrefix("\t"))
    44  		opts = append(opts, WithIndent("    "))
    45  		if td.outIndented != "" {
    46  			want = td.outIndented
    47  		}
    48  	}
    49  	enc := NewEncoder(dst, opts...)
    50  
    51  	switch typeName {
    52  	case "Token":
    53  		var pointers []Pointer
    54  		for _, tok := range td.tokens {
    55  			if err := enc.WriteToken(tok); err != nil {
    56  				t.Fatalf("%s: Encoder.WriteToken error: %v", where, err)
    57  			}
    58  			if td.pointers != nil {
    59  				pointers = append(pointers, enc.StackPointer())
    60  			}
    61  		}
    62  		if !slices.Equal(pointers, td.pointers) {
    63  			t.Fatalf("%s: pointers mismatch:\ngot  %q\nwant %q", where, pointers, td.pointers)
    64  		}
    65  	case "Value":
    66  		if err := enc.WriteValue(Value(td.in)); err != nil {
    67  			t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
    68  		}
    69  	case "TokenDelims":
    70  		// Use WriteToken for object/array delimiters, WriteValue otherwise.
    71  		for _, tok := range td.tokens {
    72  			switch tok.Kind() {
    73  			case '{', '}', '[', ']':
    74  				if err := enc.WriteToken(tok); err != nil {
    75  					t.Fatalf("%s: Encoder.WriteToken error: %v", where, err)
    76  				}
    77  			default:
    78  				val := Value(tok.String())
    79  				if tok.Kind() == '"' {
    80  					val, _ = jsonwire.AppendQuote(nil, tok.String(), &jsonflags.Flags{})
    81  				}
    82  				if err := enc.WriteValue(val); err != nil {
    83  					t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
    84  				}
    85  			}
    86  		}
    87  	}
    88  
    89  	got := dst.String()
    90  	if got != want {
    91  		t.Errorf("%s: output mismatch:\ngot  %q\nwant %q", where, got, want)
    92  	}
    93  }
    94  
    95  // TestFaultyEncoder tests that temporary I/O errors are not fatal.
    96  func TestFaultyEncoder(t *testing.T) {
    97  	for _, td := range coderTestdata {
    98  		for _, typeName := range []string{"Token", "Value"} {
    99  			t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) {
   100  				testFaultyEncoder(t, td.name.Where, typeName, td)
   101  			})
   102  		}
   103  	}
   104  }
   105  func testFaultyEncoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) {
   106  	b := &FaultyBuffer{
   107  		MaxBytes: 1,
   108  		MayError: io.ErrShortWrite,
   109  	}
   110  
   111  	// Write all the tokens.
   112  	// Even if the underlying io.Writer may be faulty,
   113  	// writing a valid token or value is guaranteed to at least
   114  	// be appended to the internal buffer.
   115  	// In other words, syntactic errors occur before I/O errors.
   116  	enc := NewEncoder(b)
   117  	switch typeName {
   118  	case "Token":
   119  		for i, tok := range td.tokens {
   120  			err := enc.WriteToken(tok)
   121  			if err != nil && !errors.Is(err, io.ErrShortWrite) {
   122  				t.Fatalf("%s: %d: Encoder.WriteToken error: %v", where, i, err)
   123  			}
   124  		}
   125  	case "Value":
   126  		err := enc.WriteValue(Value(td.in))
   127  		if err != nil && !errors.Is(err, io.ErrShortWrite) {
   128  			t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
   129  		}
   130  	}
   131  	gotOutput := string(append(b.B, enc.s.unflushedBuffer()...))
   132  	wantOutput := td.outCompacted + "\n"
   133  	if gotOutput != wantOutput {
   134  		t.Fatalf("%s: output mismatch:\ngot  %s\nwant %s", where, gotOutput, wantOutput)
   135  	}
   136  }
   137  
   138  type encoderMethodCall struct {
   139  	in          tokOrVal
   140  	wantErr     error
   141  	wantPointer Pointer
   142  }
   143  
   144  var encoderErrorTestdata = []struct {
   145  	name    jsontest.CaseName
   146  	opts    []Options
   147  	calls   []encoderMethodCall
   148  	wantOut string
   149  }{{
   150  	name: jsontest.Name("InvalidToken"),
   151  	calls: []encoderMethodCall{
   152  		{zeroToken, E(errInvalidToken), ""},
   153  	},
   154  }, {
   155  	name: jsontest.Name("InvalidValue"),
   156  	calls: []encoderMethodCall{
   157  		{Value(`#`), newInvalidCharacterError("#", "at start of value"), ""},
   158  	},
   159  }, {
   160  	name: jsontest.Name("InvalidValue/DoubleZero"),
   161  	calls: []encoderMethodCall{
   162  		{Value(`00`), newInvalidCharacterError("0", "after top-level value").withPos(`0`, ""), ""},
   163  	},
   164  }, {
   165  	name: jsontest.Name("TruncatedValue"),
   166  	calls: []encoderMethodCall{
   167  		{zeroValue, E(io.ErrUnexpectedEOF).withPos("", ""), ""},
   168  	},
   169  }, {
   170  	name: jsontest.Name("TruncatedNull"),
   171  	calls: []encoderMethodCall{
   172  		{Value(`nul`), E(io.ErrUnexpectedEOF).withPos("nul", ""), ""},
   173  	},
   174  }, {
   175  	name: jsontest.Name("InvalidNull"),
   176  	calls: []encoderMethodCall{
   177  		{Value(`nulL`), newInvalidCharacterError("L", "in literal null (expecting 'l')").withPos(`nul`, ""), ""},
   178  	},
   179  }, {
   180  	name: jsontest.Name("TruncatedFalse"),
   181  	calls: []encoderMethodCall{
   182  		{Value(`fals`), E(io.ErrUnexpectedEOF).withPos("fals", ""), ""},
   183  	},
   184  }, {
   185  	name: jsontest.Name("InvalidFalse"),
   186  	calls: []encoderMethodCall{
   187  		{Value(`falsE`), newInvalidCharacterError("E", "in literal false (expecting 'e')").withPos(`fals`, ""), ""},
   188  	},
   189  }, {
   190  	name: jsontest.Name("TruncatedTrue"),
   191  	calls: []encoderMethodCall{
   192  		{Value(`tru`), E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""},
   193  	},
   194  }, {
   195  	name: jsontest.Name("InvalidTrue"),
   196  	calls: []encoderMethodCall{
   197  		{Value(`truE`), newInvalidCharacterError("E", "in literal true (expecting 'e')").withPos(`tru`, ""), ""},
   198  	},
   199  }, {
   200  	name: jsontest.Name("TruncatedString"),
   201  	calls: []encoderMethodCall{
   202  		{Value(`"star`), E(io.ErrUnexpectedEOF).withPos(`"star`, ""), ""},
   203  	},
   204  }, {
   205  	name: jsontest.Name("InvalidString"),
   206  	calls: []encoderMethodCall{
   207  		{Value(`"ok` + "\x00"), newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""},
   208  	},
   209  }, {
   210  	name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"),
   211  	opts: []Options{AllowInvalidUTF8(true)},
   212  	calls: []encoderMethodCall{
   213  		{String("living\xde\xad\xbe\xef"), nil, ""},
   214  	},
   215  	wantOut: "\"living\xde\xad\ufffd\ufffd\"\n",
   216  }, {
   217  	name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"),
   218  	opts: []Options{AllowInvalidUTF8(true)},
   219  	calls: []encoderMethodCall{
   220  		{Value("\"living\xde\xad\xbe\xef\""), nil, ""},
   221  	},
   222  	wantOut: "\"living\xde\xad\ufffd\ufffd\"\n",
   223  }, {
   224  	name: jsontest.Name("InvalidString/RejectInvalidUTF8"),
   225  	opts: []Options{AllowInvalidUTF8(false)},
   226  	calls: []encoderMethodCall{
   227  		{String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8), ""},
   228  		{Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""},
   229  		{BeginObject, nil, ""},
   230  		{String("name"), nil, ""},
   231  		{BeginArray, nil, ""},
   232  		{String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8).withPos(`{"name":[`, "/name/0"), ""},
   233  		{Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("{\"name\":[\"living\xde\xad", "/name/0"), ""},
   234  	},
   235  	wantOut: `{"name":[`,
   236  }, {
   237  	name: jsontest.Name("TruncatedNumber"),
   238  	calls: []encoderMethodCall{
   239  		{Value(`0.`), E(io.ErrUnexpectedEOF).withPos("0", ""), ""},
   240  	},
   241  }, {
   242  	name: jsontest.Name("InvalidNumber"),
   243  	calls: []encoderMethodCall{
   244  		{Value(`0.e`), newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""},
   245  	},
   246  }, {
   247  	name: jsontest.Name("TruncatedObject/AfterStart"),
   248  	calls: []encoderMethodCall{
   249  		{Value(`{`), E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
   250  	},
   251  }, {
   252  	name: jsontest.Name("TruncatedObject/AfterName"),
   253  	calls: []encoderMethodCall{
   254  		{Value(`{"X"`), E(io.ErrUnexpectedEOF).withPos(`{"X"`, "/X"), ""},
   255  	},
   256  }, {
   257  	name: jsontest.Name("TruncatedObject/AfterColon"),
   258  	calls: []encoderMethodCall{
   259  		{Value(`{"X":`), E(io.ErrUnexpectedEOF).withPos(`{"X":`, "/X"), ""},
   260  	},
   261  }, {
   262  	name: jsontest.Name("TruncatedObject/AfterValue"),
   263  	calls: []encoderMethodCall{
   264  		{Value(`{"0":0`), E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
   265  	},
   266  }, {
   267  	name: jsontest.Name("TruncatedObject/AfterComma"),
   268  	calls: []encoderMethodCall{
   269  		{Value(`{"0":0,`), E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
   270  	},
   271  }, {
   272  	name: jsontest.Name("InvalidObject/MissingColon"),
   273  	calls: []encoderMethodCall{
   274  		{Value(` { "fizz" "buzz" } `), newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   275  		{Value(` { "fizz" , "buzz" } `), newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   276  	},
   277  }, {
   278  	name: jsontest.Name("InvalidObject/MissingComma"),
   279  	calls: []encoderMethodCall{
   280  		{Value(` { "fizz" : "buzz" "gazz" } `), newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   281  		{Value(` { "fizz" : "buzz" : "gazz" } `), newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   282  	},
   283  }, {
   284  	name: jsontest.Name("InvalidObject/ExtraComma"),
   285  	calls: []encoderMethodCall{
   286  		{Value(` { , } `), newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""},
   287  		{Value(` { "fizz" : "buzz" , } `), newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""},
   288  	},
   289  }, {
   290  	name: jsontest.Name("InvalidObject/InvalidName"),
   291  	calls: []encoderMethodCall{
   292  		{Value(`{ null }`), newInvalidCharacterError("n", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
   293  		{Value(`{ false }`), newInvalidCharacterError("f", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
   294  		{Value(`{ true }`), newInvalidCharacterError("t", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
   295  		{Value(`{ 0 }`), newInvalidCharacterError("0", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
   296  		{Value(`{ {} }`), newInvalidCharacterError("{", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
   297  		{Value(`{ [] }`), newInvalidCharacterError("[", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
   298  		{BeginObject, nil, ""},
   299  		{Null, E(ErrNonStringName).withPos(`{`, ""), ""},
   300  		{Value(`null`), E(ErrNonStringName).withPos(`{`, ""), ""},
   301  		{False, E(ErrNonStringName).withPos(`{`, ""), ""},
   302  		{Value(`false`), E(ErrNonStringName).withPos(`{`, ""), ""},
   303  		{True, E(ErrNonStringName).withPos(`{`, ""), ""},
   304  		{Value(`true`), E(ErrNonStringName).withPos(`{`, ""), ""},
   305  		{Uint(0), E(ErrNonStringName).withPos(`{`, ""), ""},
   306  		{Value(`0`), E(ErrNonStringName).withPos(`{`, ""), ""},
   307  		{BeginObject, E(ErrNonStringName).withPos(`{`, ""), ""},
   308  		{Value(`{}`), E(ErrNonStringName).withPos(`{`, ""), ""},
   309  		{BeginArray, E(ErrNonStringName).withPos(`{`, ""), ""},
   310  		{Value(`[]`), E(ErrNonStringName).withPos(`{`, ""), ""},
   311  		{EndObject, nil, ""},
   312  	},
   313  	wantOut: "{}\n",
   314  }, {
   315  	name: jsontest.Name("InvalidObject/InvalidValue"),
   316  	calls: []encoderMethodCall{
   317  		{Value(`{ "0": x }`), newInvalidCharacterError("x", `at start of value`).withPos(`{ "0": `, "/0"), ""},
   318  	},
   319  }, {
   320  	name: jsontest.Name("InvalidObject/MismatchingDelim"),
   321  	calls: []encoderMethodCall{
   322  		{Value(` { ] `), newInvalidCharacterError("]", `at start of string (expecting '"')`).withPos(` { `, ""), ""},
   323  		{Value(` { "0":0 ] `), newInvalidCharacterError("]", `after object value (expecting ',' or '}')`).withPos(` { "0":0 `, ""), ""},
   324  		{BeginObject, nil, ""},
   325  		{EndArray, E(errMismatchDelim).withPos(`{`, ""), ""},
   326  		{Value(`]`), newInvalidCharacterError("]", "at start of value").withPos(`{`, ""), ""},
   327  		{EndObject, nil, ""},
   328  	},
   329  	wantOut: "{}\n",
   330  }, {
   331  	name: jsontest.Name("ValidObject/UniqueNames"),
   332  	calls: []encoderMethodCall{
   333  		{BeginObject, nil, ""},
   334  		{String("0"), nil, ""},
   335  		{Uint(0), nil, ""},
   336  		{String("1"), nil, ""},
   337  		{Uint(1), nil, ""},
   338  		{EndObject, nil, ""},
   339  		{Value(` { "0" : 0 , "1" : 1 } `), nil, ""},
   340  	},
   341  	wantOut: `{"0":0,"1":1}` + "\n" + `{"0":0,"1":1}` + "\n",
   342  }, {
   343  	name: jsontest.Name("ValidObject/DuplicateNames"),
   344  	opts: []Options{AllowDuplicateNames(true)},
   345  	calls: []encoderMethodCall{
   346  		{BeginObject, nil, ""},
   347  		{String("0"), nil, ""},
   348  		{Uint(0), nil, ""},
   349  		{String("0"), nil, ""},
   350  		{Uint(0), nil, ""},
   351  		{EndObject, nil, ""},
   352  		{Value(` { "0" : 0 , "0" : 0 } `), nil, ""},
   353  	},
   354  	wantOut: `{"0":0,"0":0}` + "\n" + `{"0":0,"0":0}` + "\n",
   355  }, {
   356  	name: jsontest.Name("InvalidObject/DuplicateNames"),
   357  	calls: []encoderMethodCall{
   358  		{BeginObject, nil, ""},
   359  		{String("X"), nil, ""},
   360  		{BeginObject, nil, ""},
   361  		{EndObject, nil, ""},
   362  		{String("X"), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"},
   363  		{Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"},
   364  		{String("Y"), nil, ""},
   365  		{BeginObject, nil, ""},
   366  		{EndObject, nil, ""},
   367  		{String("X"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"},
   368  		{Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"},
   369  		{String("Y"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"},
   370  		{Value(`"Y"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"},
   371  		{EndObject, nil, ""},
   372  		{Value(` { "X" : 0 , "Y" : 1 , "X" : 0 } `), E(ErrDuplicateName).withPos(`{"X":{},"Y":{}}`+"\n"+` { "X" : 0 , "Y" : 1 , `, "/X"), ""},
   373  	},
   374  	wantOut: `{"X":{},"Y":{}}` + "\n",
   375  }, {
   376  	name: jsontest.Name("TruncatedArray/AfterStart"),
   377  	calls: []encoderMethodCall{
   378  		{Value(`[`), E(io.ErrUnexpectedEOF).withPos(`[`, ""), ""},
   379  	},
   380  }, {
   381  	name: jsontest.Name("TruncatedArray/AfterValue"),
   382  	calls: []encoderMethodCall{
   383  		{Value(`[0`), E(io.ErrUnexpectedEOF).withPos(`[0`, ""), ""},
   384  	},
   385  }, {
   386  	name: jsontest.Name("TruncatedArray/AfterComma"),
   387  	calls: []encoderMethodCall{
   388  		{Value(`[0,`), E(io.ErrUnexpectedEOF).withPos(`[0,`, ""), ""},
   389  	},
   390  }, {
   391  	name: jsontest.Name("TruncatedArray/MissingComma"),
   392  	calls: []encoderMethodCall{
   393  		{Value(` [ "fizz" "buzz" ] `), newInvalidCharacterError("\"", "after array value (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
   394  	},
   395  }, {
   396  	name: jsontest.Name("InvalidArray/MismatchingDelim"),
   397  	calls: []encoderMethodCall{
   398  		{Value(` [ } `), newInvalidCharacterError("}", `at start of value`).withPos(` [ `, "/0"), ""},
   399  		{BeginArray, nil, ""},
   400  		{EndObject, E(errMismatchDelim).withPos(`[`, "/0"), ""},
   401  		{Value(`}`), newInvalidCharacterError("}", "at start of value").withPos(`[`, "/0"), ""},
   402  		{EndArray, nil, ""},
   403  	},
   404  	wantOut: "[]\n",
   405  }, {
   406  	name:    jsontest.Name("Format/Object/SpaceAfterColon"),
   407  	opts:    []Options{SpaceAfterColon(true)},
   408  	calls:   []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
   409  	wantOut: "{\"fizz\": \"buzz\",\"wizz\": \"wuzz\"}\n",
   410  }, {
   411  	name:    jsontest.Name("Format/Object/SpaceAfterComma"),
   412  	opts:    []Options{SpaceAfterComma(true)},
   413  	calls:   []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
   414  	wantOut: "{\"fizz\":\"buzz\", \"wizz\":\"wuzz\"}\n",
   415  }, {
   416  	name:    jsontest.Name("Format/Object/SpaceAfterColonAndComma"),
   417  	opts:    []Options{SpaceAfterColon(true), SpaceAfterComma(true)},
   418  	calls:   []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
   419  	wantOut: "{\"fizz\": \"buzz\", \"wizz\": \"wuzz\"}\n",
   420  }, {
   421  	name:    jsontest.Name("Format/Object/NoSpaceAfterColon+SpaceAfterComma+Multiline"),
   422  	opts:    []Options{SpaceAfterColon(false), SpaceAfterComma(true), Multiline(true)},
   423  	calls:   []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
   424  	wantOut: "{\n\t\"fizz\":\"buzz\", \n\t\"wizz\":\"wuzz\"\n}\n",
   425  }, {
   426  	name:    jsontest.Name("Format/Array/SpaceAfterComma"),
   427  	opts:    []Options{SpaceAfterComma(true)},
   428  	calls:   []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}},
   429  	wantOut: "[\"fizz\", \"buzz\"]\n",
   430  }, {
   431  	name:    jsontest.Name("Format/Array/NoSpaceAfterComma+Multiline"),
   432  	opts:    []Options{SpaceAfterComma(false), Multiline(true)},
   433  	calls:   []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}},
   434  	wantOut: "[\n\t\"fizz\",\n\t\"buzz\"\n]\n",
   435  }, {
   436  	name: jsontest.Name("Format/ReorderWithWhitespace"),
   437  	opts: []Options{
   438  		AllowDuplicateNames(true),
   439  		AllowInvalidUTF8(true),
   440  		ReorderRawObjects(true),
   441  		SpaceAfterComma(true),
   442  		SpaceAfterColon(false),
   443  		Multiline(true),
   444  		WithIndentPrefix("    "),
   445  		WithIndent("\t"),
   446  		PreserveRawStrings(true),
   447  	},
   448  	calls: []encoderMethodCall{
   449  		{BeginArray, nil, ""},
   450  		{BeginArray, nil, ""},
   451  		{Value(` { "fizz" : "buzz" ,
   452  			"zip" : {
   453  				"x` + "\xfd" + `x" : 123 , "x` + "\xff" + `x" : 123, "x` + "\xfe" + `x" : 123
   454  			},
   455  			"zap" : {
   456  				"xxx" : 333, "xxx": 1, "xxx": 22
   457  			},
   458  			"alpha" : "bravo" } `), nil, ""},
   459  		{EndArray, nil, ""},
   460  		{EndArray, nil, ""},
   461  	},
   462  	wantOut: "[\n    \t[\n    \t\t{\n    \t\t\t\"alpha\":\"bravo\", \n    \t\t\t\"fizz\":\"buzz\", \n    \t\t\t\"zap\":{\n    \t\t\t\t\"xxx\":1, \n    \t\t\t\t\"xxx\":22, \n    \t\t\t\t\"xxx\":333\n    \t\t\t}, \n    \t\t\t\"zip\":{\n    \t\t\t\t\"x\xfdx\":123, \n    \t\t\t\t\"x\xfex\":123, \n    \t\t\t\t\"x\xffx\":123\n    \t\t\t}\n    \t\t}\n    \t]\n    ]\n",
   463  }, {
   464  	name: jsontest.Name("Format/CanonicalizeRawInts"),
   465  	opts: []Options{CanonicalizeRawInts(true), SpaceAfterComma(true)},
   466  	calls: []encoderMethodCall{
   467  		{Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""},
   468  	},
   469  	wantOut: "[0.100, 5.0, 1E6, -9223372036854776000, -10, -1, 0, 0, 1, 10, 9223372036854776000]\n",
   470  }, {
   471  	name: jsontest.Name("Format/CanonicalizeRawFloats"),
   472  	opts: []Options{CanonicalizeRawFloats(true), SpaceAfterComma(true)},
   473  	calls: []encoderMethodCall{
   474  		{Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""},
   475  	},
   476  	wantOut: "[0.1, 5, 1000000, -9223372036854775808, -10, -1, 0, 0, 1, 10, 9223372036854775807]\n",
   477  }, {
   478  	name: jsontest.Name("ErrorPosition"),
   479  	calls: []encoderMethodCall{
   480  		{Value(` "a` + "\xff" + `0" `), E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""},
   481  		{String(`a` + "\xff" + `0`), E(jsonwire.ErrInvalidUTF8).withPos(``, ""), ""},
   482  	},
   483  }, {
   484  	name: jsontest.Name("ErrorPosition/0"),
   485  	calls: []encoderMethodCall{
   486  		{Value(` [ "a` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
   487  		{BeginArray, nil, ""},
   488  		{Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`[ "a`, "/0"), ""},
   489  		{String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`[`, "/0"), ""},
   490  	},
   491  	wantOut: `[`,
   492  }, {
   493  	name: jsontest.Name("ErrorPosition/1"),
   494  	calls: []encoderMethodCall{
   495  		{Value(` [ "a1" , "b` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
   496  		{BeginArray, nil, ""},
   497  		{String("a1"), nil, ""},
   498  		{Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", "b`, "/1"), ""},
   499  		{String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",`, "/1"), ""},
   500  	},
   501  	wantOut: `["a1"`,
   502  }, {
   503  	name: jsontest.Name("ErrorPosition/0/0"),
   504  	calls: []encoderMethodCall{
   505  		{Value(` [ [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
   506  		{BeginArray, nil, ""},
   507  		{Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a`, "/0/0"), ""},
   508  		{BeginArray, nil, "/0"},
   509  		{Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[[ "a`, "/0/0"), "/0"},
   510  		{String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[[`, "/0/0"), "/0"},
   511  	},
   512  	wantOut: `[[`,
   513  }, {
   514  	name: jsontest.Name("ErrorPosition/1/0"),
   515  	calls: []encoderMethodCall{
   516  		{Value(` [ "a1" , [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""},
   517  		{BeginArray, nil, ""},
   518  		{String("a1"), nil, "/0"},
   519  		{Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a`, "/1/0"), ""},
   520  		{BeginArray, nil, "/1"},
   521  		{Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[ "a`, "/1/0"), "/1"},
   522  		{String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[`, "/1/0"), "/1"},
   523  	},
   524  	wantOut: `["a1",[`,
   525  }, {
   526  	name: jsontest.Name("ErrorPosition/0/1"),
   527  	calls: []encoderMethodCall{
   528  		{Value(` [ [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""},
   529  		{BeginArray, nil, ""},
   530  		{Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a2" , "b`, "/0/1"), ""},
   531  		{BeginArray, nil, "/0"},
   532  		{String("a2"), nil, "/0/0"},
   533  		{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2", "b`, "/0/1"), "/0/0"},
   534  		{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2",`, "/0/1"), "/0/0"},
   535  	},
   536  	wantOut: `[["a2"`,
   537  }, {
   538  	name: jsontest.Name("ErrorPosition/1/1"),
   539  	calls: []encoderMethodCall{
   540  		{Value(` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""},
   541  		{BeginArray, nil, ""},
   542  		{String("a1"), nil, "/0"},
   543  		{Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a2" , "b`, "/1/1"), ""},
   544  		{BeginArray, nil, "/1"},
   545  		{String("a2"), nil, "/1/0"},
   546  		{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2", "b`, "/1/1"), "/1/0"},
   547  		{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2",`, "/1/1"), "/1/0"},
   548  	},
   549  	wantOut: `["a1",["a2"`,
   550  }, {
   551  	name: jsontest.Name("ErrorPosition/a1-"),
   552  	calls: []encoderMethodCall{
   553  		{Value(` { "a` + "\xff" + `1" : "b1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
   554  		{BeginObject, nil, ""},
   555  		{Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{ "a`, ""), ""},
   556  		{String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{`, ""), ""},
   557  	},
   558  	wantOut: `{`,
   559  }, {
   560  	name: jsontest.Name("ErrorPosition/a1"),
   561  	calls: []encoderMethodCall{
   562  		{Value(` { "a1" : "b` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
   563  		{BeginObject, nil, ""},
   564  		{String("a1"), nil, "/a1"},
   565  		{Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": "b`, "/a1"), ""},
   566  		{String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":`, "/a1"), ""},
   567  	},
   568  	wantOut: `{"a1"`,
   569  }, {
   570  	name: jsontest.Name("ErrorPosition/c1-"),
   571  	calls: []encoderMethodCall{
   572  		{Value(` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""},
   573  		{BeginObject, nil, ""},
   574  		{String("a1"), nil, "/a1"},
   575  		{String("b1"), nil, "/a1"},
   576  		{Value(` "c` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1": "c`, ""), "/a1"},
   577  		{String(`c` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":`, ""), "/a1"},
   578  	},
   579  	wantOut: `{"a1":"b1"`,
   580  }, {
   581  	name: jsontest.Name("ErrorPosition/c1"),
   582  	calls: []encoderMethodCall{
   583  		{Value(` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""},
   584  		{BeginObject, nil, ""},
   585  		{String("a1"), nil, "/a1"},
   586  		{String("b1"), nil, "/a1"},
   587  		{String("c1"), nil, "/c1"},
   588  		{Value(` "d` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1": "d`, "/c1"), "/c1"},
   589  		{String(`d` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1":`, "/c1"), "/c1"},
   590  	},
   591  	wantOut: `{"a1":"b1","c1"`,
   592  }, {
   593  	name: jsontest.Name("ErrorPosition/a1/a2-"),
   594  	calls: []encoderMethodCall{
   595  		{Value(` { "a1" : { "a` + "\xff" + `2" : "b2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""},
   596  		{BeginObject, nil, ""},
   597  		{String("a1"), nil, "/a1"},
   598  		{Value(` { "a` + "\xff" + `2" : "b2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a`, "/a1"), ""},
   599  		{BeginObject, nil, "/a1"},
   600  		{Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{ "a`, "/a1"), "/a1"},
   601  		{String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{`, "/a1"), "/a1"},
   602  	},
   603  	wantOut: `{"a1":{`,
   604  }, {
   605  	name: jsontest.Name("ErrorPosition/a1/a2"),
   606  	calls: []encoderMethodCall{
   607  		{Value(` { "a1" : { "a2" : "b` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""},
   608  		{BeginObject, nil, ""},
   609  		{String("a1"), nil, "/a1"},
   610  		{Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b`, "/a1/a2"), ""},
   611  		{BeginObject, nil, "/a1"},
   612  		{String("a2"), nil, "/a1/a2"},
   613  		{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2": "b`, "/a1/a2"), "/a1/a2"},
   614  		{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":`, "/a1/a2"), "/a1/a2"},
   615  	},
   616  	wantOut: `{"a1":{"a2"`,
   617  }, {
   618  	name: jsontest.Name("ErrorPosition/a1/c2-"),
   619  	calls: []encoderMethodCall{
   620  		{Value(` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""},
   621  		{BeginObject, nil, ""},
   622  		{String("a1"), nil, "/a1"},
   623  		{BeginObject, nil, "/a1"},
   624  		{String("a2"), nil, "/a1/a2"},
   625  		{String("b2"), nil, "/a1/a2"},
   626  		{Value(` "c` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2", "c`, "/a1"), "/a1/a2"},
   627  		{String(`c` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2",`, "/a1"), "/a1/a2"},
   628  	},
   629  	wantOut: `{"a1":{"a2":"b2"`,
   630  }, {
   631  	name: jsontest.Name("ErrorPosition/a1/c2"),
   632  	calls: []encoderMethodCall{
   633  		{Value(` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
   634  		{BeginObject, nil, ""},
   635  		{String("a1"), nil, "/a1"},
   636  		{Value(` { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
   637  		{BeginObject, nil, ""},
   638  		{String("a2"), nil, "/a1/a2"},
   639  		{String("b2"), nil, "/a1/a2"},
   640  		{String("c2"), nil, "/a1/c2"},
   641  		{Value(` "d` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2": "d`, "/a1/c2"), "/a1/c2"},
   642  		{String(`d` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2":`, "/a1/c2"), "/a1/c2"},
   643  	},
   644  	wantOut: `{"a1":{"a2":"b2","c2"`,
   645  }, {
   646  	name: jsontest.Name("ErrorPosition/1/a2"),
   647  	calls: []encoderMethodCall{
   648  		{Value(` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""},
   649  		{BeginArray, nil, ""},
   650  		{String("a1"), nil, "/0"},
   651  		{Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", { "a2" : "b`, "/1/a2"), ""},
   652  		{BeginObject, nil, "/1"},
   653  		{String("a2"), nil, "/1/a2"},
   654  		{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2": "b`, "/1/a2"), "/1/a2"},
   655  		{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2":`, "/1/a2"), "/1/a2"},
   656  	},
   657  	wantOut: `["a1",{"a2"`,
   658  }, {
   659  	name: jsontest.Name("ErrorPosition/c1/1"),
   660  	calls: []encoderMethodCall{
   661  		{Value(` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""},
   662  		{BeginObject, nil, ""},
   663  		{String("a1"), nil, "/a1"},
   664  		{String("b1"), nil, "/a1"},
   665  		{String("c1"), nil, "/c1"},
   666  		{Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1": [ "a2" , "b`, "/c1/1"), ""},
   667  		{BeginArray, nil, "/c1"},
   668  		{String("a2"), nil, "/c1/0"},
   669  		{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2", "b`, "/c1/1"), "/c1/0"},
   670  		{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2",`, "/c1/1"), "/c1/0"},
   671  	},
   672  	wantOut: `{"a1":"b1","c1":["a2"`,
   673  }, {
   674  	name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"),
   675  	calls: []encoderMethodCall{
   676  		{Value(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   677  		{BeginArray, nil, ""},
   678  		{Value(` { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   679  		{BeginObject, nil, "/0"},
   680  		{String("a1"), nil, "/0/a1"},
   681  		{Value(` [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1": [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   682  		{BeginArray, nil, ""},
   683  		{String("a2"), nil, "/0/a1/0"},
   684  		{Value(` { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2", { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   685  		{BeginObject, nil, "/0/a1/1"},
   686  		{String("a3"), nil, "/0/a1/1/a3"},
   687  		{String("b3"), nil, "/0/a1/1/a3"},
   688  		{String("c3"), nil, "/0/a1/1/c3"},
   689  		{Value(` [ "a4" , "b` + "\xff" + `4" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3": [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   690  		{BeginArray, nil, "/0/a1/1/c3"},
   691  		{String("a4"), nil, "/0/a1/1/c3/0"},
   692  		{Value(` "b` + "\xff" + `4" `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4", "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
   693  		{String(`b` + "\xff" + `4`), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4",`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
   694  	},
   695  	wantOut: `[{"a1":["a2",{"a3":"b3","c3":["a4"`,
   696  }}
   697  
   698  // TestEncoderErrors test that Encoder errors occur when we expect and
   699  // leaves the Encoder in a consistent state.
   700  func TestEncoderErrors(t *testing.T) {
   701  	for _, td := range encoderErrorTestdata {
   702  		t.Run(path.Join(td.name.Name), func(t *testing.T) {
   703  			testEncoderErrors(t, td.name.Where, td.opts, td.calls, td.wantOut)
   704  		})
   705  	}
   706  }
   707  func testEncoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, calls []encoderMethodCall, wantOut string) {
   708  	dst := new(bytes.Buffer)
   709  	enc := NewEncoder(dst, opts...)
   710  	for i, call := range calls {
   711  		var gotErr error
   712  		switch tokVal := call.in.(type) {
   713  		case Token:
   714  			gotErr = enc.WriteToken(tokVal)
   715  		case Value:
   716  			gotErr = enc.WriteValue(tokVal)
   717  		}
   718  		if !equalError(gotErr, call.wantErr) {
   719  			t.Fatalf("%s: %d: error mismatch:\ngot  %v\nwant %v", where, i, gotErr, call.wantErr)
   720  		}
   721  		if call.wantPointer != "" {
   722  			gotPointer := enc.StackPointer()
   723  			if gotPointer != call.wantPointer {
   724  				t.Fatalf("%s: %d: Encoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer)
   725  			}
   726  		}
   727  	}
   728  	gotOut := dst.String() + string(enc.s.unflushedBuffer())
   729  	if gotOut != wantOut {
   730  		t.Fatalf("%s: output mismatch:\ngot  %q\nwant %q", where, gotOut, wantOut)
   731  	}
   732  	gotOffset := int(enc.OutputOffset())
   733  	wantOffset := len(wantOut)
   734  	if gotOffset != wantOffset {
   735  		t.Fatalf("%s: Encoder.OutputOffset = %v, want %v", where, gotOffset, wantOffset)
   736  	}
   737  }
   738  

View as plain text