| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- //go:build unit
- package service
- import (
- "testing"
- "time"
- "github.com/stretchr/testify/require"
- )
- // ============ shuffleWithinSortGroups 测试 ============
- func TestShuffleWithinSortGroups_Empty(t *testing.T) {
- shuffleWithinSortGroups(nil)
- shuffleWithinSortGroups([]accountWithLoad{})
- }
- func TestShuffleWithinSortGroups_SingleElement(t *testing.T) {
- accounts := []accountWithLoad{
- {account: &Account{ID: 1, Priority: 1}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- }
- shuffleWithinSortGroups(accounts)
- require.Equal(t, int64(1), accounts[0].account.ID)
- }
- func TestShuffleWithinSortGroups_DifferentGroups_OrderPreserved(t *testing.T) {
- now := time.Now()
- earlier := now.Add(-1 * time.Hour)
- accounts := []accountWithLoad{
- {account: &Account{ID: 1, Priority: 1, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- {account: &Account{ID: 2, Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 20}},
- {account: &Account{ID: 3, Priority: 2, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- }
- // 每个元素都属于不同组(Priority 或 LoadRate 或 LastUsedAt 不同),顺序不变
- for i := 0; i < 20; i++ {
- cpy := make([]accountWithLoad, len(accounts))
- copy(cpy, accounts)
- shuffleWithinSortGroups(cpy)
- require.Equal(t, int64(1), cpy[0].account.ID)
- require.Equal(t, int64(2), cpy[1].account.ID)
- require.Equal(t, int64(3), cpy[2].account.ID)
- }
- }
- func TestShuffleWithinSortGroups_SameGroup_Shuffled(t *testing.T) {
- now := time.Now()
- // 同一秒的时间戳视为同一组
- sameSecond := time.Unix(now.Unix(), 0)
- sameSecond2 := time.Unix(now.Unix(), 500_000_000) // 同一秒但不同纳秒
- accounts := []accountWithLoad{
- {account: &Account{ID: 1, Priority: 1, LastUsedAt: &sameSecond}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- {account: &Account{ID: 2, Priority: 1, LastUsedAt: &sameSecond2}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- {account: &Account{ID: 3, Priority: 1, LastUsedAt: &sameSecond}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- }
- // 多次执行,验证所有 ID 都出现在第一个位置(说明确实被打乱了)
- seen := map[int64]bool{}
- for i := 0; i < 100; i++ {
- cpy := make([]accountWithLoad, len(accounts))
- copy(cpy, accounts)
- shuffleWithinSortGroups(cpy)
- seen[cpy[0].account.ID] = true
- // 无论怎么打乱,所有 ID 都应在候选中
- ids := map[int64]bool{}
- for _, a := range cpy {
- ids[a.account.ID] = true
- }
- require.True(t, ids[1] && ids[2] && ids[3])
- }
- // 至少 2 个不同的 ID 出现在首位(随机性验证)
- require.GreaterOrEqual(t, len(seen), 2, "shuffle should produce different orderings")
- }
- func TestShuffleWithinSortGroups_NilLastUsedAt_SameGroup(t *testing.T) {
- accounts := []accountWithLoad{
- {account: &Account{ID: 1, Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}},
- {account: &Account{ID: 2, Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}},
- {account: &Account{ID: 3, Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}},
- }
- seen := map[int64]bool{}
- for i := 0; i < 100; i++ {
- cpy := make([]accountWithLoad, len(accounts))
- copy(cpy, accounts)
- shuffleWithinSortGroups(cpy)
- seen[cpy[0].account.ID] = true
- }
- require.GreaterOrEqual(t, len(seen), 2, "nil LastUsedAt accounts should be shuffled")
- }
- func TestShuffleWithinSortGroups_MixedGroups(t *testing.T) {
- now := time.Now()
- earlier := now.Add(-1 * time.Hour)
- sameAsNow := time.Unix(now.Unix(), 0)
- // 组1: Priority=1, LoadRate=10, LastUsedAt=earlier (ID 1) — 单元素组
- // 组2: Priority=1, LoadRate=20, LastUsedAt=now (ID 2, 3) — 双元素组
- // 组3: Priority=2, LoadRate=10, LastUsedAt=earlier (ID 4) — 单元素组
- accounts := []accountWithLoad{
- {account: &Account{ID: 1, Priority: 1, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- {account: &Account{ID: 2, Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 20}},
- {account: &Account{ID: 3, Priority: 1, LastUsedAt: &sameAsNow}, loadInfo: &AccountLoadInfo{LoadRate: 20}},
- {account: &Account{ID: 4, Priority: 2, LastUsedAt: &earlier}, loadInfo: &AccountLoadInfo{LoadRate: 10}},
- }
- for i := 0; i < 20; i++ {
- cpy := make([]accountWithLoad, len(accounts))
- copy(cpy, accounts)
- shuffleWithinSortGroups(cpy)
- // 组间顺序不变
- require.Equal(t, int64(1), cpy[0].account.ID, "group 1 position fixed")
- require.Equal(t, int64(4), cpy[3].account.ID, "group 3 position fixed")
- // 组2 内部可以打乱,但仍在位置 1 和 2
- mid := map[int64]bool{cpy[1].account.ID: true, cpy[2].account.ID: true}
- require.True(t, mid[2] && mid[3], "group 2 elements should stay in positions 1-2")
- }
- }
- // ============ shuffleWithinPriorityAndLastUsed 测试 ============
- func TestShuffleWithinPriorityAndLastUsed_Empty(t *testing.T) {
- shuffleWithinPriorityAndLastUsed(nil, false)
- shuffleWithinPriorityAndLastUsed([]*Account{}, false)
- }
- func TestShuffleWithinPriorityAndLastUsed_SingleElement(t *testing.T) {
- accounts := []*Account{{ID: 1, Priority: 1}}
- shuffleWithinPriorityAndLastUsed(accounts, false)
- require.Equal(t, int64(1), accounts[0].ID)
- }
- func TestShuffleWithinPriorityAndLastUsed_SameGroup_Shuffled(t *testing.T) {
- accounts := []*Account{
- {ID: 1, Priority: 1, LastUsedAt: nil},
- {ID: 2, Priority: 1, LastUsedAt: nil},
- {ID: 3, Priority: 1, LastUsedAt: nil},
- }
- seen := map[int64]bool{}
- for i := 0; i < 100; i++ {
- cpy := make([]*Account, len(accounts))
- copy(cpy, accounts)
- shuffleWithinPriorityAndLastUsed(cpy, false)
- seen[cpy[0].ID] = true
- }
- require.GreaterOrEqual(t, len(seen), 2, "same group should be shuffled")
- }
- func TestShuffleWithinPriorityAndLastUsed_DifferentPriority_OrderPreserved(t *testing.T) {
- accounts := []*Account{
- {ID: 1, Priority: 1, LastUsedAt: nil},
- {ID: 2, Priority: 2, LastUsedAt: nil},
- {ID: 3, Priority: 3, LastUsedAt: nil},
- }
- for i := 0; i < 20; i++ {
- cpy := make([]*Account, len(accounts))
- copy(cpy, accounts)
- shuffleWithinPriorityAndLastUsed(cpy, false)
- require.Equal(t, int64(1), cpy[0].ID)
- require.Equal(t, int64(2), cpy[1].ID)
- require.Equal(t, int64(3), cpy[2].ID)
- }
- }
- func TestShuffleWithinPriorityAndLastUsed_DifferentLastUsedAt_OrderPreserved(t *testing.T) {
- now := time.Now()
- earlier := now.Add(-1 * time.Hour)
- accounts := []*Account{
- {ID: 1, Priority: 1, LastUsedAt: nil},
- {ID: 2, Priority: 1, LastUsedAt: &earlier},
- {ID: 3, Priority: 1, LastUsedAt: &now},
- }
- for i := 0; i < 20; i++ {
- cpy := make([]*Account, len(accounts))
- copy(cpy, accounts)
- shuffleWithinPriorityAndLastUsed(cpy, false)
- require.Equal(t, int64(1), cpy[0].ID)
- require.Equal(t, int64(2), cpy[1].ID)
- require.Equal(t, int64(3), cpy[2].ID)
- }
- }
- // ============ sameLastUsedAt 测试 ============
- func TestSameLastUsedAt(t *testing.T) {
- now := time.Now()
- sameSecond := time.Unix(now.Unix(), 0)
- sameSecondDiffNano := time.Unix(now.Unix(), 999_999_999)
- differentSecond := now.Add(1 * time.Second)
- t.Run("both nil", func(t *testing.T) {
- require.True(t, sameLastUsedAt(nil, nil))
- })
- t.Run("one nil one not", func(t *testing.T) {
- require.False(t, sameLastUsedAt(nil, &now))
- require.False(t, sameLastUsedAt(&now, nil))
- })
- t.Run("same second different nanoseconds", func(t *testing.T) {
- require.True(t, sameLastUsedAt(&sameSecond, &sameSecondDiffNano))
- })
- t.Run("different seconds", func(t *testing.T) {
- require.False(t, sameLastUsedAt(&now, &differentSecond))
- })
- t.Run("exact same time", func(t *testing.T) {
- require.True(t, sameLastUsedAt(&now, &now))
- })
- }
- // ============ sameAccountWithLoadGroup 测试 ============
- func TestSameAccountWithLoadGroup(t *testing.T) {
- now := time.Now()
- sameSecond := time.Unix(now.Unix(), 0)
- t.Run("same group", func(t *testing.T) {
- a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
- b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &sameSecond}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
- require.True(t, sameAccountWithLoadGroup(a, b))
- })
- t.Run("different priority", func(t *testing.T) {
- a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
- b := accountWithLoad{account: &Account{Priority: 2, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
- require.False(t, sameAccountWithLoadGroup(a, b))
- })
- t.Run("different load rate", func(t *testing.T) {
- a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
- b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 20}}
- require.False(t, sameAccountWithLoadGroup(a, b))
- })
- t.Run("different last used at", func(t *testing.T) {
- later := now.Add(1 * time.Second)
- a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &now}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
- b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: &later}, loadInfo: &AccountLoadInfo{LoadRate: 10}}
- require.False(t, sameAccountWithLoadGroup(a, b))
- })
- t.Run("both nil LastUsedAt", func(t *testing.T) {
- a := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}}
- b := accountWithLoad{account: &Account{Priority: 1, LastUsedAt: nil}, loadInfo: &AccountLoadInfo{LoadRate: 0}}
- require.True(t, sameAccountWithLoadGroup(a, b))
- })
- }
- // ============ sameAccountGroup 测试 ============
- func TestSameAccountGroup(t *testing.T) {
- now := time.Now()
- t.Run("same group", func(t *testing.T) {
- a := &Account{Priority: 1, LastUsedAt: nil}
- b := &Account{Priority: 1, LastUsedAt: nil}
- require.True(t, sameAccountGroup(a, b))
- })
- t.Run("different priority", func(t *testing.T) {
- a := &Account{Priority: 1, LastUsedAt: nil}
- b := &Account{Priority: 2, LastUsedAt: nil}
- require.False(t, sameAccountGroup(a, b))
- })
- t.Run("different LastUsedAt", func(t *testing.T) {
- later := now.Add(1 * time.Second)
- a := &Account{Priority: 1, LastUsedAt: &now}
- b := &Account{Priority: 1, LastUsedAt: &later}
- require.False(t, sameAccountGroup(a, b))
- })
- }
- // ============ sortAccountsByPriorityAndLastUsed 集成随机化测试 ============
- func TestSortAccountsByPriorityAndLastUsed_WithShuffle(t *testing.T) {
- t.Run("same priority and nil LastUsedAt are shuffled", func(t *testing.T) {
- accounts := []*Account{
- {ID: 1, Priority: 1, LastUsedAt: nil},
- {ID: 2, Priority: 1, LastUsedAt: nil},
- {ID: 3, Priority: 1, LastUsedAt: nil},
- }
- seen := map[int64]bool{}
- for i := 0; i < 100; i++ {
- cpy := make([]*Account, len(accounts))
- copy(cpy, accounts)
- sortAccountsByPriorityAndLastUsed(cpy, false)
- seen[cpy[0].ID] = true
- }
- require.GreaterOrEqual(t, len(seen), 2, "identical sort keys should produce different orderings after shuffle")
- })
- t.Run("different priorities still sorted correctly", func(t *testing.T) {
- now := time.Now()
- accounts := []*Account{
- {ID: 3, Priority: 3, LastUsedAt: &now},
- {ID: 1, Priority: 1, LastUsedAt: &now},
- {ID: 2, Priority: 2, LastUsedAt: &now},
- }
- sortAccountsByPriorityAndLastUsed(accounts, false)
- require.Equal(t, int64(1), accounts[0].ID)
- require.Equal(t, int64(2), accounts[1].ID)
- require.Equal(t, int64(3), accounts[2].ID)
- })
- }
|