fix(unit): 修复 unit tag 测试编译与账号选择用例

This commit is contained in:
yangjianbo
2026-02-09 21:35:41 +08:00
parent d367d1cde6
commit 2bfb16291f
5 changed files with 130 additions and 105 deletions

View File

@@ -74,11 +74,24 @@ func TestSortAccountsByPriorityAndLastUsed_StableSort(t *testing.T) {
{ID: 2, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
{ID: 3, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
}
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)
// sortAccountsByPriorityAndLastUsed 内部会在同组(Priority+LastUsedAt)内做随机打散,
// 因此这里不再断言“稳定排序”。我们只验证:
// 1) 元素集合不变2) 多次运行能产生不同的顺序。
seenFirst := map[int64]bool{}
for i := 0; i < 100; i++ {
cpy := make([]*Account, len(accounts))
copy(cpy, accounts)
sortAccountsByPriorityAndLastUsed(cpy, false)
seenFirst[cpy[0].ID] = true
ids := map[int64]bool{}
for _, a := range cpy {
ids[a.ID] = true
}
require.True(t, ids[1] && ids[2] && ids[3])
}
require.GreaterOrEqual(t, len(seenFirst), 2, "同组账号应能被随机打散")
}
func TestSortAccountsByPriorityAndLastUsed_MixedPriorityAndTime(t *testing.T) {
@@ -98,101 +111,96 @@ func TestSortAccountsByPriorityAndLastUsed_MixedPriorityAndTime(t *testing.T) {
require.Equal(t, int64(4), accounts[3].ID, "优先级2 + 有时间")
}
// --- selectByCallCount ---
// --- filterByMinPriority ---
func TestSelectByCallCount_Empty(t *testing.T) {
result := selectByCallCount(nil, nil, false)
func TestFilterByMinPriority_Empty(t *testing.T) {
result := filterByMinPriority(nil)
require.Nil(t, result)
}
func TestSelectByCallCount_Single(t *testing.T) {
func TestFilterByMinPriority_SelectsMinPriority(t *testing.T) {
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(1, 5, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 20, nil, AccountTypeAPIKey),
makeAccWithLoad(4, 2, 10, nil, AccountTypeAPIKey),
}
result := selectByCallCount(accounts, map[int64]*ModelLoadInfo{1: {CallCount: 10}}, false)
result := filterByMinPriority(accounts)
require.Len(t, result, 2)
require.Equal(t, int64(2), result[0].account.ID)
require.Equal(t, int64(3), result[1].account.ID)
}
// --- filterByMinLoadRate ---
func TestFilterByMinLoadRate_Empty(t *testing.T) {
result := filterByMinLoadRate(nil)
require.Nil(t, result)
}
func TestFilterByMinLoadRate_SelectsMinLoadRate(t *testing.T) {
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 30, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(4, 1, 20, nil, AccountTypeAPIKey),
}
result := filterByMinLoadRate(accounts)
require.Len(t, result, 2)
require.Equal(t, int64(2), result[0].account.ID)
require.Equal(t, int64(3), result[1].account.ID)
}
// --- selectByLRU ---
func TestSelectByLRU_Empty(t *testing.T) {
result := selectByLRU(nil, false)
require.Nil(t, result)
}
func TestSelectByLRU_Single(t *testing.T) {
accounts := []accountWithLoad{makeAccWithLoad(1, 1, 10, nil, AccountTypeAPIKey)}
result := selectByLRU(accounts, false)
require.NotNil(t, result)
require.Equal(t, int64(1), result.account.ID)
}
func TestSelectByCallCount_NilModelLoadFallsBackToLRU(t *testing.T) {
func TestSelectByLRU_NilLastUsedAtWins(t *testing.T) {
now := time.Now()
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 50, testTimePtr(now), AccountTypeAPIKey),
makeAccWithLoad(2, 1, 50, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
}
result := selectByCallCount(accounts, nil, false)
result := selectByLRU(accounts, false)
require.NotNil(t, result)
require.Equal(t, int64(2), result.account.ID, "nil modelLoadMap 应回退到 LRU 选择")
require.Equal(t, int64(2), result.account.ID)
}
func TestSelectByCallCount_SelectsMinCallCount(t *testing.T) {
func TestSelectByLRU_EarliestTimeWins(t *testing.T) {
now := time.Now()
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 50, nil, AccountTypeAPIKey),
}
modelLoad := map[int64]*ModelLoadInfo{
1: {CallCount: 100},
2: {CallCount: 5},
3: {CallCount: 50},
}
// 运行多次确认总是选调用次数最少的
for i := 0; i < 10; i++ {
result := selectByCallCount(accounts, modelLoad, false)
require.NotNil(t, result)
require.Equal(t, int64(2), result.account.ID, "应选择调用次数最少的账号")
makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(-2*time.Hour)), AccountTypeAPIKey),
}
result := selectByLRU(accounts, false)
require.NotNil(t, result)
require.Equal(t, int64(3), result.account.ID)
}
func TestSelectByCallCount_NewAccountUsesAverage(t *testing.T) {
func TestSelectByLRU_TiePreferOAuth(t *testing.T) {
now := time.Now()
// 账号 1/2 LastUsedAt 相同,且同为最小值。
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, testTimePtr(now), AccountTypeOAuth),
makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(1*time.Hour)), AccountTypeAPIKey),
}
// 账号1和2有调用记录账号3是新账号CallCount=0
// 平均调用次数 = (100 + 200) / 2 = 150
// 新账号用平均值 150比账号1(100)多所以应选账号1
modelLoad := map[int64]*ModelLoadInfo{
1: {CallCount: 100},
2: {CallCount: 200},
// 3 没有记录
}
for i := 0; i < 10; i++ {
result := selectByCallCount(accounts, modelLoad, false)
for i := 0; i < 50; i++ {
result := selectByLRU(accounts, true)
require.NotNil(t, result)
require.Equal(t, int64(1), result.account.ID, "新账号虚拟调用次数(150)高于账号1(100)应选账号1")
}
}
func TestSelectByCallCount_AllNewAccountsFallToAvgZero(t *testing.T) {
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 50, nil, AccountTypeAPIKey),
}
// 所有账号都是新的avgCallCount = 0所有人 effectiveCallCount 都是 0
modelLoad := map[int64]*ModelLoadInfo{}
validIDs := map[int64]bool{1: true, 2: true}
for i := 0; i < 10; i++ {
result := selectByCallCount(accounts, modelLoad, false)
require.NotNil(t, result)
require.True(t, validIDs[result.account.ID], "所有新账号应随机选择")
}
}
func TestSelectByCallCount_PreferOAuth(t *testing.T) {
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 50, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 50, nil, AccountTypeOAuth),
}
// 两个账号调用次数相同
modelLoad := map[int64]*ModelLoadInfo{
1: {CallCount: 10},
2: {CallCount: 10},
}
for i := 0; i < 10; i++ {
result := selectByCallCount(accounts, modelLoad, true)
require.NotNil(t, result)
require.Equal(t, int64(2), result.account.ID, "调用次数相同时应优先选择 OAuth 账号")
require.Equal(t, AccountTypeOAuth, result.account.Type)
require.Equal(t, int64(2), result.account.ID)
}
}

View File

@@ -1937,7 +1937,7 @@ func sortAccountsByPriorityAndLastUsed(accounts []*Account, preferOAuth bool) {
return a.LastUsedAt.Before(*b.LastUsedAt)
}
})
shuffleWithinPriorityAndLastUsed(accounts)
shuffleWithinPriorityAndLastUsed(accounts, preferOAuth)
}
// shuffleWithinSortGroups 对排序后的 accountWithLoad 切片,按 (Priority, LoadRate, LastUsedAt) 分组后组内随机打乱。
@@ -1973,7 +1973,12 @@ func sameAccountWithLoadGroup(a, b accountWithLoad) bool {
}
// shuffleWithinPriorityAndLastUsed 对排序后的 []*Account 切片,按 (Priority, LastUsedAt) 分组后组内随机打乱。
func shuffleWithinPriorityAndLastUsed(accounts []*Account) {
//
// 注意:当 preferOAuth=true 时,需要保证 OAuth 账号在同组内仍然优先,否则会把排序时的偏好打散掉。
// 因此这里采用“组内分区 + 分区内 shuffle”的方式
// - 先把同组账号按 (OAuth / 非 OAuth) 拆成两段,保持 OAuth 段在前;
// - 再分别在各段内随机打散,避免热点。
func shuffleWithinPriorityAndLastUsed(accounts []*Account, preferOAuth bool) {
if len(accounts) <= 1 {
return
}
@@ -1984,9 +1989,29 @@ func shuffleWithinPriorityAndLastUsed(accounts []*Account) {
j++
}
if j-i > 1 {
mathrand.Shuffle(j-i, func(a, b int) {
accounts[i+a], accounts[i+b] = accounts[i+b], accounts[i+a]
})
if preferOAuth {
oauth := make([]*Account, 0, j-i)
others := make([]*Account, 0, j-i)
for _, acc := range accounts[i:j] {
if acc.Type == AccountTypeOAuth {
oauth = append(oauth, acc)
} else {
others = append(others, acc)
}
}
if len(oauth) > 1 {
mathrand.Shuffle(len(oauth), func(a, b int) { oauth[a], oauth[b] = oauth[b], oauth[a] })
}
if len(others) > 1 {
mathrand.Shuffle(len(others), func(a, b int) { others[a], others[b] = others[b], others[a] })
}
copy(accounts[i:], oauth)
copy(accounts[i+len(oauth):], others)
} else {
mathrand.Shuffle(j-i, func(a, b int) {
accounts[i+a], accounts[i+b] = accounts[i+b], accounts[i+a]
})
}
}
i = j
}

View File

@@ -125,13 +125,13 @@ func TestShuffleWithinSortGroups_MixedGroups(t *testing.T) {
// ============ shuffleWithinPriorityAndLastUsed 测试 ============
func TestShuffleWithinPriorityAndLastUsed_Empty(t *testing.T) {
shuffleWithinPriorityAndLastUsed(nil)
shuffleWithinPriorityAndLastUsed([]*Account{})
shuffleWithinPriorityAndLastUsed(nil, false)
shuffleWithinPriorityAndLastUsed([]*Account{}, false)
}
func TestShuffleWithinPriorityAndLastUsed_SingleElement(t *testing.T) {
accounts := []*Account{{ID: 1, Priority: 1}}
shuffleWithinPriorityAndLastUsed(accounts)
shuffleWithinPriorityAndLastUsed(accounts, false)
require.Equal(t, int64(1), accounts[0].ID)
}
@@ -146,7 +146,7 @@ func TestShuffleWithinPriorityAndLastUsed_SameGroup_Shuffled(t *testing.T) {
for i := 0; i < 100; i++ {
cpy := make([]*Account, len(accounts))
copy(cpy, accounts)
shuffleWithinPriorityAndLastUsed(cpy)
shuffleWithinPriorityAndLastUsed(cpy, false)
seen[cpy[0].ID] = true
}
require.GreaterOrEqual(t, len(seen), 2, "same group should be shuffled")
@@ -162,7 +162,7 @@ func TestShuffleWithinPriorityAndLastUsed_DifferentPriority_OrderPreserved(t *te
for i := 0; i < 20; i++ {
cpy := make([]*Account, len(accounts))
copy(cpy, accounts)
shuffleWithinPriorityAndLastUsed(cpy)
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)
@@ -182,7 +182,7 @@ func TestShuffleWithinPriorityAndLastUsed_DifferentLastUsedAt_OrderPreserved(t *
for i := 0; i < 20; i++ {
cpy := make([]*Account, len(accounts))
copy(cpy, accounts)
shuffleWithinPriorityAndLastUsed(cpy)
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)