Source file src/encoding/json/stream_test.go

     1  // Copyright 2010 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
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"net"
    15  	"net/http"
    16  	"net/http/httptest"
    17  	"path"
    18  	"reflect"
    19  	"runtime"
    20  	"runtime/debug"
    21  	"strings"
    22  	"testing"
    23  )
    24  
    25  // TODO(https://go.dev/issue/52751): Replace with native testing support.
    26  
    27  // CaseName is a case name annotated with a file and line.
    28  type CaseName struct {
    29  	Name  string
    30  	Where CasePos
    31  }
    32  
    33  // Name annotates a case name with the file and line of the caller.
    34  func Name(s string) (c CaseName) {
    35  	c.Name = s
    36  	runtime.Callers(2, c.Where.pc[:])
    37  	return c
    38  }
    39  
    40  // CasePos represents a file and line number.
    41  type CasePos struct{ pc [1]uintptr }
    42  
    43  func (pos CasePos) String() string {
    44  	frames := runtime.CallersFrames(pos.pc[:])
    45  	frame, _ := frames.Next()
    46  	return fmt.Sprintf("%s:%d", path.Base(frame.File), frame.Line)
    47  }
    48  
    49  // Test values for the stream test.
    50  // One of each JSON kind.
    51  var streamTest = []any{
    52  	0.1,
    53  	"hello",
    54  	nil,
    55  	true,
    56  	false,
    57  	[]any{"a", "b", "c"},
    58  	map[string]any{"K": "Kelvin", "ß": "long s"},
    59  	3.14, // another value to make sure something can follow map
    60  }
    61  
    62  var streamEncoded = `0.1
    63  "hello"
    64  null
    65  true
    66  false
    67  ["a","b","c"]
    68  {"ß":"long s","K":"Kelvin"}
    69  3.14
    70  `
    71  
    72  func TestEncoder(t *testing.T) {
    73  	for i := 0; i <= len(streamTest); i++ {
    74  		var buf strings.Builder
    75  		enc := NewEncoder(&buf)
    76  		// Check that enc.SetIndent("", "") turns off indentation.
    77  		enc.SetIndent(">", ".")
    78  		enc.SetIndent("", "")
    79  		for j, v := range streamTest[0:i] {
    80  			if err := enc.Encode(v); err != nil {
    81  				t.Fatalf("#%d.%d Encode error: %v", i, j, err)
    82  			}
    83  		}
    84  		if got, want := buf.String(), nlines(streamEncoded, i); got != want {
    85  			t.Errorf("encoding %d items: mismatch:", i)
    86  			diff(t, []byte(got), []byte(want))
    87  			break
    88  		}
    89  	}
    90  }
    91  
    92  func TestEncoderErrorAndReuseEncodeState(t *testing.T) {
    93  	// Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test.
    94  	percent := debug.SetGCPercent(-1)
    95  	defer debug.SetGCPercent(percent)
    96  
    97  	// Trigger an error in Marshal with cyclic data.
    98  	type Dummy struct {
    99  		Name string
   100  		Next *Dummy
   101  	}
   102  	dummy := Dummy{Name: "Dummy"}
   103  	dummy.Next = &dummy
   104  
   105  	var buf bytes.Buffer
   106  	enc := NewEncoder(&buf)
   107  	if err := enc.Encode(dummy); err == nil {
   108  		t.Errorf("Encode(dummy) error: got nil, want non-nil")
   109  	}
   110  
   111  	type Data struct {
   112  		A string
   113  		I int
   114  	}
   115  	want := Data{A: "a", I: 1}
   116  	if err := enc.Encode(want); err != nil {
   117  		t.Errorf("Marshal error: %v", err)
   118  	}
   119  
   120  	var got Data
   121  	if err := Unmarshal(buf.Bytes(), &got); err != nil {
   122  		t.Errorf("Unmarshal error: %v", err)
   123  	}
   124  	if got != want {
   125  		t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot:  %v\n\twant: %v", got, want)
   126  	}
   127  }
   128  
   129  var streamEncodedIndent = `0.1
   130  "hello"
   131  null
   132  true
   133  false
   134  [
   135  >."a",
   136  >."b",
   137  >."c"
   138  >]
   139  {
   140  >."ß": "long s",
   141  >."K": "Kelvin"
   142  >}
   143  3.14
   144  `
   145  
   146  func TestEncoderIndent(t *testing.T) {
   147  	var buf strings.Builder
   148  	enc := NewEncoder(&buf)
   149  	enc.SetIndent(">", ".")
   150  	for _, v := range streamTest {
   151  		enc.Encode(v)
   152  	}
   153  	if got, want := buf.String(), streamEncodedIndent; got != want {
   154  		t.Errorf("Encode mismatch:\ngot:\n%s\n\nwant:\n%s", got, want)
   155  		diff(t, []byte(got), []byte(want))
   156  	}
   157  }
   158  
   159  type strMarshaler string
   160  
   161  func (s strMarshaler) MarshalJSON() ([]byte, error) {
   162  	return []byte(s), nil
   163  }
   164  
   165  type strPtrMarshaler string
   166  
   167  func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) {
   168  	return []byte(*s), nil
   169  }
   170  
   171  func TestEncoderSetEscapeHTML(t *testing.T) {
   172  	var c C
   173  	var ct CText
   174  	var tagStruct struct {
   175  		Valid   int `json:"<>&#! "`
   176  		Invalid int `json:"\\"`
   177  	}
   178  
   179  	// This case is particularly interesting, as we force the encoder to
   180  	// take the address of the Ptr field to use its MarshalJSON method. This
   181  	// is why the '&' is important.
   182  	marshalerStruct := &struct {
   183  		NonPtr strMarshaler
   184  		Ptr    strPtrMarshaler
   185  	}{`"<str>"`, `"<str>"`}
   186  
   187  	// https://golang.org/issue/34154
   188  	stringOption := struct {
   189  		Bar string `json:"bar,string"`
   190  	}{`<html>foobar</html>`}
   191  
   192  	tests := []struct {
   193  		CaseName
   194  		v          any
   195  		wantEscape string
   196  		want       string
   197  	}{
   198  		{Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`},
   199  		{Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
   200  		{Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
   201  		{
   202  			Name("tagStruct"), tagStruct,
   203  			`{"\u003c\u003e\u0026#! ":0,"Invalid":0}`,
   204  			`{"<>&#! ":0,"Invalid":0}`,
   205  		},
   206  		{
   207  			Name(`"<str>"`), marshalerStruct,
   208  			`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`,
   209  			`{"NonPtr":"<str>","Ptr":"<str>"}`,
   210  		},
   211  		{
   212  			Name("stringOption"), stringOption,
   213  			`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`,
   214  			`{"bar":"\"<html>foobar</html>\""}`,
   215  		},
   216  	}
   217  	for _, tt := range tests {
   218  		t.Run(tt.Name, func(t *testing.T) {
   219  			var buf strings.Builder
   220  			enc := NewEncoder(&buf)
   221  			if err := enc.Encode(tt.v); err != nil {
   222  				t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err)
   223  			}
   224  			if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
   225  				t.Errorf("%s: Encode(%s):\n\tgot:  %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape)
   226  			}
   227  			buf.Reset()
   228  			enc.SetEscapeHTML(false)
   229  			if err := enc.Encode(tt.v); err != nil {
   230  				t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err)
   231  			}
   232  			if got := strings.TrimSpace(buf.String()); got != tt.want {
   233  				t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot:  %s\n\twant: %s",
   234  					tt.Where, tt.Name, got, tt.want)
   235  			}
   236  		})
   237  	}
   238  }
   239  
   240  func TestDecoder(t *testing.T) {
   241  	for i := 0; i <= len(streamTest); i++ {
   242  		// Use stream without newlines as input,
   243  		// just to stress the decoder even more.
   244  		// Our test input does not include back-to-back numbers.
   245  		// Otherwise stripping the newlines would
   246  		// merge two adjacent JSON values.
   247  		var buf bytes.Buffer
   248  		for _, c := range nlines(streamEncoded, i) {
   249  			if c != '\n' {
   250  				buf.WriteRune(c)
   251  			}
   252  		}
   253  		out := make([]any, i)
   254  		dec := NewDecoder(&buf)
   255  		for j := range out {
   256  			if err := dec.Decode(&out[j]); err != nil {
   257  				t.Fatalf("decode #%d/%d error: %v", j, i, err)
   258  			}
   259  		}
   260  		if !reflect.DeepEqual(out, streamTest[0:i]) {
   261  			t.Errorf("decoding %d items: mismatch:", i)
   262  			for j := range out {
   263  				if !reflect.DeepEqual(out[j], streamTest[j]) {
   264  					t.Errorf("#%d:\n\tgot:  %v\n\twant: %v", j, out[j], streamTest[j])
   265  				}
   266  			}
   267  			break
   268  		}
   269  	}
   270  }
   271  
   272  func TestDecoderBuffered(t *testing.T) {
   273  	r := strings.NewReader(`{"Name": "Gopher"} extra `)
   274  	var m struct {
   275  		Name string
   276  	}
   277  	d := NewDecoder(r)
   278  	err := d.Decode(&m)
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  	if m.Name != "Gopher" {
   283  		t.Errorf("Name = %s, want Gopher", m.Name)
   284  	}
   285  	rest, err := io.ReadAll(d.Buffered())
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	if got, want := string(rest), " extra "; got != want {
   290  		t.Errorf("Remaining = %s, want %s", got, want)
   291  	}
   292  }
   293  
   294  func nlines(s string, n int) string {
   295  	if n <= 0 {
   296  		return ""
   297  	}
   298  	for i, c := range s {
   299  		if c == '\n' {
   300  			if n--; n == 0 {
   301  				return s[0 : i+1]
   302  			}
   303  		}
   304  	}
   305  	return s
   306  }
   307  
   308  func TestRawMessage(t *testing.T) {
   309  	var data struct {
   310  		X  float64
   311  		Id RawMessage
   312  		Y  float32
   313  	}
   314  	const raw = `["\u0056",null]`
   315  	const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
   316  	err := Unmarshal([]byte(want), &data)
   317  	if err != nil {
   318  		t.Fatalf("Unmarshal error: %v", err)
   319  	}
   320  	if string([]byte(data.Id)) != raw {
   321  		t.Fatalf("Unmarshal:\n\tgot:  %s\n\twant: %s", []byte(data.Id), raw)
   322  	}
   323  	got, err := Marshal(&data)
   324  	if err != nil {
   325  		t.Fatalf("Marshal error: %v", err)
   326  	}
   327  	if string(got) != want {
   328  		t.Fatalf("Marshal:\n\tgot:  %s\n\twant: %s", got, want)
   329  	}
   330  }
   331  
   332  func TestNullRawMessage(t *testing.T) {
   333  	var data struct {
   334  		X     float64
   335  		Id    RawMessage
   336  		IdPtr *RawMessage
   337  		Y     float32
   338  	}
   339  	const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}`
   340  	err := Unmarshal([]byte(want), &data)
   341  	if err != nil {
   342  		t.Fatalf("Unmarshal error: %v", err)
   343  	}
   344  	if want, got := "null", string(data.Id); want != got {
   345  		t.Fatalf("Unmarshal:\n\tgot:  %s\n\twant: %s", got, want)
   346  	}
   347  	if data.IdPtr != nil {
   348  		t.Fatalf("pointer mismatch: got non-nil, want nil")
   349  	}
   350  	got, err := Marshal(&data)
   351  	if err != nil {
   352  		t.Fatalf("Marshal error: %v", err)
   353  	}
   354  	if string(got) != want {
   355  		t.Fatalf("Marshal:\n\tgot:  %s\n\twant: %s", got, want)
   356  	}
   357  }
   358  
   359  func TestBlocking(t *testing.T) {
   360  	tests := []struct {
   361  		CaseName
   362  		in string
   363  	}{
   364  		{Name(""), `{"x": 1}`},
   365  		{Name(""), `[1, 2, 3]`},
   366  	}
   367  	for _, tt := range tests {
   368  		t.Run(tt.Name, func(t *testing.T) {
   369  			r, w := net.Pipe()
   370  			go w.Write([]byte(tt.in))
   371  			var val any
   372  
   373  			// If Decode reads beyond what w.Write writes above,
   374  			// it will block, and the test will deadlock.
   375  			if err := NewDecoder(r).Decode(&val); err != nil {
   376  				t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err)
   377  			}
   378  			r.Close()
   379  			w.Close()
   380  		})
   381  	}
   382  }
   383  
   384  type decodeThis struct {
   385  	v any
   386  }
   387  
   388  func TestDecodeInStream(t *testing.T) {
   389  	tests := []struct {
   390  		CaseName
   391  		json      string
   392  		expTokens []any
   393  	}{
   394  		// streaming token cases
   395  		{CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}},
   396  		{CaseName: Name(""), json: ` [10] `, expTokens: []any{
   397  			Delim('['), float64(10), Delim(']')}},
   398  		{CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{
   399  			Delim('['), false, float64(10), "b", Delim(']')}},
   400  		{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
   401  			Delim('{'), "a", float64(1), Delim('}')}},
   402  		{CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{
   403  			Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
   404  		{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
   405  			Delim('['),
   406  			Delim('{'), "a", float64(1), Delim('}'),
   407  			Delim('{'), "a", float64(2), Delim('}'),
   408  			Delim(']')}},
   409  		{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
   410  			Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
   411  			Delim('}')}},
   412  		{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
   413  			Delim('{'), "obj", Delim('['),
   414  			Delim('{'), "a", float64(1), Delim('}'),
   415  			Delim(']'), Delim('}')}},
   416  
   417  		// streaming tokens with intermittent Decode()
   418  		{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
   419  			Delim('{'), "a",
   420  			decodeThis{float64(1)},
   421  			Delim('}')}},
   422  		{CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{
   423  			Delim('['),
   424  			decodeThis{map[string]any{"a": float64(1)}},
   425  			Delim(']')}},
   426  		{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
   427  			Delim('['),
   428  			decodeThis{map[string]any{"a": float64(1)}},
   429  			decodeThis{map[string]any{"a": float64(2)}},
   430  			Delim(']')}},
   431  		{CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
   432  			Delim('{'), "obj", Delim('['),
   433  			decodeThis{map[string]any{"a": float64(1)}},
   434  			Delim(']'), Delim('}')}},
   435  
   436  		{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
   437  			Delim('{'), "obj",
   438  			decodeThis{map[string]any{"a": float64(1)}},
   439  			Delim('}')}},
   440  		{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
   441  			Delim('{'), "obj",
   442  			decodeThis{[]any{
   443  				map[string]any{"a": float64(1)},
   444  			}},
   445  			Delim('}')}},
   446  		{CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
   447  			Delim('['),
   448  			decodeThis{map[string]any{"a": float64(1)}},
   449  			decodeThis{&SyntaxError{"expected comma after array element", 11}},
   450  		}},
   451  		{CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{
   452  			Delim('{'), strings.Repeat("a", 513),
   453  			decodeThis{&SyntaxError{"expected colon after object key", 518}},
   454  		}},
   455  		{CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{
   456  			Delim('{'),
   457  			&SyntaxError{"invalid character 'a' in string escape code", 3},
   458  		}},
   459  		{CaseName: Name(""), json: ` \a`, expTokens: []any{
   460  			&SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
   461  		}},
   462  		{CaseName: Name(""), json: `,`, expTokens: []any{
   463  			&SyntaxError{"invalid character ',' looking for beginning of value", 0},
   464  		}},
   465  	}
   466  	for _, tt := range tests {
   467  		t.Run(tt.Name, func(t *testing.T) {
   468  			dec := NewDecoder(strings.NewReader(tt.json))
   469  			for i, want := range tt.expTokens {
   470  				var got any
   471  				var err error
   472  
   473  				wantMore := true
   474  				switch want {
   475  				case Delim(']'), Delim('}'):
   476  					wantMore = false
   477  				}
   478  				if got := dec.More(); got != wantMore {
   479  					t.Fatalf("%s:\n\tinput: %s\n\tdec.More() = %v, want %v (next token: %T(%v))", tt.Where, tt.json, got, wantMore, want, want)
   480  				}
   481  
   482  				if dt, ok := want.(decodeThis); ok {
   483  					want = dt.v
   484  					err = dec.Decode(&got)
   485  				} else {
   486  					got, err = dec.Token()
   487  				}
   488  				if errWant, ok := want.(error); ok {
   489  					if err == nil || !reflect.DeepEqual(err, errWant) {
   490  						t.Fatalf("%s:\n\tinput: %s\n\tgot error:  %v\n\twant error: %v", tt.Where, tt.json, err, errWant)
   491  					}
   492  					break
   493  				} else if err != nil {
   494  					t.Fatalf("%s:\n\tinput: %s\n\tgot error:  %v\n\twant error: nil", tt.Where, tt.json, err)
   495  				}
   496  				if !reflect.DeepEqual(got, want) {
   497  					t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot:  %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want)
   498  				}
   499  			}
   500  		})
   501  	}
   502  }
   503  
   504  // Test from golang.org/issue/11893
   505  func TestHTTPDecoding(t *testing.T) {
   506  	const raw = `{ "foo": "bar" }`
   507  
   508  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   509  		w.Write([]byte(raw))
   510  	}))
   511  	defer ts.Close()
   512  	res, err := http.Get(ts.URL)
   513  	if err != nil {
   514  		log.Fatalf("http.Get error: %v", err)
   515  	}
   516  	defer res.Body.Close()
   517  
   518  	foo := struct {
   519  		Foo string
   520  	}{}
   521  
   522  	d := NewDecoder(res.Body)
   523  	err = d.Decode(&foo)
   524  	if err != nil {
   525  		t.Fatalf("Decode error: %v", err)
   526  	}
   527  	if foo.Foo != "bar" {
   528  		t.Errorf(`Decode: got %q, want "bar"`, foo.Foo)
   529  	}
   530  
   531  	// make sure we get the EOF the second time
   532  	err = d.Decode(&foo)
   533  	if err != io.EOF {
   534  		t.Errorf("Decode error:\n\tgot:  %v\n\twant: io.EOF", err)
   535  	}
   536  }
   537  
   538  func TestTokenTruncation(t *testing.T) {
   539  	tests := []struct {
   540  		in  string
   541  		err error
   542  	}{
   543  		{in: ``, err: io.EOF},
   544  		{in: `{`, err: io.EOF},
   545  		{in: `{"`, err: io.ErrUnexpectedEOF},
   546  		{in: `{"k"`, err: io.EOF},
   547  		{in: `{"k":`, err: io.EOF},
   548  		{in: `{"k",`, err: &SyntaxError{"invalid character ',' after object key", int64(len(`{"k"`))}},
   549  		{in: `{"k"}`, err: &SyntaxError{"invalid character '}' after object key", int64(len(`{"k"`))}},
   550  		{in: ` [0`, err: io.EOF},
   551  		{in: `[0.`, err: io.ErrUnexpectedEOF},
   552  		{in: `[0. `, err: &SyntaxError{"invalid character ' ' after decimal point in numeric literal", int64(len(`[0.`))}},
   553  		{in: `[0,`, err: io.EOF},
   554  		{in: `[0:`, err: &SyntaxError{"invalid character ':' after array element", int64(len(`[0`))}},
   555  		{in: `n`, err: io.ErrUnexpectedEOF},
   556  		{in: `nul`, err: io.ErrUnexpectedEOF},
   557  		{in: `fal `, err: &SyntaxError{"invalid character ' ' in literal false (expecting 's')", int64(len(`fal `))}},
   558  		{in: `false`, err: io.EOF},
   559  	}
   560  	for _, tt := range tests {
   561  		d := NewDecoder(strings.NewReader(tt.in))
   562  		for i := 0; true; i++ {
   563  			if _, err := d.Token(); err != nil {
   564  				if !reflect.DeepEqual(err, tt.err) {
   565  					t.Errorf("`%s`: %d.Token error = %#v, want %v", tt.in, i, err, tt.err)
   566  				}
   567  				break
   568  			}
   569  		}
   570  	}
   571  }
   572  
   573  func TestDecoderInputOffset(t *testing.T) {
   574  	const input = ` [
   575  		[ ] , [ "one" ] , [ "one" , "two" ] ,
   576  		{ } , { "alpha" : "bravo" } , { "alpha" : "bravo" , "fizz" : "buzz" }
   577  	] `
   578  	wantOffsets := []int64{
   579  		0, 1, 2, 5, 6, 7, 8, 9, 12, 13, 18, 19, 20, 21, 24, 25, 30, 31,
   580  		38, 39, 40, 41, 46, 47, 48, 49, 52, 53, 60, 61, 70, 71, 72, 73,
   581  		76, 77, 84, 85, 94, 95, 103, 104, 112, 113, 114, 116, 117, 117,
   582  		117, 117,
   583  	}
   584  	wantMores := []bool{
   585  		true, true, false, true, true, false, true, true, true, false,
   586  		true, false, true, true, true, false, true, true, true, true,
   587  		true, false, false, false, false,
   588  	}
   589  
   590  	d := NewDecoder(strings.NewReader(input))
   591  	checkOffset := func() {
   592  		t.Helper()
   593  		got := d.InputOffset()
   594  		if len(wantOffsets) == 0 {
   595  			t.Fatalf("InputOffset = %d, want nil", got)
   596  		}
   597  		want := wantOffsets[0]
   598  		if got != want {
   599  			t.Fatalf("InputOffset = %d, want %d", got, want)
   600  		}
   601  		wantOffsets = wantOffsets[1:]
   602  	}
   603  	checkMore := func() {
   604  		t.Helper()
   605  		got := d.More()
   606  		if len(wantMores) == 0 {
   607  			t.Fatalf("More = %v, want nil", got)
   608  		}
   609  		want := wantMores[0]
   610  		if got != want {
   611  			t.Fatalf("More = %v, want %v", got, want)
   612  		}
   613  		wantMores = wantMores[1:]
   614  	}
   615  	checkOffset()
   616  	checkMore()
   617  	checkOffset()
   618  	for {
   619  		if _, err := d.Token(); err == io.EOF {
   620  			break
   621  		} else if err != nil {
   622  			t.Fatalf("Token error: %v", err)
   623  		}
   624  		checkOffset()
   625  		checkMore()
   626  		checkOffset()
   627  	}
   628  	checkOffset()
   629  	checkMore()
   630  	checkOffset()
   631  
   632  	if len(wantOffsets)+len(wantMores) > 0 {
   633  		t.Fatal("unconsumed testdata")
   634  	}
   635  }
   636  

View as plain text