mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-11-04 08:40:20 +00:00
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
package singleton
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nezhahq/nezha/model"
|
|
)
|
|
|
|
func newNotificationClassWithItems(items ...*model.Notification) *NotificationClass {
|
|
nc := NewEmptyNotificationClassForTest()
|
|
for _, item := range items {
|
|
nc.InsertForTest(item)
|
|
}
|
|
return nc
|
|
}
|
|
|
|
func TestNotificationClassDeleteGroupCleansReverseIndex(t *testing.T) {
|
|
n := &model.Notification{Common: model.Common{ID: 1, UserID: 2}, Name: "hook"}
|
|
nc := newNotificationClassWithItems(n)
|
|
group := &model.NotificationGroup{Common: model.Common{ID: 10, UserID: 2}, Name: "group"}
|
|
nc.UpdateGroup(group, []uint64{n.ID})
|
|
|
|
nc.DeleteGroup([]uint64{group.ID})
|
|
|
|
if _, ok := nc.groupToIDList[group.ID]; ok {
|
|
t.Fatalf("forward index still contains deleted group %d", group.ID)
|
|
}
|
|
if groups, ok := nc.idToGroupList[n.ID]; ok {
|
|
if _, stale := groups[group.ID]; stale {
|
|
t.Fatalf("reverse index for notification %d still contains deleted group %d", n.ID, group.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNotificationClassUpdateGroupCleansRemovedMembership(t *testing.T) {
|
|
first := &model.Notification{Common: model.Common{ID: 1, UserID: 2}, Name: "first"}
|
|
second := &model.Notification{Common: model.Common{ID: 2, UserID: 2}, Name: "second"}
|
|
nc := newNotificationClassWithItems(first, second)
|
|
group := &model.NotificationGroup{Common: model.Common{ID: 10, UserID: 2}, Name: "group"}
|
|
nc.UpdateGroup(group, []uint64{first.ID, second.ID})
|
|
|
|
nc.UpdateGroup(group, []uint64{second.ID})
|
|
|
|
if groups, ok := nc.idToGroupList[first.ID]; ok {
|
|
if _, stale := groups[group.ID]; stale {
|
|
t.Fatalf("reverse index for notification %d still contains group %d", first.ID, group.ID)
|
|
}
|
|
}
|
|
if _, ok := nc.idToGroupList[second.ID][group.ID]; !ok {
|
|
t.Fatalf("reverse index lost retained membership of notification %d in group %d", second.ID, group.ID)
|
|
}
|
|
}
|
|
|
|
func TestNotificationClassUpdateRepairsOrphanedReverseIndex(t *testing.T) {
|
|
n := &model.Notification{Common: model.Common{ID: 1, UserID: 2}, Name: "hook"}
|
|
nc := newNotificationClassWithItems(n)
|
|
nc.idToGroupList[n.ID] = map[uint64]struct{}{99: {}}
|
|
|
|
n.Name = "updated"
|
|
nc.Update(n)
|
|
|
|
if groups, ok := nc.idToGroupList[n.ID]; ok && len(groups) != 0 {
|
|
t.Fatalf("orphaned reverse memberships were not removed: %v", groups)
|
|
}
|
|
}
|
|
|
|
func TestNotificationClassGroupMutationsDoNotDeadlock(t *testing.T) {
|
|
nc := NewEmptyNotificationClassForTest()
|
|
nc.listMu.RLock()
|
|
readLockHeld := true
|
|
defer func() {
|
|
if readLockHeld {
|
|
nc.listMu.RUnlock()
|
|
}
|
|
}()
|
|
|
|
deleteDone := make(chan struct{})
|
|
go func() {
|
|
nc.DeleteGroup([]uint64{10})
|
|
close(deleteDone)
|
|
}()
|
|
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for nc.listMu.TryRLock() {
|
|
nc.listMu.RUnlock()
|
|
if time.Now().After(deadline) {
|
|
t.Fatal("DeleteGroup did not queue for listMu")
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
|
|
updateDone := make(chan struct{})
|
|
go func() {
|
|
nc.UpdateGroup(&model.NotificationGroup{
|
|
Common: model.Common{ID: 10, UserID: 2},
|
|
Name: "group",
|
|
}, nil)
|
|
close(updateDone)
|
|
}()
|
|
|
|
deadline = time.Now().Add(2 * time.Second)
|
|
for nc.groupMu.TryLock() {
|
|
nc.groupMu.Unlock()
|
|
if time.Now().After(deadline) {
|
|
t.Fatal("neither group mutation acquired groupMu")
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
|
|
nc.listMu.RUnlock()
|
|
readLockHeld = false
|
|
|
|
timer := time.NewTimer(2 * time.Second)
|
|
defer timer.Stop()
|
|
for deleteDone != nil || updateDone != nil {
|
|
select {
|
|
case <-deleteDone:
|
|
deleteDone = nil
|
|
case <-updateDone:
|
|
updateDone = nil
|
|
case <-timer.C:
|
|
t.Fatal("concurrent UpdateGroup and DeleteGroup deadlocked")
|
|
}
|
|
}
|
|
}
|