git.lukeshu.com/go/lowmemjson@v0.3.9-0.20230723050957-72f6d13f6fb2/compat/json/borrowed_stream_test.go (about) 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 // SPDX-License-Identifier: BSD-3-Clause 6 7 package json 8 9 import ( 10 "bytes" 11 "io" 12 "net" 13 "net/http" 14 "net/http/httptest" 15 "reflect" 16 "runtime/debug" 17 "strings" 18 "testing" 19 ) 20 21 // Test values for the stream test. 22 // One of each JSON kind. 23 var streamTest = []any{ 24 0.1, 25 "hello", 26 nil, 27 true, 28 false, 29 []any{"a", "b", "c"}, 30 map[string]any{"K": "Kelvin", "ß": "long s"}, 31 3.14, // another value to make sure something can follow map 32 } 33 34 var streamEncoded = `0.1 35 "hello" 36 null 37 true 38 false 39 ["a","b","c"] 40 {"ß":"long s","K":"Kelvin"} 41 3.14 42 ` 43 44 func TestEncoder(t *testing.T) { 45 t.Parallel() // MODIFIED: added 46 for i := 0; i <= len(streamTest); i++ { 47 var buf bytes.Buffer 48 enc := NewEncoder(&buf) 49 // Check that enc.SetIndent("", "") turns off indentation. 50 enc.SetIndent(">", ".") 51 enc.SetIndent("", "") 52 for j, v := range streamTest[0:i] { 53 if err := enc.Encode(v); err != nil { 54 t.Fatalf("encode #%d: %v", j, err) 55 } 56 } 57 if have, want := buf.String(), nlines(streamEncoded, i); have != want { 58 t.Errorf("encoding %d items: mismatch", i) 59 diff(t, []byte(have), []byte(want)) 60 break 61 } 62 } 63 } 64 65 //nolint:paralleltest // MODIFIED: added; can't be parallel because it fusses with the global GC. 66 func TestEncoderErrorAndReuseEncodeState(t *testing.T) { 67 // Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test. 68 percent := debug.SetGCPercent(-1) 69 defer debug.SetGCPercent(percent) 70 71 // Trigger an error in Marshal with cyclic data. 72 type Dummy struct { 73 Name string 74 Next *Dummy 75 } 76 dummy := Dummy{Name: "Dummy"} 77 dummy.Next = &dummy 78 79 var buf bytes.Buffer 80 enc := NewEncoder(&buf) 81 if err := enc.Encode(dummy); err == nil { 82 t.Errorf("Encode(dummy) == nil; want error") 83 } 84 85 type Data struct { 86 A string 87 I int 88 } 89 data := Data{A: "a", I: 1} 90 if err := enc.Encode(data); err != nil { 91 t.Errorf("Marshal(%v) = %v", data, err) 92 } 93 94 var data2 Data 95 if err := Unmarshal(buf.Bytes(), &data2); err != nil { 96 t.Errorf("Unmarshal(%v) = %v", data2, err) 97 } 98 if data2 != data { 99 t.Errorf("expect: %v, but get: %v", data, data2) 100 } 101 } 102 103 var streamEncodedIndent = `0.1 104 "hello" 105 null 106 true 107 false 108 [ 109 >."a", 110 >."b", 111 >."c" 112 >] 113 { 114 >."ß": "long s", 115 >."K": "Kelvin" 116 >} 117 3.14 118 ` 119 120 func TestEncoderIndent(t *testing.T) { 121 t.Parallel() // MODIFIED: added 122 var buf bytes.Buffer 123 enc := NewEncoder(&buf) 124 enc.SetIndent(">", ".") 125 for _, v := range streamTest { 126 if err := enc.Encode(v); err != nil { // MODIFIED: check the error 127 t.Error("Encode:", err) // MODIFIED: added 128 } // MODIFIED: added 129 } 130 if have, want := buf.String(), streamEncodedIndent; have != want { 131 t.Error("indented encoding mismatch") 132 diff(t, []byte(have), []byte(want)) 133 } 134 } 135 136 type strMarshaler string 137 138 func (s strMarshaler) MarshalJSON() ([]byte, error) { 139 return []byte(s), nil 140 } 141 142 type strPtrMarshaler string 143 144 func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) { 145 return []byte(*s), nil 146 } 147 148 func TestEncoderSetEscapeHTML(t *testing.T) { 149 t.Parallel() // MODIFIED: added 150 var c C 151 var ct CText 152 var tagStruct struct { 153 Valid int `json:"<>&#! "` 154 Invalid int `json:"\\"` //nolint:staticcheck // testing handling of bad tags // MODIFIED: added nolint annotation 155 } 156 157 // This case is particularly interesting, as we force the encoder to 158 // take the address of the Ptr field to use its MarshalJSON method. This 159 // is why the '&' is important. 160 marshalerStruct := &struct { 161 NonPtr strMarshaler 162 Ptr strPtrMarshaler 163 }{`"<str>"`, `"<str>"`} 164 165 // https://golang.org/issue/34154 166 stringOption := struct { 167 Bar string `json:"bar,string"` 168 }{`<html>foobar</html>`} 169 170 for _, tt := range []struct { 171 name string 172 v any 173 wantEscape string 174 want string 175 }{ 176 {"c", c, `"\u003c\u0026\u003e"`, `"<&>"`}, 177 {"ct", ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, 178 {`"<&>"`, "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, 179 { 180 "tagStruct", tagStruct, 181 `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`, 182 `{"<>&#! ":0,"Invalid":0}`, 183 }, 184 { 185 `"<str>"`, marshalerStruct, 186 `{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`, 187 `{"NonPtr":"<str>","Ptr":"<str>"}`, 188 }, 189 { 190 "stringOption", stringOption, 191 `{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`, 192 `{"bar":"\"<html>foobar</html>\""}`, 193 }, 194 } { 195 var buf bytes.Buffer 196 enc := NewEncoder(&buf) 197 if err := enc.Encode(tt.v); err != nil { 198 t.Errorf("Encode(%s): %s", tt.name, err) 199 continue 200 } 201 if got := strings.TrimSpace(buf.String()); got != tt.wantEscape { 202 t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.wantEscape) 203 } 204 buf.Reset() 205 enc.SetEscapeHTML(false) 206 if err := enc.Encode(tt.v); err != nil { 207 t.Errorf("SetEscapeHTML(false) Encode(%s): %s", tt.name, err) 208 continue 209 } 210 if got := strings.TrimSpace(buf.String()); got != tt.want { 211 t.Errorf("SetEscapeHTML(false) Encode(%s) = %#q, want %#q", 212 tt.name, got, tt.want) 213 } 214 } 215 } 216 217 func TestDecoder(t *testing.T) { 218 t.Parallel() // MODIFIED: added 219 for i := 0; i <= len(streamTest); i++ { 220 // Use stream without newlines as input, 221 // just to stress the decoder even more. 222 // Our test input does not include back-to-back numbers. 223 // Otherwise stripping the newlines would 224 // merge two adjacent JSON values. 225 var buf bytes.Buffer 226 for _, c := range nlines(streamEncoded, i) { 227 if c != '\n' { 228 buf.WriteRune(c) 229 } 230 } 231 out := make([]any, i) 232 dec := NewDecoder(&buf) 233 for j := range out { 234 if err := dec.Decode(&out[j]); err != nil { 235 t.Fatalf("decode #%d/%d: %v", j, i, err) 236 } 237 } 238 if !reflect.DeepEqual(out, streamTest[0:i]) { 239 t.Errorf("decoding %d items: mismatch", i) 240 for j := range out { 241 if !reflect.DeepEqual(out[j], streamTest[j]) { 242 t.Errorf("#%d: have %v want %v", j, out[j], streamTest[j]) 243 } 244 } 245 break 246 } 247 } 248 } 249 250 func TestDecoderBuffered(t *testing.T) { 251 t.Parallel() // MODIFIED: added 252 r := strings.NewReader(`{"Name": "Gopher"} extra `) 253 var m struct { 254 Name string 255 } 256 d := NewDecoder(r) 257 err := d.Decode(&m) 258 if err != nil { 259 t.Fatal(err) 260 } 261 if m.Name != "Gopher" { 262 t.Errorf("Name = %q; want Gopher", m.Name) 263 } 264 rest, err := io.ReadAll(d.Buffered()) 265 if err != nil { 266 t.Fatal(err) 267 } 268 if g, w := string(rest), " extra "; g != w { 269 t.Errorf("Remaining = %q; want %q", g, w) 270 } 271 } 272 273 func nlines(s string, n int) string { 274 if n <= 0 { 275 return "" 276 } 277 for i, c := range s { 278 if c == '\n' { 279 if n--; n == 0 { 280 return s[0 : i+1] 281 } 282 } 283 } 284 return s 285 } 286 287 func TestRawMessage(t *testing.T) { 288 t.Parallel() // MODIFIED: added 289 var data struct { 290 X float64 291 Id RawMessage 292 Y float32 293 } 294 const raw = `["\u0056",null]` 295 const msg = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` 296 err := Unmarshal([]byte(msg), &data) 297 if err != nil { 298 t.Fatalf("Unmarshal: %v", err) 299 } 300 if string([]byte(data.Id)) != raw { 301 t.Fatalf("Raw mismatch: have %#q want %#q", []byte(data.Id), raw) 302 } 303 b, err := Marshal(&data) 304 if err != nil { 305 t.Fatalf("Marshal: %v", err) 306 } 307 if string(b) != msg { 308 t.Fatalf("Marshal: have %#q want %#q", b, msg) 309 } 310 } 311 312 func TestNullRawMessage(t *testing.T) { 313 t.Parallel() // MODIFIED: added 314 var data struct { 315 X float64 316 Id RawMessage 317 IdPtr *RawMessage 318 Y float32 319 } 320 const msg = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}` 321 err := Unmarshal([]byte(msg), &data) 322 if err != nil { 323 t.Fatalf("Unmarshal: %v", err) 324 } 325 if want, got := "null", string(data.Id); want != got { 326 t.Fatalf("Raw mismatch: have %q, want %q", got, want) 327 } 328 if data.IdPtr != nil { 329 t.Fatalf("Raw pointer mismatch: have non-nil, want nil") 330 } 331 b, err := Marshal(&data) 332 if err != nil { 333 t.Fatalf("Marshal: %v", err) 334 } 335 if string(b) != msg { 336 t.Fatalf("Marshal: have %#q want %#q", b, msg) 337 } 338 } 339 340 var blockingTests = []string{ 341 `{"x": 1}`, 342 `[1, 2, 3]`, 343 } 344 345 func TestBlocking(t *testing.T) { 346 t.Parallel() // MODIFIED: added 347 for _, enc := range blockingTests { 348 r, w := net.Pipe() 349 ch := make(chan error) // MODIFIED: added 350 go func() { _, err := w.Write([]byte(enc)); ch <- err }() // MODIFIED: check the error 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("decoding %s: %v", enc, err) 357 } 358 r.Close() 359 if err := <-ch; err != nil { // MODIFIED: added 360 t.Error(err) // MODIFIED: added 361 } // MODIFIED: added 362 w.Close() 363 } 364 } 365 366 type tokenStreamCase struct { 367 json string 368 expTokens []any 369 } 370 371 type decodeThis struct { 372 v any 373 } 374 375 var tokenStreamCases = []tokenStreamCase{ 376 // streaming token cases 377 {json: `10`, expTokens: []any{float64(10)}}, 378 {json: ` [10] `, expTokens: []any{ 379 Delim('['), float64(10), Delim(']')}}, 380 {json: ` [false,10,"b"] `, expTokens: []any{ 381 Delim('['), false, float64(10), "b", Delim(']')}}, 382 {json: `{ "a": 1 }`, expTokens: []any{ 383 Delim('{'), "a", float64(1), Delim('}')}}, 384 {json: `{"a": 1, "b":"3"}`, expTokens: []any{ 385 Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, 386 {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ 387 Delim('['), 388 Delim('{'), "a", float64(1), Delim('}'), 389 Delim('{'), "a", float64(2), Delim('}'), 390 Delim(']')}}, 391 {json: `{"obj": {"a": 1}}`, expTokens: []any{ 392 Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), 393 Delim('}')}}, 394 {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ 395 Delim('{'), "obj", Delim('['), 396 Delim('{'), "a", float64(1), Delim('}'), 397 Delim(']'), Delim('}')}}, 398 399 // streaming tokens with intermittent Decode() 400 {json: `{ "a": 1 }`, expTokens: []any{ 401 Delim('{'), "a", 402 decodeThis{float64(1)}, 403 Delim('}')}}, 404 {json: ` [ { "a" : 1 } ] `, expTokens: []any{ 405 Delim('['), 406 decodeThis{map[string]any{"a": float64(1)}}, 407 Delim(']')}}, 408 {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ 409 Delim('['), 410 decodeThis{map[string]any{"a": float64(1)}}, 411 decodeThis{map[string]any{"a": float64(2)}}, 412 Delim(']')}}, 413 {json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ 414 Delim('{'), "obj", Delim('['), 415 decodeThis{map[string]any{"a": float64(1)}}, 416 Delim(']'), Delim('}')}}, 417 418 {json: `{"obj": {"a": 1}}`, expTokens: []any{ 419 Delim('{'), "obj", 420 decodeThis{map[string]any{"a": float64(1)}}, 421 Delim('}')}}, 422 {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ 423 Delim('{'), "obj", 424 decodeThis{[]any{ 425 map[string]any{"a": float64(1)}, 426 }}, 427 Delim('}')}}, 428 {json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ 429 Delim('['), 430 decodeThis{map[string]any{"a": float64(1)}}, 431 decodeThis{&SyntaxError{"expected comma after array element", 11}}, 432 }}, 433 {json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{ 434 Delim('{'), strings.Repeat("a", 513), 435 decodeThis{&SyntaxError{"expected colon after object key", 518}}, 436 }}, 437 {json: `{ "\a" }`, expTokens: []any{ 438 Delim('{'), 439 &SyntaxError{"invalid character 'a' in string escape code", 3}, 440 }}, 441 {json: ` \a`, expTokens: []any{ 442 &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1}, 443 }}, 444 } 445 446 func TestDecodeInStream(t *testing.T) { 447 t.Parallel() // MODIFIED: added 448 t.Skip("we don't have tokens") // MODIFIED: added 449 for ci, tcase := range tokenStreamCases { 450 451 dec := NewDecoder(strings.NewReader(tcase.json)) 452 for i, etk := range tcase.expTokens { 453 454 var tk any 455 var err error 456 457 if dt, ok := etk.(decodeThis); ok { 458 etk = dt.v 459 err = dec.Decode(&tk) 460 } else { 461 tk, err = dec.Token() 462 } 463 if experr, ok := etk.(error); ok { 464 if err == nil || !reflect.DeepEqual(err, experr) { 465 t.Errorf("case %v: Expected error %#v in %q, but was %#v", ci, experr, tcase.json, err) 466 } 467 break 468 } else if err == io.EOF { 469 t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json) 470 break 471 } else if err != nil { 472 t.Errorf("case %v: Unexpected error '%#v' in %q", ci, err, tcase.json) 473 break 474 } 475 if !reflect.DeepEqual(tk, etk) { 476 t.Errorf(`case %v: %q @ %v expected %T(%v) was %T(%v)`, ci, tcase.json, i, etk, etk, tk, tk) 477 break 478 } 479 } 480 } 481 } 482 483 // Test from golang.org/issue/11893 484 func TestHTTPDecoding(t *testing.T) { 485 t.Parallel() // MODIFIED: added 486 const raw = `{ "foo": "bar" }` 487 488 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 489 _, _ = w.Write([]byte(raw)) // MODIFIED: added the _, _ dogsled for the linter 490 })) 491 defer ts.Close() 492 res, err := http.Get(ts.URL) 493 if err != nil { 494 t.Fatalf("GET failed: %v", err) // MODIFIED: changed from log.Fatalf to t.Fatalf 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: %v", err) 506 } 507 if foo.Foo != "bar" { 508 t.Errorf("decoded %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("err = %v; want io.EOF", err) 515 } 516 }