mirror of
https://gitee.com/johng/gf
synced 2026-07-09 06:50:30 +08:00
fix(container/gmap): added WithError methods for KVMap and ListKVMap (#4714)
This commit is contained in:
@ -333,6 +333,54 @@ func (m *KVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// GetOrSetFuncWithError returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil
|
||||
// or if `f` returns a non-nil error.
|
||||
func (m *KVMap[K, V]) GetOrSetFuncWithError(key K, f func() (V, error)) (V, error) {
|
||||
if v, ok := m.Search(key); ok {
|
||||
return v, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
var zero V
|
||||
return zero, err
|
||||
}
|
||||
v, _ := m.doSetWithLockCheck(key, value)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// GetOrSetFuncLockWithError returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLockWithError differs with GetOrSetFuncWithError function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil
|
||||
// or if `f` returns a non-nil error.
|
||||
func (m *KVMap[K, V]) GetOrSetFuncLockWithError(key K, f func() (V, error)) (V, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
var zero V
|
||||
return zero, err
|
||||
}
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *KVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
@ -375,6 +423,49 @@ func (m *KVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncWithError sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored.
|
||||
func (m *KVMap[K, V]) SetIfNotExistFuncWithError(key K, f func() (V, error)) (bool, error) {
|
||||
if m.Contains(key) {
|
||||
return false, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if m.isNil(value) {
|
||||
return true, nil
|
||||
}
|
||||
return m.SetIfNotExist(key, value), nil
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLockWithError sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored.
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
//
|
||||
// SetIfNotExistFuncLockWithError differs with SetIfNotExistFuncWithError function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *KVMap[K, V]) SetIfNotExistFuncLockWithError(key K, f func() (V, error)) (bool, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = value
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *KVMap[K, V]) Remove(key K) (value V) {
|
||||
m.mu.Lock()
|
||||
|
||||
@ -400,6 +400,54 @@ func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// GetOrSetFuncWithError returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil
|
||||
// or if `f` returns a non-nil error.
|
||||
func (m *ListKVMap[K, V]) GetOrSetFuncWithError(key K, f func() (V, error)) (V, error) {
|
||||
if v, ok := m.Search(key); ok {
|
||||
return v, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
var zero V
|
||||
return zero, err
|
||||
}
|
||||
return m.doSetWithLockCheck(key, value), nil
|
||||
}
|
||||
|
||||
// GetOrSetFuncLockWithError returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLockWithError differs with GetOrSetFuncWithError function is that it executes function `f`
|
||||
// with mutex.Lock of the map.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil
|
||||
// or if `f` returns a non-nil error.
|
||||
func (m *ListKVMap[K, V]) GetOrSetFuncLockWithError(key K, f func() (V, error)) (V, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.value, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
var zero V
|
||||
return zero, err
|
||||
}
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
//
|
||||
@ -426,21 +474,10 @@ func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
if m.Contains(key) {
|
||||
return false
|
||||
}
|
||||
value := f()
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true
|
||||
return m.SetIfNotExist(key, f())
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -468,6 +505,51 @@ func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncWithError sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFuncWithError(key K, f func() (V, error)) (bool, error) {
|
||||
if m.Contains(key) {
|
||||
return false, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return m.SetIfNotExist(key, value), nil
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLockWithError sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored.
|
||||
//
|
||||
// SetIfNotExistFuncLockWithError differs with SetIfNotExistFuncWithError function is that
|
||||
// it executes function `f` with mutex.Lock of the map.
|
||||
//
|
||||
// Note that, it does not add the value to the map if the returned value of `f` is nil.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFuncLockWithError(key K, f func() (V, error)) (bool, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false, nil
|
||||
}
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *ListKVMap[K, V]) Remove(key K) (value V) {
|
||||
m.mu.Lock()
|
||||
|
||||
629
container/gmap/gmap_z_unit_k_v_map_with_error_test.go
Normal file
629
container/gmap/gmap_z_unit_k_v_map_with_error_test.go
Normal file
@ -0,0 +1,629 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// GetOrSetFuncWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_KVMap_GetOrSetFuncWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → value stored and returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, string]()
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (string, error) {
|
||||
return "val1", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "val1")
|
||||
t.Assert(m.Get("k1"), "val1")
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → existing value returned, f is NOT called.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, string]()
|
||||
m.Set("k1", "existing")
|
||||
called := false
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (string, error) {
|
||||
called = true
|
||||
return "new", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "existing")
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), "existing")
|
||||
})
|
||||
|
||||
// Case: f returns an error → zero value returned, error propagated, key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, string]()
|
||||
testErr := errors.New("load error")
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (string, error) {
|
||||
return "", testErr
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(v, "")
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → nil not stored in map, nil returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, *string]()
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (*string, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.AssertNil(v)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — f returns a struct treated as "nil" by checker → not stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker: valid=false
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "ignored"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Valid, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value: valid=true → stored
|
||||
v, err = m.GetOrSetFuncWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "hello"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Valid, true)
|
||||
t.Assert(v.Data, "hello")
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "hello")
|
||||
})
|
||||
|
||||
// Case: after f returns error, key is absent and a subsequent call can succeed.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
attempts := gtype.NewInt32()
|
||||
|
||||
_, err := m.GetOrSetFuncWithError("k1", func() (int, error) {
|
||||
attempts.Add(1)
|
||||
return 0, errors.New("temporary error")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (int, error) {
|
||||
attempts.Add(1)
|
||||
return 42, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 42)
|
||||
t.Assert(m.Get("k1"), 42)
|
||||
t.Assert(attempts.Val(), 2)
|
||||
})
|
||||
|
||||
// Case: safe mode (concurrent-safe=true) — basic functionality is correct.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int](true)
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (int, error) {
|
||||
return 99, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 99)
|
||||
t.Assert(m.Get("k1"), 99)
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// GetOrSetFuncLockWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_KVMap_GetOrSetFuncLockWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → value stored and returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, string]()
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) {
|
||||
return "val1", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "val1")
|
||||
t.Assert(m.Get("k1"), "val1")
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → existing value returned, f is NOT called.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, string]()
|
||||
m.Set("k1", "existing")
|
||||
called := false
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) {
|
||||
called = true
|
||||
return "new", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "existing")
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), "existing")
|
||||
})
|
||||
|
||||
// Case: f returns an error → zero value returned, error propagated, key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, string]()
|
||||
testErr := errors.New("lock load error")
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) {
|
||||
return "", testErr
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(v, "")
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → nil not stored in map, nil returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, *string]()
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (*string, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.AssertNil(v)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — f returns a struct treated as "nil" by checker → not stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker → not stored
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "ignored"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Valid, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value → stored
|
||||
v, err = m.GetOrSetFuncLockWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "world"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Data, "world")
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "world")
|
||||
})
|
||||
|
||||
// Case: after f returns error, key is absent and a subsequent call can succeed.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
|
||||
_, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) {
|
||||
return 0, errors.New("temporary")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) {
|
||||
return 77, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 77)
|
||||
t.Assert(m.Get("k1"), 77)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_KVMap_GetOrSetFuncLockWithError_Race verifies that f is called exactly once
|
||||
// under high concurrency because GetOrSetFuncLockWithError holds the mutex while calling f.
|
||||
// This differs from GetOrSetFuncWithError, which calls f outside the lock and may invoke
|
||||
// f multiple times when multiple goroutines all see the key as absent simultaneously.
|
||||
func Test_KVMap_GetOrSetFuncLockWithError_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int](true)
|
||||
key := "shared"
|
||||
callCount := gtype.NewInt32()
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for range goroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) {
|
||||
callCount.Add(1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return 999, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 999)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// f must be called exactly once because it executes inside the write lock.
|
||||
t.Assert(callCount.Val(), 1)
|
||||
t.Assert(m.Get(key), 999)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_KVMap_GetOrSetFuncLockWithError_Race_ErrorCase verifies that when f returns an error
|
||||
// concurrently, the key is never stored and a later successful call stores the value.
|
||||
func Test_KVMap_GetOrSetFuncLockWithError_Race_ErrorCase(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int](true)
|
||||
key := "retry"
|
||||
attempts := gtype.NewInt32()
|
||||
|
||||
// All goroutines call f, which errors; key must never be stored.
|
||||
goroutines := 20
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
for range goroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := m.GetOrSetFuncLockWithError(key, func() (int, error) {
|
||||
attempts.Add(1)
|
||||
return 0, errors.New("transient")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
t.Assert(m.Contains(key), false)
|
||||
|
||||
// A subsequent call with a successful f stores the value.
|
||||
v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) {
|
||||
return 55, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 55)
|
||||
t.Assert(m.Get(key), 55)
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// SetIfNotExistFuncWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_KVMap_SetIfNotExistFuncWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → true, nil, value stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
return 100, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get("k1"), 100)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → f NOT called, returns (false, nil), original value unchanged.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
m.Set("k1", 42)
|
||||
called := false
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
called = true
|
||||
return 999, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), 42)
|
||||
})
|
||||
|
||||
// Case: f returns an error → (false, error), key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
testErr := errors.New("set error")
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
return 0, testErr
|
||||
})
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → returns (true, nil) but value NOT stored in map.
|
||||
// This is the special nil-value behavior: the operation reports "intent to set"
|
||||
// but skips storage when the value is nil.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, *int]()
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (*int, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true) // returns true
|
||||
t.Assert(m.Contains("k1"), false) // but NOT stored
|
||||
t.Assert(m.Size(), 0)
|
||||
|
||||
// A subsequent call can still attempt to set (key was never stored).
|
||||
n := 7
|
||||
ok, err = m.SetIfNotExistFuncWithError("k1", func() (*int, error) {
|
||||
return &n, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), true)
|
||||
t.Assert(*m.Get("k1"), 7)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker → not stored, but returns true
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "irrelevant"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value → stored
|
||||
ok, err = m.SetIfNotExistFuncWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "stored"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "stored")
|
||||
})
|
||||
|
||||
// Case: safe mode (concurrent-safe=true) — basic functionality is correct.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int](true)
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
return 55, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get("k1"), 55)
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// SetIfNotExistFuncLockWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_KVMap_SetIfNotExistFuncLockWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → true, nil, value stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) {
|
||||
return 200, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get("k1"), 200)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → f NOT called, returns (false, nil), original value unchanged.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
m.Set("k1", 99)
|
||||
called := false
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) {
|
||||
called = true
|
||||
return 999, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), 99)
|
||||
})
|
||||
|
||||
// Case: f returns an error → (false, error), key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
testErr := errors.New("lock set error")
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) {
|
||||
return 0, testErr
|
||||
})
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → returns (true, nil) but value NOT stored in map.
|
||||
// Special behavior: the method signals "key was absent and no error" via true,
|
||||
// but skips the actual insertion because the value is nil.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, *int]()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true) // true: key was absent and f had no error
|
||||
t.Assert(m.Contains("k1"), false) // but nil value was NOT stored
|
||||
t.Assert(m.Size(), 0)
|
||||
|
||||
// A subsequent call can still store a real value (key remains absent).
|
||||
n := 9
|
||||
ok, err = m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) {
|
||||
return &n, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), true)
|
||||
t.Assert(*m.Get("k1"), 9)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker → not stored, but returns true
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "irrelevant"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value → stored
|
||||
ok, err = m.SetIfNotExistFuncLockWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "hello"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "hello")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_KVMap_SetIfNotExistFuncLockWithError_Race verifies that f is called exactly once
|
||||
// and only one goroutine succeeds under high concurrency, because
|
||||
// SetIfNotExistFuncLockWithError holds the mutex for the entire check-and-set operation.
|
||||
func Test_KVMap_SetIfNotExistFuncLockWithError_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int](true)
|
||||
key := "race_key"
|
||||
callCount := gtype.NewInt32()
|
||||
successCount := gtype.NewInt32()
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for range goroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) {
|
||||
callCount.Add(1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return 42, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
if ok {
|
||||
successCount.Add(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// f must be called exactly once (lock held during f execution).
|
||||
t.Assert(callCount.Val(), 1)
|
||||
// Exactly one goroutine reports success.
|
||||
t.Assert(successCount.Val(), 1)
|
||||
t.Assert(m.Get(key), 42)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_KVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys verifies correctness when
|
||||
// multiple goroutines compete over different keys simultaneously.
|
||||
func Test_KVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int](true)
|
||||
keys := []string{"alpha", "beta", "gamma", "delta"}
|
||||
callCounts := make([]*gtype.Int32, len(keys))
|
||||
successCounts := make([]*gtype.Int32, len(keys))
|
||||
for i := range callCounts {
|
||||
callCounts[i] = gtype.NewInt32()
|
||||
successCounts[i] = gtype.NewInt32()
|
||||
}
|
||||
goroutines := 30
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i, key := range keys {
|
||||
keyIdx := i
|
||||
for range goroutines {
|
||||
wg.Add(1)
|
||||
go func(idx int, k string) {
|
||||
defer wg.Done()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError(k, func() (int, error) {
|
||||
callCounts[idx].Add(1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return (idx + 1) * 10, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
if ok {
|
||||
successCounts[idx].Add(1)
|
||||
}
|
||||
}(keyIdx, key)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i, key := range keys {
|
||||
// f called exactly once per key
|
||||
t.Assert(callCounts[i].Val(), 1)
|
||||
// exactly one goroutine succeeded per key
|
||||
t.Assert(successCounts[i].Val(), 1)
|
||||
t.Assert(m.Get(key), (i+1)*10)
|
||||
}
|
||||
t.Assert(m.Size(), len(keys))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_KVMap_SetIfNotExistFuncLockWithError_ErrorRetry verifies that after f returns an error
|
||||
// the key remains absent and a subsequent successful call stores the value correctly.
|
||||
func Test_KVMap_SetIfNotExistFuncLockWithError_ErrorRetry(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewKVMap[string, int]()
|
||||
key := "retry"
|
||||
|
||||
ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) {
|
||||
return 0, errors.New("transient error")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(m.Contains(key), false)
|
||||
|
||||
// After the error the key is still absent; a new call succeeds.
|
||||
ok, err = m.SetIfNotExistFuncLockWithError(key, func() (int, error) {
|
||||
return 123, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get(key), 123)
|
||||
})
|
||||
}
|
||||
699
container/gmap/gmap_z_unit_list_k_v_map_with_error_test.go
Normal file
699
container/gmap/gmap_z_unit_list_k_v_map_with_error_test.go
Normal file
@ -0,0 +1,699 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// GetOrSetFuncWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_ListKVMap_GetOrSetFuncWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → value stored and returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, string]()
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (string, error) {
|
||||
return "val1", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "val1")
|
||||
t.Assert(m.Get("k1"), "val1")
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → existing value returned, f is NOT called.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, string]()
|
||||
m.Set("k1", "existing")
|
||||
called := false
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (string, error) {
|
||||
called = true
|
||||
return "new", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "existing")
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), "existing")
|
||||
})
|
||||
|
||||
// Case: f returns an error → zero value returned, error propagated, key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, string]()
|
||||
testErr := errors.New("load error")
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (string, error) {
|
||||
return "", testErr
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(v, "")
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → nil not stored in map, nil returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *string]()
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (*string, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.AssertNil(v)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — struct treated as "nil" by checker → not stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewListKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker: valid=false
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "ignored"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Valid, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value → stored
|
||||
v, err = m.GetOrSetFuncWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "hello"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Valid, true)
|
||||
t.Assert(v.Data, "hello")
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "hello")
|
||||
})
|
||||
|
||||
// Case: insertion order is maintained after GetOrSetFuncWithError adds entries.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
|
||||
_, err := m.GetOrSetFuncWithError("c", func() (int, error) {
|
||||
return 3, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
keys := m.Keys()
|
||||
t.Assert(len(keys), 3)
|
||||
t.Assert(keys[0], "a")
|
||||
t.Assert(keys[1], "b")
|
||||
t.Assert(keys[2], "c")
|
||||
})
|
||||
|
||||
// Case: after f returns error, key is absent and a subsequent call can succeed.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
attempts := gtype.NewInt32()
|
||||
|
||||
_, err := m.GetOrSetFuncWithError("k1", func() (int, error) {
|
||||
attempts.Add(1)
|
||||
return 0, errors.New("temporary error")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (int, error) {
|
||||
attempts.Add(1)
|
||||
return 42, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 42)
|
||||
t.Assert(m.Get("k1"), 42)
|
||||
t.Assert(attempts.Val(), 2)
|
||||
})
|
||||
|
||||
// Case: safe mode (concurrent-safe=true) — basic functionality is correct.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
v, err := m.GetOrSetFuncWithError("k1", func() (int, error) {
|
||||
return 99, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 99)
|
||||
t.Assert(m.Get("k1"), 99)
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// GetOrSetFuncLockWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_ListKVMap_GetOrSetFuncLockWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → value stored and returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, string]()
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) {
|
||||
return "val1", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "val1")
|
||||
t.Assert(m.Get("k1"), "val1")
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → existing value returned, f is NOT called.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, string]()
|
||||
m.Set("k1", "existing")
|
||||
called := false
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) {
|
||||
called = true
|
||||
return "new", nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, "existing")
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), "existing")
|
||||
})
|
||||
|
||||
// Case: f returns an error → zero value returned, error propagated, key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, string]()
|
||||
testErr := errors.New("lock load error")
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) {
|
||||
return "", testErr
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(v, "")
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → nil not stored in map, nil returned.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *string]()
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (*string, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.AssertNil(v)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — struct treated as "nil" by checker → not stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewListKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker → not stored
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "ignored"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Valid, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value → stored
|
||||
v, err = m.GetOrSetFuncLockWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "world"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v.Data, "world")
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "world")
|
||||
})
|
||||
|
||||
// Case: insertion order is maintained after GetOrSetFuncLockWithError adds entries.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
m.Set("x", 10)
|
||||
m.Set("y", 20)
|
||||
|
||||
_, err := m.GetOrSetFuncLockWithError("z", func() (int, error) {
|
||||
return 30, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
keys := m.Keys()
|
||||
t.Assert(len(keys), 3)
|
||||
t.Assert(keys[0], "x")
|
||||
t.Assert(keys[1], "y")
|
||||
t.Assert(keys[2], "z")
|
||||
})
|
||||
|
||||
// Case: after f returns error, key is absent and a subsequent call can succeed.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
|
||||
_, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) {
|
||||
return 0, errors.New("temporary")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
v, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) {
|
||||
return 77, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 77)
|
||||
t.Assert(m.Get("k1"), 77)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLockWithError_Race verifies that f is called exactly once
|
||||
// under high concurrency because GetOrSetFuncLockWithError holds the mutex while calling f.
|
||||
// This differs from GetOrSetFuncWithError, which calls f outside the lock and may invoke
|
||||
// f multiple times when multiple goroutines all see the key as absent simultaneously.
|
||||
func Test_ListKVMap_GetOrSetFuncLockWithError_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "shared"
|
||||
callCount := gtype.NewInt32()
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for range goroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) {
|
||||
callCount.Add(1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return 999, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 999)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// f must be called exactly once because it executes inside the write lock.
|
||||
t.Assert(callCount.Val(), 1)
|
||||
t.Assert(m.Get(key), 999)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLockWithError_Race_ErrorCase verifies that when f returns
|
||||
// an error concurrently, the key is never stored and a later successful call works.
|
||||
func Test_ListKVMap_GetOrSetFuncLockWithError_Race_ErrorCase(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "retry"
|
||||
attempts := gtype.NewInt32()
|
||||
|
||||
goroutines := 20
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
for range goroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := m.GetOrSetFuncLockWithError(key, func() (int, error) {
|
||||
attempts.Add(1)
|
||||
return 0, errors.New("transient")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
t.Assert(m.Contains(key), false)
|
||||
|
||||
v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) {
|
||||
return 55, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 55)
|
||||
t.Assert(m.Get(key), 55)
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// SetIfNotExistFuncWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_ListKVMap_SetIfNotExistFuncWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → true, nil, value stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
return 100, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get("k1"), 100)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → f NOT called, returns (false, nil), original value unchanged.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
m.Set("k1", 42)
|
||||
called := false
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
called = true
|
||||
return 999, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), 42)
|
||||
})
|
||||
|
||||
// Case: f returns an error → (false, error), key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
testErr := errors.New("set error")
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
return 0, testErr
|
||||
})
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → returns (true, nil) but value NOT stored in map.
|
||||
// This is the special nil-value behavior: the operation reports "intent to set"
|
||||
// but skips storage when the value is nil.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *int]()
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (*int, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true) // returns true
|
||||
t.Assert(m.Contains("k1"), false) // but NOT stored
|
||||
t.Assert(m.Size(), 0)
|
||||
|
||||
// A subsequent call can still set the value (key was never stored).
|
||||
n := 7
|
||||
ok, err = m.SetIfNotExistFuncWithError("k1", func() (*int, error) {
|
||||
return &n, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), true)
|
||||
t.Assert(*m.Get("k1"), 7)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewListKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker → not stored, but returns true
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "irrelevant"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value → stored
|
||||
ok, err = m.SetIfNotExistFuncWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "stored"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "stored")
|
||||
})
|
||||
|
||||
// Case: insertion order is maintained after SetIfNotExistFuncWithError adds entries.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
|
||||
ok, err := m.SetIfNotExistFuncWithError("c", func() (int, error) {
|
||||
return 3, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
|
||||
keys := m.Keys()
|
||||
t.Assert(len(keys), 3)
|
||||
t.Assert(keys[0], "a")
|
||||
t.Assert(keys[1], "b")
|
||||
t.Assert(keys[2], "c")
|
||||
})
|
||||
|
||||
// Case: safe mode (concurrent-safe=true) — basic functionality is correct.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) {
|
||||
return 55, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get("k1"), 55)
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// SetIfNotExistFuncLockWithError
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
func Test_ListKVMap_SetIfNotExistFuncLockWithError(t *testing.T) {
|
||||
type MyVal struct {
|
||||
Valid bool
|
||||
Data string
|
||||
}
|
||||
// Case: key not exist, f returns valid value → true, nil, value stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) {
|
||||
return 200, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get("k1"), 200)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
|
||||
// Case: key already exists → f NOT called, returns (false, nil), original value unchanged.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
m.Set("k1", 99)
|
||||
called := false
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) {
|
||||
called = true
|
||||
return 999, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(called, false)
|
||||
t.Assert(m.Get("k1"), 99)
|
||||
})
|
||||
|
||||
// Case: f returns an error → (false, error), key NOT stored.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
testErr := errors.New("lock set error")
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) {
|
||||
return 0, testErr
|
||||
})
|
||||
t.Assert(err, testErr)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
t.Assert(m.Size(), 0)
|
||||
})
|
||||
|
||||
// Case: f returns nil pointer → returns (true, nil) but value NOT stored in map.
|
||||
// Special behavior: the method signals "key was absent and no error" via true,
|
||||
// but skips the actual insertion because the value is nil.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *int]()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) {
|
||||
return nil, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true) // true: key was absent and f had no error
|
||||
t.Assert(m.Contains("k1"), false) // but nil value was NOT stored
|
||||
t.Assert(m.Size(), 0)
|
||||
|
||||
// A subsequent call can still store a real value (key remains absent).
|
||||
n := 9
|
||||
ok, err = m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) {
|
||||
return &n, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), true)
|
||||
t.Assert(*m.Get("k1"), 9)
|
||||
})
|
||||
|
||||
// Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
checker := func(v *MyVal) bool { return !v.Valid }
|
||||
m := gmap.NewListKVMapWithChecker[string, *MyVal](checker)
|
||||
|
||||
// "nil" per checker → not stored, but returns true
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: false, Data: "irrelevant"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k1"), false)
|
||||
|
||||
// valid value → stored
|
||||
ok, err = m.SetIfNotExistFuncLockWithError("k2", func() (*MyVal, error) {
|
||||
return &MyVal{Valid: true, Data: "hello"}, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Contains("k2"), true)
|
||||
t.Assert(m.Get("k2").Data, "hello")
|
||||
})
|
||||
|
||||
// Case: insertion order is maintained after SetIfNotExistFuncLockWithError adds entries.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
|
||||
ok, err := m.SetIfNotExistFuncLockWithError("c", func() (int, error) {
|
||||
return 3, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
|
||||
keys := m.Keys()
|
||||
t.Assert(len(keys), 3)
|
||||
t.Assert(keys[0], "a")
|
||||
t.Assert(keys[1], "b")
|
||||
t.Assert(keys[2], "c")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLockWithError_Race verifies that f is called exactly once
|
||||
// and only one goroutine succeeds under high concurrency, because
|
||||
// SetIfNotExistFuncLockWithError holds the mutex for the entire check-and-set operation.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLockWithError_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "race_key"
|
||||
callCount := gtype.NewInt32()
|
||||
successCount := gtype.NewInt32()
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for range goroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) {
|
||||
callCount.Add(1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return 42, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
if ok {
|
||||
successCount.Add(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// f must be called exactly once (lock held during f execution).
|
||||
t.Assert(callCount.Val(), 1)
|
||||
// Exactly one goroutine reports success.
|
||||
t.Assert(successCount.Val(), 1)
|
||||
t.Assert(m.Get(key), 42)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys verifies correctness when
|
||||
// multiple goroutines compete over different keys simultaneously.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
keys := []string{"alpha", "beta", "gamma", "delta"}
|
||||
callCounts := make([]*gtype.Int32, len(keys))
|
||||
successCounts := make([]*gtype.Int32, len(keys))
|
||||
for i := range callCounts {
|
||||
callCounts[i] = gtype.NewInt32()
|
||||
successCounts[i] = gtype.NewInt32()
|
||||
}
|
||||
goroutines := 30
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i, key := range keys {
|
||||
keyIdx := i
|
||||
for range goroutines {
|
||||
wg.Add(1)
|
||||
go func(idx int, k string) {
|
||||
defer wg.Done()
|
||||
ok, err := m.SetIfNotExistFuncLockWithError(k, func() (int, error) {
|
||||
callCounts[idx].Add(1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return (idx + 1) * 10, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
if ok {
|
||||
successCounts[idx].Add(1)
|
||||
}
|
||||
}(keyIdx, key)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i, key := range keys {
|
||||
// f called exactly once per key
|
||||
t.Assert(callCounts[i].Val(), 1)
|
||||
// exactly one goroutine succeeded per key
|
||||
t.Assert(successCounts[i].Val(), 1)
|
||||
t.Assert(m.Get(key), (i+1)*10)
|
||||
}
|
||||
t.Assert(m.Size(), len(keys))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLockWithError_ErrorRetry verifies that after f returns an error
|
||||
// the key remains absent and a subsequent successful call stores the value correctly.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLockWithError_ErrorRetry(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int]()
|
||||
key := "retry"
|
||||
|
||||
ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) {
|
||||
return 0, errors.New("transient error")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(ok, false)
|
||||
t.Assert(m.Contains(key), false)
|
||||
|
||||
// After the error the key is still absent; a new call succeeds.
|
||||
ok, err = m.SetIfNotExistFuncLockWithError(key, func() (int, error) {
|
||||
return 123, nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(m.Get(key), 123)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user