Source file src/encoding/json/v2_scanner_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  	"math"
    12  	"math/rand"
    13  	"reflect"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  func indentNewlines(s string) string {
    19  	return strings.Join(strings.Split(s, "\n"), "\n\t")
    20  }
    21  
    22  func stripWhitespace(s string) string {
    23  	return strings.Map(func(r rune) rune {
    24  		if r == ' ' || r == '\n' || r == '\r' || r == '\t' {
    25  			return -1
    26  		}
    27  		return r
    28  	}, s)
    29  }
    30  
    31  func TestValid(t *testing.T) {
    32  	tests := []struct {
    33  		CaseName
    34  		data string
    35  		ok   bool
    36  	}{
    37  		{Name(""), `foo`, false},
    38  		{Name(""), `}{`, false},
    39  		{Name(""), `{]`, false},
    40  		{Name(""), `{}`, true},
    41  		{Name(""), `{"foo":"bar"}`, true},
    42  		{Name(""), `{"foo":"bar","bar":{"baz":["qux"]}}`, true},
    43  	}
    44  	for _, tt := range tests {
    45  		t.Run(tt.Name, func(t *testing.T) {
    46  			if ok := Valid([]byte(tt.data)); ok != tt.ok {
    47  				t.Errorf("%s: Valid(`%s`) = %v, want %v", tt.Where, tt.data, ok, tt.ok)
    48  			}
    49  		})
    50  	}
    51  }
    52  
    53  func TestCompactAndIndent(t *testing.T) {
    54  	tests := []struct {
    55  		CaseName
    56  		compact string
    57  		indent  string
    58  	}{
    59  		{Name(""), `1`, `1`},
    60  		{Name(""), `{}`, `{}`},
    61  		{Name(""), `[]`, `[]`},
    62  		{Name(""), `{"":2}`, "{\n\t\"\": 2\n}"},
    63  		{Name(""), `[3]`, "[\n\t3\n]"},
    64  		{Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
    65  		{Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"},
    66  		{Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[
    67  	true,
    68  	false,
    69  	null,
    70  	"x",
    71  	1,
    72  	1.5,
    73  	0,
    74  	-5e+2
    75  ]`},
    76  		{Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070
    77  	}
    78  	var buf bytes.Buffer
    79  	for _, tt := range tests {
    80  		t.Run(tt.Name, func(t *testing.T) {
    81  			buf.Reset()
    82  			if err := Compact(&buf, []byte(tt.compact)); err != nil {
    83  				t.Errorf("%s: Compact error: %v", tt.Where, err)
    84  			} else if got := buf.String(); got != tt.compact {
    85  				t.Errorf("%s: Compact:\n\tgot:  %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
    86  			}
    87  
    88  			buf.Reset()
    89  			if err := Compact(&buf, []byte(tt.indent)); err != nil {
    90  				t.Errorf("%s: Compact error: %v", tt.Where, err)
    91  			} else if got := buf.String(); got != tt.compact {
    92  				t.Errorf("%s: Compact:\n\tgot:  %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
    93  			}
    94  
    95  			buf.Reset()
    96  			if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil {
    97  				t.Errorf("%s: Indent error: %v", tt.Where, err)
    98  			} else if got := buf.String(); got != tt.indent {
    99  				t.Errorf("%s: Compact:\n\tgot:  %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))
   100  			}
   101  
   102  			buf.Reset()
   103  			if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil {
   104  				t.Errorf("%s: Indent error: %v", tt.Where, err)
   105  			} else if got := buf.String(); got != tt.indent {
   106  				t.Errorf("%s: Compact:\n\tgot:  %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))
   107  			}
   108  		})
   109  	}
   110  }
   111  
   112  func TestCompactSeparators(t *testing.T) {
   113  	// U+2028 and U+2029 should be escaped inside strings.
   114  	// They should not appear outside strings.
   115  	tests := []struct {
   116  		CaseName
   117  		in, compact string
   118  	}{
   119  		{Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"},
   120  		{Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"},
   121  	}
   122  	for _, tt := range tests {
   123  		t.Run(tt.Name, func(t *testing.T) {
   124  			var buf bytes.Buffer
   125  			if err := Compact(&buf, []byte(tt.in)); err != nil {
   126  				t.Errorf("%s: Compact error: %v", tt.Where, err)
   127  			} else if got := buf.String(); got != tt.compact {
   128  				t.Errorf("%s: Compact:\n\tgot:  %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  // Tests of a large random structure.
   135  
   136  func TestCompactBig(t *testing.T) {
   137  	initBig()
   138  	var buf bytes.Buffer
   139  	if err := Compact(&buf, jsonBig); err != nil {
   140  		t.Fatalf("Compact error: %v", err)
   141  	}
   142  	b := buf.Bytes()
   143  	if !bytes.Equal(b, jsonBig) {
   144  		t.Error("Compact:")
   145  		diff(t, b, jsonBig)
   146  		return
   147  	}
   148  }
   149  
   150  func TestIndentBig(t *testing.T) {
   151  	t.Parallel()
   152  	initBig()
   153  	var buf bytes.Buffer
   154  	if err := Indent(&buf, jsonBig, "", "\t"); err != nil {
   155  		t.Fatalf("Indent error: %v", err)
   156  	}
   157  	b := buf.Bytes()
   158  	if len(b) == len(jsonBig) {
   159  		// jsonBig is compact (no unnecessary spaces);
   160  		// indenting should make it bigger
   161  		t.Fatalf("Indent did not expand the input")
   162  	}
   163  
   164  	// should be idempotent
   165  	var buf1 bytes.Buffer
   166  	if err := Indent(&buf1, b, "", "\t"); err != nil {
   167  		t.Fatalf("Indent error: %v", err)
   168  	}
   169  	b1 := buf1.Bytes()
   170  	if !bytes.Equal(b1, b) {
   171  		t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):")
   172  		diff(t, b1, b)
   173  		return
   174  	}
   175  
   176  	// should get back to original
   177  	buf1.Reset()
   178  	if err := Compact(&buf1, b); err != nil {
   179  		t.Fatalf("Compact error: %v", err)
   180  	}
   181  	b1 = buf1.Bytes()
   182  	if !bytes.Equal(b1, jsonBig) {
   183  		t.Error("Compact(Indent(jsonBig)) != jsonBig:")
   184  		diff(t, b1, jsonBig)
   185  		return
   186  	}
   187  }
   188  
   189  func TestIndentErrors(t *testing.T) {
   190  	tests := []struct {
   191  		CaseName
   192  		in  string
   193  		err error
   194  	}{
   195  		{Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", len64(`{"X": "foo", "Y"`)}},
   196  		{Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", len64(`{"X": "foo" `)}},
   197  	}
   198  	for _, tt := range tests {
   199  		t.Run(tt.Name, func(t *testing.T) {
   200  			slice := make([]uint8, 0)
   201  			buf := bytes.NewBuffer(slice)
   202  			if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {
   203  				if !reflect.DeepEqual(err, tt.err) {
   204  					t.Fatalf("%s: Indent error:\n\tgot:  %v\n\twant: %v", tt.Where, err, tt.err)
   205  				}
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func diff(t *testing.T, a, b []byte) {
   212  	t.Helper()
   213  	for i := 0; ; i++ {
   214  		if i >= len(a) || i >= len(b) || a[i] != b[i] {
   215  			j := i - 10
   216  			if j < 0 {
   217  				j = 0
   218  			}
   219  			t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:]))
   220  			return
   221  		}
   222  	}
   223  }
   224  
   225  func trim(b []byte) []byte {
   226  	return b[:min(len(b), 20)]
   227  }
   228  
   229  // Generate a random JSON object.
   230  
   231  var jsonBig []byte
   232  
   233  func initBig() {
   234  	n := 10000
   235  	if testing.Short() {
   236  		n = 100
   237  	}
   238  	b, err := Marshal(genValue(n))
   239  	if err != nil {
   240  		panic(err)
   241  	}
   242  	jsonBig = b
   243  }
   244  
   245  func genValue(n int) any {
   246  	if n > 1 {
   247  		switch rand.Intn(2) {
   248  		case 0:
   249  			return genArray(n)
   250  		case 1:
   251  			return genMap(n)
   252  		}
   253  	}
   254  	switch rand.Intn(3) {
   255  	case 0:
   256  		return rand.Intn(2) == 0
   257  	case 1:
   258  		return rand.NormFloat64()
   259  	case 2:
   260  		return genString(30)
   261  	}
   262  	panic("unreachable")
   263  }
   264  
   265  func genString(stddev float64) string {
   266  	n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2))
   267  	c := make([]rune, n)
   268  	for i := range c {
   269  		f := math.Abs(rand.NormFloat64()*64 + 32)
   270  		if f > 0x10ffff {
   271  			f = 0x10ffff
   272  		}
   273  		c[i] = rune(f)
   274  	}
   275  	return string(c)
   276  }
   277  
   278  func genArray(n int) []any {
   279  	f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
   280  	if f > n {
   281  		f = n
   282  	}
   283  	if f < 1 {
   284  		f = 1
   285  	}
   286  	x := make([]any, f)
   287  	for i := range x {
   288  		x[i] = genValue(((i+1)*n)/f - (i*n)/f)
   289  	}
   290  	return x
   291  }
   292  
   293  func genMap(n int) map[string]any {
   294  	f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
   295  	if f > n {
   296  		f = n
   297  	}
   298  	if n > 0 && f == 0 {
   299  		f = 1
   300  	}
   301  	x := make(map[string]any)
   302  	for i := 0; i < f; i++ {
   303  		x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f)
   304  	}
   305  	return x
   306  }
   307  

View as plain text