mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 15:18:49 +00:00
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:
@@ -15,6 +15,7 @@ import "core:mem"
|
|||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:net"
|
import "core:net"
|
||||||
import "core:strconv"
|
import "core:strconv"
|
||||||
|
import "core:sync"
|
||||||
import "core:time"
|
import "core:time"
|
||||||
import "core:thread"
|
import "core:thread"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
@@ -62,11 +63,7 @@ main :: proc() {
|
|||||||
|
|
||||||
address_parsing_test(t)
|
address_parsing_test(t)
|
||||||
|
|
||||||
when ODIN_OS != .Windows {
|
tcp_tests(t)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
split_url_test(t)
|
split_url_test(t)
|
||||||
join_url_test(t)
|
join_url_test(t)
|
||||||
@@ -338,38 +335,6 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{
|
|||||||
{ .IP6, "c0a8", "", ""},
|
{ .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) {
|
tcp_tests :: proc(t: ^testing.T) {
|
||||||
fmt.println("Testing two servers trying to bind to the same endpoint...")
|
fmt.println("Testing two servers trying to bind to the same endpoint...")
|
||||||
two_servers_binding_same_endpoint(t)
|
two_servers_binding_same_endpoint(t)
|
||||||
@@ -381,131 +346,119 @@ tcp_tests :: proc(t: ^testing.T) {
|
|||||||
client_sends_server_data(t)
|
client_sends_server_data(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
tcp_client :: proc(retval: rawptr) {
|
ENDPOINT := net.Endpoint{
|
||||||
send :: proc(content: []u8) -> (err: net.Network_Error) {
|
net.IP4_Address{127, 0, 0, 1},
|
||||||
skt := net.dial_tcp(ENDPOINT) or_return
|
9999,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
two_servers_binding_same_endpoint :: proc(t: ^testing.T) {
|
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)
|
expect(t, err1 == nil, "expected first server binding to endpoint to do so without error")
|
||||||
thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_server, context)
|
expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use")
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
client_connects_to_closed_port :: proc(t: ^testing.T) {
|
client_connects_to_closed_port :: proc(t: ^testing.T) {
|
||||||
thread_data = {}
|
skt, err := net.dial_tcp(ENDPOINT)
|
||||||
|
defer net.close(skt)
|
||||||
thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_client, context)
|
expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused")
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
client_connects_to_open_but_non_accepting_port :: proc(t: ^testing.T) {
|
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
|
c_skt, c_err := net.dial_tcp(ENDPOINT)
|
||||||
thread_data[0].no_accept = true
|
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)
|
net.set_option(c_skt, .Send_Timeout, time.Millisecond * 10)
|
||||||
thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context)
|
|
||||||
|
|
||||||
defer {
|
// NOTE: Have to send a bunch of data so the kernel buffer fills up and it requires writing to the socket.
|
||||||
cleanup_thread(thread_data[0])
|
buf, alloc_err := make([]byte, mem.Gigabyte)
|
||||||
cleanup_thread(thread_data[1])
|
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
|
_, send_err := net.send(c_skt, buf)
|
||||||
time.sleep(4 * time.Second)
|
expect(t, send_err == net.TCP_Send_Error.Timeout, fmt.tprintf("expected sending to non-accepting socket to timeout, got %v", send_err))
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
client_sends_server_data :: proc(t: ^testing.T) {
|
client_sends_server_data :: proc(t: ^testing.T) {
|
||||||
thread_data = {}
|
CONTENT: string: "Hellope!"
|
||||||
|
|
||||||
// Tell server proc not to accept
|
SEND_TIMEOUT :: time.Duration(1 * time.Second)
|
||||||
// thread_data[0].no_accept = true
|
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[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)
|
thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context)
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
cleanup_thread(thread_data[0])
|
net.close(thread_data[0].skt)
|
||||||
cleanup_thread(thread_data[1])
|
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
|
sync.wait_group_wait(&wg)
|
||||||
time.sleep(1 * time.Second)
|
|
||||||
|
|
||||||
okay := thread_data[0].err == nil && thread_data[1].err == nil
|
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)
|
msg := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err)
|
||||||
|
|||||||
Reference in New Issue
Block a user