github.com/grafana/pyroscope@v1.18.0/pkg/settings/store/store_test.go (about) 1 package store 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 "path/filepath" 8 "testing" 9 10 "github.com/grafana/pyroscope/pkg/objstore/providers/filesystem" 11 12 "github.com/go-kit/log" 13 "github.com/stretchr/testify/require" 14 ) 15 16 type testObj struct { 17 Name string 18 Data string 19 Generation int64 20 } 21 22 const storeJSON = `{ 23 "generation":"4", 24 "elements":[ 25 {"Name":"a","Data":"data-a-v3","Generation":3}, 26 {"Name":"b","Data":"data-b","Generation":1} 27 ] 28 }` 29 30 type testObjHelper struct{} 31 32 func (*testObjHelper) ID(o *testObj) string { 33 return o.Name 34 } 35 36 func (*testObjHelper) GetGeneration(o *testObj) int64 { 37 return o.Generation 38 } 39 40 func (*testObjHelper) SetGeneration(o *testObj, gen int64) { 41 o.Generation = gen 42 } 43 44 func (*testObjHelper) FromStore(data json.RawMessage) (*testObj, error) { 45 var obj testObj 46 err := json.Unmarshal(data, &obj) 47 return &obj, err 48 } 49 50 func (*testObjHelper) ToStore(obj *testObj) (json.RawMessage, error) { 51 return json.Marshal(obj) 52 } 53 54 func (*testObjHelper) TypePath() string { 55 return "testobj.v1" 56 } 57 58 type testStore struct { 59 *GenericStore[*testObj, *testObjHelper] 60 bucketPath string 61 } 62 63 func newTestStore(t testing.TB, tenantID string) *testStore { 64 logger := log.NewNopLogger() 65 if testing.Verbose() { 66 logger = log.NewLogfmtLogger(os.Stderr) 67 } 68 bucketPath := t.TempDir() 69 bucket, err := filesystem.NewBucket(bucketPath) 70 require.NoError(t, err) 71 return &testStore{ 72 GenericStore: New( 73 logger, 74 bucket, 75 Key{TenantID: tenantID}, 76 &testObjHelper{}, 77 ), 78 bucketPath: bucketPath, 79 } 80 } 81 82 func Test_GenericStore(t *testing.T) { 83 s := newTestStore(t, "user-a") 84 ctx := context.Background() 85 86 t.Run("empty", func(t *testing.T) { 87 result, err := s.Get(ctx) 88 require.NoError(t, err) 89 require.Equal(t, []*testObj{}, result.Elements) 90 }) 91 92 t.Run("one element", func(t *testing.T) { 93 require.NoError(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a"}, nil)) 94 result, err := s.Get(ctx) 95 require.NoError(t, err) 96 require.Equal(t, []*testObj{ 97 {Name: "a", Data: "data-a", Generation: 1}, 98 }, result.Elements) 99 }) 100 101 t.Run("second element", func(t *testing.T) { 102 require.NoError(t, s.Upsert(ctx, &testObj{Name: "b", Data: "data-b"}, nil)) 103 result, err := s.Get(ctx) 104 require.NoError(t, err) 105 require.Equal(t, []*testObj{ 106 {Name: "a", Data: "data-a", Generation: 1}, 107 {Name: "b", Data: "data-b", Generation: 1}, 108 }, result.Elements) 109 }) 110 111 t.Run("update without generation", func(t *testing.T) { 112 require.NoError(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a-v2"}, nil)) 113 result, err := s.Get(ctx) 114 require.NoError(t, err) 115 require.Equal(t, []*testObj{ 116 {Name: "a", Data: "data-a-v2", Generation: 2}, 117 {Name: "b", Data: "data-b", Generation: 1}, 118 }, result.Elements) 119 }) 120 121 t.Run("update with generation", func(t *testing.T) { 122 observedGeneration := int64(2) 123 require.NoError(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a-v3"}, &observedGeneration)) 124 result, err := s.Get(ctx) 125 require.NoError(t, err) 126 require.Equal(t, []*testObj{ 127 {Name: "a", Data: "data-a-v3", Generation: 3}, 128 {Name: "b", Data: "data-b", Generation: 1}, 129 }, result.Elements) 130 }) 131 132 t.Run("validate stored data is as expected", func(t *testing.T) { 133 storePath := filepath.Join(s.bucketPath, "user-a/testobj.v1.json") 134 actual, err := os.ReadFile(storePath) 135 require.NoError(t, err) 136 require.JSONEq(t, storeJSON, string(actual)) 137 }) 138 139 t.Run("restore from stored data", func(t *testing.T) { 140 newS := newTestStore(t, "user-b") 141 storePath := filepath.Join(newS.bucketPath, "user-b/testobj.v1.json") 142 require.NoError(t, os.MkdirAll(filepath.Dir(storePath), 0o755)) 143 require.NoError(t, os.WriteFile( 144 storePath, 145 []byte(storeJSON), 146 0o644, 147 )) 148 result, err := newS.Get(ctx) 149 require.NoError(t, err) 150 require.Equal(t, []*testObj{ 151 {Name: "a", Data: "data-a-v3", Generation: 3}, 152 {Name: "b", Data: "data-b", Generation: 1}, 153 }, result.Elements) 154 }) 155 156 t.Run("update with wrong generation", func(t *testing.T) { 157 observedGeneration := int64(2) 158 require.ErrorContains(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a-v4"}, &observedGeneration), "conflicting update, please try again: observed_generation=2, store_generation=3") 159 result, err := s.Get(ctx) 160 require.NoError(t, err) 161 require.Equal(t, []*testObj{ 162 {Name: "a", Data: "data-a-v3", Generation: 3}, 163 {Name: "b", Data: "data-b", Generation: 1}, 164 }, result.Elements) 165 }) 166 167 t.Run("delete element that exists", func(t *testing.T) { 168 require.NoError(t, s.Delete(ctx, "a")) 169 result, err := s.Get(ctx) 170 require.NoError(t, err) 171 require.Equal(t, []*testObj{ 172 {Name: "b", Data: "data-b", Generation: 1}, 173 }, result.Elements) 174 }) 175 t.Run("delete element that doesnt exist", func(t *testing.T) { 176 require.ErrorContains(t, s.Delete(ctx, "c"), "element not found") 177 result, err := s.Get(ctx) 178 require.NoError(t, err) 179 require.Equal(t, []*testObj{ 180 {Name: "b", Data: "data-b", Generation: 1}, 181 }, result.Elements) 182 }) 183 }