diff --git a/cloud/tests/test_audit_loop_coord_fix.py b/cloud/tests/test_audit_loop_coord_fix.py index 20fb5a5..b67dffb 100644 --- a/cloud/tests/test_audit_loop_coord_fix.py +++ b/cloud/tests/test_audit_loop_coord_fix.py @@ -180,6 +180,55 @@ class CoordFixTestCase(unittest.TestCase): [{"name": "a", "id": "C1"}]) self.assertEqual(out["decision"], "escalate") + def test_b1_m1_id_tiebreak_with_id_column(self): + # M1:生产 audit_log 有 id 列时,_coord_gate_rejected_ + # child_ids 走 ORDER BY created_at DESC, id DESC 的 + # 第一条查询;B1 旧测试用的无 id 列建表只覆盖了回落 + # 路径,tie-break 语义(同 created_at 时 id 倒序) + # 没有任何断言。 + # + # 诚实的设计说明:该函数返回的是子任务 id 集合而非 + # 单行,所以“同一子任务多行、同 created_at”时无论 + # 取哪行结果都一样——那样的测试是假测试,抓不到取 + # 错行。tie-break 唯一可观测的后果在 5000 行封顶 + # 处:同 created_at 时 id 倒序保留最新 5000 行、丢 + # 最旧一行;若改成 id 正序或落到无 id 排序的回落 + # 路径,则正好相反(丢最新一行、留最旧一行,见 + # /tmp 实测:三条查询在 5001 行同 created_at 下 + # 的结果互为反面)。因此用 5001 行同 created_at + # 压封顶边界断言:最新 id 的子任务必须在集合里、 + # 最旧 id 的子任务必须不在,且集合恰好 5000 条。 + conn = sqlite3.connect(self.db_path) + try: + conn.execute( + "CREATE TABLE audit_log(id INTEGER PRIMARY KEY" + " AUTOINCREMENT, user_id TEXT, project_id TEXT," + " action TEXT, detail TEXT," + " created_at DOUBLE PRECISION)") + now = time.time() + conn.executemany( + "INSERT INTO audit_log(user_id, project_id," + " action, detail, created_at)" + " VALUES('system', 'p1'," + " 'worker_capability_gate_rejected', ?, ?)", + [("C%04d: gate rejection" % i, now) + for i in range(5001)]) + conn.commit() + finally: + conn.close() + child_ids = ["C%04d" % i for i in range(5001)] + found = self.worker._coord_gate_rejected_child_ids( + "p1", child_ids, + attempt_started_at={cid: now - 100 + for cid in child_ids}) + self.assertEqual(len(found), 5000) + # 最新 id 的行(C5000)必须被 id 倒序保留下来。 + self.assertIn("C5000", found) + # 最旧 id 的行(C0000)必须被 5000 行封顶挤掉; + # 若排序改成 id 正序或落到回落路径,这里会反过来 + # (C5000 被挤掉、C0000 留下),断言即失败。 + self.assertNotIn("C0000", found) + # ---- I1 ---- def test_i1_merged_counts_as_completed_everywhere(self): overall, failed, _tol = evaluate_outcome(