diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 5326e5023..32c8650c6 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -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)