"""视频级切分科研旋钮单元测试:诊断指纹 + val_wrong_min 功效护栏。 覆盖: - diag_fingerprint 对 (prompt 版本 / 模型 / 代码版本) 三元组确定且敏感; - split_by_video_assignment 的 val_wrong_min 门控 fail loud(验证信号不足即报错); - val_wrong_min 默认 0 时行为与 Task 11 现有调用完全一致(不回归)。 注:结果驱动视频级切分不复用 PoolConfig——它有独立的 VideoSplitConfig / SplitBuildConfig / SelectConfig,故 PoolConfig 不承载视频级切分旋钮(无死配置面)。 """ from __future__ import annotations import pytest from app.harness.pools import InsufficientValSignal, split_by_video_assignment from app.harness.split_selection import diag_fingerprint from core.types import GeneratedQuestion def _q(qid: str, vid: str, tt: str = "Counting Problem") -> GeneratedQuestion: """构造最小可用题目(补齐 GeneratedQuestion 的必填 source_nodes/difficulty)。""" return GeneratedQuestion( question_id=qid, video_id=vid, task_type=tt, question="", options=("A", "B", "C", "D"), answer="A", source_nodes=(), difficulty="easy", ) def test_diag_fingerprint_deterministic_and_sensitive(): """诊断指纹对相同三元组稳定,对任一分量变化敏感。""" a = diag_fingerprint("p1", "deepseek-v4", "abc123") assert a == diag_fingerprint("p1", "deepseek-v4", "abc123") # 确定性 assert a != diag_fingerprint("p2", "deepseek-v4", "abc123") # prompt 变则变 assert a != diag_fingerprint("p1", "kimi", "abc123") # model 变则变 assert a != diag_fingerprint("p1", "deepseek-v4", "def456") # 代码版本变则变 assert len(a) == 16 # sha256 截断 16 位十六进制 def test_val_wrong_min_enforced(): """val 错题数 < val_wrong_min 时 fail loud(InsufficientValSignal),不静默兜底。""" qs = [_q("v1-1", "v1"), _q("v1-2", "v1"), _q("v1-3", "v1")] correctness = {q.question_id: True for q in qs} # 全对 → val 无错题 with pytest.raises(InsufficientValSignal): # val 错题 < val_wrong_min split_by_video_assignment( qs, {"v1": "trainval"}, correctness=correctness, val_ratio=0.5, seed=0, val_wrong_min=5, ) def test_val_wrong_min_default_zero_no_regression(): """val_wrong_min 默认 0 时不检查错题数,保持 Task 11 现有调用契约不破。""" qs = [_q("v1-1", "v1"), _q("v2-1", "v2")] assignment = {"v1": "trainval", "v2": "trainval"} correctness = {"v1-1": True, "v2-1": True} # 全对但默认不触发护栏 pools = split_by_video_assignment( qs, assignment, correctness=correctness, val_ratio=1.0, seed=0 ) assert len(pools.validation) == 2 # 未抛异常,正常返回