Improve net tests

Watching the sporadic CI failures it seems to come from these tests a
lot of the time, this PR cleans up and simplifies (while testing the
same things):

1. Lots of tests were using threads without a need for it
2. Tests had hardcoded `time.sleep` calls which is never a good idea
3. An unclear abstraction was implemented without a real need
4. They weren't being ran on non-windows
5. The `client_connects_to_open_but_not_accepting_port` was not doing
   what you wanted to test for, the `tcp_server` proc was returning, and
   then `dial` was called, which meant that the server already closed
   and you got a refusal error. Now it correctly listens without
   accepting, which even results in a different error because the kernel
   buffer would have buffered the send
This commit is contained in:
Laytan Laats
2024-02-20 22:49:47 +01:00
parent 43a199b57b
commit 1ab3ec5731

View File

@@ -15,6 +15,7 @@ import "core:mem"
import "core:fmt"
import "core:net"
import "core:strconv"
import "core:sync"
import "core:time"
import "core:thread"
import "core:os"
@@ -62,11 +63,7 @@ main :: proc() {
address_parsing_test(t)
when ODIN_OS != .Windows {
fmt.printf("IMPORTANT: `core:thread` seems to still be a bit wonky on Linux and MacOS, so we can't run tests relying on them.\n", ODIN_OS)
} else {
tcp_tests(t)
}
tcp_tests(t)
split_url_test(t)
join_url_test(t)
@@ -338,38 +335,6 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{
{ .IP6, "c0a8", "", ""},
}
ENDPOINT := net.Endpoint{
net.IP4_Address{127, 0, 0, 1},
9999,
}
CONTENT := "Hellope!"
SEND_TIMEOUT :: time.Duration(1 * time.Second)
RECV_TIMEOUT :: time.Duration(1 * time.Second)
Thread_Data :: struct {
skt: net.Any_Socket,
err: net.Network_Error,
tid: ^thread.Thread,
no_accept: bool, // Tell the server proc not to accept.
data: [1024]u8, // Received data and its length
length: int,
}
thread_data := [3]Thread_Data{}
/*
This runs a bunch of socket tests using threads:
- two servers trying to bind the same endpoint
- client trying to connect to closed port
- client trying to connect to an open port with a non-accepting server
- client sending server data and server sending client data
- etc.
*/
tcp_tests :: proc(t: ^testing.T) {
fmt.println("Testing two servers trying to bind to the same endpoint...")
two_servers_binding_same_endpoint(t)
@@ -381,131 +346,119 @@ tcp_tests :: proc(t: ^testing.T) {
client_sends_server_data(t)
}
tcp_client :: proc(retval: rawptr) {
send :: proc(content: []u8) -> (err: net.Network_Error) {
skt := net.dial_tcp(ENDPOINT) or_return
defer net.close(skt)
net.set_option(skt, .Send_Timeout, SEND_TIMEOUT)
net.set_option(skt, .Receive_Timeout, RECV_TIMEOUT)
_, err = net.send(skt, content)
return
}
r := transmute(^Thread_Data)retval
r.err = send(transmute([]u8)CONTENT)
return
}
tcp_server :: proc(retval: rawptr) {
r := transmute(^Thread_Data)retval
if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil {
return
}
defer net.close(r.skt)
if r.no_accept {
// Don't accept any connections, just listen.
return
}
client: net.TCP_Socket
if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil {
return
}
defer net.close(client)
r.length, r.err = net.recv_tcp(client, r.data[:])
return
}
cleanup_thread :: proc(data: Thread_Data) {
net.close(data.skt)
thread.terminate(data.tid, 1)
thread.destroy(data.tid)
ENDPOINT := net.Endpoint{
net.IP4_Address{127, 0, 0, 1},
9999,
}
@(test)
two_servers_binding_same_endpoint :: proc(t: ^testing.T) {
thread_data = {}
skt1, err1 := net.listen_tcp(ENDPOINT)
defer net.close(skt1)
skt2, err2 := net.listen_tcp(ENDPOINT)
defer net.close(skt2)
thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_server, context)
defer {
cleanup_thread(thread_data[0])
cleanup_thread(thread_data[1])
}
// Give the two servers enough time to try and bind the same endpoint
time.sleep(1 * time.Second)
first_won := thread_data[0].err == nil && thread_data[1].err == net.Bind_Error.Address_In_Use
second_won := thread_data[1].err == nil && thread_data[0].err == net.Bind_Error.Address_In_Use
okay := first_won || second_won
msg := fmt.tprintf("Expected servers to return `nil` and `Address_In_Use`, got %v and %v", thread_data[0].err, thread_data[1].err)
expect(t, okay, msg)
expect(t, err1 == nil, "expected first server binding to endpoint to do so without error")
expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use")
}
@(test)
client_connects_to_closed_port :: proc(t: ^testing.T) {
thread_data = {}
thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_client, context)
defer {
cleanup_thread(thread_data[0])
}
// Give the socket enough time to return `Refused`
time.sleep(4 * time.Second)
okay := thread_data[0].err == net.Dial_Error.Refused
msg := fmt.tprintf("Expected client to return `Refused` connecting to closed port, got %v", thread_data[0].err)
expect(t, okay, msg)
skt, err := net.dial_tcp(ENDPOINT)
defer net.close(skt)
expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused")
}
@(test)
client_connects_to_open_but_non_accepting_port :: proc(t: ^testing.T) {
thread_data = {}
s_skt, s_err := net.listen_tcp(ENDPOINT)
expect(t, s_err == nil, "expected listening to succeed")
defer net.close(s_skt)
// Tell server proc not to accept
thread_data[0].no_accept = true
c_skt, c_err := net.dial_tcp(ENDPOINT)
expect(t, c_err == nil, "expected dial to succeed")
defer net.close(c_skt)
thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context)
net.set_option(c_skt, .Send_Timeout, time.Millisecond * 10)
defer {
cleanup_thread(thread_data[0])
cleanup_thread(thread_data[1])
}
// NOTE: Have to send a bunch of data so the kernel buffer fills up and it requires writing to the socket.
buf, alloc_err := make([]byte, mem.Gigabyte)
defer delete(buf)
expect(t, alloc_err == nil, "expected to be able to allocate a gigabyte to fill the send buffer")
// Give the two servers enough time to try and bind the same endpoint
time.sleep(4 * time.Second)
okay := thread_data[0].err == nil && thread_data[1].err == net.Dial_Error.Refused
msg := fmt.tprintf("Expected server and client to return `nil` and `Refused`, got %v and %v", thread_data[0].err, thread_data[1].err)
expect(t, okay, msg)
_, send_err := net.send(c_skt, buf)
expect(t, send_err == net.TCP_Send_Error.Timeout, fmt.tprintf("expected sending to non-accepting socket to timeout, got %v", send_err))
}
@(test)
client_sends_server_data :: proc(t: ^testing.T) {
thread_data = {}
CONTENT: string: "Hellope!"
// Tell server proc not to accept
// thread_data[0].no_accept = true
SEND_TIMEOUT :: time.Duration(1 * time.Second)
RECV_TIMEOUT :: time.Duration(1 * time.Second)
Thread_Data :: struct {
skt: net.Any_Socket,
err: net.Network_Error,
tid: ^thread.Thread,
data: [1024]u8, // Received data and its length
length: int,
wg: ^sync.Wait_Group,
}
thread_data := [2]Thread_Data{}
wg: sync.Wait_Group
thread_data[0].wg = &wg
thread_data[1].wg = &wg
tcp_client :: proc(thread_data: rawptr) {
r := transmute(^Thread_Data)thread_data
defer sync.wait_group_done(r.wg)
if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil {
return
}
net.set_option(r.skt, .Send_Timeout, SEND_TIMEOUT)
_, r.err = net.send(r.skt, transmute([]byte)CONTENT)
}
tcp_server :: proc(thread_data: rawptr) {
r := transmute(^Thread_Data)thread_data
defer sync.wait_group_done(r.wg)
if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil {
return
}
client: net.TCP_Socket
if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil {
return
}
defer net.close(client)
net.set_option(client, .Receive_Timeout, RECV_TIMEOUT)
r.length, r.err = net.recv_tcp(client, r.data[:])
return
}
sync.wait_group_add(&wg, 2)
thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context)
defer {
cleanup_thread(thread_data[0])
cleanup_thread(thread_data[1])
net.close(thread_data[0].skt)
thread.destroy(thread_data[0].tid)
net.close(thread_data[1].skt)
thread.destroy(thread_data[1].tid)
}
// Give the two servers enough time to try and bind the same endpoint
time.sleep(1 * time.Second)
sync.wait_group_wait(&wg)
okay := thread_data[0].err == nil && thread_data[1].err == nil
msg := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err)