chore: snapshot in-progress question-gen work before preflight fixes
This commit is contained in:
@@ -9,7 +9,6 @@ import pytest
|
||||
|
||||
from app.harness.store import (
|
||||
_parse_version,
|
||||
_write_meta,
|
||||
advance_version,
|
||||
extract_run_db,
|
||||
init_seed,
|
||||
@@ -21,7 +20,6 @@ from app.harness.store import (
|
||||
read_seed,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _parse_version
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -52,7 +50,7 @@ class TestParseVersion:
|
||||
class TestListVersions:
|
||||
"""list_versions 按数字排序,v10 排在 v2 后。"""
|
||||
|
||||
def test_list_versions_numeric_sort(self, tmp_path: "Path") -> None:
|
||||
def test_list_versions_numeric_sort(self, tmp_path: Path) -> None:
|
||||
"""v10 必须排在 v2 后面(非字典序)。"""
|
||||
store = tmp_path / "store"
|
||||
resource = store / "skills"
|
||||
@@ -62,11 +60,11 @@ class TestListVersions:
|
||||
result = list_versions(store, "skills")
|
||||
assert result == ["v1", "v2", "v3", "v10", "v20"]
|
||||
|
||||
def test_list_versions_empty(self, tmp_path: "Path") -> None:
|
||||
def test_list_versions_empty(self, tmp_path: Path) -> None:
|
||||
store = tmp_path / "store"
|
||||
assert list_versions(store, "skills") == []
|
||||
|
||||
def test_list_versions_ignores_non_version_dirs(self, tmp_path: "Path") -> None:
|
||||
def test_list_versions_ignores_non_version_dirs(self, tmp_path: Path) -> None:
|
||||
"""非 v\\d+ 格式的目录被忽略。"""
|
||||
store = tmp_path / "store"
|
||||
resource = store / "skills"
|
||||
@@ -85,11 +83,11 @@ class TestListVersions:
|
||||
class TestNextVersion:
|
||||
"""next_version 返回下一个可用版本号。"""
|
||||
|
||||
def test_next_version_empty(self, tmp_path: "Path") -> None:
|
||||
def test_next_version_empty(self, tmp_path: Path) -> None:
|
||||
store = tmp_path / "store"
|
||||
assert next_version(store, "skills") == "v1"
|
||||
|
||||
def test_next_version_after_existing(self, tmp_path: "Path") -> None:
|
||||
def test_next_version_after_existing(self, tmp_path: Path) -> None:
|
||||
store = tmp_path / "store"
|
||||
resource = store / "skills"
|
||||
resource.mkdir(parents=True)
|
||||
@@ -97,7 +95,7 @@ class TestNextVersion:
|
||||
(resource / "v2").mkdir()
|
||||
assert next_version(store, "skills") == "v3"
|
||||
|
||||
def test_next_version_with_gap(self, tmp_path: "Path") -> None:
|
||||
def test_next_version_with_gap(self, tmp_path: Path) -> None:
|
||||
"""v1 和 v10 之间有 gap,next 应为 v11。"""
|
||||
store = tmp_path / "store"
|
||||
resource = store / "skills"
|
||||
@@ -115,7 +113,7 @@ class TestNextVersion:
|
||||
class TestAdvanceVersion:
|
||||
"""advance_version copytree + _write_meta。"""
|
||||
|
||||
def test_advance_version(self, tmp_path: "Path") -> None:
|
||||
def test_advance_version(self, tmp_path: Path) -> None:
|
||||
store = tmp_path / "store"
|
||||
resource = store / "skills"
|
||||
resource.mkdir(parents=True)
|
||||
@@ -149,7 +147,7 @@ class TestAdvanceVersion:
|
||||
class TestInitStore:
|
||||
"""init_store 初始化 Store 目录结构。"""
|
||||
|
||||
def test_init_store(self, tmp_path: "Path") -> None:
|
||||
def test_init_store(self, tmp_path: Path) -> None:
|
||||
videos = tmp_path / "videos_src"
|
||||
videos.mkdir()
|
||||
(videos / "v001").mkdir()
|
||||
@@ -172,13 +170,11 @@ class TestInitStore:
|
||||
assert (store / "skills" / "v1" / "search.md").read_text() == "skill"
|
||||
assert (store / "prompts" / "v1" / "system.md").read_text() == "prompt"
|
||||
|
||||
skills_meta = json.loads(
|
||||
(store / "skills" / "v1" / "meta.json").read_text()
|
||||
)
|
||||
skills_meta = json.loads((store / "skills" / "v1" / "meta.json").read_text())
|
||||
assert skills_meta["version"] == "v1"
|
||||
assert skills_meta["source"] == "manual"
|
||||
|
||||
def test_init_store_exists_raises(self, tmp_path: "Path") -> None:
|
||||
def test_init_store_exists_raises(self, tmp_path: Path) -> None:
|
||||
store = tmp_path / "store"
|
||||
store.mkdir()
|
||||
with pytest.raises(FileExistsError, match="Store 已存在"):
|
||||
@@ -205,13 +201,9 @@ def _make_seed_fixtures(tmp_path):
|
||||
|
||||
baseline_db = tmp_path / "base.db"
|
||||
conn = sqlite3.connect(baseline_db)
|
||||
conn.execute(
|
||||
"CREATE TABLE _runs (run_id TEXT PRIMARY KEY, status TEXT)"
|
||||
)
|
||||
conn.execute("CREATE TABLE _runs (run_id TEXT PRIMARY KEY, status TEXT)")
|
||||
conn.execute("INSERT INTO _runs VALUES ('r1', 'done')")
|
||||
conn.execute(
|
||||
"CREATE TABLE predictions (run_id TEXT, question_id TEXT, answer TEXT)"
|
||||
)
|
||||
conn.execute("CREATE TABLE predictions (run_id TEXT, question_id TEXT, answer TEXT)")
|
||||
conn.execute("INSERT INTO predictions VALUES ('r1', 'q1', 'A')")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -222,7 +214,7 @@ def _make_seed_fixtures(tmp_path):
|
||||
class TestInitSeed:
|
||||
"""init_seed 创建种子目录。"""
|
||||
|
||||
def test_init_seed(self, tmp_path: "Path") -> None:
|
||||
def test_init_seed(self, tmp_path: Path) -> None:
|
||||
store, skills_dir, prompts_dir, baseline_db = _make_seed_fixtures(tmp_path)
|
||||
seed_dir = init_seed(
|
||||
store,
|
||||
@@ -245,25 +237,23 @@ class TestInitSeed:
|
||||
assert meta["description"] == "初始种子"
|
||||
assert "created_at" in meta
|
||||
|
||||
def test_init_seed_exists_raises(self, tmp_path: "Path") -> None:
|
||||
def test_init_seed_exists_raises(self, tmp_path: Path) -> None:
|
||||
store, skills_dir, prompts_dir, baseline_db = _make_seed_fixtures(tmp_path)
|
||||
init_seed(store, "dup", skills_dir, prompts_dir, baseline_db, "r1", None, "first")
|
||||
with pytest.raises(FileExistsError, match="种子已存在"):
|
||||
init_seed(
|
||||
store, "dup", skills_dir, prompts_dir, baseline_db, "r1", None, "second"
|
||||
)
|
||||
init_seed(store, "dup", skills_dir, prompts_dir, baseline_db, "r1", None, "second")
|
||||
|
||||
|
||||
class TestListSeeds:
|
||||
"""list_seeds 列出所有种子。"""
|
||||
|
||||
def test_list_seeds(self, tmp_path: "Path") -> None:
|
||||
def test_list_seeds(self, tmp_path: Path) -> None:
|
||||
store, skills_dir, prompts_dir, baseline_db = _make_seed_fixtures(tmp_path)
|
||||
init_seed(store, "beta", skills_dir, prompts_dir, baseline_db, "r1", None, "b")
|
||||
init_seed(store, "alpha", skills_dir, prompts_dir, baseline_db, "r1", None, "a")
|
||||
assert list_seeds(store) == ["alpha", "beta"]
|
||||
|
||||
def test_list_seeds_empty(self, tmp_path: "Path") -> None:
|
||||
def test_list_seeds_empty(self, tmp_path: Path) -> None:
|
||||
store = tmp_path / "store"
|
||||
assert list_seeds(store) == []
|
||||
|
||||
@@ -271,14 +261,14 @@ class TestListSeeds:
|
||||
class TestReadSeed:
|
||||
"""read_seed 读取 seed.json。"""
|
||||
|
||||
def test_read_seed(self, tmp_path: "Path") -> None:
|
||||
def test_read_seed(self, tmp_path: Path) -> None:
|
||||
store, skills_dir, prompts_dir, baseline_db = _make_seed_fixtures(tmp_path)
|
||||
init_seed(store, "s1", skills_dir, prompts_dir, baseline_db, "r1", None, "desc")
|
||||
meta = read_seed(store, "s1")
|
||||
assert meta["baseline_run_id"] == "r1"
|
||||
assert meta["description"] == "desc"
|
||||
|
||||
def test_read_seed_not_found(self, tmp_path: "Path") -> None:
|
||||
def test_read_seed_not_found(self, tmp_path: Path) -> None:
|
||||
store = tmp_path / "store"
|
||||
store.mkdir()
|
||||
with pytest.raises(FileNotFoundError, match="种子不存在"):
|
||||
@@ -296,22 +286,17 @@ class TestExtractRunDb:
|
||||
def _make_src_db(self, path):
|
||||
"""创建带 _runs + predictions 表的源 db。"""
|
||||
conn = sqlite3.connect(path)
|
||||
conn.execute(
|
||||
"CREATE TABLE _runs (run_id TEXT PRIMARY KEY, status TEXT)"
|
||||
)
|
||||
conn.execute("CREATE TABLE _runs (run_id TEXT PRIMARY KEY, status TEXT)")
|
||||
conn.execute("INSERT INTO _runs VALUES ('r1', 'done')")
|
||||
conn.execute("INSERT INTO _runs VALUES ('r2', 'done')")
|
||||
conn.execute(
|
||||
"CREATE TABLE predictions "
|
||||
"(run_id TEXT, question_id TEXT, answer TEXT)"
|
||||
)
|
||||
conn.execute("CREATE TABLE predictions (run_id TEXT, question_id TEXT, answer TEXT)")
|
||||
conn.execute("INSERT INTO predictions VALUES ('r1', 'q1', 'A')")
|
||||
conn.execute("INSERT INTO predictions VALUES ('r1', 'q2', 'B')")
|
||||
conn.execute("INSERT INTO predictions VALUES ('r2', 'q1', 'C')")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def test_extract_run_db_preserves_pk(self, tmp_path: "Path") -> None:
|
||||
def test_extract_run_db_preserves_pk(self, tmp_path: Path) -> None:
|
||||
"""原始 CREATE 保留主键约束。"""
|
||||
src = tmp_path / "src.db"
|
||||
dst = tmp_path / "dst.db"
|
||||
@@ -334,7 +319,7 @@ class TestExtractRunDb:
|
||||
assert len(preds) == 2
|
||||
conn.close()
|
||||
|
||||
def test_extract_run_db_missing_table(self, tmp_path: "Path") -> None:
|
||||
def test_extract_run_db_missing_table(self, tmp_path: Path) -> None:
|
||||
"""源 db 无目标表时报错。"""
|
||||
src = tmp_path / "src.db"
|
||||
dst = tmp_path / "dst.db"
|
||||
@@ -345,7 +330,7 @@ class TestExtractRunDb:
|
||||
with pytest.raises(RuntimeError, match="源 db 无表"):
|
||||
extract_run_db(src, dst, "r1")
|
||||
|
||||
def test_extract_run_db_no_rows(self, tmp_path: "Path") -> None:
|
||||
def test_extract_run_db_no_rows(self, tmp_path: Path) -> None:
|
||||
"""目标 run_id 不存在时报错。"""
|
||||
src = tmp_path / "src.db"
|
||||
dst = tmp_path / "dst.db"
|
||||
@@ -382,9 +367,7 @@ def _make_promote_fixtures(tmp_path):
|
||||
prompts_version TEXT
|
||||
)
|
||||
""")
|
||||
conn.execute(
|
||||
"INSERT INTO _runs VALUES ('eval_001', 'v2', 'v2')"
|
||||
)
|
||||
conn.execute("INSERT INTO _runs VALUES ('eval_001', 'v2', 'v2')")
|
||||
conn.execute("""
|
||||
CREATE TABLE predictions (
|
||||
run_id TEXT, question_id TEXT, answer TEXT
|
||||
@@ -400,7 +383,7 @@ def _make_promote_fixtures(tmp_path):
|
||||
class TestPromoteToSeed:
|
||||
"""promote_to_seed 固化 workspace 版本为种子。"""
|
||||
|
||||
def test_promote_to_seed_success(self, tmp_path: "Path") -> None:
|
||||
def test_promote_to_seed_success(self, tmp_path: Path) -> None:
|
||||
ws, store = _make_promote_fixtures(tmp_path)
|
||||
seed_dir = promote_to_seed(ws, store, "v2", "eval_001", "evolved-seed", "good")
|
||||
assert seed_dir == store / "seeds" / "evolved-seed"
|
||||
@@ -411,13 +394,13 @@ class TestPromoteToSeed:
|
||||
assert meta["baseline_run_id"] == "eval_001"
|
||||
assert meta["parent"] == "ws:v2"
|
||||
|
||||
def test_promote_to_seed_version_mismatch(self, tmp_path: "Path") -> None:
|
||||
def test_promote_to_seed_version_mismatch(self, tmp_path: Path) -> None:
|
||||
"""eval run 的 skills_version 与 --version 不符时报错。"""
|
||||
ws, store = _make_promote_fixtures(tmp_path)
|
||||
with pytest.raises(ValueError, match="版本.*不符"):
|
||||
promote_to_seed(ws, store, "v3", "eval_001", "bad", "mismatch")
|
||||
|
||||
def test_promote_to_seed_null_version(self, tmp_path: "Path") -> None:
|
||||
def test_promote_to_seed_null_version(self, tmp_path: Path) -> None:
|
||||
"""eval run 的版本为 NULL 时报错。"""
|
||||
ws = tmp_path / "ws2"
|
||||
ws.mkdir()
|
||||
@@ -438,20 +421,20 @@ class TestPromoteToSeed:
|
||||
with pytest.raises(ValueError, match="NULL"):
|
||||
promote_to_seed(ws, store, "v1", "eval_null", "bad", "null ver")
|
||||
|
||||
def test_promote_to_seed_run_not_found(self, tmp_path: "Path") -> None:
|
||||
def test_promote_to_seed_run_not_found(self, tmp_path: Path) -> None:
|
||||
"""eval run 不存在时报错。"""
|
||||
ws, store = _make_promote_fixtures(tmp_path)
|
||||
with pytest.raises(ValueError, match="eval run 不存在"):
|
||||
promote_to_seed(ws, store, "v1", "nonexistent", "bad", "no run")
|
||||
|
||||
def test_promote_cleanup_tmp_db(self, tmp_path: "Path") -> None:
|
||||
def test_promote_cleanup_tmp_db(self, tmp_path: Path) -> None:
|
||||
"""finally 清理临时 db 文件。"""
|
||||
ws, store = _make_promote_fixtures(tmp_path)
|
||||
promote_to_seed(ws, store, "v2", "eval_001", "clean-test", "cleanup")
|
||||
# 临时 db 应已清理
|
||||
assert not (ws / "_promote_tmp.db").exists()
|
||||
|
||||
def test_promote_cleanup_tmp_db_on_error(self, tmp_path: "Path") -> None:
|
||||
def test_promote_cleanup_tmp_db_on_error(self, tmp_path: Path) -> None:
|
||||
"""即使 init_seed 失败(同名种子),临时 db 也应被清理。"""
|
||||
ws, store = _make_promote_fixtures(tmp_path)
|
||||
promote_to_seed(ws, store, "v2", "eval_001", "first", "first time")
|
||||
|
||||
Reference in New Issue
Block a user