Przeglądaj źródła

execute_process: Improve COMMAND_ERROR_IS_FATAL error capture scenarios

1.  COMMAND_ERROR_IS_FATAL ANY will capture errors if the exit code is
    non zero, there is a timeout or an abnormal exit.

2.  COMMAND_ERROR_IS_FATAL LAST
    will capture if only the last process has an exit code non zero, there
    is a timeout or an abnormal exit.

Fixes: #21562
Asit Dhal 4 lat temu
rodzic
commit
e5a4ffaad1
22 zmienionych plików z 217 dodań i 42 usunięć
  1. 87 31
      Source/cmExecuteProcessCommand.cxx
  2. 4 1
      Tests/RunCMake/CMakeLists.txt
  3. 1 0
      Tests/RunCMake/execute_process/AnyCommandAbnormalExit-result.txt
  4. 4 0
      Tests/RunCMake/execute_process/AnyCommandAbnormalExit-stderr.txt
  5. 5 0
      Tests/RunCMake/execute_process/AnyCommandAbnormalExit.cmake
  6. 4 1
      Tests/RunCMake/execute_process/AnyCommandError-stderr.txt
  7. 4 6
      Tests/RunCMake/execute_process/AnyCommandError.cmake
  8. 4 0
      Tests/RunCMake/execute_process/AnyCommandGood.cmake
  9. 1 0
      Tests/RunCMake/execute_process/AnyCommandTimeout-result.txt
  10. 2 0
      Tests/RunCMake/execute_process/AnyCommandTimeout-stderr.txt
  11. 15 0
      Tests/RunCMake/execute_process/AnyCommandTimeout.cmake
  12. 13 0
      Tests/RunCMake/execute_process/LastCommandAbnormalExit-1.cmake
  13. 1 0
      Tests/RunCMake/execute_process/LastCommandAbnormalExit-2-result.txt
  14. 2 0
      Tests/RunCMake/execute_process/LastCommandAbnormalExit-2-stderr.txt
  15. 13 0
      Tests/RunCMake/execute_process/LastCommandAbnormalExit-2.cmake
  16. 1 1
      Tests/RunCMake/execute_process/LastCommandError-stderr.txt
  17. 12 1
      Tests/RunCMake/execute_process/LastCommandError.cmake
  18. 15 0
      Tests/RunCMake/execute_process/LastCommandGood.cmake
  19. 1 0
      Tests/RunCMake/execute_process/LastCommandTimeout-result.txt
  20. 2 0
      Tests/RunCMake/execute_process/LastCommandTimeout-stderr.txt
  21. 15 0
      Tests/RunCMake/execute_process/LastCommandTimeout.cmake
  22. 11 1
      Tests/RunCMake/execute_process/RunCMakeTest.cmake

+ 87 - 31
Source/cmExecuteProcessCommand.cxx

@@ -6,8 +6,10 @@
 #include <cctype> /* isspace */
 #include <cstdio>
 #include <iostream>
+#include <map>
 #include <memory>
 #include <sstream>
+#include <utility>
 #include <vector>
 
 #include <cm/string_view>
@@ -375,47 +377,101 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
     }
   }
 
-  if (arguments.CommandErrorIsFatal == "ANY"_s) {
-    if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) {
-      std::vector<int> failedIndexes;
-      for (int i = 0; i < static_cast<int>(arguments.Commands.size()); ++i) {
-        if (cmsysProcess_GetStateByIndex(cp, i) ==
-            kwsysProcess_StateByIndex_Exited) {
-          int exitCode = cmsysProcess_GetExitValueByIndex(cp, i);
-          if (exitCode) {
-            failedIndexes.push_back(i);
-          }
+  auto queryProcessStatusByIndex = [&cp](int index) -> std::string {
+    std::string processStatus;
+    switch (cmsysProcess_GetStateByIndex(cp, static_cast<int>(index))) {
+      case kwsysProcess_StateByIndex_Exited: {
+        int exitCode = cmsysProcess_GetExitValueByIndex(cp, index);
+        if (exitCode) {
+          processStatus = "Child return code: " + std::to_string(exitCode);
         }
+      } break;
+      case kwsysProcess_StateByIndex_Exception: {
+        processStatus = cmStrCat(
+          "Abnormal exit with child return code: ",
+          cmsysProcess_GetExceptionStringByIndex(cp, static_cast<int>(index)));
+        break;
       }
-      if (!failedIndexes.empty()) {
-        std::ostringstream oss;
-        oss << "failed command indexes: ";
-        for (auto i = 0u; i < failedIndexes.size(); i++) {
-          if (i == failedIndexes.size() - 1) {
-            oss << failedIndexes[i] + 1;
-          } else {
-            oss << failedIndexes[i] + 1 << ", ";
+      case kwsysProcess_StateByIndex_Error:
+      default:
+        processStatus = "Error getting the child return code";
+        break;
+    }
+    return processStatus;
+  };
+
+  if (arguments.CommandErrorIsFatal == "ANY"_s) {
+    bool ret = true;
+    switch (cmsysProcess_GetState(cp)) {
+      case cmsysProcess_State_Exited: {
+        std::map<int, std::string> failureIndices;
+        for (int i = 0; i < static_cast<int>(arguments.Commands.size()); ++i) {
+          std::string processStatus = queryProcessStatusByIndex(i);
+          if (!processStatus.empty()) {
+            failureIndices[i] = processStatus;
+          }
+          if (!failureIndices.empty()) {
+            std::ostringstream oss;
+            oss << "failed command indexes:\n";
+            for (auto const& e : failureIndices) {
+              oss << "  " << e.first + 1 << ": \"" << e.second << "\"\n";
+            }
+            status.SetError(oss.str());
+            ret = false;
           }
         }
-        status.SetError(oss.str());
-        cmSystemTools::SetFatalErrorOccured();
-        return false;
-      }
+      } break;
+      case cmsysProcess_State_Exception:
+        status.SetError(
+          cmStrCat("abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
+        ret = false;
+        break;
+      case cmsysProcess_State_Error:
+        status.SetError(cmStrCat("error getting child return code: ",
+                                 cmsysProcess_GetErrorString(cp)));
+        ret = false;
+        break;
+      case cmsysProcess_State_Expired:
+        status.SetError("Process terminated due to timeout");
+        ret = false;
+        break;
+    }
+
+    if (!ret) {
+      cmSystemTools::SetFatalErrorOccured();
+      return false;
     }
   }
 
   if (arguments.CommandErrorIsFatal == "LAST"_s) {
-    if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) {
-      int lastIndex = static_cast<int>(arguments.Commands.size() - 1);
-      if (cmsysProcess_GetStateByIndex(cp, lastIndex) ==
-          kwsysProcess_StateByIndex_Exited) {
-        int exitCode = cmsysProcess_GetExitValueByIndex(cp, lastIndex);
-        if (exitCode) {
+    bool ret = true;
+    switch (cmsysProcess_GetState(cp)) {
+      case cmsysProcess_State_Exited: {
+        int lastIndex = static_cast<int>(arguments.Commands.size() - 1);
+        const std::string processStatus = queryProcessStatusByIndex(lastIndex);
+        if (!processStatus.empty()) {
           status.SetError("last command failed");
-          cmSystemTools::SetFatalErrorOccured();
-          return false;
+          ret = false;
         }
-      }
+      } break;
+      case cmsysProcess_State_Exception:
+        status.SetError(
+          cmStrCat("Abnormal exit: ", cmsysProcess_GetExceptionString(cp)));
+        ret = false;
+        break;
+      case cmsysProcess_State_Error:
+        status.SetError(cmStrCat("Error getting child return code: ",
+                                 cmsysProcess_GetErrorString(cp)));
+        ret = false;
+        break;
+      case cmsysProcess_State_Expired:
+        status.SetError("Process terminated due to timeout");
+        ret = false;
+        break;
+    }
+    if (!ret) {
+      cmSystemTools::SetFatalErrorOccured();
+      return false;
     }
   }
 

+ 4 - 1
Tests/RunCMake/CMakeLists.txt

@@ -299,7 +299,10 @@ add_RunCMake_test(add_subdirectory)
 add_RunCMake_test(add_test)
 add_RunCMake_test(build_command)
 add_executable(exit_code exit_code.c)
-set(execute_process_ARGS -DEXIT_CODE_EXE=$<TARGET_FILE:exit_code>)
+set(execute_process_ARGS
+  -DEXIT_CODE_EXE=$<TARGET_FILE:exit_code>
+  -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}
+  )
 if(NOT CMake_TEST_EXTERNAL_CMAKE)
   list(APPEND execute_process_ARGS -DTEST_ENCODING_EXE=$<TARGET_FILE:testEncoding>)
 endif()

+ 1 - 0
Tests/RunCMake/execute_process/AnyCommandAbnormalExit-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/execute_process/AnyCommandAbnormalExit-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at .*AnyCommandAbnormalExit.cmake:[0-9]+ \(execute_process\):
+  execute_process failed command indexes:
+
+    1: "Abnormal exit with child return code: Segmentation fault

+ 5 - 0
Tests/RunCMake/execute_process/AnyCommandAbnormalExit.cmake

@@ -0,0 +1,5 @@
+execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c
+    "import os; os.kill(os.getpid(),11)"
+  COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND_ERROR_IS_FATAL ANY
+  )

+ 4 - 1
Tests/RunCMake/execute_process/AnyCommandError-stderr.txt

@@ -1,2 +1,5 @@
 CMake Error at .*AnyCommandError.cmake:1 \(execute_process\):
-  execute_process failed command indexes: 2, 3, 4
+  execute_process failed command indexes:
+
+    2: "Child return code: 1"
+    3: "Child return code: 1"

+ 4 - 6
Tests/RunCMake/execute_process/AnyCommandError.cmake

@@ -1,8 +1,6 @@
 execute_process(COMMAND ${CMAKE_COMMAND} -E true
-    COMMAND ${CMAKE_COMMAND} -E false
-    COMMAND ${CMAKE_COMMAND} -E false
-    COMMAND ${CMAKE_COMMAND} -E false
-    COMMAND ${CMAKE_COMMAND} -E true
-    COMMAND ${CMAKE_COMMAND} -E true
-    COMMAND_ERROR_IS_FATAL ANY
+  COMMAND ${CMAKE_COMMAND} -E false
+  COMMAND ${CMAKE_COMMAND} -E false
+  COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND_ERROR_IS_FATAL ANY
 )

+ 4 - 0
Tests/RunCMake/execute_process/AnyCommandGood.cmake

@@ -0,0 +1,4 @@
+execute_process(COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND_ERROR_IS_FATAL ANY
+  )

+ 1 - 0
Tests/RunCMake/execute_process/AnyCommandTimeout-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/execute_process/AnyCommandTimeout-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error at .*AnyCommandTimeout.cmake:9 \(execute_process\):
+  execute_process Process terminated due to timeout

+ 15 - 0
Tests/RunCMake/execute_process/AnyCommandTimeout.cmake

@@ -0,0 +1,15 @@
+execute_process(COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND ${CMAKE_COMMAND} -E sleep 10
+  COMMAND ${CMAKE_COMMAND} -E true
+  TIMEOUT 1
+  RESULT_VARIABLE result
+)
+
+if(NOT result EQUAL "0")
+  execute_process(COMMAND ${CMAKE_COMMAND} -E true
+    COMMAND ${CMAKE_COMMAND} -E sleep 10
+    COMMAND ${CMAKE_COMMAND} -E true
+    TIMEOUT 1
+    COMMAND_ERROR_IS_FATAL ANY
+    )
+endif()

+ 13 - 0
Tests/RunCMake/execute_process/LastCommandAbnormalExit-1.cmake

@@ -0,0 +1,13 @@
+execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c
+    "import os; os.kill(os.getpid(),11)"
+  COMMAND ${CMAKE_COMMAND} -E true
+  RESULT_VARIABLE result
+  )
+
+if(result EQUAL "0")
+  execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c
+      "import os; os.kill(os.getpid(),11)"
+    COMMAND ${CMAKE_COMMAND} -E true
+    COMMAND_ERROR_IS_FATAL LAST
+    )
+endif()

+ 1 - 0
Tests/RunCMake/execute_process/LastCommandAbnormalExit-2-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/execute_process/LastCommandAbnormalExit-2-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error at .*LastCommandAbnormalExit-2.cmake:[0-9]+ \(execute_process\):
+  execute_process Abnormal exit: Segmentation fault

+ 13 - 0
Tests/RunCMake/execute_process/LastCommandAbnormalExit-2.cmake

@@ -0,0 +1,13 @@
+execute_process(COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND "${PYTHON_EXECUTABLE}" -c
+    "import os; os.kill(os.getpid(),11)"
+  RESULT_VARIABLE result
+  )
+
+if(NOT result EQUAL "0")
+  execute_process(COMMAND ${CMAKE_COMMAND} -E true
+    COMMAND "${PYTHON_EXECUTABLE}" -c
+      "import os; os.kill(os.getpid(),11)"
+    COMMAND_ERROR_IS_FATAL LAST
+    )
+endif()

+ 1 - 1
Tests/RunCMake/execute_process/LastCommandError-stderr.txt

@@ -1,2 +1,2 @@
-CMake Error at .*LastCommandError.cmake:1 \(execute_process\):
+CMake Error at .*LastCommandError.cmake:11 \(execute_process\):
   execute_process last command failed

+ 12 - 1
Tests/RunCMake/execute_process/LastCommandError.cmake

@@ -1,8 +1,19 @@
 execute_process(COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND ${CMAKE_COMMAND} -E false
+  COMMAND ${CMAKE_COMMAND} -E false
+  COMMAND ${CMAKE_COMMAND} -E false
+  COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND ${CMAKE_COMMAND} -E false
+  RESULT_VARIABLE result
+)
+
+if(NOT result EQUAL "0")
+  execute_process(COMMAND ${CMAKE_COMMAND} -E true
     COMMAND ${CMAKE_COMMAND} -E false
     COMMAND ${CMAKE_COMMAND} -E false
     COMMAND ${CMAKE_COMMAND} -E false
     COMMAND ${CMAKE_COMMAND} -E true
     COMMAND ${CMAKE_COMMAND} -E false
     COMMAND_ERROR_IS_FATAL LAST
-)
+    )
+endif()

+ 15 - 0
Tests/RunCMake/execute_process/LastCommandGood.cmake

@@ -0,0 +1,15 @@
+execute_process(COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND ${CMAKE_COMMAND} -E false
+  COMMAND ${CMAKE_COMMAND} -E false
+  COMMAND ${CMAKE_COMMAND} -E true
+  RESULT_VARIABLE result
+  )
+
+if(result EQUAL "0")
+  execute_process(COMMAND ${CMAKE_COMMAND} -E true
+    COMMAND ${CMAKE_COMMAND} -E false
+    COMMAND ${CMAKE_COMMAND} -E false
+    COMMAND ${CMAKE_COMMAND} -E true
+    COMMAND_ERROR_IS_FATAL LAST
+    )
+endif()

+ 1 - 0
Tests/RunCMake/execute_process/LastCommandTimeout-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/execute_process/LastCommandTimeout-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error at .*LastCommandTimeout.cmake:9 \(execute_process\):
+  execute_process Process terminated due to timeout

+ 15 - 0
Tests/RunCMake/execute_process/LastCommandTimeout.cmake

@@ -0,0 +1,15 @@
+execute_process(COMMAND ${CMAKE_COMMAND} -E true
+  COMMAND ${CMAKE_COMMAND} -E sleep 10
+  COMMAND ${CMAKE_COMMAND} -E true
+  TIMEOUT 1
+  RESULT_VARIABLE result
+)
+
+if(NOT result EQUAL "0")
+  execute_process(COMMAND ${CMAKE_COMMAND} -E true
+    COMMAND ${CMAKE_COMMAND} -E sleep 10
+    COMMAND ${CMAKE_COMMAND} -E true
+    TIMEOUT 1
+    COMMAND_ERROR_IS_FATAL LAST
+    )
+endif()

+ 11 - 1
Tests/RunCMake/execute_process/RunCMakeTest.cmake

@@ -27,6 +27,16 @@ run_cmake_command(EchoCommand3 ${CMAKE_COMMAND}
 
 run_cmake_command(EchoVariable ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/EchoVariable.cmake)
 
+run_cmake_command(CommandError ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/CommandError.cmake)
 run_cmake_command(AnyCommandError ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/AnyCommandError.cmake)
+run_cmake_command(AnyCommandTimeout ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/AnyCommandTimeout.cmake)
+run_cmake_command(AnyCommandGood ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/AnyCommandGood.cmake)
 run_cmake_command(LastCommandError ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/LastCommandError.cmake)
-run_cmake_command(CommandError ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/CommandError.cmake)
+run_cmake_command(LastCommandTimeout ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/LastCommandTimeout.cmake)
+run_cmake_command(LastCommandGood ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/LastCommandGood.cmake)
+
+if(UNIX AND PYTHON_EXECUTABLE)
+  run_cmake_command(AnyCommandAbnormalExit ${CMAKE_COMMAND} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -P ${RunCMake_SOURCE_DIR}/AnyCommandAbnormalExit.cmake)
+  run_cmake_command(LastCommandAbnormalExit-1 ${CMAKE_COMMAND} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -P ${RunCMake_SOURCE_DIR}/LastCommandAbnormalExit-1.cmake)
+  run_cmake_command(LastCommandAbnormalExit-2 ${CMAKE_COMMAND} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -P ${RunCMake_SOURCE_DIR}/LastCommandAbnormalExit-2.cmake)
+endif()