Skip to content

Commit 4bf849c

Browse files
authored
Rewrite, update, and refactor sockets-related tests (WebAssembly#768)
In WebAssembly#762 I'm seeing an error related to `EADDRINUSE` which I can't reproduce locally. Tests currently all use the same port which means that they are required to be run serially and additionally may run into this risk if the port happens to already be taken. I don't really know what's going on in CI, but I wanted to nevertheless refactor these tests. This commit rearchitects, rewrites, and fixes sockets-related tests. Changes here are: * Servers now print the port they're listening to. Clients read this port and then connect to that port. This orchestration happens through some specially crafted bash and CMake, similar to before. All tests now bind to port 0 to get an OS-assigned port instead of picking a port. * All tests are now ungated, notably enabling some nonblocking I/O tests that were previously gated. * A number of tests are refactored or updated to be less flaky. Many tests were failing nondeterministically locally due to how the test was constructed and assuming certain things were ready when they may not be. Some tests were entirely rewritten, such as `sockets-nonblocking-multiple.c`, while others were only lightly refactored to `poll` in a few more places. * All tests are updated to pass in less than 100ms. Some tests before hung for awhile as timeouts were reached for example, but this wasn't a necessary part of the test. Other tests for nonblocking I/O had many calls to `poll` with a number of them reaching the timeout. These are rewritten to use a single call to `poll` with no timeout which enables the test to progress much faster as timeouts are never reached. Overall the intent is that the sockets-related tests are more comprehensive now, more robust and/or less flaky, and work in more environments.
1 parent 1305fb6 commit 4bf849c

25 files changed

Lines changed: 888 additions & 861 deletions

test/CMakeLists.txt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ function(register_test test_name executable_name)
123123
list(APPEND wasmtime_args --dir ${fsdir}::/)
124124
endif()
125125
if (arg_NETWORK)
126-
list(APPEND wasmtime_args -Sinherit-network)
126+
list(APPEND wasmtime_args -Sinherit-network,allow-ip-name-lookup)
127127
endif()
128128
foreach(env IN LISTS arg_ENV)
129129
list(APPEND wasmtime_args --env ${env})
@@ -135,7 +135,6 @@ function(register_test test_name executable_name)
135135
list(APPEND wasmtime_args --wasm component-model-async)
136136
list(APPEND wasmtime_args --wasi p3)
137137
endif()
138-
list(APPEND wasmtime_args --wasi allow-ip-name-lookup)
139138

140139
add_test(
141140
NAME "${test_name}"
@@ -155,12 +154,6 @@ function(register_test test_name executable_name)
155154
set_tests_properties("${test_name}" PROPERTIES FIXTURES_REQUIRED "fs_${test_name}")
156155
endif()
157156

158-
# All sockets tests use the same port right now, so only one can run at a
159-
# time.
160-
if (arg_NETWORK)
161-
set_tests_properties(${test_name} PROPERTIES RESOURCE_LOCK socket-test)
162-
endif()
163-
164157
if (arg_PASS_REGULAR_EXPRESSION)
165158
set_tests_properties(${test_name} PROPERTIES PASS_REGULAR_EXPRESSION "${arg_PASS_REGULAR_EXPRESSION}")
166159
endif()
@@ -341,10 +334,8 @@ if (NOT (WASI STREQUAL "p1"))
341334
add_wasilibc_test(sockets-fcntl.c NETWORK)
342335
add_wasilibc_test(sockets-udp-connected.c NETWORK)
343336
add_wasilibc_test(getaddrinfo.c NETWORK)
344-
345-
# TODO: flaky tests
346-
# add_wasilibc_test(sockets-nonblocking.c NETWORK)
347-
# add_wasilibc_test(sockets-nonblocking-udp-no-connection.c NETWORK)
337+
add_wasilibc_test(sockets-nonblocking.c NETWORK FAILP3)
338+
add_wasilibc_test(sockets-nonblocking-udp-no-connection.c NETWORK FAILP3)
348339

349340
# Define executables for server/client tests, and they're paired together in
350341
# various combinations below for various tests.
@@ -387,7 +378,6 @@ if (NOT (WASI STREQUAL "p1"))
387378
-DNCLIENTS=${arg_NCLIENTS}
388379
-P ${CMAKE_CURRENT_SOURCE_DIR}/socket-test.cmake
389380
)
390-
set_tests_properties(${test_name} PROPERTIES RESOURCE_LOCK socket-test)
391381

392382
if (arg_FAILP3 AND (WASI STREQUAL "p3"))
393383
set_tests_properties(${test_name} PROPERTIES WILL_FAIL TRUE)

test/socket-test.cmake

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,39 @@ if (NOT NCLIENTS)
1010
endif()
1111

1212
foreach(i RANGE 1 ${NCLIENTS})
13-
list(APPEND CLIENTS COMMAND bash -c "exec ${ENGINE} -Wcomponent-model-async -Sp3,inherit-network ${CLIENT} 1>&2")
13+
# This is a bit tricky to setup, but the flow is:
14+
#
15+
# * The first line of output from the server is the port number that it's
16+
# listening on.
17+
#
18+
# * This is read by the script below, and then printed again. In case it wasn't
19+
# actually a port number, or if there's more clients, this means that the port
20+
# will show up to the next client or print at the end if it's an error.
21+
#
22+
# * Next the shell's stdout is closed by putting stderr into the stdout slot.
23+
# This closes stdout for the next client in the pipeline or the final cmake
24+
# reader itself. Importantly this means that any prints of the client will
25+
# show up on stderr which will make its way out of cmake.
26+
#
27+
# * Finally the client is run with the port as an argument.
28+
#
29+
# Overall this should handle cross-process synchronization to let the server bind
30+
# any arbitrary port, print out the port, and then thread that port to any number
31+
# of clients. All the while all other output from the tests is still surfaced in
32+
# CMake to debug and such. A bit baroque but we are scripting in CMake after all
33+
# so there's only so much that can be done.
34+
set(client_script "
35+
read port # read the first line of stdin from the previous process
36+
echo $port # forward this line to the next process, or out to cmake itself
37+
exec 2>&1 # close our stdout and replace it with stderr
38+
cat <&0 & # forward the rest of stdin to stderr so it shows up in cmake
39+
exec ${ENGINE} -Wcomponent-model-async -Sp3,inherit-network ${CLIENT} \"$port\"
40+
")
41+
list(APPEND CLIENTS COMMAND bash -c ${client_script})
1442
endforeach()
1543

1644
execute_process(
17-
COMMAND bash -c "exec ${ENGINE} -Wcomponent-model-async -Sp3,inherit-network ${SERVER} 1>&2"
45+
COMMAND ${ENGINE} -Wcomponent-model-async -Sp3,inherit-network ${SERVER}
1846
${CLIENTS}
1947
TIMEOUT 5
2048
COMMAND_ERROR_IS_FATAL ANY

test/src/poll-connect.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,20 @@ int main() {
115115

116116
// Test that we can `recv` what we `send`
117117
{
118-
// Send some data from the server to the client.
119118
uint8_t data[5] = {1, 2, 3, 4, 5};
120-
TEST(send(server_client, data, sizeof(data), 0) == sizeof(data));
119+
while (!t_status) {
120+
// Send some data from the server to the client.
121+
ssize_t sent = send(server_client, data, sizeof(data), 0);
122+
if (sent != -1) {
123+
TEST2(sent == sizeof(data));
124+
break;
125+
}
126+
if (errno != EWOULDBLOCK)
127+
t_error("send failed (errno = %d)\n", errno);
128+
struct pollfd fd = {
129+
.fd = server_client, .events = POLLWRNORM, .revents = 0};
130+
TEST(poll(&fd, 1, -1) != -1);
131+
}
121132

122133
fds[0].events = POLLRDNORM;
123134
errno = 0;

test/src/poll-nonblocking-socket.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,21 @@ void test_tcp_client() {
2626
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
2727
TEST(socket_fd != -1);
2828

29-
int server_port = 4001;
29+
// Bind a socket to get a free port, and leave it open. Note that `listen`
30+
// is specifically not used to refuse future `connect`s.
31+
int tmp_fd = socket(AF_INET, SOCK_STREAM, 0);
32+
struct sockaddr_in tmp_addr;
33+
socklen_t tmp_addr_len = sizeof(tmp_addr);
34+
tmp_addr.sin_family = AF_INET;
35+
tmp_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
36+
tmp_addr.sin_port = 0;
37+
TEST(bind(tmp_fd, (struct sockaddr *)&tmp_addr, sizeof(tmp_addr)) != -1);
38+
TEST(getsockname(tmp_fd, (struct sockaddr *)&tmp_addr, &tmp_addr_len) != -1);
39+
int server_port = ntohs(tmp_addr.sin_port);
3040

3141
// Check that if a non-blocking socket is in progress, poll() indicates the
3242
// right result
33-
socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
43+
TEST((socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) != -1);
3444
struct sockaddr_in sockaddr_in;
3545
// Doesn't matter what address we connect to, since we only attempt to connect
3646
// once
@@ -44,14 +54,17 @@ void test_tcp_client() {
4454

4555
// Poll the socket for writability
4656
struct pollfd poll_fd = {.fd = socket_fd, .events = POLLWRNORM, .revents = 0};
47-
poll(&poll_fd, 1, 10);
57+
TEST(poll(&poll_fd, 1, 10) != -1);
4858

4959
// Socket should not be writable
5060
int32_t error = -1;
5161
socklen_t len = -1;
5262
TEST(getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) == 0);
5363
TEST(error == ECONNREFUSED);
54-
close(socket_fd);
64+
TEST(close(socket_fd) == 0);
65+
66+
// Release our port which is now no longer needed.
67+
TEST(close(tmp_fd) == 0);
5568
}
5669

5770
int main(void) {

test/src/sockets-client-handle-hangups.c

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,9 @@
1919

2020
int BUFSIZE = 256;
2121

22-
static int wait_for_server(struct sockaddr_in *addr) {
23-
for (int attempt = 0; attempt < 200; attempt++) {
24-
int fd = socket(AF_INET, SOCK_STREAM, 0);
25-
TEST(fd != -1);
26-
if (connect(fd, (struct sockaddr *)addr, sizeof(*addr)) != -1)
27-
return fd;
28-
close(fd);
29-
usleep(5000); // sleep for 5ms
30-
}
31-
return -1;
32-
}
33-
3422
// Server must be running already as a separate executable
3523
// (except when testing the case where connect() fails)
36-
void test_tcp_client() {
37-
// Prepare server socket
38-
int server_port = 4001;
24+
void test_tcp_client(int server_port) {
3925

4026
// Prepare sockaddr_in for client
4127
struct sockaddr_in sockaddr_in;
@@ -48,14 +34,19 @@ void test_tcp_client() {
4834
int len = strlen(message);
4935
char client_buffer[BUFSIZE];
5036

51-
int socket_fd = wait_for_server(&sockaddr_in);
52-
if (socket_fd == -1)
37+
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
38+
TEST(socket_fd != -1);
39+
if (connect(socket_fd, (struct sockaddr *)&sockaddr_in,
40+
sizeof(sockaddr_in)) == -1) {
41+
close(socket_fd);
5342
return;
43+
}
5444

5545
// Client writes a message to server
5646
// This should succeed even in the presence of hangups,
5747
// since send() only fails if there's a local error
5848
TEST(send(socket_fd, message, len, 0) == len);
49+
TEST(shutdown(socket_fd, SHUT_WR) != -1);
5950

6051
// Set timeout for recv
6152
struct timeval tv;
@@ -74,8 +65,18 @@ void test_tcp_client() {
7465
close(socket_fd);
7566
}
7667

77-
int main(void) {
78-
test_tcp_client();
68+
int main(int argc, char *argv[]) {
69+
if (argc != 2) {
70+
fprintf(stderr, "Usage: %s <server_port>\n", argv[0]);
71+
return 1;
72+
}
73+
int port;
74+
if (sscanf(argv[1], "%d", &port) != 1) {
75+
// test a port without anything listening on it
76+
test_tcp_client(1);
77+
} else {
78+
test_tcp_client(port);
79+
}
7980

8081
return t_status;
8182
}

test/src/sockets-client-hangup-after-connect.c

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,36 @@
1919

2020
int BUFSIZE = 256;
2121

22-
static int wait_for_server(struct sockaddr_in *addr) {
23-
for (int attempt = 0; attempt < 200; attempt++) {
24-
int fd = socket(AF_INET, SOCK_STREAM, 0);
25-
TEST(fd != -1);
26-
if (connect(fd, (struct sockaddr *)addr, sizeof(*addr)) != -1)
27-
return fd;
28-
close(fd);
29-
usleep(5000); // sleep for 5ms
30-
}
31-
t_error("server didn't come online within 1 second (errno = %d)\n", errno);
32-
return -1;
33-
}
34-
3522
// Server must be running already as a separate executable
3623
// (except when testing the case where connect() fails)
37-
void test_tcp_client() {
38-
// Prepare server socket
39-
int server_port = 4001;
24+
void test_tcp_client(int server_port) {
4025

4126
// Prepare sockaddr_in for client
4227
struct sockaddr_in sockaddr_in;
4328
sockaddr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
4429
sockaddr_in.sin_family = AF_INET;
4530
sockaddr_in.sin_port = htons(server_port);
4631

47-
// Connect from client
48-
int socket_fd = wait_for_server(&sockaddr_in);
32+
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
33+
TEST(socket_fd != -1);
34+
TEST(connect(socket_fd, (struct sockaddr *)&sockaddr_in,
35+
sizeof(sockaddr_in)) == 0);
4936

5037
// Hang up immediately
5138
close(socket_fd);
5239
}
5340

54-
int main(void) {
55-
test_tcp_client();
41+
int main(int argc, char *argv[]) {
42+
if (argc != 2) {
43+
fprintf(stderr, "Usage: %s <server_port>\n", argv[0]);
44+
return 1;
45+
}
46+
int port;
47+
if (sscanf(argv[1], "%d", &port) != 1) {
48+
fprintf(stderr, "Invalid port number: %s\n", argv[1]);
49+
return 1;
50+
}
51+
test_tcp_client(port);
5652

5753
return t_status;
5854
}

test/src/sockets-client-hangup-after-sending.c

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,8 @@
1919

2020
int BUFSIZE = 256;
2121

22-
static int wait_for_server(struct sockaddr_in *addr) {
23-
for (int attempt = 0; attempt < 200; attempt++) {
24-
int fd = socket(AF_INET, SOCK_STREAM, 0);
25-
TEST(fd != -1);
26-
if (connect(fd, (struct sockaddr *)addr, sizeof(*addr)) != -1)
27-
return fd;
28-
close(fd);
29-
usleep(5000); // sleep for 5ms
30-
}
31-
t_error("server didn't come online within 1 second (errno = %d)\n", errno);
32-
return -1;
33-
}
34-
3522
// See sockets-server.c -- must be running already as a separate executable
36-
void test_tcp_client() {
37-
// Prepare server socket
38-
int server_port = 4001;
23+
void test_tcp_client(int server_port) {
3924

4025
// Prepare sockaddr_in for client
4126
struct sockaddr_in sockaddr_in;
@@ -47,7 +32,10 @@ void test_tcp_client() {
4732
char message[] = "There's gonna be a party when the wolf comes home";
4833
int len = strlen(message);
4934

50-
int socket_fd = wait_for_server(&sockaddr_in);
35+
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
36+
TEST(socket_fd != -1);
37+
TEST(connect(socket_fd, (struct sockaddr *)&sockaddr_in,
38+
sizeof(sockaddr_in)) == 0);
5139

5240
// Client writes a message to server
5341
TEST(send(socket_fd, message, len, 0) == len);
@@ -56,8 +44,17 @@ void test_tcp_client() {
5644
close(socket_fd);
5745
}
5846

59-
int main(void) {
60-
test_tcp_client();
47+
int main(int argc, char *argv[]) {
48+
if (argc != 2) {
49+
fprintf(stderr, "Usage: %s <server_port>\n", argv[0]);
50+
return 1;
51+
}
52+
int port;
53+
if (sscanf(argv[1], "%d", &port) != 1) {
54+
fprintf(stderr, "Invalid port number: %s\n", argv[1]);
55+
return 1;
56+
}
57+
test_tcp_client(port);
6158

6259
return t_status;
6360
}

test/src/sockets-client-hangup-while-receiving.c

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,8 @@
1919

2020
int BUFSIZE = 256;
2121

22-
static int wait_for_server(struct sockaddr_in *addr) {
23-
for (int attempt = 0; attempt < 200; attempt++) {
24-
int fd = socket(AF_INET, SOCK_STREAM, 0);
25-
TEST(fd != -1);
26-
if (connect(fd, (struct sockaddr *)addr, sizeof(*addr)) != -1)
27-
return fd;
28-
close(fd);
29-
usleep(5000); // sleep for 5ms
30-
}
31-
t_error("server didn't come online within 1 second (errno = %d)\n", errno);
32-
return -1;
33-
}
34-
3522
// See sockets-server.c -- must be running already as a separate executable
36-
void test_tcp_client() {
37-
// Prepare server socket
38-
int server_port = 4001;
23+
void test_tcp_client(int server_port) {
3924

4025
// Prepare sockaddr_in for client
4126
struct sockaddr_in sockaddr_in;
@@ -48,7 +33,10 @@ void test_tcp_client() {
4833
int len = strlen(message);
4934
char client_buffer[BUFSIZE];
5035

51-
int socket_fd = wait_for_server(&sockaddr_in);
36+
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
37+
TEST(socket_fd != -1);
38+
TEST(connect(socket_fd, (struct sockaddr *)&sockaddr_in,
39+
sizeof(sockaddr_in)) == 0);
5240

5341
// Client writes a message to server
5442
TEST(send(socket_fd, message, len, 0) == len);
@@ -65,8 +53,17 @@ void test_tcp_client() {
6553
close(socket_fd);
6654
}
6755

68-
int main(void) {
69-
test_tcp_client();
56+
int main(int argc, char *argv[]) {
57+
if (argc != 2) {
58+
fprintf(stderr, "Usage: %s <server_port>\n", argv[0]);
59+
return 1;
60+
}
61+
int port;
62+
if (sscanf(argv[1], "%d", &port) != 1) {
63+
fprintf(stderr, "Invalid port number: %s\n", argv[1]);
64+
return 1;
65+
}
66+
test_tcp_client(port);
7067

7168
return t_status;
7269
}

0 commit comments

Comments
 (0)