File size: 4,561 Bytes
b1603b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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")