// Package conformance 提供 Recognizer 公开契约的行为验收套件。 // 消费方在自己的测试里用真实配置组装 Registry 与 Recognizer 后调用 Run, // 断言分支、Top、槽位,并附带确定性与并发冒烟检查。 package conformance import ( "context" "reflect" "sync" "testing" "agent-intent/intent" ) // Case 单条行为验收用例。ExpectTopID 为空断言 Top 为 nil; // ExpectSlots / ExpectMissingSlots 只对 Top 断言。 type Case struct { Name string Input string ExpectBranch intent.Branch ExpectTopID string ExpectSlots map[string]string ExpectMissingSlots []string } // Run 逐条执行用例并输出标准断言失败信息;任何用例失败使 t 失败。 func Run(t *testing.T, rec intent.Recognizer, snap *intent.Snapshot, cases []Case) { t.Helper() for _, tc := range cases { t.Run(tc.Name, func(t *testing.T) { res, err := rec.Recognize(context.Background(), tc.Input, snap) if err != nil { t.Fatalf("recognize %q: %v", tc.Input, err) } if res.Branch != tc.ExpectBranch { t.Fatalf("branch = %s, want %s", res.Branch, tc.ExpectBranch) } switch { case tc.ExpectTopID == "" && res.Top != nil: t.Fatalf("expected no top, got %+v", res.Top) case tc.ExpectTopID != "" && res.Top == nil: t.Fatalf("expected top %s, got nil", tc.ExpectTopID) case tc.ExpectTopID != "" && res.Top.ID != tc.ExpectTopID: t.Fatalf("top = %s, want %s", res.Top.ID, tc.ExpectTopID) } if res.Top != nil { for k, v := range tc.ExpectSlots { if res.Top.Slots[k] != v { t.Fatalf("slot %s = %q, want %q", k, res.Top.Slots[k], v) } } } if len(tc.ExpectMissingSlots) > 0 && (res.Top == nil || !reflect.DeepEqual(res.Top.MissingRequiredSlots, tc.ExpectMissingSlots)) { t.Fatalf("missing slots = %+v, want %v", res.Top, tc.ExpectMissingSlots) } // 确定性:同输入 + 同快照 → 同结果。 again, err := rec.Recognize(context.Background(), tc.Input, snap) if err != nil { t.Fatalf("second recognize: %v", err) } if !reflect.DeepEqual(res, again) { t.Fatalf("nondeterministic result for %q", tc.Input) } }) } // 并发冒烟:同一快照并行识别互不影响(配合 -race)。 t.Run("parallel-smoke", func(t *testing.T) { var wg sync.WaitGroup for _, tc := range cases { wg.Add(1) go func(input string) { defer wg.Done() if _, err := rec.Recognize(context.Background(), input, snap); err != nil { t.Errorf("parallel recognize %q: %v", input, err) } }(tc.Input) } wg.Wait() }) }