started working on a self-hosted testing script
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 2 Dec 2021 20:06:12 +0000 (14:06 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 2 Dec 2021 20:06:12 +0000 (14:06 -0600)
core/io/process.onyx
core/io/reader.onyx
core/runtime/onyx_run.onyx
core/string.onyx
core/wasi/wasi.onyx
misc/onyx.sublime-syntax
scripts/run_tests.onyx [new file with mode: 0644]
src/wasm_runtime.c

index bfa055e5ce13ad7fc11f72899461b59d00078f3c..e1e7dfda53f3ae6036e7a6809b280df67313d1a1 100644 (file)
@@ -37,6 +37,10 @@ process_wait :: (use p: ^Process) => {
     return runtime.__process_wait(process_handle);
 }
 
+process_destroy :: (use p: ^Process) => {
+    runtime.__process_destroy(process_handle);
+}
+
 #local process_stream_vtable := Stream_Vtable.{
     read = (use p: ^Process, buffer: [] u8) -> (Error, u32) {
         // Read from the process stdout
index 7d38953d22710e6773a8720bc984817d2bf8b09a..3eccde393a06043aa80d92b8c8269f067fa0a6ea 100644 (file)
@@ -23,6 +23,7 @@ reader_make :: (s: ^Stream, buffer_size := 4096, allocator := context.allocator)
 
     reader: Reader;
     reader.stream = s;
+    reader.error = .None;
 
     memory.alloc_slice(^reader.buffer, buffer_size, allocator);
     reader.buffer_allocator = allocator;
@@ -60,6 +61,29 @@ reader_empty :: (use reader: ^Reader) -> bool {
     return done && reader_get_buffered(reader) == 0;
 }
 
+read_all :: (use reader: ^Reader, allocator := context.allocator) -> [] u8 {
+    array :: package core.array
+    output := array.make(u8, 128, allocator=allocator);
+
+    while !reader_empty(reader) {
+        if err := reader_read_next_chunk(reader); err != .None do break;
+        if error != .None {
+            reader_consume_error(reader);
+            break;
+        }
+
+        buffered := reader_get_buffered(reader);
+        if buffered > 0 {
+            array.ensure_capacity(^output, output.count + buffered);
+            memory.copy(output.data + output.count, buffer.data, buffered);
+            output.count += buffered;
+            start = end;
+        }
+    }
+
+    return output;
+}
+
 read_byte :: (use reader: ^Reader) -> u8 {
     while start == end {
         if error != .None {
index f18a3847dd3cad6068edf58e020ad6fe9792d5d8..48ab62fb92b85a61efa733d9436555237e758ef8 100644 (file)
@@ -25,8 +25,9 @@ use package wasi
     Error       :: 0x02;
 }
 
-__process_spawn :: (path: str, args: [] str, non_blocking_io: bool) -> io.Process.Handle #foreign "env" "process_spawn" ---
-__process_read  :: (handle: io.Process.Handle, buffer: [] u8) -> u32 #foreign "env" "process_read" ---
-__process_write :: (handle: io.Process.Handle, buffer: [] u8) -> u32 #foreign "env" "process_write" ---
-__process_kill  :: (handle: io.Process.Handle) -> bool #foreign "env" "process_kill" ---
-__process_wait  :: (handle: io.Process.Handle) -> ProcessResult #foreign "env" "process_wait" ---
\ No newline at end of file
+__process_spawn   :: (path: str, args: [] str, non_blocking_io: bool) -> io.Process.Handle #foreign "env" "process_spawn" ---
+__process_read    :: (handle: io.Process.Handle, buffer: [] u8) -> u32 #foreign "env" "process_read" ---
+__process_write   :: (handle: io.Process.Handle, buffer: [] u8) -> u32 #foreign "env" "process_write" ---
+__process_kill    :: (handle: io.Process.Handle) -> bool #foreign "env" "process_kill" ---
+__process_wait    :: (handle: io.Process.Handle) -> ProcessResult #foreign "env" "process_wait" ---
+__process_destroy :: (handle: io.Process.Handle) -> void #foreign "env" "process_destroy" ---
\ No newline at end of file
index e7d907e38032a534d6b7b4310ab347cda4e5f6cf..1673b601724b84d1658ea31b651c5fc9d78a4956 100644 (file)
@@ -72,6 +72,20 @@ concat :: #match {
         return str.{ data, total_length };
     },
 
+    (buffer: [] u8, strings: ..str) -> str {
+        total_copied := 0;
+        for s: strings {
+            // Should never greater than, but better safe than sorry.
+            if total_copied >= buffer.count do break;
+
+            bytes_to_copy := math.min(s.count, buffer.count - total_copied);
+            memory.copy(buffer.data + total_copied, s.data, bytes_to_copy);
+            total_copied += bytes_to_copy;
+        }
+
+        return buffer[0 .. total_copied];
+    },
+
     (into: ^[..] u8, s: str) -> str {
         array.ensure_capacity(into, into.count + s.count);
         memory.copy(into.data, s.data, into.count);
index 4a873baa5b791a0e6e167f0944919783a83b8b02..e0ecea6f5f4171e1748d2e1e8d7c690e5535e27e 100644 (file)
@@ -54,7 +54,7 @@ Errno :: enum (u16) {
     NetReset     :: 0x27;
     NetUnreach   :: 0x28;
     NFile        :: 0x29;
-    NoBufs       :: 0x2a;   
+    NoBufs       :: 0x2a;
     NoDev        :: 0x2b;
     NoEntry      :: 0x2c;
     NoExec       :: 0x2d;
@@ -152,7 +152,7 @@ Filetype :: enum (u8) {
     Directory    :: 0x03;
     RegularFile  :: 0x04;
     SocketDgram  :: 0x05;
-    SocketStream :: 0x06;   
+    SocketStream :: 0x06;
     SymLink      :: 0x07;
 }
 
@@ -164,7 +164,7 @@ DirEnt :: struct {
 }
 
 Advice :: enum (u8) {
-    Normal     :: 0x00; 
+    Normal     :: 0x00;
     Sequential :: 0x01;
     Random     :: 0x02;
     WillNeed   :: 0x03;
index 9f0233b29acb3e9d132d5a604a6b06ebd98ac457..735c96e17effca62080526c486c2390d9494702b 100644 (file)
@@ -38,7 +38,7 @@ contexts:
     - match: '\b(i8x16|i16x8|i32x4|i64x2|f32x4|f64x2|v128)\b'
       scope: storage.type
 
-    - match: '\b(true|false|null|null_proc|context)\b'
+    - match: '\b(true|false|null|null_proc|context|it)\b'
       scope: constant.numeric.onyx
 
     # Numbers
diff --git a/scripts/run_tests.onyx b/scripts/run_tests.onyx
new file mode 100644 (file)
index 0000000..077c3dc
--- /dev/null
@@ -0,0 +1,112 @@
+#load "core/std"
+
+use package core
+#local 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 };
+}
+
+@Temporary
+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);
+        }
+    }
+
+    return;
+}
+
+main :: (args) => {
+    test_folder := "./tests";
+
+    cases := array.make(str);
+    find_onyx_files(test_folder, ^cases);
+    for cases {
+        printf("Running test {}...\n", it);
+
+        proc := io.process_spawn("./bin/onyx", .["run", it]);
+        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
+            println(exit);
+            println(output);
+        }
+    }
+
+    // Ensure the corresponding correct output file present
+    // Run tests, checking their output
+}
\ No newline at end of file
index 9ca7425755fb5e2b45e12d31b6001752dc1787f5..3b327837c0be39dc1bc5d24b6a1a1dbb0a07d21a 100644 (file)
@@ -206,6 +206,7 @@ WASM_INTEROP(onyx_process_spawn_impl) {
     process_path[process_len] = '\0';
 
     OnyxProcess *process = bh_alloc_item(global_heap_allocator, OnyxProcess);
+    memset(process, 0, sizeof(*process));
     process->magic_number = ONYX_PROCESS_MAGIC_NUMBER;
 
     #ifdef _BH_LINUX
@@ -231,13 +232,19 @@ WASM_INTEROP(onyx_process_spawn_impl) {
         switch (pid = fork()) {
             case -1: // Bad fork
                 wasm_val_init_ptr(&results->data[0], NULL); // Failed to run
+
+                close(process->proc_to_host[0]);
+                close(process->proc_to_host[1]);
+                close(process->host_to_proc[0]);
+                close(process->host_to_proc[1]);
                 break;
 
             case 0: // Child process
                 close(process->proc_to_host[0]);
                 close(process->host_to_proc[1]);
-                dup2(process->proc_to_host[1], 1); // Map the output to the pipe
                 dup2(process->host_to_proc[0], 0); // Map the output to the pipe
+                dup2(process->proc_to_host[1], 1); // Map the output to the pipe
+                dup2(process->proc_to_host[1], 2); // Stderr to stdout
 
                 if (!blocking_io) {
                     fcntl(0, F_SETFL, O_NONBLOCK);
@@ -245,9 +252,9 @@ WASM_INTEROP(onyx_process_spawn_impl) {
                 }
 
                 execv(process_path, process_args);
-                wasm_val_init_ptr(&results->data[0], NULL);
+                exit(-1);
                 break;
-            
+
             default: {
                 process->pid = pid;
                 close(process->host_to_proc[0]);
@@ -265,7 +272,7 @@ WASM_INTEROP(onyx_process_spawn_impl) {
         char cmdLine[2048];
         memset(cmdLine, 0, 2048);
         strncat(cmdLine, process_path, 2047);
-        
+
         byte_t* data = wasm_memory_data(wasm_memory);
         byte_t* array_loc = data + args_ptr;
         fori (i, 0, args_len) {
@@ -363,6 +370,8 @@ WASM_INTEROP(onyx_process_wait_impl) {
     #ifdef _BH_LINUX
         i32 status;
         waitpid(process->pid, &status, 0);
+        close(process->proc_to_host[0]);
+        close(process->host_to_proc[1]);
 
         i32 exit_code = WEXITSTATUS(status);
         results->data[0] = WASM_I32_VAL(exit_code != 0 ? 2 : 0);
@@ -371,6 +380,22 @@ WASM_INTEROP(onyx_process_wait_impl) {
     return NULL;
 }
 
+WASM_INTEROP(onyx_process_destroy_impl) {
+    OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64;
+    if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) {
+        return NULL;
+    }
+
+    #ifdef _BH_LINUX
+        kill(process->pid, SIGKILL);
+        close(process->proc_to_host[0]);
+        close(process->host_to_proc[1]);
+        bh_free(global_heap_allocator, process);
+    #endif
+
+    return NULL;
+}
+
 void onyx_run_wasm(bh_buffer wasm_bytes) {
     wasm_instance_t* instance = NULL;
     wasmer_features_t* features = NULL;
@@ -523,6 +548,14 @@ void onyx_run_wasm(bh_buffer wasm_bytes) {
                 import = wasm_func_as_extern(wasm_func);
                 goto import_found;
             }
+
+            if (wasm_name_equals_string(import_name, "process_destroy")) {
+                wasm_functype_t* func_type = wasm_functype_new_1_0(wasm_valtype_new_i64());
+
+                wasm_func_t* wasm_func = wasm_func_new(wasm_store, func_type, onyx_process_destroy_impl);
+                import = wasm_func_as_extern(wasm_func);
+                goto import_found;
+            }
         }
 
         goto bad_import;