fix: preserve artifact line endings and restore deletions
This commit is contained in:
parent
f261f0bf8f
commit
3172ce264b
@ -1,10 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from difflib import unified_diff
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from difflib import unified_diff
|
|
||||||
|
|
||||||
from engine.models import BaselineSnapshot, TaskSpec
|
from engine.models import BaselineSnapshot, TaskSpec
|
||||||
|
|
||||||
@ -30,19 +30,28 @@ class ArtifactManager:
|
|||||||
file_contents: dict[Path, str] = {}
|
file_contents: dict[Path, str] = {}
|
||||||
file_hashes: dict[Path, str] = {}
|
file_hashes: dict[Path, str] = {}
|
||||||
for path in self.resolve_paths():
|
for path in self.resolve_paths():
|
||||||
content = path.read_text(encoding="utf-8")
|
with path.open("r", encoding="utf-8", newline="") as handle:
|
||||||
|
content = handle.read()
|
||||||
file_contents[path] = content
|
file_contents[path] = content
|
||||||
file_hashes[path] = sha256(content.encode("utf-8")).hexdigest()
|
file_hashes[path] = sha256(content.encode("utf-8")).hexdigest()
|
||||||
return BaselineSnapshot(file_contents=file_contents, file_hashes=file_hashes)
|
return BaselineSnapshot(file_contents=file_contents, file_hashes=file_hashes)
|
||||||
|
|
||||||
def restore(self, snapshot: BaselineSnapshot) -> None:
|
def restore(self, snapshot: BaselineSnapshot) -> None:
|
||||||
|
current_paths = set(self.resolve_paths())
|
||||||
|
snapshot_paths = set(snapshot.file_contents)
|
||||||
|
for path in current_paths - snapshot_paths:
|
||||||
|
path.unlink()
|
||||||
for path, content in snapshot.file_contents.items():
|
for path, content in snapshot.file_contents.items():
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
path.write_text(content, encoding="utf-8")
|
with path.open("w", encoding="utf-8", newline="") as handle:
|
||||||
|
handle.write(content)
|
||||||
|
|
||||||
def diff_summary(self, snapshot: BaselineSnapshot) -> str:
|
def diff_summary(self, snapshot: BaselineSnapshot) -> str:
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
current_contents = {path: path.read_text(encoding="utf-8") for path in self.resolve_paths()}
|
current_contents: dict[Path, str] = {}
|
||||||
|
for path in self.resolve_paths():
|
||||||
|
with path.open("r", encoding="utf-8", newline="") as handle:
|
||||||
|
current_contents[path] = handle.read()
|
||||||
all_paths = sorted(set(snapshot.file_contents) | set(current_contents))
|
all_paths = sorted(set(snapshot.file_contents) | set(current_contents))
|
||||||
for path in all_paths:
|
for path in all_paths:
|
||||||
before = snapshot.file_contents.get(path, "")
|
before = snapshot.file_contents.get(path, "")
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from hashlib import sha256
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@ -42,6 +43,56 @@ class ArtifactManagerTest(unittest.TestCase):
|
|||||||
manager.restore(snapshot)
|
manager.restore(snapshot)
|
||||||
self.assertEqual(target.read_text(encoding="utf-8"), "hello\n")
|
self.assertEqual(target.read_text(encoding="utf-8"), "hello\n")
|
||||||
|
|
||||||
|
def test_restore_removes_newly_created_included_artifact(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
artifact_dir = root / "artifacts"
|
||||||
|
artifact_dir.mkdir()
|
||||||
|
target = artifact_dir / "sample.md"
|
||||||
|
target.write_text("hello\n", encoding="utf-8")
|
||||||
|
manager = ArtifactManager(make_task(root))
|
||||||
|
snapshot = manager.snapshot()
|
||||||
|
extra = artifact_dir / "new.md"
|
||||||
|
extra.write_text("new\n", encoding="utf-8")
|
||||||
|
manager.restore(snapshot)
|
||||||
|
self.assertFalse(extra.exists())
|
||||||
|
self.assertEqual(target.read_text(encoding="utf-8"), "hello\n")
|
||||||
|
|
||||||
|
def test_snapshot_and_restore_preserve_crlf_content(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
artifact_dir = root / "artifacts"
|
||||||
|
artifact_dir.mkdir()
|
||||||
|
target = artifact_dir / "sample.md"
|
||||||
|
original = "line1\r\nline2\r\n"
|
||||||
|
with target.open("w", encoding="utf-8", newline="") as handle:
|
||||||
|
handle.write(original)
|
||||||
|
manager = ArtifactManager(make_task(root))
|
||||||
|
snapshot = manager.snapshot()
|
||||||
|
self.assertEqual(snapshot.file_contents[target], original)
|
||||||
|
self.assertEqual(
|
||||||
|
snapshot.file_hashes[target],
|
||||||
|
sha256(original.encode("utf-8")).hexdigest(),
|
||||||
|
)
|
||||||
|
with target.open("w", encoding="utf-8", newline="") as handle:
|
||||||
|
handle.write("changed\r\n")
|
||||||
|
manager.restore(snapshot)
|
||||||
|
with target.open("r", encoding="utf-8", newline="") as handle:
|
||||||
|
restored = handle.read()
|
||||||
|
self.assertEqual(restored, original)
|
||||||
|
|
||||||
|
def test_resolve_paths_is_deterministic_and_respects_excludes(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
artifact_dir = root / "artifacts"
|
||||||
|
artifact_dir.mkdir()
|
||||||
|
(artifact_dir / "b.md").write_text("b\n", encoding="utf-8")
|
||||||
|
(artifact_dir / "ignore.md").write_text("ignore\n", encoding="utf-8")
|
||||||
|
(artifact_dir / "a.md").write_text("a\n", encoding="utf-8")
|
||||||
|
manager = ArtifactManager(make_task(root))
|
||||||
|
paths = manager.resolve_paths()
|
||||||
|
self.assertEqual(paths, [artifact_dir / "a.md", artifact_dir / "b.md"])
|
||||||
|
|
||||||
def test_diff_summary_contains_changed_line(self) -> None:
|
def test_diff_summary_contains_changed_line(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
root = Path(tmp)
|
root = Path(tmp)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user