mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-04 01:34:39 +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: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)
|
||||
|
||||
Reference in New Issue
Block a user