onesvm-browser-server/server/internal/proxymanager/selector_test.go
chii eb972dfa93 feat: 落地 browser-server 控制面并打通 mgr1 海外订阅
单二进制三角色 + Dock 适配器 + Swarm stack 达到可部署态;mgr1 实测订阅经 central-proxy bootstrap,探活 alive=41/52。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 15:05:12 +08:00

134 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// selector_test.go:P2C + sticky LRU 单测(无外网,固定随机源)。
package proxymanager
import (
"testing"
"time"
)
// fixedRnd 固定序列随机源(可复现)。
func fixedRnd(seq []int) func(int) int {
i := 0
return func(n int) int {
v := seq[i%len(seq)] % n
i++
return v
}
}
func TestPickLowestDelay(t *testing.T) {
cands := []string{"A", "B", "C"}
delays := map[string]int{"A": 200, "B": 100, "C": 300}
picked := Pick(cands, func(n string) int { return delays[n] }, fixedRnd([]int{0, 1}))
if picked != "B" {
t.Errorf("P2C picked = %s, want B(最低延迟)", picked)
}
}
func TestPickUnknownDelayNotPreferred(t *testing.T) {
// 未探测节点(<=0)视为最差:A 已探测 500ms,B 未探测 → 应选 A
cands := []string{"A", "B"}
picked := Pick(cands, func(n string) int {
if n == "A" {
return 500
}
return 0
}, fixedRnd([]int{0, 1}))
if picked != "A" {
t.Errorf("picked = %s, want A(未探测节点不应霸占选择)", picked)
}
}
func TestPickSingleCandidate(t *testing.T) {
if got := Pick([]string{"only"}, func(string) int { return 1 }, fixedRnd([]int{0})); got != "only" {
t.Errorf("单候选 picked = %s", got)
}
if got := Pick(nil, func(string) int { return 0 }, fixedRnd([]int{0})); got != "" {
t.Errorf("空候选 picked = %q, want 空串", got)
}
}
func TestPickNeverSameIndexTwice(t *testing.T) {
// rnd 恒返同值时,两样本必须不同(j=(i+1)%n 兜底)
cands := []string{"A", "B", "C"}
delays := map[string]int{"A": 100, "B": 200, "C": 300}
picked := Pick(cands, func(n string) int { return delays[n] }, func(int) int { return 0 })
if picked != "A" {
t.Errorf("picked = %s, want A(0 与 1 比较取低)", picked)
}
}
func TestStickyTTLExpiry(t *testing.T) {
s := NewStickyTable(time.Minute, 10)
s.Set("k", "node1")
if n, ok := s.Get("k"); !ok || n != "node1" {
t.Fatalf("Get = %q,%v", n, ok)
}
// TTL 过期(直接改 ExpireAt 模拟时钟前进)
s.mu.Lock()
e := s.m["k"]
e.ExpireAt = time.Now().Add(-time.Second)
s.m["k"] = e
s.mu.Unlock()
if _, ok := s.Get("k"); ok {
t.Fatal("过期条目应失效")
}
}
func TestStickyLRUEviction(t *testing.T) {
s := NewStickyTable(time.Hour, 3)
s.Set("a", "1")
s.Set("b", "2")
s.Set("c", "3")
s.Get("a") // touch a → b 变最旧
s.Set("d", "4")
if _, ok := s.Get("b"); ok {
t.Fatal("LRU 应淘汰 b(容量 3)")
}
if _, ok := s.Get("a"); !ok {
t.Fatal("a 被访问过不应被淘汰")
}
if s.Len() != 3 {
t.Errorf("Len = %d, want 3", s.Len())
}
}
func TestStickyReset(t *testing.T) {
s := NewStickyTable(time.Hour, 10)
s.Set("k", "v")
s.Reset()
if s.Len() != 0 {
t.Fatal("Reset 后应为空")
}
}
func TestStickyKey(t *testing.T) {
if StickyKey("sess1", "d.com") != "s:sess1" {
t.Error("session 优先")
}
if StickyKey("", "d.com") != "d:d.com" {
t.Error("无 session 用 domain")
}
}
func TestStickyDefaultCapacity(t *testing.T) {
s := NewStickyTable(0, 0) // 走默认值:TTL 10min / 容量 1000
for i := 0; i < 1200; i++ {
s.Set(string(rune('a'+i%26))+itoa(i), "n")
}
if s.Len() > 1000 {
t.Errorf("容量上限失效: Len=%d", s.Len())
}
}
func itoa(n int) string {
if n == 0 {
return "0"
}
var b []byte
for n > 0 {
b = append([]byte{byte('0' + n%10)}, b...)
n /= 10
}
return string(b)
}