Run host region end to end test on the launcher

Bug: 118688323
Test: build & run locally

Change-Id: I9f53afb96f3a90e977f509b6935ddf950b4d3f39
diff --git a/host/commands/launch/launcher_defs.h b/host/commands/launch/launcher_defs.h
index 2732fdf..1f52497 100644
--- a/host/commands/launch/launcher_defs.h
+++ b/host/commands/launch/launcher_defs.h
@@ -34,6 +34,7 @@
   kMonitorCreationFailed = 13,
   kServerError = 14,
   kUsbV1SocketError = 15,
+  kE2eTestFailed = 16,
 };
 
 // Actions supported by the launcher server
diff --git a/host/commands/launch/main.cc b/host/commands/launch/main.cc
index a94af3e..ecfd09f 100644
--- a/host/commands/launch/main.cc
+++ b/host/commands/launch/main.cc
@@ -191,6 +191,10 @@
 DEFINE_string(hypervisor_uri, "qemu:///system", "Hypervisor cannonical uri.");
 DEFINE_bool(log_xml, false, "Log the XML machine configuration");
 DEFINE_bool(restart_subprocesses, true, "Restart any crashed host process");
+DEFINE_bool(run_e2e_test, true, "Run e2e test after device launches");
+DEFINE_string(e2e_test_binary,
+              vsoc::DefaultHostArtifactsPath("bin/host_region_e2e_test"),
+              "Location of the region end to end test binary");
 
 DECLARE_string(config_file);
 
@@ -341,15 +345,132 @@
                                    OnSubprocessExitCallback);
 }
 
+// Maintains the state of the boot process, once a final state is reached
+// (success or failure) it sends the appropriate exit code to the foreground
+// launcher process
+class CvdBootStateMachine {
+ public:
+  CvdBootStateMachine(cvd::SharedFD fg_launcher_pipe)
+      : fg_launcher_pipe_(fg_launcher_pipe), state_(kBootStarted) {}
+
+  // Returns true if the machine is left in a final state
+  bool OnBootEvtReceived(cvd::SharedFD boot_events_pipe) {
+    monitor::BootEvent evt;
+    auto bytes_read = boot_events_pipe->Read(&evt, sizeof(evt));
+    if (bytes_read != sizeof(evt)) {
+      LOG(ERROR) << "Fail to read a complete event, read " << bytes_read
+                 << " bytes only instead of the expected " << sizeof(evt);
+      state_ |= kGuestBootFailed;
+    } else if (evt == monitor::BootEvent::BootCompleted) {
+      LOG(INFO) << "Virtual device booted successfully";
+      state_ |= kGuestBootCompleted;
+    } else if (evt == monitor::BootEvent::BootFailed) {
+      LOG(ERROR) << "Virtual device failed to boot";
+      state_ |= kGuestBootFailed;
+    }  // Ignore the other signals
+
+    return MaybeWriteToForegroundLauncher();
+  }
+
+  bool  OnE2eTestCompleted(int exit_code) {
+    if (exit_code != 0) {
+      LOG(ERROR) << "VSoC e2e test failed";
+      state_ |= kE2eTestFailed;
+    } else {
+      LOG(INFO) << "VSoC e2e test passed";
+      state_ |= kE2eTestPassed;
+    }
+    return MaybeWriteToForegroundLauncher();
+  }
+
+  bool BootCompleted() const {
+    return state_ == (kGuestBootCompleted | kE2eTestPassed);
+  }
+
+  bool BootFailed() const {
+    return state_ & (kGuestBootFailed | kE2eTestFailed);
+  }
+
+ private:
+  void SendExitCode(cvd::LauncherExitCodes exit_code) {
+    fg_launcher_pipe_->Write(&exit_code, sizeof(exit_code));
+    // The foreground process will exit after receiving the exit code, if we try
+    // to write again we'll get a SIGPIPE
+    fg_launcher_pipe_->Close();
+  }
+  bool MaybeWriteToForegroundLauncher() {
+    if (fg_launcher_pipe_->IsOpen()) {
+      if (BootCompleted()) {
+        SendExitCode(cvd::LauncherExitCodes::kSuccess);
+      } else if (state_ & kGuestBootFailed) {
+        SendExitCode(cvd::LauncherExitCodes::kVirtualDeviceBootFailed);
+      } else if (state_ & kE2eTestFailed) {
+        SendExitCode(cvd::LauncherExitCodes::kE2eTestFailed);
+      } else {
+        // No final state was reached
+        return false;
+      }
+    }
+    // Either we sent the code before or just sent it, in any case the state is
+    // final
+    return true;
+  }
+
+  cvd::SharedFD fg_launcher_pipe_;
+  int state_;
+  static const int kBootStarted = 0;
+  static const int kGuestBootCompleted = 1 << 0;
+  static const int kGuestBootFailed = 1 << 1;
+  static const int kE2eTestPassed = 1 << 2;
+  static const int kE2eTestFailed = 1 << 3;
+};
+
+// Abuse the process monitor to make it call us back when boot events are ready
+void SetUpHandlingOfBootEvents(
+    cvd::ProcessMonitor* process_monitor, cvd::SharedFD boot_events_pipe,
+    std::shared_ptr<CvdBootStateMachine> state_machine) {
+  process_monitor->MonitorExistingSubprocess(
+      // A dummy command, so logs are desciptive
+      cvd::Command("boot_events_listener"),
+      // A dummy subprocess, with the boot events pipe as control socket
+      cvd::Subprocess(-1, boot_events_pipe),
+      [boot_events_pipe, state_machine](cvd::MonitorEntry*) {
+        auto sent_code = state_machine->OnBootEvtReceived(boot_events_pipe);
+        return !sent_code;
+      });
+}
+
+void LaunchE2eTest(cvd::ProcessMonitor* process_monitor,
+                   std::shared_ptr<CvdBootStateMachine> state_machine) {
+  // Run a command that always succeeds if we are not running e2e tests
+  std::string e2e_test_cmd =
+      FLAGS_run_e2e_test ? FLAGS_e2e_test_binary : "/bin/true";
+  process_monitor->StartSubprocess(
+      cvd::Command(e2e_test_cmd),
+      [state_machine](cvd::MonitorEntry* entry) {
+        auto test_result = entry->proc->Wait();
+        state_machine->OnE2eTestCompleted(test_result);
+        return false;
+      });
+}
+
+// Build the kernel log monitor command. If boot_event_pipe is not NULL, a
+// subscription to boot events from the kernel log monitor will be created and
+// events will appear on *boot_events_pipe
 cvd::Command GetKernelLogMonitorCommand(const vsoc::CuttlefishConfig& config,
-                                        cvd::SharedFD boot_events_pipe) {
+                                        cvd::SharedFD* boot_events_pipe) {
   auto log_name = config.kernel_log_socket_name();
   auto server = cvd::SharedFD::SocketLocalServer(log_name.c_str(), false,
                                                  SOCK_STREAM, 0666);
   cvd::Command kernel_log_monitor(FLAGS_kernel_log_monitor_binary);
   kernel_log_monitor.AddParameter("-log_server_fd=", server);
-  if (boot_events_pipe->IsOpen()) {
-    kernel_log_monitor.AddParameter("-subscriber_fd=", boot_events_pipe);
+  if (boot_events_pipe) {
+    cvd::SharedFD pipe_write_end;
+    if (!cvd::SharedFD::Pipe(boot_events_pipe, &pipe_write_end)) {
+      LOG(ERROR) << "Unable to create boot events pipe: " << strerror(errno);
+      std::exit(LauncherExitCodes::kPipeIOError);
+    }
+    kernel_log_monitor.AddParameter("-subscriber_fd=", pipe_write_end);
   }
   return kernel_log_monitor;
 }
@@ -791,24 +912,22 @@
     // Explicitly close here, otherwise we may end up reading forever if the
     // child process dies.
     write_end->Close();
-    monitor::BootEvent evt;
-    while(true) {
-      auto bytes_read = read_end->Read(&evt, sizeof(evt));
-      if (bytes_read != sizeof(evt)) {
-        LOG(ERROR) << "Fail to read a complete event, read " << bytes_read
-                   << " bytes only instead of the expected " << sizeof(evt);
-        std::exit(LauncherExitCodes::kPipeIOError);
-      }
-      if (evt == monitor::BootEvent::BootCompleted) {
-        LOG(INFO) << "Virtual device booted successfully";
-        std::exit(LauncherExitCodes::kSuccess);
-      }
-      if (evt == monitor::BootEvent::BootFailed) {
-        LOG(ERROR) << "Virtual device failed to boot";
-        std::exit(LauncherExitCodes::kVirtualDeviceBootFailed);
-      }
-      // Do nothing for the other signals
+    LauncherExitCodes exit_code;
+    auto bytes_read = read_end->Read(&exit_code, sizeof(exit_code));
+    if (bytes_read != sizeof(exit_code)) {
+      LOG(ERROR) << "Failed to read a complete exit code, read " << bytes_read
+                 << " bytes only instead of the expected " << sizeof(exit_code);
+      exit_code = LauncherExitCodes::kPipeIOError;
+    } else if (exit_code == LauncherExitCodes::kSuccess) {
+      LOG(INFO) << "Virtual device booted successfully";
+    } else if (exit_code == LauncherExitCodes::kVirtualDeviceBootFailed) {
+      LOG(ERROR) << "Virtual device failed to boot";
+    } else if (exit_code == LauncherExitCodes::kE2eTestFailed) {
+      LOG(ERROR) << "Host VSoC region end to end test failed";
+    } else {
+      LOG(ERROR) << "Unexpected exit code: " << exit_code;
     }
+    std::exit(exit_code);
   } else {
     // The child returns the write end of the pipe
     if (daemon(/*nochdir*/ 1, /*noclose*/ 1) != 0) {
@@ -993,10 +1112,10 @@
                << launcher_monitor_socket->StrError();
     return cvd::LauncherExitCodes::kMonitorCreationFailed;
   }
-  cvd::SharedFD boot_events_pipe;
+  cvd::SharedFD foreground_launcher_pipe;
   if (FLAGS_daemon) {
-    boot_events_pipe = DaemonizeLauncher(*config);
-    if (!boot_events_pipe->IsOpen()) {
+    foreground_launcher_pipe = DaemonizeLauncher(*config);
+    if (!foreground_launcher_pipe->IsOpen()) {
       return LauncherExitCodes::kDaemonizationError;
     }
   } else {
@@ -1011,13 +1130,24 @@
     }
   }
 
+  auto boot_state_machine =
+      std::make_shared<CvdBootStateMachine>(foreground_launcher_pipe);
+
   // Monitor and restart host processes supporting the CVD
   cvd::ProcessMonitor process_monitor;
 
+  cvd::SharedFD boot_events_pipe;
+  // Only subscribe to boot events if running as daemon
   process_monitor.StartSubprocess(
-      GetKernelLogMonitorCommand(*config, boot_events_pipe),
+      GetKernelLogMonitorCommand(*config,
+                                 FLAGS_daemon ? &boot_events_pipe : nullptr),
       OnSubprocessExitCallback);
+
+  SetUpHandlingOfBootEvents(&process_monitor, boot_events_pipe,
+                            boot_state_machine);
+
   LaunchUsbServerIfEnabled(*config, &process_monitor);
+
   process_monitor.StartSubprocess(
       GetIvServerCommand(*config),
       OnSubprocessExitCallback);
@@ -1025,6 +1155,9 @@
   // Initialize the regions that require so before the VM starts.
   PreLaunchInitializers::Initialize(*config);
 
+  // Launch the e2e test after the shared memory is initialized
+  LaunchE2eTest(&process_monitor, boot_state_machine);
+
   // Start the guest VM, don't monitor it, if it fails the device is considered
   // failed
   if (!vm_manager->Start()) {
diff --git a/host/commands/launch/process_monitor.cc b/host/commands/launch/process_monitor.cc
index 24b50b9..a405180 100644
--- a/host/commands/launch/process_monitor.cc
+++ b/host/commands/launch/process_monitor.cc
@@ -69,6 +69,11 @@
     LOG(ERROR) << "Failed to start process";
     return;
   }
+  MonitorExistingSubprocess(std::move(cmd), std::move(proc), callback);
+}
+
+void ProcessMonitor::MonitorExistingSubprocess(Command cmd, Subprocess proc,
+                                               OnSocketReadyCb callback) {
   {
     std::lock_guard<std::mutex> lock(processes_mutex_);
     monitored_processes_.push_back(MonitorEntry());
diff --git a/host/commands/launch/process_monitor.h b/host/commands/launch/process_monitor.h
index b78a196..a375653 100644
--- a/host/commands/launch/process_monitor.h
+++ b/host/commands/launch/process_monitor.h
@@ -44,6 +44,9 @@
   // lead to a dealock. If the callback returns false the subprocess will no
   // longer be monitored
   void StartSubprocess(Command cmd, OnSocketReadyCb on_control_socket_ready_cb);
+  // Monitors an alreacy started subprocess
+  void MonitorExistingSubprocess(Command cmd, Subprocess sub_process,
+                                 OnSocketReadyCb on_control_socket_ready_cb);
   static bool RestartOnExitCb(MonitorEntry* entry);
   static bool DoNotMonitorCb(MonitorEntry* entry);