Landin library reference discuss source

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