浏览代码

cmUVProcessChain: Add Status::SpawnResult field

Kyle Edwards 2 年之前
父节点
当前提交
891b60d691

+ 2 - 3
Source/cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool.cxx

@@ -35,7 +35,7 @@ bool cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool::GetFileInfo(
   builder.AddCommand(command);
 
   auto process = builder.Start();
-  if (!process.Valid()) {
+  if (!process.Valid() || process.GetStatus(0).SpawnResult != 0) {
     std::ostringstream e;
     e << "Failed to start objdump process for:\n  " << file;
     this->SetError(e.str());
@@ -73,8 +73,7 @@ bool cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool::GetFileInfo(
     this->SetError(e.str());
     return false;
   }
-  auto status = process.GetStatus();
-  if (!status[0] || status[0]->ExitStatus != 0) {
+  if (process.GetStatus(0).ExitStatus != 0) {
     std::ostringstream e;
     e << "Failed to run objdump on:\n  " << file;
     this->SetError(e.str());

+ 2 - 3
Source/cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool.cxx

@@ -34,7 +34,7 @@ bool cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool::GetFileInfo(
     .AddCommand(command);
 
   auto process = builder.Start();
-  if (!process.Valid()) {
+  if (!process.Valid() || process.GetStatus(0).SpawnResult != 0) {
     std::ostringstream e;
     e << "Failed to start otool process for:\n  " << file;
     this->SetError(e.str());
@@ -88,8 +88,7 @@ bool cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool::GetFileInfo(
     this->SetError(e.str());
     return false;
   }
-  auto status = process.GetStatus();
-  if (!status[0] || status[0]->ExitStatus != 0) {
+  if (process.GetStatus(0).ExitStatus != 0) {
     std::ostringstream e;
     e << "Failed to run otool on:\n  " << file;
     this->SetError(e.str());

+ 2 - 3
Source/cmBinUtilsWindowsPEDumpbinGetRuntimeDependenciesTool.cxx

@@ -33,7 +33,7 @@ bool cmBinUtilsWindowsPEDumpbinGetRuntimeDependenciesTool::GetFileInfo(
   builder.AddCommand(command);
 
   auto process = builder.Start();
-  if (!process.Valid()) {
+  if (!process.Valid() || process.GetStatus(0).SpawnResult != 0) {
     std::ostringstream e;
     e << "Failed to start dumpbin process for:\n  " << file;
     this->SetError(e.str());
@@ -56,8 +56,7 @@ bool cmBinUtilsWindowsPEDumpbinGetRuntimeDependenciesTool::GetFileInfo(
     this->SetError(e.str());
     return false;
   }
-  auto status = process.GetStatus();
-  if (!status[0] || status[0]->ExitStatus != 0) {
+  if (process.GetStatus(0).ExitStatus != 0) {
     std::ostringstream e;
     e << "Failed to run dumpbin on:\n  " << file;
     this->SetError(e.str());

+ 2 - 3
Source/cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.cxx

@@ -34,7 +34,7 @@ bool cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool::GetFileInfo(
   builder.AddCommand(command);
 
   auto process = builder.Start();
-  if (!process.Valid()) {
+  if (!process.Valid() || process.GetStatus(0).SpawnResult != 0) {
     std::ostringstream e;
     e << "Failed to start objdump process for:\n  " << file;
     this->SetError(e.str());
@@ -57,8 +57,7 @@ bool cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool::GetFileInfo(
     this->SetError(e.str());
     return false;
   }
-  auto status = process.GetStatus();
-  if (!status[0] || status[0]->ExitStatus != 0) {
+  if (process.GetStatus(0).ExitStatus != 0) {
     std::ostringstream e;
     e << "Failed to run objdump on:\n  " << file;
     this->SetError(e.str());

+ 2 - 3
Source/cmLDConfigLDConfigTool.cxx

@@ -43,7 +43,7 @@ bool cmLDConfigLDConfigTool::GetLDConfigPaths(std::vector<std::string>& paths)
   builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
     .AddCommand(ldConfigCommand);
   auto process = builder.Start();
-  if (!process.Valid()) {
+  if (!process.Valid() || process.GetStatus(0).SpawnResult != 0) {
     this->Archive->SetError("Failed to start ldconfig process");
     return false;
   }
@@ -61,8 +61,7 @@ bool cmLDConfigLDConfigTool::GetLDConfigPaths(std::vector<std::string>& paths)
     this->Archive->SetError("Failed to wait on ldconfig process");
     return false;
   }
-  auto status = process.GetStatus();
-  if (!status[0] || status[0]->ExitStatus != 0) {
+  if (process.GetStatus(0).ExitStatus != 0) {
     this->Archive->SetError("Failed to run ldconfig");
     return false;
   }

+ 28 - 33
Source/cmUVProcessChain.cxx

@@ -53,8 +53,9 @@ struct cmUVProcessChain::InternalData
     cm::uv_process_ptr Process;
     cm::uv_pipe_ptr InputPipe;
     cm::uv_pipe_ptr OutputPipe;
-    bool Finished = false;
     Status ProcessStatus;
+
+    void Finish();
   };
 
   const cmUVProcessChainBuilder* Builder = nullptr;
@@ -72,13 +73,11 @@ struct cmUVProcessChain::InternalData
   std::vector<std::unique_ptr<ProcessData>> Processes;
 
   bool Prepare(const cmUVProcessChainBuilder* builder);
-  bool SpawnProcess(
+  void SpawnProcess(
     std::size_t index,
     const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first,
     bool last);
   void Finish();
-
-  static const Status* GetStatus(const ProcessData& data);
 };
 
 cmUVProcessChainBuilder::cmUVProcessChainBuilder()
@@ -171,10 +170,8 @@ cmUVProcessChain cmUVProcessChainBuilder::Start() const
   }
 
   for (std::size_t i = 0; i < this->Processes.size(); i++) {
-    if (!chain.Data->SpawnProcess(i, this->Processes[i], i == 0,
-                                  i == this->Processes.size() - 1)) {
-      return chain;
-    }
+    chain.Data->SpawnProcess(i, this->Processes[i], i == 0,
+                             i == this->Processes.size() - 1);
   }
 
   chain.Data->Finish();
@@ -182,15 +179,6 @@ cmUVProcessChain cmUVProcessChainBuilder::Start() const
   return chain;
 }
 
-const cmUVProcessChain::Status* cmUVProcessChain::InternalData::GetStatus(
-  const cmUVProcessChain::InternalData::ProcessData& data)
-{
-  if (data.Finished) {
-    return &data.ProcessStatus;
-  }
-  return nullptr;
-}
-
 bool cmUVProcessChain::InternalData::Prepare(
   const cmUVProcessChainBuilder* builder)
 {
@@ -285,6 +273,7 @@ bool cmUVProcessChain::InternalData::Prepare(
     this->Processes.emplace_back(cm::make_unique<ProcessData>());
     auto& process = *this->Processes.back();
     process.Data = this;
+    process.ProcessStatus.Finished = false;
 
     if (!first) {
       auto& prevProcess = *this->Processes[i - 1];
@@ -314,7 +303,7 @@ bool cmUVProcessChain::InternalData::Prepare(
   return true;
 }
 
-bool cmUVProcessChain::InternalData::SpawnProcess(
+void cmUVProcessChain::InternalData::SpawnProcess(
   std::size_t index,
   const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first,
   bool last)
@@ -360,16 +349,17 @@ bool cmUVProcessChain::InternalData::SpawnProcess(
   options.exit_cb = [](uv_process_t* handle, int64_t exitStatus,
                        int termSignal) {
     auto* processData = static_cast<ProcessData*>(handle->data);
-    processData->Finished = true;
     processData->ProcessStatus.ExitStatus = exitStatus;
     processData->ProcessStatus.TermSignal = termSignal;
-    processData->Data->ProcessesCompleted++;
+    processData->Finish();
   };
 
-  bool result = process.Process.spawn(*this->Loop, options, &process) >= 0;
+  if ((process.ProcessStatus.SpawnResult =
+         process.Process.spawn(*this->Loop, options, &process)) < 0) {
+    process.Finish();
+  }
   process.InputPipe.reset();
   process.OutputPipe.reset();
-  return result;
 }
 
 void cmUVProcessChain::InternalData::Finish()
@@ -451,19 +441,15 @@ std::vector<const cmUVProcessChain::Status*> cmUVProcessChain::GetStatus()
   std::vector<const cmUVProcessChain::Status*> statuses(
     this->Data->Processes.size(), nullptr);
   for (std::size_t i = 0; i < statuses.size(); i++) {
-    statuses[i] = this->GetStatus(i);
+    statuses[i] = &this->GetStatus(i);
   }
   return statuses;
 }
 
-const cmUVProcessChain::Status* cmUVProcessChain::GetStatus(
+const cmUVProcessChain::Status& cmUVProcessChain::GetStatus(
   std::size_t index) const
 {
-  auto const& process = *this->Data->Processes[index];
-  if (process.Finished) {
-    return &process.ProcessStatus;
-  }
-  return nullptr;
+  return this->Data->Processes[index]->ProcessStatus;
 }
 
 bool cmUVProcessChain::Finished() const
@@ -474,8 +460,12 @@ bool cmUVProcessChain::Finished() const
 std::pair<cmUVProcessChain::ExceptionCode, std::string>
 cmUVProcessChain::Status::GetException() const
 {
+  if (this->SpawnResult) {
+    return std::make_pair(ExceptionCode::Spawn,
+                          uv_strerror(this->SpawnResult));
+  }
 #ifdef _WIN32
-  if ((this->ExitStatus & 0xF0000000) == 0xC0000000) {
+  if (this->Finished && (this->ExitStatus & 0xF0000000) == 0xC0000000) {
     // Child terminated due to exceptional behavior.
     switch (this->ExitStatus) {
       case STATUS_CONTROL_C_EXIT:
@@ -550,9 +540,8 @@ cmUVProcessChain::Status::GetException() const
       }
     }
   }
-  return std::make_pair(ExceptionCode::None, "");
 #else
-  if (this->TermSignal) {
+  if (this->Finished && this->TermSignal) {
     switch (this->TermSignal) {
 #  ifdef SIGSEGV
       case SIGSEGV:
@@ -709,6 +698,12 @@ cmUVProcessChain::Status::GetException() const
       }
     }
   }
-  return std::make_pair(ExceptionCode::None, "");
 #endif
+  return std::make_pair(ExceptionCode::None, "");
+}
+
+void cmUVProcessChain::InternalData::ProcessData::Finish()
+{
+  this->ProcessStatus.Finished = true;
+  this->Data->ProcessesCompleted++;
 }

+ 4 - 1
Source/cmUVProcessChain.h

@@ -74,11 +74,14 @@ public:
     Illegal,
     Interrupt,
     Numerical,
+    Spawn,
     Other,
   };
 
   struct Status
   {
+    int SpawnResult;
+    bool Finished;
     int64_t ExitStatus;
     int TermSignal;
 
@@ -102,7 +105,7 @@ public:
   bool Valid() const;
   bool Wait(int64_t milliseconds = -1);
   std::vector<const Status*> GetStatus() const;
-  const Status* GetStatus(std::size_t index) const;
+  const Status& GetStatus(std::size_t index) const;
   bool Finished() const;
 
 private:

+ 1 - 1
Source/cmake.cxx

@@ -3916,7 +3916,7 @@ std::function<int()> cmake::BuildWorkflowStep(
   return [builder]() -> int {
     auto chain = builder.Start();
     chain.Wait();
-    return static_cast<int>(chain.GetStatus().front()->ExitStatus);
+    return static_cast<int>(chain.GetStatus(0).ExitStatus);
   };
 }
 #endif

+ 4 - 6
Source/cmcmd.cxx

@@ -2008,7 +2008,7 @@ int cmcmd::RunPreprocessor(const std::vector<std::string>& command,
     .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR)
     .AddCommand(command);
   auto process = builder.Start();
-  if (!process.Valid()) {
+  if (!process.Valid() || process.GetStatus(0).SpawnResult != 0) {
     std::cerr << "Failed to start preprocessor.";
     return 1;
   }
@@ -2016,8 +2016,7 @@ int cmcmd::RunPreprocessor(const std::vector<std::string>& command,
     std::cerr << "Failed to wait for preprocessor";
     return 1;
   }
-  auto status = process.GetStatus();
-  if (!status[0] || status[0]->ExitStatus != 0) {
+  if (process.GetStatus(0).ExitStatus != 0) {
     auto* errorStream = process.ErrorStream();
     if (errorStream) {
       std::cerr << errorStream->rdbuf();
@@ -2130,7 +2129,7 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args)
     .AddCommand(resource_compile);
   auto process = builder.Start();
   result = 0;
-  if (!process.Valid()) {
+  if (!process.Valid() || process.GetStatus(0).SpawnResult != 0) {
     std::cerr << "Failed to start resource compiler.";
     result = 1;
   } else {
@@ -2144,8 +2143,7 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args)
   if (result != 0) {
     return result;
   }
-  auto status = process.GetStatus();
-  if (!status[0] || status[0]->ExitStatus != 0) {
+  if (process.GetStatus(0).ExitStatus != 0) {
     auto* errorStream = process.ErrorStream();
     if (errorStream) {
       std::cerr << errorStream->rdbuf();

+ 201 - 62
Tests/CMakeLib/testUVProcessChain.cxx

@@ -20,7 +20,6 @@
 
 struct ExpectedStatus
 {
-  bool Finished;
   bool MatchExitStatus;
   bool MatchTermSignal;
   cmUVProcessChain::Status Status;
@@ -28,38 +27,6 @@ struct ExpectedStatus
   std::string ExceptionString;
 };
 
-static const std::vector<ExpectedStatus> status1 = {
-  { false, false, false, { 0, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-  { false, false, false, { 0, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-  { false, false, false, { 0, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-};
-
-static const std::vector<ExpectedStatus> status2 = {
-  { true, true, true, { 0, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-  { false, false, false, { 0, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-  { false, false, false, { 0, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-};
-
-static const std::vector<ExpectedStatus> status3 = {
-  { true, true, true, { 0, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-  { true, true, true, { 1, 0 }, cmUVProcessChain::ExceptionCode::None, "" },
-#ifdef _WIN32
-  { true,
-    true,
-    true,
-    { STATUS_ACCESS_VIOLATION, 0 },
-    cmUVProcessChain::ExceptionCode::Fault,
-    "Access violation" },
-#else
-  { true,
-    false,
-    true,
-    { 0, SIGABRT },
-    cmUVProcessChain::ExceptionCode::Other,
-    "Subprocess aborted" },
-#endif
-};
-
 static const char* ExceptionCodeToString(cmUVProcessChain::ExceptionCode code)
 {
   switch (code) {
@@ -73,6 +40,8 @@ static const char* ExceptionCodeToString(cmUVProcessChain::ExceptionCode code)
       return "Interrupt";
     case cmUVProcessChain::ExceptionCode::Numerical:
       return "Numerical";
+    case cmUVProcessChain::ExceptionCode::Spawn:
+      return "Spawn";
     case cmUVProcessChain::ExceptionCode::Other:
       return "Other";
     default:
@@ -83,9 +52,10 @@ static const char* ExceptionCodeToString(cmUVProcessChain::ExceptionCode code)
 bool operator==(const cmUVProcessChain::Status* actual,
                 const ExpectedStatus& expected)
 {
-  if (!expected.Finished) {
-    return !actual;
-  } else if (!actual) {
+  if (expected.Status.SpawnResult != actual->SpawnResult) {
+    return false;
+  }
+  if (expected.Status.Finished != actual->Finished) {
     return false;
   }
   if (expected.MatchExitStatus &&
@@ -96,7 +66,7 @@ bool operator==(const cmUVProcessChain::Status* actual,
       expected.Status.TermSignal != actual->TermSignal) {
     return false;
   }
-  if (expected.Finished &&
+  if (expected.Status.Finished &&
       std::make_pair(expected.ExceptionCode, expected.ExceptionString) !=
         actual->GetException()) {
     return false;
@@ -150,39 +120,96 @@ static void printResults(
 {
   std::cout << "Expected: " << std::endl;
   for (auto const& e : expected) {
-    if (e.Finished) {
-      std::cout << "  ExitStatus: "
-                << printExpected(e.MatchExitStatus, e.Status.ExitStatus)
-                << ", TermSignal: "
-                << printExpected(e.MatchTermSignal, e.Status.TermSignal)
-                << ", ExceptionCode: "
-                << printExpected(e.Finished,
-                                 ExceptionCodeToString(e.ExceptionCode))
-                << ", ExceptionString: \""
-                << printExpected(e.Finished, e.ExceptionString) << '"'
-                << std::endl;
-    } else {
-      std::cout << "  null" << std::endl;
-    }
+    std::cout << "  SpawnResult: " << e.Status.SpawnResult
+              << ", Finished: " << e.Status.Finished << ", ExitStatus: "
+              << printExpected(e.MatchExitStatus, e.Status.ExitStatus)
+              << ", TermSignal: "
+              << printExpected(e.MatchTermSignal, e.Status.TermSignal)
+              << ", ExceptionCode: "
+              << printExpected(e.Status.Finished,
+                               ExceptionCodeToString(e.ExceptionCode))
+              << ", ExceptionString: \""
+              << printExpected(e.Status.Finished, e.ExceptionString) << '"'
+              << std::endl;
   }
   std::cout << "Actual:" << std::endl;
   for (auto const& a : actual) {
-    if (a) {
-      auto exception = a->GetException();
-      std::cout << "  ExitStatus: " << a->ExitStatus
-                << ", TermSignal: " << a->TermSignal << ", ExceptionCode: "
-                << ExceptionCodeToString(exception.first)
-                << ", ExceptionString: \"" << exception.second << '"'
-                << std::endl;
-    } else {
-      std::cout << "  null" << std::endl;
-    }
+    auto exception = a->GetException();
+    std::cout << "  SpawnResult: " << a->SpawnResult
+              << ", Finished: " << a->Finished
+              << ", ExitStatus: " << a->ExitStatus
+              << ", TermSignal: " << a->TermSignal
+              << ", ExceptionCode: " << ExceptionCodeToString(exception.first)
+              << ", ExceptionString: \"" << exception.second << '"'
+              << std::endl;
   }
 }
 
 static bool checkExecution(cmUVProcessChainBuilder& builder,
                            std::unique_ptr<cmUVProcessChain>& chain)
 {
+  static const std::vector<ExpectedStatus> status1 = {
+    { false,
+      false,
+      { 0, false, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+    { false,
+      false,
+      { 0, false, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+    { false,
+      false,
+      { 0, false, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+  };
+
+  static const std::vector<ExpectedStatus> status2 = {
+    { true,
+      true,
+      { 0, true, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+    { false,
+      false,
+      { 0, false, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+    { false,
+      false,
+      { 0, false, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+  };
+
+  static const std::vector<ExpectedStatus> status3 = {
+    { true,
+      true,
+      { 0, true, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+    { true,
+      true,
+      { 0, true, 1, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+#ifdef _WIN32
+    { true,
+      true,
+      { 0, true, STATUS_ACCESS_VIOLATION, 0 },
+      cmUVProcessChain::ExceptionCode::Fault,
+      "Access violation" },
+#else
+    { false,
+      true,
+      { 0, true, 0, SIGABRT },
+      cmUVProcessChain::ExceptionCode::Other,
+      "Subprocess aborted" },
+#endif
+  };
+
   std::vector<const cmUVProcessChain::Status*> status;
 
   chain = cm::make_unique<cmUVProcessChain>(builder.Start());
@@ -201,7 +228,7 @@ static bool checkExecution(cmUVProcessChainBuilder& builder,
     return false;
   }
 
-  if (chain->Wait(6000)) {
+  if (chain->Wait(9000)) {
     std::cout << "Wait() returned true, should be false" << std::endl;
     return false;
   }
@@ -481,6 +508,113 @@ bool testUVProcessChainCwdChanged(const char* helperCommand)
   return true;
 }
 
+bool testUVProcessChainSpawnFail(const char* helperCommand)
+{
+  static const std::vector<ExpectedStatus> status1 = {
+    { false,
+      false,
+      { 0, false, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+    { false,
+      false,
+      { UV_ENOENT, true, 0, 0 },
+      cmUVProcessChain::ExceptionCode::Spawn,
+      uv_strerror(UV_ENOENT) },
+#ifdef _WIN32
+    { true,
+      true,
+      { 0, true, STATUS_ACCESS_VIOLATION, 0 },
+      cmUVProcessChain::ExceptionCode::Fault,
+      "Access violation" },
+#else
+    { false,
+      true,
+      { 0, true, 0, SIGABRT },
+      cmUVProcessChain::ExceptionCode::Other,
+      "Subprocess aborted" },
+#endif
+  };
+
+  static const std::vector<ExpectedStatus> status2 = {
+#ifdef _WIN32
+    { true,
+      true,
+      { 0, true, 0, 0 },
+      cmUVProcessChain::ExceptionCode::None,
+      "" },
+#else
+    { false,
+      true,
+      { 0, true, 0, SIGPIPE },
+      cmUVProcessChain::ExceptionCode::Other,
+      "SIGPIPE" },
+#endif
+    { false,
+      false,
+      { UV_ENOENT, true, 0, 0 },
+      cmUVProcessChain::ExceptionCode::Spawn,
+      uv_strerror(UV_ENOENT) },
+#ifdef _WIN32
+    { true,
+      true,
+      { 0, true, STATUS_ACCESS_VIOLATION, 0 },
+      cmUVProcessChain::ExceptionCode::Fault,
+      "Access violation" },
+#else
+    { false,
+      true,
+      { 0, true, 0, SIGABRT },
+      cmUVProcessChain::ExceptionCode::Other,
+      "Subprocess aborted" },
+#endif
+  };
+
+  std::vector<const cmUVProcessChain::Status*> status;
+
+  cmUVProcessChainBuilder builder;
+  builder.AddCommand({ helperCommand, "echo" })
+    .AddCommand({ "this_command_is_for_cmake_and_should_never_exist" })
+    .AddCommand({ helperCommand, "dedup" })
+    .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
+    .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
+
+  auto chain = builder.Start();
+  if (!chain.Valid()) {
+    std::cout << "Valid() returned false, should be true" << std::endl;
+    return false;
+  }
+
+  // Some platforms, like Solaris 10, take a long time to report a trapped
+  // subprocess to the parent process (about 1.7 seconds in the case of
+  // Solaris 10.) Wait 3 seconds to give it enough time.
+  if (chain.Wait(3000)) {
+    std::cout << "Wait() did not time out" << std::endl;
+    return false;
+  }
+
+  status = chain.GetStatus();
+  if (!resultsMatch(status, status1)) {
+    std::cout << "GetStatus() did not produce expected output" << std::endl;
+    printResults(status, status1);
+    return false;
+  }
+
+  if (!chain.Wait()) {
+    std::cout << "Wait() timed out" << std::endl;
+    return false;
+  }
+
+  status = chain.GetStatus();
+  if (!resultsMatch(status, status2)) {
+    std::cout << "GetStatus() did not produce expected output" << std::endl;
+    printResults(status, status2);
+    return false;
+  }
+
+  return true;
+}
+
 int testUVProcessChain(int argc, char** const argv)
 {
   if (argc < 2) {
@@ -518,5 +652,10 @@ int testUVProcessChain(int argc, char** const argv)
     return -1;
   }
 
+  if (!testUVProcessChainSpawnFail(argv[1])) {
+    std::cout << "While executing testUVProcessChainSpawnFail().\n";
+    return -1;
+  }
+
   return 0;
 }

+ 2 - 2
Tests/CMakeLib/testUVProcessChainHelper.cxx

@@ -32,13 +32,13 @@ int main(int argc, char** argv)
 
   std::string command = argv[1];
   if (command == "echo") {
-    std::this_thread::sleep_for(std::chrono::milliseconds(3000));
+    std::this_thread::sleep_for(std::chrono::milliseconds(6000));
     std::cout << "HELLO world!" << std::flush;
     std::cerr << "1" << std::flush;
     return 0;
   }
   if (command == "capitalize") {
-    std::this_thread::sleep_for(std::chrono::milliseconds(9000));
+    std::this_thread::sleep_for(std::chrono::milliseconds(12000));
     std::string input = getStdin();
     for (auto& c : input) {
       c = static_cast<char>(std::toupper(c));