mirror of
https://gitee.com/johng/gf
synced 2026-07-03 03:39:35 +08:00
## 描述
本PR为`gmap`、`gset`和`gtree`容器引入了`NilChecker`机制,以解决Go语言中的`typed
nil`问题。该实现允许用户注册自定义的nil检查函数来确定值是否应被视为nil,这对于处理那些会被存储到容器中的`typed
nil`值特别有用。
## 情况描述
当前`gmap`等容器的泛型容器存在对`value`的`nil`值无法正确过滤的问题,例如以下例子中如果使用默认的`if any(value)
!=
nil`去判断就会得到错误的结果,原因是会出现带有类型的`(*Student)(nil)`直接和`nil`比较或者使用`any`强转都是不对的,使用反射可以解决但是性能太差了,所以换个思虑我们让用户自己决定如何判断`nil`就能解决这个问题
```golang
func main() {
type Student struct {
Name string
Age int
}
m1 := gmap.NewKVMap[int, *Student](true)
for i := 0; i < 10; i++ {
m1.GetOrSetFuncLock(i, func() *Student {
if i%2 == 0 {
return &Student{}
}
return nil
})
}
fmt.Println(m1.Size()) // 10
m2 := gmap.NewKVMap[int, *Student](true)
m2.RegisterNilChecker(func(student *Student) bool {
return student == nil
})
for i := 0; i < 10; i++ {
m2.GetOrSetFuncLock(i, func() *Student {
if i%2 == 0 {
return &Student{}
}
return nil
})
}
fmt.Println(m2.Size()) // 5
}
```
## 变更内容
- 在gmap、gset和gtree包中添加了`NilChecker`类型定义
- 扩展容器结构体,增加`nilChecker`字段来存储自定义nil检查函数
- 实现了`RegisterNilChecker`方法,允许用户注册自定义nil检查逻辑
- 添加了`isNil`内部方法,优先使用自定义nil检查函数或回退到默认的`any(v) == nil`检查
- 更新关键操作(AddIfNotExist、Set等)以利用nil检查机制
- 为所有三个容器类型添加了全面的测试用例以验证nilchecker功能
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1666 lines
38 KiB
Go
1666 lines
38 KiB
Go
// 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 (
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/gogf/gf/v2/container/gmap"
|
|
"github.com/gogf/gf/v2/internal/json"
|
|
"github.com/gogf/gf/v2/test/gtest"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
)
|
|
|
|
func Test_KVMap_NewKVMap(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
t.Assert(m.Size(), 0)
|
|
t.Assert(m.IsEmpty(), true)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string](true)
|
|
t.Assert(m.Size(), 0)
|
|
t.Assert(m.IsEmpty(), true)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_NewKVMapFrom(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
data := map[string]string{"a": "1", "b": "2"}
|
|
m := gmap.NewKVMapFrom(data)
|
|
t.Assert(m.Size(), 2)
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Get("b"), "2")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
data := map[int]int{1: 10, 2: 20}
|
|
m := gmap.NewKVMapFrom(data, true)
|
|
t.Assert(m.Size(), 2)
|
|
t.Assert(m.Get(1), 10)
|
|
t.Assert(m.Get(2), 20)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Set_Get(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
m.Set("a", "1")
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Size(), 1)
|
|
|
|
m.Set("b", "2")
|
|
t.Assert(m.Get("b"), "2")
|
|
t.Assert(m.Size(), 2)
|
|
|
|
// Set existing key
|
|
m.Set("a", "10")
|
|
t.Assert(m.Get("a"), "10")
|
|
t.Assert(m.Size(), 2)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[int, int]()
|
|
m.Set(1, 100)
|
|
m.Set(2, 200)
|
|
t.Assert(m.Get(1), 100)
|
|
t.Assert(m.Get(2), 200)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Sets(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
m.Sets(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
t.Assert(m.Size(), 3)
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Get("b"), "2")
|
|
t.Assert(m.Get("c"), "3")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
data := map[string]string{"x": "10", "y": "20"}
|
|
m := gmap.NewKVMapFrom(data)
|
|
m.Sets(map[string]string{"a": "1", "b": "2"})
|
|
t.Assert(m.Size(), 4)
|
|
t.Assert(m.Get("x"), "10")
|
|
t.Assert(m.Get("a"), "1")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Search(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
|
|
v, found := m.Search("a")
|
|
t.Assert(found, true)
|
|
t.Assert(v, "1")
|
|
|
|
v, found = m.Search("c")
|
|
t.Assert(found, false)
|
|
t.Assert(v, "")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[int, string]()
|
|
v, found := m.Search(1)
|
|
t.Assert(found, false)
|
|
t.Assert(v, "")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Contains(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
t.Assert(m.Contains("a"), true)
|
|
t.Assert(m.Contains("b"), true)
|
|
t.Assert(m.Contains("c"), false)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Remove(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
t.Assert(m.Size(), 2)
|
|
|
|
v := m.Remove("a")
|
|
t.Assert(v, "1")
|
|
t.Assert(m.Contains("a"), false)
|
|
t.Assert(m.Size(), 1)
|
|
|
|
v = m.Remove("c")
|
|
t.Assert(v, "")
|
|
t.Assert(m.Size(), 1)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Removes(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
t.Assert(m.Size(), 3)
|
|
|
|
m.Removes([]string{"a", "c"})
|
|
t.Assert(m.Size(), 1)
|
|
t.Assert(m.Contains("a"), false)
|
|
t.Assert(m.Contains("c"), false)
|
|
t.Assert(m.Contains("b"), true)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
m.Removes([]string{"x", "y"})
|
|
t.Assert(m.Size(), 2)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Pop(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
t.Assert(m.Size(), 2)
|
|
|
|
k, v := m.Pop()
|
|
t.AssertIN(k, []string{"a", "b"})
|
|
t.AssertIN(v, []string{"1", "2"})
|
|
t.Assert(m.Size(), 1)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
k, v := m.Pop()
|
|
t.Assert(k, "")
|
|
t.Assert(v, "")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Pops(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
t.Assert(m.Size(), 3)
|
|
|
|
popped := m.Pops(2)
|
|
t.Assert(len(popped), 2)
|
|
t.Assert(m.Size(), 1)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
popped := m.Pops(-1)
|
|
t.Assert(len(popped), 3)
|
|
t.Assert(m.Size(), 0)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
popped := m.Pops(10)
|
|
t.Assert(len(popped), 2)
|
|
t.Assert(m.Size(), 0)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
popped := m.Pops(1)
|
|
t.AssertNil(popped)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Keys(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
keys := m.Keys()
|
|
t.Assert(len(keys), 3)
|
|
t.AssertIN("a", keys)
|
|
t.AssertIN("b", keys)
|
|
t.AssertIN("c", keys)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[int, string]()
|
|
keys := m.Keys()
|
|
t.Assert(len(keys), 0)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Values(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
values := m.Values()
|
|
t.Assert(len(values), 3)
|
|
t.AssertIN("1", values)
|
|
t.AssertIN("2", values)
|
|
t.AssertIN("3", values)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
values := m.Values()
|
|
t.Assert(len(values), 0)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Size(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
t.Assert(m.Size(), 0)
|
|
|
|
m.Set("a", "1")
|
|
t.Assert(m.Size(), 1)
|
|
|
|
m.Set("b", "2")
|
|
t.Assert(m.Size(), 2)
|
|
|
|
m.Remove("a")
|
|
t.Assert(m.Size(), 1)
|
|
|
|
m.Clear()
|
|
t.Assert(m.Size(), 0)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_IsEmpty(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
t.Assert(m.IsEmpty(), true)
|
|
|
|
m.Set("a", "1")
|
|
t.Assert(m.IsEmpty(), false)
|
|
|
|
m.Remove("a")
|
|
t.Assert(m.IsEmpty(), true)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Clear(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
t.Assert(m.Size(), 3)
|
|
|
|
m.Clear()
|
|
t.Assert(m.Size(), 0)
|
|
t.Assert(m.IsEmpty(), true)
|
|
t.Assert(m.Get("a"), "")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Map(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
data := m.Map()
|
|
t.Assert(data["a"], "1")
|
|
t.Assert(data["b"], "2")
|
|
t.Assert(len(data), 2)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Unsafe map, modifying returned map affects original
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}, false)
|
|
data := m.Map()
|
|
data["c"] = "3"
|
|
t.Assert(m.Get("c"), "3")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Safe map, modifying returned map doesn't affect original
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}, true)
|
|
data := m.Map()
|
|
data["c"] = "3"
|
|
t.Assert(m.Get("c"), "")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_MapCopy(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
data := m.MapCopy()
|
|
t.Assert(data["a"], "1")
|
|
t.Assert(data["b"], "2")
|
|
|
|
// Modifying copy doesn't affect original
|
|
data["c"] = "3"
|
|
t.Assert(m.Get("c"), "")
|
|
|
|
m.Set("d", "4")
|
|
t.Assert(data["d"], "")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_MapStrAny(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2})
|
|
data := m.MapStrAny()
|
|
t.Assert(len(data), 2)
|
|
t.Assert(data["a"], 1)
|
|
t.Assert(data["b"], 2)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[int]string{1: "a", 2: "b"})
|
|
data := m.MapStrAny()
|
|
t.Assert(len(data), 2)
|
|
t.Assert(data["1"], "a")
|
|
t.Assert(data["2"], "b")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_FilterEmpty(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "", "b": "2", "c": "3"})
|
|
t.Assert(m.Size(), 3)
|
|
|
|
m.FilterEmpty()
|
|
t.Assert(m.Size(), 2)
|
|
t.Assert(m.Contains("a"), false)
|
|
t.Assert(m.Contains("b"), true)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 0, "b": 1, "c": 2})
|
|
t.Assert(m.Size(), 3)
|
|
|
|
m.FilterEmpty()
|
|
t.Assert(m.Size(), 2)
|
|
t.Assert(m.Contains("a"), false)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_FilterNil(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, *string]()
|
|
a := "a"
|
|
m.Set("key1", &a)
|
|
m.Set("key2", nil)
|
|
m.Set("key3", nil)
|
|
t.Assert(m.Size(), 3)
|
|
|
|
m.FilterNil()
|
|
t.Assert(m.Size(), 1)
|
|
t.Assert(m.Contains("key1"), true)
|
|
t.Assert(m.Contains("key2"), false)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_GetOrSet(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
|
|
v := m.GetOrSet("a", "1")
|
|
t.Assert(v, "1")
|
|
t.Assert(m.Get("a"), "1")
|
|
|
|
v = m.GetOrSet("a", "10")
|
|
t.Assert(v, "1")
|
|
t.Assert(m.Get("a"), "1")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 10})
|
|
|
|
v := m.GetOrSet("a", 20)
|
|
t.Assert(v, 10)
|
|
|
|
v = m.GetOrSet("b", 30)
|
|
t.Assert(v, 30)
|
|
t.Assert(m.Get("b"), 30)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_GetOrSetFunc(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
|
|
v := m.GetOrSetFunc("a", func() string { return "1" })
|
|
t.Assert(v, "1")
|
|
|
|
v = m.GetOrSetFunc("a", func() string { return "10" })
|
|
t.Assert(v, "1")
|
|
|
|
v = m.GetOrSetFunc("b", func() string { return "2" })
|
|
t.Assert(v, "2")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_GetOrSetFuncLock(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
counter := 0
|
|
|
|
v := m.GetOrSetFuncLock("a", func() int {
|
|
counter++
|
|
return 10
|
|
})
|
|
t.Assert(v, 10)
|
|
t.Assert(counter, 1)
|
|
|
|
v = m.GetOrSetFuncLock("a", func() int {
|
|
counter++
|
|
return 20
|
|
})
|
|
t.Assert(v, 10)
|
|
t.Assert(counter, 1)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_SetIfNotExist(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
|
|
ok := m.SetIfNotExist("a", "1")
|
|
t.Assert(ok, true)
|
|
t.Assert(m.Get("a"), "1")
|
|
|
|
ok = m.SetIfNotExist("a", "10")
|
|
t.Assert(ok, false)
|
|
t.Assert(m.Get("a"), "1")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_SetIfNotExistFunc(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
|
|
ok := m.SetIfNotExistFunc("a", func() int { return 10 })
|
|
t.Assert(ok, true)
|
|
t.Assert(m.Get("a"), 10)
|
|
|
|
ok = m.SetIfNotExistFunc("a", func() int { return 20 })
|
|
t.Assert(ok, false)
|
|
t.Assert(m.Get("a"), 10)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_SetIfNotExistFuncLock(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
counter := 0
|
|
|
|
ok := m.SetIfNotExistFuncLock("a", func() string {
|
|
counter++
|
|
return "1"
|
|
})
|
|
t.Assert(ok, true)
|
|
t.Assert(counter, 1)
|
|
|
|
ok = m.SetIfNotExistFuncLock("a", func() string {
|
|
counter++
|
|
return "2"
|
|
})
|
|
t.Assert(ok, false)
|
|
t.Assert(counter, 1)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_GetVar(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
|
|
v := m.GetVar("a")
|
|
t.AssertNE(v, nil)
|
|
t.Assert(v.Val(), "1")
|
|
|
|
v = m.GetVar("c")
|
|
t.Assert(v.Val(), nil)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_GetVarOrSet(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
|
|
v := m.GetVarOrSet("a", "1")
|
|
t.AssertNE(v, nil)
|
|
t.Assert(v.Val(), "1")
|
|
|
|
v = m.GetVarOrSet("a", "10")
|
|
t.Assert(v.Val(), "1")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_GetVarOrSetFunc(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
|
|
v := m.GetVarOrSetFunc("a", func() int { return 10 })
|
|
t.AssertNE(v, nil)
|
|
t.Assert(v.Val(), 10)
|
|
|
|
v = m.GetVarOrSetFunc("a", func() int { return 20 })
|
|
t.Assert(v.Val(), 10)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_GetVarOrSetFuncLock(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
|
|
v := m.GetVarOrSetFuncLock("a", func() string { return "1" })
|
|
t.AssertNE(v, nil)
|
|
t.Assert(v.Val(), "1")
|
|
|
|
v = m.GetVarOrSetFuncLock("a", func() string { return "10" })
|
|
t.Assert(v.Val(), "1")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Iterator(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
data := map[string]string{"a": "1", "b": "2", "c": "3"}
|
|
m := gmap.NewKVMapFrom(data)
|
|
|
|
count := 0
|
|
m.Iterator(func(k string, v string) bool {
|
|
t.Assert(data[k], v)
|
|
count++
|
|
return true
|
|
})
|
|
t.Assert(count, 3)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[int]string{1: "a", 2: "b", 3: "c"})
|
|
|
|
count := 0
|
|
m.Iterator(func(k int, v string) bool {
|
|
count++
|
|
return count < 2
|
|
})
|
|
t.Assert(count, 2)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Iterator_Deadlock(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"1": "1", "2": "2", "3": "3", "4": "4"}, true)
|
|
m.Iterator(func(k string, _ string) bool {
|
|
kInt, _ := strconv.Atoi(k)
|
|
if kInt%2 == 0 {
|
|
m.Remove(k)
|
|
}
|
|
return true
|
|
})
|
|
t.Assert(m.Size(), 2)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_LockFunc(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
|
|
m.LockFunc(func(data map[string]string) {
|
|
t.Assert(data["a"], "1")
|
|
t.Assert(data["b"], "2")
|
|
data["c"] = "3"
|
|
})
|
|
|
|
t.Assert(m.Get("c"), "3")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_RLockFunc(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
count := 0
|
|
|
|
m.RLockFunc(func(data map[string]string) {
|
|
count += len(data)
|
|
})
|
|
|
|
t.Assert(count, 2)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Replace(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
t.Assert(m.Size(), 2)
|
|
|
|
m.Replace(map[string]string{"x": "10", "y": "20", "z": "30"})
|
|
t.Assert(m.Size(), 3)
|
|
t.Assert(m.Get("a"), "")
|
|
t.Assert(m.Get("x"), "10")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Clone(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
m2 := m.Clone()
|
|
|
|
t.Assert(m2.Get("a"), "1")
|
|
t.Assert(m2.Get("b"), "2")
|
|
t.Assert(m2.Size(), 2)
|
|
|
|
m.Set("a", "10")
|
|
t.Assert(m2.Get("a"), "1")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2}, false)
|
|
m2 := m.Clone(true)
|
|
|
|
t.Assert(m2.Size(), 2)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Flip(t *testing.T) {
|
|
// Test with same type for key and value (string -> string)
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
m.Flip()
|
|
|
|
t.Assert(m.Get("1"), "a")
|
|
t.Assert(m.Get("2"), "b")
|
|
t.Assert(m.Get("3"), "c")
|
|
})
|
|
|
|
// Test with same type for key and value (int -> int)
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[int]int{1: 10, 2: 20})
|
|
m.Flip()
|
|
|
|
t.Assert(m.Get(10), 1)
|
|
t.Assert(m.Get(20), 2)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Merge(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]string{"a": "1"})
|
|
m2 := gmap.NewKVMapFrom(map[string]string{"b": "2", "c": "3"})
|
|
|
|
m1.Merge(m2)
|
|
t.Assert(m1.Size(), 3)
|
|
t.Assert(m1.Get("a"), "1")
|
|
t.Assert(m1.Get("b"), "2")
|
|
t.Assert(m1.Get("c"), "3")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMap[string, int]()
|
|
m2 := gmap.NewKVMapFrom(map[string]int{"a": 10, "b": 20})
|
|
|
|
m1.Merge(m2)
|
|
t.Assert(m1.Size(), 2)
|
|
t.Assert(m1.Get("a"), 10)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]string{"a": "1"})
|
|
m2 := gmap.NewKVMapFrom(map[string]string{"a": "10", "b": "2"})
|
|
|
|
m1.Merge(m2)
|
|
t.Assert(m1.Get("a"), "10")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_IsSubOf(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
m2 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
|
|
t.Assert(m1.IsSubOf(m2), true)
|
|
t.Assert(m2.IsSubOf(m1), false)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
m2 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "10"})
|
|
|
|
t.Assert(m1.IsSubOf(m2), false)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]string{"a": "1"})
|
|
t.Assert(m1.IsSubOf(m1), true)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Diff(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
m2 := gmap.NewKVMapFrom(map[string]string{"a": "1", "d": "4"})
|
|
|
|
added, removed, updated := m1.Diff(m2)
|
|
t.Assert(len(added), 1)
|
|
t.AssertIN("d", added)
|
|
t.Assert(len(removed), 2)
|
|
t.AssertIN("b", removed)
|
|
t.AssertIN("c", removed)
|
|
t.Assert(len(updated), 0)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
m2 := gmap.NewKVMapFrom(map[string]string{"a": "10", "b": "2"})
|
|
|
|
added, removed, updated := m1.Diff(m2)
|
|
t.Assert(len(added), 0)
|
|
t.Assert(len(removed), 0)
|
|
t.Assert(len(updated), 1)
|
|
t.AssertIN("a", updated)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_String(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1"})
|
|
s := m.String()
|
|
t.AssertNE(s, "")
|
|
t.AssertIN("a", s)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var m *gmap.KVMap[string, string]
|
|
s := m.String()
|
|
t.Assert(s, "")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_MarshalJSON(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2})
|
|
b, err := json.Marshal(m)
|
|
t.AssertNil(err)
|
|
t.AssertNE(b, nil)
|
|
|
|
var data map[string]int
|
|
err = json.Unmarshal(b, &data)
|
|
t.AssertNil(err)
|
|
t.Assert(data["a"], 1)
|
|
t.Assert(data["b"], 2)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_UnmarshalJSON(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
data := []byte(`{"a":1,"b":2,"c":3}`)
|
|
|
|
err := json.UnmarshalUseNumber(data, m)
|
|
t.AssertNil(err)
|
|
t.Assert(m.Get("a"), 1)
|
|
t.Assert(m.Get("b"), 2)
|
|
t.Assert(m.Get("c"), 3)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var m gmap.KVMap[string, string]
|
|
data := []byte(`{"x":"10","y":"20"}`)
|
|
|
|
err := json.UnmarshalUseNumber(data, &m)
|
|
t.AssertNil(err)
|
|
t.Assert(m.Get("x"), "10")
|
|
t.Assert(m.Get("y"), "20")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_UnmarshalValue(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, string]()
|
|
err := m.UnmarshalValue(map[string]any{
|
|
"a": "1",
|
|
"b": "2",
|
|
})
|
|
t.AssertNil(err)
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Get("b"), "2")
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_DeepCopy(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string][]string{
|
|
"a": {"1", "2"},
|
|
"b": {"3", "4"},
|
|
})
|
|
|
|
n := m.DeepCopy().(*gmap.KVMap[string, []string])
|
|
t.Assert(n.Size(), 2)
|
|
t.Assert(n.Get("a"), []string{"1", "2"})
|
|
|
|
// Modifying original doesn't affect copy
|
|
m.Get("a")[0] = "10"
|
|
t.Assert(n.Get("a")[0], "1")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var m *gmap.KVMap[string, int]
|
|
n := m.DeepCopy()
|
|
t.AssertNil(n)
|
|
})
|
|
}
|
|
|
|
// Test Set with nil data
|
|
func Test_KVMap_Set_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Create map with nil internal data
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
m.Set("a", "1")
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Size(), 1)
|
|
})
|
|
}
|
|
|
|
// Test Sets with nil data
|
|
func Test_KVMap_Sets_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Create map with nil internal data
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
m.Sets(map[string]string{"a": "1", "b": "2"})
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Get("b"), "2")
|
|
t.Assert(m.Size(), 2)
|
|
})
|
|
}
|
|
|
|
// Test doSetWithLockCheck - key exists and value is nil
|
|
func Test_KVMap_GetOrSet_KeyExists(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
// First call: key does not exist, set value
|
|
v := m.GetOrSet("a", "1")
|
|
t.Assert(v, "1")
|
|
|
|
// Second call: key exists, should return existing value
|
|
v = m.GetOrSet("a", "2")
|
|
t.Assert(v, "1")
|
|
})
|
|
}
|
|
|
|
// Test GetOrSet with nil value
|
|
func Test_KVMap_GetOrSet_NilValue(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, *string]()
|
|
// Set nil value
|
|
v := m.GetOrSet("a", nil)
|
|
t.Assert(v, nil)
|
|
// Key is not stored when value is nil (based on implementation)
|
|
// The doSetWithLockCheck checks: if any(value) != nil
|
|
// For pointer type, nil is actually stored because any(nil pointer) is not nil interface
|
|
// Let's verify the actual behavior
|
|
})
|
|
|
|
// Test with interface type to trigger the nil check
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, any]()
|
|
v := m.GetOrSet("a", nil)
|
|
t.Assert(v, nil)
|
|
// nil interface value should not be stored
|
|
t.Assert(m.Contains("a"), false)
|
|
})
|
|
}
|
|
|
|
// Test GetOrSetFunc with nil value
|
|
func Test_KVMap_GetOrSetFunc_NilValue(t *testing.T) {
|
|
// Test with interface type to trigger the nil check
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, any]()
|
|
v := m.GetOrSetFunc("a", func() any { return nil })
|
|
t.Assert(v, nil)
|
|
// nil interface value should not be stored
|
|
t.Assert(m.Contains("a"), false)
|
|
})
|
|
}
|
|
|
|
// Test GetOrSetFuncLock with nil data and nil value
|
|
func Test_KVMap_GetOrSetFuncLock_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
v := m.GetOrSetFuncLock("a", func() string { return "1" })
|
|
t.Assert(v, "1")
|
|
t.Assert(m.Get("a"), "1")
|
|
})
|
|
|
|
// Test with nil value (using any type)
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, any]()
|
|
v := m.GetOrSetFuncLock("a", func() any { return nil })
|
|
t.Assert(v, nil)
|
|
// nil interface value should not be stored
|
|
t.Assert(m.Contains("a"), false)
|
|
})
|
|
}
|
|
|
|
// Test SetIfNotExist with nil data
|
|
func Test_KVMap_SetIfNotExist_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
ok := m.SetIfNotExist("a", "1")
|
|
t.Assert(ok, true)
|
|
t.Assert(m.Get("a"), "1")
|
|
})
|
|
}
|
|
|
|
// Test SetIfNotExistFuncLock with nil data
|
|
func Test_KVMap_SetIfNotExistFuncLock_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
ok := m.SetIfNotExistFuncLock("a", func() string { return "1" })
|
|
t.Assert(ok, true)
|
|
t.Assert(m.Get("a"), "1")
|
|
})
|
|
}
|
|
|
|
// Test Flip with conversion errors
|
|
func Test_KVMap_Flip_ConversionError(t *testing.T) {
|
|
// Test with incompatible types that will fail conversion
|
|
gtest.C(t, func(t *gtest.T) {
|
|
type customKey struct {
|
|
ID int
|
|
}
|
|
type customVal struct {
|
|
Name string
|
|
}
|
|
m := gmap.NewKVMapFrom(map[customKey]customVal{
|
|
{ID: 1}: {Name: "a"},
|
|
{ID: 2}: {Name: "b"},
|
|
})
|
|
// Flip will fail because customVal cannot be converted to customKey
|
|
m.Flip()
|
|
// After failed flip, map should be empty or unchanged depending on implementation
|
|
// Based on the code, items that fail conversion are skipped
|
|
})
|
|
}
|
|
|
|
// Test Merge with self
|
|
func Test_KVMap_Merge_Self(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
m.Merge(m)
|
|
t.Assert(m.Size(), 2)
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Get("b"), "2")
|
|
})
|
|
}
|
|
|
|
// Test Merge with nil data
|
|
func Test_KVMap_Merge_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
m2 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
m.Merge(m2)
|
|
t.Assert(m.Size(), 2)
|
|
t.Assert(m.Get("a"), "1")
|
|
t.Assert(m.Get("b"), "2")
|
|
})
|
|
}
|
|
|
|
// Test UnmarshalJSON with invalid JSON
|
|
func Test_KVMap_UnmarshalJSON_InvalidJSON(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
err := m.UnmarshalJSON([]byte(`{invalid json}`))
|
|
t.AssertNE(err, nil)
|
|
})
|
|
}
|
|
|
|
// Test UnmarshalJSON with incompatible value types
|
|
func Test_KVMap_UnmarshalJSON_TypeMismatch(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
// Valid JSON but values are strings, not ints
|
|
err := m.UnmarshalJSON([]byte(`{"a":"not_a_number"}`))
|
|
// This may or may not error depending on gconv.Scan behavior
|
|
// The test verifies the code path is executed
|
|
_ = err
|
|
})
|
|
}
|
|
|
|
// Test UnmarshalValue with conversion error
|
|
func Test_KVMap_UnmarshalValue_ConversionError(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
// This tests the conversion path
|
|
err := m.UnmarshalValue(map[string]any{
|
|
"a": "1",
|
|
"b": "2",
|
|
})
|
|
// Even with string values, gconv.Scan should handle conversion
|
|
t.AssertNil(err)
|
|
t.Assert(m.Get("a"), 1)
|
|
t.Assert(m.Get("b"), 2)
|
|
})
|
|
}
|
|
|
|
// Test Search with nil data
|
|
func Test_KVMap_Search_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
v, found := m.Search("a")
|
|
t.Assert(found, false)
|
|
t.Assert(v, "")
|
|
})
|
|
}
|
|
|
|
// Test Get with nil data
|
|
func Test_KVMap_Get_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, int](nil)
|
|
v := m.Get("a")
|
|
t.Assert(v, 0)
|
|
})
|
|
}
|
|
|
|
// Test Contains with nil data
|
|
func Test_KVMap_Contains_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
t.Assert(m.Contains("a"), false)
|
|
})
|
|
}
|
|
|
|
// Test Remove with nil data
|
|
func Test_KVMap_Remove_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
v := m.Remove("a")
|
|
t.Assert(v, "")
|
|
})
|
|
}
|
|
|
|
// Test Removes with nil data
|
|
func Test_KVMap_Removes_NilData(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
m.Removes([]string{"a", "b"})
|
|
t.Assert(m.Size(), 0)
|
|
})
|
|
}
|
|
|
|
// Test Pop from empty map
|
|
func Test_KVMap_Pop_Empty(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom[string, string](nil)
|
|
k, v := m.Pop()
|
|
t.Assert(k, "")
|
|
t.Assert(v, "")
|
|
})
|
|
}
|
|
|
|
// Test Pops with size 0
|
|
func Test_KVMap_Pops_ZeroSize(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"})
|
|
popped := m.Pops(0)
|
|
t.AssertNil(popped)
|
|
t.Assert(m.Size(), 2)
|
|
})
|
|
}
|
|
|
|
// Test Iterator early break
|
|
func Test_KVMap_Iterator_Break(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"})
|
|
count := 0
|
|
m.Iterator(func(k string, v string) bool {
|
|
count++
|
|
return false // Break immediately
|
|
})
|
|
t.Assert(count, 1)
|
|
})
|
|
}
|
|
|
|
// Test DeepCopy with safe mode
|
|
func Test_KVMap_DeepCopy_Safe(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}, true)
|
|
n := m.DeepCopy().(*gmap.KVMap[string, string])
|
|
t.Assert(n.Size(), 2)
|
|
t.Assert(n.Get("a"), "1")
|
|
})
|
|
}
|
|
|
|
// Concurrent safety tests
|
|
func Test_KVMap_Concurrent_Safe(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
ch := make(chan int, 10)
|
|
|
|
// Concurrent writes
|
|
for i := 0; i < 10; i++ {
|
|
go func(idx int) {
|
|
m.Set(gconv.String(idx), idx)
|
|
ch <- 1
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
<-ch
|
|
}
|
|
|
|
t.Assert(m.Size(), 10)
|
|
})
|
|
}
|
|
|
|
func Test_KVMap_Concurrent_RW(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
m.Sets(map[string]int{"a": 1, "b": 2, "c": 3})
|
|
|
|
ch := make(chan int, 20)
|
|
|
|
// Concurrent reads and writes
|
|
for i := 0; i < 10; i++ {
|
|
go func() {
|
|
_ = m.Get("a")
|
|
ch <- 1
|
|
}()
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
go func(idx int) {
|
|
m.Set(gconv.String(idx), idx)
|
|
ch <- 1
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < 20; i++ {
|
|
<-ch
|
|
}
|
|
|
|
t.Assert(m.Size(), 13)
|
|
})
|
|
}
|
|
|
|
// Test concurrent GetOrSet
|
|
func Test_KVMap_Concurrent_GetOrSet(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
ch := make(chan int, 100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
go func(idx int) {
|
|
m.GetOrSet("key", idx)
|
|
ch <- 1
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < 100; i++ {
|
|
<-ch
|
|
}
|
|
|
|
// Only one value should be set
|
|
t.Assert(m.Size(), 1)
|
|
t.Assert(m.Contains("key"), true)
|
|
})
|
|
}
|
|
|
|
// Test concurrent SetIfNotExist
|
|
func Test_KVMap_Concurrent_SetIfNotExist(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
successCount := 0
|
|
ch := make(chan bool, 100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
go func(idx int) {
|
|
ok := m.SetIfNotExist("key", idx)
|
|
ch <- ok
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < 100; i++ {
|
|
if <-ch {
|
|
successCount++
|
|
}
|
|
}
|
|
|
|
// Only one goroutine should succeed
|
|
t.Assert(successCount, 1)
|
|
t.Assert(m.Size(), 1)
|
|
})
|
|
}
|
|
|
|
// Test doSetWithLockCheck when key exists (race condition scenario)
|
|
func Test_KVMap_DoSetWithLockCheck_KeyExists(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
// First, set the key using GetOrSet
|
|
v := m.GetOrSet("a", 1)
|
|
t.Assert(v, 1)
|
|
|
|
// Second call - key exists in doSetWithLockCheck
|
|
// This simulates the race condition where the key is set between Search and doSetWithLockCheck
|
|
v = m.GetOrSet("a", 2)
|
|
t.Assert(v, 1)
|
|
})
|
|
}
|
|
|
|
// Test Flip with key conversion error
|
|
func Test_KVMap_Flip_KeyConversionError(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Create a map where key->value conversion will fail
|
|
// Using struct types that cannot be converted to each other
|
|
type Key struct {
|
|
ID int
|
|
}
|
|
type Value struct {
|
|
Name string
|
|
}
|
|
m := gmap.NewKVMapFrom(map[Key]Value{
|
|
{ID: 1}: {Name: "a"},
|
|
})
|
|
// This should not panic, but the conversion may succeed or fail
|
|
// depending on gconv.Scan implementation
|
|
m.Flip()
|
|
// Just verify it doesn't panic - size depends on conversion behavior
|
|
})
|
|
}
|
|
|
|
// Test Flip with value->key conversion success but key->value conversion failure
|
|
func Test_KVMap_Flip_ValueKeyConversionError(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Use int -> int where one direction might fail
|
|
m := gmap.NewKVMapFrom(map[int]int{1: 10, 2: 20})
|
|
m.Flip()
|
|
// Should flip successfully
|
|
t.Assert(m.Contains(10), true)
|
|
t.Assert(m.Contains(20), true)
|
|
t.Assert(m.Get(10), 1)
|
|
t.Assert(m.Get(20), 2)
|
|
})
|
|
}
|
|
|
|
// Test UnmarshalJSON with Scan error
|
|
func Test_KVMap_UnmarshalJSON_ScanError(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Create a map with int keys, but provide string keys in JSON
|
|
// that cannot be properly scanned to int
|
|
m := gmap.NewKVMap[int, string]()
|
|
// This JSON has string keys that need to be converted to int
|
|
err := m.UnmarshalJSON([]byte(`{"not_a_number":"value"}`))
|
|
// The error depends on gconv.Scan behavior
|
|
// Just verify the code path is executed
|
|
_ = err
|
|
})
|
|
}
|
|
|
|
// Test UnmarshalValue with Scan error
|
|
func Test_KVMap_UnmarshalValue_ScanError(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Create a map where the value type conversion will fail
|
|
type CustomStruct struct {
|
|
Field int
|
|
}
|
|
m := gmap.NewKVMap[string, CustomStruct]()
|
|
// Try to unmarshal incompatible data
|
|
err := m.UnmarshalValue(map[string]any{
|
|
"a": "not_a_struct",
|
|
})
|
|
// The error depends on gconv.Scan behavior
|
|
_ = err
|
|
})
|
|
}
|
|
|
|
// Test concurrent GetOrSetFunc
|
|
func Test_KVMap_Concurrent_GetOrSetFunc(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
ch := make(chan int, 100)
|
|
counter := int32(0)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
go func(idx int) {
|
|
m.GetOrSetFunc("key", func() int {
|
|
// Increment counter to track how many times the function is called
|
|
return idx
|
|
})
|
|
ch <- 1
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < 100; i++ {
|
|
<-ch
|
|
}
|
|
|
|
t.Assert(m.Size(), 1)
|
|
_ = counter
|
|
})
|
|
}
|
|
|
|
// Test concurrent GetOrSetFuncLock
|
|
func Test_KVMap_Concurrent_GetOrSetFuncLock(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
ch := make(chan int, 100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
go func(idx int) {
|
|
m.GetOrSetFuncLock("key", func() int {
|
|
return idx
|
|
})
|
|
ch <- 1
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < 100; i++ {
|
|
<-ch
|
|
}
|
|
|
|
t.Assert(m.Size(), 1)
|
|
})
|
|
}
|
|
|
|
// Test concurrent LockFunc
|
|
func Test_KVMap_Concurrent_LockFunc(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
m.Set("counter", 0)
|
|
ch := make(chan int, 100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
go func() {
|
|
m.LockFunc(func(data map[string]int) {
|
|
data["counter"]++
|
|
})
|
|
ch <- 1
|
|
}()
|
|
}
|
|
|
|
for i := 0; i < 100; i++ {
|
|
<-ch
|
|
}
|
|
|
|
t.Assert(m.Get("counter"), 100)
|
|
})
|
|
}
|
|
|
|
// Test empty map operations
|
|
func Test_KVMap_EmptyMapOperations(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
|
|
// Test Keys on empty map
|
|
keys := m.Keys()
|
|
t.Assert(len(keys), 0)
|
|
|
|
// Test Values on empty map
|
|
values := m.Values()
|
|
t.Assert(len(values), 0)
|
|
|
|
// Test MapCopy on empty map
|
|
copy := m.MapCopy()
|
|
t.Assert(len(copy), 0)
|
|
|
|
// Test MapStrAny on empty map
|
|
strAny := m.MapStrAny()
|
|
t.Assert(len(strAny), 0)
|
|
})
|
|
}
|
|
|
|
// Test FilterEmpty with various empty values
|
|
func Test_KVMap_FilterEmpty_Various(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, any]()
|
|
m.Set("nil", nil)
|
|
m.Set("zero", 0)
|
|
m.Set("empty_string", "")
|
|
m.Set("false", false)
|
|
m.Set("valid", "value")
|
|
m.Set("empty_slice", []int{})
|
|
m.Set("empty_map", map[string]int{})
|
|
|
|
t.Assert(m.Size(), 7)
|
|
m.FilterEmpty()
|
|
t.Assert(m.Size(), 1)
|
|
t.Assert(m.Contains("valid"), true)
|
|
})
|
|
}
|
|
|
|
// Test FilterNil with various nil values
|
|
func Test_KVMap_FilterNil_Various(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, any]()
|
|
m.Set("nil", nil)
|
|
m.Set("zero", 0)
|
|
m.Set("empty_string", "")
|
|
m.Set("valid", "value")
|
|
|
|
t.Assert(m.Size(), 4)
|
|
m.FilterNil()
|
|
t.Assert(m.Size(), 3)
|
|
t.Assert(m.Contains("nil"), false)
|
|
})
|
|
}
|
|
|
|
// Test Clone with different safe modes
|
|
func Test_KVMap_Clone_SafeMode(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Clone unsafe map to safe
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 1}, false)
|
|
m2 := m.Clone(true)
|
|
t.Assert(m2.Get("a"), 1)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Clone safe map to unsafe
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 1}, true)
|
|
m2 := m.Clone(false)
|
|
t.Assert(m2.Get("a"), 1)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Clone with inherited safe mode
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 1}, true)
|
|
m2 := m.Clone()
|
|
t.Assert(m2.Get("a"), 1)
|
|
})
|
|
}
|
|
|
|
// Test Diff with empty maps
|
|
func Test_KVMap_Diff_Empty(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMap[string, int]()
|
|
m2 := gmap.NewKVMap[string, int]()
|
|
|
|
added, removed, updated := m1.Diff(m2)
|
|
t.Assert(len(added), 0)
|
|
t.Assert(len(removed), 0)
|
|
t.Assert(len(updated), 0)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMap[string, int]()
|
|
m2 := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2})
|
|
|
|
added, removed, updated := m1.Diff(m2)
|
|
t.Assert(len(added), 2)
|
|
t.Assert(len(removed), 0)
|
|
t.Assert(len(updated), 0)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2})
|
|
m2 := gmap.NewKVMap[string, int]()
|
|
|
|
added, removed, updated := m1.Diff(m2)
|
|
t.Assert(len(added), 0)
|
|
t.Assert(len(removed), 2)
|
|
t.Assert(len(updated), 0)
|
|
})
|
|
}
|
|
|
|
// Test IsSubOf with empty maps
|
|
func Test_KVMap_IsSubOf_Empty(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMap[string, int]()
|
|
m2 := gmap.NewKVMapFrom(map[string]int{"a": 1})
|
|
|
|
// Empty map is always a subset
|
|
t.Assert(m1.IsSubOf(m2), true)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m1 := gmap.NewKVMapFrom(map[string]int{"a": 1})
|
|
m2 := gmap.NewKVMap[string, int]()
|
|
|
|
// Non-empty map is not a subset of empty map
|
|
t.Assert(m1.IsSubOf(m2), false)
|
|
})
|
|
}
|
|
|
|
// Test concurrent access to doSetWithLockCheck
|
|
func Test_KVMap_DoSetWithLockCheck_Concurrent(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// This test creates a race condition where multiple goroutines
|
|
// try to set the same key, triggering the "key exists" branch in doSetWithLockCheck
|
|
m := gmap.NewKVMap[string, int](true)
|
|
var wg sync.WaitGroup
|
|
results := make([]int, 100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go func(idx int) {
|
|
defer wg.Done()
|
|
// All goroutines try to set the same key
|
|
v := m.GetOrSet("key", idx)
|
|
results[idx] = v
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
// All results should be the same (the first value that was set)
|
|
firstValue := results[0]
|
|
for _, v := range results {
|
|
t.Assert(v, firstValue)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test GetOrSetFunc concurrent to trigger doSetWithLockCheck key exists branch
|
|
func Test_KVMap_GetOrSetFunc_Concurrent(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int](true)
|
|
var wg sync.WaitGroup
|
|
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go func(idx int) {
|
|
defer wg.Done()
|
|
m.GetOrSetFunc("key", func() int { return idx })
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
t.Assert(m.Size(), 1)
|
|
})
|
|
}
|
|
|
|
// Test SetIfNotExistFunc returning false when key exists
|
|
func Test_KVMap_SetIfNotExistFunc_KeyExists(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
m.Set("a", 1)
|
|
|
|
called := false
|
|
ok := m.SetIfNotExistFunc("a", func() int {
|
|
called = true
|
|
return 2
|
|
})
|
|
t.Assert(ok, false)
|
|
t.Assert(called, false) // Function should not be called if key exists
|
|
t.Assert(m.Get("a"), 1)
|
|
})
|
|
}
|
|
|
|
// Test UnmarshalValue with nil input
|
|
func Test_KVMap_UnmarshalValue_Nil(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
err := m.UnmarshalValue(nil)
|
|
t.AssertNil(err)
|
|
t.Assert(m.Size(), 0)
|
|
})
|
|
}
|
|
|
|
// Test MarshalJSON with empty map
|
|
func Test_KVMap_MarshalJSON_Empty(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
b, err := m.MarshalJSON()
|
|
t.AssertNil(err)
|
|
t.Assert(string(b), "{}")
|
|
})
|
|
}
|
|
|
|
// Test String with empty map
|
|
func Test_KVMap_String_Empty(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMap[string, int]()
|
|
s := m.String()
|
|
t.Assert(s, "{}")
|
|
})
|
|
}
|
|
|
|
// Test RLockFunc with concurrent access
|
|
func Test_KVMap_RLockFunc_Concurrent(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2}, true)
|
|
var wg sync.WaitGroup
|
|
results := make([]int, 100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go func(idx int) {
|
|
defer wg.Done()
|
|
m.RLockFunc(func(data map[string]int) {
|
|
results[idx] = data["a"]
|
|
})
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
for _, v := range results {
|
|
t.Assert(v, 1)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test Flip with string types to cover both conversion branches
|
|
func Test_KVMap_Flip_String(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
m := gmap.NewKVMapFrom(map[string]string{"key1": "val1", "key2": "val2"})
|
|
m.Flip()
|
|
t.Assert(m.Get("val1"), "key1")
|
|
t.Assert(m.Get("val2"), "key2")
|
|
})
|
|
}
|
|
|
|
// Test TypedNil with custom nil checker for pointers
|
|
func Test_KVMap_TypedNil(t *testing.T) {
|
|
gtest.C(t, func(t *gtest.T) {
|
|
type Student struct {
|
|
Name string
|
|
Age int
|
|
}
|
|
m1 := gmap.NewKVMap[int, *Student](true)
|
|
for i := 0; i < 10; i++ {
|
|
m1.GetOrSetFuncLock(i, func() *Student {
|
|
if i%2 == 0 {
|
|
return &Student{}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
t.Assert(m1.Size(), 10)
|
|
m2 := gmap.NewKVMap[int, *Student](true)
|
|
m2.RegisterNilChecker(func(student *Student) bool {
|
|
return student == nil
|
|
})
|
|
for i := 0; i < 10; i++ {
|
|
m2.GetOrSetFuncLock(i, func() *Student {
|
|
if i%2 == 0 {
|
|
return &Student{}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
t.Assert(m2.Size(), 5)
|
|
})
|
|
}
|