diff --git a/skills/kvb-check-gate/scripts/gates.py b/skills/kvb-check-gate/scripts/gates.py index 45bb5ea..c8d4041 100755 --- a/skills/kvb-check-gate/scripts/gates.py +++ b/skills/kvb-check-gate/scripts/gates.py @@ -184,6 +184,16 @@ def journal(origin: Path, kept_dir: Path, kept: Path) -> list[dict]: return list(merged.values()) +def encodable(value: object) -> bool: + if not isinstance(value, str): + return False + try: + value.encode() + except UnicodeEncodeError: + return False + return True + + def journal_lock(path: Path) -> contextlib.AbstractContextManager: return watch.nofollow.locked(watch.ROOT, path.with_suffix(".lock"), FILE_MODE, None) @@ -262,7 +272,7 @@ def opened(path: Path, write: bool, kept_dir: Path) -> sqlite3.Connection: db.executemany("INSERT INTO recorded VALUES (?, ?, ?)", [ (row["repo"], row["branch"], row["parent"]) for row in journal(watch.PARENTS, kept_dir, kept_dir / watch.KEPT_PARENTS.name) - if all(isinstance(row.get(key), str) for key in ("repo", "branch", "parent")) and row["branch"] != row["parent"] + if all(encodable(row.get(key)) for key in ("repo", "branch", "parent")) and row["branch"] != row["parent"] ]) except BaseException: db.close() diff --git a/tests/test_gates.py b/tests/test_gates.py index b0d0d20..7188007 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -139,6 +139,12 @@ class TestGates(Scratch): check = self.gates("receipt", "check", "--repo", repo, "--tree", tree, "--level", "test") self.assertEqual((check.returncode, check.stderr), (0, "")) + def test_a_parent_row_with_a_lone_surrogate_is_skipped_and_the_others_still_count(self): + self.council.mkdir(exist_ok=True) + rows = [{"id": "bad", "repo": str(self.repo), "branch": "main", "parent": "\ud800"}, {"id": "good", "repo": str(self.repo), "branch": "main", "parent": "feature"}] + (self.council / "gate-parents.jsonl").write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8") + self.assertEqual(self.parents("main"), [{"parent": "feature", "source": "recorded"}]) + def test_a_receipt_older_than_30_days_neither_counts_nor_survives_the_next_write(self): repo, tree = str(self.repo), self.out("rev-parse", "HEAD^{tree}") old = {"id": "old", "repo": repo, "tree": tree, "level": "test", "gpu": "false", "at": time.time() - 31 * 86400}