| @@ -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) | |||
| }) | |||
| } | |||
| @@ -15,6 +15,7 @@ const ( | |||
| type HasSuchLockFn = func() bool | |||
| // LockCompatibilitySpecialFn 判断锁与指定的锁名是否兼容 | |||
| type LockCompatibilitySpecialFn func(lock distlock.Lock, testLockName string) bool | |||
| type LockCompatibilityType string | |||
| @@ -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) | |||
| }) | |||
| } | |||
| @@ -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 | |||
| @@ -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) | |||
| }) | |||
| } | |||
| } | |||
| @@ -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 { | |||
| @@ -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"}) | |||
| }) | |||
| } | |||