test(flake): 4 条负载敏感用例确定性化(只改测试,生产代码零改动)
- #1 TestSpawnTimeoutFromToolDefault:墙钟竞态(Spawn→Wait 的调度抖动 vs 120ms 工具时限)⇒ 用「托底子代理占住唯一槽位 + gate 放行」把 running 变成由门控保证;并新增 DeadlineExceeded 断言。 - #2 TestParallelLimit:真实成因**不是**「同进程迭代状态泄漏」(任务书推测错了)—— 是 transitionRunning 与 runner 内 active++ 之间的**抢占窗口**(复现:-count=200 六次红、三进程并行 1/9)⇒ 改等 runner 内部 activeCount()>=2 这一**稳定态**,并新增「整轮排空峰值==2」。 - #3 TestBackthink_List_NewestFirst:真实成因**不是**时间戳并列(SQL 是 ORDER BY id DESC)—— 是**后台心跳 writer 在两次 List 之间插入新行**(决定性证据:心跳周期缩到 1ms ⇒ 21/200 红,且第二次读到的 id 更大)⇒ 不再 Start() 心跳,改同步 think 写 3 条。 - #4 TestClient_OnConnectErrorTriggersRedial:固定 60ms 睡眠 vs 每周期耗时不定的墙钟竞态(改动前基线 5/9 红)⇒ 改为等第 2 次拨号事件;顺带改正与内容不符的旧注释。 - 证据:逐条 -count=20 绿;整树连跑 3 次 59 ok/0 FAIL;**三进程并行整树 9/9 绿(改动前基线 5/9 红)**;GOMAXPROCS=1 ×4 进程加压;-count=200/500/500/300;-race 三包绿。 - 主会话独立复验:隔离 worktree build/vet 绿、四条 -count=20 全绿、两进程并行整树双 59 ok/0 FAIL;**变异独立复现**(TestParallelLimit 的 MaxParallel 2→3 ⇒ max concurrent runs = 3, want 2 红)。 - 顺带发现第 5 条既有 flake:internal/runtime/exec/TestTerminal_Timeout_Killed(改动前基线 2/9 红);以及 audit/00-总览.md:321/372 对 #2/#3 成因的记载有误(DevDocs 由主会话更正)。
This commit is contained in:
1 parent
b19d798a2d
commit
b1f9b11d6e
4 files changed
+84
-19
No files matched your search
@@ -266,18 +266,23 @@ func TestBackthink_SystemEventHighValueThinks(t *testing.T) {
|
||||
eventually(t, 2*time.Second, func() bool { return thinker.count() >= before+1 })
|
||||
}
|
||||
|
||||
// TestBackthink_List_NewestFirst 断言 List 严格由新到旧,且单行列表的头部与完整
|
||||
// 列表的头部是同一条记录。记录全部由测试同步写入,因此两次读取之间不存在并发
|
||||
// 写入者(见下方注释)。
|
||||
func TestBackthink_List_NewestFirst(t *testing.T) {
|
||||
t.Parallel()
|
||||
thinker := &mockThinker{text: "nothing to report"}
|
||||
b, _ := newBackthink(t, Config{Interval: 15 * time.Millisecond}, Deps{Thinker: thinker})
|
||||
b, _ := newBackthink(t, Config{}, Deps{Thinker: thinker})
|
||||
|
||||
b.Start(context.Background())
|
||||
defer func() { _ = b.Stop(context.Background()) }()
|
||||
// 三条可区分的记录,写入顺序即 sources 的顺序。
|
||||
sources := []string{"first", "second", "third"}
|
||||
// 记录由测试同步写入(直接调用 think),刻意不启动心跳:后台 ticker 会在
|
||||
// 两次 List 之间插入一条新记录,使「单行列表的头部 == 完整列表的头部」这个
|
||||
// 断言变成跟调度赛跑。此前偶红时读到的正是「刚插入的更新的 id」。
|
||||
for _, src := range sources {
|
||||
b.think(context.Background(), Trigger{Source: src, Summary: src, Import: ImportanceLow, Kind: TriggerHeartbeat})
|
||||
}
|
||||
|
||||
eventually(t, 2*time.Second, func() bool {
|
||||
recs, _ := b.List(context.Background(), 10)
|
||||
return len(recs) >= 2
|
||||
})
|
||||
top, err := b.List(context.Background(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
@@ -294,11 +299,21 @@ func TestBackthink_List_NewestFirst(t *testing.T) {
|
||||
if top[0].ID != all[0].ID {
|
||||
t.Fatalf("List(1)[0].ID = %d, want %d (== newest)", top[0].ID, all[0].ID)
|
||||
}
|
||||
if len(all) != len(sources) {
|
||||
t.Fatalf("len(all) = %d, want %d (no writer ran between the two reads)", len(all), len(sources))
|
||||
}
|
||||
for i := 1; i < len(all); i++ {
|
||||
if all[i].ID >= all[i-1].ID {
|
||||
t.Fatalf("records not newest-first: %d before %d", all[i-1].ID, all[i].ID)
|
||||
}
|
||||
}
|
||||
// 最新在前不仅体现在 id 上,也体现在内容上:列表是写入顺序的逆序。
|
||||
for i := range sources {
|
||||
want := sources[len(sources)-1-i]
|
||||
if all[i].Trigger != want {
|
||||
t.Fatalf("all[%d].Trigger = %q, want %q (newest first)", i, all[i].Trigger, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackthink_Stop_NoLeak(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user