Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add core:io test suite #4112

Merged
merged 36 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
981a2e1
Add missing `io.Stream_Mode` responses
Feoramund Aug 19, 2024
d66486c
Make `io.Section_Reader` set `base` too
Feoramund Aug 19, 2024
da49f7e
Make `bytes.reader_init` return an `io.Stream`
Feoramund Aug 19, 2024
8251f4d
Return `.EOF` in `bytes.buffer_read_at` instead
Feoramund Aug 19, 2024
521e47f
Don't invalidate `prev_rune` if `Reader` seek failed
Feoramund Aug 19, 2024
7c6cc81
Add `Seek` behavior to `bytes.Buffer`
Feoramund Aug 19, 2024
e83b982
Measure `bytes.Buffer` size by `length` instead of `capacity`
Feoramund Aug 19, 2024
1ced76c
Fix broken cases of `Seek` usage in `_file_stream_proc`
Feoramund Aug 19, 2024
5c8c63a
Fix FreeBSD implementations of `read_at` and `write_at`
Feoramund Aug 19, 2024
861d892
Make Windows `pread` and `pwrite` conform to POSIX
Feoramund Aug 19, 2024
7061032
Fix NetBSD implementations of `read_at` and `write_at`
Feoramund Aug 19, 2024
e1555e0
Fix Haiku implementations of `read_at` and `write_at`
Feoramund Aug 19, 2024
6c48068
Fix indentation
Feoramund Aug 19, 2024
d60f404
Fix OpenBSD implementations of `read_at` and `write_at`
Feoramund Aug 19, 2024
eb92a2d
Fix indentation
Feoramund Aug 19, 2024
741ccd7
Zero `n` on error in `_file_stream_proc`
Feoramund Aug 19, 2024
5b9e9fb
Add test suite for `core:io`
Feoramund Aug 19, 2024
ef2cd9d
Copy missing errors in `os2._get_platform_error` from `posix` to `linux`
Feoramund Aug 22, 2024
7683c1f
Report `Invalid_Whence` in `os2` POSIX seek
Feoramund Aug 22, 2024
3ec4db2
Report `Invalid_Whence` in `os2` Linux seek
Feoramund Aug 22, 2024
0243647
Add missing `flush` functionality to `os` platforms
Feoramund Aug 26, 2024
6aedb26
Report `Invalid_Whence` on some `os` platforms
Feoramund Aug 26, 2024
6798d15
Check `int(abs)` instead to avoid overflows
Feoramund Aug 22, 2024
8b40be5
Update `core:io` tests
Feoramund Aug 26, 2024
e7d6e2d
Add documentation to `os2.close`
Feoramund Aug 26, 2024
de1432b
Fix Windows infinite recursion with `os2._flush`
Feoramund Aug 26, 2024
56f232e
Report invalid whence & offset on `os2` Windows
Feoramund Aug 26, 2024
1cd5cbb
Test `io` unexpected pointer movement
Feoramund Aug 26, 2024
ef99373
Fix `pread` and `pwrite` on `os2` Windows
Feoramund Aug 26, 2024
f453054
Return `0, nil` in all `io` cases where an empty slice is provided
Feoramund Aug 27, 2024
b11fa90
Test empty slice usage in `io` procs
Feoramund Aug 27, 2024
24a53c2
Make sure `seek` succeeds in `io.Limited_Reader` test setup
Feoramund Aug 27, 2024
7fa2e56
Add `io` tests for `bufio` streams
Feoramund Aug 27, 2024
0f2ad95
Fix EOF detection is os2 window read
laytan Aug 28, 2024
fe2d256
Properly close the temporary files in io tests
laytan Aug 28, 2024
cca3852
Remove double close
laytan Aug 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions core/bytes/buffer.odin
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) {
}

buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) {
if len(p) == 0 {
return 0, nil
}
b.last_read = .Invalid
if offset < 0 {
err = .Invalid_Offset
Expand Down Expand Up @@ -246,10 +249,13 @@ buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.
}

buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
if len(p) == 0 {
return 0, nil
}
b.last_read = .Invalid

if uint(offset) >= len(b.buf) {
err = .Invalid_Offset
err = .EOF
return
}
n = copy(p, b.buf[offset:])
Expand Down Expand Up @@ -310,6 +316,27 @@ buffer_unread_rune :: proc(b: ^Buffer) -> io.Error {
return nil
}

buffer_seek :: proc(b: ^Buffer, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
abs: i64
switch whence {
case .Start:
abs = offset
case .Current:
abs = i64(b.off) + offset
case .End:
abs = i64(len(b.buf)) + offset
case:
return 0, .Invalid_Whence
}

abs_int := int(abs)
if abs_int < 0 {
return 0, .Invalid_Offset
}
b.last_read = .Invalid
b.off = abs_int
return abs, nil
}

buffer_read_bytes :: proc(b: ^Buffer, delim: byte) -> (line: []byte, err: io.Error) {
i := index_byte(b.buf[b.off:], delim)
Expand Down Expand Up @@ -395,14 +422,17 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
return io._i64_err(buffer_write(b, p))
case .Write_At:
return io._i64_err(buffer_write_at(b, p, int(offset)))
case .Seek:
n, err = buffer_seek(b, offset, whence)
return
case .Size:
n = i64(buffer_capacity(b))
n = i64(buffer_length(b))
return
case .Destroy:
buffer_destroy(b)
return
case .Query:
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy})
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query})
}
return 0, .Empty
}
11 changes: 9 additions & 2 deletions core/bytes/reader.odin
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ Reader :: struct {
prev_rune: int, // previous reading index of rune or < 0
}

reader_init :: proc(r: ^Reader, s: []byte) {
reader_init :: proc(r: ^Reader, s: []byte) -> io.Stream {
r.s = s
r.i = 0
r.prev_rune = -1
return reader_to_stream(r)
}

reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
Expand All @@ -33,6 +34,9 @@ reader_size :: proc(r: ^Reader) -> i64 {
}

reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
if len(p) == 0 {
return 0, nil
}
if r.i >= i64(len(r.s)) {
return 0, .EOF
}
Expand All @@ -42,6 +46,9 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
return
}
reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) {
if len(p) == 0 {
return 0, nil
}
if off < 0 {
return 0, .Invalid_Offset
}
Expand Down Expand Up @@ -97,7 +104,6 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error {
return nil
}
reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
r.prev_rune = -1
abs: i64
switch whence {
case .Start:
Expand All @@ -114,6 +120,7 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E
return 0, .Invalid_Offset
}
r.i = abs
r.prev_rune = -1
return abs, nil
}
reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
Expand Down
2 changes: 1 addition & 1 deletion core/c/libc/stdio.odin
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ to_stream :: proc(file: ^FILE) -> io.Stream {
return 0, .Empty

case .Query:
return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size })
return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query })
}
return
}
Expand Down
14 changes: 12 additions & 2 deletions core/io/util.odin
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ _limited_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte,
l := (^Limited_Reader)(stream_data)
#partial switch mode {
case .Read:
if len(p) == 0 {
return 0, nil
}
if l.n <= 0 {
return 0, .EOF
}
Expand Down Expand Up @@ -376,11 +379,12 @@ Section_Reader :: struct {
limit: i64,
}

section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) {
section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) -> Reader {
s.r = r
s.base = off
s.off = off
s.limit = off + n
return
return section_reader_to_stream(s)
}
section_reader_to_stream :: proc(s: ^Section_Reader) -> (out: Stream) {
out.data = s
Expand All @@ -393,6 +397,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte,
s := (^Section_Reader)(stream_data)
#partial switch mode {
case .Read:
if len(p) == 0 {
return 0, nil
}
if s.off >= s.limit {
return 0, .EOF
}
Expand All @@ -404,6 +411,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte,
s.off += i64(n)
return
case .Read_At:
if len(p) == 0 {
return 0, nil
}
p, off := p, offset

if off < 0 || off >= s.limit - s.base {
Expand Down
30 changes: 10 additions & 20 deletions core/os/file_windows.odin
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
case 0: w = win32.FILE_BEGIN
case 1: w = win32.FILE_CURRENT
case 2: w = win32.FILE_END
case:
return 0, .Invalid_Whence
}
hi := i32(offset>>32)
lo := i32(offset)
Expand Down Expand Up @@ -223,11 +225,13 @@ file_size :: proc(fd: Handle) -> (i64, Error) {
MAX_RW :: 1<<30

@(private)
pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
curr_off := seek(fd, 0, 1) or_return
defer seek(fd, curr_off, 0)
Feoramund marked this conversation as resolved.
Show resolved Hide resolved

buf := data
if len(buf) > MAX_RW {
buf = buf[:MAX_RW]

}

o := win32.OVERLAPPED{
Expand All @@ -247,11 +251,13 @@ pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
return int(done), e
}
@(private)
pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
curr_off := seek(fd, 0, 1) or_return
defer seek(fd, curr_off, 0)

buf := data
if len(buf) > MAX_RW {
buf = buf[:MAX_RW]

}

o := win32.OVERLAPPED{
Expand All @@ -271,13 +277,6 @@ pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {

/*
read_at returns n: 0, err: 0 on EOF
on Windows, read_at changes the position of the file cursor, on *nix, it does not.

bytes: [8]u8{}
read_at(fd, bytes, 0)
read(fd, bytes)

will read from the location twice on *nix, and from two different locations on Windows
*/
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
if offset < 0 {
Expand All @@ -302,15 +301,6 @@ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
return
}

/*
on Windows, write_at changes the position of the file cursor, on *nix, it does not.

bytes: [8]u8{}
write_at(fd, bytes, 0)
write(fd, bytes)

will write to the location twice on *nix, and to two different locations on Windows
*/
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
if offset < 0 {
return 0, .Invalid_Offset
Expand Down
8 changes: 8 additions & 0 deletions core/os/os2/errors_linux.odin
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ _get_platform_error :: proc(errno: linux.Errno) -> Error {
return .Exist
case .ENOENT:
return .Not_Exist
case .ETIMEDOUT:
return .Timeout
case .EPIPE:
return .Broken_Pipe
case .EBADF:
return .Invalid_File
case .ENOMEM:
return .Out_Of_Memory
}

return Platform_Error(i32(errno))
Expand Down
3 changes: 3 additions & 0 deletions core/os/os2/errors_windows.odin
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ _get_platform_error :: proc() -> Error {
case win32.ERROR_INVALID_HANDLE:
return .Invalid_File

case win32.ERROR_NEGATIVE_SEEK:
return .Invalid_Offset

case
win32.ERROR_BAD_ARGUMENTS,
win32.ERROR_INVALID_PARAMETER,
Expand Down
6 changes: 6 additions & 0 deletions core/os/os2/file.odin
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ name :: proc(f: ^File) -> string {
return _name(f)
}

/*
Close a file and its stream.

Any further use of the file or its stream should be considered to be in the
same class of bugs as a use-after-free.
*/
close :: proc(f: ^File) -> Error {
if f != nil {
return io.close(f.stream)
Expand Down
22 changes: 20 additions & 2 deletions core/os/os2/file_linux.odin
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,23 @@ _name :: proc(f: ^File) -> string {
}

_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
// We have to handle this here, because Linux returns EINVAL for both
// invalid offsets and invalid whences.
switch whence {
case .Start, .Current, .End:
break
case:
return 0, .Invalid_Whence
}
n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence))
if errno != .NONE {
#partial switch errno {
case .EINVAL:
return 0, .Invalid_Offset
case .NONE:
return n, nil
case:
return -1, _get_platform_error(errno)
}
return n, nil
}

_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
Expand All @@ -189,6 +201,9 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
}

_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
if len(p) == 0 {
return 0, nil
}
if offset < 0 {
return 0, .Invalid_Offset
}
Expand All @@ -214,6 +229,9 @@ _write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
}

_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
if len(p) == 0 {
return 0, nil
}
if offset < 0 {
return 0, .Invalid_Offset
}
Expand Down
17 changes: 15 additions & 2 deletions core/os/os2/file_posix.odin
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,22 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
#assert(int(posix.Whence.CUR) == int(io.Seek_From.Current))
#assert(int(posix.Whence.END) == int(io.Seek_From.End))

switch whence {
case .Start, .Current, .End:
break
case:
err = .Invalid_Whence
return
}

n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence)))
if n < 0 {
err = .Unknown
#partial switch posix.get_errno() {
case .EINVAL:
err = .Invalid_Offset
case:
err = .Unknown
}
}
return

Expand All @@ -446,7 +459,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
return

case .Query:
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query})
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})

case:
return 0, .Empty
Expand Down
Loading