AU/unittest: test code spawns local HTTP server with unique port
With this change, unit testing code spawns a local (test) HTTP server
that listens on a unique TCP port. It is up to the server to allocate an
available port number (we use auto-allocation via bind) and report it
back (by default, via its stdout), which the unit test process parses.
Also part of this CL:
- Made the port a property of the server object, rather than a global
value. This makes more sense in general and may lend itself better to
future testing scenarios, such as running multiple servers in
parallel.
- Removed a redundant field (validate_quit) from PythonHttpServer and
simplified/robustified its shutdown procedure: if the server is known
to be responsive, a graceful signal is sent (via wget); otherwise, or
if the former failed, a more brutral signal(SIGKILL) is used.
- http_fetcher_unittest code now properly kills test_http_server if the
latter is unresponsive.
BUG=chromium:236465
TEST=Test server spawned with unique port
Change-Id: I699cd5019e4bd860f38205d84e5403cfb9b39f81
Reviewed-on: https://gerrit.chromium.org/gerrit/60637
Commit-Queue: Gilad Arnold <[email protected]>
Reviewed-by: Gilad Arnold <[email protected]>
Tested-by: Gilad Arnold <[email protected]>
diff --git a/test_http_server.cc b/test_http_server.cc
index 2272052..92904ad 100644
--- a/test_http_server.cc
+++ b/test_http_server.cc
@@ -12,6 +12,7 @@
#include <err.h>
#include <errno.h>
+#include <fcntl.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <signal.h>
@@ -19,6 +20,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -44,10 +46,7 @@
namespace chromeos_update_engine {
-// Allowed port range and default value.
-const long kPortMin = static_cast<long>(1) << 10;
-const long kPortMax = (static_cast<long>(1) << 16) - 1;
-const in_port_t kPortDefault = 8080;
+static const char* kListeningMsgPrefix = "listening on port ";
enum {
RC_OK = 0,
@@ -55,6 +54,9 @@
RC_ERR_READ,
RC_ERR_SETSOCKOPT,
RC_ERR_BIND,
+ RC_ERR_LISTEN,
+ RC_ERR_GETSOCKNAME,
+ RC_ERR_REPORT,
};
struct HttpRequest {
@@ -514,12 +516,15 @@
using namespace chromeos_update_engine;
+
void usage(const char *prog_arg) {
- static const char usage_str[] =
- "Usage: %s [ PORT ]\n"
- "where PORT is an integer between %ld and %ld (default is %d).\n";
- fprintf(stderr, usage_str, basename(prog_arg), kPortMin, kPortMax,
- kPortDefault);
+ fprintf(
+ stderr,
+ "Usage: %s [ FILE ]\n"
+ "Once accepting connections, the following is written to FILE (or "
+ "stdout):\n"
+ "\"%sN\" (where N is an integer port number)\n",
+ basename(prog_arg), kListeningMsgPrefix);
}
int main(int argc, char** argv) {
@@ -527,35 +532,28 @@
if (argc > 2)
errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)");
- // Parse inbound port number argument (in host byte-order, as of yet).
- in_port_t port = kPortDefault;
+ // Parse (optional) argument.
+ int report_fd = STDOUT_FILENO;
if (argc == 2) {
if (!strcmp(argv[1], "-h")) {
usage(argv[0]);
exit(RC_OK);
}
- char *end_ptr;
- long raw_port = strtol(argv[1], &end_ptr, 10);
- if (*end_ptr || raw_port < kPortMin || raw_port > kPortMax)
- errx(RC_BAD_ARGS, "invalid port: %s", argv[1]);
- port = static_cast<int>(raw_port);
+ report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644);
}
// Ignore SIGPIPE on write() to sockets.
signal(SIGPIPE, SIG_IGN);
- socklen_t clilen;
- struct sockaddr_in server_addr = sockaddr_in();
- struct sockaddr_in client_addr = sockaddr_in();
-
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
LOG(FATAL) << "socket() failed";
+ struct sockaddr_in server_addr = sockaddr_in();
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
- server_addr.sin_port = htons(port); // byte-order conversion is necessary!
+ server_addr.sin_port = 0;
{
// Get rid of "Address in use" error
@@ -567,19 +565,45 @@
}
}
+ // Bind the socket and set for listening.
if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
sizeof(server_addr)) < 0) {
perror("bind");
exit(RC_ERR_BIND);
}
- CHECK_EQ(listen(listen_fd,5), 0);
+ if (listen(listen_fd, 5) < 0) {
+ perror("listen");
+ exit(RC_ERR_LISTEN);
+ }
+
+ // Check the actual port.
+ struct sockaddr_in bound_addr = sockaddr_in();
+ socklen_t bound_addr_len = sizeof(bound_addr);
+ if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr),
+ &bound_addr_len) < 0) {
+ perror("getsockname");
+ exit(RC_ERR_GETSOCKNAME);
+ }
+ in_port_t port = ntohs(bound_addr.sin_port);
+
+ // Output the listening port, indicating that the server is processing
+ // requests. IMPORTANT! (a) the format of this message is as expected by some
+ // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the
+ // file to prevent the spawning process from waiting indefinitely for this
+ // message.
+ string listening_msg = StringPrintf("%s%hu", kListeningMsgPrefix, port);
+ LOG(INFO) << listening_msg;
+ CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()),
+ static_cast<int>(listening_msg.length()));
+ CHECK_EQ(write(report_fd, "\n", 1), 1);
+ if (report_fd == STDOUT_FILENO)
+ fsync(report_fd);
+ else
+ close(report_fd);
+
while (1) {
- LOG(INFO) << "pid(" << getpid()
- << "): waiting to accept new connection on port " << port;
- clilen = sizeof(client_addr);
- int client_fd = accept(listen_fd,
- (struct sockaddr *) &client_addr,
- &clilen);
+ LOG(INFO) << "pid(" << getpid() << "): waiting to accept new connection";
+ int client_fd = accept(listen_fd, NULL, NULL);
LOG(INFO) << "got past accept";
if (client_fd < 0)
LOG(FATAL) << "ERROR on accept";