Skip to content

Commit

Permalink
test: Add tests from JSONTestSuite
Browse files Browse the repository at this point in the history
  • Loading branch information
romshark committed Mar 28, 2023
1 parent c4cee51 commit d527324
Show file tree
Hide file tree
Showing 326 changed files with 539 additions and 74 deletions.
116 changes: 57 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
</a>

# jscan
[jscan](https://github.com/romshark/jscan) provides a high-performance zero-allocation JSON iterator for Go. It's **not** compatible with [encoding/json](https://pkg.go.dev/encoding/json) and doesn't provide the usual Marshal/Unmarshal capabilities, instead it focuses on fast and efficient scanning over JSON strings with on-the-fly validation.
jscan provides a high-performance zero-allocation JSON iterator for Go. It's **not** compatible with [encoding/json](https://pkg.go.dev/encoding/json) and doesn't provide the usual `Marshal`/`Unmarshal` capabilities, instead it focuses on fast and efficient scanning over JSON strings with on-the-fly validation and error reporting.

jscan is tested against https://github.com/nst/JSONTestSuite, a comprehensive test suite for RFC 8259 compliant JSON parsers.

## Example
https://go.dev/play/p/v-VeiMO2fsJ
Expand Down Expand Up @@ -81,7 +83,7 @@ func main() {

## Benchmark Results

The following results were recorded on an Apple M1 Max MBP running macOS 12.1
The following results were recorded on an Apple M1 Max MBP running macOS 13.2.1

```
goos: darwin
Expand All @@ -90,103 +92,88 @@ goarch: arm64

|package|version|
|-|-|
|pkg.go.dev/encoding/json|[go1.20.2](https://pkg.go.dev/encoding/json)|
|github.com/go-faster/jx|[v1.0.0](https://github.com/go-faster/jx/releases/tag/v1.0.0)|
|github.com/go-faster/jx|[v1.0.0](https://github.com/go-faster/jx/releases/tag/v1.0.0)|
|github.com/json-iterator/go|[v1.1.12](https://github.com/json-iterator/go/releases/tag/v1.1.12)|
|github.com/sinhashubham95/jsonic|[v1.1.0](https://github.com/sinhashubham95/jsonic/releases/tag/v1.1.0)|
|github.com/tidwall/gjson|[v1.14.4](https://github.com/tidwall/gjson/releases/tag/v1.14.4)|
|github.com/valyala/fastjson|[v1.6.4](https://github.com/valyala/fastjson/releases/tag/v1.6.4)|

Tiny JSON document (`{"x":0}`):
Calculating statistics for a tiny JSON document (`{"x":0}`):

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 46.22 ns/op | 0 B/op | 0 allocs/op |
| jscan | 46.39 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 46.39 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 85.98 ns/op | 160 B/op | 2 allocs/op |
| gofaster-jx | 54.21 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 50.17 ns/op | 0 B/op | 0 allocs/op |
| jscan_withpath | 61.20 ns/op | 0 B/op | 0 allocs/op |
| jsoniter_withpath | 92.64 ns/op | 160 B/op | 2 allocs/op |
| gofaster-jx_withpath | 64.24 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson_withpath | 73.10 ns/op | 8 B/op | 1 allocs/op |


Small JSON document (335 bytes):
Calculating statistics for a small JSON document (335 bytes):

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 506.7 ns/op | 0 B/op | 0 allocs/op |
| jscan | 486.3 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 768.7 ns/op | 224 B/op | 12 allocs/op |
| gofaster-jx | 561.2 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 553.0 ns/op | 0 B/op | 0 allocs/op |
| jscan_withpath | 670.7 ns/op | 0 B/op | 0 allocs/op |
| jsoniter_withpath | 1091 ns/op | 288 B/op | 21 allocs/op |
| gofaster-jx_withpath | 934.6 ns/op | 80 B/op | 13 allocs/op |
| valyala-fastjson_withpath | 784.7 ns/op | 88 B/op | 10 allocs/op |

Large JSON document (26.1 MB):
Calculating statistics for a large JSON document (26.1 MB):

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan/ | 24938751 ns/op | 28 B/op | 0 allocs/op |
| jsoniter/ | 54856708 ns/op | 32851643 B/op | 1108519 allocs/op |
| gofaster-jx/ | 27957946 ns/op | 30 B/op | 0 allocs/op |
| valyala-fastjson/ | 28625101 ns/op | 35 B/op | 0 allocs/op |
| jscan_withpath/ | 30114974 ns/op | 33 B/op | 0 allocs/op |
| jsoniter_withpath/ | 74731725 ns/op | 55597093 B/op | 1757365 allocs/op |
| gofaster-jx_withpath/ | 61522048 ns/op | 51623144 B/op | 1533007 allocs/op |
| valyala-fastjson_withpath/ | 37039634 ns/op | 13135410 B/op | 325005 allocs/op |
| jscan | 24468140 ns/op | 6 B/op | 0 allocs/op |
| jsoniter | 54457668 ns/op | 32851588 B/op | 1108519 allocs/op |
| gofaster-jx | 28043963 ns/op | 8 B/op | 0 allocs/op |
| valyala-fastjson | 28241526 ns/op | 7 B/op | 0 allocs/op |

Calculating statistics for an object that contains a key and a string value consisting entirely of escape sequences (~3KB):

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 1891 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 7862 ns/op | 2208 B/op | 16 allocs/op |
| gofaster-jx | 6677 ns/op | 504 B/op | 6 allocs/op |
| valyala-fastjson | 11292 ns/op | 0 B/op | 0 allocs/op |


Array of 1024 integers:

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 20441 ns/op | 0 B/op | 0 allocs/op |
| jscan | 20105 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 38419 ns/op | 16528 B/op | 1025 allocs/op |
| gofaster-jx | 29924 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 21763 ns/op | 0 B/op | 0 allocs/op |
| jscan_withpath | 30549 ns/op | 0 B/op | 0 allocs/op |
| jsoniter_withpath | 82523 ns/op | 24496 B/op | 2973 allocs/op |
| gofaster-jx_withpath | 75776 ns/op | 7970 B/op | 1948 allocs/op |
| valyala-fastjson_withpath | 42226 ns/op | 8207 B/op | 1024 allocs/op |

Array of 1024 floating point numbers:

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 19628 ns/op | 0 B/op | 0 allocs/op |
| jscan | 19196 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 43730 ns/op | 16528 B/op | 1025 allocs/op |
| gofaster-jx | 37036 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 21320 ns/op | 6 B/op | 0 allocs/op |
| jscan_withpath | 34130 ns/op | 0 B/op | 0 allocs/op |
| jsoniter_withpath | 88569 ns/op | 24496 B/op | 2973 allocs/op |
| gofaster-jx_withpath | 91708 ns/op | 7970 B/op | 1948 allocs/op |
| valyala-fastjson_withpath | 42265 ns/op | 8207 B/op | 1024 allocs/op |

Array of 1024 strings:

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 277372 ns/op | 0 B/op | 0 allocs/op |
| jscan | 267332 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 585148 ns/op | 670313 B/op | 1019 allocs/op |
| gofaster-jx | 166966 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 61301 ns/op | 50 B/op | 0 allocs/op |
| jscan_withpath | 287013 ns/op | 0 B/op | 0 allocs/op |
| jsoniter_withpath | 629999 ns/op | 678286 B/op | 2967 allocs/op |
| gofaster-jx_withpath | 281957 ns/op | 677639 B/op | 2927 allocs/op |
| valyala-fastjson_withpath | 80256 ns/op | 8261 B/op | 1024 allocs/op |

Array of 1024 nullable booleans:

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 12308 ns/op | 0 B/op | 0 allocs/op |
| jscan | 11592 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 21736 ns/op | 144 B/op | 1 allocs/op |
| gofaster-jx | 33209 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 10913 ns/op | 0 B/op | 0 allocs/op |
| jscan_withpath | 22391 ns/op | 0 B/op | 0 allocs/op |
| jsoniter_withpath | 62427 ns/op | 8112 B/op | 1949 allocs/op |
| gofaster-jx_withpath | 65697 ns/op | 7970 B/op | 1948 allocs/op |
| valyala-fastjson_withpath | 31945 ns/op | 8194 B/op | 1024 allocs/op |

Get by path:

Expand All @@ -204,86 +191,97 @@ Tiny

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 35.17 ns/op | 0 B/op | 0 allocs/op |
| jscan | 35.13 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 42.93 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 45.14 ns/op | 0 B/op | 0 allocs/op |
| gofaster-jx | 39.19 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 42.53 ns/op | 0 B/op | 0 allocs/op |
| tidwallgjson | 15.85 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 19.25 ns/op | 0 B/op | 0 allocs/op |

Small

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 411.0 ns/op | 0 B/op | 0 allocs/op |
| jscan | 391.5 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 903.8 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 717.3 ns/op | 56 B/op | 7 allocs/op |
| gofaster-jx | 392.2 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 904.0 ns/op | 0 B/op | 0 allocs/op |
| tidwallgjson | 336.0 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 372.0 ns/op | 0 B/op | 0 allocs/op |

Large

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 22007394 ns/op | 23 B/op | 0 allocs/op |
| jscan | 21481452 ns/op | 7 B/op | 0 allocs/op |
| encoding-json | 68749373 ns/op | 29 B/op | 0 allocs/op |
| jsoniter | 44151891 ns/op | 13583525 B/op | 644362 allocs/op |
| gofaster-jx | 20625554 ns/op | 24 B/op | 0 allocs/op |
| encoding-json | 68774883 ns/op | 92 B/op | 0 allocs/op |
| tidwallgjson | 27378477 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 25695158 ns/op | 0 B/op | 0 allocs/op |

Validating an object that contains a key and a string value consisting entirely of escape sequences (~3KB):

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 1879 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 9313 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 7858 ns/op | 2064 B/op | 15 allocs/op |
| gofaster-jx | 5897 ns/op | 0 B/op | 0 allocs/op |
| tidwallgjson | 2989 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 9062 ns/op | 0 B/op | 0 allocs/op |

Unwinding Stack

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 2612 ns/op | 0 B/op | 0 allocs/op |
| jscan | 2614 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 5133 ns/op | 24 B/op | 1 allocs/op |
| jsoniter | 65817 ns/op | 33149 B/op | 1033 allocs/op |
| gofaster-jx | 398530 ns/op | 65687 B/op | 1026 allocs/op |
| encoding-json | 5133 ns/op | 24 B/op | 1 allocs/op |
| tidwallgjson | 14033 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 4876865 ns/op | 52431864 B/op | 4134 allocs/op |

Array of 1024 integers

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 17942 ns/op | 0 B/op | 0 allocs/op |
| jscan | 17170 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 34104 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 21317 ns/op | 0 B/op | 0 allocs/op |
| gofaster-jx | 17843 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 33607 ns/op | 0 B/op | 0 allocs/op |
| tidwallgjson | 13525 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 14741 ns/op | 0 B/op | 0 allocs/op |

Array of 1024 floats

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 12755 ns/op | 0 B/op | 0 allocs/op |
| jscan | 12925 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 36390 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 66119 ns/op | 8755 B/op | 547 allocs/op |
| gofaster-jx | 20111 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 35978 ns/op | 0 B/op | 0 allocs/op |
| tidwallgjson | 12176 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 17346 ns/op | 0 B/op | 0 allocs/op |

Array of 1024 nullable booleans

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 5483 ns/op | 0 B/op | 0 allocs/op |
| jscan | 4968 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 20239 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 16538 ns/op | 0 B/op | 0 allocs/op |
| gofaster-jx | 20987 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 20309 ns/op | 0 B/op | 0 allocs/op |
| tidwallgjson | 5081 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 4891 ns/op | 0 B/op | 0 allocs/op |

Array of 1024 strings

|implementation|ns/op|B/op|allocs/op|
|-|-|-|-|
| jscan | 275319 ns/op | 0 B/op | 0 allocs/op |
| jscan | 264281 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 1391270 ns/op | 0 B/op | 0 allocs/op |
| jsoniter | 507315 ns/op | 0 B/op | 0 allocs/op |
| gofaster-jx | 152812 ns/op | 0 B/op | 0 allocs/op |
| encoding-json | 1391323 ns/op | 1 B/op | 0 allocs/op |
| tidwallgjson | 501928 ns/op | 0 B/op | 0 allocs/op |
| valyala-fastjson | 253798 ns/op | 0 B/op | 0 allocs/op |
33 changes: 21 additions & 12 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package jscan_test
import (
"bytes"
_ "embed"
"encoding/json"
encodingjson "encoding/json"
"fmt"
"strconv"
Expand Down Expand Up @@ -631,6 +632,9 @@ func TestImplementationsWithPath(t *testing.T) {

var gs Stats

//go:embed miniscule.json
var jsonMiniscule []byte

//go:embed tiny.json
var jsonTiny []byte

Expand All @@ -640,6 +644,9 @@ var jsonSmall []byte
//go:embed large.json
var jsonLarge []byte

//go:embed escaped.json
var jsonEscaped []byte

//go:embed array_int_1024.json
var jsonArrayInt1024 []byte

Expand Down Expand Up @@ -695,9 +702,11 @@ func BenchmarkCalcStats(b *testing.B) {
name string
json []byte
}{
{"miniscule", jsonMiniscule},
{"tiny", jsonTiny},
{"small", jsonSmall},
{"large", jsonLarge},
{"escaped", jsonEscaped},
{"array_int_1024", jsonArrayInt1024},
{"array_dec_1024", jsonArrayDec1024},
{"array_nullbool_1024", jsonArrayNullBool1024},
Expand Down Expand Up @@ -826,14 +835,14 @@ func TestValid(t *testing.T) {
require.True(t, jscan.Valid(j))
})

t.Run("jsoniter", func(t *testing.T) {
require.True(t, jsoniter.Valid([]byte(j)))
})

t.Run("encoding-json", func(t *testing.T) {
require.True(t, encodingjson.Valid([]byte(j)))
})

t.Run("jsoniter", func(t *testing.T) {
require.True(t, jsoniter.Valid([]byte(j)))
})

t.Run("tidwallgjson", func(t *testing.T) {
require.True(t, tidwallgjson.Valid(j))
})
Expand All @@ -850,9 +859,11 @@ func BenchmarkValid(b *testing.B) {
name string
input []byte
}{
{"miniscule", jsonMiniscule},
{"tiny", jsonTiny},
{"small", jsonSmall},
{"large", jsonLarge},
{"escaped", jsonEscaped},
{"unwind_stack", MakeRepeated("[", 1024)},
{"array_int_1024", jsonArrayInt1024},
{"array_dec_1024", jsonArrayDec1024},
Expand All @@ -866,27 +877,25 @@ func BenchmarkValid(b *testing.B) {
}
})

b.Run("jsoniter", func(b *testing.B) {
jb := []byte(bb.input)
b.ResetTimer()
b.Run("encoding-json", func(b *testing.B) {
for i := 0; i < b.N; i++ {
gbool = jsoniter.Valid(jb)
gbool = json.Valid(bb.input)
}
})

b.Run("gofaster-jx", func(b *testing.B) {
b.Run("jsoniter", func(b *testing.B) {
jb := []byte(bb.input)
b.ResetTimer()
for i := 0; i < b.N; i++ {
gbool = gofasterjx.Valid(jb)
gbool = jsoniter.Valid(jb)
}
})

b.Run("encoding-json", func(b *testing.B) {
b.Run("gofaster-jx", func(b *testing.B) {
jb := []byte(bb.input)
b.ResetTimer()
for i := 0; i < b.N; i++ {
gbool = encodingjson.Valid(jb)
gbool = gofasterjx.Valid(jb)
}
})

Expand Down
4 changes: 4 additions & 0 deletions escaped.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"\u005c\u0074\u0068\u0069\u0073\u0020\u0069\u0073\u0020\u0061\u0020\u0076\u0065\u0072\u0079\u0020\u006c\u006f\u006e\u0067\u0020\u0053\u0074\u0072\u0069\u006e\u0067\u0020\u0077\u0068\u0069\u0063\u0068\u0020\u0063\u006f\u006e\u0074\u0061\u0069\u006e\u0073\u0020\u006f\u006e\u006c\u0079\u0020\u0065\u0073\u0063\u0061\u0070\u0065\u0020\u0073\u0065\u0071\u0075\u0065\u006e\u0063\u0065\u0073\u002e\u0020\u0054\u0068\u0065\u0072\u0065\u0020\u0061\u0072\u0065\u0020\u006e\u006f\u0020\u0074\u0065\u0078\u0074\u0020\u0063\u0068\u0061\u0072\u0061\u0063\u0074\u0065\u0072\u0073\u0020\u006f\u0072\u0020\u0075\u006e\u0065\u0073\u0063\u0061\u0070\u0065\u0020\u0073\u0065\u0071\u0075\u0065\u006e\u0063\u0065\u0073\u0020\u0069\u006e\u0020\u0074\u0068\u0069\u0073\u0020\u006c\u006f\u006e\u0067\u0020\u0073\u0074\u0072\u0069\u006e\u0067\u0020\u0076\u0061\u006c\u0075\u0065\u002e\u0020\u0054\u0068\u0069\u0073\u0020\u0073\u0074\u0072\u0069\u006e\u0067\u0020\u0063\u006f\u006e\u0074\u0061\u0069\u006e\u0073\u0020\u006f\u006e\u006c\u0079\u0020\u0065\u0073\u0063\u0061\u0070\u0065\u0020\u0073\u0065\u0071\u0075\u0065\u006e\u0063\u0065\u0073\u0020\u0062\u0061\u0063\u006b\u0073\u006c\u0061\u0073\u0068\u0065\u0073\u0020\u006f\u0072\r\n\r\n\r\n\t\t\/\b\f\"\"":
"\u0020\u006d\u0075\u006c\"\"\u0074\u0069\u0062\u0079\u0074\u0065\u0073\u0020\u006f\u0066\u0020\u0065\u0073\u0063\u0061\u0070\u0065\u0020\u0073\u0065\u0071\u0075\u0065\u006e\u0063\u0065\u0073\u0020\u0061\u006e\u0064\u0020\u0074\u0068\u0065\u0020\u0063\u006f\u0072\u0065\u0073\u0070\u006f\u006e\u0073\u0065\u0073\u002e\u0020\u0054\u0068\u0065\u0020\u0073\u0074\u0072\u0069\u006e\u0067\u0020\u0063\u006f\u006e\u0074\u0061\u0069\u006e\u0073\u0020\u006d\u0075\u006c\u0074\u0069\u0070\u006c\u0065\u0020\u006c\u0069\u006e\u0065\u0073\u0020\u006f\u0066\u0020\u0065\u0073\u0063\u0061\u0070\u0065\u0020\u0073\u0065\u0071\u0075\u0065\u006e\u0063\u0065\u0073\u0020\u0061\u0072\u0065\u0020\u006e\u006f\u0074\u0020\u0072\u0065\u0070\u0072\u0065\u0073\u0065\u006e\u0074\u0065\u0064\u0020\u0062\u0079\u0020\u0061\u006e\u0079\u0020\u0070\u0072\u006f\u0067\u0072\u0061\u006d\u002e\u0020\u0054\u0068\u0065\u0073\u0065\u0020\u0073\u0074\u0072\u0069\u006e\u0067\u0073\u0020\u0061\u0072\u0065\u0020\u006d\u0065\u0061\u006e\u0074\u0020\u0074\u006f\u0020\u0062\u0065\u0020\u0072\u0065\u0061\u0064\u0020\u0062\u0079\u0020\u0061\u006e\u0079\u0020\u0070\u0072\u006f\u0067\u0072\u0061\u006d\u0020\u0074\u0068\u0061\u0074\u0020\u0070\u0072\u006f\u0076\u0069\u0064\u0065\u0073\u0020\u0065\u0078\u0061\u006d\u0070\u006c\u0065\u0073\u0020\u006f\u0066\u0020\u0065\u0073\u0063\u0061\u0070\u0065\u0020\u0073\u0065\u0071\u0075\u0065\u006e\u0063\u0065\u0073\u002e\u0020\u0054\u0068\u0065\u0072\u0065\u0020\u0061\u0072\u0065\u0020\u006d\u0061\u006e\u0079\u0020\u006d\u006f\u0072\u0065\u0020\u0065\u0073\u0063\u0061\u0070\u0065\u0020\u0073\u0065\u0071\u0075\u0065\u006e\u0063\u0065\u0073\u0020\u0074\u0068\u0061\u0074\u0020\u0063\u0061\u006e\u0027\u0074\u0020\u0062\u0065\u0020\u0072\u0065\u0070\u0072\u0065\u0073\u0065\u006e"
}

0 comments on commit d527324

Please sign in to comment.