mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-04 00:03:59 +00:00
test(seq): 三个压力测试 —— 超长序列 / 100 工具并行 / 串行降级
与单元判据的分工:单元判据钉住**语义**(一条路径对不对);压力测试钉住
**规模下的不变量**。沿用仓内既有范式(media/soak_test.go):
testing.Short() 跳过 + 独立 -run 跑。
① 超长序列
· 解析 10 / 100 / 1000 组(250KB 文本):6.8ms,无硬上限误报
· 执行 200 组 × 5 工具 = 1000 次调用:2.0ms
断言:每工具恰好被调 nGroups 次(无遗漏/重复)、结果含**最后一组**
—— 组间串行在规模下仍成立
② 100 工具组内并行
· 100 工具全声明并发安全 ⇒ 11ms,完成顺序**确实被打乱**(判据会校验
这一点,否则它测不到并发)
· 断言每个槽拿到**自己**的结果(并发下若按完成顺序合并就会错位)
③ 串行降级
· 50 个工具里**一个**未声明并发安全 ⇒ 整批退回串行,
完成顺序严格等于声明序(106ms vs 并发的 11ms,降级确实生效)
★ 压力测试第一次跑就抓到一个**真实分层缺陷**:
「含非并发安全工具则整批串行」这条规则**只在上层 runGroup 实现**,
而引擎层 execGroup 只信 g.Parallel 字段 ⇒ 任何人直接调 execGroup
都会拿到不受约束的并发。
已修:降级判据下沉到引擎层,新增 batchCanRun(g, runner),
toolRunner 增加 parallelSafe 方法(生产路径行为不变,只是把判据
放到了它本该在的层)。
过程中压测自身也暴露了两个测试缺陷(都修了):
· fixture 让 100 个工具写同一个标量槽 o,被静态校验正确拦下
("组内并行下同名写入是数据竞争")—— 压测不该去撞这条规则;
· ★ e2eRunner.called 是无锁 append,100 工具并发时 -race 报出**真竞态**
(不是误报)—— 加锁 + 提供 calledSnapshot 供断言。
另:建序列与跑序列原本用了**不同 plugin 实例**(序列存在实例的 store 里,
换实例就读不到自己刚建的),已改为同一实例。
回归:go test -race ./internal/plugins/seq 全绿;go test ./internal/... 全绿。
This commit is contained in:
@ -4,7 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 阶段 P4 端到端:真实 Plugin + 真实工具执行,串通
|
||||
@ -21,14 +23,24 @@ import (
|
||||
|
||||
// e2eRunner 是真实执行面:它按名字查工具声明、真的返回结果。
|
||||
type e2eRunner struct {
|
||||
defs map[string]toolDefInfo
|
||||
defs map[string]toolDefInfo
|
||||
// mu 保护 called:组内并发时多个 goroutine 同时 append,
|
||||
// 无锁会**真的**触发 -race(压测首次跑就报出来了)。
|
||||
mu sync.Mutex
|
||||
called []string
|
||||
// results 覆盖默认返回
|
||||
results map[string]string
|
||||
// delay 按工具名制造延迟(毫秒),用于**打乱完成顺序**——
|
||||
// 并发下若按完成顺序合并,槽内容就会错位;压力测试需要能造出这种乱序。
|
||||
delay map[string]int
|
||||
}
|
||||
|
||||
func newE2ERunner(names ...string) *e2eRunner {
|
||||
r := &e2eRunner{defs: map[string]toolDefInfo{}, results: map[string]string{}}
|
||||
r := &e2eRunner{
|
||||
defs: map[string]toolDefInfo{},
|
||||
results: map[string]string{},
|
||||
delay: map[string]int{},
|
||||
}
|
||||
for _, n := range names {
|
||||
// 默认**不**声明并发安全 ⇒ 序列会整批退回串行(保守默认)
|
||||
r.defs[n] = toolDefInfo{Name: n, Description: n}
|
||||
@ -45,7 +57,12 @@ func (r *e2eRunner) markParallel(names ...string) {
|
||||
}
|
||||
|
||||
func (r *e2eRunner) call(name string, args map[string]interface{}) (string, error) {
|
||||
if d := r.delay[name]; d > 0 {
|
||||
time.Sleep(time.Duration(d) * time.Millisecond)
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.called = append(r.called, name)
|
||||
r.mu.Unlock()
|
||||
if s, ok := r.results[name]; ok {
|
||||
return s, nil
|
||||
}
|
||||
@ -337,3 +354,12 @@ func TestSeqRunDoesNotSilentlyTruncateSlot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calledSnapshot 返回调用记录的快照(加锁)。
|
||||
func (r *e2eRunner) calledSnapshot() []string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
out := make([]string, len(r.called))
|
||||
copy(out, r.called)
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user