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

View as plain text