mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
* fix(workspace): publish the snapshot text cache byte-exactly on every host * test(workspace): pin diff line counts and guard the text cache on Windows CI * ci(workspace): run Windows text-cache guards on source changes Include the workspace_changes package in the Windows workflow's PR path filter so scanner or diff regressions run the native Windows guards before merge, rather than waiting for the weekly schedule. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
131 lines
4.4 KiB
Python
131 lines
4.4 KiB
Python
# SPDX-License-Identifier: MIT
|
|
"""A cached preview must never be served half written."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def scanner():
|
|
# Import after the autouse fixtures initialize the runtime package.
|
|
from deerflow.workspace_changes import scanner
|
|
|
|
return scanner
|
|
|
|
|
|
def test_publishes_the_text_and_leaves_no_temp_file(tmp_path, scanner):
|
|
target = tmp_path / "entry"
|
|
|
|
scanner._publish_text_atomically(target, "decoded body")
|
|
|
|
assert target.read_text(encoding="utf-8") == "decoded body"
|
|
assert list(tmp_path.glob("*.tmp")) == []
|
|
|
|
|
|
def test_a_failed_publish_keeps_the_previous_entry(tmp_path, monkeypatch, scanner):
|
|
target = tmp_path / "entry"
|
|
target.write_text("previous body", encoding="utf-8")
|
|
|
|
def _boom(*args, **kwargs):
|
|
raise OSError("disk full")
|
|
|
|
monkeypatch.setattr(os, "replace", _boom)
|
|
|
|
with pytest.raises(OSError):
|
|
scanner._publish_text_atomically(target, "new body")
|
|
|
|
assert target.read_text(encoding="utf-8") == "previous body"
|
|
assert list(tmp_path.glob("*.tmp")) == []
|
|
|
|
|
|
@pytest.mark.parametrize("publish_error", [OSError("publish failed"), KeyboardInterrupt("publish interrupted")], ids=["io-error", "interrupt"])
|
|
def test_cleanup_failure_preserves_the_publish_error(tmp_path, monkeypatch, scanner, publish_error):
|
|
target = tmp_path / "entry"
|
|
target.write_text("previous body", encoding="utf-8")
|
|
|
|
def _fail_publish(*args, **kwargs):
|
|
raise publish_error
|
|
|
|
def _fail_cleanup(*args, **kwargs):
|
|
raise PermissionError("cleanup failed")
|
|
|
|
with monkeypatch.context() as patches:
|
|
patches.setattr(os, "replace", _fail_publish)
|
|
patches.setattr(Path, "unlink", _fail_cleanup)
|
|
|
|
with pytest.raises(type(publish_error)) as caught:
|
|
scanner._publish_text_atomically(target, "new body")
|
|
|
|
assert caught.value is publish_error
|
|
assert target.read_text(encoding="utf-8") == "previous body"
|
|
|
|
|
|
def test_truncating_write_would_have_destroyed_it(tmp_path):
|
|
"""Control: the previous implementation loses the entry in the same failure."""
|
|
target = tmp_path / "entry"
|
|
target.write_text("previous body", encoding="utf-8")
|
|
|
|
try:
|
|
with open(target, "w", encoding="utf-8") as handle:
|
|
handle.write("new ")
|
|
raise OSError("disk full")
|
|
except OSError:
|
|
pass
|
|
|
|
assert target.read_text(encoding="utf-8") != "previous body"
|
|
|
|
|
|
def test_cache_text_file_returns_a_complete_entry(tmp_path, scanner):
|
|
cache_dir = tmp_path / "cache"
|
|
cache_dir.mkdir()
|
|
virtual_path = "/workspace/report.csv"
|
|
|
|
returned = scanner._cache_text_file("a,b\n1,2\n", virtual_path, cache_dir)
|
|
|
|
assert Path(returned).read_text(encoding="utf-8") == "a,b\n1,2\n"
|
|
# The key is derived from the virtual path, so a partial entry would be served
|
|
# as the file's content on every later read.
|
|
assert Path(returned).name == hashlib.sha256(virtual_path.encode()).hexdigest()
|
|
|
|
|
|
def test_cache_text_file_publishes_decoded_crlf_content_byte_exactly(tmp_path, scanner):
|
|
"""A decoded CRLF snapshot must reach the cache verbatim on every host.
|
|
|
|
The default text-mode write translates ``\\n`` into ``os.linesep``, which
|
|
on native Windows turns ``\\r\\n`` content into ``\\r\\r\\n`` bytes -- the
|
|
cache is then served as the file's text with a spurious blank line.
|
|
"""
|
|
cache_dir = tmp_path / "cache"
|
|
cache_dir.mkdir()
|
|
|
|
returned = scanner._cache_text_file("a,b\r\n1,2\r\n", "/workspace/report.csv", cache_dir)
|
|
|
|
assert Path(returned).read_bytes() == b"a,b\r\n1,2\r\n"
|
|
|
|
|
|
def test_cache_entry_survives_a_failed_publish(tmp_path, monkeypatch, scanner):
|
|
"""The real entry point: a failed publish must not damage the cached entry."""
|
|
cache_dir = tmp_path / "cache"
|
|
cache_dir.mkdir()
|
|
virtual_path = "/workspace/report.csv"
|
|
name = hashlib.sha256(virtual_path.encode()).hexdigest()
|
|
entry = cache_dir / name
|
|
entry.write_text("previous body", encoding="utf-8")
|
|
|
|
def _boom(*args, **kwargs):
|
|
raise OSError("disk full")
|
|
|
|
monkeypatch.setattr(os, "replace", _boom)
|
|
|
|
with pytest.raises(OSError):
|
|
scanner._cache_text_file("new body", virtual_path, cache_dir)
|
|
|
|
# A truncating write would have emptied the entry before failing, and the
|
|
# caller serves this path as the file's content on every later read.
|
|
assert entry.read_text(encoding="utf-8") == "previous body"
|