lph-11/odin-http/nbio/nbio_test.odin
2025-03-13 18:14:21 +13:00

346 lines
8.6 KiB
Odin

package nbio
import "core:fmt"
import "core:log"
import "core:mem"
import "core:net"
import "core:os"
import "core:slice"
import "core:testing"
import "core:time"
expect :: testing.expect
@(test)
test_timeout :: proc(t: ^testing.T) {
io: IO
ierr := init(&io)
expect(t, ierr == os.ERROR_NONE, fmt.tprintf("nbio.init error: %v", ierr))
defer destroy(&io)
timeout_fired: bool
timeout(&io, time.Millisecond * 10, &timeout_fired, proc(t_: rawptr) {
timeout_fired := cast(^bool)t_
timeout_fired^ = true
})
start := time.now()
for {
terr := tick(&io)
expect(t, terr == os.ERROR_NONE, fmt.tprintf("nbio.tick error: %v", terr))
if time.since(start) > time.Millisecond * 11 {
expect(t, timeout_fired, "timeout did not run in time")
break
}
}
}
@(test)
test_write_read_close :: proc(t: ^testing.T) {
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
defer {
for _, leak in track.allocation_map {
fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
}
for bad_free in track.bad_free_array {
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
}
}
{
Test_Ctx :: struct {
t: ^testing.T,
io: ^IO,
done: bool,
fd: os.Handle,
write_buf: [20]byte,
read_buf: [20]byte,
written: int,
read: int,
}
io: IO
init(&io)
defer destroy(&io)
tctx := Test_Ctx {
write_buf = [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
read_buf = [20]byte{},
}
tctx.t = t
tctx.io = &io
path := "test_write_read_close"
handle, errno := open(
&io,
path,
os.O_RDWR | os.O_CREATE | os.O_TRUNC,
os.S_IRUSR | os.S_IWUSR | os.S_IRGRP | os.S_IROTH when ODIN_OS != .Windows else 0,
)
expect(t, errno == os.ERROR_NONE, fmt.tprintf("open file error: %i", errno))
defer close(&io, handle)
defer os.remove(path)
tctx.fd = handle
write(&io, handle, tctx.write_buf[:], &tctx, write_callback)
for !tctx.done {
terr := tick(&io)
expect(t, terr == os.ERROR_NONE, fmt.tprintf("error ticking: %v", terr))
}
expect(t, tctx.read == 20, "expected to have read 20 bytes")
expect(t, tctx.written == 20, "expected to have written 20 bytes")
expect(t, slice.equal(tctx.write_buf[:], tctx.read_buf[:]))
write_callback :: proc(ctx: rawptr, written: int, err: os.Errno) {
ctx := cast(^Test_Ctx)ctx
expect(ctx.t, err == os.ERROR_NONE, fmt.tprintf("write error: %i", err))
ctx.written = written
read_at(ctx.io, ctx.fd, 0, ctx.read_buf[:], ctx, read_callback)
}
read_callback :: proc(ctx: rawptr, r: int, err: os.Errno) {
ctx := cast(^Test_Ctx)ctx
expect(ctx.t, err == os.ERROR_NONE, fmt.tprintf("read error: %i", err))
ctx.read = r
close(ctx.io, ctx.fd, ctx, close_callback)
}
close_callback :: proc(ctx: rawptr, ok: bool) {
ctx := cast(^Test_Ctx)ctx
expect(ctx.t, ok, "close error")
ctx.done = true
}
}
}
@(test)
test_client_and_server_send_recv :: proc(t: ^testing.T) {
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
defer {
for _, leak in track.allocation_map {
fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
}
for bad_free in track.bad_free_array {
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
}
}
{
Test_Ctx :: struct {
t: ^testing.T,
io: ^IO,
send_buf: []byte,
recv_buf: []byte,
sent: int,
received: int,
accepted_sock: Maybe(net.TCP_Socket),
done: bool,
ep: net.Endpoint,
}
io: IO
init(&io)
defer destroy(&io)
tctx := Test_Ctx {
send_buf = []byte{1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
recv_buf = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}
tctx.t = t
tctx.io = &io
tctx.ep = {
address = net.IP4_Loopback,
port = 3131,
}
server, err := open_and_listen_tcp(&io, tctx.ep)
expect(t, err == nil, fmt.tprintf("create socket error: %s", err))
accept(&io, server, &tctx, accept_callback)
terr := tick(&io)
expect(t, terr == os.ERROR_NONE, fmt.tprintf("tick error: %v", terr))
connect(&io, tctx.ep, &tctx, connect_callback)
for !tctx.done {
terr := tick(&io)
expect(t, terr == os.ERROR_NONE, fmt.tprintf("tick error: %v", terr))
}
expect(
t,
len(tctx.send_buf) == int(tctx.sent),
fmt.tprintf("expected sent to be length of buffer: %i != %i", len(tctx.send_buf), tctx.sent),
)
expect(
t,
len(tctx.recv_buf) == int(tctx.received),
fmt.tprintf("expected recv to be length of buffer: %i != %i", len(tctx.recv_buf), tctx.received),
)
expect(
t,
slice.equal(tctx.send_buf[:tctx.received], tctx.recv_buf),
fmt.tprintf("send and received not the same: %v != %v", tctx.send_buf[:tctx.received], tctx.recv_buf),
)
connect_callback :: proc(ctx: rawptr, sock: net.TCP_Socket, err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
// I believe this is because we are connecting in the same tick as accepting
// and it goes wrong, might actually be a bug though, can't find anything.
if err != nil {
log.info("connect err, trying again", err)
connect(ctx.io, ctx.ep, ctx, connect_callback)
return
}
send(ctx.io, sock, ctx.send_buf, ctx, send_callback)
}
send_callback :: proc(ctx: rawptr, res: int, err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
expect(ctx.t, err == nil, fmt.tprintf("send error: %i", err))
ctx.sent = res
}
accept_callback :: proc(ctx: rawptr, client: net.TCP_Socket, source: net.Endpoint, err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
expect(ctx.t, err == nil, fmt.tprintf("accept error: %i", err))
ctx.accepted_sock = client
recv(ctx.io, client, ctx.recv_buf, ctx, recv_callback)
}
recv_callback :: proc(ctx: rawptr, received: int, _: Maybe(net.Endpoint), err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
expect(ctx.t, err == nil, fmt.tprintf("recv error: %i", err))
ctx.received = received
ctx.done = true
}
}
}
@test
test_send_all :: proc(t: ^testing.T) {
Test_Ctx :: struct {
t: ^testing.T,
io: ^IO,
send_buf: []byte,
recv_buf: []byte,
sent: int,
received: int,
accepted_sock: Maybe(net.TCP_Socket),
done: bool,
ep: net.Endpoint,
}
io: IO
init(&io)
defer destroy(&io)
tctx := Test_Ctx {
send_buf = make([]byte, mem.Megabyte * 50),
recv_buf = make([]byte, mem.Megabyte * 60),
}
defer delete(tctx.send_buf)
defer delete(tctx.recv_buf)
slice.fill(tctx.send_buf, 1)
tctx.t = t
tctx.io = &io
tctx.ep = {
address = net.IP4_Loopback,
port = 3132,
}
server, err := open_and_listen_tcp(&io, tctx.ep)
expect(t, err == nil, fmt.tprintf("create socket error: %s", err))
defer close(&io, server)
defer close(&io, tctx.accepted_sock.?)
accept(&io, server, &tctx, accept_callback)
terr := tick(&io)
expect(t, terr == os.ERROR_NONE, fmt.tprintf("tick error: %v", terr))
connect(&io, tctx.ep, &tctx, connect_callback)
for !tctx.done {
terr := tick(&io)
expect(t, terr == os.ERROR_NONE, fmt.tprintf("tick error: %v", terr))
}
expect(t, slice.simple_equal(tctx.send_buf, tctx.recv_buf[:mem.Megabyte * 50]), "expected the sent bytes to be the same as the received")
expected := make([]byte, mem.Megabyte * 10)
expect(t, slice.simple_equal(tctx.recv_buf[mem.Megabyte * 50:], expected), "expected the rest of the bytes to be 0")
connect_callback :: proc(ctx: rawptr, sock: net.TCP_Socket, err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
send_all(ctx.io, sock, ctx.send_buf, ctx, send_callback)
}
send_callback :: proc(ctx: rawptr, res: int, err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
if !expect(ctx.t, err == nil, fmt.tprintf("send error: %i", err)) {
ctx.done = true
}
ctx.sent = res
}
accept_callback :: proc(ctx: rawptr, client: net.TCP_Socket, source: net.Endpoint, err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
if !expect(ctx.t, err == nil, fmt.tprintf("accept error: %i", err)) {
ctx.done = true
}
ctx.accepted_sock = client
recv(ctx.io, client, ctx.recv_buf, ctx, recv_callback)
}
recv_callback :: proc(ctx: rawptr, received: int, _: Maybe(net.Endpoint), err: net.Network_Error) {
ctx := cast(^Test_Ctx)ctx
if !expect(ctx.t, err == nil, fmt.tprintf("recv error: %i", err)) {
ctx.done = true
}
ctx.received += received
if ctx.received < mem.Megabyte * 50 {
recv(ctx.io, ctx.accepted_sock.?, ctx.recv_buf[ctx.received:], ctx, recv_callback)
log.infof("received %.0M", received)
} else {
ctx.done = true
}
}
}