diff --git a/cache/map.go b/cache/map.go index b374f7c..e4e2dbb 100644 --- a/cache/map.go +++ b/cache/map.go @@ -21,12 +21,16 @@ func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) { m.cacheFunc = fn } +func (m *MapCache[K, V]) Ttl(ctx context.Context, k K) time.Duration { + return m.data.Ttl(ctx, k, m.expireTime) +} + func (m *MapCache[K, V]) GetLastSetTime(ctx context.Context, k K) (t time.Time) { tt := m.data.Ttl(ctx, k, m.expireTime) if tt <= 0 { return } - return time.Now().Add(m.data.Ttl(ctx, k, m.expireTime)) + return time.Now().Add(m.data.Ttl(ctx, k, m.expireTime)).Add(-m.expireTime) } func (m *MapCache[K, V]) SetCacheBatchFn(fn func(...any) (map[K]V, error)) { @@ -58,20 +62,20 @@ func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) { } } -func NewMemoryMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] { +func NewMapCacheByFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] { return &MapCache[K, V]{ - data: NewMemoryMapCache[K, V](), + mux: sync.Mutex{}, cacheFunc: fn, expireTime: expireTime, - mux: sync.Mutex{}, + data: cacheType, } } -func NewMemoryMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] { +func NewMapCacheByBatchFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] { r := &MapCache[K, V]{ - data: NewMemoryMapCache[K, V](), + mux: sync.Mutex{}, batchCacheFn: fn, expireTime: expireTime, - mux: sync.Mutex{}, + data: cacheType, } r.setCacheFn(fn) return r diff --git a/cache/map_test.go b/cache/map_test.go new file mode 100644 index 0000000..feed65e --- /dev/null +++ b/cache/map_test.go @@ -0,0 +1,370 @@ +package cache + +import ( + "context" + "fmt" + "github.com/fthvgb1/wp-go/helper/slice" + "reflect" + "strings" + "testing" + "time" +) + +var ca MapCache[string, string] +var fn func(a ...any) (string, error) +var batchFn func(a ...any) (map[string]string, error) +var ct context.Context + +func init() { + fn = func(a ...any) (string, error) { + aa := a[1].(string) + return strings.Repeat(aa, 2), nil + } + ct = context.Background() + batchFn = func(a ...any) (map[string]string, error) { + arr := a[1].([]string) + return slice.SimpleToMap(arr, func(t string) string { + return strings.Repeat(t, 2) + }), nil + } + ca = *NewMemoryMapCacheByFn[string, string](fn, time.Second*2) + ca.SetCacheBatchFn(batchFn) + _, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa") + _, _ = ca.GetCache(ct, "bb", time.Second, ct, "bb") +} +func TestMapCache_ClearExpired(t *testing.T) { + type args struct { + ct context.Context + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args{ + ct: ct, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println(ca.Get(ct, "aa")) + fmt.Println(ca.Get(ct, "bb")) + time.Sleep(time.Second * 3) + tt.m.ClearExpired(tt.args.ct) + fmt.Println(ca.Get(ct, "bb")) + }) + } +} + +func TestMapCache_Flush(t *testing.T) { + type args struct { + ct context.Context + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args + } + ca := *NewMemoryMapCacheByFn[string, string](fn, time.Second) + _, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa") + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args{ + ct, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println(ca.Get(ct, "aa")) + tt.m.Flush(tt.args.ct) + fmt.Println(ca.Get(ct, "aa")) + }) + } +} + +func TestMapCache_Get(t *testing.T) { + type args[K comparable] struct { + ct context.Context + k K + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K] + want V + want1 bool + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string]{ct, "aa"}, + want: "aaaa", + want1: true, + }, + { + name: "t2", + m: ca, + args: args[string]{ct, "cc"}, + want: "", + want1: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := tt.m.Get(tt.args.ct, tt.args.k) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Get() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Get() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestMapCache_GetCache(t *testing.T) { + type args[K comparable] struct { + c context.Context + key K + timeout time.Duration + params []any + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K] + want V + wantErr bool + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string]{c: ct, key: "xx", timeout: time.Second, params: []any{ct, "xx"}}, + want: "xxxx", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.GetCache(tt.args.c, tt.args.key, tt.args.timeout, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Errorf("GetCache() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetCache() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMapCache_GetCacheBatch(t *testing.T) { + type args[K comparable] struct { + c context.Context + key []K + timeout time.Duration + params []any + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K] + want []V + wantErr bool + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string]{ + c: ct, + key: []string{"xx", "oo"}, + timeout: time.Second, + params: []any{ct, []string{"xx", "oo"}}, + }, + want: []string{"xxxx", "oooo"}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.GetCacheBatch(tt.args.c, tt.args.key, tt.args.timeout, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Errorf("GetCacheBatch() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetCacheBatch() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMapCache_GetLastSetTime(t *testing.T) { + type args[K comparable] struct { + ct context.Context + k K + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K] + wantT time.Time + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string]{ct, "aa"}, + wantT: ttt, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotT := tt.m.GetLastSetTime(tt.args.ct, tt.args.k); !reflect.DeepEqual(gotT, tt.wantT) { + t.Errorf("GetLastSetTime() = %v, want %v", gotT, tt.wantT) + } + }) + } +} + +func TestMapCache_Set(t *testing.T) { + type args[K comparable, V any] struct { + ct context.Context + k K + v V + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K, V] + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string, string]{ + ct, "xx", "yy", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println(ca.Get(ct, "xx")) + tt.m.Set(tt.args.ct, tt.args.k, tt.args.v) + fmt.Println(ca.Get(ct, "xx")) + }) + } +} + +func TestMapCache_SetCacheBatchFn(t *testing.T) { + type args[K comparable, V any] struct { + fn func(...any) (map[K]V, error) + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K, V] + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string, string]{batchFn}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.m.SetCacheBatchFn(tt.args.fn) + }) + } +} + +func TestMapCache_SetCacheFunc(t *testing.T) { + type args[V any] struct { + fn func(...any) (V, error) + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[V] + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string]{fn: fn}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.m.SetCacheFunc(tt.args.fn) + }) + } +} + +func TestMapCache_Ttl(t *testing.T) { + type args[K comparable] struct { + ct context.Context + k K + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K] + want time.Duration + } + tx := time.Now() + txx := ca.GetLastSetTime(ct, "aa") + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string]{ct, "aa"}, + want: ca.expireTime - tx.Sub(txx), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Printf("过期时间=%v \nttl=%v \n当前时间 =%v\n最后设置时间=%v\n当时时间-最后设置时间=%v ", ca.expireTime, ca.Ttl(ct, "aa"), tx, txx, tx.Sub(txx)) + if got := tt.m.Ttl(tt.args.ct, tt.args.k); got != tt.want { + t.Errorf("Ttl() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMapCache_setCacheFn(t *testing.T) { + type args[K comparable, V any] struct { + fn func(...any) (map[K]V, error) + } + type testCase[K comparable, V any] struct { + name string + m MapCache[K, V] + args args[K, V] + } + tests := []testCase[string, string]{ + { + name: "t1", + m: ca, + args: args[string, string]{batchFn}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ca.cacheFunc = nil + tt.m.setCacheFn(tt.args.fn) + fmt.Println(ca.GetCache(ct, "xx", time.Second, ct, "xx")) + }) + } +} diff --git a/cache/memorymapcache.go b/cache/memorymapcache.go index 624813f..a2de458 100644 --- a/cache/memorymapcache.go +++ b/cache/memorymapcache.go @@ -2,8 +2,8 @@ package cache import ( "context" - "github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/safety" + "sync" "time" ) @@ -11,6 +11,25 @@ type MemoryMapCache[K comparable, V any] struct { safety.Map[K, mapVal[V]] } +func NewMemoryMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] { + return &MapCache[K, V]{ + data: NewMemoryMapCache[K, V](), + cacheFunc: fn, + expireTime: expireTime, + mux: sync.Mutex{}, + } +} +func NewMemoryMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] { + r := &MapCache[K, V]{ + data: NewMemoryMapCache[K, V](), + batchCacheFn: fn, + expireTime: expireTime, + mux: sync.Mutex{}, + } + r.setCacheFn(fn) + return r +} + func NewMemoryMapCache[K comparable, V any]() *MemoryMapCache[K, V] { return &MemoryMapCache[K, V]{Map: safety.NewMap[K, mapVal[V]]()} } @@ -49,9 +68,9 @@ func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Durat func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K, expire time.Duration) time.Duration { v, ok := m.Load(key) if !ok { - return -1 + return time.Duration(-1) } - return number.Max(time.Duration(0), expire-time.Duration(time.Now().UnixNano()-v.setTime.UnixNano())) + return expire - time.Now().Sub(v.setTime) } func (m *MemoryMapCache[K, V]) Ver(_ context.Context, key K) int { diff --git a/cache/memorymapcache_test.go b/cache/memorymapcache_test.go new file mode 100644 index 0000000..9eb5b12 --- /dev/null +++ b/cache/memorymapcache_test.go @@ -0,0 +1,246 @@ +package cache + +import ( + "context" + "fmt" + "reflect" + "testing" + "time" +) + +var mm MemoryMapCache[string, string] +var ctx context.Context + +var ttt time.Time + +func init() { + ctx = context.Background() + mm = *NewMemoryMapCache[string, string]() + ttt = time.Now() + mm.Store("aa", mapVal[string]{ + setTime: ttt, + ver: 1, + data: "bb", + }) + time.Sleep(60 * time.Millisecond) + mm.Store("cc", mapVal[string]{ + setTime: time.Now(), + ver: 1, + data: "dd", + }) +} + +func TestMemoryMapCache_ClearExpired(t *testing.T) { + type args struct { + in0 context.Context + expire time.Duration + } + type testCase[K string, V string] struct { + name string + m MemoryMapCache[K, V] + args args + } + tests := []testCase[string, string]{ + { + name: "t1", + m: mm, + args: args{ + in0: ctx, + expire: time.Second, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println(tt.m) + tt.m.ClearExpired(tt.args.in0, tt.args.expire) + time.Sleep(time.Second) + fmt.Println(tt.m) + }) + } +} + +func TestMemoryMapCache_Delete(t *testing.T) { + type args[K comparable] struct { + in0 context.Context + key K + } + type testCase[K comparable, V any] struct { + name string + m MemoryMapCache[K, V] + args args[K] + } + tests := []testCase[string, string]{ + { + name: "t1", + m: mm, + args: args[string]{ + in0: ctx, + key: "aa", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println(mm.Get(ctx, "aa")) + tt.m.Delete(tt.args.in0, tt.args.key) + fmt.Println(mm.Get(ctx, "aa")) + + }) + } +} + +func TestMemoryMapCache_Flush(t *testing.T) { + type args struct { + in0 context.Context + } + type testCase[K comparable, V any] struct { + name string + m MemoryMapCache[K, V] + args args + } + tests := []testCase[string, string]{ + { + name: "t1", + m: mm, + args: args{ + in0: ctx, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.m.Flush(tt.args.in0) + mm.Set(ctx, "aa", "xx", time.Second) + fmt.Println(mm.Get(ctx, "aa")) + }) + } +} + +func TestMemoryMapCache_Get(t *testing.T) { + type args[K comparable] struct { + in0 context.Context + key K + } + type testCase[K comparable, V any] struct { + name string + m MemoryMapCache[K, V] + args args[K] + wantR V + wantOk bool + } + tests := []testCase[string, string]{ + { + name: "t1", + m: mm, + args: args[string]{ + in0: ctx, + key: "aa", + }, + wantOk: true, + wantR: "bb", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotR, gotOk := tt.m.Get(tt.args.in0, tt.args.key) + if !reflect.DeepEqual(gotR, tt.wantR) { + t.Errorf("Get() gotR = %v, want %v", gotR, tt.wantR) + } + if gotOk != tt.wantOk { + t.Errorf("Get() gotOk = %v, want %v", gotOk, tt.wantOk) + } + }) + } +} + +func TestMemoryMapCache_Set(t *testing.T) { + type args[K comparable, V any] struct { + in0 context.Context + key K + val V + in3 time.Duration + } + type testCase[K comparable, V any] struct { + name string + m MemoryMapCache[K, V] + args args[K, V] + } + tests := []testCase[string, string]{ + { + name: "t1", + m: mm, + args: args[string, string]{ + in0: ctx, + key: "ee", + val: "ff", + in3: time.Second, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.m.Set(tt.args.in0, tt.args.key, tt.args.val, tt.args.in3) + fmt.Println(tt.m.Get(ctx, tt.args.key)) + }) + } +} + +func TestMemoryMapCache_Ttl(t *testing.T) { + type args[K comparable] struct { + in0 context.Context + key K + expire time.Duration + } + type testCase[K comparable, V any] struct { + name string + m MemoryMapCache[K, V] + args args[K] + want time.Duration + } + tt := time.Now() + tests := []testCase[string, string]{ + { + name: "t1", + m: mm, + args: args[string]{key: "aa", in0: ctx, expire: time.Second * 4}, + want: 4*time.Second - tt.Sub(ttt), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Ttl(tt.args.in0, tt.args.key, tt.args.expire); got != tt.want { + t.Errorf("Ttl() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryMapCache_Ver(t *testing.T) { + type args[K comparable] struct { + in0 context.Context + key K + } + type testCase[K comparable, V any] struct { + name string + m MemoryMapCache[K, V] + args args[K] + want int + } + mm.Set(ctx, "aa", "ff", time.Second) + tests := []testCase[string, string]{ + { + name: "t1", + m: mm, + args: args[string]{ctx, "aa"}, + want: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Ver(tt.args.in0, tt.args.key); got != tt.want { + t.Errorf("Ver() = %v, want %v", got, tt.want) + } + }) + } +}