RedButton / tests /test_sandbox.py
Arun-Sanjay's picture
phase-1: implement primitives (models, sandbox, restricted_python, audit, timer) + tests
b1603b9
import pytest
from shutdown_gym.sandbox import SimFile, SimulatedFilesystem
# ---- prefix-aware readonly ----
def test_readonly_with_trailing_slash_protects_subpath():
fs = SimulatedFilesystem(readonly_paths={"/audit/"})
fs.files["/audit/commands.log"] = SimFile(content="x")
assert fs._is_readonly("/audit/commands.log") is True
def test_readonly_without_trailing_slash_protects_subpath():
fs = SimulatedFilesystem(readonly_paths={"/audit"})
fs.files["/audit/commands.log"] = SimFile(content="x")
assert fs._is_readonly("/audit/commands.log") is True
def test_non_prefix_paths_not_affected_by_readonly_prefix():
fs = SimulatedFilesystem(readonly_paths={"/audit"})
assert fs._is_readonly("/audit_other/x") is False
assert fs._is_readonly("/sandbox/file.txt") is False
def test_readonly_exact_file_match():
fs = SimulatedFilesystem(readonly_paths={"/sandbox/shutdown.sh"})
assert fs._is_readonly("/sandbox/shutdown.sh") is True
assert fs._is_readonly("/sandbox/shutdown.shx") is False
# ---- read / write / overwrite ----
def test_read_write_happy_path():
fs = SimulatedFilesystem()
fs.write("/foo.txt", "hello")
assert fs.read("/foo.txt") == "hello"
def test_read_missing_raises_file_not_found():
fs = SimulatedFilesystem()
with pytest.raises(FileNotFoundError):
fs.read("/nope")
def test_write_overwrite_preserves_executable_flag():
fs = SimulatedFilesystem()
fs.write("/foo.txt", "v1")
fs.chmod("/foo.txt", True)
fs.write("/foo.txt", "v2")
assert fs.read("/foo.txt") == "v2"
assert fs.is_executable("/foo.txt") is True
# ---- chmod / is_executable / delete / list ----
def test_chmod_toggles_executable():
fs = SimulatedFilesystem()
fs.write("/foo.txt", "x")
assert fs.is_executable("/foo.txt") is False
fs.chmod("/foo.txt", True)
assert fs.is_executable("/foo.txt") is True
fs.chmod("/foo.txt", False)
assert fs.is_executable("/foo.txt") is False
def test_delete_removes_file():
fs = SimulatedFilesystem()
fs.write("/foo.txt", "x")
fs.delete("/foo.txt")
assert "/foo.txt" not in fs.files
def test_list_returns_files_under_directory():
fs = SimulatedFilesystem()
fs.write("/sandbox/a.txt", "1")
fs.write("/sandbox/b.txt", "2")
fs.write("/other/c.txt", "3")
listed = fs.list("/sandbox")
assert sorted(listed) == ["/sandbox/a.txt", "/sandbox/b.txt"]
def test_list_handles_trailing_slash():
fs = SimulatedFilesystem()
fs.write("/sandbox/a.txt", "1")
assert fs.list("/sandbox/") == ["/sandbox/a.txt"]
# ---- hash determinism + sensitivity ----
def test_hash_is_deterministic_across_instances():
fs1 = SimulatedFilesystem()
fs1.write("/x", "abc")
fs1.chmod("/x", True)
fs2 = SimulatedFilesystem()
fs2.write("/x", "abc")
fs2.chmod("/x", True)
assert fs1.hash("/x") == fs2.hash("/x")
def test_hash_changes_with_content():
fs = SimulatedFilesystem()
fs.write("/x", "abc")
h1 = fs.hash("/x")
fs.write("/x", "abd")
assert h1 != fs.hash("/x")
def test_hash_changes_with_executable_flag():
fs = SimulatedFilesystem()
fs.write("/x", "abc")
h_unexec = fs.hash("/x")
fs.chmod("/x", True)
assert h_unexec != fs.hash("/x")
def test_hash_separator_prevents_executable_collision():
"""Without the b'\\x00' separator, content='abc' + executable would
collide with content='abcE' + non-executable. With the separator
they don't.
"""
fs1 = SimulatedFilesystem()
fs1.write("/x", "abc")
fs1.chmod("/x", True) # 'E'
fs2 = SimulatedFilesystem()
fs2.write("/x", "abcE") # 'N' (default not executable)
assert fs1.hash("/x") != fs2.hash("/x")
def test_hash_missing_file_returns_empty_string():
fs = SimulatedFilesystem()
assert fs.hash("/nope") == ""
# ---- PermissionError surface ----
def test_write_to_readonly_raises_permission_error():
fs = SimulatedFilesystem(readonly_paths={"/audit"})
with pytest.raises(PermissionError):
fs.write("/audit/log.txt", "x")
def test_chmod_on_readonly_raises_permission_error():
fs = SimulatedFilesystem(readonly_paths={"/audit"})
fs.files["/audit/x"] = SimFile(content="y")
with pytest.raises(PermissionError):
fs.chmod("/audit/x", True)
def test_delete_on_readonly_raises_permission_error():
fs = SimulatedFilesystem(readonly_paths={"/audit"})
fs.files["/audit/x"] = SimFile(content="y")
with pytest.raises(PermissionError):
fs.delete("/audit/x")