diff --git a/pkg/distlock/lockprovider/ipfs_lock_test.go b/pkg/distlock/lockprovider/ipfs_lock_test.go new file mode 100644 index 0000000..4c71254 --- /dev/null +++ b/pkg/distlock/lockprovider/ipfs_lock_test.go @@ -0,0 +1,113 @@ +package lockprovider + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + "gitlink.org.cn/cloudream/common/pkg/distlock" +) + +func Test_IPFSLock(t *testing.T) { + cases := []struct { + title string + initLocks []distlock.Lock + doLock distlock.Lock + wantOK bool + }{ + { + title: "同节点,同一个Read锁", + initLocks: []distlock.Lock{ + { + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_SET_READ_LOCK, + }, + }, + doLock: distlock.Lock{ + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_SET_READ_LOCK, + }, + wantOK: true, + }, + { + title: "同节点,同一个Write锁", + initLocks: []distlock.Lock{ + { + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_SET_WRITE_LOCK, + }, + }, + doLock: distlock.Lock{ + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_SET_WRITE_LOCK, + }, + wantOK: false, + }, + { + title: "不同节点,同一个Write锁", + initLocks: []distlock.Lock{ + { + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_SET_WRITE_LOCK, + }, + }, + doLock: distlock.Lock{ + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node2"}, + Name: IPFS_SET_WRITE_LOCK, + }, + wantOK: true, + }, + { + title: "相同对象的Read、Write锁", + initLocks: []distlock.Lock{ + { + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_ELEMENT_WRITE_LOCK, + Target: *NewStringLockTarget(), + }, + }, + doLock: distlock.Lock{ + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_ELEMENT_WRITE_LOCK, + Target: *NewStringLockTarget(), + }, + wantOK: false, + }, + } + + for _, ca := range cases { + Convey(ca.title, t, func() { + ipfsLock := NewIPFSLock() + + for _, l := range ca.initLocks { + ipfsLock.Lock("req1", l) + } + + err := ipfsLock.CanLock(ca.doLock) + if ca.wantOK { + So(err, ShouldBeNil) + } else { + So(err, ShouldNotBeNil) + } + }) + } + + Convey("解锁", t, func() { + ipfsLock := NewIPFSLock() + + lock := distlock.Lock{ + Path: []string{distlock.IPFS_LOCK_PATH_PREFIX, "node1"}, + Name: IPFS_SET_WRITE_LOCK, + } + + ipfsLock.Lock("req1", lock) + + err := ipfsLock.CanLock(lock) + So(err, ShouldNotBeNil) + + ipfsLock.Unlock("req1", lock) + + err = ipfsLock.CanLock(lock) + So(err, ShouldBeNil) + }) + +} diff --git a/pkg/distlock/lockprovider/lock_compatibility_table.go b/pkg/distlock/lockprovider/lock_compatibility_table.go index 18b3497..0d125c0 100644 --- a/pkg/distlock/lockprovider/lock_compatibility_table.go +++ b/pkg/distlock/lockprovider/lock_compatibility_table.go @@ -15,6 +15,7 @@ const ( type HasSuchLockFn = func() bool +// LockCompatibilitySpecialFn 判断锁与指定的锁名是否兼容 type LockCompatibilitySpecialFn func(lock distlock.Lock, testLockName string) bool type LockCompatibilityType string diff --git a/pkg/distlock/lockprovider/lock_compatibility_table_test.go b/pkg/distlock/lockprovider/lock_compatibility_table_test.go new file mode 100644 index 0000000..d28cdf3 --- /dev/null +++ b/pkg/distlock/lockprovider/lock_compatibility_table_test.go @@ -0,0 +1,41 @@ +package lockprovider + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + "gitlink.org.cn/cloudream/common/pkg/distlock" +) + +func Test_LockCompatibilityTable(t *testing.T) { + Convey("兼容,互斥,特殊比较", t, func() { + table := LockCompatibilityTable{} + + table. + Column("l1", func() bool { return true }). + Column("l2", func() bool { return true }). + Column("l3", func() bool { return false }) + + comp := LockCompatible() + uncp := LockUncompatible() + spcl := LockSpecial(func(lock distlock.Lock, testLockName string) bool { return true }) + table.Row(comp, comp, comp) + table.Row(comp, uncp, comp) + table.Row(comp, comp, spcl) + + err := table.Test(distlock.Lock{ + Name: "l1", + }) + So(err, ShouldBeNil) + + err = table.Test(distlock.Lock{ + Name: "l2", + }) + So(err, ShouldNotBeNil) + + err = table.Test(distlock.Lock{ + Name: "l3", + }) + So(err, ShouldBeNil) + }) +} diff --git a/pkg/distlock/lockprovider/string_lock_target.go b/pkg/distlock/lockprovider/string_lock_target.go index b674fd1..729dc85 100644 --- a/pkg/distlock/lockprovider/string_lock_target.go +++ b/pkg/distlock/lockprovider/string_lock_target.go @@ -30,6 +30,10 @@ func (t *StringLockTarget) IsConflict(other *StringLockTarget) bool { return false } + if len(t.Components) == 0 { + return true + } + for i := 0; i < len(t.Components); i++ { if t.Components[i].IsEquals(&other.Components[i]) { return true diff --git a/pkg/distlock/lockprovider/string_lock_target_test.go b/pkg/distlock/lockprovider/string_lock_target_test.go new file mode 100644 index 0000000..9cc5a73 --- /dev/null +++ b/pkg/distlock/lockprovider/string_lock_target_test.go @@ -0,0 +1,60 @@ +package lockprovider + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func Test_StringLockTarget(t *testing.T) { + cases := []struct { + title string + target1 *StringLockTarget + target2 *StringLockTarget + wantIsConflict bool + }{ + { + title: "没有任何段算冲突", + target1: NewStringLockTarget(), + target2: NewStringLockTarget(), + wantIsConflict: true, + }, + { + title: "有段,但段内为空,算冲突", + target1: NewStringLockTarget().Add(), + target2: NewStringLockTarget().Add(), + wantIsConflict: true, + }, + { + title: "每一段不同才不冲突", + target1: NewStringLockTarget().Add("a").Add("b"), + target2: NewStringLockTarget().Add("b").Add("c"), + wantIsConflict: false, + }, + { + title: "只要有一段相同就冲突", + target1: NewStringLockTarget().Add("a").Add("b"), + target2: NewStringLockTarget().Add("a").Add("c"), + wantIsConflict: true, + }, + { + title: "同段内,只要有一个数据不同就不冲突", + target1: NewStringLockTarget().Add("a", "b"), + target2: NewStringLockTarget().Add("b", "b"), + wantIsConflict: false, + }, + { + title: "同段内,只要每个数据都相同才不冲突", + target1: NewStringLockTarget().Add("a", "b"), + target2: NewStringLockTarget().Add("a", "b"), + wantIsConflict: true, + }, + } + + for _, ca := range cases { + Convey(ca.title, t, func() { + ret := ca.target1.IsConflict(ca.target2) + So(ret, ShouldEqual, ca.wantIsConflict) + }) + } +} diff --git a/utils/lo/lo.go b/utils/lo/lo.go index 85f3dc7..1852af3 100644 --- a/utils/lo/lo.go +++ b/utils/lo/lo.go @@ -16,7 +16,7 @@ func RemoveAt[T any](arr []T, index int) []T { return arr } - return append(arr[:index], arr[:index+1]...) + return append(arr[:index], arr[index+1:]...) } func ArrayClone[T any](arr []T) []T { diff --git a/utils/lo/lo_test.go b/utils/lo/lo_test.go new file mode 100644 index 0000000..b6ff303 --- /dev/null +++ b/utils/lo/lo_test.go @@ -0,0 +1,56 @@ +package lo + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func Test_Remove(t *testing.T) { + Convey("删除数组元素", t, func() { + arr := []string{"a", "b", "c"} + arr = Remove(arr, "b") + So(arr, ShouldResemble, []string{"a", "c"}) + }) + + Convey("删除最后一个元素", t, func() { + arr := []string{"a", "b", "c"} + arr = Remove(arr, "c") + So(arr, ShouldResemble, []string{"a", "b"}) + }) + + Convey("删除第一个元素", t, func() { + arr := []string{"a", "b", "c"} + arr = Remove(arr, "a") + So(arr, ShouldResemble, []string{"b", "c"}) + }) + + Convey("删除不存在的元素", t, func() { + arr := []string{"a", "b", "c"} + arr = Remove(arr, "d") + So(arr, ShouldResemble, []string{"a", "b", "c"}) + }) +} + +func Test_ArrayClone(t *testing.T) { + Convey("复制数组", t, func() { + arr := []string{"a", "b", "c"} + arr2 := ArrayClone(arr) + + arr2[1] = "a" + + So(arr, ShouldResemble, []string{"a", "b", "c"}) + So(arr2, ShouldResemble, []string{"a", "a", "c"}) + }) + + Convey("复制出来的数组进行append", t, func() { + arr := []string{"a", "b", "c"} + arr2 := ArrayClone(arr) + + arr = append(arr, "d") + arr2 = append(arr2, "c") + + So(arr, ShouldResemble, []string{"a", "b", "c", "d"}) + So(arr2, ShouldResemble, []string{"a", "b", "c", "c"}) + }) +}