compiler/tests/fixtures/runtime/volume-memory/main.ldn
1-- core/io's memory volume, the file system in memory that fakes
2-- `file_system` and with it `reader`, `writer`, `files` and
3-- `directories`, each with its failure oracles; and core/io's memory
4-- terminals. The volume's nodes and their storage are the program's,
5-- so it runs the same everywhere, firmware included.
6import core/cmp
7import core/io
8import core/io/fake
9import core/mem
10import core/text
11
12-- Something came back other than what the volume promises.
13wrong: atom
14
15-- How a call to the volume may fail.
16failure: type = io.already_exists | io.not_found | io.no_access
17 | io.io_failed
18
19-- Rooms for the nodes' paths and contents.
20mut paths: [8][32]u8 = zeroed
21mut contents: [8][32]u8 = zeroed
22
23-- A free node over the room at INDEX.
24free_node: (index: usize) -> (node: fake.volume_node) =
25 node = (path: paths[index][0..<32], path_length: 0,
26 contents: contents[index][0..<32], length: 0, kind: io.regular,
27 read_only: false, modified: (seconds: 0, nanoseconds: 0))
28end free_node
29
30-- Give the node at INDEX the path PATH, the kind KIND and the bytes
31-- BYTES, as a test sets up a tree.
32given: (inout node: fake.volume_node, path: []u8, kind: io.entry_kind,
33 bytes: []u8) -> none ! ... =
34 try mem.copy(node.path[0..<lenof path], path)
35 node.path_length = lenof path
36 try mem.copy(node.contents[0..<lenof bytes], bytes)
37 node.length = lenof bytes
38 node.kind = kind
39end given
40
41-- Whether BYTES are WANTED.
42same: (bytes: []u8, wanted: []u8) -> (yes: bool) =
43 yes = cmp.compare(bytes, wanted) == cmp.equal
44end same
45
46-- Whether opening PATH with OPTIONS fails as WANTED says.
47refused: (host: fake.volume, path: []u8, options: io.open_options,
48 wanted: failure) -> (yes: bool) =
49 yes = false
50 opened: io.file = io.open_file(host, text.host_over(path), options)
51 else (problem)
52 yes = problem == wanted
53 return
54 end
55 io.close(host, opened) else none
56end refused
57
58-- Whether renaming OLD to NEW fails as WANTED says.
59rename_refused: (host: fake.volume, old: []u8, new: []u8,
60 wanted: failure) -> (yes: bool) =
61 yes = false
62 io.rename(host, text.host_over(old), text.host_over(new)) else (problem)
63 yes = problem == wanted
64 end
65end rename_refused
66
67-- Whether the status of PATH fails as WANTED says.
68status_refused: (host: fake.volume, path: []u8, links: io.link_handling,
69 wanted: failure) -> (yes: bool) =
70 yes = false
71 found: io.status = io.status_of(host, text.host_over(path), links)
72 else (problem)
73 yes = problem == wanted
74 return
75 end
76 _ = found
77end status_refused
78
79-- The bytes of the file at PATH, read into INTO.
80read_back: (host: fake.volume, path: []u8, into: []mut u8)
81 -> (bytes: []u8 from into) ! ... =
82 opened: io.file = try io.open_read(host, text.host_over(path))
83 defer io.close(host, opened) else none
84 count: usize = try io.read(host, opened, into)
85 bytes = into[0..<count]
86end read_back
87
88-- Opening, writing and reading: a new file, a taken name, a missing
89-- directory, a path too long for any node, appending, a write past the
90-- end, a read past it and a file that fills its room.
91files: (host: fake.volume) -> none ! ... =
92 made: io.file = try io.open_file(host, text.host_over("docs/new.txt"),
93 io.creating)
94 try io.write(host, made, "abc")
95 try io.write_at(host, made, u64(6), "xyz")
96 mut buffer: [16]u8 = [16 of 255]
97 hole: usize = try io.read_at(host, made, u64(2), buffer[0..<16])
98 past: usize = try io.read_at(host, made, u64(9), buffer[8..<16])
99 fail wrong when hole <> 7 or past <> 0
100 or not same(buffer[0..<7], "c\x00\x00\x00xyz")
101 try io.close(host, made)
102 -- A file that fills its node's room: the write that finds no room is
103 -- refused after what fitted.
104 filling: io.file = try io.open_file(host, text.host_over("docs/full"),
105 io.creating)
106 full: [40]u8 = [40 of 65]
107 mut met: bool = false
108 io.write(host, filling, full[0..<40]) else (problem)
109 met = problem == io.io_failed
110 end
111 filled: io.status = try io.status_of_file(host, filling)
112 try io.close(host, filling)
113 try io.remove_file(host, text.host_over("docs/full"))
114 fail wrong when not met or filled.size <> 32
115 fail wrong when not refused(host, "docs/new.txt", io.creating,
116 io.already_exists)
117 or not refused(host, "nowhere/new.txt", io.creating, io.not_found)
118 or not refused(host, "docs/a-name-far-longer-than-any-node-room", io.creating,
119 io.io_failed)
120 or not refused(host, "docs/missing", io.updating, io.not_found)
121 log: io.file = try io.open_file(host, text.host_over("docs/readme"),
122 io.appending)
123 try io.write(host, log, "!")
124 try io.close(host, log)
125 fail wrong when not same(try read_back(host, "docs/readme",
126 buffer[0..<16]), "hello\n!")
127end files
128
129-- A link's status followed and not, a dangling link, and a read-only file
130-- refusing an open for writing until it is writable again.
131statuses: (host: fake.volume) -> none ! ... =
132 followed: io.status = try io.status_of(host, text.host_over("pointer"),
133 io.following)
134 itself: io.status = try io.status_of(host, text.host_over("pointer"),
135 io.not_following)
136 fail wrong when followed.kind <> io.regular or followed.size <> 7
137 or itself.kind <> io.symbolic_link or itself.size <> 11
138 fail wrong when not status_refused(host, "dangling", io.following, io.not_found)
139 or not status_refused(host, "absent", io.not_following, io.not_found)
140 root: io.status = try io.status_of(host, text.host_over("."), io.not_following)
141 fail wrong when root.kind <> io.subdirectory
142 try io.set_read_only(host, text.host_over("docs/readme"), true)
143 locked: io.status = try io.status_of(host, text.host_over("docs/readme"),
144 io.not_following)
145 fail wrong when not locked.read_only
146 or not refused(host, "docs/readme", io.updating, io.no_access)
147 try io.set_read_only(host, text.host_over("docs/readme"), false)
148 opened: io.file = try io.open_file(host, text.host_over("docs/readme"),
149 io.updating)
150 try io.truncate(host, opened, u64(2))
151 try io.truncate(host, opened, u64(4))
152 try io.synchronize(host, opened, io.data_and_status)
153 cut: io.status = try io.status_of_file(host, opened)
154 try io.close(host, opened)
155 fail wrong when cut.size <> 4
156end statuses
157
158-- Renaming: onto a taken name, which it replaces; a directory, which
159-- takes what is in it along; a directory into itself; a missing name;
160-- and a name a file is open on.
161renames: (host: fake.volume) -> none ! ... =
162 mut buffer: [16]u8 = zeroed
163 try io.rename(host, text.host_over("docs/new.txt"),
164 text.host_over("docs/readme"))
165 fail wrong when not same(try read_back(host, "docs/readme",
166 buffer[0..<16]), "abc\x00\x00\x00xyz")
167 try io.rename(host, text.host_over("docs"), text.host_over("notes"))
168 moved: io.status = try io.status_of(host, text.host_over("notes/readme"),
169 io.not_following)
170 fail wrong when moved.size <> 9
171 fail wrong when not rename_refused(host, "notes", "notes/inner",
172 io.io_failed)
173 or not rename_refused(host, "absent", "notes/x", io.not_found)
174 opened: io.file = try io.open_read(host, text.host_over("notes/readme"))
175 fail wrong when not rename_refused(host, "pointer", "notes/readme",
176 io.io_failed)
177 try io.close(host, opened)
178end renames
179
180-- Listing: the root's entries and their kinds, sizes by name, and a
181-- directory that is not empty refusing removal.
182listing: (host: fake.volume) -> none ! ... =
183 mut buffer: [64]u8 = zeroed
184 mut opened: io.directory =
185 try io.open_directory(host, text.host_over("."), buffer[0..<64])
186 mut count: usize = 0
187 mut links: usize = 0
188 loop do
189 item: io.entry = try io.read_entry(host, opened)
190 name: []u8 = text.host_bytes(item.name)
191 break when lenof name == 0
192 inc count
193 if item.kind == io.symbolic_link then inc links end if
194 end loop
195 size: u64 = try io.entry_size(host, opened, text.host_over("data"))
196 try io.close_directory(host, opened)
197 fail wrong when count <> 4 or links <> 2 or size <> 7
198 mut met: bool = false
199 io.remove_directory(host, text.host_over("notes")) else (problem)
200 met = problem == io.io_failed
201 end
202 fail wrong when not met
203 try io.remove_file(host, text.host_over("notes/readme"))
204 try io.remove_directory(host, text.host_over("notes"))
205 try io.make_directory(host, text.host_over("fresh"))
206 met = false
207 io.make_directory(host, text.host_over("fresh")) else (problem)
208 met = problem == io.already_exists
209 end
210 fail wrong when not met
211end listing
212
213-- Temporary files named from scripted entropy, the second name taken and
214-- tried again, and the working directory.
215temporaries: (host: fake.volume) -> none ! ... =
216 bytes: [24]u8 = [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
217 10, 11, 12, 13, 14, 15, 16, 17]
218 mut entropy_state: fake.entropy_space = zeroed
219 names := fake.entropy_over(entropy_state, bytes[0..<24])
220 mut first_room: [40]u8 = zeroed
221 first: io.temporary = try io.make_temporary(host, names,
222 text.host_over("fresh"),
223 first_room[0..<40])
224 try io.close(host, first.opened)
225 mut second_room: [40]u8 = zeroed
226 second: io.temporary = try io.make_temporary(host, names,
227 text.host_over("fresh"),
228 second_room[0..<40])
229 try io.write(host, second.opened, "temporary")
230 try io.close(host, second.opened)
231 fail wrong when not text.host_equal(first.path,
232 "fresh/.tmp-0102030405060708")
233 or not text.host_equal(second.path, "fresh/.tmp-0a0b0c0d0e0f1011")
234 or entropy_state.requests <> 3
235 mut room: [16]u8 = zeroed
236 here: text.host_string = try io.current_directory(host, room[0..<16])
237 fail wrong when not text.host_equal(here, "/volume")
238end temporaries
239
240-- The call the test chose fails, and the next succeeds.
241injected: (host: fake.volume) -> none ! ... =
242 fail wrong when not status_refused(host, "data", io.following, io.io_failed)
243 again: io.status = try io.status_of(host, text.host_over("data"), io.following)
244 fail wrong when again.size <> 7
245end injected
246
247-- Memory terminals: one screen is a terminal of its size, and every other
248-- file none.
249screens: () -> none ! ... =
250 given: [1]fake.screen = [(stream: io.file(1), size: (rows: 25,
251 columns: 80))]
252 host := fake.terminals_over(given[0..<1])
253 size: io.window = try io.window_size(host, io.file(1))
254 fail wrong when not io.is_terminal(host, io.file(1))
255 or io.is_terminal(host, io.file(2)) or size.columns <> 80
256 mut met: bool = false
257 other: io.window = io.window_size(host, io.file(2)) else (problem)
258 met = problem == io.not_a_terminal
259 size
260 end
261 fail wrong when not met or other.rows <> 25
262end screens
263
264public main: () -> (code: i32) =
265 code = 1
266 mut nodes: [8]fake.volume_node = [free_node(0), free_node(1), free_node(2),
267 free_node(3), free_node(4), free_node(5),
268 free_node(6), free_node(7)]
269 given(nodes[0], "docs", io.subdirectory, "") else (problem)
270 _ = problem
271 return
272 end
273 given(nodes[1], "docs/readme", io.regular, "hello\n") else (problem)
274 _ = problem
275 return
276 end
277 given(nodes[2], "data", io.regular, "numbers") else (problem)
278 _ = problem
279 return
280 end
281 given(nodes[3], "pointer", io.symbolic_link, "docs/readme") else (problem)
282 _ = problem
283 return
284 end
285 given(nodes[4], "dangling", io.symbolic_link, "absent") else (problem)
286 _ = problem
287 return
288 end
289 mut state: fake.volume_space = zeroed
290 host := fake.volume_over(state, nodes[0..<8], "/volume")
291 code = 2
292 files(host) else (problem)
293 _ = problem
294 return
295 end
296 code = 3
297 statuses(host) else (problem)
298 _ = problem
299 return
300 end
301 code = 4
302 renames(host) else (problem)
303 _ = problem
304 return
305 end
306 return when state.renames < 5
307 code = 5
308 listing(host) else (problem)
309 _ = problem
310 return
311 end
312 code = 6
313 temporaries(host) else (problem)
314 _ = problem
315 return
316 end
317 return when state.temporary_tries <> 3
318 code = 7
319 state.fail_at = state.calls + 1
320 injected(host) else (problem)
321 _ = problem
322 return
323 end
324 code = 8
325 screens() else (problem)
326 _ = problem
327 return
328 end
329 code = 42
330end main