mirror of
https://github.com/odin-lang/Odin.git
synced 2026-05-03 11:34:41 +00:00
306 lines
7.8 KiB
Odin
306 lines
7.8 KiB
Odin
package tests_nbio
|
|
|
|
import "core:log"
|
|
import "core:nbio"
|
|
import "core:testing"
|
|
import "core:thread"
|
|
import "core:time"
|
|
import "core:os"
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that if multiple accepts are queued, and a dial comes in which completes one of them,
|
|
// the rest are queued again properly.
|
|
@(test)
|
|
still_pending :: 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)
|
|
|
|
N :: 3
|
|
|
|
State :: struct {
|
|
accepted: int,
|
|
}
|
|
state: State
|
|
|
|
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
|
|
ev(t, op.accept.err, nil)
|
|
state.accepted += 1
|
|
nbio.close(op.accept.client)
|
|
}
|
|
|
|
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
|
ev(t, op.dial.err, nil)
|
|
nbio.close(op.dial.socket)
|
|
}
|
|
|
|
for _ in 0..<N {
|
|
nbio.accept_poly2(sock, t, &state, on_accept)
|
|
}
|
|
|
|
nbio.dial_poly(ep, t, on_dial)
|
|
|
|
for state.accepted < 1 {
|
|
ev(t, nbio.tick(), nil)
|
|
}
|
|
|
|
for _ in 0..<N-1 {
|
|
nbio.dial_poly(ep, t, on_dial)
|
|
}
|
|
|
|
ev(t, nbio.run(), nil)
|
|
ev(t, state.accepted, N)
|
|
}
|
|
}
|