permanence-training / tests /test_mock_fs.py
chane35's picture
PERMANENCE: reversibility-aware RL environment for training LLM agents
796da7c verified
Raw
History Blame
4.65 kB
"""
Tests for permanence.world.fs β€” the mock POSIX-style filesystem.
Two things are verified:
1. Reversibility classifications are correct for representative ops.
2. The mock FS cannot reach the real filesystem. No real file is created
and no real file is read during an episode rollout.
"""
from __future__ import annotations
import os
import pathlib
from unittest.mock import patch
import pytest
from permanence.world.fs import MockFS
# ─────────────────────────────────────────────────────────────────────────────
# Reversibility semantics
# ─────────────────────────────────────────────────────────────────────────────
def test_touch_and_read_are_r1_r2():
fs = MockFS(trash_enabled=True)
fs.mkdir("/project")
assert fs.touch("/project/readme.md", b"hello").r_level == 2
assert fs.read("/project/readme.md").r_level == 1
def test_rm_with_trash_is_r3_and_recoverable():
fs = MockFS(trash_enabled=True)
fs.touch("/a.txt", b"x")
rm_res = fs.rm("/a.txt")
assert rm_res.ok and rm_res.r_level == 3
restore = fs.restore_from_trash("/a.txt")
assert restore.ok and "/a.txt" in fs.files
def test_rm_without_trash_on_git_tracked_is_r4():
fs = MockFS(trash_enabled=False)
fs.touch("/tracked.py", b"print(1)")
fs.git_tracked.add("/tracked.py")
res = fs.rm("/tracked.py")
assert res.ok and res.r_level == 4
assert "recoverable" in res.message
def test_rm_without_trash_untracked_is_r5():
fs = MockFS(trash_enabled=False)
fs.touch("/ephemeral.log", b"secret")
res = fs.rm("/ephemeral.log")
assert res.ok and res.r_level == 5
assert "UNRECOVERABLE" in res.message
def test_rm_rf_untracked_tree_is_r5():
fs = MockFS(trash_enabled=True)
fs.mkdir("/var")
fs.mkdir("/var/cache")
fs.touch("/var/cache/a.bin", b"1")
fs.touch("/var/cache/b.bin", b"2")
res = fs.rm_rf("/var")
assert res.ok and res.r_level == 5
assert len(res.affected_paths) == 2
def test_rm_rf_when_all_backed_up_is_r4():
fs = MockFS(trash_enabled=True)
fs.mkdir("/data")
fs.touch("/data/x", b"1")
fs.touch("/data/y", b"2")
fs.snapshot("pre-cleanup")
res = fs.rm_rf("/data")
assert res.ok and res.r_level == 4
def test_empty_trash_of_untracked_file_is_r5():
fs = MockFS(trash_enabled=True)
fs.touch("/a", b"1")
fs.rm("/a") # goes to trash
assert "/a" in fs.trash
res = fs.empty_trash()
assert res.ok and res.r_level == 5
def test_empty_trash_when_backed_up_is_r4():
fs = MockFS(trash_enabled=True)
fs.touch("/a", b"1")
fs.snapshot("pre")
fs.rm("/a")
res = fs.empty_trash()
assert res.ok and res.r_level == 4
# ─────────────────────────────────────────────────────────────────────────────
# Isolation: the mock FS must not touch the real disk
# ─────────────────────────────────────────────────────────────────────────────
def test_mock_fs_never_touches_real_disk(tmp_path):
"""Belt-and-suspenders isolation check.
Patch the stdlib entry points that could mutate the real FS and assert
they are never called during a full exercise of the mock FS. This is the
test a security-minded judge or reviewer will want to see.
"""
cwd_before = pathlib.Path.cwd()
entries_before = set(os.listdir(cwd_before))
with patch("os.remove") as mock_rm, patch("os.rmdir") as mock_rmdir, patch(
"shutil.rmtree"
) as mock_rmtree:
fs = MockFS(trash_enabled=False)
fs.mkdir("/app")
fs.touch("/app/main.py", b"print('hi')")
fs.touch("/app/config.yml", b"debug: true")
fs.snapshot("v1")
fs.rm("/app/config.yml")
fs.rm_rf("/app")
assert mock_rm.call_count == 0, "os.remove was called by MockFS"
assert mock_rmdir.call_count == 0, "os.rmdir was called by MockFS"
assert mock_rmtree.call_count == 0, "shutil.rmtree was called by MockFS"
entries_after = set(os.listdir(cwd_before))
assert entries_before == entries_after, "real cwd was modified"