From ee4a94e647acdeff8dc91263431974ebee1bbce0 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Fri, 3 Dec 2021 10:55:07 -0600 Subject: [PATCH] made test runner better --- core/io/file.onyx | 13 +++ core/os/os.onyx | 77 +++++++++++++ core/std.onyx | 1 + scripts/run_tests.onyx | 227 ++++++++++++++++++++------------------ tests/aoc-2021/day03.onyx | 11 +- 5 files changed, 216 insertions(+), 113 deletions(-) create mode 100644 core/os/os.onyx diff --git a/core/io/file.onyx b/core/io/file.onyx index 5b6ad2af..81eaad09 100644 --- a/core/io/file.onyx +++ b/core/io/file.onyx @@ -1,3 +1,4 @@ +@TODO // Move this file to the core.os package package core.io #local runtime :: package runtime @@ -163,6 +164,18 @@ get_contents :: #match { } } +file_exists :: (path: str) -> bool { + fs: wasi.FileStat; + + exists := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_filestat_get(it, .SymLinkFollow, path, ^fs); + if err == .Success do exists = true; + } + + return exists; +} + FileStream :: struct { use stream : Stream; use file : File; diff --git a/core/os/os.onyx b/core/os/os.onyx new file mode 100644 index 00000000..c26a6d59 --- /dev/null +++ b/core/os/os.onyx @@ -0,0 +1,77 @@ +package core.os + +#if runtime.Runtime != runtime.Runtime_Wasi + && runtime.Runtime != runtime.Runtime_Onyx { + #error "The os library is currently only available on the WASI runtime, and should only be included if that is the chosen runtime." +} + +#local { + runtime :: package runtime + wasi :: package wasi +} + +Directory_Entry :: struct { + dirent : wasi.DirEnt; + name : str; +} + +list_directory :: (path: str) -> Iterator(Directory_Entry) { + Context :: struct { + dir_fd: wasi.FileDescriptor; + opened := false; + reached_end := false; + + entry_walker: ^u8; + + buffer_used: u32 = 0; + buffer: [1024] u8; + + last_cookie: wasi.DirCookie = 0; + } + + next :: (use c: ^Context) -> (Directory_Entry, bool) { + use package core.intrinsics.onyx {__zero_value} + if !opened do return __zero_value(Directory_Entry), false; + + read_more :: macro () { + if !reached_end { + err := wasi.fd_readdir(dir_fd, ~~buffer, sizeof typeof buffer, last_cookie, ^buffer_used); + reached_end = buffer_used != sizeof typeof buffer; + + entry_walker = ~~buffer; + } else { + return __zero_value(Directory_Entry), false; + } + } + + if buffer_used < sizeof(wasi.DirEnt) do read_more(); + + dirent := cast(^wasi.DirEnt) entry_walker; + if buffer_used < sizeof(wasi.DirEnt) + dirent.d_namlen { + read_more(); + dirent = ~~entry_walker; + } + + name := str.{~~(dirent + 1), dirent.d_namlen}; + + entry_size := sizeof(wasi.DirEnt) + dirent.d_namlen; + entry_walker += entry_size; + buffer_used -= entry_size; + last_cookie = dirent.d_next; + + return .{*dirent, name}, true; + } + + close :: (use c: ^Context) { + wasi.fd_close(dir_fd); + cfree(c); + } + + c := new(Context); + if err := wasi.path_open(4, 0, path, .Directory, ~~0xffffffff, ~~0xffffffff, .Sync, ^c.dir_fd); err == .Success { + c.opened = true; + } + + return .{ c, next, close }; +} + diff --git a/core/std.onyx b/core/std.onyx index 936bfca2..ea66bc54 100644 --- a/core/std.onyx +++ b/core/std.onyx @@ -42,6 +42,7 @@ package core #load "./wasi/env" #load "./wasi/clock" #load "./io/file" + #load "./os/os" } #if runtime.Runtime == runtime.Runtime_Onyx { #load "./runtime/onyx_run" } diff --git a/scripts/run_tests.onyx b/scripts/run_tests.onyx index 73c88d97..330d218c 100644 --- a/scripts/run_tests.onyx +++ b/scripts/run_tests.onyx @@ -1,91 +1,15 @@ -#load "core/std" - -use package core -#local wasi :: package wasi -#local runtime :: package runtime - -Directory_Entry :: struct { - dirent : wasi.DirEnt; - name : str; -} +// To be added to make this complete: +// - Fancy display -list_directory :: (path: str) -> Iterator(Directory_Entry) { - Context :: struct { - dir_fd: wasi.FileDescriptor; - opened := false; - reached_end := false; - - entry_walker: ^u8; - - buffer_used: u32 = 0; - buffer: [1024] u8; - - last_cookie: wasi.DirCookie = 0; - } - - next :: (use c: ^Context) -> (Directory_Entry, bool) { - use package core.intrinsics.onyx {__zero_value} - if !opened do return __zero_value(Directory_Entry), false; - read_more :: macro () { - if !reached_end { - err := wasi.fd_readdir(dir_fd, ~~buffer, sizeof typeof buffer, last_cookie, ^buffer_used); - reached_end = buffer_used != sizeof typeof buffer; - entry_walker = ~~buffer; - } else { - return __zero_value(Directory_Entry), false; - } - } - - if buffer_used < sizeof(wasi.DirEnt) do read_more(); - - dirent := cast(^wasi.DirEnt) entry_walker; - if buffer_used < sizeof(wasi.DirEnt) + dirent.d_namlen { - read_more(); - dirent = ~~entry_walker; - } - - name := str.{~~(dirent + 1), dirent.d_namlen}; - - entry_size := sizeof(wasi.DirEnt) + dirent.d_namlen; - entry_walker += entry_size; - buffer_used -= entry_size; - last_cookie = dirent.d_next; - - return .{*dirent, name}, true; - } - - close :: (use c: ^Context) { - wasi.fd_close(dir_fd); - cfree(c); - } - - c := new(Context); - if err := wasi.path_open(4, 0, path, .Directory, ~~0xffffffff, ~~0xffffffff, .Sync, ^c.dir_fd); err == .Success { - c.opened = true; - } - - return .{ c, next, close }; -} - -find_onyx_files :: (root: str, cases: ^[..] str) { - for list_directory(root) { - path_buffer: [512] u8; - if string.ends_with(it.name, ".onyx") { - test_case := string.concat(path_buffer, root, "/", it.name) |> string.alloc_copy(); - array.push(cases, test_case); - } - - if it.dirent.d_type == .Directory { - find_onyx_files(string.concat(path_buffer, root, "/", it.name), cases); - } - } +#load "core/std" - return; -} +use package core +#local runtime :: package runtime #if false { + @Relocate divide_array :: (arr: [] $T, divisions: [] [] T) { if divisions.count == 0 do return; @@ -102,6 +26,7 @@ find_onyx_files :: (root: str, cases: ^[..] str) { } } +@Relocate distributor :: (arr: [] $T) -> Iterator(T) { Context :: struct (T: type_expr) { mutex: sync.Mutex; @@ -131,12 +56,102 @@ distributor :: (arr: [] $T) -> Iterator(T) { return .{ c, #solidify next {T=T}, #solidify close {T=T}}; } +Color :: enum { + White; + Red; + Green; + Yellow; + Blue; +} + +print_color :: (color: Color, format: str, args: ..any) { + buffer: [2048] u8; + output := conv.str_format_va(buffer, format, args); + + #if runtime.OS == runtime.OS_Linux { + color_code: str; + switch color { + case .Red do color_code = "\x1b[91m"; + case .Green do color_code ="\x1b[92m"; + case .Yellow do color_code = "\x1b[93m"; + case .Blue do color_code ="\x1b[94m"; + case .White do fallthrough; + case #default do color_code = "\x1b[97m"; + } + + printf("{}{}\x1b[0m", color_code, output); + __flush_stdio(); + + } else { + // No color output on Windows because most windows terminals suck. + print(output); + } +} + +Test_Case :: struct { + source_file : str; + expected_file : str; +} + +find_onyx_files :: (root: str, cases: ^[..] Test_Case) { + for os.list_directory(root) { + path_buffer: [512] u8; + if string.ends_with(it.name, ".onyx") { + test_case := string.concat(path_buffer, root, "/", it.name) |> string.alloc_copy(); + expected_file := test_case[0 .. (test_case.count - 5)]; + + if !io.file_exists(expected_file) { + print_color(.Yellow, "Skipping test case {} because an expected output file was not found.\n", test_case); + continue; + } + + array.push(cases, .{ test_case, expected_file }); + } + + if it.dirent.d_type == .Directory { + find_onyx_files(string.concat(path_buffer, root, "/", it.name), cases); + } + } + + return; +} + +test_cases :: (cases) => { + for *cases { + printf("[{}] Running test {}...\n", context.thread_id, it.source_file); + + proc := io.process_spawn(onyx_cmd, .["run", it.source_file]); + defer io.process_destroy(^proc); + + proc_reader := io.reader_make(^proc); + output := io.read_all(^proc_reader); + defer memory.free_slice(^output); + + if exit := io.process_wait(^proc); exit != .Success { + // Error running the test case + print_color(.Red, "[{}] Error compiling test case {}.\n{}", context.thread_id, it.source_file, output); + continue; + } + + for expected_file: io.with_file(it.expected_file) { + expected_reader := io.reader_make(expected_file); + expected_output := io.read_all(^expected_reader); + + if output != expected_output { + print_color(.Red, "[{}] Output did not match for {}.\n", context.thread_id, it.source_file); + } + } + } +} + +// The executable to use when compiling +onyx_cmd: str; + main :: (args) => { test_folder := "./tests"; - #persist onyx_cmd: str; switch runtime.OS { - case runtime.OS_Linux { + case runtime.OS_Linux { onyx_cmd = "./bin/onyx"; if args.count > 1 { if string.from_cstr(args[1]) == "debug" do onyx_cmd = "./bin/onyx-debug"; @@ -145,38 +160,36 @@ main :: (args) => { case runtime.OS_Windows do onyx_cmd = "onyx.exe"; } - test_cases :: (cases) => { - for *cases { - printf("[{}] Running test {}...\n", context.thread_id, it); - - proc := io.process_spawn(onyx_cmd, .["run", it]); - defer io.process_destroy(^proc); + cases := array.make(Test_Case); + find_onyx_files(test_folder, ^cases); - proc_reader := io.reader_make(^proc); - output := io.read_all(^proc_reader); - defer memory.free_slice(^output); + thread_count := 3; + for args { + arg := string.from_cstr(it); + if string.starts_with(arg, "--threads=") { + string.read_until(^arg, #char "="); - if exit := io.process_wait(^proc); exit != .Success { - // Error running the test case - println(exit); - println(output); - } + thread_count = ~~ conv.str_to_i64(arg[1 .. arg.count]); } } - cases := array.make(str); - find_onyx_files(test_folder, ^cases); + case_iterator := distributor(cases); - Thread_Count :: 3 - #persist threads: [Thread_Count] thread.Thread; + if thread_count > 0 { + // Stack allocate this in the future? + threads := memory.make_slice(thread.Thread, thread_count); - case_iterator := distributor(cases); + for thread_count { + thread.spawn(^threads[it], ^case_iterator, test_cases); + } + + test_cases(^case_iterator); + + for ^ threads do thread.join(it); - for Thread_Count { - thread.spawn(^threads[it], ^case_iterator, test_cases); + } else { + test_cases(^case_iterator); } - test_cases(^case_iterator); - for ^ threads do thread.join(it); println("Done"); } \ No newline at end of file diff --git a/tests/aoc-2021/day03.onyx b/tests/aoc-2021/day03.onyx index 8779a638..ad8d4689 100644 --- a/tests/aoc-2021/day03.onyx +++ b/tests/aoc-2021/day03.onyx @@ -34,16 +34,15 @@ main :: (args) => { nums << read_binary(^reader); } - ones_count: [BITS] i32; // Every digit is 12-bits + num1 := 0; for BITS { - ones_count[it] = 0; + one_count := 0; for num: nums { - if num & (1 << it) != 0 do ones_count[it] += 1; + if num & (1 << it) != 0 do one_count += 1; } - } - num1 := 0; - for BITS do num1 |= (1 << it) if ones_count[it] >= (nums.count / 2) else 0; + if one_count >= (nums.count / 2) do num1 |= (1 << it); + } num2 := ((1 << BITS) - 1) & (~num1); -- 2.25.1