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  }