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