From 5b9e9fb822ee45f64322d0f10a406d567e6563a8 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 04:50:48 -0400 Subject: [PATCH] Add test suite for `core:io` --- tests/core/io/test_core_io.odin | 549 ++++++++++++++++++++++++++++++++ tests/core/normal.odin | 1 + 2 files changed, 550 insertions(+) create mode 100644 tests/core/io/test_core_io.odin diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin new file mode 100644 index 000000000..1d5df3adb --- /dev/null +++ b/tests/core/io/test_core_io.odin @@ -0,0 +1,549 @@ +package test_core_io + +import "core:bytes" +import "core:io" +import "core:log" +import "core:os" +import "core:strings" +import "core:testing" + +Passed_Tests :: distinct io.Stream_Mode_Set + +_test_stream :: proc( + t: ^testing.T, + stream: io.Stream, + buffer: []u8, + + reading_consumes: bool = false, + resets_on_empty: bool = false, + do_destroy: bool = true, + + loc := #caller_location +) -> (passed: Passed_Tests, ok: bool) { + // We only test what the stream reports to support. + + mode_set := io.query(stream) + + // Can't feature-test anything if Query isn't supported. + testing.expectf(t, .Query in mode_set, "stream does not support .Query: %v", mode_set, loc = loc) or_return + + passed += { .Query } + + size := i64(len(buffer)) + + // Do some basic Seek sanity testing. + if .Seek in mode_set { + pos, err := io.seek(stream, 0, io.Seek_From(-1)) + testing.expectf(t, pos == 0 && err == .Invalid_Whence, + "Seek(-1) didn't fail with Invalid_Whence: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && err == nil, + "Seek Start isn't 0: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Current) + testing.expectf(t, pos == 0 && err == nil, + "Seek Current isn't 0 at the start: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, -1, .Start) + testing.expectf(t, pos == 0 && err == .Invalid_Offset, + "Seek Start-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, -1, .Current) + testing.expectf(t, pos == 0 && err == .Invalid_Offset, + "Seek Current-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .End) + testing.expectf(t, pos == size && err == nil, + "Seek End+0 failed: %v != size<%i>, %v", pos, size, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Current) + testing.expectf(t, pos == size && err == nil, + "Seek Current isn't size<%v> at the End: %v, %v", size, pos, err, loc = loc) or_return + + // Seeking past the End is accepted throughout the API. + // + // It's _reading_ past the End which is erroneous. + pos, err = io.seek(stream, 1, .End) + testing.expectf(t, pos == size+1 && err == nil, + "Seek End+1 failed: %v, %v", pos, err, loc = loc) or_return + + // Reset our position for future tests. + pos, err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && err == nil, + "Seek Start reset failed: %v, %v", pos, err, loc = loc) or_return + + passed += { .Seek } + } + + // Test Size. + if .Size in mode_set { + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Size reports %v for its size; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Size expected no error: %v", size_err, loc = loc) or_return + + passed += { .Size } + } + + // Test Read_At. + if .Read_At in mode_set { + read_buf, alloc_err := make([]u8, size) + testing.expect_value(t, alloc_err, nil, loc = loc) or_return + defer delete(read_buf) + + for start in 0.. != buffer<%v>", read_buf, buffer, loc = loc) or_return + } + } + + // Test empty streams and EOF. + one_buf: [1]u8 + bytes_read, err := io.read_at(stream, one_buf[:], size) + testing.expectf(t, bytes_read == 0 && err == .EOF, + "Read_At at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return + + // Make sure size is still sane. + if .Size in mode_set { + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Read_At+Size reports %v for its size after Read_At tests; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read_At+Size expected no error: %v", size_err, loc = loc) or_return + } + + passed += { .Read_At } + } + + // Test Read. + if .Read in mode_set { + if size > 0 { + read_buf, alloc_err := make([]u8, size) + testing.expectf(t, alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, err := io.read(stream, read_buf[:1]) + testing.expectf(t, bytes_read == 1 && err == nil, + "Read 1 byte at start failed: %v, %v", bytes_read, err, loc = loc) or_return + testing.expectf(t, read_buf[0] == buffer[0], + "Read of first byte failed: read_buf[0]<%v> != buffer[0]<%v>", read_buf[0], buffer[0], loc = loc) or_return + + // Test rolling back the stream one byte then reading it again. + if .Seek in mode_set { + pos, seek_err := io.seek(stream, -1, .Current) + testing.expectf(t, pos == 0 && err == nil, + "Read+Seek Current-1 reset to 0 failed: %v, %v", pos, seek_err, loc = loc) or_return + + bytes_read, err = io.read(stream, read_buf[:1]) + testing.expectf(t, bytes_read == 1 && err == nil, + "Read 1 byte at start after Seek reset failed: %v, %v", bytes_read, err, loc = loc) or_return + testing.expectf(t, read_buf[0] == buffer[0] , + "re-Read of first byte failed: read_buf[0]<%v> != buffer[0]<%v>", read_buf[0], buffer[0], loc = loc) or_return + } + + // Make sure size is still sane. + if .Size in mode_set { + api_size, size_err := io.size(stream) + expected_api_size := size - 1 if reading_consumes else size + + testing.expectf(t, api_size == expected_api_size, + "Read+Size reports %v for its size after Read tests; expected %v", api_size, expected_api_size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read+Size expected no error: %v", size_err, loc = loc) or_return + } + + // Read the rest. + if size > 1 { + bytes_read, err = io.read(stream, read_buf[1:]) + testing.expectf(t, i64(bytes_read) == size - 1 && err == nil, + "Read rest of stream failed: %v != %v, %v", bytes_read, size-1, err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, buffer) == 0, + "Read buffer compare failed: read_buf<%v> != buffer<%v>", read_buf, buffer, loc = loc) or_return + } + } + + // Test empty streams and EOF. + one_buf: [1]u8 + bytes_read, err := io.read(stream, one_buf[:]) + testing.expectf(t, bytes_read == 0 && err == .EOF, + "Read at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return + + if !resets_on_empty && .Size in mode_set { + // Make sure size is still sane. + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Read+Size reports %v for its size after Read tests; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read+Size expected no error: %v", size_err, loc = loc) or_return + } + + passed += { .Read } + } + + // Test Write_At. + if .Write_At in mode_set { + if size > 0 { + write_buf, write_buf_alloc_err := make([]u8, size) + testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(write_buf) + + for i in 0.. != size<%v>: %v", bytes_written, size, write_err, loc = loc) or_return + + // Test reading what we've written. + if .Read_At in mode_set { + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + bytes_read, read_err := io.read_at(stream, read_buf[:], 0) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write_At+Read_At failed: bytes_read<%i> != size<%i>, %v", bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write_At+Read_At buffer compare failed: write_buf<%v> != read_buf<%v>", write_buf, read_buf, loc = loc) or_return + } + } else { + // Expect that it should be okay to write a single byte to an empty stream. + x_buf: [1]u8 = { 'Z' } + + bytes_written, write_err := io.write_at(stream, x_buf[:], 0) + testing.expectf(t, i64(bytes_written) == 1 && write_err == nil, + "Write_At(0) with 'Z' on empty stream failed: bytes_written<%v>, %v", bytes_written, write_err, loc = loc) or_return + + // Test reading what we've written. + if .Read_At in mode_set { + x_buf[0] = 0 + bytes_read, read_err := io.read_at(stream, x_buf[:], 0) + testing.expectf(t, i64(bytes_read) == 1 && read_err == nil, + "Write_At(0)+Read_At(0) failed expectation: bytes_read<%v> != 1, %q != 'Z', %v", bytes_read, x_buf[0], read_err, loc = loc) or_return + } + } + + passed += { .Write_At } + } + + // Test Write. + if .Write in mode_set { + write_buf, write_buf_alloc_err := make([]u8, size) + testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(write_buf) + + for i in 0.., %v", pos, seek_err) or_return + } + + // Get the Size before writing. + if .Size in mode_set { + size_err: io.Error + before_write_size, size_err = io.size(stream) + testing.expectf(t, size_err == nil, + "Write+Size failed: %v", size_err, loc = loc) or_return + } + + bytes_written, write_err := io.write(stream, write_buf[:]) + testing.expectf(t, i64(bytes_written) == size && write_err == nil, + "Write %i bytes failed: %i, %v", size, bytes_written, write_err, loc = loc) or_return + + // Size sanity check, part 2. + if before_write_size >= 0 && .Size in mode_set { + after_write_size, size_err := io.size(stream) + testing.expectf(t, size_err == nil, + "Write+Size.part_2 failed: %v", size_err, loc = loc) or_return + testing.expectf(t, after_write_size == before_write_size + size, + "Write+Size.part_2 failed: %v != %v + %v", after_write_size, before_write_size, size, loc = loc) or_return + } + + // Test reading what we've written directly with Read_At. + if pos >= 0 && .Read_At in mode_set { + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, read_err := io.read_at(stream, read_buf[:], pos) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write+Read_At(%i) failed: bytes_read<%i> != size<%i>, %v", pos, bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write+Read_At buffer compare failed: read_buf<%v> != write_buf<%v>", read_buf, write_buf, loc = loc) or_return + } + + // Test resetting the pointer and reading what we've written with Read. + if .Read in mode_set && .Seek in mode_set { + seek_err: io.Error + pos, seek_err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && seek_err == nil, + "Write+Read+Seek(Start) failed: pos<%i>, %v", pos, seek_err) or_return + + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, read_err := io.read(stream, read_buf[:]) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write+Read failed: bytes_read<%i> != size<%i>, %v", bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write+Readbuffer compare failed: read_buf<%v> != write_buf<%v>", read_buf, write_buf, loc = loc) or_return + } + + passed += { .Write } + } + + // Test the other modes. + if .Flush in mode_set { + err := io.flush(stream) + testing.expectf(t, err == nil, "stream failed to Flush: %v", err, loc = loc) or_return + passed += { .Flush } + } + + if .Close in mode_set { + close_err := io.close(stream) + testing.expectf(t, close_err == nil, "stream failed to Close: %v", close_err, loc = loc) or_return + passed += { .Close } + + // TODO(Feoramund): The OS<->IO interface will need updating for more + // specific error messages. + // + // This test will catch it when they start using them, but so long as + // they actually error, it should be okay for now. + // + // .EOF would be a better message for all of these. + + if .Seek in mode_set { + pos, err := io.seek(stream, 0, .Start) + + testing.expectf(t, pos == 0 && err == .Invalid_Offset, + "Seek after Close unexpected output: pos<%v>, %v", pos, err, loc = loc) or_return + } + + if .Read in mode_set { + eof_buf: [1]u8 + bytes_read, err := io.read(stream, eof_buf[:]) + testing.expectf(t, bytes_read == 0 && err == .Unknown, + "Read after Close unexpected output: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + + if size > 0 && .Read_At in mode_set { + eof_buf: [1]u8 + bytes_read, err := io.read_at(stream, eof_buf[:], 0) + testing.expectf(t, bytes_read == 0 && err == .Unknown, + "Read_At after Close unexpected output: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + + if .Write in mode_set { + eof_buf: [1]u8 = {'Z'} + bytes_written, err := io.write(stream, eof_buf[:]) + testing.expectf(t, bytes_written == 0 && err == .Unknown, + "Write after Close unexpected output: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + + if .Write_At in mode_set { + eof_buf: [1]u8 = {'Z'} + bytes_written, err := io.write_at(stream, eof_buf[:], 0) + testing.expectf(t, bytes_written == 0 && err == .Unknown, + "Write after Close unexpected output: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + } + + if do_destroy && .Destroy in mode_set { + err := io.destroy(stream) + testing.expectf(t, err == nil, "stream failed to Destroy: %v", err, loc = loc) or_return + passed += { .Destroy } + } + + ok = true + return +} + + + +@test +test_bytes_reader :: proc(t: ^testing.T) { + buf: [32]u8 + for i in 0..\n!=\nbuffer <%q>", sb.buf[:], expected_buf[:]) + + log.debugf("%#v", results) +} + +@test +test_os_file_stream :: proc(t: ^testing.T) { + defer if !testing.failed(t) { + testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) + } + + buf: [32]u8 + for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { + return + } + + flush_err := io.flush(stream) + if !testing.expectf(t, flush_err == nil, + "failed to Flush initial buffer: %v", write_err) { + return + } + + results, _ := _test_stream(t, stream, buf[:]) + + log.debugf("%#v", results) +} diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 510d66d1a..6174f2d5c 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -23,6 +23,7 @@ download_assets :: proc() { @(require) import "encoding/xml" @(require) import "flags" @(require) import "fmt" +@(require) import "io" @(require) import "math" @(require) import "math/big" @(require) import "math/linalg/glsl"