1 | import os |
2 | |
3 | const tfolder = os.join_path(os.vtmp_dir(), 'v', 'tests', 'os_file_test') |
4 | |
5 | const tfile = os.join_path_single(tfolder, 'test_file') |
6 | |
7 | fn testsuite_begin() { |
8 | os.rmdir_all(tfolder) or {} |
9 | assert !os.is_dir(tfolder) |
10 | os.mkdir_all(tfolder)! |
11 | os.chdir(tfolder)! |
12 | assert os.is_dir(tfolder) |
13 | } |
14 | |
15 | fn testsuite_end() { |
16 | os.rmdir_all(tfolder) or {} |
17 | } |
18 | |
19 | struct Point { |
20 | x f64 |
21 | y f64 |
22 | z f64 |
23 | } |
24 | |
25 | struct Extended_Point { |
26 | a f64 |
27 | b f64 |
28 | c f64 |
29 | d f64 |
30 | e f64 |
31 | f f64 |
32 | g f64 |
33 | h f64 |
34 | i f64 |
35 | } |
36 | |
37 | enum Color { |
38 | red |
39 | green |
40 | blue |
41 | } |
42 | |
43 | [flag] |
44 | enum Permissions { |
45 | read |
46 | write |
47 | execute |
48 | } |
49 | |
50 | const ( |
51 | unit_point = Point{1.0, 1.0, 1.0} |
52 | another_point = Point{0.25, 2.25, 6.25} |
53 | extended_point = Extended_Point{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} |
54 | another_byte = u8(123) |
55 | another_color = Color.red |
56 | another_permission = Permissions.read | .write |
57 | ) |
58 | |
59 | // test_read_bytes_into_newline_text tests reading text from a file with newlines. |
60 | // This test simulates reading a larger text file step by step into a buffer and |
61 | // returning on each newline, even before the buffer is full, and reaching EOF before |
62 | // the buffer is completely filled. |
63 | fn test_read_bytes_into_newline_text() { |
64 | mut f := os.open_file(tfile, 'w')! |
65 | f.write_string('Hello World!\nGood\r morning.')! |
66 | f.close() |
67 | |
68 | f = os.open_file(tfile, 'r')! |
69 | mut buf := []u8{len: 8} |
70 | |
71 | n0 := f.read_bytes_into_newline(mut buf)! |
72 | assert n0 == 8 |
73 | |
74 | n1 := f.read_bytes_into_newline(mut buf)! |
75 | assert n1 == 5 |
76 | |
77 | n2 := f.read_bytes_into_newline(mut buf)! |
78 | assert n2 == 8 |
79 | |
80 | n3 := f.read_bytes_into_newline(mut buf)! |
81 | assert n3 == 6 |
82 | |
83 | f.close() |
84 | } |
85 | |
86 | // test_read_bytes_into_newline_binary tests reading a binary file with NUL bytes. |
87 | // This test simulates the scenario when a byte stream is read and a newline byte |
88 | // appears in that stream and an EOF occurs before the buffer is full. |
89 | fn test_read_bytes_into_newline_binary() { |
90 | os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w' |
91 | mut bw := []u8{len: 15} |
92 | bw[9] = 0xff |
93 | bw[12] = 10 // newline |
94 | |
95 | n0_bytes := bw[0..10] |
96 | n1_bytes := bw[10..13] |
97 | n2_bytes := bw[13..] |
98 | |
99 | mut f := os.open_file(tfile, 'w')! |
100 | f.write(bw)! |
101 | f.close() |
102 | |
103 | f = os.open_file(tfile, 'r')! |
104 | mut buf := []u8{len: 10} |
105 | |
106 | n0 := f.read_bytes_into_newline(mut buf)! |
107 | assert n0 == 10 |
108 | assert buf[..n0] == n0_bytes |
109 | |
110 | n1 := f.read_bytes_into_newline(mut buf)! |
111 | assert n1 == 3 |
112 | assert buf[..n1] == n1_bytes |
113 | |
114 | n2 := f.read_bytes_into_newline(mut buf)! |
115 | assert n2 == 2 |
116 | assert buf[..n2] == n2_bytes |
117 | f.close() |
118 | } |
119 | |
120 | // test_read_eof_last_read_partial_buffer_fill tests that when reading a file |
121 | // the end-of-file is detected and results in a none error being returned. This |
122 | // test simulates file reading where the end-of-file is reached inside an fread |
123 | // containing data. |
124 | fn test_read_eof_last_read_partial_buffer_fill() { |
125 | mut f := os.open_file(tfile, 'w')! |
126 | bw := []u8{len: 199, init: 5} |
127 | f.write(bw)! |
128 | f.close() |
129 | |
130 | f = os.open_file(tfile, 'r')! |
131 | mut br := []u8{len: 100} |
132 | // Read first 100 bytes of 199 byte file, should fill buffer with no error. |
133 | n0 := f.read(mut br)! |
134 | assert n0 == 100 |
135 | // Read remaining 99 bytes of 199 byte file, should fill buffer with no |
136 | // error, even though end-of-file was reached. |
137 | n1 := f.read(mut br)! |
138 | assert n1 == 99 |
139 | // Read again, end-of-file was previously reached so should return none |
140 | // error. |
141 | if _ := f.read(mut br) { |
142 | // This is not intended behavior because the read function should |
143 | // not return a number of bytes read when end-of-file is reached. |
144 | assert false |
145 | } else { |
146 | // Expected an error when received end-of-file. |
147 | assert err is os.Eof |
148 | } |
149 | f.close() |
150 | } |
151 | |
152 | // test_read_eof_last_read_full_buffer_fill tests that when reading a file the |
153 | // end-of-file is detected and results in a none error being returned. This test |
154 | // simulates file reading where the end-of-file is reached at the beinning of an |
155 | // fread that returns no data. |
156 | fn test_read_eof_last_read_full_buffer_fill() { |
157 | mut f := os.open_file(tfile, 'w')! |
158 | bw := []u8{len: 200, init: 5} |
159 | f.write(bw)! |
160 | f.close() |
161 | |
162 | f = os.open_file(tfile, 'r')! |
163 | mut br := []u8{len: 100} |
164 | // Read first 100 bytes of 200 byte file, should fill buffer with no error. |
165 | n0 := f.read(mut br)! |
166 | assert n0 == 100 |
167 | // Read remaining 100 bytes of 200 byte file, should fill buffer with no |
168 | // error. The end-of-file isn't reached yet, but there is no more data. |
169 | n1 := f.read(mut br)! |
170 | assert n1 == 100 |
171 | // Read again, end-of-file was previously reached so should return none |
172 | // error. |
173 | if _ := f.read(mut br) { |
174 | // This is not intended behavior because the read function should |
175 | // not return a number of bytes read when end-of-file is reached. |
176 | assert false |
177 | } else { |
178 | // Expect an error at EOF. |
179 | assert err is os.Eof |
180 | } |
181 | f.close() |
182 | } |
183 | |
184 | fn test_write_struct() { |
185 | os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w' |
186 | size_of_point := int(sizeof(Point)) |
187 | mut f := os.open_file(tfile, 'w')! |
188 | f.write_struct(another_point)! |
189 | f.close() |
190 | x := os.read_file(tfile)! |
191 | pcopy := unsafe { &u8(memdup(&another_point, size_of_point)) } |
192 | y := unsafe { pcopy.vstring_with_len(size_of_point) } |
193 | assert x == y |
194 | $if debug { |
195 | eprintln(x.bytes()) |
196 | eprintln(y.bytes()) |
197 | } |
198 | } |
199 | |
200 | fn test_write_struct_at() { |
201 | mut f := os.open_file(tfile, 'w')! |
202 | f.write_struct(extended_point)! |
203 | f.write_struct_at(another_point, 3)! |
204 | f.close() |
205 | f = os.open_file(tfile, 'r')! |
206 | mut p := Point{} |
207 | f.read_struct_at(mut p, 3)! |
208 | f.close() |
209 | |
210 | assert p == another_point |
211 | } |
212 | |
213 | fn test_read_struct() { |
214 | mut f := os.open_file(tfile, 'w')! |
215 | f.write_struct(another_point)! |
216 | f.close() |
217 | |
218 | f = os.open_file(tfile, 'r')! |
219 | mut p := Point{} |
220 | f.read_struct(mut p)! |
221 | f.close() |
222 | |
223 | assert p == another_point |
224 | } |
225 | |
226 | fn test_read_struct_at() { |
227 | mut f := os.open_file(tfile, 'w')! |
228 | f.write([u8(1), 2, 3])! |
229 | f.write_struct(another_point)! |
230 | f.close() |
231 | f = os.open_file(tfile, 'r')! |
232 | mut p := Point{} |
233 | f.read_struct_at(mut p, 3)! |
234 | f.close() |
235 | |
236 | assert p == another_point |
237 | } |
238 | |
239 | fn test_write_raw() { |
240 | os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w' |
241 | size_of_point := int(sizeof(Point)) |
242 | mut f := os.open_file(tfile, 'w')! |
243 | f.write_raw(another_point)! |
244 | f.close() |
245 | x := os.read_file(tfile)! |
246 | pcopy := unsafe { &u8(memdup(&another_point, size_of_point)) } |
247 | y := unsafe { pcopy.vstring_with_len(size_of_point) } |
248 | assert x == y |
249 | $if debug { |
250 | eprintln(x.bytes()) |
251 | eprintln(y.bytes()) |
252 | } |
253 | } |
254 | |
255 | fn test_write_raw_at() { |
256 | mut f := os.open_file(tfile, 'w')! |
257 | f.write_raw(extended_point)! |
258 | f.write_raw_at(another_point, 3)! |
259 | f.close() |
260 | f = os.open_file(tfile, 'r')! |
261 | mut p := Point{} |
262 | f.read_struct_at(mut p, 3)! |
263 | f.close() |
264 | |
265 | assert p == another_point |
266 | } |
267 | |
268 | fn test_write_raw_at_negative_pos() { |
269 | mut f := os.open_file(tfile, 'w')! |
270 | if _ := f.write_raw_at(another_point, u64(-1)) { |
271 | assert false |
272 | } |
273 | f.write_raw_at(another_point, u64(-1)) or { assert err.msg() == 'Invalid argument' } |
274 | f.close() |
275 | } |
276 | |
277 | fn test_read_raw() { |
278 | mut f := os.open_file(tfile, 'w')! |
279 | f.write_raw(another_point)! |
280 | f.write_raw(another_byte)! |
281 | f.write_raw(another_color)! |
282 | f.write_raw(another_permission)! |
283 | f.close() |
284 | f = os.open_file(tfile, 'r')! |
285 | p := f.read_raw[Point]()! |
286 | b := f.read_raw[u8]()! |
287 | c := f.read_raw[Color]()! |
288 | x := f.read_raw[Permissions]()! |
289 | f.close() |
290 | |
291 | assert p == another_point |
292 | assert b == another_byte |
293 | assert c == another_color |
294 | assert x == another_permission |
295 | } |
296 | |
297 | fn test_read_raw_at() { |
298 | mut f := os.open_file(tfile, 'w')! |
299 | f.write([u8(1), 2, 3])! |
300 | f.write_raw(another_point)! |
301 | f.write_raw(another_byte)! |
302 | f.write_raw(another_color)! |
303 | f.write_raw(another_permission)! |
304 | f.close() |
305 | f = os.open_file(tfile, 'r')! |
306 | mut at := u64(3) |
307 | p := f.read_raw_at[Point](at)! |
308 | at += sizeof(Point) |
309 | b := f.read_raw_at[u8](at)! |
310 | at += sizeof(u8) |
311 | c := f.read_raw_at[Color](at)! |
312 | at += sizeof(Color) |
313 | x := f.read_raw_at[Permissions](at)! |
314 | at += sizeof(Permissions) |
315 | f.close() |
316 | |
317 | assert p == another_point |
318 | assert b == another_byte |
319 | assert c == another_color |
320 | assert x == another_permission |
321 | } |
322 | |
323 | fn test_read_raw_at_negative_pos() { |
324 | mut f := os.open_file(tfile, 'r')! |
325 | if _ := f.read_raw_at[Point](u64(-1)) { |
326 | assert false |
327 | } |
328 | f.read_raw_at[Point](u64(-1)) or { assert err.msg() == 'Invalid argument' } |
329 | f.close() |
330 | } |
331 | |
332 | fn test_seek() { |
333 | mut f := os.open_file(tfile, 'w')! |
334 | f.write_raw(another_point)! |
335 | f.write_raw(another_byte)! |
336 | f.write_raw(another_color)! |
337 | f.write_raw(another_permission)! |
338 | f.close() |
339 | |
340 | // println('> ${sizeof(Point)} ${sizeof(byte)} ${sizeof(Color)} ${sizeof(Permissions)}') |
341 | f = os.open_file(tfile, 'r')! |
342 | // |
343 | f.seek(i64(sizeof(Point)), .start)! |
344 | assert f.tell()! == sizeof(Point) |
345 | b := f.read_raw[u8]()! |
346 | assert b == another_byte |
347 | |
348 | f.seek(i64(sizeof(Color)), .current)! |
349 | x := f.read_raw[Permissions]()! |
350 | assert x == another_permission |
351 | // |
352 | f.close() |
353 | } |
354 | |
355 | fn test_tell() { |
356 | for size in 10 .. 30 { |
357 | s := 'x'.repeat(size) |
358 | os.write_file(tfile, s)! |
359 | fs := os.file_size(tfile) |
360 | assert int(fs) == size |
361 | // |
362 | mut f := os.open_file(tfile, 'r')! |
363 | f.seek(-5, .end)! |
364 | pos := f.tell()! |
365 | f.close() |
366 | // dump(pos) |
367 | assert pos == size - 5 |
368 | } |
369 | } |
370 | |
371 | fn test_reopen() { |
372 | tfile1 := os.join_path_single(tfolder, 'tfile1') |
373 | tfile2 := os.join_path_single(tfolder, 'tfile2') |
374 | os.write_file(tfile1, 'Hello World!\nGood\r morning.\nBye 1.')! |
375 | os.write_file(tfile2, 'Another file\nAnother line.\nBye 2.')! |
376 | assert os.file_size(tfile1) > 0 |
377 | assert os.file_size(tfile2) > 0 |
378 | |
379 | mut line_buffer := []u8{len: 1024} |
380 | |
381 | mut f2 := os.open(tfile2)! |
382 | x := f2.read_bytes_into_newline(mut line_buffer)! |
383 | assert !f2.eof() |
384 | assert x > 0 |
385 | assert line_buffer#[..x].bytestr() == 'Another file\n' |
386 | |
387 | // Note: after this call, f2 should be using the file `tfile1`: |
388 | f2.reopen(tfile1, 'r')! |
389 | assert !f2.eof() |
390 | |
391 | z := f2.read(mut line_buffer) or { panic(err) } |
392 | assert f2.eof() |
393 | assert z > 0 |
394 | content := line_buffer#[..z].bytestr() |
395 | // dump(content) |
396 | assert content.starts_with('Hello World') |
397 | assert content.ends_with('Bye 1.') |
398 | } |
399 | |
400 | fn test_eof() { |
401 | os.write_file(tfile, 'Hello World!\n')! |
402 | |
403 | mut f := os.open(tfile)! |
404 | f.read_bytes(10) |
405 | assert !f.eof() |
406 | f.read_bytes(100) |
407 | assert f.eof() |
408 | f.close() |
409 | } |
410 | |
411 | fn test_open_file_wb_ab() { |
412 | os.rm(tfile) or {} |
413 | mut wfile := os.open_file('text.txt', 'wb', 0o666)! |
414 | wfile.write_string('hello')! |
415 | wfile.close() |
416 | assert os.read_file('text.txt')! == 'hello' |
417 | // |
418 | mut afile := os.open_file('text.txt', 'ab', 0o666)! |
419 | afile.write_string('hello')! |
420 | afile.close() |
421 | assert os.read_file('text.txt')! == 'hellohello' |
422 | } |
423 | |
424 | fn test_open_append() { |
425 | os.rm(tfile) or {} |
426 | mut f1 := os.open_append(tfile)! |
427 | f1.write_string('abc\n')! |
428 | f1.close() |
429 | assert os.read_lines(tfile)! == ['abc'] |
430 | // |
431 | mut f2 := os.open_append(tfile)! |
432 | f2.write_string('abc\n')! |
433 | f2.close() |
434 | assert os.read_lines(tfile)! == ['abc', 'abc'] |
435 | // |
436 | mut f3 := os.open_append(tfile)! |
437 | f3.write_string('def\n')! |
438 | f3.close() |
439 | assert os.read_lines(tfile)! == ['abc', 'abc', 'def'] |
440 | } |
441 | |
442 | fn test_open_file_on_chinese_windows() { |
443 | $if windows { |
444 | os.rm('中文.txt') or {} |
445 | mut f1 := os.open_file('中文.txt', 'w+', 0x666) or { panic(err) } |
446 | f1.write_string('test')! |
447 | f1.close() |
448 | |
449 | assert os.read_file('中文.txt')! == 'test' |
450 | assert os.file_size('中文.txt') == 4 |
451 | |
452 | os.truncate('中文.txt', 2)! |
453 | assert os.file_size('中文.txt') == 2 |
454 | } |
455 | } |