From 597bf2720436e7e39ed88e14f40fc27b2b8a3487 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Sun, 17 Sep 2017 19:39:13 +0100 Subject: [PATCH] Gracefully exit programs during tests. Makes the test environment a bit more robust. Also makes a lot of the tests pass under Wine(!). --- tests/lib/IPXWrapper/Tool/Bind.pm | 13 +++++++- tests/lib/IPXWrapper/Tool/Generic.pm | 19 +++++++++-- tests/lib/IPXWrapper/Tool/IPXISR.pm | 33 ++++++++++++++++--- tests/lib/IPXWrapper/Tool/IPXRecv.pm | 34 ++++++++++++++++--- tools/bind.c | 9 ++--- tools/ipx-isr.c | 49 +++++++++++++++++++++------- tools/ipx-recv.c | 24 +++++++++++++- tools/spx-server.c | 24 +++++++++++++- 8 files changed, 171 insertions(+), 34 deletions(-) diff --git a/tests/lib/IPXWrapper/Tool/Bind.pm b/tests/lib/IPXWrapper/Tool/Bind.pm index 31e2afb..57c5915 100644 --- a/tests/lib/IPXWrapper/Tool/Bind.pm +++ b/tests/lib/IPXWrapper/Tool/Bind.pm @@ -36,6 +36,7 @@ sub new my $self = bless({ pid => $pid, + stdin => $in, sockets => [], }, $class); @@ -72,8 +73,18 @@ sub DESTROY { my ($self) = @_; - kill(SIGKILL, $self->{pid}); + # bind.exe will exit once it reads EOF. + delete $self->{stdin}; + + local $SIG{ALRM} = sub + { + warn "Killing hung bind.exe process"; + kill(SIGKILL, $self->{pid}); + }; + + alarm(5); waitpid($self->{pid}, 0); + alarm(0); } sub result diff --git a/tests/lib/IPXWrapper/Tool/Generic.pm b/tests/lib/IPXWrapper/Tool/Generic.pm index 6ef797b..7f3e4f9 100644 --- a/tests/lib/IPXWrapper/Tool/Generic.pm +++ b/tests/lib/IPXWrapper/Tool/Generic.pm @@ -33,7 +33,10 @@ sub new # No need for error checking here - open3 throws on failure. my $pid = open3(my $in, my $out, undef, @command); - my $self = bless(\$pid, $class); + my $self = bless({ + pid => $pid, + in => $in, + }, $class); my $output = ""; @@ -56,8 +59,18 @@ sub DESTROY { my ($self) = @_; - kill(SIGKILL, $$self); - waitpid($$self, 0); + # Process should exit once it gets EOF + delete $self->{in}; + + local $SIG{ALRM} = sub + { + warn "Killing hung process"; + kill(SIGKILL, $self->{pid}); + }; + + alarm(5); + waitpid($self->{pid}, 0); + alarm(0); } 1; diff --git a/tests/lib/IPXWrapper/Tool/IPXISR.pm b/tests/lib/IPXWrapper/Tool/IPXISR.pm index 30a1e52..e7d4de9 100644 --- a/tests/lib/IPXWrapper/Tool/IPXISR.pm +++ b/tests/lib/IPXWrapper/Tool/IPXISR.pm @@ -65,11 +65,32 @@ sub DESTROY if(defined($self->{pid})) { - kill(SIGKILL, $self->{pid}); - waitpid($self->{pid}, 0); + $self->_end(); } } +sub _end +{ + my ($self) = @_; + + # ipx-isr.exe will exit when it reads EOF + delete $self->{in}; + + local $SIG{ALRM} = sub + { + warn "Killing hung ipx-isr.exe process"; + kill(SIGKILL, $self->{pid}); + }; + + alarm(5); + waitpid($self->{pid}, 0); + alarm(0); + + delete $self->{pid}; + + return ($? == 0); +} + sub kill_and_read { my ($self) = @_; @@ -82,9 +103,11 @@ sub kill_and_read # Kill the child process so we can read EOF from the pipe once we have # all the output. - kill(SIGKILL, $self->{pid}); - waitpid($self->{pid}, 0); - delete $self->{pid}; + if(!$self->_end()) + { + # Didn't exit properly, pipe might still have a writer. + die "ipx-isr.exe didn't exit cleanly"; + } my $out = $self->{out}; my @packets = (); diff --git a/tests/lib/IPXWrapper/Tool/IPXRecv.pm b/tests/lib/IPXWrapper/Tool/IPXRecv.pm index 08c9934..f8251b6 100644 --- a/tests/lib/IPXWrapper/Tool/IPXRecv.pm +++ b/tests/lib/IPXWrapper/Tool/IPXRecv.pm @@ -38,6 +38,7 @@ sub new my $self = bless({ pid => $pid, out => $out, + in => $in, sockets => {}, }, $class); @@ -73,11 +74,32 @@ sub DESTROY if(defined($self->{pid})) { - kill(SIGKILL, $self->{pid}); - waitpid($self->{pid}, 0); + $self->_end(); } } +sub _end +{ + my ($self) = @_; + + # ipx-recv.exe will exit once we close its stdin + delete $self->{in}; + + local $SIG{ALRM} = sub + { + warn "Killing hung ipx-recv.exe process"; + kill(SIGKILL, $self->{pid}); + }; + + alarm(5); + waitpid($self->{pid}, 0); + alarm(0); + + delete $self->{pid}; + + return ($? == 0); +} + sub kill_and_read { my ($self) = @_; @@ -90,9 +112,11 @@ sub kill_and_read # Kill the child process so we can read EOF from the pipe once we have # all the output. - kill(SIGKILL, $self->{pid}); - waitpid($self->{pid}, 0); - delete $self->{pid}; + if(!$self->_end()) + { + # Didn't exit properly, pipe might still have a writer. + die "ipx-recv.exe didn't exit cleanly"; + } my $out = $self->{out}; my @packets = (); diff --git a/tools/bind.c b/tools/bind.c index 46002fd..698c5c9 100644 --- a/tools/bind.c +++ b/tools/bind.c @@ -97,13 +97,10 @@ int main(int argc, const char **argv) */ printf("Ready\n"); - /* Hang around until we're killed. This is to facilitate testing bind - * behaviour in concurrent processes. + /* Hang around until something is written to our stdin. This is to + * facilitate testing bind behaviour in concurrent processes. */ - while(1) - { - Sleep(10000); - } + getchar(); return 0; } diff --git a/tools/ipx-isr.c b/tools/ipx-isr.c index f599906..1a7fe39 100644 --- a/tools/ipx-isr.c +++ b/tools/ipx-isr.c @@ -96,30 +96,55 @@ int main(int argc, char **argv) assert(bind(sock, (struct sockaddr*)(&local_addr), sizeof(local_addr)) == 0); - assert(CreateThread(NULL, 0, &send_thread, &sock, 0, NULL)); + HANDLE send_thread_h = CreateThread(NULL, 0, &send_thread, &sock, 0, NULL); + assert(send_thread_h != NULL); printf("Ready\n"); char buf[1024]; while(1) { - struct sockaddr_ipx recv_addr; - int addrlen = sizeof(recv_addr); + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(sock, &read_fds); - int r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)(&recv_addr), &addrlen); - assert(r > 0); + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = 100000, /* 1/10th sec */ + }; - buf[r] = '\0'; + assert(select(sock + 1, &read_fds, NULL, NULL, &timeout) >= 0); - char net_s[ADDR32_STRING_SIZE]; - addr32_string(net_s, addr32_in(recv_addr.sa_netnum)); + if(WaitForSingleObject(send_thread_h, 0) == WAIT_OBJECT_0) + { + /* Send thread ended, must've hit EOF. Time to exit */ + break; + } - char node_s[ADDR48_STRING_SIZE]; - addr48_string(node_s, addr48_in(recv_addr.sa_nodenum)); - - printf("%s %s %hu %s\n", net_s, node_s, ntohs(recv_addr.sa_socket), buf); + if(FD_ISSET(sock, &read_fds)) + { + /* Packet waiting to be read. */ + + struct sockaddr_ipx recv_addr; + int addrlen = sizeof(recv_addr); + + int r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)(&recv_addr), &addrlen); + assert(r > 0); + + buf[r] = '\0'; + + char net_s[ADDR32_STRING_SIZE]; + addr32_string(net_s, addr32_in(recv_addr.sa_netnum)); + + char node_s[ADDR48_STRING_SIZE]; + addr48_string(node_s, addr48_in(recv_addr.sa_nodenum)); + + printf("%s %s %hu %s\n", net_s, node_s, ntohs(recv_addr.sa_socket), buf); + } } + CloseHandle(send_thread_h); + closesocket(sock); WSACleanup(); diff --git a/tools/ipx-recv.c b/tools/ipx-recv.c index d21b564..0904067 100644 --- a/tools/ipx-recv.c +++ b/tools/ipx-recv.c @@ -38,6 +38,12 @@ static void usage(const char *argv0) exit(1); } +static DWORD WINAPI getchar_thread_main(LPVOID lpParameter) +{ + getchar(); + return 0; +} + int main(int argc, char **argv) { setbuf(stdout, NULL); @@ -128,6 +134,9 @@ int main(int argc, char **argv) usage(argv[0]); } + HANDLE getchar_thread = CreateThread(NULL, 0, &getchar_thread_main, NULL, 0, NULL); + assert(getchar_thread != NULL); + printf("Ready\n"); while(1) @@ -140,7 +149,18 @@ int main(int argc, char **argv) FD_SET(sockets[i], &read_fds); } - assert(select(n_sockets, &read_fds, NULL, NULL, NULL) > 0); + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = 100000, /* 1/10th sec */ + }; + + assert(select(n_sockets, &read_fds, NULL, NULL, &timeout) >= 0); + + if(WaitForSingleObject(getchar_thread, 0) == WAIT_OBJECT_0) + { + /* Input is available on stdin, time to exit. */ + break; + } for(int i = 0; i < n_sockets; ++i) { @@ -165,6 +185,8 @@ int main(int argc, char **argv) } } + CloseHandle(getchar_thread); + for(int i = 0; i < n_sockets; ++i) { closesocket(sockets[i]); diff --git a/tools/spx-server.c b/tools/spx-server.c index b30eb2b..ffa87cb 100644 --- a/tools/spx-server.c +++ b/tools/spx-server.c @@ -39,6 +39,12 @@ static void usage(const char *argv0) exit(1); } +static DWORD WINAPI getchar_thread_main(LPVOID lpParameter) +{ + getchar(); + return 0; +} + int main(int argc, char **argv) { setbuf(stdout, NULL); @@ -116,6 +122,9 @@ int main(int argc, char **argv) usage(argv[0]); } + HANDLE getchar_thread = CreateThread(NULL, 0, &getchar_thread_main, NULL, 0, NULL); + assert(getchar_thread != NULL); + printf("Ready\n"); int clients[MAX_CLIENTS]; @@ -136,7 +145,18 @@ int main(int argc, char **argv) FD_SET(clients[i], &read_fds); } - assert(select(n_listeners + n_clients, &read_fds, NULL, NULL, NULL) > 0); + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = 100000, /* 1/10th sec */ + }; + + assert(select(n_listeners + n_clients, &read_fds, NULL, NULL, &timeout) >= 0); + + if(WaitForSingleObject(getchar_thread, 0) == WAIT_OBJECT_0) + { + /* Input is available on stdin, time to exit. */ + break; + } for(int i = 0; i < n_listeners; ++i) { @@ -198,6 +218,8 @@ int main(int argc, char **argv) } } + CloseHandle(getchar_thread); + for(int i = 0; i < n_clients; ++i) { closesocket(clients[i]);