github.com/GuanceCloud/cliutils@v1.1.21/diskcache/get_test.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the MIT License. 3 // This product includes software developed at Guance Cloud (https://www.guance.com/). 4 // Copyright 2021-present Guance, Inc. 5 6 package diskcache 7 8 import ( 9 "errors" 10 "fmt" 11 "os" 12 T "testing" 13 "time" 14 15 "github.com/GuanceCloud/cliutils/metrics" 16 "github.com/prometheus/client_golang/prometheus" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestGetPut(t *T.T) { 22 testDir := t.TempDir() 23 24 err := os.MkdirAll(testDir, 0o755) 25 assert.NoError(t, err) 26 27 dq, err := Open(WithPath(testDir), WithCapacity(1<<30)) 28 assert.NoError(t, err) 29 30 assert.NoError(t, dq.Put([]byte("hello message-1"))) 31 32 for { 33 if err := dq.Get(func(msg []byte) error { 34 t.Logf("get message: %q\n", string(msg)) 35 return nil 36 }); err != nil { 37 t.Log(time.Now().Format(time.RFC3339Nano), " fail to get message: ", err) 38 time.Sleep(time.Second * 1) 39 } else { 40 break 41 } 42 } 43 44 assert.NoError(t, dq.Put([]byte("hello message-2"))) 45 46 ok := false 47 48 for i := 0; i < 10; i++ { 49 if err := dq.Get(func(msg []byte) error { 50 t.Logf("get message: %q\n", string(msg)) 51 ok = true 52 return nil 53 }); err != nil { 54 t.Log(time.Now().Format(time.RFC3339Nano), " fail to get message: ", err) 55 time.Sleep(time.Second * 1) 56 } else { 57 break 58 } 59 } 60 61 assert.True(t, ok, "expected consume 1 message in 10 seconds, but got no message") 62 63 assert.NoError(t, dq.Close()) 64 } 65 66 func TestDropInvalidDataFile(t *T.T) { 67 t.Run(`get-on-0bytes-data-file`, func(t *T.T) { 68 p := t.TempDir() 69 c, err := Open(WithPath(p)) 70 require.NoError(t, err) 71 72 // put some data and rotate 10 datafiles 73 data := make([]byte, 100) 74 for i := 0; i < 10; i++ { 75 assert.NoError(t, c.Put(data)) 76 assert.NoError(t, c.rotate()) 77 78 // destroy the datafile 79 if i%2 == 0 { 80 assert.NoError(t, os.Truncate(c.dataFiles[i], 0)) 81 } 82 } 83 84 assert.Len(t, c.dataFiles, 10) 85 86 for { 87 err := c.Get(func(get []byte) error { 88 // switch to 2nd file 89 assert.Equal(t, data, get) 90 return nil 91 }) 92 if err != nil { 93 require.ErrorIs(t, err, ErrEOF) 94 break 95 } 96 } 97 98 reg := prometheus.NewRegistry() 99 register(reg) 100 mfs, err := reg.Gather() 101 require.NoError(t, err) 102 103 assert.Equalf(t, float64(5), 104 metrics.GetMetricOnLabels(mfs, 105 "diskcache_dropped_total", 106 c.path, 107 reasonBadDataFile, 108 ).GetCounter().GetValue(), 109 "got metrics\n%s", metrics.MetricFamily2Text(mfs)) 110 }) 111 } 112 113 func TestFallbackOnError(t *T.T) { 114 t.Run(`get-erro-on-EOF`, func(t *T.T) { 115 p := t.TempDir() 116 c, err := Open(WithPath(p)) 117 require.NoError(t, err) 118 119 // put some data 120 data := make([]byte, 100) 121 assert.NoError(t, c.Put(data)) 122 123 assert.NoError(t, c.rotate()) 124 125 require.NoError(t, c.Get(func(_ []byte) error { 126 return nil // ignore the data 127 })) 128 129 err = c.Get(func(_ []byte) error { 130 assert.True(t, 1 == 2) // should not been here 131 return nil 132 }) 133 134 assert.ErrorIs(t, err, ErrEOF) 135 t.Logf("get: %s", err) 136 137 if errors.Is(err, ErrEOF) { 138 t.Logf("we should ignore the error") 139 } 140 }) 141 142 t.Run(`fallback-on-error`, func(t *T.T) { 143 ResetMetrics() 144 145 p := t.TempDir() 146 c, err := Open(WithPath(p)) 147 assert.NoError(t, err) 148 149 data := make([]byte, 100) 150 assert.NoError(t, c.Put(data)) 151 152 assert.NoError(t, c.rotate()) 153 154 // should get error when callback fail 155 require.Error(t, c.Get(func(_ []byte) error { 156 return fmt.Errorf("get error") 157 })) 158 159 assert.Equal(t, int64(0), c.pos.Seek) 160 161 // should no error when callback ok 162 assert.NoError(t, c.Get(func(x []byte) error { 163 assert.Equal(t, data, x) 164 return nil 165 })) 166 167 reg := prometheus.NewRegistry() 168 register(reg) 169 mfs, err := reg.Gather() 170 require.NoError(t, err) 171 172 assert.Equalf(t, float64(1), 173 metrics.GetMetricOnLabels(mfs, "diskcache_seek_back_total", c.path).GetCounter().GetValue(), 174 "got metrics\n%s", metrics.MetricFamily2Text(mfs)) 175 176 t.Cleanup(func() { 177 ResetMetrics() 178 }) 179 }) 180 181 t.Run(`fallback-on-eof-error`, func(t *T.T) { 182 p := t.TempDir() 183 c, err := Open(WithPath(p)) 184 assert.NoError(t, err) 185 186 // while on EOF, Fn error ignored 187 assert.ErrorIs(t, c.Get(func(_ []byte) error { 188 return fmt.Errorf("get error") 189 }), ErrEOF) 190 191 // still got EOF 192 assert.ErrorIs(t, c.Get(func(x []byte) error { 193 return nil 194 }), ErrEOF) 195 }) 196 197 t.Run(`no-fallback-on-error`, func(t *T.T) { 198 p := t.TempDir() 199 c, err := Open(WithPath(p), WithNoFallbackOnError(true)) 200 assert.NoError(t, err) 201 202 data := make([]byte, 100) 203 assert.NoError(t, c.Put(data)) 204 205 assert.NoError(t, c.rotate()) 206 207 c.Get(func(_ []byte) error { 208 return fmt.Errorf("get error") 209 }) 210 211 assert.ErrorIs(t, c.Get(func(x []byte) error { 212 return nil 213 }), ErrEOF) 214 }) 215 } 216 217 func TestPutGet(t *T.T) { 218 t.Run(`clean-pos-on-eof`, func(t *T.T) { 219 reg := prometheus.NewRegistry() 220 register(reg) 221 222 p := t.TempDir() 223 c, err := Open(WithPath(p)) 224 assert.NoError(t, err) 225 226 data := make([]byte, 100) 227 if err := c.Put(data); err != nil { 228 t.Error(err) 229 } 230 231 assert.NoError(t, c.rotate()) 232 assert.NoError(t, c.Get(func(data []byte) error { return nil })) 233 assert.Error(t, c.Get(func(data []byte) error { return nil })) // EOF 234 235 pos, err := posFromFile(c.pos.fname) 236 assert.NoError(t, err) 237 238 t.Logf("pos: %s", pos) 239 240 mfs, err := reg.Gather() 241 require.NoError(t, err) 242 243 t.Logf("\n%s", metrics.MetricFamily2Text(mfs)) 244 245 t.Cleanup(func() { 246 c.Close() 247 ResetMetrics() 248 }) 249 }) 250 251 t.Run("put-get", func(t *T.T) { 252 reg := prometheus.NewRegistry() 253 register(reg) 254 255 p := t.TempDir() 256 c, err := Open(WithPath(p)) 257 assert.NoError(t, err) 258 259 str := "hello world" 260 if err := c.Put([]byte(str)); err != nil { 261 t.Error(err) 262 } 263 264 assert.Equal(t, int64(len(str)+dataHeaderLen), c.curBatchSize) 265 266 if err := c.Get(func(data []byte) error { 267 t.Logf("get: %s", string(data)) 268 return nil 269 }); err != nil { 270 t.Logf("get: %s", err) 271 } 272 273 mfs, err := reg.Gather() 274 require.NoError(t, err) 275 t.Logf("\n%s", metrics.MetricFamily2Text(mfs)) 276 277 t.Cleanup(func() { 278 c.Close() 279 os.RemoveAll(p) 280 }) 281 }) 282 283 t.Run(`get-without-pos`, func(t *T.T) { 284 reg := prometheus.NewRegistry() 285 register(reg) 286 287 p := t.TempDir() 288 289 kbdata := make([]byte, 1024) 290 291 c, err := Open(WithPath(p), WithNoPos(true)) 292 assert.NoError(t, err) 293 294 for i := 0; i < 10; i++ { // write 10kb 295 require.NoError(t, c.Put(kbdata), "cache: %s", c) 296 } 297 298 // force rotate 299 assert.NoError(t, c.rotate()) 300 301 // read 2 cache 302 assert.NoError(t, c.Get(func(data []byte) error { 303 assert.Len(t, data, len(kbdata)) 304 return nil 305 })) 306 307 assert.NoError(t, c.Get(func(data []byte) error { 308 assert.Len(t, data, len(kbdata)) 309 return nil 310 })) 311 312 // close the cache for next re-Open() 313 assert.NoError(t, c.Close()) 314 315 mfs, err := reg.Gather() 316 require.NoError(t, err) 317 t.Logf("\n%s", metrics.MetricFamily2Text(mfs)) 318 319 c2, err := Open(WithPath(p), WithNoPos(true)) 320 assert.NoError(t, err) 321 defer c2.Close() 322 323 require.Lenf(t, c2.dataFiles, 1, "cache: %s", c2) 324 325 var ncached int 326 for { 327 if err := c2.Get(func(_ []byte) error { 328 ncached++ 329 return nil 330 }); err != nil { 331 if errors.Is(err, ErrEOF) { 332 t.Logf("cache EOF") 333 break 334 } else { 335 assert.NoError(t, err) 336 } 337 } 338 } 339 340 mfs, err = reg.Gather() 341 require.NoError(t, err) 342 t.Logf("\n%s", metrics.MetricFamily2Text(mfs)) 343 344 // without .pos, still got 10 cache 345 assert.Equal(t, 10, ncached, "cache: %s", c2) 346 }) 347 348 t.Run(`get-with-pos`, func(t *T.T) { 349 p := t.TempDir() 350 351 kbdata := make([]byte, 1024) 352 353 c, err := Open(WithPath(p), 354 WithCapacity(int64(len(kbdata)*10)), 355 WithBatchSize(int64(len(kbdata)*2))) 356 assert.NoError(t, err) 357 358 for i := 0; i < 10; i++ { // write 10kb 359 require.NoError(t, c.Put(kbdata), "cache: %s", c) 360 } 361 362 // create a read pos 363 assert.NoError(t, c.Get(func(data []byte) error { 364 assert.Len(t, data, len(kbdata)) 365 return nil 366 })) 367 assert.Equal(t, int64(len(kbdata)+dataHeaderLen), c.pos.Seek) 368 assert.NoError(t, c.Close()) 369 370 _, err = os.Stat(c.pos.fname) 371 require.NoError(t, err) 372 373 // reopen the cache 374 c2, err := Open(WithPath(p), 375 WithCapacity(int64(len(kbdata)*10)), 376 WithBatchSize(int64(len(kbdata)*2))) 377 require.NoError(t, err, "get error: %s", err) 378 assert.Equal(t, c2.pos.Seek, int64(len(kbdata)+dataHeaderLen)) 379 380 assert.NoError(t, c2.Get(func(data []byte) error { 381 assert.Len(t, data, len(kbdata)) 382 return nil 383 })) 384 assert.Equal(t, int64(len(kbdata)+dataHeaderLen), c.pos.Seek) 385 assert.NoError(t, c2.Close()) 386 assert.Equal(t, c2.pos.Seek, int64(2*(len(kbdata)+dataHeaderLen))) 387 388 t.Cleanup(func() { 389 c2.Close() 390 os.RemoveAll(p) 391 }) 392 }) 393 }