k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/internal/third_party/go-json-experiment/json/encode_test.go (about) 1 // Copyright 2020 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 package json 6 7 import ( 8 "bufio" 9 "bytes" 10 "compress/gzip" 11 "crypto/sha256" 12 "encoding/binary" 13 "encoding/hex" 14 "errors" 15 "flag" 16 "io" 17 "math" 18 "net/http" 19 "path" 20 "reflect" 21 "strconv" 22 "strings" 23 "testing" 24 "time" 25 "unicode" 26 ) 27 28 // TestEncoder tests whether we can produce JSON with either tokens or raw values. 29 func TestEncoder(t *testing.T) { 30 for _, td := range coderTestdata { 31 for _, formatName := range []string{"Compact", "Escaped", "Indented"} { 32 for _, typeName := range []string{"Token", "Value", "TokenDelims"} { 33 t.Run(path.Join(td.name.name, typeName, formatName), func(t *testing.T) { 34 testEncoder(t, td.name.where, formatName, typeName, td) 35 }) 36 } 37 } 38 } 39 } 40 func testEncoder(t *testing.T, where pc, formatName, typeName string, td coderTestdataEntry) { 41 var want string 42 dst := new(bytes.Buffer) 43 enc := NewEncoder(dst) 44 enc.options.omitTopLevelNewline = true 45 want = td.outCompacted 46 switch formatName { 47 case "Escaped": 48 enc.options.EscapeRune = func(rune) bool { return true } 49 if td.outEscaped != "" { 50 want = td.outEscaped 51 } 52 case "Indented": 53 enc.options.multiline = true 54 enc.options.IndentPrefix = "\t" 55 enc.options.Indent = " " 56 if td.outIndented != "" { 57 want = td.outIndented 58 } 59 } 60 61 switch typeName { 62 case "Token": 63 var pointers []string 64 for _, tok := range td.tokens { 65 if err := enc.WriteToken(tok); err != nil { 66 t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) 67 } 68 if td.pointers != nil { 69 pointers = append(pointers, enc.StackPointer()) 70 } 71 } 72 if !reflect.DeepEqual(pointers, td.pointers) { 73 t.Fatalf("%s: pointers mismatch:\ngot %q\nwant %q", where, pointers, td.pointers) 74 } 75 case "Value": 76 if err := enc.WriteValue(RawValue(td.in)); err != nil { 77 t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) 78 } 79 case "TokenDelims": 80 // Use WriteToken for object/array delimiters, WriteValue otherwise. 81 for _, tok := range td.tokens { 82 switch tok.Kind() { 83 case '{', '}', '[', ']': 84 if err := enc.WriteToken(tok); err != nil { 85 t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) 86 } 87 default: 88 val := RawValue(tok.String()) 89 if tok.Kind() == '"' { 90 val, _ = appendString(nil, tok.String(), false, nil) 91 } 92 if err := enc.WriteValue(val); err != nil { 93 t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) 94 } 95 } 96 } 97 } 98 99 got := dst.String() 100 if got != want { 101 t.Errorf("%s: output mismatch:\ngot %q\nwant %q", where, got, want) 102 } 103 } 104 105 // TestFaultyEncoder tests that temporary I/O errors are not fatal. 106 func TestFaultyEncoder(t *testing.T) { 107 for _, td := range coderTestdata { 108 for _, typeName := range []string{"Token", "Value"} { 109 t.Run(path.Join(td.name.name, typeName), func(t *testing.T) { 110 testFaultyEncoder(t, td.name.where, typeName, td) 111 }) 112 } 113 } 114 } 115 func testFaultyEncoder(t *testing.T, where pc, typeName string, td coderTestdataEntry) { 116 b := &FaultyBuffer{ 117 MaxBytes: 1, 118 MayError: io.ErrShortWrite, 119 } 120 121 // Write all the tokens. 122 // Even if the underlying io.Writer may be faulty, 123 // writing a valid token or value is guaranteed to at least 124 // be appended to the internal buffer. 125 // In other words, syntactic errors occur before I/O errors. 126 enc := NewEncoder(b) 127 switch typeName { 128 case "Token": 129 for i, tok := range td.tokens { 130 err := enc.WriteToken(tok) 131 if err != nil && !errors.Is(err, io.ErrShortWrite) { 132 t.Fatalf("%s: %d: Encoder.WriteToken error: %v", where, i, err) 133 } 134 } 135 case "Value": 136 err := enc.WriteValue(RawValue(td.in)) 137 if err != nil && !errors.Is(err, io.ErrShortWrite) { 138 t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) 139 } 140 } 141 gotOutput := string(append(b.B, enc.unflushedBuffer()...)) 142 wantOutput := td.outCompacted + "\n" 143 if gotOutput != wantOutput { 144 t.Fatalf("%s: output mismatch:\ngot %s\nwant %s", where, gotOutput, wantOutput) 145 } 146 } 147 148 type encoderMethodCall struct { 149 in tokOrVal 150 wantErr error 151 wantPointer string 152 } 153 154 var encoderErrorTestdata = []struct { 155 name testName 156 opts EncodeOptions 157 calls []encoderMethodCall 158 wantOut string 159 }{{ 160 name: name("InvalidToken"), 161 calls: []encoderMethodCall{ 162 {zeroToken, &SyntacticError{str: "invalid json.Token"}, ""}, 163 }, 164 }, { 165 name: name("InvalidValue"), 166 calls: []encoderMethodCall{ 167 {RawValue(`#`), newInvalidCharacterError([]byte("#"), "at start of value"), ""}, 168 }, 169 }, { 170 name: name("InvalidValue/DoubleZero"), 171 calls: []encoderMethodCall{ 172 {RawValue(`00`), newInvalidCharacterError([]byte("0"), "after top-level value"), ""}, 173 }, 174 }, { 175 name: name("TruncatedValue"), 176 calls: []encoderMethodCall{ 177 {zeroValue, io.ErrUnexpectedEOF, ""}, 178 }, 179 }, { 180 name: name("TruncatedNull"), 181 calls: []encoderMethodCall{ 182 {RawValue(`nul`), io.ErrUnexpectedEOF, ""}, 183 }, 184 }, { 185 name: name("InvalidNull"), 186 calls: []encoderMethodCall{ 187 {RawValue(`nulL`), newInvalidCharacterError([]byte("L"), "within literal null (expecting 'l')"), ""}, 188 }, 189 }, { 190 name: name("TruncatedFalse"), 191 calls: []encoderMethodCall{ 192 {RawValue(`fals`), io.ErrUnexpectedEOF, ""}, 193 }, 194 }, { 195 name: name("InvalidFalse"), 196 calls: []encoderMethodCall{ 197 {RawValue(`falsE`), newInvalidCharacterError([]byte("E"), "within literal false (expecting 'e')"), ""}, 198 }, 199 }, { 200 name: name("TruncatedTrue"), 201 calls: []encoderMethodCall{ 202 {RawValue(`tru`), io.ErrUnexpectedEOF, ""}, 203 }, 204 }, { 205 name: name("InvalidTrue"), 206 calls: []encoderMethodCall{ 207 {RawValue(`truE`), newInvalidCharacterError([]byte("E"), "within literal true (expecting 'e')"), ""}, 208 }, 209 }, { 210 name: name("TruncatedString"), 211 calls: []encoderMethodCall{ 212 {RawValue(`"star`), io.ErrUnexpectedEOF, ""}, 213 }, 214 }, { 215 name: name("InvalidString"), 216 calls: []encoderMethodCall{ 217 {RawValue(`"ok` + "\x00"), newInvalidCharacterError([]byte("\x00"), `within string (expecting non-control character)`), ""}, 218 }, 219 }, { 220 name: name("ValidString/AllowInvalidUTF8/Token"), 221 opts: EncodeOptions{AllowInvalidUTF8: true}, 222 calls: []encoderMethodCall{ 223 {String("living\xde\xad\xbe\xef"), nil, ""}, 224 }, 225 wantOut: "\"living\xde\xad\ufffd\ufffd\"\n", 226 }, { 227 name: name("ValidString/AllowInvalidUTF8/Value"), 228 opts: EncodeOptions{AllowInvalidUTF8: true}, 229 calls: []encoderMethodCall{ 230 {RawValue("\"living\xde\xad\xbe\xef\""), nil, ""}, 231 }, 232 wantOut: "\"living\xde\xad\ufffd\ufffd\"\n", 233 }, { 234 name: name("InvalidString/RejectInvalidUTF8"), 235 opts: EncodeOptions{AllowInvalidUTF8: false}, 236 calls: []encoderMethodCall{ 237 {String("living\xde\xad\xbe\xef"), &SyntacticError{str: "invalid UTF-8 within string"}, ""}, 238 {RawValue("\"living\xde\xad\xbe\xef\""), &SyntacticError{str: "invalid UTF-8 within string"}, ""}, 239 }, 240 }, { 241 name: name("TruncatedNumber"), 242 calls: []encoderMethodCall{ 243 {RawValue(`0.`), io.ErrUnexpectedEOF, ""}, 244 }, 245 }, { 246 name: name("InvalidNumber"), 247 calls: []encoderMethodCall{ 248 {RawValue(`0.e`), newInvalidCharacterError([]byte("e"), "within number (expecting digit)"), ""}, 249 }, 250 }, { 251 name: name("TruncatedObject/AfterStart"), 252 calls: []encoderMethodCall{ 253 {RawValue(`{`), io.ErrUnexpectedEOF, ""}, 254 }, 255 }, { 256 name: name("TruncatedObject/AfterName"), 257 calls: []encoderMethodCall{ 258 {RawValue(`{"0"`), io.ErrUnexpectedEOF, ""}, 259 }, 260 }, { 261 name: name("TruncatedObject/AfterColon"), 262 calls: []encoderMethodCall{ 263 {RawValue(`{"0":`), io.ErrUnexpectedEOF, ""}, 264 }, 265 }, { 266 name: name("TruncatedObject/AfterValue"), 267 calls: []encoderMethodCall{ 268 {RawValue(`{"0":0`), io.ErrUnexpectedEOF, ""}, 269 }, 270 }, { 271 name: name("TruncatedObject/AfterComma"), 272 calls: []encoderMethodCall{ 273 {RawValue(`{"0":0,`), io.ErrUnexpectedEOF, ""}, 274 }, 275 }, { 276 name: name("InvalidObject/MissingColon"), 277 calls: []encoderMethodCall{ 278 {RawValue(` { "fizz" "buzz" } `), newInvalidCharacterError([]byte("\""), "after object name (expecting ':')"), ""}, 279 {RawValue(` { "fizz" , "buzz" } `), newInvalidCharacterError([]byte(","), "after object name (expecting ':')"), ""}, 280 }, 281 }, { 282 name: name("InvalidObject/MissingComma"), 283 calls: []encoderMethodCall{ 284 {RawValue(` { "fizz" : "buzz" "gazz" } `), newInvalidCharacterError([]byte("\""), "after object value (expecting ',' or '}')"), ""}, 285 {RawValue(` { "fizz" : "buzz" : "gazz" } `), newInvalidCharacterError([]byte(":"), "after object value (expecting ',' or '}')"), ""}, 286 }, 287 }, { 288 name: name("InvalidObject/ExtraComma"), 289 calls: []encoderMethodCall{ 290 {RawValue(` { , } `), newInvalidCharacterError([]byte(","), `at start of string (expecting '"')`), ""}, 291 {RawValue(` { "fizz" : "buzz" , } `), newInvalidCharacterError([]byte("}"), `at start of string (expecting '"')`), ""}, 292 }, 293 }, { 294 name: name("InvalidObject/InvalidName"), 295 calls: []encoderMethodCall{ 296 {RawValue(`{ null }`), newInvalidCharacterError([]byte("n"), `at start of string (expecting '"')`), ""}, 297 {RawValue(`{ false }`), newInvalidCharacterError([]byte("f"), `at start of string (expecting '"')`), ""}, 298 {RawValue(`{ true }`), newInvalidCharacterError([]byte("t"), `at start of string (expecting '"')`), ""}, 299 {RawValue(`{ 0 }`), newInvalidCharacterError([]byte("0"), `at start of string (expecting '"')`), ""}, 300 {RawValue(`{ {} }`), newInvalidCharacterError([]byte("{"), `at start of string (expecting '"')`), ""}, 301 {RawValue(`{ [] }`), newInvalidCharacterError([]byte("["), `at start of string (expecting '"')`), ""}, 302 {ObjectStart, nil, ""}, 303 {Null, errMissingName, ""}, 304 {RawValue(`null`), errMissingName, ""}, 305 {False, errMissingName, ""}, 306 {RawValue(`false`), errMissingName, ""}, 307 {True, errMissingName, ""}, 308 {RawValue(`true`), errMissingName, ""}, 309 {Uint(0), errMissingName, ""}, 310 {RawValue(`0`), errMissingName, ""}, 311 {ObjectStart, errMissingName, ""}, 312 {RawValue(`{}`), errMissingName, ""}, 313 {ArrayStart, errMissingName, ""}, 314 {RawValue(`[]`), errMissingName, ""}, 315 {ObjectEnd, nil, ""}, 316 }, 317 wantOut: "{}\n", 318 }, { 319 name: name("InvalidObject/InvalidValue"), 320 calls: []encoderMethodCall{ 321 {RawValue(`{ "0": x }`), newInvalidCharacterError([]byte("x"), `at start of value`), ""}, 322 }, 323 }, { 324 name: name("InvalidObject/MismatchingDelim"), 325 calls: []encoderMethodCall{ 326 {RawValue(` { ] `), newInvalidCharacterError([]byte("]"), `at start of string (expecting '"')`), ""}, 327 {RawValue(` { "0":0 ] `), newInvalidCharacterError([]byte("]"), `after object value (expecting ',' or '}')`), ""}, 328 {ObjectStart, nil, ""}, 329 {ArrayEnd, errMismatchDelim, ""}, 330 {RawValue(`]`), newInvalidCharacterError([]byte("]"), "at start of value"), ""}, 331 {ObjectEnd, nil, ""}, 332 }, 333 wantOut: "{}\n", 334 }, { 335 name: name("ValidObject/UniqueNames"), 336 calls: []encoderMethodCall{ 337 {ObjectStart, nil, ""}, 338 {String("0"), nil, ""}, 339 {Uint(0), nil, ""}, 340 {String("1"), nil, ""}, 341 {Uint(1), nil, ""}, 342 {ObjectEnd, nil, ""}, 343 {RawValue(` { "0" : 0 , "1" : 1 } `), nil, ""}, 344 }, 345 wantOut: `{"0":0,"1":1}` + "\n" + `{"0":0,"1":1}` + "\n", 346 }, { 347 name: name("ValidObject/DuplicateNames"), 348 opts: EncodeOptions{AllowDuplicateNames: true}, 349 calls: []encoderMethodCall{ 350 {ObjectStart, nil, ""}, 351 {String("0"), nil, ""}, 352 {Uint(0), nil, ""}, 353 {String("0"), nil, ""}, 354 {Uint(0), nil, ""}, 355 {ObjectEnd, nil, ""}, 356 {RawValue(` { "0" : 0 , "0" : 0 } `), nil, ""}, 357 }, 358 wantOut: `{"0":0,"0":0}` + "\n" + `{"0":0,"0":0}` + "\n", 359 }, { 360 name: name("InvalidObject/DuplicateNames"), 361 calls: []encoderMethodCall{ 362 {ObjectStart, nil, ""}, 363 {String("0"), nil, ""}, 364 {ObjectStart, nil, ""}, 365 {ObjectEnd, nil, ""}, 366 {String("0"), &SyntacticError{str: `duplicate name "0" in object`}, "/0"}, 367 {RawValue(`"0"`), &SyntacticError{str: `duplicate name "0" in object`}, "/0"}, 368 {String("1"), nil, ""}, 369 {ObjectStart, nil, ""}, 370 {ObjectEnd, nil, ""}, 371 {String("0"), &SyntacticError{str: `duplicate name "0" in object`}, "/1"}, 372 {RawValue(`"0"`), &SyntacticError{str: `duplicate name "0" in object`}, "/1"}, 373 {String("1"), &SyntacticError{str: `duplicate name "1" in object`}, "/1"}, 374 {RawValue(`"1"`), &SyntacticError{str: `duplicate name "1" in object`}, "/1"}, 375 {ObjectEnd, nil, ""}, 376 {RawValue(` { "0" : 0 , "1" : 1 , "0" : 0 } `), &SyntacticError{str: `duplicate name "0" in object`}, ""}, 377 }, 378 wantOut: `{"0":{},"1":{}}` + "\n", 379 }, { 380 name: name("TruncatedArray/AfterStart"), 381 calls: []encoderMethodCall{ 382 {RawValue(`[`), io.ErrUnexpectedEOF, ""}, 383 }, 384 }, { 385 name: name("TruncatedArray/AfterValue"), 386 calls: []encoderMethodCall{ 387 {RawValue(`[0`), io.ErrUnexpectedEOF, ""}, 388 }, 389 }, { 390 name: name("TruncatedArray/AfterComma"), 391 calls: []encoderMethodCall{ 392 {RawValue(`[0,`), io.ErrUnexpectedEOF, ""}, 393 }, 394 }, { 395 name: name("TruncatedArray/MissingComma"), 396 calls: []encoderMethodCall{ 397 {RawValue(` [ "fizz" "buzz" ] `), newInvalidCharacterError([]byte("\""), "after array value (expecting ',' or ']')"), ""}, 398 }, 399 }, { 400 name: name("InvalidArray/MismatchingDelim"), 401 calls: []encoderMethodCall{ 402 {RawValue(` [ } `), newInvalidCharacterError([]byte("}"), `at start of value`), ""}, 403 {ArrayStart, nil, ""}, 404 {ObjectEnd, errMismatchDelim, ""}, 405 {RawValue(`}`), newInvalidCharacterError([]byte("}"), "at start of value"), ""}, 406 {ArrayEnd, nil, ""}, 407 }, 408 wantOut: "[]\n", 409 }} 410 411 // TestEncoderErrors test that Encoder errors occur when we expect and 412 // leaves the Encoder in a consistent state. 413 func TestEncoderErrors(t *testing.T) { 414 for _, td := range encoderErrorTestdata { 415 t.Run(path.Join(td.name.name), func(t *testing.T) { 416 testEncoderErrors(t, td.name.where, td.opts, td.calls, td.wantOut) 417 }) 418 } 419 } 420 func testEncoderErrors(t *testing.T, where pc, opts EncodeOptions, calls []encoderMethodCall, wantOut string) { 421 dst := new(bytes.Buffer) 422 enc := opts.NewEncoder(dst) 423 for i, call := range calls { 424 var gotErr error 425 switch tokVal := call.in.(type) { 426 case Token: 427 gotErr = enc.WriteToken(tokVal) 428 case RawValue: 429 gotErr = enc.WriteValue(tokVal) 430 } 431 if !reflect.DeepEqual(gotErr, call.wantErr) { 432 t.Fatalf("%s: %d: error mismatch: got %#v, want %#v", where, i, gotErr, call.wantErr) 433 } 434 if call.wantPointer != "" { 435 gotPointer := enc.StackPointer() 436 if gotPointer != call.wantPointer { 437 t.Fatalf("%s: %d: Encoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer) 438 } 439 } 440 } 441 gotOut := dst.String() + string(enc.unflushedBuffer()) 442 if gotOut != wantOut { 443 t.Fatalf("%s: output mismatch:\ngot %q\nwant %q", where, gotOut, wantOut) 444 } 445 gotOffset := int(enc.OutputOffset()) 446 wantOffset := len(wantOut) 447 if gotOffset != wantOffset { 448 t.Fatalf("%s: Encoder.OutputOffset = %v, want %v", where, gotOffset, wantOffset) 449 } 450 } 451 452 func TestAppendString(t *testing.T) { 453 var ( 454 escapeNothing = func(r rune) bool { return false } 455 escapeHTML = func(r rune) bool { return r == '<' || r == '>' || r == '&' || r == '\u2028' || r == '\u2029' } 456 escapeNonASCII = func(r rune) bool { return r > unicode.MaxASCII } 457 escapeEverything = func(r rune) bool { return true } 458 ) 459 460 tests := []struct { 461 in string 462 escapeRune func(rune) bool 463 want string 464 wantErr error 465 wantErrUTF8 error 466 }{ 467 {"", nil, `""`, nil, nil}, 468 {"hello", nil, `"hello"`, nil, nil}, 469 {"\x00", nil, `"\u0000"`, nil, nil}, 470 {"\x1f", nil, `"\u001f"`, nil, nil}, 471 {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", nil, `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, nil, nil}, 472 {" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", nil, "\" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f\"", nil, nil}, 473 {"x\x80\ufffd", nil, "\"x\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 474 {"x\xff\ufffd", nil, "\"x\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 475 {"x\x80\ufffd", escapeNonASCII, "\"x\\ufffd\\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 476 {"x\xff\ufffd", escapeNonASCII, "\"x\\ufffd\\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 477 {"x\xc0", nil, "\"x\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 478 {"x\xc0\x80", nil, "\"x\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 479 {"x\xe0", nil, "\"x\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 480 {"x\xe0\x80", nil, "\"x\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 481 {"x\xe0\x80\x80", nil, "\"x\ufffd\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 482 {"x\xf0", nil, "\"x\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 483 {"x\xf0\x80", nil, "\"x\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 484 {"x\xf0\x80\x80", nil, "\"x\ufffd\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 485 {"x\xf0\x80\x80\x80", nil, "\"x\ufffd\ufffd\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 486 {"x\xed\xba\xad", nil, "\"x\ufffd\ufffd\ufffd\"", nil, &SyntacticError{str: "invalid UTF-8 within string"}}, 487 {"\"\\/\b\f\n\r\t", nil, `"\"\\/\b\f\n\r\t"`, nil, nil}, 488 {"\"\\/\b\f\n\r\t", escapeEverything, `"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, nil, nil}, 489 {"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).", nil, `"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)."`, nil, nil}, 490 {"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).", escapeNonASCII, `"\u0669(-\u032e\u032e\u0303-\u0303)\u06f6 \u0669(\u25cf\u032e\u032e\u0303\u2022\u0303)\u06f6 \u0669(\u0361\u0e4f\u032f\u0361\u0e4f)\u06f6 \u0669(-\u032e\u032e\u0303\u2022\u0303)."`, nil, nil}, 491 {"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).", escapeEverything, `"\u0669\u0028\u002d\u032e\u032e\u0303\u002d\u0303\u0029\u06f6\u0020\u0669\u0028\u25cf\u032e\u032e\u0303\u2022\u0303\u0029\u06f6\u0020\u0669\u0028\u0361\u0e4f\u032f\u0361\u0e4f\u0029\u06f6\u0020\u0669\u0028\u002d\u032e\u032e\u0303\u2022\u0303\u0029\u002e"`, nil, nil}, 492 {"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", nil, "\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", nil, nil}, 493 {"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", escapeEverything, `"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`, nil, nil}, 494 {"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", nil, "\"\\u0000\\u001f\u0020\\\"\u0026\u003c\u003e\\\\\u007f\u0080\u2028\u2029\ufffd\U0001f602\"", nil, nil}, 495 {"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", escapeNothing, "\"\\u0000\\u001f\u0020\\\"\u0026\u003c\u003e\\\\\u007f\u0080\u2028\u2029\ufffd\U0001f602\"", nil, nil}, 496 {"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", escapeHTML, "\"\\u0000\\u001f\u0020\\\"\\u0026\\u003c\\u003e\\\\\u007f\u0080\\u2028\\u2029\ufffd\U0001f602\"", nil, nil}, 497 {"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", escapeNonASCII, "\"\\u0000\\u001f\u0020\\\"\u0026\u003c\u003e\\\\\u007f\\u0080\\u2028\\u2029\\ufffd\\ud83d\\ude02\"", nil, nil}, 498 {"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", escapeEverything, "\"\\u0000\\u001f\\u0020\\u0022\\u0026\\u003c\\u003e\\u005c\\u007f\\u0080\\u2028\\u2029\\ufffd\\ud83d\\ude02\"", nil, nil}, 499 } 500 501 for _, tt := range tests { 502 t.Run("", func(t *testing.T) { 503 got, gotErr := appendString(nil, tt.in, false, tt.escapeRune) 504 if string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { 505 t.Errorf("appendString(nil, %q, false, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) 506 } 507 switch got, gotErr := appendString(nil, tt.in, true, tt.escapeRune); { 508 case tt.wantErrUTF8 == nil && (string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr)): 509 t.Errorf("appendString(nil, %q, true, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) 510 case tt.wantErrUTF8 != nil && (!strings.HasPrefix(tt.want, string(got)) || !reflect.DeepEqual(gotErr, tt.wantErrUTF8)): 511 t.Errorf("appendString(nil, %q, true, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErrUTF8) 512 } 513 }) 514 } 515 } 516 517 func TestAppendNumber(t *testing.T) { 518 tests := []struct { 519 in float64 520 want32 string 521 want64 string 522 }{ 523 {math.E, "2.7182817", "2.718281828459045"}, 524 {math.Pi, "3.1415927", "3.141592653589793"}, 525 {math.SmallestNonzeroFloat32, "1e-45", "1.401298464324817e-45"}, 526 {math.SmallestNonzeroFloat64, "0", "5e-324"}, 527 {math.MaxFloat32, "3.4028235e+38", "3.4028234663852886e+38"}, 528 {math.MaxFloat64, "", "1.7976931348623157e+308"}, 529 {0.1111111111111111, "0.11111111", "0.1111111111111111"}, 530 {0.2222222222222222, "0.22222222", "0.2222222222222222"}, 531 {0.3333333333333333, "0.33333334", "0.3333333333333333"}, 532 {0.4444444444444444, "0.44444445", "0.4444444444444444"}, 533 {0.5555555555555555, "0.5555556", "0.5555555555555555"}, 534 {0.6666666666666666, "0.6666667", "0.6666666666666666"}, 535 {0.7777777777777777, "0.7777778", "0.7777777777777777"}, 536 {0.8888888888888888, "0.8888889", "0.8888888888888888"}, 537 {0.9999999999999999, "1", "0.9999999999999999"}, 538 539 // The following entries are from RFC 8785, appendix B 540 // which are designed to ensure repeatable formatting of 64-bit floats. 541 {math.Float64frombits(0x0000000000000000), "0", "0"}, 542 {math.Float64frombits(0x8000000000000000), "-0", "-0"}, // differs from RFC 8785 543 {math.Float64frombits(0x0000000000000001), "0", "5e-324"}, 544 {math.Float64frombits(0x8000000000000001), "-0", "-5e-324"}, 545 {math.Float64frombits(0x7fefffffffffffff), "", "1.7976931348623157e+308"}, 546 {math.Float64frombits(0xffefffffffffffff), "", "-1.7976931348623157e+308"}, 547 {math.Float64frombits(0x4340000000000000), "9007199000000000", "9007199254740992"}, 548 {math.Float64frombits(0xc340000000000000), "-9007199000000000", "-9007199254740992"}, 549 {math.Float64frombits(0x4430000000000000), "295147900000000000000", "295147905179352830000"}, 550 {math.Float64frombits(0x44b52d02c7e14af5), "1e+23", "9.999999999999997e+22"}, 551 {math.Float64frombits(0x44b52d02c7e14af6), "1e+23", "1e+23"}, 552 {math.Float64frombits(0x44b52d02c7e14af7), "1e+23", "1.0000000000000001e+23"}, 553 {math.Float64frombits(0x444b1ae4d6e2ef4e), "1e+21", "999999999999999700000"}, 554 {math.Float64frombits(0x444b1ae4d6e2ef4f), "1e+21", "999999999999999900000"}, 555 {math.Float64frombits(0x444b1ae4d6e2ef50), "1e+21", "1e+21"}, 556 {math.Float64frombits(0x3eb0c6f7a0b5ed8c), "0.000001", "9.999999999999997e-7"}, 557 {math.Float64frombits(0x3eb0c6f7a0b5ed8d), "0.000001", "0.000001"}, 558 {math.Float64frombits(0x41b3de4355555553), "333333340", "333333333.3333332"}, 559 {math.Float64frombits(0x41b3de4355555554), "333333340", "333333333.33333325"}, 560 {math.Float64frombits(0x41b3de4355555555), "333333340", "333333333.3333333"}, 561 {math.Float64frombits(0x41b3de4355555556), "333333340", "333333333.3333334"}, 562 {math.Float64frombits(0x41b3de4355555557), "333333340", "333333333.33333343"}, 563 {math.Float64frombits(0xbecbf647612f3696), "-0.0000033333333", "-0.0000033333333333333333"}, 564 {math.Float64frombits(0x43143ff3c1cb0959), "1424953900000000", "1424953923781206.2"}, 565 566 // The following are select entries from RFC 8785, appendix B, 567 // but modified for equivalent 32-bit behavior. 568 {float64(math.Float32frombits(0x65a96815)), "9.999999e+22", "9.999998877476383e+22"}, 569 {float64(math.Float32frombits(0x65a96816)), "1e+23", "9.999999778196308e+22"}, 570 {float64(math.Float32frombits(0x65a96817)), "1.0000001e+23", "1.0000000678916234e+23"}, 571 {float64(math.Float32frombits(0x6258d725)), "999999900000000000000", "999999879303389000000"}, 572 {float64(math.Float32frombits(0x6258d726)), "999999950000000000000", "999999949672133200000"}, 573 {float64(math.Float32frombits(0x6258d727)), "1e+21", "1.0000000200408773e+21"}, 574 {float64(math.Float32frombits(0x6258d728)), "1.0000001e+21", "1.0000000904096215e+21"}, 575 {float64(math.Float32frombits(0x358637bc)), "9.999999e-7", "9.99999883788405e-7"}, 576 {float64(math.Float32frombits(0x358637bd)), "0.000001", "9.999999974752427e-7"}, 577 {float64(math.Float32frombits(0x358637be)), "0.0000010000001", "0.0000010000001111620804"}, 578 } 579 580 for _, tt := range tests { 581 t.Run("", func(t *testing.T) { 582 if got32 := string(appendNumber(nil, tt.in, 32)); got32 != tt.want32 && tt.want32 != "" { 583 t.Errorf("appendNumber(nil, %v, 32) = %v, want %v", tt.in, got32, tt.want32) 584 } 585 if got64 := string(appendNumber(nil, tt.in, 64)); got64 != tt.want64 && tt.want64 != "" { 586 t.Errorf("appendNumber(nil, %v, 64) = %v, want %v", tt.in, got64, tt.want64) 587 } 588 }) 589 } 590 } 591 592 // The default of 1e4 lines was chosen since it is sufficiently large to include 593 // test numbers from all three categories (i.e., static, series, and random). 594 // Yet, it is sufficiently low to execute quickly relative to other tests. 595 // 596 // Processing 1e8 lines takes a minute and processes about 4GiB worth of text. 597 var testCanonicalNumberLines = flag.Float64("canonical-number-lines", 1e4, "specify the number of lines to check from the canonical numbers testdata") 598 599 // TestCanonicalNumber verifies that appendNumber complies with RFC 8785 600 // according to the testdata provided by the reference implementation. 601 // See https://github.com/cyberphone/json-canonicalization/tree/master/testdata#es6-numbers. 602 func TestCanonicalNumber(t *testing.T) { 603 const testfileURL = "https://github.com/cyberphone/json-canonicalization/releases/download/es6testfile/es6testfile100m.txt.gz" 604 hashes := map[float64]string{ 605 1e3: "be18b62b6f69cdab33a7e0dae0d9cfa869fda80ddc712221570f9f40a5878687", 606 1e4: "b9f7a8e75ef22a835685a52ccba7f7d6bdc99e34b010992cbc5864cd12be6892", 607 1e5: "22776e6d4b49fa294a0d0f349268e5c28808fe7e0cb2bcbe28f63894e494d4c7", 608 1e6: "49415fee2c56c77864931bd3624faad425c3c577d6d74e89a83bc725506dad16", 609 1e7: "b9f8a44a91d46813b21b9602e72f112613c91408db0b8341fb94603d9db135e0", 610 1e8: "0f7dda6b0837dde083c5d6b896f7d62340c8a2415b0c7121d83145e08a755272", 611 } 612 wantHash := hashes[*testCanonicalNumberLines] 613 if wantHash == "" { 614 t.Fatalf("canonical-number-lines must be one of the following values: 1e3, 1e4, 1e5, 1e6, 1e7, 1e8") 615 } 616 numLines := int(*testCanonicalNumberLines) 617 618 // generator returns a function that generates the next float64 to format. 619 // This implements the algorithm specified in the reference implementation. 620 generator := func() func() float64 { 621 static := [...]uint64{ 622 0x0000000000000000, 0x8000000000000000, 0x0000000000000001, 0x8000000000000001, 623 0xc46696695dbd1cc3, 0xc43211ede4974a35, 0xc3fce97ca0f21056, 0xc3c7213080c1a6ac, 624 0xc39280f39a348556, 0xc35d9b1f5d20d557, 0xc327af4c4a80aaac, 0xc2f2f2a36ecd5556, 625 0xc2be51057e155558, 0xc28840d131aaaaac, 0xc253670dc1555557, 0xc21f0b4935555557, 626 0xc1e8d5d42aaaaaac, 0xc1b3de4355555556, 0xc17fca0555555556, 0xc1496e6aaaaaaaab, 627 0xc114585555555555, 0xc0e046aaaaaaaaab, 0xc0aa0aaaaaaaaaaa, 0xc074d55555555555, 628 0xc040aaaaaaaaaaab, 0xc00aaaaaaaaaaaab, 0xbfd5555555555555, 0xbfa1111111111111, 629 0xbf6b4e81b4e81b4f, 0xbf35d867c3ece2a5, 0xbf0179ec9cbd821e, 0xbecbf647612f3696, 630 0xbe965e9f80f29212, 0xbe61e54c672874db, 0xbe2ca213d840baf8, 0xbdf6e80fe033c8c6, 631 0xbdc2533fe68fd3d2, 0xbd8d51ffd74c861c, 0xbd5774ccac3d3817, 0xbd22c3d6f030f9ac, 632 0xbcee0624b3818f79, 0xbcb804ea293472c7, 0xbc833721ba905bd3, 0xbc4ebe9c5db3c61e, 633 0xbc18987d17c304e5, 0xbbe3ad30dfcf371d, 0xbbaf7b816618582f, 0xbb792f9ab81379bf, 634 0xbb442615600f9499, 0xbb101e77800c76e1, 0xbad9ca58cce0be35, 0xbaa4a1e0a3e6fe90, 635 0xba708180831f320d, 0xba3a68cd9e985016, 0x446696695dbd1cc3, 0x443211ede4974a35, 636 0x43fce97ca0f21056, 0x43c7213080c1a6ac, 0x439280f39a348556, 0x435d9b1f5d20d557, 637 0x4327af4c4a80aaac, 0x42f2f2a36ecd5556, 0x42be51057e155558, 0x428840d131aaaaac, 638 0x4253670dc1555557, 0x421f0b4935555557, 0x41e8d5d42aaaaaac, 0x41b3de4355555556, 639 0x417fca0555555556, 0x41496e6aaaaaaaab, 0x4114585555555555, 0x40e046aaaaaaaaab, 640 0x40aa0aaaaaaaaaaa, 0x4074d55555555555, 0x4040aaaaaaaaaaab, 0x400aaaaaaaaaaaab, 641 0x3fd5555555555555, 0x3fa1111111111111, 0x3f6b4e81b4e81b4f, 0x3f35d867c3ece2a5, 642 0x3f0179ec9cbd821e, 0x3ecbf647612f3696, 0x3e965e9f80f29212, 0x3e61e54c672874db, 643 0x3e2ca213d840baf8, 0x3df6e80fe033c8c6, 0x3dc2533fe68fd3d2, 0x3d8d51ffd74c861c, 644 0x3d5774ccac3d3817, 0x3d22c3d6f030f9ac, 0x3cee0624b3818f79, 0x3cb804ea293472c7, 645 0x3c833721ba905bd3, 0x3c4ebe9c5db3c61e, 0x3c18987d17c304e5, 0x3be3ad30dfcf371d, 646 0x3baf7b816618582f, 0x3b792f9ab81379bf, 0x3b442615600f9499, 0x3b101e77800c76e1, 647 0x3ad9ca58cce0be35, 0x3aa4a1e0a3e6fe90, 0x3a708180831f320d, 0x3a3a68cd9e985016, 648 0x4024000000000000, 0x4014000000000000, 0x3fe0000000000000, 0x3fa999999999999a, 649 0x3f747ae147ae147b, 0x3f40624dd2f1a9fc, 0x3f0a36e2eb1c432d, 0x3ed4f8b588e368f1, 650 0x3ea0c6f7a0b5ed8d, 0x3e6ad7f29abcaf48, 0x3e35798ee2308c3a, 0x3ed539223589fa95, 651 0x3ed4ff26cd5a7781, 0x3ed4f95a762283ff, 0x3ed4f8c60703520c, 0x3ed4f8b72f19cd0d, 652 0x3ed4f8b5b31c0c8d, 0x3ed4f8b58d1c461a, 0x3ed4f8b5894f7f0e, 0x3ed4f8b588ee37f3, 653 0x3ed4f8b588e47da4, 0x3ed4f8b588e3849c, 0x3ed4f8b588e36bb5, 0x3ed4f8b588e36937, 654 0x3ed4f8b588e368f8, 0x3ed4f8b588e368f1, 0x3ff0000000000000, 0xbff0000000000000, 655 0xbfeffffffffffffa, 0xbfeffffffffffffb, 0x3feffffffffffffa, 0x3feffffffffffffb, 656 0x3feffffffffffffc, 0x3feffffffffffffe, 0xbfefffffffffffff, 0xbfefffffffffffff, 657 0x3fefffffffffffff, 0x3fefffffffffffff, 0x3fd3333333333332, 0x3fd3333333333333, 658 0x3fd3333333333334, 0x0010000000000000, 0x000ffffffffffffd, 0x000fffffffffffff, 659 0x7fefffffffffffff, 0xffefffffffffffff, 0x4340000000000000, 0xc340000000000000, 660 0x4430000000000000, 0x44b52d02c7e14af5, 0x44b52d02c7e14af6, 0x44b52d02c7e14af7, 661 0x444b1ae4d6e2ef4e, 0x444b1ae4d6e2ef4f, 0x444b1ae4d6e2ef50, 0x3eb0c6f7a0b5ed8c, 662 0x3eb0c6f7a0b5ed8d, 0x41b3de4355555553, 0x41b3de4355555554, 0x41b3de4355555555, 663 0x41b3de4355555556, 0x41b3de4355555557, 0xbecbf647612f3696, 0x43143ff3c1cb0959, 664 } 665 var state struct { 666 idx int 667 data []byte 668 block [sha256.Size]byte 669 } 670 return func() float64 { 671 const numSerial = 2000 672 var f float64 673 switch { 674 case state.idx < len(static): 675 f = math.Float64frombits(static[state.idx]) 676 case state.idx < len(static)+numSerial: 677 f = math.Float64frombits(0x0010000000000000 + uint64(state.idx-len(static))) 678 default: 679 for f == 0 || math.IsNaN(f) || math.IsInf(f, 0) { 680 if len(state.data) == 0 { 681 state.block = sha256.Sum256(state.block[:]) 682 state.data = state.block[:] 683 } 684 f = math.Float64frombits(binary.LittleEndian.Uint64(state.data)) 685 state.data = state.data[8:] 686 } 687 } 688 state.idx++ 689 return f 690 } 691 } 692 693 // Pass through the test twice. In the first pass we only hash the output, 694 // while in the second pass we check every line against the golden testdata. 695 // If the hashes match in the first pass, then we skip the second pass. 696 for _, checkGolden := range []bool{false, true} { 697 var br *bufio.Reader // for line-by-line reading of es6testfile100m.txt 698 if checkGolden { 699 resp, err := http.Get(testfileURL) 700 if err != nil { 701 t.Fatalf("http.Get error: %v", err) 702 } 703 defer resp.Body.Close() 704 705 zr, err := gzip.NewReader(resp.Body) 706 if err != nil { 707 t.Fatalf("gzip.NewReader error: %v", err) 708 } 709 710 br = bufio.NewReader(zr) 711 } 712 713 // appendNumberJCS differs from appendNumber only for -0. 714 appendNumberJCS := func(b []byte, f float64) []byte { 715 if math.Signbit(f) && f == 0 { 716 return append(b, '0') 717 } 718 return appendNumber(b, f, 64) 719 } 720 721 var gotLine []byte 722 next := generator() 723 hash := sha256.New() 724 start := time.Now() 725 lastPrint := start 726 for n := 1; n <= numLines; n++ { 727 // Generate the formatted line for this number. 728 f := next() 729 gotLine = gotLine[:0] // reset from previous usage 730 gotLine = strconv.AppendUint(gotLine, math.Float64bits(f), 16) 731 gotLine = append(gotLine, ',') 732 gotLine = appendNumberJCS(gotLine, f) 733 gotLine = append(gotLine, '\n') 734 hash.Write(gotLine) 735 736 // Check that the formatted line matches. 737 if checkGolden { 738 wantLine, err := br.ReadBytes('\n') 739 if err != nil { 740 t.Fatalf("bufio.Reader.ReadBytes error: %v", err) 741 } 742 if !bytes.Equal(gotLine, wantLine) { 743 t.Errorf("mismatch on line %d:\n\tgot %v\n\twant %v", 744 n, strings.TrimSpace(string(gotLine)), strings.TrimSpace(string(wantLine))) 745 } 746 } 747 748 // Print progress. 749 if now := time.Now(); now.Sub(lastPrint) > time.Second || n == numLines { 750 remaining := float64(now.Sub(start)) * float64(numLines-n) / float64(n) 751 t.Logf("%0.3f%% (%v remaining)", 752 100.0*float64(n)/float64(numLines), 753 time.Duration(remaining).Round(time.Second)) 754 lastPrint = now 755 } 756 } 757 758 gotHash := hex.EncodeToString(hash.Sum(nil)) 759 if gotHash == wantHash { 760 return // hashes match, no need to check golden testdata 761 } 762 } 763 }