"""U-036: real unittest coverage for change_manifest.py -- adapted from graphloop's test_change_manifest.py (2026-08-02). Module logic is unchanged from graphloop ("adapted verbatim", see change_manifest.py's own docstring) except graphlemon's `unified_diff` collapses "@@ -l,s +l,s @@" hunk headers to a single leading "@@" marker (see that module's own comment) -- the "only text gets hunks" assertion below counts "@@" substring occurrences, which still holds 1-to-1 with graphlemon's collapsed format for these single-hunk fixtures. Run: python3 -m unittest discover -s supervisor/isolation -p "test_*.py" -v """ import hashlib import os import shutil import tempfile import unittest from supervisor.isolation import change_manifest as cm TEXT_A = "hello\n" TEXT_A_MODIFIED = "hello modified\n" BINARY_BYTES = b"\x89PNG\r\n\x1a\n\x00\x01\x02\x03BINARYDATA\xff\xfe" def sha256_of(data): return hashlib.sha256(data).hexdigest() class ChangeManifestTestCase(unittest.TestCase): def setUp(self): self.source_dir = tempfile.mkdtemp(prefix="cm-src-") self.addCleanup(shutil.rmtree, self.source_dir, ignore_errors=True) self._extra_dirs = [] self.addCleanup(self._cleanup_extra_dirs) def _cleanup_extra_dirs(self): for path in self._extra_dirs: shutil.rmtree(path, ignore_errors=True) def _new_isolated_dir(self): path = tempfile.mkdtemp(prefix="cm-iso-") self._extra_dirs.append(path) return path @staticmethod def _write_text(root, rel_path, content): full = os.path.join(root, *rel_path.split("/")) os.makedirs(os.path.dirname(full), exist_ok=True) with open(full, "w", encoding="utf-8") as handle: handle.write(content) return full @staticmethod def _write_bytes(root, rel_path, data): full = os.path.join(root, *rel_path.split("/")) os.makedirs(os.path.dirname(full), exist_ok=True) with open(full, "wb") as handle: handle.write(data) return full def _copy_source_to_isolated(self): isolated_dir = self._new_isolated_dir() shutil.rmtree(isolated_dir) shutil.copytree(self.source_dir, isolated_dir) return isolated_dir class SnapshotWorkspaceTests(ChangeManifestTestCase): def test_records_text_file_with_hash_size_and_kind(self): self._write_text(self.source_dir, "a.txt", TEXT_A) snapshot = cm.snapshot_workspace(self.source_dir) entry = snapshot["files"]["a.txt"] self.assertEqual(entry["sha256"], sha256_of(TEXT_A.encode("utf-8"))) self.assertEqual(entry["size"], len(TEXT_A.encode("utf-8"))) self.assertEqual(entry["kind"], "text") def test_records_binary_file_as_binary_kind(self): self._write_bytes(self.source_dir, "logo.bin", BINARY_BYTES) snapshot = cm.snapshot_workspace(self.source_dir) entry = snapshot["files"]["logo.bin"] self.assertEqual(entry["sha256"], sha256_of(BINARY_BYTES)) self.assertEqual(entry["size"], len(BINARY_BYTES)) self.assertEqual(entry["kind"], "binary") def test_uses_relative_posix_paths_for_nested_files(self): self._write_text(self.source_dir, "subdir/nested/file.txt", "x") snapshot = cm.snapshot_workspace(self.source_dir) self.assertIn("subdir/nested/file.txt", snapshot["files"]) def test_ignores_git_pycache_pytest_cache_and_dsstore(self): self._write_text(self.source_dir, "keep.txt", "keep") self._write_text(self.source_dir, ".git/config", "git config") self._write_text(self.source_dir, ".git/objects/ab/cdef", "blob") self._write_text(self.source_dir, "__pycache__/mod.cpython-39.pyc", "pyc") self._write_text(self.source_dir, ".pytest_cache/v/cache/lastfailed", "{}") self._write_text(self.source_dir, ".DS_Store", "junk") snapshot = cm.snapshot_workspace(self.source_dir) self.assertEqual(set(snapshot["files"]), {"keep.txt"}) def test_skips_symlinks_but_keeps_their_real_target(self): target = self._write_text(self.source_dir, "real.txt", "real content") link_path = os.path.join(self.source_dir, "link.txt") os.symlink(target, link_path) snapshot = cm.snapshot_workspace(self.source_dir) self.assertIn("real.txt", snapshot["files"]) self.assertNotIn("link.txt", snapshot["files"]) class BuildChangeManifestTests(ChangeManifestTestCase): def _baseline_snapshot(self): self._write_text(self.source_dir, "a.txt", TEXT_A) self._write_text(self.source_dir, "b.txt", "world\n") self._write_text(self.source_dir, "c.txt", "keep me\n") return cm.snapshot_workspace(self.source_dir) def test_added_modified_deleted_and_unchanged_classification(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() self._write_text(isolated_dir, "a.txt", TEXT_A_MODIFIED) os.remove(os.path.join(isolated_dir, "b.txt")) self._write_text(isolated_dir, "d.txt", "brand new\n") manifest = cm.build_change_manifest(snapshot, isolated_dir) by_path = {entry["path"]: entry for entry in manifest["changed_files"]} self.assertEqual(set(by_path), {"a.txt", "b.txt", "d.txt"}) self.assertNotIn("c.txt", by_path) modified = by_path["a.txt"] self.assertEqual(modified["status"], "modified") self.assertEqual(modified["old_hash"], sha256_of(TEXT_A.encode("utf-8"))) self.assertEqual(modified["new_hash"], sha256_of(TEXT_A_MODIFIED.encode("utf-8"))) deleted = by_path["b.txt"] self.assertEqual(deleted["status"], "deleted") self.assertIsNone(deleted["new_hash"]) self.assertEqual(deleted["old_hash"], sha256_of(b"world\n")) added = by_path["d.txt"] self.assertEqual(added["status"], "added") self.assertIsNone(added["old_hash"]) self.assertEqual(added["new_hash"], sha256_of(b"brand new\n")) def test_unchanged_files_never_appear_in_changed_files(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() manifest = cm.build_change_manifest(snapshot, isolated_dir) self.assertEqual(manifest["changed_files"], []) self.assertEqual(cm.unified_diff(manifest), "") def test_ignored_files_excluded_even_when_added_in_isolated(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() self._write_text(isolated_dir, ".git/config", "new git config") self._write_text(isolated_dir, "__pycache__/mod.cpython-39.pyc", "pyc") self._write_text(isolated_dir, ".DS_Store", "junk") manifest = cm.build_change_manifest(snapshot, isolated_dir) self.assertEqual(manifest["changed_files"], []) def test_binary_file_added_is_classified_binary(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() self._write_bytes(isolated_dir, "logo.bin", BINARY_BYTES) manifest = cm.build_change_manifest(snapshot, isolated_dir) self.assertEqual(len(manifest["changed_files"]), 1) entry = manifest["changed_files"][0] self.assertEqual(entry["path"], "logo.bin") self.assertEqual(entry["status"], "added") self.assertEqual(entry["kind"], "binary") self.assertEqual(entry["new_hash"], sha256_of(BINARY_BYTES)) def test_changed_files_are_sorted_deterministically(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() self._write_text(isolated_dir, "z_file.txt", "z\n") self._write_text(isolated_dir, "0_file.txt", "0\n") self._write_text(isolated_dir, "m_file.txt", "m\n") manifest = cm.build_change_manifest(snapshot, isolated_dir) paths = [entry["path"] for entry in manifest["changed_files"]] self.assertEqual(paths, sorted(paths)) self.assertEqual(paths, ["0_file.txt", "m_file.txt", "z_file.txt"]) def test_rejects_symlink_found_in_isolated_workspace(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() target = os.path.join(isolated_dir, "a.txt") os.symlink(target, os.path.join(isolated_dir, "sneaky_link.txt")) with self.assertRaises(cm.ChangeManifestError): cm.build_change_manifest(snapshot, isolated_dir) def test_rejects_symlink_escaping_isolated_workspace(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() outside_dir = self._new_isolated_dir() outside_file = self._write_text(outside_dir, "outside.txt", "outside") os.symlink(outside_file, os.path.join(isolated_dir, "escape_link.txt")) with self.assertRaises(cm.ChangeManifestError): cm.build_change_manifest(snapshot, isolated_dir) def test_rejects_tampered_snapshot_with_traversal_path(self): snapshot = self._baseline_snapshot() isolated_dir = self._copy_source_to_isolated() for bad_path in ("../evil.txt", "/etc/passwd", "sub/../../escape.txt"): with self.subTest(bad_path=bad_path): tampered = {"root": snapshot["root"], "files": dict(snapshot["files"])} tampered["files"][bad_path] = {"sha256": "0" * 64, "size": 0, "kind": "text"} with self.assertRaises(cm.ChangeManifestError): cm.build_change_manifest(tampered, isolated_dir) class UnifiedDiffTests(ChangeManifestTestCase): def test_modified_text_file_produces_readable_unified_diff(self): self._write_text(self.source_dir, "a.txt", TEXT_A) snapshot = cm.snapshot_workspace(self.source_dir) isolated_dir = self._copy_source_to_isolated() self._write_text(isolated_dir, "a.txt", TEXT_A_MODIFIED) manifest = cm.build_change_manifest(snapshot, isolated_dir) diff_text = cm.unified_diff(manifest) lines = diff_text.splitlines() self.assertTrue(any(l.startswith("---") and "a.txt" in l for l in lines)) self.assertTrue(any(l.startswith("+++") and "a.txt" in l for l in lines)) self.assertTrue(any(l.startswith("@@") for l in lines)) self.assertTrue(any(l.startswith("-") and "hello" in l and not l.startswith("---") for l in lines)) self.assertTrue(any(l.startswith("+") and "hello modified" in l and not l.startswith("+++") for l in lines)) def test_added_text_file_diffs_against_dev_null(self): snapshot = cm.snapshot_workspace(self.source_dir) isolated_dir = self._copy_source_to_isolated() self._write_text(isolated_dir, "new.txt", "brand new content\n") manifest = cm.build_change_manifest(snapshot, isolated_dir) diff_text = cm.unified_diff(manifest) lines = diff_text.splitlines() self.assertTrue(any("/dev/null" in l for l in lines)) self.assertTrue(any(l.startswith("+") and "brand new content" in l for l in lines)) removed_content_lines = [l for l in lines if l.startswith("-") and not l.startswith("---")] self.assertEqual(removed_content_lines, []) def test_deleted_text_file_diffs_against_dev_null(self): self._write_text(self.source_dir, "gone.txt", "will be deleted\n") snapshot = cm.snapshot_workspace(self.source_dir) isolated_dir = self._copy_source_to_isolated() os.remove(os.path.join(isolated_dir, "gone.txt")) manifest = cm.build_change_manifest(snapshot, isolated_dir) diff_text = cm.unified_diff(manifest) lines = diff_text.splitlines() self.assertTrue(any("/dev/null" in l for l in lines)) self.assertTrue(any(l.startswith("-") and "will be deleted" in l for l in lines)) added_content_lines = [l for l in lines if l.startswith("+") and not l.startswith("+++")] self.assertEqual(added_content_lines, []) def test_binary_file_diff_is_marker_not_content_diff(self): snapshot = cm.snapshot_workspace(self.source_dir) isolated_dir = self._copy_source_to_isolated() self._write_bytes(isolated_dir, "logo.bin", BINARY_BYTES) manifest = cm.build_change_manifest(snapshot, isolated_dir) diff_text = cm.unified_diff(manifest) self.assertIn("Binary files", diff_text) self.assertIn("logo.bin", diff_text) self.assertNotIn("@@", diff_text) def test_text_and_binary_changes_together_only_text_gets_hunks(self): self._write_text(self.source_dir, "a.txt", TEXT_A) self._write_text(self.source_dir, "gone.txt", "will be deleted\n") snapshot = cm.snapshot_workspace(self.source_dir) isolated_dir = self._copy_source_to_isolated() self._write_text(isolated_dir, "a.txt", TEXT_A_MODIFIED) os.remove(os.path.join(isolated_dir, "gone.txt")) self._write_text(isolated_dir, "new.txt", "added text\n") self._write_bytes(isolated_dir, "logo.bin", BINARY_BYTES) manifest = cm.build_change_manifest(snapshot, isolated_dir) diff_text = cm.unified_diff(manifest) text_change_count = sum(1 for entry in manifest["changed_files"] if entry["kind"] == "text") self.assertEqual(diff_text.count("@@"), text_change_count) self.assertIn("Binary files", diff_text) if __name__ == "__main__": unittest.main()