mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-17 23:51:19 +00:00
* perf(auto_resource): reuse historical ownership lookup per batch Share a lazy ownership index across routed processors and expire it at the end of each resource batch. Refresh changed days and their safe aliases after writes, deletes, and partial failures. Add regression coverage for scan counts, ownership, lookup scope isolation, and retry behavior. * fix(auto_resource): reconcile shared lookup across active batches * fix(auto_resource): refresh batch ownership from filesystem metadata * refactor(auto_resource): simplify batch ownership lookup Keep per-day ownership caches paired with pre-read metadata and refresh changed days before subsequent resources. Remove reverse-index bookkeeping, post-read stabilization retries, and recursive scope fallback. Handle late ELOOP errors on Python 3.13 while preserving other OS errors. Update regression tests for cross-resource freshness and retain safety and lifecycle coverage. * refactor(auto_resource): streamline batch lookup refresh * refactor(auto_resource): scope ownership cache to batch mutations Replace filesystem snapshots with one lazy historical lookup shared by resource processors. Reload only days changed by this invocation and discard the cache when it ends. Reuse canonical daily-note scans for symlink safety and Python 3.13 compatibility. Consolidate regression tests around the narrowed batch contract. Validation: Python 3.11, 3.12 and 3.13 core/plugin suites each passed 1407 tests; pre-commit --all-files and offline lookup/deletion comparison passed. * refactor(auto_resource): reuse initial ownership without refreshes * test(auto_resource): streamline ownership lookup regression coverage
135 lines
5 KiB
Python
135 lines
5 KiB
Python
"""Daily-note scans preserve internal links without reading outside the workspace."""
|
|
|
|
import errno
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from reme.steps.file_io import _daily_index
|
|
|
|
DAY = "2026-01-01"
|
|
|
|
|
|
def _symlink(link: Path, target: Path, *, directory: bool = False) -> None:
|
|
"""Skip platforms where creating a test symlink is unavailable."""
|
|
try:
|
|
link.symlink_to(target, target_is_directory=directory)
|
|
except NotImplementedError as exc:
|
|
pytest.skip(f"Symlinks unavailable: {exc}")
|
|
except OSError as exc:
|
|
if exc.errno in {errno.EPERM, errno.EACCES, errno.ENOSYS, errno.EOPNOTSUPP}:
|
|
pytest.skip(f"Symlinks unavailable: {exc}")
|
|
raise
|
|
|
|
|
|
@pytest.fixture(name="daily_workspace")
|
|
def _daily_workspace(tmp_path):
|
|
"""Keep note targets and an outside sibling under pytest's temporary directory."""
|
|
workspace = (tmp_path / "workspace").resolve()
|
|
day_dir = workspace / "daily" / DAY
|
|
day_dir.mkdir(parents=True)
|
|
(day_dir / "regular.md").write_text("---\nname: regular\n---\nBody\n", encoding="utf-8")
|
|
return workspace, day_dir
|
|
|
|
|
|
@pytest.mark.parametrize("kind", ["internal", "escaping", "dangling", "self-loop", "mutual-loop"])
|
|
def test_scan_note_links_keep_logical_paths_and_skip_unreadable_targets(kind, daily_workspace, monkeypatch):
|
|
"""Unsafe or unreadable links must neither hide a valid note nor reach read_text."""
|
|
workspace, day_dir = daily_workspace
|
|
link = day_dir / "linked.md"
|
|
target = workspace / "target.md"
|
|
if kind == "escaping":
|
|
target = workspace.parent / "outside.md"
|
|
if kind in {"internal", "escaping"}:
|
|
target.write_text("---\nname: linked\nsource_resource: '[[resource/photo.png]]'\n---\nBody\n", encoding="utf-8")
|
|
elif kind == "self-loop":
|
|
target = link
|
|
elif kind == "mutual-loop":
|
|
target = day_dir / "other.md"
|
|
_symlink(link=target, target=link)
|
|
_symlink(link, target)
|
|
|
|
read_paths = []
|
|
original_read = Path.read_text
|
|
|
|
def record_read(path, *args, **kwargs):
|
|
read_paths.append(path)
|
|
return original_read(path, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(Path, "read_text", record_read)
|
|
notes = _daily_index.scan_notes(workspace, DAY, "daily")
|
|
|
|
expected_paths = [f"daily/{DAY}/regular.md"]
|
|
if kind == "internal":
|
|
expected_paths.insert(0, f"daily/{DAY}/linked.md")
|
|
assert notes[0]["metadata"]["source_resource"] == "[[resource/photo.png]]"
|
|
assert target in read_paths
|
|
else:
|
|
assert read_paths == [day_dir / "regular.md"]
|
|
assert [note["path"] for note in notes] == expected_paths
|
|
assert link.is_symlink()
|
|
|
|
|
|
@pytest.mark.parametrize("kind", ["internal", "escaping", "dangling", "self-loop", "mutual-loop"])
|
|
def test_scan_date_directory_links(kind, daily_workspace, monkeypatch):
|
|
"""A valid date alias retains its logical date; invalid aliases are not read."""
|
|
workspace, day_dir = daily_workspace
|
|
alias = day_dir.parent / "2026-01-02"
|
|
target = day_dir
|
|
if kind == "escaping":
|
|
target = workspace.parent / "outside-day"
|
|
target.mkdir()
|
|
(target / "outside.md").write_text("---\nname: outside\n---\nBody\n", encoding="utf-8")
|
|
elif kind == "dangling":
|
|
target = day_dir.parent / "missing"
|
|
elif kind == "self-loop":
|
|
target = alias
|
|
elif kind == "mutual-loop":
|
|
target = day_dir.parent / "2026-01-03"
|
|
_symlink(target, alias, directory=True)
|
|
_symlink(alias, target, directory=True)
|
|
|
|
read_paths = []
|
|
original_read = Path.read_text
|
|
|
|
def record_read(path, *args, **kwargs):
|
|
read_paths.append(path)
|
|
return original_read(path, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(Path, "read_text", record_read)
|
|
notes = _daily_index.scan_notes(workspace, alias.name, "daily")
|
|
|
|
if kind == "internal":
|
|
assert notes == [{"path": "daily/2026-01-02/regular.md", "metadata": {"name": "regular"}}]
|
|
assert read_paths == [day_dir / "regular.md"]
|
|
else:
|
|
assert not notes
|
|
assert not read_paths
|
|
assert alias.is_symlink()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"method,error_number",
|
|
[("is_file", errno.ELOOP), ("is_file", errno.EIO), ("iterdir", errno.EACCES)],
|
|
)
|
|
def test_scan_only_suppresses_loop_errors(method, error_number, daily_workspace, monkeypatch):
|
|
"""Do not turn unrelated enumeration or stat failures into an empty history."""
|
|
workspace, day_dir = daily_workspace
|
|
note = day_dir / "regular.md"
|
|
error = OSError(error_number, "injected filesystem error")
|
|
original = getattr(Path, method)
|
|
target = note if method == "is_file" else day_dir
|
|
|
|
def fail_operation(path, *args, **kwargs):
|
|
if path == target:
|
|
raise error
|
|
return original(path, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(Path, method, fail_operation)
|
|
|
|
if error_number == errno.ELOOP:
|
|
assert not _daily_index.scan_notes(workspace, DAY, "daily")
|
|
else:
|
|
with pytest.raises(OSError) as captured:
|
|
_daily_index.scan_notes(workspace, DAY, "daily")
|
|
assert captured.value.errno == error_number
|