1// Copyright 2019 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 5package json_test 6 7import ( 8 "math" 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/go-cmp/cmp/cmpopts" 14 15 "google.golang.org/protobuf/internal/detrand" 16 "google.golang.org/protobuf/internal/encoding/json" 17) 18 19// Disable detrand to enable direct comparisons on outputs. 20func init() { detrand.Disable() } 21 22// splitLines is a cmpopts.Option for comparing strings with line breaks. 23var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string { 24 return strings.Split(s, "\n") 25}) 26 27func TestEncoder(t *testing.T) { 28 tests := []struct { 29 desc string 30 write func(*json.Encoder) 31 wantOut string 32 wantOutIndent string 33 }{ 34 { 35 desc: "null", 36 write: func(e *json.Encoder) { 37 e.WriteNull() 38 }, 39 wantOut: `null`, 40 }, 41 { 42 desc: "true", 43 write: func(e *json.Encoder) { 44 e.WriteBool(true) 45 }, 46 wantOut: `true`, 47 }, 48 { 49 desc: "false", 50 write: func(e *json.Encoder) { 51 e.WriteBool(false) 52 }, 53 wantOut: `false`, 54 }, 55 { 56 desc: "string", 57 write: func(e *json.Encoder) { 58 e.WriteString("hello world") 59 }, 60 wantOut: `"hello world"`, 61 }, 62 { 63 desc: "string contains escaped characters", 64 write: func(e *json.Encoder) { 65 e.WriteString("\u0000\"\\/\b\f\n\r\t") 66 }, 67 wantOut: `"\u0000\"\\/\b\f\n\r\t"`, 68 }, 69 { 70 desc: "float64", 71 write: func(e *json.Encoder) { 72 e.WriteFloat(1.0199999809265137, 64) 73 }, 74 wantOut: `1.0199999809265137`, 75 }, 76 { 77 desc: "float64 max value", 78 write: func(e *json.Encoder) { 79 e.WriteFloat(math.MaxFloat64, 64) 80 }, 81 wantOut: `1.7976931348623157e+308`, 82 }, 83 { 84 desc: "float64 min value", 85 write: func(e *json.Encoder) { 86 e.WriteFloat(-math.MaxFloat64, 64) 87 }, 88 wantOut: `-1.7976931348623157e+308`, 89 }, 90 { 91 desc: "float64 NaN", 92 write: func(e *json.Encoder) { 93 e.WriteFloat(math.NaN(), 64) 94 }, 95 wantOut: `"NaN"`, 96 }, 97 { 98 desc: "float64 Infinity", 99 write: func(e *json.Encoder) { 100 e.WriteFloat(math.Inf(+1), 64) 101 }, 102 wantOut: `"Infinity"`, 103 }, 104 { 105 desc: "float64 -Infinity", 106 write: func(e *json.Encoder) { 107 e.WriteFloat(math.Inf(-1), 64) 108 }, 109 wantOut: `"-Infinity"`, 110 }, 111 { 112 desc: "float64 negative zero", 113 write: func(e *json.Encoder) { 114 e.WriteFloat(math.Copysign(0, -1), 64) 115 }, 116 wantOut: `-0`, 117 }, 118 { 119 desc: "float32", 120 write: func(e *json.Encoder) { 121 e.WriteFloat(1.02, 32) 122 }, 123 wantOut: `1.02`, 124 }, 125 { 126 desc: "float32 max value", 127 write: func(e *json.Encoder) { 128 e.WriteFloat(math.MaxFloat32, 32) 129 }, 130 wantOut: `3.4028235e+38`, 131 }, 132 { 133 desc: "float32 min value", 134 write: func(e *json.Encoder) { 135 e.WriteFloat(-math.MaxFloat32, 32) 136 }, 137 wantOut: `-3.4028235e+38`, 138 }, 139 { 140 desc: "float32 negative zero", 141 write: func(e *json.Encoder) { 142 e.WriteFloat(math.Copysign(0, -1), 32) 143 }, 144 wantOut: `-0`, 145 }, 146 { 147 desc: "int", 148 write: func(e *json.Encoder) { 149 e.WriteInt(-math.MaxInt64) 150 }, 151 wantOut: `-9223372036854775807`, 152 }, 153 { 154 desc: "uint", 155 write: func(e *json.Encoder) { 156 e.WriteUint(math.MaxUint64) 157 }, 158 wantOut: `18446744073709551615`, 159 }, 160 { 161 desc: "empty object", 162 write: func(e *json.Encoder) { 163 e.StartObject() 164 e.EndObject() 165 }, 166 wantOut: `{}`, 167 }, 168 { 169 desc: "empty array", 170 write: func(e *json.Encoder) { 171 e.StartArray() 172 e.EndArray() 173 }, 174 wantOut: `[]`, 175 }, 176 { 177 desc: "object with one member", 178 write: func(e *json.Encoder) { 179 e.StartObject() 180 e.WriteName("hello") 181 e.WriteString("world") 182 e.EndObject() 183 }, 184 wantOut: `{"hello":"world"}`, 185 wantOutIndent: `{ 186 "hello": "world" 187}`, 188 }, 189 { 190 desc: "array with one member", 191 write: func(e *json.Encoder) { 192 e.StartArray() 193 e.WriteNull() 194 e.EndArray() 195 }, 196 wantOut: `[null]`, 197 wantOutIndent: `[ 198 null 199]`, 200 }, 201 { 202 desc: "simple object", 203 write: func(e *json.Encoder) { 204 e.StartObject() 205 { 206 e.WriteName("null") 207 e.WriteNull() 208 } 209 { 210 e.WriteName("bool") 211 e.WriteBool(true) 212 } 213 { 214 e.WriteName("string") 215 e.WriteString("hello") 216 } 217 { 218 e.WriteName("float") 219 e.WriteFloat(6.28318, 64) 220 } 221 { 222 e.WriteName("int") 223 e.WriteInt(42) 224 } 225 { 226 e.WriteName("uint") 227 e.WriteUint(47) 228 } 229 e.EndObject() 230 }, 231 wantOut: `{"null":null,"bool":true,"string":"hello","float":6.28318,"int":42,"uint":47}`, 232 wantOutIndent: `{ 233 "null": null, 234 "bool": true, 235 "string": "hello", 236 "float": 6.28318, 237 "int": 42, 238 "uint": 47 239}`, 240 }, 241 { 242 desc: "simple array", 243 write: func(e *json.Encoder) { 244 e.StartArray() 245 { 246 e.WriteString("hello") 247 e.WriteFloat(6.28318, 32) 248 e.WriteInt(42) 249 e.WriteUint(47) 250 e.WriteBool(true) 251 e.WriteNull() 252 } 253 e.EndArray() 254 }, 255 wantOut: `["hello",6.28318,42,47,true,null]`, 256 wantOutIndent: `[ 257 "hello", 258 6.28318, 259 42, 260 47, 261 true, 262 null 263]`, 264 }, 265 { 266 desc: "fancy object", 267 write: func(e *json.Encoder) { 268 e.StartObject() 269 { 270 e.WriteName("object0") 271 e.StartObject() 272 e.EndObject() 273 } 274 { 275 e.WriteName("array0") 276 e.StartArray() 277 e.EndArray() 278 } 279 { 280 e.WriteName("object1") 281 e.StartObject() 282 { 283 e.WriteName("null") 284 e.WriteNull() 285 } 286 { 287 e.WriteName("object1-1") 288 e.StartObject() 289 { 290 e.WriteName("bool") 291 e.WriteBool(false) 292 } 293 { 294 e.WriteName("float") 295 e.WriteFloat(3.14159, 32) 296 } 297 e.EndObject() 298 } 299 e.EndObject() 300 } 301 { 302 e.WriteName("array1") 303 e.StartArray() 304 { 305 e.WriteNull() 306 e.StartObject() 307 e.EndObject() 308 e.StartObject() 309 { 310 e.WriteName("hello") 311 e.WriteString("world") 312 } 313 { 314 e.WriteName("hola") 315 e.WriteString("mundo") 316 } 317 e.EndObject() 318 e.StartArray() 319 { 320 e.WriteUint(1) 321 e.WriteUint(0) 322 e.WriteUint(1) 323 } 324 e.EndArray() 325 } 326 e.EndArray() 327 } 328 e.EndObject() 329 }, 330 wantOutIndent: `{ 331 "object0": {}, 332 "array0": [], 333 "object1": { 334 "null": null, 335 "object1-1": { 336 "bool": false, 337 "float": 3.14159 338 } 339 }, 340 "array1": [ 341 null, 342 {}, 343 { 344 "hello": "world", 345 "hola": "mundo" 346 }, 347 [ 348 1, 349 0, 350 1 351 ] 352 ] 353}`, 354 }} 355 356 for _, tc := range tests { 357 t.Run(tc.desc, func(t *testing.T) { 358 if tc.wantOut != "" { 359 enc, err := json.NewEncoder("") 360 if err != nil { 361 t.Fatalf("NewEncoder() returned error: %v", err) 362 } 363 tc.write(enc) 364 got := string(enc.Bytes()) 365 if got != tc.wantOut { 366 t.Errorf("%s:\n<got>:\n%v\n<want>\n%v\n", tc.desc, got, tc.wantOut) 367 } 368 } 369 if tc.wantOutIndent != "" { 370 enc, err := json.NewEncoder("\t") 371 if err != nil { 372 t.Fatalf("NewEncoder() returned error: %v", err) 373 } 374 tc.write(enc) 375 got, want := string(enc.Bytes()), tc.wantOutIndent 376 if got != want { 377 t.Errorf("%s(indent):\n<got>:\n%v\n<want>\n%v\n<diff -want +got>\n%v\n", 378 tc.desc, got, want, cmp.Diff(want, got, splitLines)) 379 } 380 } 381 }) 382 } 383} 384 385func TestWriteStringError(t *testing.T) { 386 tests := []string{"abc\xff"} 387 388 for _, in := range tests { 389 t.Run(in, func(t *testing.T) { 390 enc, err := json.NewEncoder("") 391 if err != nil { 392 t.Fatalf("NewEncoder() returned error: %v", err) 393 } 394 if err := enc.WriteString(in); err == nil { 395 t.Errorf("WriteString(%v): got nil error, want error", in) 396 } 397 }) 398 } 399} 400