github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/jsp/io_test.go (about) 1 // Package jsp (JSON persistence) provides utilities to store and load arbitrary 2 // JSON-encoded structures with optional checksumming and compression. 3 /* 4 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 5 */ 6 package jsp_test 7 8 import ( 9 "bytes" 10 "io" 11 "math/rand" 12 "reflect" 13 "strconv" 14 "testing" 15 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/jsp" 18 "github.com/NVIDIA/aistore/memsys" 19 "github.com/NVIDIA/aistore/tools/tassert" 20 "github.com/NVIDIA/aistore/tools/trand" 21 ) 22 23 // go test -v -bench=. -tags=debug 24 // go test -v -bench=. -tags=debug -benchtime=10s -benchmem 25 26 type testStruct struct { 27 I int `json:"a,omitempty"` 28 S string `json:"zero"` 29 B []byte `json:"bytes,omitempty"` 30 ST struct { 31 I64 int64 `json:"int64"` 32 } 33 M map[string]string 34 } 35 36 func (ts *testStruct) equal(other testStruct) bool { 37 return ts.I == other.I && 38 ts.S == other.S && 39 bytes.Equal(ts.B, other.B) && 40 ts.ST.I64 == other.ST.I64 && 41 reflect.DeepEqual(ts.M, other.M) 42 } 43 44 func makeRandStruct() (ts testStruct) { 45 if rand.Intn(2) == 0 { 46 ts.I = rand.Int() 47 } 48 ts.S = trand.String(rand.Intn(100)) 49 if rand.Intn(2) == 0 { 50 ts.B = []byte(trand.String(rand.Intn(200))) 51 } 52 ts.ST.I64 = rand.Int63() 53 if rand.Intn(2) == 0 { 54 ts.M = make(map[string]string) 55 for range rand.Intn(100) + 1 { 56 ts.M[trand.String(10)] = trand.String(20) 57 } 58 } 59 return 60 } 61 62 func makeStaticStruct() (ts testStruct) { 63 ts.I = rand.Int() 64 ts.S = trand.String(100) 65 ts.B = []byte(trand.String(200)) 66 ts.ST.I64 = rand.Int63() 67 ts.M = make(map[string]string, 10) 68 for range 10 { 69 ts.M[trand.String(10)] = trand.String(20) 70 } 71 return 72 } 73 74 func TestDecodeAndEncode(t *testing.T) { 75 tests := []struct { 76 name string 77 v testStruct 78 opts jsp.Options 79 }{ 80 {name: "empty", v: testStruct{}, opts: jsp.Options{}}, 81 {name: "default", v: makeRandStruct(), opts: jsp.Options{}}, 82 {name: "compress", v: makeRandStruct(), opts: jsp.Options{Compress: true}}, 83 {name: "cksum", v: makeRandStruct(), opts: jsp.Options{Checksum: true}}, 84 {name: "sign", v: makeRandStruct(), opts: jsp.Options{Signature: true}}, 85 {name: "compress_cksum", v: makeRandStruct(), opts: jsp.Options{Compress: true, Checksum: true}}, 86 {name: "cksum_sign", v: makeRandStruct(), opts: jsp.Options{Checksum: true, Signature: true}}, 87 {name: "ccs", v: makeRandStruct(), opts: jsp.CCSign(1)}, 88 { 89 name: "special_char", 90 v: testStruct{I: 10, S: "abc\ncd]}{", B: []byte{'a', 'b', '\n', 'c', 'd', ']', '}'}}, 91 opts: jsp.Options{Checksum: true}, 92 }, 93 } 94 for _, test := range tests { 95 t.Run(test.name, func(t *testing.T) { 96 var ( 97 v testStruct 98 mmsa = memsys.PageMM() 99 b = mmsa.NewSGL(cos.MiB) 100 ) 101 defer b.Free() 102 103 err := jsp.Encode(b, test.v, test.opts) 104 tassert.CheckFatal(t, err) 105 106 _, err = jsp.Decode(b, &v, test.opts, "test") 107 tassert.CheckFatal(t, err) 108 109 // reflect.DeepEqual may not work here due to using `[]byte` in the struct. 110 // `Decode` may generate empty slice from original `nil` slice and while 111 // both are kind of the same, DeepEqual says they differ. From output when 112 // the test fails: 113 // v(B:[]uint8(nil)) != test.v(B:[]uint8{}) 114 tassert.Fatalf( 115 t, v.equal(test.v), 116 "structs are not equal, (got: %+v, expected: %+v)", v, test.v, 117 ) 118 }) 119 } 120 } 121 122 func TestDecodeAndEncodeFuzz(t *testing.T) { 123 mmsa := memsys.PageMM() 124 b := mmsa.NewSGL(cos.MiB) 125 defer b.Free() 126 127 for i := range 10000 { 128 var ( 129 x, v string 130 opts = jsp.Options{Signature: true, Checksum: true} 131 ) 132 133 x = trand.String(i) 134 135 err := jsp.Encode(b, x, opts) 136 tassert.CheckFatal(t, err) 137 138 _, err = jsp.Decode(b, &v, opts, strconv.Itoa(i)) 139 tassert.CheckFatal(t, err) 140 141 tassert.Fatalf(t, x == v, "strings are not equal, (got: %+v, expected: %+v)", x, v) 142 143 b.Reset() 144 } 145 } 146 147 func BenchmarkEncode(b *testing.B) { 148 benches := []struct { 149 name string 150 v testStruct 151 opts jsp.Options 152 }{ 153 {name: "empty", v: testStruct{}, opts: jsp.Options{}}, 154 {name: "default", v: makeStaticStruct(), opts: jsp.Options{}}, 155 {name: "sign", v: makeStaticStruct(), opts: jsp.Options{Signature: true}}, 156 {name: "cksum", v: makeStaticStruct(), opts: jsp.Options{Checksum: true}}, 157 {name: "compress", v: makeStaticStruct(), opts: jsp.Options{Compress: true}}, 158 {name: "ccs", v: makeStaticStruct(), opts: jsp.CCSign(7)}, 159 } 160 mmsa := memsys.PageMM() 161 for _, bench := range benches { 162 b.Run(bench.name, func(b *testing.B) { 163 body := mmsa.NewSGL(cos.MiB) 164 defer func() { 165 b.StopTimer() 166 body.Free() 167 }() 168 b.ReportAllocs() 169 b.ResetTimer() 170 171 for range b.N { 172 err := jsp.Encode(body, bench.v, bench.opts) 173 tassert.CheckFatal(b, err) 174 body.Reset() 175 } 176 }) 177 } 178 } 179 180 func BenchmarkDecode(b *testing.B) { 181 benches := []struct { 182 name string 183 v testStruct 184 opts jsp.Options 185 }{ 186 {name: "empty", v: testStruct{}, opts: jsp.Options{}}, 187 {name: "default", v: makeStaticStruct(), opts: jsp.Options{}}, 188 {name: "sign", v: makeStaticStruct(), opts: jsp.Options{Signature: true}}, 189 {name: "cksum", v: makeStaticStruct(), opts: jsp.Options{Checksum: true}}, 190 {name: "compress", v: makeStaticStruct(), opts: jsp.Options{Compress: true}}, 191 {name: "ccs", v: makeStaticStruct(), opts: jsp.CCSign(13)}, 192 } 193 for _, bench := range benches { 194 b.Run(bench.name, func(b *testing.B) { 195 mmsa, _ := memsys.NewMMSA("jsp.test", false) 196 defer mmsa.Terminate(false) 197 sgl := mmsa.NewSGL(cos.MiB) 198 199 err := jsp.Encode(sgl, bench.v, bench.opts) 200 tassert.CheckFatal(b, err) 201 network := sgl.ReadAll() 202 sgl.Free() 203 204 b.ReportAllocs() 205 b.ResetTimer() 206 207 for range b.N { 208 var ( 209 v testStruct 210 r = io.NopCloser(bytes.NewReader(network)) 211 ) 212 _, err := jsp.Decode(r, &v, bench.opts, "benchmark") 213 tassert.CheckFatal(b, err) 214 } 215 }) 216 } 217 }