1// Copyright (C) MongoDB, Inc. 2014-present. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6// 7// Based on github.com/golang/go by The Go Authors 8// See THIRD-PARTY-NOTICES for original license terms. 9 10package json 11 12import ( 13 "bytes" 14 "math" 15 "math/rand" 16 "reflect" 17 "testing" 18) 19 20// Tests of simple examples. 21 22type example struct { 23 compact string 24 indent string 25} 26 27var examples = []example{ 28 {`1`, `1`}, 29 {`{}`, `{}`}, 30 {`[]`, `[]`}, 31 {`{"":2}`, "{\n\t\"\": 2\n}"}, 32 {`[3]`, "[\n\t3\n]"}, 33 {`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, 34 {`{"x":1}`, "{\n\t\"x\": 1\n}"}, 35 {ex1, ex1i}, 36} 37 38var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]` 39 40var ex1i = `[ 41 true, 42 false, 43 null, 44 "x", 45 1, 46 1.5, 47 0, 48 -5e+2 49]` 50 51func TestCompact(t *testing.T) { 52 var buf bytes.Buffer 53 for _, tt := range examples { 54 buf.Reset() 55 if err := Compact(&buf, []byte(tt.compact)); err != nil { 56 t.Errorf("Compact(%#q): %v", tt.compact, err) 57 } else if s := buf.String(); s != tt.compact { 58 t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s) 59 } 60 61 buf.Reset() 62 if err := Compact(&buf, []byte(tt.indent)); err != nil { 63 t.Errorf("Compact(%#q): %v", tt.indent, err) 64 continue 65 } else if s := buf.String(); s != tt.compact { 66 t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact) 67 } 68 } 69} 70 71func TestCompactSeparators(t *testing.T) { 72 // U+2028 and U+2029 should be escaped inside strings. 73 // They should not appear outside strings. 74 tests := []struct { 75 in, compact string 76 }{ 77 {"{\"\u2028\": 1}", `{"\u2028":1}`}, 78 {"{\"\u2029\" :2}", `{"\u2029":2}`}, 79 } 80 for _, tt := range tests { 81 var buf bytes.Buffer 82 if err := Compact(&buf, []byte(tt.in)); err != nil { 83 t.Errorf("Compact(%q): %v", tt.in, err) 84 } else if s := buf.String(); s != tt.compact { 85 t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact) 86 } 87 } 88} 89 90func TestIndent(t *testing.T) { 91 var buf bytes.Buffer 92 for _, tt := range examples { 93 buf.Reset() 94 if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { 95 t.Errorf("Indent(%#q): %v", tt.indent, err) 96 } else if s := buf.String(); s != tt.indent { 97 t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s) 98 } 99 100 buf.Reset() 101 if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { 102 t.Errorf("Indent(%#q): %v", tt.compact, err) 103 continue 104 } else if s := buf.String(); s != tt.indent { 105 t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent) 106 } 107 } 108} 109 110// Tests of a large random structure. 111 112func TestCompactBig(t *testing.T) { 113 initBig() 114 var buf bytes.Buffer 115 if err := Compact(&buf, jsonBig); err != nil { 116 t.Fatalf("Compact: %v", err) 117 } 118 b := buf.Bytes() 119 if !bytes.Equal(b, jsonBig) { 120 t.Error("Compact(jsonBig) != jsonBig") 121 diff(t, b, jsonBig) 122 return 123 } 124} 125 126func TestIndentBig(t *testing.T) { 127 initBig() 128 var buf bytes.Buffer 129 if err := Indent(&buf, jsonBig, "", "\t"); err != nil { 130 t.Fatalf("Indent1: %v", err) 131 } 132 b := buf.Bytes() 133 if len(b) == len(jsonBig) { 134 // jsonBig is compact (no unnecessary spaces); 135 // indenting should make it bigger 136 t.Fatalf("Indent(jsonBig) did not get bigger") 137 } 138 139 // should be idempotent 140 var buf1 bytes.Buffer 141 if err := Indent(&buf1, b, "", "\t"); err != nil { 142 t.Fatalf("Indent2: %v", err) 143 } 144 b1 := buf1.Bytes() 145 if !bytes.Equal(b1, b) { 146 t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)") 147 diff(t, b1, b) 148 return 149 } 150 151 // should get back to original 152 buf1.Reset() 153 if err := Compact(&buf1, b); err != nil { 154 t.Fatalf("Compact: %v", err) 155 } 156 b1 = buf1.Bytes() 157 if !bytes.Equal(b1, jsonBig) { 158 t.Error("Compact(Indent(jsonBig)) != jsonBig") 159 diff(t, b1, jsonBig) 160 return 161 } 162} 163 164type indentErrorTest struct { 165 in string 166 err error 167} 168 169var indentErrorTests = []indentErrorTest{ 170 {`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, 171 {`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}}, 172} 173 174func TestIndentErrors(t *testing.T) { 175 for i, tt := range indentErrorTests { 176 var slice []uint8 177 buf := bytes.NewBuffer(slice) 178 if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { 179 if !reflect.DeepEqual(err, tt.err) { 180 t.Errorf("#%d: Indent: %#v", i, err) 181 continue 182 } 183 } 184 } 185} 186 187func TestNextValueBig(t *testing.T) { 188 initBig() 189 var scan scanner 190 item, rest, err := nextValue(jsonBig, &scan) 191 if err != nil { 192 t.Fatalf("nextValue: %s", err) 193 } 194 if len(item) != len(jsonBig) || &item[0] != &jsonBig[0] { 195 t.Errorf("invalid item: %d %d", len(item), len(jsonBig)) 196 } 197 if len(rest) != 0 { 198 t.Errorf("invalid rest: %d", len(rest)) 199 } 200 201 item, rest, err = nextValue(append(jsonBig, "HELLO WORLD"...), &scan) 202 if err != nil { 203 t.Fatalf("nextValue extra: %s", err) 204 } 205 if len(item) != len(jsonBig) { 206 t.Errorf("invalid item: %d %d", len(item), len(jsonBig)) 207 } 208 if string(rest) != "HELLO WORLD" { 209 t.Errorf("invalid rest: %d", len(rest)) 210 } 211} 212 213var benchScan scanner 214 215func BenchmarkSkipValue(b *testing.B) { 216 initBig() 217 for i := 0; i < b.N; i++ { 218 nextValue(jsonBig, &benchScan) 219 } 220 b.SetBytes(int64(len(jsonBig))) 221} 222 223func diff(t *testing.T, a, b []byte) { 224 for i := 0; ; i++ { 225 if i >= len(a) || i >= len(b) || a[i] != b[i] { 226 j := i - 10 227 if j < 0 { 228 j = 0 229 } 230 t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) 231 return 232 } 233 } 234} 235 236func trim(b []byte) []byte { 237 if len(b) > 20 { 238 return b[0:20] 239 } 240 return b 241} 242 243// Generate a random JSON object. 244 245var jsonBig []byte 246 247func initBig() { 248 n := 10000 249 if testing.Short() { 250 n = 100 251 } 252 b, err := Marshal(genValue(n)) 253 if err != nil { 254 panic(err) 255 } 256 jsonBig = b 257} 258 259func genValue(n int) interface{} { 260 if n > 1 { 261 switch rand.Intn(2) { 262 case 0: 263 return genArray(n) 264 case 1: 265 return genMap(n) 266 } 267 } 268 switch rand.Intn(3) { 269 case 0: 270 return rand.Intn(2) == 0 271 case 1: 272 return rand.NormFloat64() 273 case 2: 274 return genString(30) 275 } 276 panic("unreachable") 277} 278 279func genString(stddev float64) string { 280 n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) 281 c := make([]rune, n) 282 for i := range c { 283 f := math.Abs(rand.NormFloat64()*64 + 32) 284 if f > 0x10ffff { 285 f = 0x10ffff 286 } 287 c[i] = rune(f) 288 } 289 return string(c) 290} 291 292func genArray(n int) []interface{} { 293 f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) 294 if f > n { 295 f = n 296 } 297 if f < 1 { 298 f = 1 299 } 300 x := make([]interface{}, f) 301 for i := range x { 302 x[i] = genValue(((i+1)*n)/f - (i*n)/f) 303 } 304 return x 305} 306 307func genMap(n int) map[string]interface{} { 308 f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) 309 if f > n { 310 f = n 311 } 312 if n > 0 && f == 0 { 313 f = 1 314 } 315 x := make(map[string]interface{}) 316 for i := 0; i < f; i++ { 317 x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) 318 } 319 return x 320} 321