made test runner better
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 3 Dec 2021 16:55:07 +0000 (10:55 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 3 Dec 2021 16:55:07 +0000 (10:55 -0600)
core/io/file.onyx
core/os/os.onyx [new file with mode: 0644]
core/std.onyx
scripts/run_tests.onyx
tests/aoc-2021/day03.onyx

index 5b6ad2af1140a6b8572b0a2a7f4328afc2305c25..81eaad09ba2cad11d1f9eeffd2a6c76123c6e72b 100644 (file)
@@ -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 (file)
index 0000000..c26a6d5
--- /dev/null
@@ -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 };
+}
+
index 936bfca22615414894f7627734d43dcc6256cded..ea66bc546cf52ac95e4e0ccf37dc1f2d0d01d83b 100644 (file)
@@ -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" }
index 73c88d97912ff5d5e63f5bae4da570c8cbe3d7d7..330d218c94394bef4fabbe9cc2662d0fd69623d9 100644 (file)
@@ -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
index 8779a63866577f7bc69f5b7ca12ccd2fa48832e1..ad8d46895b5c636c9d4ecf79fe34c184f356c46c 100644 (file)
@@ -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);