codekingpro/portable-devtools
114k
1// Copyright 2010 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5//go:build goexperiment.jsonv26 7package json8 9import (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 24type CaseName = jsontest.CaseName25type CasePos = jsontest.CasePos26 27var Name = jsontest.Name28 29// Test values for the stream test.30// One of each JSON kind.31var 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 map40}41 42var streamEncoded = `0.143"hello"44null45true46false47["a","b","c"]48{"ß":"long s","K":"Kelvin"}493.1450`51 52func TestEncoder(t *testing.T) {53 for i := 0; i <= len(streamTest); i++ {54 var buf strings.Builder55 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 break68 }69 }70}71 72func 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 string80 Next *Dummy81 }82 dummy := Dummy{Name: "Dummy"}83 dummy.Next = &dummy84 85 var buf bytes.Buffer86 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 string93 I int94 }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 Data101 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 109var streamEncodedIndent = `0.1110"hello"111null112true113false114[115>."a",116>."b",117>."c"118>]119{120>."ß": "long s",121>."K": "Kelvin"122>}1233.14124`125 126func TestEncoderIndent(t *testing.T) {127 var buf strings.Builder128 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 139type strMarshaler string140 141func (s strMarshaler) MarshalJSON() ([]byte, error) {142 return []byte(s), nil143}144 145type strPtrMarshaler string146 147func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) {148 return []byte(*s), nil149}150 151func TestEncoderSetEscapeHTML(t *testing.T) {152 var c C153 var ct CText154 var tagStruct struct {155 Valid int `json:"<>&#! "`156 Invalid int `json:"\\"`157 }158 159 // This case is particularly interesting, as we force the encoder to160 // take the address of the Ptr field to use its MarshalJSON method. This161 // is why the '&' is important.162 marshalerStruct := &struct {163 NonPtr strMarshaler164 Ptr strPtrMarshaler165 }{`"<str>"`, `"<str>"`}166 167 // https://golang.org/issue/34154168 stringOption := struct {169 Bar string `json:"bar,string"`170 }{`<html>foobar</html>`}171 172 tests := []struct {173 CaseName174 v any175 wantEscape string176 want string177 }{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.Builder200 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 220func 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 would226 // merge two adjacent JSON values.227 var buf bytes.Buffer228 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 break248 }249 }250}251 252func TestDecoderBuffered(t *testing.T) {253 r := strings.NewReader(`{"Name": "Gopher"} extra `)254 var m struct {255 Name string256 }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 274func 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 s286}287 288func TestRawMessage(t *testing.T) {289 var data struct {290 X float64291 Id RawMessage292 Y float32293 }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 312func TestNullRawMessage(t *testing.T) {313 var data struct {314 X float64315 Id RawMessage316 IdPtr *RawMessage317 Y float32318 }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 339func TestBlocking(t *testing.T) {340 tests := []struct {341 CaseName342 in string343 }{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 any352 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 364type decodeThis struct {365 v any366}367 368func TestDecodeInStream(t *testing.T) {369 tests := []struct {370 CaseName371 json string372 expTokens []any373 }{374 // streaming token cases375 {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 any451 var err error452 453 wantMore := true454 switch want {455 case Delim(']'), Delim('}'):456 wantMore = false457 }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.v464 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 break473 } 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/11893485func 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 string500 }{}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 time512 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 518func TestTokenTruncation(t *testing.T) {519 tests := []struct {520 in string521 err error522 }{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 break548 }549 }550 }551}552 553func 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 break601 } 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 