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/subprocess_unittest.cc b/subprocess_unittest.cc
index 427f7e3..cb295e1 100644
--- a/subprocess_unittest.cc
+++ b/subprocess_unittest.cc
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -31,7 +35,7 @@
};
namespace {
-const int kLocalHttpPort = 8088;
+int local_server_port = 0;
void Callback(int return_code, const string& output, void *p) {
EXPECT_EQ(1, return_code);
@@ -110,33 +114,62 @@
GMainLoop *loop;
};
+// TODO(garnold) this test method uses test_http_server as a representative for
+// interactive processes that can be spawned/terminated at will. This causes us
+// to go through hoops when spawning this process (e.g. obtaining the port
+// number it uses so we can control it with wget). It would have been much
+// preferred to use something else and thus simplify both test_http_server
+// (doesn't have to be able to communicate through a temp file) and the test
+// code below; for example, it sounds like a brain dead sleep loop with proper
+// signal handlers could be used instead.
gboolean StartAndCancelInRunLoop(gpointer data) {
CancelTestData* cancel_test_data = reinterpret_cast<CancelTestData*>(data);
+
+ // Create a temp file for test_http_server to communicate its port number.
+ char temp_file_name[] = "/tmp/subprocess_unittest-test_http_server-XXXXXX";
+ int temp_fd = mkstemp(temp_file_name);
+ CHECK_GE(temp_fd, 0);
+ int temp_flags = fcntl(temp_fd, F_GETFL, 0) | O_NONBLOCK;
+ CHECK_EQ(fcntl(temp_fd, F_SETFL, temp_flags), 0);
+
vector<string> cmd;
cmd.push_back("./test_http_server");
- cmd.push_back(StringPrintf("%d", kLocalHttpPort));
+ cmd.push_back(temp_file_name);
uint32_t tag = Subprocess::Get().Exec(cmd, CallbackBad, NULL);
EXPECT_NE(0, tag);
cancel_test_data->spawned = true;
- printf("spawned\n");
+ printf("test http server spawned\n");
// Wait for server to be up and running
TimeDelta total_wait_time;
const TimeDelta kSleepTime = TimeDelta::FromMilliseconds(100);
const TimeDelta kMaxWaitTime = TimeDelta::FromSeconds(3);
- for (;;) {
- int status =
- System(StringPrintf("wget -O /dev/null http://127.0.0.1:%d/foo",
- kLocalHttpPort));
- EXPECT_NE(-1, status) << "system() failed";
- EXPECT_TRUE(WIFEXITED(status))
- << "command failed to run or died abnormally";
- if (0 == WEXITSTATUS(status))
+ local_server_port = 0;
+ static const char* kServerListeningMsgPrefix = "listening on port ";
+ while (total_wait_time.InMicroseconds() < kMaxWaitTime.InMicroseconds()) {
+ char line[80];
+ int line_len = read(temp_fd, line, sizeof(line) - 1);
+ if (line_len > 0) {
+ line[line_len] = '\0';
+ CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+ const char* listening_port_str =
+ line + strlen(kServerListeningMsgPrefix);
+ char* end_ptr;
+ long raw_port = strtol(listening_port_str, &end_ptr, 10);
+ CHECK(!*end_ptr || *end_ptr == '\n');
+ local_server_port = static_cast<in_port_t>(raw_port);
break;
-
+ } else if (line_len < 0 && errno != EAGAIN) {
+ LOG(INFO) << "error reading from " << temp_file_name << ": "
+ << strerror(errno);
+ break;
+ }
g_usleep(kSleepTime.InMicroseconds());
total_wait_time += kSleepTime;
- CHECK_LT(total_wait_time.InMicroseconds(), kMaxWaitTime.InMicroseconds());
}
+ close(temp_fd);
+ remove(temp_file_name);
+ CHECK_GT(local_server_port, 0);
+ LOG(INFO) << "server listening on port " << local_server_port;
Subprocess::Get().CancelExec(tag);
return FALSE;
}
@@ -149,7 +182,7 @@
printf("tear down time\n");
int status = System(
StringPrintf("wget -O /dev/null http://127.0.0.1:%d/quitquitquit",
- kLocalHttpPort));
+ local_server_port));
EXPECT_NE(-1, status) << "system() failed";
EXPECT_TRUE(WIFEXITED(status))
<< "command failed to run or died abnormally";
@@ -168,7 +201,6 @@
g_timeout_add(10, &ExitWhenDone, &cancel_test_data);
g_main_loop_run(loop);
g_main_loop_unref(loop);
- printf("here\n");
}
} // namespace chromeos_update_engine