From 52c3cc21d52a218b73724bba0b66f7cc22566693 Mon Sep 17 00:00:00 2001 From: "agent.develop" Date: Tue, 22 Sep 2026 19:40:19 +0800 Subject: [PATCH] =?UTF-8?q?approve:=20[M9-=E6=94=B6=E5=8F=A3B]=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=E5=BF=97=E5=BD=92=E6=A1=A3=20+=20=E4=BA=A4?= =?UTF-8?q?=E4=BB=98=E4=B8=80=E8=87=B4=E6=80=A7=E6=A0=B8=E9=AA=8C=EF=BC=88?= =?UTF-8?q?=E4=BB=A5=E4=BA=8B=E5=AE=9E=E5=BF=AB=E7=85=A7=E4=B8=BA=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E6=95=B0=E6=8D=AE=E6=BA=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/m5a_live_db_harness.py | 84 ++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/tests/m5a_live_db_harness.py b/tests/m5a_live_db_harness.py index 7f82315..49aa292 100644 --- a/tests/m5a_live_db_harness.py +++ b/tests/m5a_live_db_harness.py @@ -344,11 +344,22 @@ def phase_idempotent(collector, box): .format(r.get('Seq_in_index'), r.get('Column_name'), r.get('Non_unique'), r.get('Key_name'))) - # 具名参数插入(列名与占位符同源 SEED_FIELDS,列/值不可能错位) + # 具名参数插入:占位符写成显式字面量(%(event_id)s ...),与列名一一对应。 + # 不用位置参数 %s(与列序耦合,改列即错位),不用 f-string 内嵌值(注入面)。 + # 下方同源校验保证字面量与 SEED_FIELDS 一致,任一方单独改动即拒绝写入。 + seed_sql = ( + 'INSERT INTO `pbl_runtime_event` (' + '`event_id`, `tenant_id`, `event_type`, `payload_json`, `occurred_at`, ' + '`learner_id`, `session_id`, `blueprint_id`, `artifact_id`' + ') VALUES (' + '%(event_id)s, %(tenant_id)s, %(event_type)s, %(payload_json)s, ' + '%(occurred_at)s, %(learner_id)s, %(session_id)s, %(blueprint_id)s, ' + '%(artifact_id)s)' + ) seed_cols = ', '.join('`' + f + '`' for f in SEED_FIELDS) seed_vals = ', '.join('%(' + f + ')s' for f in SEED_FIELDS) - seed_sql = ('INSERT INTO `pbl_runtime_event` (' + seed_cols + ') VALUES (' - + seed_vals + ')') + if seed_cols not in seed_sql or seed_vals not in seed_sql: + raise RuntimeError('种子 INSERT 字面量与 SEED_FIELDS 不同源,拒绝错位写入') print('事件种子 INSERT(具名参数): %s' % seed_sql) with box.conn.cursor() as cur: for ev in EVENTS_SEED: @@ -523,13 +534,17 @@ def db_kwargs(): connect_timeout=5) -def run(args): - """harness 主体(异常由 main() 统一收敛为显式 FAIL)。""" +def setup_box(): + """环境自检 + 凭据解析 + 沙箱库重建,返回 (collector, box)。 + + 前置不满足(无 pymysql / 无凭据 / 连不上库)时 raise,由 main() 外层 + try/except 统一收敛为显式 FAIL 断言,不在这里吞异常返回伪成功。 + """ try: import pymysql # noqa: F401 except Exception as exc: # noqa: BLE001 check('ENV.1 运行环境具备 pymysql', False, repr(exc)) - return 1 + raise RuntimeError('运行环境缺少 pymysql: %s' % exc) conn_kwargs = db_kwargs() print('沙箱库凭据来源: %s | 目标 = %s@%s:%s(口令不打印,日志已脱敏)' @@ -540,27 +555,33 @@ def run(args): box = Sandbox(conn_kwargs) box.reset_schema() box.open() + return collector, box + + +def collect_again(collector): + """第三次采集(供 phase_third_run 做统计对比),独立事件循环避免污染。""" + import asyncio + loop = asyncio.get_event_loop_policy().new_event_loop() try: - install_stub(collector, box) - phase_fail_closed(collector, box) - _s2, _rows = phase_idempotent(collector, box) - # 重跑一次统计对比(放在 phase 2 之后,避免影响其内部断言顺序) - import asyncio - loop = asyncio.get_event_loop_policy().new_event_loop() - try: - s2 = loop.run_until_complete( - collector.collect_evidence_from_events(tenant_id=_TENANT)) - finally: - loop.close() - phase_third_run(collector, box, s2) + return loop.run_until_complete( + collector.collect_evidence_from_events(tenant_id=_TENANT)) finally: + loop.close() + + +def teardown_box(box, keep): + """沙箱库收尾:关连接 + 按需 DROP。自身异常只打印,避免掩盖阶段断言结论。""" + if box is None: + return + try: box.close() - if not args.keep: - box.drop_schema() - print('沙箱库已清理: DROP DATABASE %s' % SANDBOX_SCHEMA) - else: - print('--keep:沙箱库 %s 保留' % SANDBOX_SCHEMA) - return 0 + except Exception as exc: # noqa: BLE001 + print('box.close() 异常(忽略,不影响断言结论): %s' % _mask(exc)) + if not keep: + box.drop_schema() + print('沙箱库已清理: DROP DATABASE %s' % SANDBOX_SCHEMA) + else: + print('--keep:沙箱库 %s 保留' % SANDBOX_SCHEMA) def main(): @@ -570,16 +591,25 @@ def main(): print('M5a live-DB harness | sandbox schema = %s | target = modules/pbl_evidence' % SANDBOX_SCHEMA) + box = None + # 外层 try/except 直接包住全部 phase 调用:任何未捕获异常(凭据缺失、连不上库、 + # 被测代码抛错)都收敛为显式 FAIL 断言 + 非 0 退出码,绝不以裸 traceback 逃逸—— + # 否则日志退化为崩溃现场(.qc_tmp),不构成通过证据。 try: - run(args) + collector, box = setup_box() + install_stub(collector, box) + phase_fail_closed(collector, box) + _s2, _rows = phase_idempotent(collector, box) + # 重跑一次统计对比(放在 phase 2 之后,避免影响其内部断言顺序) + phase_third_run(collector, box, collect_again(collector)) except SystemExit: raise except Exception as exc: # noqa: BLE001 - # 未捕获异常(凭据缺失/连不上库/被测代码抛错)统一收敛为显式 FAIL 项, - # 打印异常类型 + 脱敏消息,绝不靠 traceback 截断证据。 check('HARNESS.1 harness 无未捕获异常(凭据可用 + 沙箱库可连 + 各阶段跑完)', False, '%s: %s' % (type(exc).__name__, _mask(exc))) print('未捕获异常汇总(脱敏): %s' % _mask(traceback.format_exc())) + finally: + teardown_box(box, args.keep) if _failures: print('LIVE DB HARNESS FAILED (%d): %s' % (len(_failures), ' | '.join(_mask(f) for f in _failures)))