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 "math"12 "math/rand"13 "reflect"14 "strings"15 "testing"16)17 18func indentNewlines(s string) string {19 return strings.Join(strings.Split(s, "\n"), "\n\t")20}21 22func stripWhitespace(s string) string {23 return strings.Map(func(r rune) rune {24 if r == ' ' || r == '\n' || r == '\r' || r == '\t' {25 return -126 }27 return r28 }, s)29}30 31func TestValid(t *testing.T) {32 tests := []struct {33 CaseName34 data string35 ok bool36 }{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 53func TestCompactAndIndent(t *testing.T) {54 tests := []struct {55 CaseName56 compact string57 indent string58 }{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+275]`},76 {Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/3407077 {Name(""), `null`, "null \n\r\t"}, // See golang.org/issue/13520 and golang.org/issue/7480678 }79 var buf bytes.Buffer80 for _, tt := range tests {81 t.Run(tt.Name, func(t *testing.T) {82 buf.Reset()83 if err := Compact(&buf, []byte(tt.compact)); err != nil {84 t.Errorf("%s: Compact error: %v", tt.Where, err)85 } else if got := buf.String(); got != tt.compact {86 t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))87 }88 89 buf.Reset()90 if err := Compact(&buf, []byte(tt.indent)); err != nil {91 t.Errorf("%s: Compact error: %v", tt.Where, err)92 } else if got := buf.String(); got != tt.compact {93 t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))94 }95 96 buf.Reset()97 if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil {98 t.Errorf("%s: Indent error: %v", tt.Where, err)99 } else if got := buf.String(); got != tt.indent {100 t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))101 }102 103 buf.Reset()104 if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil {105 t.Errorf("%s: Indent error: %v", tt.Where, err)106 } else if got := buf.String(); got != strings.TrimRight(tt.indent, " \n\r\t") {107 t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))108 }109 })110 }111}112 113func TestCompactSeparators(t *testing.T) {114 // U+2028 and U+2029 should be escaped inside strings.115 // They should not appear outside strings.116 tests := []struct {117 CaseName118 in, compact string119 }{120 {Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"},121 {Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"},122 }123 for _, tt := range tests {124 t.Run(tt.Name, func(t *testing.T) {125 var buf bytes.Buffer126 if err := Compact(&buf, []byte(tt.in)); err != nil {127 t.Errorf("%s: Compact error: %v", tt.Where, err)128 } else if got := buf.String(); got != tt.compact {129 t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))130 }131 })132 }133}134 135// Tests of a large random structure.136 137func TestCompactBig(t *testing.T) {138 initBig()139 var buf bytes.Buffer140 if err := Compact(&buf, jsonBig); err != nil {141 t.Fatalf("Compact error: %v", err)142 }143 b := buf.Bytes()144 if !bytes.Equal(b, jsonBig) {145 t.Error("Compact:")146 diff(t, b, jsonBig)147 return148 }149}150 151func TestIndentBig(t *testing.T) {152 t.Parallel()153 initBig()154 var buf bytes.Buffer155 if err := Indent(&buf, jsonBig, "", "\t"); err != nil {156 t.Fatalf("Indent error: %v", err)157 }158 b := buf.Bytes()159 if len(b) == len(jsonBig) {160 // jsonBig is compact (no unnecessary spaces);161 // indenting should make it bigger162 t.Fatalf("Indent did not expand the input")163 }164 165 // should be idempotent166 var buf1 bytes.Buffer167 if err := Indent(&buf1, b, "", "\t"); err != nil {168 t.Fatalf("Indent error: %v", err)169 }170 b1 := buf1.Bytes()171 if !bytes.Equal(b1, b) {172 t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):")173 diff(t, b1, b)174 return175 }176 177 // should get back to original178 buf1.Reset()179 if err := Compact(&buf1, b); err != nil {180 t.Fatalf("Compact error: %v", err)181 }182 b1 = buf1.Bytes()183 if !bytes.Equal(b1, jsonBig) {184 t.Error("Compact(Indent(jsonBig)) != jsonBig:")185 diff(t, b1, jsonBig)186 return187 }188}189 190func TestIndentErrors(t *testing.T) {191 tests := []struct {192 CaseName193 in string194 err error195 }{196 {Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}},197 {Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}},198 }199 for _, tt := range tests {200 t.Run(tt.Name, func(t *testing.T) {201 slice := make([]uint8, 0)202 buf := bytes.NewBuffer(slice)203 if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {204 if !reflect.DeepEqual(err, tt.err) {205 t.Fatalf("%s: Indent error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err)206 }207 }208 })209 }210}211 212func diff(t *testing.T, a, b []byte) {213 t.Helper()214 for i := 0; ; i++ {215 if i >= len(a) || i >= len(b) || a[i] != b[i] {216 j := i - 10217 if j < 0 {218 j = 0219 }220 t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:]))221 return222 }223 }224}225 226func trim(b []byte) []byte {227 return b[:min(len(b), 20)]228}229 230// Generate a random JSON object.231 232var jsonBig []byte233 234func initBig() {235 n := 10000236 if testing.Short() {237 n = 100238 }239 b, err := Marshal(genValue(n))240 if err != nil {241 panic(err)242 }243 jsonBig = b244}245 246func genValue(n int) any {247 if n > 1 {248 switch rand.Intn(2) {249 case 0:250 return genArray(n)251 case 1:252 return genMap(n)253 }254 }255 switch rand.Intn(3) {256 case 0:257 return rand.Intn(2) == 0258 case 1:259 return rand.NormFloat64()260 case 2:261 return genString(30)262 }263 panic("unreachable")264}265 266func genString(stddev float64) string {267 n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2))268 c := make([]rune, n)269 for i := range c {270 f := math.Abs(rand.NormFloat64()*64 + 32)271 if f > 0x10ffff {272 f = 0x10ffff273 }274 c[i] = rune(f)275 }276 return string(c)277}278 279func genArray(n int) []any {280 f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))281 if f > n {282 f = n283 }284 if f < 1 {285 f = 1286 }287 x := make([]any, f)288 for i := range x {289 x[i] = genValue(((i+1)*n)/f - (i*n)/f)290 }291 return x292}293 294func genMap(n int) map[string]any {295 f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))296 if f > n {297 f = n298 }299 if n > 0 && f == 0 {300 f = 1301 }302 x := make(map[string]any)303 for i := 0; i < f; i++ {304 x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f)305 }306 return x307}308 