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