aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2026-01-22 11:47:23 +0100
committerGitHub <noreply@github.com>2026-01-22 11:47:23 +0100
commit3f8a32aeb963e056ac7709c26f05a99fb884ef95 (patch)
tree4eed35fa23e9f4c5b7dedc6a355b16f5d3119661 /tests
parentfb479b3aaec5a9a715bcff7d65498ba18020f4f0 (diff)
parent5c09550d383df49a1d29d8f66bc1c45ee3be1136 (diff)
Merge branch 'master' into xmlcomment
Diffstat (limited to 'tests')
-rw-r--r--tests/core/container/test_core_rbtree.odin34
-rw-r--r--tests/core/encoding/base64/base64.odin17
-rw-r--r--tests/core/encoding/xml/test_core_xml.odin2
-rw-r--r--tests/core/nbio/fs.odin100
-rw-r--r--tests/core/nbio/nbio.odin258
-rw-r--r--tests/core/nbio/net.odin400
-rw-r--r--tests/core/nbio/remove.odin247
-rw-r--r--tests/core/net/test_core_net.odin2
-rw-r--r--tests/core/normal.odin2
-rw-r--r--tests/core/sys/kqueue/structs.odin56
-rw-r--r--tests/core/sys/kqueue/structs/structs.c63
-rw-r--r--tests/core/sys/kqueue/structs/structs.odin58
-rw-r--r--tests/internal/test_imported_proc_groups.odin11
-rw-r--r--tests/internal/test_imported_proc_groups/proc_group.odin4
14 files changed, 1228 insertions, 26 deletions
diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin
index d220b7ed6..78d710b7f 100644
--- a/tests/core/container/test_core_rbtree.odin
+++ b/tests/core/container/test_core_rbtree.odin
@@ -4,22 +4,15 @@ import rb "core:container/rbtree"
import "core:math/rand"
import "core:testing"
import "base:intrinsics"
-import "core:mem"
import "core:slice"
import "core:log"
test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
- track: mem.Tracking_Allocator
- mem.tracking_allocator_init(&track, context.allocator)
- track.bad_free_callback = mem.tracking_allocator_bad_free_callback_add_to_array
- defer mem.tracking_allocator_destroy(&track)
- context.allocator = mem.tracking_allocator(&track)
-
log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed)
tree: rb.Tree(Key, Value)
rb.init(&tree)
- testing.expect(t, rb.len(&tree) == 0, "empty: len should be 0")
+ testing.expect(t, rb.len(tree) == 0, "empty: len should be 0")
testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil")
testing.expect(t, rb.last(&tree) == nil, "empty: last should be nil")
iter := rb.iterator(&tree, .Forward)
@@ -48,7 +41,7 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
}
entry_count := len(inserted_map)
- testing.expect(t, rb.len(&tree) == entry_count, "insert: len after")
+ testing.expect(t, rb.len(tree) == entry_count, "insert: len after")
validate_rbtree(t, &tree)
first := rb.first(&tree)
@@ -58,8 +51,8 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
// Ensure that all entries can be found.
for k, v in inserted_map {
- testing.expect(t, v == rb.find(&tree, k), "Find(): Node")
- testing.expect(t, k == v.key, "Find(): Node key")
+ testing.expect(t, v == rb.find(tree, k), "Find(): Node")
+ testing.expect(t, k == v.key, "Find(): Node key")
}
// Test the forward/backward iterators.
@@ -97,17 +90,17 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
(^int)(user_data)^ -= 1
}
for k, i in inserted_keys {
- node := rb.find(&tree, k)
+ node := rb.find(tree, k)
testing.expect(t, node != nil, "remove: find (pre)")
ok := rb.remove(&tree, k)
testing.expect(t, ok, "remove: succeeds")
- testing.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)")
+ testing.expect(t, entry_count - (i + 1) == rb.len(tree), "remove: len (post)")
validate_rbtree(t, &tree)
- testing.expect(t, nil == rb.find(&tree, k), "remove: find (post")
+ testing.expect(t, nil == rb.find(tree, k), "remove: find (post")
}
- testing.expect(t, rb.len(&tree) == 0, "remove: len should be 0")
+ testing.expect(t, rb.len(tree) == 0, "remove: len should be 0")
testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)
testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil")
testing.expect(t, rb.last(&tree) == nil, "remove: last should be nil")
@@ -129,28 +122,25 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
ok = rb.iterator_remove(&iter)
testing.expect(t, !ok, "iterator/remove: redundant removes should fail")
- testing.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone")
+ testing.expect(t, rb.find(tree, k) == nil, "iterator/remove: node should be gone")
testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil")
// Ensure that iterator_next still works.
node, ok = rb.iterator_next(&iter)
- testing.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false")
+ testing.expect(t, ok == (rb.len(tree) > 0), "iterator/remove: next should return false")
testing.expect(t, node == rb.first(&tree), "iterator/remove: next should return first")
validate_rbtree(t, &tree)
}
- testing.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1")
+ testing.expect(t, rb.len(tree) == entry_count - 1, "iterator/remove: len should drop by 1")
rb.destroy(&tree)
- testing.expect(t, rb.len(&tree) == 0, "destroy: len should be 0")
+ testing.expect(t, rb.len(tree) == 0, "destroy: len should be 0")
testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)
// print_tree_node(tree._root)
delete(inserted_map)
delete(inserted_keys)
- testing.expectf(t, len(track.allocation_map) == 0, "Expected 0 leaks, have %v", len(track.allocation_map))
- testing.expectf(t, len(track.bad_free_array) == 0, "Expected 0 bad frees, have %v", len(track.bad_free_array))
- return
}
@(test)
diff --git a/tests/core/encoding/base64/base64.odin b/tests/core/encoding/base64/base64.odin
index ed1bee8af..93b3afb59 100644
--- a/tests/core/encoding/base64/base64.odin
+++ b/tests/core/encoding/base64/base64.odin
@@ -50,4 +50,19 @@ test_roundtrip :: proc(t: ^testing.T) {
for v, i in decoded {
testing.expect_value(t, v, values[i])
}
-} \ No newline at end of file
+}
+
+@(test)
+test_base64url :: proc(t: ^testing.T) {
+ plain := ">>>"
+ url := "Pj4-"
+
+ encoded := base64.encode(transmute([]byte)plain, base64.ENC_URL_TABLE)
+ defer delete(encoded)
+ testing.expect_value(t, encoded, url)
+
+ decoded := string(base64.decode(url, base64.DEC_URL_TABLE))
+ defer delete(decoded)
+ testing.expect_value(t, decoded, plain)
+
+}
diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin
index 23d583e98..9f233e00e 100644
--- a/tests/core/encoding/xml/test_core_xml.odin
+++ b/tests/core/encoding/xml/test_core_xml.odin
@@ -142,7 +142,7 @@ xml_test_entities_unbox_decode :: proc(t: ^testing.T) {
},
expected_doctype = "html",
},
- crc32 = 0x3c0973e2,
+ crc32 = 0x6e45a697,
})
}
diff --git a/tests/core/nbio/fs.odin b/tests/core/nbio/fs.odin
new file mode 100644
index 000000000..6e079f96e
--- /dev/null
+++ b/tests/core/nbio/fs.odin
@@ -0,0 +1,100 @@
+package tests_nbio
+
+import "core:nbio"
+import "core:testing"
+import "core:time"
+import os "core:os/os2"
+
+@(test)
+close_invalid_handle :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ nbio.close(max(nbio.Handle))
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+write_read_close :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ @static content := [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
+ @static result: [20]byte
+
+ FILENAME :: "test_write_read_close"
+
+ nbio.open_poly(FILENAME, t, on_open, mode={.Read, .Write, .Create, .Trunc})
+
+ on_open :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.open.err, nil)
+
+ nbio.write_poly(op.open.handle, 0, content[:], t, on_write)
+ }
+
+ on_write :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.write.err, nil)
+ ev(t, op.write.written, len(content))
+
+ nbio.read_poly(op.write.handle, 0, result[:], t, on_read, all=true)
+ }
+
+ on_read :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.read.err, nil)
+ ev(t, op.read.read, len(result))
+ ev(t, result, content)
+
+ nbio.close_poly(op.read.handle, t, on_close)
+ }
+
+ on_close :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.close.err, nil)
+ os.remove(FILENAME)
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+read_empty_file :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ FILENAME :: "test_read_empty_file"
+
+ handle, err := nbio.open_sync(FILENAME, mode={.Read, .Write, .Create, .Trunc})
+ ev(t, err, nil)
+
+ buf: [128]byte
+ nbio.read_poly(handle, 0, buf[:], t, proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.read.err, nbio.FS_Error.EOF)
+ ev(t, op.read.read, 0)
+
+ nbio.close_poly(op.read.handle, t, proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.close.err, nil)
+ os.remove(FILENAME)
+ })
+ })
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+read_entire_file :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ nbio.read_entire_file(#file, t, on_read)
+
+ on_read :: proc(t: rawptr, data: []byte, err: nbio.Read_Entire_File_Error) {
+ t := (^testing.T)(t)
+ ev(t, err.value, nil)
+ ev(t, string(data), #load(#file, string))
+ delete(data)
+ }
+ }
+}
diff --git a/tests/core/nbio/nbio.odin b/tests/core/nbio/nbio.odin
new file mode 100644
index 000000000..2f454f55b
--- /dev/null
+++ b/tests/core/nbio/nbio.odin
@@ -0,0 +1,258 @@
+package tests_nbio
+
+import "core:log"
+import "core:nbio"
+import "core:testing"
+import "core:thread"
+import "core:time"
+import os "core:os/os2"
+
+ev :: testing.expect_value
+e :: testing.expect
+
+@(deferred_in=event_loop_guard_exit)
+event_loop_guard :: proc(t: ^testing.T) -> bool {
+ err := nbio.acquire_thread_event_loop()
+ if err == .Unsupported || !nbio.FULLY_SUPPORTED {
+ log.warn("nbio unsupported, skipping")
+ return false
+ }
+
+ ev(t, err, nil)
+ return true
+}
+
+event_loop_guard_exit :: proc(t: ^testing.T) {
+ ev(t, nbio.run(), nil) // Could have some things to clean up from a `defer` in the test.
+ nbio.release_thread_event_loop()
+}
+
+// Tests that all poly variants are correctly passing through arguments, and that
+// all procs eventually get their callback called.
+//
+// This is important because the poly procs are only checked when they are called,
+// So this will also catch any typos in their implementations.
+@(test)
+all_poly_work :: proc(tt: ^testing.T) {
+ if event_loop_guard(tt) {
+ testing.set_fail_timeout(tt, time.Minute)
+
+ @static t: ^testing.T
+ t = tt
+
+ @static n: int
+ n = 0
+ NUM_TESTS :: 39
+
+ UDP_SOCKET :: max(nbio.UDP_Socket)
+ TCP_SOCKET :: max(nbio.TCP_Socket)
+
+ tmp, terr := os.create_temp_file("", "tests_nbio_poly*", {.Non_Blocking})
+ ev(t, terr, nil)
+ defer os.close(tmp)
+
+ HANDLE, aerr := nbio.associate_handle(os.fd(tmp))
+ ev(t, aerr, nil)
+
+ _buf: [1]byte
+ buf := _buf[:]
+
+ one :: proc(op: ^nbio.Operation, one: int) {
+ n += 1
+ ev(t, one, 1)
+ }
+
+ two :: proc(op: ^nbio.Operation, one: int, two: int) {
+ n += 1
+ ev(t, one, 1)
+ ev(t, two, 2)
+ }
+
+ three :: proc(op: ^nbio.Operation, one: int, two: int, three: int) {
+ n += 1
+ ev(t, one, 1)
+ ev(t, two, 2)
+ ev(t, three, 3)
+ }
+
+ nbio.accept_poly(TCP_SOCKET, 1, one)
+ nbio.accept_poly2(TCP_SOCKET, 1, 2, two)
+ nbio.accept_poly3(TCP_SOCKET, 1, 2, 3, three)
+
+ nbio.close_poly(max(nbio.Handle), 1, one)
+ nbio.close_poly2(max(nbio.Handle), 1, 2, two)
+ nbio.close_poly3(max(nbio.Handle), 1, 2, 3, three)
+
+ nbio.dial_poly({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, one)
+ nbio.dial_poly2({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, 2, two)
+ nbio.dial_poly3({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, 2, 3, three)
+
+ nbio.recv_poly(TCP_SOCKET, {buf}, 1, one)
+ nbio.recv_poly2(TCP_SOCKET, {buf}, 1, 2, two)
+ nbio.recv_poly3(TCP_SOCKET, {buf}, 1, 2, 3, three)
+
+ nbio.send_poly(TCP_SOCKET, {buf}, 1, one)
+ nbio.send_poly2(TCP_SOCKET, {buf}, 1, 2, two)
+ nbio.send_poly3(TCP_SOCKET, {buf}, 1, 2, 3, three)
+
+ nbio.sendfile_poly(TCP_SOCKET, HANDLE, 1, one)
+ nbio.sendfile_poly2(TCP_SOCKET, HANDLE, 1, 2, two)
+ nbio.sendfile_poly3(TCP_SOCKET, HANDLE, 1, 2, 3, three)
+
+ nbio.read_poly(HANDLE, 0, buf, 1, one)
+ nbio.read_poly2(HANDLE, 0, buf, 1, 2, two)
+ nbio.read_poly3(HANDLE, 0, buf, 1, 2, 3, three)
+
+ nbio.write_poly(HANDLE, 0, buf, 1, one)
+ nbio.write_poly2(HANDLE, 0, buf, 1, 2, two)
+ nbio.write_poly3(HANDLE, 0, buf, 1, 2, 3, three)
+
+ nbio.next_tick_poly(1, one)
+ nbio.next_tick_poly2(1, 2, two)
+ nbio.next_tick_poly3(1, 2, 3, three)
+
+ nbio.timeout_poly(1, 1, one)
+ nbio.timeout_poly2(1, 1, 2, two)
+ nbio.timeout_poly3(1, 1, 2, 3, three)
+
+ nbio.poll_poly(TCP_SOCKET, .Receive, 1, one)
+ nbio.poll_poly2(TCP_SOCKET, .Receive, 1, 2, two)
+ nbio.poll_poly3(TCP_SOCKET, .Receive, 1, 2, 3, three)
+
+ nbio.open_poly("", 1, one)
+ nbio.open_poly2("", 1, 2, two)
+ nbio.open_poly3("", 1, 2, 3, three)
+
+ nbio.stat_poly(HANDLE, 1, one)
+ nbio.stat_poly2(HANDLE, 1, 2, two)
+ nbio.stat_poly3(HANDLE, 1, 2, 3, three)
+
+ ev(t, n, 0) // Test that no callbacks are ran before the loop is ticked.
+ ev(t, nbio.run(), nil)
+ ev(t, n, NUM_TESTS) // Test that all callbacks have ran.
+ }
+}
+
+@(test)
+two_ops_at_the_same_time :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ server, err := nbio.create_udp_socket(.IP4)
+ ev(t, err, nil)
+ defer nbio.close(server)
+
+ berr := nbio.bind(server, {nbio.IP4_Loopback, 0})
+ ev(t, berr, nil)
+ ep, eperr := nbio.bound_endpoint(server)
+ ev(t, eperr, nil)
+
+ // Server.
+ {
+ nbio.poll_poly(server, .Receive, t, on_poll)
+
+ on_poll :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.poll.result, nbio.Poll_Result.Ready)
+ }
+
+ buf: [128]byte
+ nbio.recv_poly(server, {buf[:]}, t, on_recv)
+
+ on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.recv.err, nil)
+ }
+ }
+
+ // Client.
+ {
+ sock, cerr := nbio.create_udp_socket(.IP4)
+ ev(t, cerr, nil)
+
+ // Make sure the server would block.
+ nbio.timeout_poly3(time.Millisecond*10, t, sock, ep.port, on_timeout)
+
+ on_timeout :: proc(op: ^nbio.Operation, t: ^testing.T, sock: nbio.UDP_Socket, port: int) {
+ nbio.send_poly(sock, {transmute([]byte)string("Hiya")}, t, on_send, {nbio.IP4_Loopback, port})
+ }
+
+ on_send :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.send.err, nil)
+ ev(t, op.send.sent, 4)
+
+ // Do another send after a bit, some backends don't trigger both ops when one was enough to
+ // use up the socket.
+ nbio.timeout_poly3(time.Millisecond*10, t, op.send.socket.(nbio.UDP_Socket), op.send.endpoint.port, on_timeout2)
+ }
+
+ on_timeout2 :: proc(op: ^nbio.Operation, t: ^testing.T, sock: nbio.UDP_Socket, port: int) {
+ nbio.send_poly(sock, {transmute([]byte)string("Hiya")}, t, on_send2, {nbio.IP4_Loopback, port})
+ }
+
+ on_send2 :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.send.err, nil)
+ ev(t, op.send.sent, 4)
+
+ nbio.close(op.send.socket.(nbio.UDP_Socket))
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+timeout :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ start := time.now()
+
+ nbio.timeout_poly2(time.Millisecond*20, t, start, on_timeout)
+
+ on_timeout :: proc(op: ^nbio.Operation, t: ^testing.T, start: time.Time) {
+ since := time.since(start)
+ log.infof("timeout ran after: %v", since)
+ testing.expect(t, since >= time.Millisecond*19) // A ms grace, for some reason it is sometimes ran after 19.8ms.
+ if since < 20 {
+ log.warnf("timeout ran after: %v", since)
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+wake_up :: proc(t: ^testing.T) {
+ testing.set_fail_timeout(t, time.Minute)
+ if event_loop_guard(t) {
+ for _ in 0..<2 {
+ sock, _ := open_next_available_local_port(t)
+
+ // Add an accept, with nobody dialling this should block the event loop forever.
+ accept := nbio.accept(sock, proc(op: ^nbio.Operation) {
+ log.error("shouldn't be called")
+ })
+
+ // Make sure the accept is in progress.
+ ev(t, nbio.tick(timeout=0), nil)
+
+ hit: bool
+ thr := thread.create_and_start_with_poly_data2(nbio.current_thread_event_loop(), &hit, proc(l: ^nbio.Event_Loop, hit: ^bool) {
+ hit^ = true
+ nbio.wake_up(l)
+ }, context)
+ defer thread.destroy(thr)
+
+ // Should block forever until the thread calling wake_up will make it return.
+ ev(t, nbio.tick(), nil)
+ e(t, hit)
+
+ nbio.remove(accept)
+ nbio.close(sock)
+
+ ev(t, nbio.run(), nil)
+ ev(t, nbio.tick(timeout=0), nil)
+ }
+ }
+}
diff --git a/tests/core/nbio/net.odin b/tests/core/nbio/net.odin
new file mode 100644
index 000000000..688ee0b45
--- /dev/null
+++ b/tests/core/nbio/net.odin
@@ -0,0 +1,400 @@
+package tests_nbio
+
+import "core:mem"
+import "core:nbio"
+import "core:net"
+import "core:testing"
+import "core:time"
+import "core:log"
+
+open_next_available_local_port :: proc(t: ^testing.T, addr: net.Address = net.IP4_Loopback, loc := #caller_location) -> (sock: net.TCP_Socket, ep: net.Endpoint) {
+ err: net.Network_Error
+ sock, err = nbio.listen_tcp({addr, 0})
+ if err != nil {
+ log.errorf("listen_tcp: %v", err, location=loc)
+ return
+ }
+
+ ep, err = net.bound_endpoint(sock)
+ if err != nil {
+ log.errorf("bound_endpoint: %v", err, location=loc)
+ }
+
+ return
+}
+
+@(test)
+client_and_server_send_recv :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ server, ep := open_next_available_local_port(t)
+
+ CONTENT :: [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
+
+ State :: struct {
+ server: net.TCP_Socket,
+ server_client: net.TCP_Socket,
+ client: net.TCP_Socket,
+ recv_buf: [20]byte,
+ send_buf: [20]byte,
+ }
+
+ state := State{
+ server = server,
+ send_buf = CONTENT,
+ }
+
+ close_ok :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.close.err, nil)
+ }
+
+ // Server
+ {
+ nbio.accept_poly2(server, t, &state, on_accept)
+
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
+ ev(t, op.accept.err, nil)
+
+ state.server_client = op.accept.client
+
+ log.debugf("accepted connection from: %v", op.accept.client_endpoint)
+
+ nbio.recv_poly2(state.server_client, {state.recv_buf[:]}, t, state, on_recv)
+ }
+
+ on_recv :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
+ ev(t, op.recv.err, nil)
+ ev(t, op.recv.received, 20)
+ ev(t, state.recv_buf, CONTENT)
+
+ nbio.close_poly(state.server_client, t, close_ok)
+ nbio.close_poly(state.server, t, close_ok)
+ }
+
+ ev(t, nbio.tick(0), nil)
+ }
+
+ // Client
+ {
+ nbio.dial_poly2(ep, t, &state, on_dial)
+
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
+ ev(t, op.dial.err, nil)
+
+ state.client = op.dial.socket
+
+ nbio.send_poly2(state.client, {state.send_buf[:]}, t, state, on_send)
+ }
+
+ on_send :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
+ ev(t, op.send.err, nil)
+ ev(t, op.send.sent, 20)
+
+ nbio.close_poly(state.client, t, close_ok)
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+close_and_remove_accept :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ server, _ := open_next_available_local_port(t)
+
+ accept := nbio.accept_poly(server, t, proc(_: ^nbio.Operation, t: ^testing.T) {
+ testing.fail_now(t)
+ })
+
+ ev(t, nbio.tick(0), nil)
+
+ nbio.close_poly(server, t, proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.close.err, nil)
+ })
+
+ nbio.remove(accept)
+ ev(t, nbio.run(), nil)
+ }
+}
+
+// Tests that when a client calls `close` on it's socket, `recv` returns with `0, nil` (connection closed).
+@(test)
+close_errors_recv :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ server, ep := open_next_available_local_port(t)
+
+ // Server
+ {
+ nbio.accept_poly(server, t, on_accept)
+
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.accept.err, nil)
+
+ bytes := make([]byte, 128, context.temp_allocator)
+ nbio.recv_poly(op.accept.client, {bytes}, t, on_recv)
+ }
+
+ on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.recv.received, 0)
+ ev(t, op.recv.err, nil)
+ }
+
+ ev(t, nbio.tick(0), nil)
+ }
+
+ // Client
+ {
+ nbio.dial_poly(ep, t, on_dial)
+
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.dial.err, nil)
+ nbio.close_poly(op.dial.socket, t, on_close)
+ }
+
+ on_close :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.close.err, nil)
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+ipv6 :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ server, ep := open_next_available_local_port(t, net.IP6_Loopback)
+
+ nbio.accept_poly(server, t, on_accept)
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.accept.err, nil)
+ addr, is_ipv6 := op.accept.client_endpoint.address.(net.IP6_Address)
+ e(t, is_ipv6)
+ ev(t, addr, net.IP6_Loopback)
+ e(t, op.accept.client_endpoint.port != 0)
+ nbio.close(op.accept.client)
+ nbio.close(op.accept.socket)
+ }
+
+ nbio.dial_poly(ep, t, on_dial)
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.dial.err, nil)
+ nbio.close(op.dial.socket)
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+accept_timeout :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ sock, _ := open_next_available_local_port(t)
+
+ hit: bool
+ nbio.accept_poly2(sock, t, &hit, on_accept, timeout=time.Millisecond)
+
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, hit: ^bool) {
+ hit^ = true
+ ev(t, op.accept.err, net.Accept_Error.Timeout)
+ nbio.close(op.accept.socket)
+ }
+
+ ev(t, nbio.run(), nil)
+
+ e(t, hit)
+ }
+}
+
+@(test)
+poll_timeout :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ sock, err := nbio.create_udp_socket(.IP4)
+ ev(t, err, nil)
+ berr := nbio.bind(sock, {nbio.IP4_Loopback, 0})
+ ev(t, berr, nil)
+
+ nbio.poll_poly(sock, .Receive, t, on_poll, time.Millisecond)
+ on_poll :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.poll.result, nbio.Poll_Result.Timeout)
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+/*
+This test walks through the scenario where a user wants to `poll` in order to check if some other package (in this case `core:net`),
+would be able to do an operation without blocking.
+
+It also tests whether a poll can be issues when it is already in a ready state.
+And it tests big send/recv buffers being handled properly.
+*/
+@(test)
+poll :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+// testing.set_fail_timeout(t, time.Minute)
+
+ can_recv: bool
+
+ sock, ep := open_next_available_local_port(t)
+
+ // Server
+ {
+ nbio.accept_poly2(sock, t, &can_recv, on_accept)
+
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) {
+ ev(t, op.accept.err, nil)
+
+ check_recv :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool, client: net.TCP_Socket) {
+ // Not ready to unblock the client yet, requeue for after 10ms.
+ if !can_recv^ {
+ nbio.timeout_poly3(time.Millisecond * 10, t, can_recv, client, check_recv)
+ return
+ }
+
+ free_all(context.temp_allocator)
+
+ // Connection was closed by client, close server.
+ if op.type == .Recv && op.recv.received == 0 && op.recv.err == nil {
+ nbio.close(client)
+ return
+ }
+
+ if op.type == .Recv {
+ log.debugf("received %M this time", op.recv.received)
+ }
+
+ // Receive some data to unblock the client, which should complete the poll it does, allowing it to send data again.
+ buf, mem_err := make([]byte, mem.Gigabyte, context.temp_allocator)
+ ev(t, mem_err, nil)
+ nbio.recv_poly3(client, {buf}, t, can_recv, client, check_recv)
+ }
+ nbio.timeout_poly3(time.Millisecond * 10, t, can_recv, op.accept.client, check_recv)
+ }
+
+ ev(t, nbio.tick(0), nil)
+ }
+
+ // Client
+ {
+ nbio.dial_poly2(ep, t, &can_recv, on_dial)
+
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) {
+ ev(t, op.dial.err, nil)
+
+ // Do a poll even though we know it's ready, so we can test that all implementations can handle that.
+ nbio.poll_poly2(op.dial.socket, .Send, t, can_recv, on_poll1)
+ }
+
+ on_poll1 :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) {
+ ev(t, op.poll.result, nil)
+
+ // Send 4 GB of data, which in my experience causes a Would_Block error because we filled up the internal buffer.
+ buf, mem_err := make([]byte, mem.Gigabyte*4, context.temp_allocator)
+ ev(t, mem_err, nil)
+
+ // Use `core:net` as example external code that doesn't care about the event loop.
+ net.set_blocking(op.poll.socket, false)
+ n, send_err := net.send(op.poll.socket, buf)
+ ev(t, send_err, net.TCP_Send_Error.Would_Block)
+
+ log.debugf("blocking after %M", n)
+
+ // Tell the server it can start issueing recv calls, so it unblocks us.
+ can_recv^ = true
+
+ // Now poll again, when the server reads enough data it should complete, telling us we can send without blocking again.
+ nbio.poll_poly(op.poll.socket, .Send, t, on_poll2)
+ }
+
+ on_poll2 :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.poll.result, nil)
+
+ buf: [128]byte
+ bytes_written, send_err := net.send(op.poll.socket, buf[:])
+ ev(t, bytes_written, 128)
+ ev(t, send_err, nil)
+
+ nbio.close(op.poll.socket.(net.TCP_Socket))
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ nbio.close(sock)
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+sendfile :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ CONTENT :: #load(#file)
+
+ sock, ep := open_next_available_local_port(t)
+
+ // Server
+ {
+ nbio.accept_poly(sock, t, on_accept)
+
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.accept.err, nil)
+ e(t, op.accept.client != 0)
+
+ log.debugf("connection from: %v", op.accept.client_endpoint)
+ nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open)
+ }
+
+ on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
+ ev(t, op.open.err, nil)
+
+ nbio.sendfile_poly2(client, op.open.handle, t, server, on_sendfile)
+ }
+
+ on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) {
+ ev(t, op.sendfile.err, nil)
+ ev(t, op.sendfile.sent, len(CONTENT))
+
+ nbio.close(op.sendfile.file)
+ nbio.close(op.sendfile.socket)
+ nbio.close(server)
+ }
+ }
+
+ // Client
+ {
+ nbio.dial_poly(ep, t, on_dial)
+
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.dial.err, nil)
+
+ buf := make([]byte, len(CONTENT), context.temp_allocator)
+ nbio.recv_poly(op.dial.socket, {buf}, t, on_recv, all=true)
+ }
+
+ on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.recv.err, nil)
+ ev(t, op.recv.received, len(CONTENT))
+ ev(t, string(op.recv.bufs[0]), string(CONTENT))
+
+ nbio.close(op.recv.socket.(net.TCP_Socket))
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
diff --git a/tests/core/nbio/remove.odin b/tests/core/nbio/remove.odin
new file mode 100644
index 000000000..063c2cf58
--- /dev/null
+++ b/tests/core/nbio/remove.odin
@@ -0,0 +1,247 @@
+package tests_nbio
+
+import "core:nbio"
+import "core:net"
+import "core:testing"
+import "core:time"
+import "core:log"
+
+// Removals are pretty complex.
+
+@(test)
+immediate_remove_of_sendfile :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ sock, ep := open_next_available_local_port(t)
+
+ // Server
+ {
+ nbio.accept_poly(sock, t, on_accept)
+
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.accept.err, nil)
+ e(t, op.accept.client != 0)
+
+ log.debugf("connection from: %v", op.accept.client_endpoint)
+ nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open)
+ }
+
+ on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
+ ev(t, op.open.err, nil)
+ e(t, op.open.handle != 0)
+
+ sendfile_op := nbio.sendfile_poly2(client, op.open.handle, t, server, on_sendfile)
+
+ // oh no changed my mind.
+ nbio.remove(sendfile_op)
+
+ nbio.close(op.open.handle)
+ nbio.close(client)
+ nbio.close(server)
+ }
+
+ on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) {
+ log.error("on_sendfile shouldn't be called")
+ }
+ }
+
+ // Client
+ {
+ nbio.dial_poly(ep, t, on_dial)
+
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.dial.err, nil)
+
+ buf := make([]byte, 128, context.temp_allocator)
+ nbio.recv_poly(op.dial.socket, {buf}, t, on_recv)
+ }
+
+ on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.recv.err, nil)
+
+ nbio.close(op.recv.socket.(net.TCP_Socket))
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+immediate_remove_of_sendfile_without_stat :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ sock, ep := open_next_available_local_port(t)
+
+ // Server
+ {
+ nbio.accept_poly(sock, t, on_accept)
+
+ on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.accept.err, nil)
+ e(t, op.accept.client != 0)
+
+ log.debugf("connection from: %v", op.accept.client_endpoint)
+ nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open)
+ }
+
+ on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
+ ev(t, op.open.err, nil)
+ e(t, op.open.handle != 0)
+
+ nbio.stat_poly3(op.open.handle, t, server, client, on_stat)
+ }
+
+ on_stat :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
+ ev(t, op.stat.err, nil)
+
+ sendfile_op := nbio.sendfile_poly2(client, op.stat.handle, t, server, on_sendfile, nbytes=int(op.stat.size))
+
+ // oh no changed my mind.
+ nbio.remove(sendfile_op)
+
+ nbio.timeout_poly3(time.Millisecond * 10, op.stat.handle, client, server, proc(op: ^nbio.Operation, p1: nbio.Handle, p2, p3: net.TCP_Socket){
+ nbio.close(p1)
+ nbio.close(p2)
+ nbio.close(p3)
+ })
+ }
+
+ on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) {
+ log.error("on_sendfile shouldn't be called")
+ }
+ }
+
+ // Client
+ {
+ nbio.dial_poly(ep, t, on_dial)
+
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.dial.err, nil)
+
+ buf := make([]byte, 128, context.temp_allocator)
+ nbio.recv_poly(op.dial.socket, {buf}, t, on_recv)
+ }
+
+ on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.recv.err, nil)
+
+ nbio.close(op.recv.socket.(net.TCP_Socket))
+ }
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+// Open should free the temporary memory allocated for the path when removed.
+// Can't really test that though, so should be checked manually that the internal callback is called but not the external.
+@(test)
+remove_open :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ open := nbio.open(#file, on_open)
+ nbio.remove(open)
+
+ on_open :: proc(op: ^nbio.Operation) {
+ log.error("on_open shouldn't be called")
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+// Dial should close the socket when removed.
+// Can't really test that though, so should be checked manually that the internal callback is called but not the external.
+@(test)
+remove_dial :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ sock, ep := open_next_available_local_port(t)
+ defer nbio.close(sock)
+
+ dial := nbio.dial(ep, on_dial)
+ nbio.remove(dial)
+
+ on_dial :: proc(op: ^nbio.Operation) {
+ log.error("on_dial shouldn't be called")
+ }
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+remove_next_tick :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ nt := nbio.next_tick_poly(t, proc(op: ^nbio.Operation, t: ^testing.T) {
+ log.error("shouldn't be called")
+ })
+ nbio.remove(nt)
+
+ ev(t, nbio.run(), nil)
+ }
+}
+
+@(test)
+remove_timeout :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ hit: bool
+ timeout := nbio.timeout_poly(time.Second, &hit, proc(_: ^nbio.Operation, hit: ^bool) {
+ hit^ = true
+ })
+
+ nbio.remove(timeout)
+
+ ev(t, nbio.run(), nil)
+
+ e(t, !hit)
+ }
+}
+
+@(test)
+remove_multiple_poll :: proc(t: ^testing.T) {
+ if event_loop_guard(t) {
+ testing.set_fail_timeout(t, time.Minute)
+
+ sock, ep := open_next_available_local_port(t)
+ defer nbio.close(sock)
+
+ hit: bool
+
+ first := nbio.poll(sock, .Receive, on_poll)
+ nbio.poll_poly2(sock, .Receive, t, &hit, on_poll2)
+
+ on_poll :: proc(op: ^nbio.Operation) {
+ log.error("shouldn't be called")
+ }
+
+ on_poll2 :: proc(op: ^nbio.Operation, t: ^testing.T, hit: ^bool) {
+ ev(t, op.poll.result, nbio.Poll_Result.Ready)
+ hit^ = true
+ }
+
+ ev(t, nbio.tick(0), nil)
+
+ nbio.remove(first)
+
+ ev(t, nbio.tick(0), nil)
+
+ nbio.dial_poly(ep, t, on_dial)
+
+ on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
+ ev(t, op.dial.err, nil)
+ }
+
+ ev(t, nbio.run(), nil)
+ e(t, hit)
+ }
+}
diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin
index 9b3973a60..55fe6671d 100644
--- a/tests/core/net/test_core_net.odin
+++ b/tests/core/net/test_core_net.odin
@@ -10,8 +10,6 @@
A test suite for `core:net`
*/
-#+build !netbsd
-#+build !openbsd
#+feature dynamic-literals
package test_core_net
diff --git a/tests/core/normal.odin b/tests/core/normal.odin
index e8b61fee8..d0889bf89 100644
--- a/tests/core/normal.odin
+++ b/tests/core/normal.odin
@@ -32,6 +32,7 @@ download_assets :: proc "contextless" () {
@(require) import "math/noise"
@(require) import "math/rand"
@(require) import "mem"
+@(require) import "nbio"
@(require) import "net"
@(require) import "odin"
@(require) import "os"
@@ -45,6 +46,7 @@ download_assets :: proc "contextless" () {
@(require) import "sync"
@(require) import "sync/chan"
@(require) import "sys/posix"
+@(require) import "sys/kqueue"
@(require) import "sys/windows"
@(require) import "text/i18n"
@(require) import "text/match"
diff --git a/tests/core/sys/kqueue/structs.odin b/tests/core/sys/kqueue/structs.odin
new file mode 100644
index 000000000..edf1fdd1e
--- /dev/null
+++ b/tests/core/sys/kqueue/structs.odin
@@ -0,0 +1,56 @@
+#+build darwin, freebsd, openbsd, netbsd
+package tests_core_sys_kqueue
+
+import "core:strings"
+import "core:testing"
+import os "core:os/os2"
+
+@(test)
+structs :: proc(t: ^testing.T) {
+ {
+ c_compiler := os.get_env("CC", context.temp_allocator)
+ if c_compiler == "" {
+ c_compiler = "clang"
+ }
+
+ c_compilation, c_start_err := os.process_start({
+ command = {c_compiler, #directory + "/structs/structs.c", "-o", #directory + "/structs/c_structs"},
+ stdout = os.stdout,
+ stderr = os.stderr,
+ })
+ testing.expect_value(t, c_start_err, nil)
+
+ o_compilation, o_start_err := os.process_start({
+ command = {ODIN_ROOT + "/odin", "build", #directory + "/structs", "-out:" + #directory + "/structs/odin_structs"},
+ stdout = os.stdout,
+ stderr = os.stderr,
+ })
+ testing.expect_value(t, o_start_err, nil)
+
+ c_status, c_err := os.process_wait(c_compilation)
+ testing.expect_value(t, c_err, nil)
+ testing.expect_value(t, c_status.exit_code, 0)
+
+ o_status, o_err := os.process_wait(o_compilation)
+ testing.expect_value(t, o_err, nil)
+ testing.expect_value(t, o_status.exit_code, 0)
+ }
+
+ c_status, c_stdout, c_stderr, c_err := os.process_exec({command={#directory + "/structs/c_structs"}}, context.temp_allocator)
+ testing.expect_value(t, c_err, nil)
+ testing.expect_value(t, c_status.exit_code, 0)
+ testing.expect_value(t, string(c_stderr), "")
+
+ o_status, o_stdout, o_stderr, o_err := os.process_exec({command={#directory + "/structs/odin_structs"}}, context.temp_allocator)
+ testing.expect_value(t, o_err, nil)
+ testing.expect_value(t, o_status.exit_code, 0)
+ testing.expect_value(t, string(o_stderr), "")
+
+ testing.expect(t, strings.trim_space(string(c_stdout)) != "")
+
+ testing.expect_value(
+ t,
+ strings.trim_space(string(o_stdout)),
+ strings.trim_space(string(c_stdout)),
+ )
+}
diff --git a/tests/core/sys/kqueue/structs/structs.c b/tests/core/sys/kqueue/structs/structs.c
new file mode 100644
index 000000000..e7620c994
--- /dev/null
+++ b/tests/core/sys/kqueue/structs/structs.c
@@ -0,0 +1,63 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/event.h>
+
+int main(int argc, char *argv[])
+{
+ printf("kevent %zu %zu\n", sizeof(struct kevent), _Alignof(struct kevent));
+ printf("kevent.ident %zu\n", offsetof(struct kevent, ident));
+ printf("kevent.filter %zu\n", offsetof(struct kevent, filter));
+ printf("kevent.flags %zu\n", offsetof(struct kevent, flags));
+ printf("kevent.fflags %zu\n", offsetof(struct kevent, fflags));
+ printf("kevent.data %zu\n", offsetof(struct kevent, data));
+ printf("kevent.udata %zu\n", offsetof(struct kevent, udata));
+
+ printf("EV_ADD %d\n", EV_ADD);
+ printf("EV_DELETE %d\n", EV_DELETE);
+ printf("EV_ENABLE %d\n", EV_ENABLE);
+ printf("EV_DISABLE %d\n", EV_DISABLE);
+ printf("EV_ONESHOT %d\n", EV_ONESHOT);
+ printf("EV_CLEAR %d\n", EV_CLEAR);
+ printf("EV_RECEIPT %d\n", EV_RECEIPT);
+ printf("EV_DISPATCH %d\n", EV_DISPATCH);
+ printf("EV_ERROR %d\n", EV_ERROR);
+ printf("EV_EOF %d\n", EV_EOF);
+
+ printf("EVFILT_READ %d\n", EVFILT_READ);
+ printf("EVFILT_WRITE %d\n", EVFILT_WRITE);
+ printf("EVFILT_AIO %d\n", EVFILT_AIO);
+ printf("EVFILT_VNODE %d\n", EVFILT_VNODE);
+ printf("EVFILT_PROC %d\n", EVFILT_PROC);
+ printf("EVFILT_SIGNAL %d\n", EVFILT_SIGNAL);
+ printf("EVFILT_TIMER %d\n", EVFILT_TIMER);
+ printf("EVFILT_USER %d\n", EVFILT_USER);
+
+ printf("NOTE_SECONDS %u\n", NOTE_SECONDS);
+ printf("NOTE_USECONDS %u\n", NOTE_USECONDS);
+ printf("NOTE_NSECONDS %u\n", NOTE_NSECONDS);
+#if defined(NOTE_ABSOLUTE)
+ printf("NOTE_ABSOLUTE %u\n", NOTE_ABSOLUTE);
+#else
+ printf("NOTE_ABSOLUTE %u\n", NOTE_ABSTIME);
+#endif
+
+ printf("NOTE_LOWAT %u\n", NOTE_LOWAT);
+
+ printf("NOTE_DELETE %u\n", NOTE_DELETE);
+ printf("NOTE_WRITE %u\n", NOTE_WRITE);
+ printf("NOTE_EXTEND %u\n", NOTE_EXTEND);
+ printf("NOTE_ATTRIB %u\n", NOTE_ATTRIB);
+ printf("NOTE_LINK %u\n", NOTE_LINK);
+ printf("NOTE_RENAME %u\n", NOTE_RENAME);
+ printf("NOTE_REVOKE %u\n", NOTE_REVOKE);
+
+ printf("NOTE_EXIT %u\n", NOTE_EXIT);
+ printf("NOTE_FORK %u\n", NOTE_FORK);
+ printf("NOTE_EXEC %u\n", NOTE_EXEC);
+
+ printf("NOTE_TRIGGER %u\n", NOTE_TRIGGER);
+ printf("NOTE_FFAND %u\n", NOTE_FFAND);
+ printf("NOTE_FFOR %u\n", NOTE_FFOR);
+ printf("NOTE_FFCOPY %u\n", NOTE_FFCOPY);
+ return 0;
+}
diff --git a/tests/core/sys/kqueue/structs/structs.odin b/tests/core/sys/kqueue/structs/structs.odin
new file mode 100644
index 000000000..4886f63e4
--- /dev/null
+++ b/tests/core/sys/kqueue/structs/structs.odin
@@ -0,0 +1,58 @@
+package main
+
+import "core:fmt"
+import "core:sys/kqueue"
+
+main :: proc() {
+ fmt.println("kevent", size_of(kqueue.KEvent), align_of(kqueue.KEvent))
+ fmt.println("kevent.ident", offset_of(kqueue.KEvent, ident))
+ fmt.println("kevent.filter", offset_of(kqueue.KEvent, filter))
+ fmt.println("kevent.flags", offset_of(kqueue.KEvent, flags))
+ fmt.println("kevent.fflags", offset_of(kqueue.KEvent, fflags))
+ fmt.println("kevent.data", offset_of(kqueue.KEvent, data))
+ fmt.println("kevent.udata", offset_of(kqueue.KEvent, udata))
+
+ fmt.println("EV_ADD", transmute(kqueue._Flags_Backing)kqueue.Flags{.Add})
+ fmt.println("EV_DELETE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Delete})
+ fmt.println("EV_ENABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Enable})
+ fmt.println("EV_DISABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Disable})
+ fmt.println("EV_ONESHOT", transmute(kqueue._Flags_Backing)kqueue.Flags{.One_Shot})
+ fmt.println("EV_CLEAR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Clear})
+ fmt.println("EV_RECEIPT", transmute(kqueue._Flags_Backing)kqueue.Flags{.Receipt})
+ fmt.println("EV_DISPATCH", transmute(kqueue._Flags_Backing)kqueue.Flags{.Dispatch})
+ fmt.println("EV_ERROR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Error})
+ fmt.println("EV_EOF", transmute(kqueue._Flags_Backing)kqueue.Flags{.EOF})
+
+ fmt.println("EVFILT_READ", int(kqueue.Filter.Read))
+ fmt.println("EVFILT_WRITE", int(kqueue.Filter.Write))
+ fmt.println("EVFILT_AIO", int(kqueue.Filter.AIO))
+ fmt.println("EVFILT_VNODE", int(kqueue.Filter.VNode))
+ fmt.println("EVFILT_PROC", int(kqueue.Filter.Proc))
+ fmt.println("EVFILT_SIGNAL", int(kqueue.Filter.Signal))
+ fmt.println("EVFILT_TIMER", int(kqueue.Filter.Timer))
+ fmt.println("EVFILT_USER", int(kqueue.Filter.User))
+
+ fmt.println("NOTE_SECONDS", transmute(u32)kqueue.Timer_Flags{.Seconds})
+ fmt.println("NOTE_USECONDS", transmute(u32)kqueue.Timer_Flags{.USeconds})
+ fmt.println("NOTE_NSECONDS", transmute(u32)kqueue.TIMER_FLAGS_NSECONDS)
+ fmt.println("NOTE_ABSOLUTE", transmute(u32)kqueue.Timer_Flags{.Absolute})
+
+ fmt.println("NOTE_LOWAT", transmute(u32)kqueue.RW_Flags{.Low_Water_Mark})
+
+ fmt.println("NOTE_DELETE", transmute(u32)kqueue.VNode_Flags{.Delete})
+ fmt.println("NOTE_WRITE", transmute(u32)kqueue.VNode_Flags{.Write})
+ fmt.println("NOTE_EXTEND", transmute(u32)kqueue.VNode_Flags{.Extend})
+ fmt.println("NOTE_ATTRIB", transmute(u32)kqueue.VNode_Flags{.Attrib})
+ fmt.println("NOTE_LINK", transmute(u32)kqueue.VNode_Flags{.Link})
+ fmt.println("NOTE_RENAME", transmute(u32)kqueue.VNode_Flags{.Rename})
+ fmt.println("NOTE_REVOKE", transmute(u32)kqueue.VNode_Flags{.Revoke})
+
+ fmt.println("NOTE_EXIT", transmute(u32)kqueue.Proc_Flags{.Exit})
+ fmt.println("NOTE_FORK", transmute(u32)kqueue.Proc_Flags{.Fork})
+ fmt.println("NOTE_EXEC", transmute(u32)kqueue.Proc_Flags{.Exec})
+
+ fmt.println("NOTE_TRIGGER", transmute(u32)kqueue.User_Flags{.Trigger})
+ fmt.println("NOTE_FFAND", transmute(u32)kqueue.User_Flags{.FFAnd})
+ fmt.println("NOTE_FFOR", transmute(u32)kqueue.User_Flags{.FFOr})
+ fmt.println("NOTE_FFCOPY", transmute(u32)kqueue.USER_FLAGS_COPY)
+}
diff --git a/tests/internal/test_imported_proc_groups.odin b/tests/internal/test_imported_proc_groups.odin
new file mode 100644
index 000000000..e91af4bf5
--- /dev/null
+++ b/tests/internal/test_imported_proc_groups.odin
@@ -0,0 +1,11 @@
+package test_internal
+
+import "core:testing"
+import "test_imported_proc_groups"
+
+// https://github.com/odin-lang/Odin/pull/6119
+@test
+test_use_imported_proc_group_as_argument :: proc(t: ^testing.T) {
+ use_proc :: proc(proc()) { }
+ use_proc(test_imported_proc_groups.proc_group)
+}
diff --git a/tests/internal/test_imported_proc_groups/proc_group.odin b/tests/internal/test_imported_proc_groups/proc_group.odin
new file mode 100644
index 000000000..59519ab9c
--- /dev/null
+++ b/tests/internal/test_imported_proc_groups/proc_group.odin
@@ -0,0 +1,4 @@
+package test_imported_proc_groups
+
+proc_group :: proc{empty_proc}
+empty_proc :: proc() { }