owenrusk.dev

casebench

test cases straight onto the board.

git clone https://owenrusk.dev/casebench.git

commit 14a2b2ff8bf2b2c4dd68be96de9d61d99651c6bb
parent 71e65c89933d25d238a483807b7a3de0e6f55f13
author Owen Rusk <owen@papermothgames.com>
date   2025-03-11 11:48:02 -0500
casefile: check ids and that text files exist
casebench/casefile.py+18-5
casebench/cli.py+5-1
diff --git a/casebench/casefile.py b/casebench/casefile.py
index 2beeafc..fffaf6c 100644
--- a/casebench/casefile.py
+++ b/casebench/casefile.py
@@ -1,8 +1,11 @@
+import re
 from dataclasses import dataclass
 from pathlib import Path
 
 import yaml
 
+ID = re.compile(r"[a-z0-9][a-z0-9-]*")
+
 
 class CaseError(ValueError):
     pass
@@ -24,11 +27,21 @@ class Case:
 
 def load(folder: Path) -> Case:
     path = Path(folder) / "case.yaml"
+    if not path.is_file():
+        raise CaseError(f"{folder}: no case.yaml")
     raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
-    items = tuple(
-        Item(entry["id"], entry["title"], (path.parent / entry["text"]).read_text(encoding="utf-8"))
-        for entry in raw.get("items") or []
-    )
+    items = []
+    for entry in raw.get("items") or []:
+        text = path.parent / entry["text"]
+        if not text.is_file():
+            raise CaseError(f"{path}: no file {entry['text']}")
+        items.append(Item(_id(entry.get("id"), path), entry["title"], text.read_text(encoding="utf-8")))
     if not items:
         raise CaseError(f"{path}: no items")
-    return Case(raw["id"], raw["title"], items)
+    return Case(_id(raw.get("id"), path), raw["title"], tuple(items))
+
+
+def _id(value: object, path: Path) -> str:
+    if not isinstance(value, str) or not ID.fullmatch(value):
+        raise CaseError(f"{path}: bad id {value!r}")
+    return value
diff --git a/casebench/cli.py b/casebench/cli.py
index 8098615..0708045 100644
--- a/casebench/cli.py
+++ b/casebench/cli.py
@@ -18,7 +18,11 @@ def main(argv: list[str] | None = None) -> int:
     if not dsn:
         print("casebench: set CASEBENCH_DSN", file=sys.stderr)
         return 2
-    case = casefile.load(args.folder)
+    try:
+        case = casefile.load(args.folder)
+    except casefile.CaseError as e:
+        print(f"casebench: {e}", file=sys.stderr)
+        return 1
     with db.connect(dsn) as conn:
         player = db.player_id(conn, args.player)
         if player is None: