Skip to content

Commit

Permalink
重构: syncx: map LoadOrStoreFunc (#198)
Browse files Browse the repository at this point in the history
* refactor: LoadOrStoreFunc直接调用m.LoadOrStore

Signed-off-by: longyue0521 <[email protected]>

* refactor: 重构测试代码——使用子测试增强可理解性,用same方法代替equal方法明确意图“

Signed-off-by: longyue0521 <[email protected]>

* 添加.CHANGELOG.md

Signed-off-by: longyue0521 <[email protected]>

---------

Signed-off-by: longyue0521 <[email protected]>
  • Loading branch information
longyue0521 authored Jul 14, 2023
1 parent 72ded6a commit 45a0228
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 87 deletions.
1 change: 1 addition & 0 deletions .CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [mapx: 为 MultipleMap 添加 PutVals 方法](https://github.com/ecodeclub/ekit/pull/189)
- [mapx: LinkedMap 特性](https://github.com/ecodeclub/ekit/pull/191)
- [copier: ReflectCopier 支持忽略字段](https://github.com/ecodeclub/ekit/pull/196)
- [syncx: 重构LoadOrStoreFunc方法及相关测试](https://github.com/ecodeclub/ekit/pull/198)

# v0.0.7
- [slice: FilterDelete](https://github.com/ecodeclub/ekit/pull/152)
Expand Down
6 changes: 1 addition & 5 deletions syncx/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
// 它的代价就是 Key 不存在的时候会多一次 Load 调用。
// 当 fn 返回 error 的时候,LoadOrStoreFunc 也会返回 error。
func (m *Map[K, V]) LoadOrStoreFunc(key K, fn func() (V, error)) (actual V, loaded bool, err error) {
var anyVal any
val, ok := m.Load(key)
if ok {
return val, true, nil
Expand All @@ -65,10 +64,7 @@ func (m *Map[K, V]) LoadOrStoreFunc(key K, fn func() (V, error)) (actual V, load
if err != nil {
return
}
anyVal, loaded = m.m.LoadOrStore(key, val)
if anyVal != nil {
actual = anyVal.(V)
}
actual, loaded = m.LoadOrStore(key, val)
return
}

Expand Down
250 changes: 168 additions & 82 deletions syncx/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,127 +57,213 @@ func TestMap_Load(t *testing.T) {
},
}
var mu Map[string, *User]
mu.Store("found", &User{Name: "found"})
mu.Store("found but empty", &User{})
mu.Store("found", testCases[0].wantVal)
mu.Store("found but empty", testCases[1].wantVal)
mu.Store("found but nil", nil)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
val, ok := mu.Load(tc.key)
assert.Equal(t, tc.wantOk, ok)
assert.Equal(t, tc.wantVal, val)
assert.Same(t, tc.wantVal, val)
})
}
}

func TestMap_LoadOrStore(t *testing.T) {
var m = Map[string, *User]{}
val, loaded := m.LoadOrStore("Tom", &User{Name: "Tom"})
assert.False(t, loaded)
assert.Equal(t, &User{Name: "Tom"}, val)

val, loaded = m.LoadOrStore("Tom", &User{Name: "Tom-copy"})
assert.True(t, loaded)
assert.Equal(t, &User{Name: "Tom"}, val)
t.Run("store non-nil value", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Tom"}
val, loaded := m.LoadOrStore(user.Name, user)
assert.False(t, loaded)
assert.Same(t, user, val)
})

val, loaded = m.LoadOrStore("Jerry", nil)
assert.False(t, loaded)
assert.Nil(t, val)
t.Run("load non-nil value", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Tom"}
val, loaded := m.LoadOrStore(user.Name, user)
assert.False(t, loaded)
assert.Same(t, user, val)

val, loaded = m.LoadOrStore("Jerry", &User{Name: "Jerry"})
assert.True(t, loaded)
assert.Nil(t, val)
val, loaded = m.LoadOrStore("Tom", &User{Name: "Tom-copy"})

assert.True(t, loaded)
assert.Same(t, user, val)
})

t.Run("store nil value", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Jerry"}
val, loaded := m.LoadOrStore(user.Name, nil)
assert.False(t, loaded)
assert.Nil(t, val)
})

t.Run("load nil value", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Jerry"}
val, loaded := m.LoadOrStore(user.Name, nil)
assert.False(t, loaded)
assert.Nil(t, val)

val, loaded = m.LoadOrStore(user.Name, user)

assert.True(t, loaded)
assert.Nil(t, val)
})
}

func TestMap_LoadOrStoreFunc(t *testing.T) {
var m = Map[string, *User]{}
val, loaded, err := m.LoadOrStoreFunc("Tom", func() (*User, error) {
return &User{Name: "Tom"}, nil

t.Run("store non-nil value returned by func", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Tom"}

val, loaded, err := m.LoadOrStoreFunc(user.Name, func() (*User, error) {
return user, nil
})

assert.NoError(t, err)
assert.False(t, loaded)
assert.Same(t, user, val)
})
assert.NoError(t, err)
assert.False(t, loaded)
assert.Equal(t, &User{Name: "Tom"}, val)

// 测试 Tom 存在的情况
val, loaded, err = m.LoadOrStoreFunc("Tom", func() (*User, error) {
return &User{Name: "Tom"}, nil
t.Run("load non-nil value returned by func", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Tom"}
val, loaded, err := m.LoadOrStoreFunc(user.Name, func() (*User, error) {
return user, nil
})
assert.NoError(t, err)
assert.False(t, loaded)
assert.Same(t, user, val)

val, loaded, err = m.LoadOrStoreFunc(user.Name, func() (*User, error) {
return &User{Name: "Tom"}, nil
})

assert.NoError(t, err)
assert.True(t, loaded)
assert.Same(t, user, val)
})
assert.NoError(t, err)
assert.True(t, loaded)
assert.Equal(t, &User{Name: "Tom"}, val)

// 测试初始化失败
val, loaded, err = m.LoadOrStoreFunc("Jerry", func() (*User, error) {
return nil, errors.New("初始话失败")
t.Run("store nil value returned by func", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Tom"}

val, loaded, err := m.LoadOrStoreFunc(user.Name, func() (*User, error) {
return nil, nil
})

assert.NoError(t, err)
assert.False(t, loaded)
assert.Nil(t, val)
})

t.Run("load nil value returned by func", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Tom"}
val, loaded, err := m.LoadOrStoreFunc(user.Name, func() (*User, error) {
return nil, nil
})
assert.NoError(t, err)
assert.False(t, loaded)
assert.Nil(t, val)

val, loaded, err = m.LoadOrStoreFunc(user.Name, func() (*User, error) {
return nil, nil
})

assert.NoError(t, err)
assert.True(t, loaded)
assert.Nil(t, val)
})

t.Run("got error returned by func", func(t *testing.T) {
m := Map[string, *User]{}
val, loaded, err := m.LoadOrStoreFunc("Jerry", func() (*User, error) {
return nil, errors.New("初始话失败")
})
assert.Equal(t, err, errors.New("初始话失败"))
assert.False(t, loaded)
assert.Equal(t, (*User)(nil), val)
})
assert.Equal(t, err, errors.New("初始话失败"))
assert.False(t, loaded)
assert.Equal(t, (*User)(nil), val)
}

func TestMap_LoadAndDelete(t *testing.T) {
var m = Map[string, *User]{}
m.Store("Tom", nil)
val, loaded := m.LoadAndDelete("Tom")
assert.True(t, loaded)
assert.Nil(t, val)

val, loaded = m.LoadAndDelete("Tom")
assert.False(t, loaded)
assert.Nil(t, val)
t.Run("non-nil value", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Jerry"}
m.Store("Jerry", user)

m.Store("Jerry", &User{Name: "Jerry"})
val, loaded = m.LoadAndDelete("Jerry")
assert.True(t, loaded)
assert.Equal(t, &User{Name: "Jerry"}, val)
val, loaded := m.LoadAndDelete(user.Name)
assert.True(t, loaded)
assert.Same(t, user, val)

val, loaded = m.LoadAndDelete("Jerry")
assert.False(t, loaded)
assert.Nil(t, val)
val, loaded = m.LoadAndDelete(user.Name)
assert.False(t, loaded)
assert.Nil(t, val)
})

t.Run("nil value", func(t *testing.T) {
m, user := Map[string, *User]{}, &User{Name: "Tom"}
m.Store(user.Name, nil)

val, loaded := m.LoadAndDelete(user.Name)
assert.True(t, loaded)
assert.Nil(t, val)

val, loaded = m.LoadAndDelete(user.Name)
assert.False(t, loaded)
assert.Nil(t, val)
})
}

func TestMap_Delete(t *testing.T) {
var m = Map[string, *User]{}
m.Store("Tom", &User{Name: "Tom"})
val, ok := m.Load("Tom")
m, user := Map[string, *User]{}, &User{Name: "Tom"}
m.Store(user.Name, user)
val, ok := m.Load(user.Name)
assert.True(t, ok)
assert.Equal(t, &User{Name: "Tom"}, val)
m.Delete("Tom")
val, ok = m.Load("Tom")
assert.Same(t, user, val)

m.Delete(user.Name)

val, ok = m.Load(user.Name)
assert.False(t, ok)
assert.Nil(t, val)
}

func TestMap_Range(t *testing.T) {
var m = Map[string, *User]{}
m.Store("Tom", &User{Name: "Tom"})
m.Store("Jerry", &User{Name: "Jerry"})
m.Store("nil", nil)

shadow := make(map[string]*User, 3)
m.Range(func(key string, val *User) bool {
shadow[key] = val
return true
t.Run("non-pointer type key", func(t *testing.T) {
m, tom, jerry := Map[string, *User]{}, &User{Name: "Tom"}, &User{Name: "Jerry"}
var zero *User
m.Store(tom.Name, tom)
m.Store(jerry.Name, jerry)
m.Store("zero", zero)
m.Store("nil", nil)

shadow := make(map[string]*User, 4)
m.Range(func(key string, val *User) bool {
shadow[key] = val
return true
})

assert.Same(t, tom, shadow[tom.Name])
assert.Same(t, jerry, shadow[jerry.Name])
assert.Same(t, zero, shadow["zero"])
assert.Same(t, (*User)(nil), shadow["nil"])
})
assert.Equal(t, map[string]*User{
"Tom": {Name: "Tom"},
"Jerry": {Name: "Jerry"},
"nil": nil,
}, shadow)

var ptrKeyMap Map[*User, string]
key1 := &User{Name: "Tom"}
var key2 *User
ptrKeyMap.Store(key1, "Tom")
ptrKeyMap.Store(key2, "nil")
ptrShadow := make(map[*User]string, 2)
ptrKeyMap.Range(func(key *User, val string) bool {
ptrShadow[key] = val
return true

t.Run("pointer type key", func(t *testing.T) {
m, tom := Map[*User, string]{}, &User{Name: "Tom"}
var zero *User
m.Store(tom, "Tom")
m.Store(zero, "nil")

shadow := make(map[*User]string, 2)
m.Range(func(key *User, val string) bool {
shadow[key] = val
return true
})

assert.Equal(t, shadow[tom], tom.Name)
assert.Equal(t, shadow[zero], "nil")
assert.Equal(t, shadow[nil], "nil")
})
assert.Equal(t, map[*User]string{
key1: "Tom",
nil: "nil",
}, ptrShadow)
}

func ExampleMap_LoadAndDelete() {
Expand Down

0 comments on commit 45a0228

Please sign in to comment.