Port tests\core\net

This commit is contained in:
Jeroen van Rijn
2024-06-02 14:47:09 -04:00
committed by Feoramund
parent dacb0f7786
commit 62b7d8de97
3 changed files with 85 additions and 195 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ noise_test:
$(ODIN) test math/noise $(COMMON) -out:test_noise $(ODIN) test math/noise $(COMMON) -out:test_noise
net_test: net_test:
$(ODIN) run net $(COMMON) -out:test_core_net $(ODIN) test net $(COMMON) -out:test_core_net
os_exit_test: os_exit_test:
$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0
+1 -1
View File
@@ -81,7 +81,7 @@ echo ---
echo --- echo ---
echo Running core:net echo Running core:net
echo --- echo ---
%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b %PATH_TO_ODIN% test net %COMMON% -out:test_core_net.exe || exit /b
echo --- echo ---
echo Running core:odin tests echo Running core:odin tests
+83 -193
View File
@@ -11,71 +11,12 @@
package test_core_net package test_core_net
import "core:testing" import "core:testing"
import "core:mem"
import "core:fmt"
import "core:net" import "core:net"
import "core:strconv" import "core:strconv"
import "core:sync" import "core:sync"
import "core:time" import "core:time"
import "core:thread" import "core:thread"
import "core:os" import "core:fmt"
_, _ :: time, thread
TEST_count := 0
TEST_fail := 0
t := &testing.T{}
when ODIN_TEST {
expect :: testing.expect
log :: testing.log
} else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
TEST_count += 1
if !condition {
TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message)
return
}
}
log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc)
fmt.printf("log: %v\n", v)
}
}
_tracking_allocator := mem.Tracking_Allocator{}
print_tracking_allocator_report :: proc() {
for _, leak in _tracking_allocator.allocation_map {
fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
}
for bf in _tracking_allocator.bad_free_array {
fmt.printf("%v allocation %p was freed badly\n", bf.location, bf.memory)
}
}
main :: proc() {
mem.tracking_allocator_init(&_tracking_allocator, context.allocator)
context.allocator = mem.tracking_allocator(&_tracking_allocator)
address_parsing_test(t)
tcp_tests(t)
split_url_test(t)
join_url_test(t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
print_tracking_allocator_report()
if TEST_fail > 0 {
os.exit(1)
}
}
@test @test
address_parsing_test :: proc(t: ^testing.T) { address_parsing_test :: proc(t: ^testing.T) {
@@ -89,127 +30,66 @@ address_parsing_test :: proc(t: ^testing.T) {
} }
valid := len(vector.binstr) > 0 valid := len(vector.binstr) > 0
fmt.printf("%v %v\n", kind, vector.input)
msg := "-set a proper message-"
switch vector.family { switch vector.family {
case .IP4, .IP4_Alt: case .IP4, .IP4_Alt:
/* // Does `net.parse_ip4_address` think we parsed the address properly?
Does `net.parse_ip4_address` think we parsed the address properly?
*/
non_decimal := vector.family == .IP4_Alt non_decimal := vector.family == .IP4_Alt
any_addr := net.parse_address(vector.input, non_decimal)
parsed_ok := any_addr != nil
parsed: net.IP4_Address
any_addr := net.parse_address(vector.input, non_decimal) // Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake.
parsed_ok := any_addr != nil
parsed: net.IP4_Address
/*
Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake.
*/
switch addr in any_addr { switch addr in any_addr {
case net.IP4_Address: case net.IP4_Address:
parsed = addr parsed = addr
case net.IP6_Address: case net.IP6_Address:
parsed_ok = false parsed_ok = false
msg = fmt.tprintf("parse_address mistook %v as IPv6 address %04x", vector.input, addr) testing.expectf(t, false, "parse_address mistook %v as IPv6 address %04x", vector.input, addr)
expect(t, false, msg)
} }
if !parsed_ok && valid { if !parsed_ok && valid {
msg = fmt.tprintf("parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(vector.binstr)) testing.expectf(t, parsed_ok == valid, "parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(t, vector.binstr))
} else if parsed_ok && !valid { } else if parsed_ok && !valid {
msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) testing.expectf(t, parsed_ok == valid, "parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed)
} }
expect(t, parsed_ok == valid, msg)
if valid && parsed_ok { if valid && parsed_ok {
actual_binary := address_to_binstr(parsed) actual_binary := address_to_binstr(parsed)
msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) testing.expectf(t, actual_binary == vector.binstr, "parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
expect(t, actual_binary == vector.binstr, msg)
/* // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed.
Do we turn an address back into the same string properly?
No point in testing the roundtrip if the first part failed.
*/
if len(vector.output) > 0 && actual_binary == vector.binstr { if len(vector.output) > 0 && actual_binary == vector.binstr {
stringified := net.address_to_string(parsed) stringified := net.address_to_string(parsed)
msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
expect(t, stringified == vector.output, msg)
} }
} }
case .IP6: case .IP6:
/* // Do we parse the address properly?
Do we parse the address properly?
*/
parsed, parsed_ok := net.parse_ip6_address(vector.input) parsed, parsed_ok := net.parse_ip6_address(vector.input)
if !parsed_ok && valid { if !parsed_ok && valid {
msg = fmt.tprintf("parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(vector.binstr)) testing.expectf(t, parsed_ok == valid, "parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(t, vector.binstr))
} else if parsed_ok && !valid { } else if parsed_ok && !valid {
msg = fmt.tprintf("parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) testing.expectf(t, parsed_ok == valid, "parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed)
} }
expect(t, parsed_ok == valid, msg)
if valid && parsed_ok { if valid && parsed_ok {
actual_binary := address_to_binstr(parsed) actual_binary := address_to_binstr(parsed)
msg = fmt.tprintf("parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) testing.expectf(t, actual_binary == vector.binstr, "parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
expect(t, actual_binary == vector.binstr, msg)
/* // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed.
Do we turn an address back into the same string properly?
No point in testing the roundtrip if the first part failed.
*/
if len(vector.output) > 0 && actual_binary == vector.binstr { if len(vector.output) > 0 && actual_binary == vector.binstr {
stringified := net.address_to_string(parsed) stringified := net.address_to_string(parsed)
msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
expect(t, stringified == vector.output, msg)
} }
} }
} }
} }
} }
address_to_binstr :: proc(address: net.Address) -> (binstr: string) {
switch t in address {
case net.IP4_Address:
b := transmute(u32be)t
return fmt.tprintf("%08x", b)
case net.IP6_Address:
b := transmute(u128be)t
return fmt.tprintf("%32x", b)
case:
return ""
}
unreachable()
}
binstr_to_address :: proc(binstr: string) -> (address: net.Address) {
switch len(binstr) {
case 8: // IPv4
a, ok := strconv.parse_u64_of_base(binstr, 16)
expect(t, ok, "failed to parse test case bin string")
ipv4 := u32be(a)
return net.IP4_Address(transmute([4]u8)ipv4)
case 32: // IPv6
a, ok := strconv.parse_u128_of_base(binstr, 16)
expect(t, ok, "failed to parse test case bin string")
ipv4 := u128be(a)
return net.IP6_Address(transmute([8]u16be)ipv4)
case 0:
return nil
}
panic("Invalid test case")
}
Kind :: enum { Kind :: enum {
IP4, // Decimal IPv4 IP4, // Decimal IPv4
IP4_Alt, // Non-decimal address IP4_Alt, // Non-decimal address
@@ -223,10 +103,7 @@ IP_Address_Parsing_Test_Vector :: struct {
// Input address to try and parse. // Input address to try and parse.
input: string, input: string,
/* // Hexadecimal representation of the expected numeric value of the address. Zero length means input is invalid and the parser should report failure.
Hexadecimal representation of the expected numeric value of the address.
Zero length means input is invalid and the parser should report failure.
*/
binstr: string, binstr: string,
// Expected `address_to_string` output, if a valid input and this string is non-empty. // Expected `address_to_string` output, if a valid input and this string is non-empty.
@@ -335,38 +212,30 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{
{ .IP6, "c0a8", "", ""}, { .IP6, "c0a8", "", ""},
} }
tcp_tests :: proc(t: ^testing.T) { ENDPOINT_TWO_SERVERS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9991}
fmt.println("Testing two servers trying to bind to the same endpoint...") ENDPOINT_CLOSED_PORT := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9992}
two_servers_binding_same_endpoint(t) ENDPOINT_SERVER_SENDS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9993}
fmt.println("Testing client connecting to a closed port...")
client_connects_to_closed_port(t)
fmt.println("Testing client sending server data...")
client_sends_server_data(t)
}
ENDPOINT := net.Endpoint{
net.IP4_Address{127, 0, 0, 1},
9999,
}
@(test) @(test)
two_servers_binding_same_endpoint :: proc(t: ^testing.T) { two_servers_binding_same_endpoint :: proc(t: ^testing.T) {
skt1, err1 := net.listen_tcp(ENDPOINT) skt1, err1 := net.listen_tcp(ENDPOINT_TWO_SERVERS)
defer net.close(skt1) defer net.close(skt1)
skt2, err2 := net.listen_tcp(ENDPOINT) skt2, err2 := net.listen_tcp(ENDPOINT_TWO_SERVERS)
defer net.close(skt2) defer net.close(skt2)
expect(t, err1 == nil, "expected first server binding to endpoint to do so without error") testing.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") testing.expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use")
} }
@(test) @(test)
client_connects_to_closed_port :: proc(t: ^testing.T) { client_connects_to_closed_port :: proc(t: ^testing.T) {
skt, err := net.dial_tcp(ENDPOINT)
skt, err := net.dial_tcp(ENDPOINT_CLOSED_PORT)
defer net.close(skt) defer net.close(skt)
expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused") testing.expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused")
} }
@(test) @(test)
client_sends_server_data :: proc(t: ^testing.T) { client_sends_server_data :: proc(t: ^testing.T) {
CONTENT: string: "Hellope!" CONTENT: string: "Hellope!"
@@ -390,8 +259,8 @@ client_sends_server_data :: proc(t: ^testing.T) {
defer sync.wait_group_done(r.wg) defer sync.wait_group_done(r.wg)
if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil { if r.skt, r.err = net.dial_tcp(ENDPOINT_SERVER_SENDS); r.err != nil {
log(r.t, r.err) testing.expectf(r.t, false, "[tcp_client:dial_tcp] %v", r.err)
return return
} }
@@ -405,19 +274,17 @@ client_sends_server_data :: proc(t: ^testing.T) {
defer sync.wait_group_done(r.wg) defer sync.wait_group_done(r.wg)
log(r.t, "tcp_server listen") if r.skt, r.err = net.listen_tcp(ENDPOINT_SERVER_SENDS); r.err != nil {
if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil {
sync.wait_group_done(r.wg) sync.wait_group_done(r.wg)
log(r.t, r.err) testing.expectf(r.t, false, "[tcp_server:listen_tcp] %v", r.err)
return return
} }
sync.wait_group_done(r.wg) sync.wait_group_done(r.wg)
log(r.t, "tcp_server accept")
client: net.TCP_Socket client: net.TCP_Socket
if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil { if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil {
log(r.t, r.err) testing.expectf(r.t, false, "[tcp_server:accept_tcp] %v", r.err)
return return
} }
defer net.close(client) defer net.close(client)
@@ -437,10 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) {
thread_data[0].wg = &wg thread_data[0].wg = &wg
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)
log(t, "waiting for server to start listening")
sync.wait_group_wait(&wg) sync.wait_group_wait(&wg)
log(t, "starting up client")
sync.wait_group_add(&wg, 2) sync.wait_group_add(&wg, 2)
thread_data[1].t = t thread_data[1].t = t
@@ -454,20 +318,15 @@ client_sends_server_data :: proc(t: ^testing.T) {
net.close(thread_data[1].skt) net.close(thread_data[1].skt)
thread.destroy(thread_data[1].tid) thread.destroy(thread_data[1].tid)
} }
log(t, "waiting for threads to finish")
sync.wait_group_wait(&wg) sync.wait_group_wait(&wg)
log(t, "threads finished")
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) testing.expectf(t, okay, "Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err)
expect(t, okay, msg)
received := string(thread_data[0].data[:thread_data[0].length]) received := string(thread_data[0].data[:thread_data[0].length])
okay = received == CONTENT okay = received == CONTENT
msg = fmt.tprintf("Expected client to send \"{}\", got \"{}\"", CONTENT, received) testing.expectf(t, okay, "Expected client to send \"{}\", got \"{}\"", CONTENT, received)
expect(t, okay, msg)
} }
URL_Test :: struct { URL_Test :: struct {
@@ -559,22 +418,15 @@ split_url_test :: proc(t: ^testing.T) {
delete(test.queries) delete(test.queries)
} }
msg := fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.scheme, scheme) testing.expectf(t, scheme == test.scheme, "Expected `net.split_url` to return %s, got %s", test.scheme, scheme)
expect(t, scheme == test.scheme, msg) testing.expectf(t, host == test.host, "Expected `net.split_url` to return %s, got %s", test.host, host)
msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.host, host) testing.expectf(t, path == test.path, "Expected `net.split_url` to return %s, got %s", test.path, path)
expect(t, host == test.host, msg) testing.expectf(t, len(queries) == len(test.queries), "Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries))
msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.path, path)
expect(t, path == test.path, msg)
msg = fmt.tprintf("Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries))
expect(t, len(queries) == len(test.queries), msg)
for k, v in queries { for k, v in queries {
expected := test.queries[k] expected := test.queries[k]
msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", expected, v) testing.expectf(t, v == expected, "Expected `net.split_url` to return %s, got %s", expected, v)
expect(t, v == expected, msg)
} }
msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.fragment, fragment) testing.expectf(t, fragment == test.fragment, "Expected `net.split_url` to return %s, got %s", test.fragment, fragment)
expect(t, fragment == test.fragment, msg)
} }
} }
@@ -659,7 +511,45 @@ join_url_test :: proc(t: ^testing.T) {
for test_url in test.url { for test_url in test.url {
pass |= url == test_url pass |= url == test_url
} }
msg := fmt.tprintf("Expected `net.join_url` to return one of %s, got %s", test.url, url) testing.expectf(t, pass, "Expected `net.join_url` to return one of %s, got %s", test.url, url)
expect(t, pass, msg)
} }
} }
@(private)
address_to_binstr :: proc(address: net.Address) -> (binstr: string) {
switch t in address {
case net.IP4_Address:
b := transmute(u32be)t
return fmt.tprintf("%08x", b)
case net.IP6_Address:
b := transmute(u128be)t
return fmt.tprintf("%32x", b)
case:
return ""
}
unreachable()
}
@(private)
binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Address) {
switch len(binstr) {
case 8: // IPv4
a, ok := strconv.parse_u64_of_base(binstr, 16)
testing.expect(t, ok, "failed to parse test case bin string")
ipv4 := u32be(a)
return net.IP4_Address(transmute([4]u8)ipv4)
case 32: // IPv6
a, ok := strconv.parse_u128_of_base(binstr, 16)
testing.expect(t, ok, "failed to parse test case bin string")
ipv4 := u128be(a)
return net.IP6_Address(transmute([8]u16be)ipv4)
case 0:
return nil
}
panic("Invalid test case")
}