[libc-commits] [libc] 0368997 - [libc] [UnitTest] Create death tests

Alex Brachet via libc-commits libc-commits at lists.llvm.org
Mon Feb 24 14:55:01 PST 2020


Author: Alex Brachet
Date: 2020-02-24T17:53:43-05:00
New Revision: 0368997402ae5f62efc83ec7b6f9052e01916943

URL: https://github.com/llvm/llvm-project/commit/0368997402ae5f62efc83ec7b6f9052e01916943
DIFF: https://github.com/llvm/llvm-project/commit/0368997402ae5f62efc83ec7b6f9052e01916943.diff

LOG: [libc] [UnitTest] Create death tests

Summary: This patch adds `EXPECT_EXITS` and `EXPECT_DEATH` macros for testing exit codes and deadly signals. They are less convoluted than their analogs in GTEST and don't have matchers but just take an int for either the exit code or the signal respectively. Nor do they have any regex match against the stdout/stderr of the child process.

Reviewers: sivachandra, gchatelet

Reviewed By: sivachandra

Subscribers: mgorny, MaskRay, tschuett, libc-commits

Differential Revision: https://reviews.llvm.org/D74665

Added: 
    libc/utils/testutils/CMakeLists.txt
    libc/utils/testutils/ExecuteFunction.h
    libc/utils/testutils/ExecuteFunctionUnix.cpp

Modified: 
    libc/cmake/modules/LLVMLibCRules.cmake
    libc/test/src/signal/raise_test.cpp
    libc/utils/CMakeLists.txt
    libc/utils/UnitTest/Test.cpp
    libc/utils/UnitTest/Test.h

Removed: 
    


################################################################################
diff  --git a/libc/cmake/modules/LLVMLibCRules.cmake b/libc/cmake/modules/LLVMLibCRules.cmake
index ae4a1448a594..e807998d0221 100644
--- a/libc/cmake/modules/LLVMLibCRules.cmake
+++ b/libc/cmake/modules/LLVMLibCRules.cmake
@@ -355,7 +355,7 @@ function(add_libc_unittest target_name)
     ${LIBC_UNITTEST_DEPENDS}
   )
 
-  target_link_libraries(${target_name} PRIVATE LibcUnitTest)
+  target_link_libraries(${target_name} PRIVATE LibcUnitTest libc_test_utils)
 
   add_custom_command(
     TARGET ${target_name}

diff  --git a/libc/test/src/signal/raise_test.cpp b/libc/test/src/signal/raise_test.cpp
index 8d48874c63c2..882fee49e26e 100644
--- a/libc/test/src/signal/raise_test.cpp
+++ b/libc/test/src/signal/raise_test.cpp
@@ -14,4 +14,8 @@ TEST(SignalTest, Raise) {
   // SIGCONT is ingored unless stopped, so we can use it to check the return
   // value of raise without needing to block.
   EXPECT_EQ(__llvm_libc::raise(SIGCONT), 0);
+
+  // SIGKILL is chosen because other fatal signals could be caught by sanitizers
+  // for example and incorrectly report test failure.
+  EXPECT_DEATH([] { __llvm_libc::raise(SIGKILL); }, SIGKILL);
 }

diff  --git a/libc/utils/CMakeLists.txt b/libc/utils/CMakeLists.txt
index cbc551bca9f8..e0aa20e19a3e 100644
--- a/libc/utils/CMakeLists.txt
+++ b/libc/utils/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_subdirectory(CPP)
 add_subdirectory(HdrGen)
+add_subdirectory(testutils)
 add_subdirectory(UnitTest)
 add_subdirectory(benchmarks)

diff  --git a/libc/utils/UnitTest/Test.cpp b/libc/utils/UnitTest/Test.cpp
index 032bf2cadca9..99fb17d143ed 100644
--- a/libc/utils/UnitTest/Test.cpp
+++ b/libc/utils/UnitTest/Test.cpp
@@ -8,6 +8,7 @@
 
 #include "Test.h"
 
+#include "utils/testutils/ExecuteFunction.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -228,6 +229,64 @@ bool Test::testStrNe(RunContext &Ctx, const char *LHS, const char *RHS,
                         llvm::StringRef(RHS), LHSStr, RHSStr, File, Line);
 }
 
+bool Test::testProcessKilled(RunContext &Ctx, testutils::FunctionCaller *Func,
+                             int Signal, const char *LHSStr, const char *RHSStr,
+                             const char *File, unsigned long Line) {
+  testutils::ProcessStatus Result = testutils::invokeInSubprocess(Func);
+
+  if (Result.exitedNormally()) {
+    Ctx.markFail();
+    llvm::outs() << File << ":" << Line << ": FAILURE\n"
+                 << "Expected " << LHSStr
+                 << " to be killed by a signal\nBut it exited normally!\n";
+    return false;
+  }
+
+  int KilledBy = Result.getFatalSignal();
+  assert(KilledBy != 0 && "Not killed by any signal");
+  if (Signal == -1 || KilledBy == Signal)
+    return true;
+
+  using testutils::signalAsString;
+  Ctx.markFail();
+  llvm::outs() << File << ":" << Line << ": FAILURE\n"
+               << "              Expected: " << LHSStr << '\n'
+               << "To be killed by signal: " << Signal << '\n'
+               << "              Which is: " << signalAsString(Signal) << '\n'
+               << "  But it was killed by: " << KilledBy << '\n'
+               << "              Which is: " << signalAsString(KilledBy)
+               << '\n';
+  return false;
+}
+
+bool Test::testProcessExits(RunContext &Ctx, testutils::FunctionCaller *Func,
+                            int ExitCode, const char *LHSStr,
+                            const char *RHSStr, const char *File,
+                            unsigned long Line) {
+  testutils::ProcessStatus Result = testutils::invokeInSubprocess(Func);
+
+  if (!Result.exitedNormally()) {
+    Ctx.markFail();
+    llvm::outs() << File << ":" << Line << ": FAILURE\n"
+                 << "Expected " << LHSStr << '\n'
+                 << "to exit with exit code " << ExitCode << '\n'
+                 << "But it exited abnormally!\n";
+    return false;
+  }
+
+  int ActualExit = Result.getExitCode();
+  if (ActualExit == ExitCode)
+    return true;
+
+  Ctx.markFail();
+  llvm::outs() << File << ":" << Line << ": FAILURE\n"
+               << "Expected exit code of: " << LHSStr << '\n'
+               << "             Which is: " << ActualExit << '\n'
+               << "       To be equal to: " << RHSStr << '\n'
+               << "             Which is: " << ExitCode << '\n';
+  return false;
+}
+
 } // namespace testing
 } // namespace __llvm_libc
 

diff  --git a/libc/utils/UnitTest/Test.h b/libc/utils/UnitTest/Test.h
index cb7963e86729..509c532448f5 100644
--- a/libc/utils/UnitTest/Test.h
+++ b/libc/utils/UnitTest/Test.h
@@ -6,10 +6,14 @@
 //
 //===----------------------------------------------------------------------===//
 
-// This file can only include headers from utils/CPP/. No other header should be
-// included.
+#ifndef LLVM_LIBC_UTILS_UNITTEST_H
+#define LLVM_LIBC_UTILS_UNITTEST_H
+
+// This file can only include headers from utils/CPP/ or utils/testutils. No
+// other headers should be included.
 
 #include "utils/CPP/TypeTraits.h"
+#include "utils/testutils/ExecuteFunction.h"
 
 namespace __llvm_libc {
 namespace testing {
@@ -89,6 +93,26 @@ class Test {
                         const char *LHSStr, const char *RHSStr,
                         const char *File, unsigned long Line);
 
+  static bool testProcessExits(RunContext &Ctx, testutils::FunctionCaller *Func,
+                               int ExitCode, const char *LHSStr,
+                               const char *RHSStr, const char *File,
+                               unsigned long Line);
+
+  static bool testProcessKilled(RunContext &Ctx,
+                                testutils::FunctionCaller *Func, int Signal,
+                                const char *LHSStr, const char *RHSStr,
+                                const char *File, unsigned long Line);
+
+  template <typename Func> testutils::FunctionCaller *createCallable(Func f) {
+    struct Callable : public testutils::FunctionCaller {
+      Func f;
+      Callable(Func f) : f(f) {}
+      void operator()() override { f(); }
+    };
+
+    return new Callable(f);
+  }
+
 private:
   virtual void Run(RunContext &Ctx) = 0;
   virtual const char *getName() const = 0;
@@ -187,3 +211,23 @@ class Test {
 #define ASSERT_FALSE(VAL)                                                      \
   if (!EXPECT_FALSE(VAL))                                                      \
   return
+
+#define EXPECT_EXITS(FUNC, EXIT)                                               \
+  __llvm_libc::testing::Test::testProcessExits(                                \
+      Ctx, __llvm_libc::testing::Test::createCallable(FUNC), EXIT, #FUNC,      \
+      #EXIT, __FILE__, __LINE__)
+
+#define ASSERT_EXITS(FUNC, EXIT)                                               \
+  if (!EXPECT_EXITS(FUNC, EXIT))                                               \
+  return
+
+#define EXPECT_DEATH(FUNC, SIG)                                                \
+  __llvm_libc::testing::Test::testProcessKilled(                               \
+      Ctx, __llvm_libc::testing::Test::createCallable(FUNC), SIG, #FUNC, #SIG, \
+      __FILE__, __LINE__)
+
+#define ASSERT_DEATH(FUNC, EXIT)                                               \
+  if (!EXPECT_DEATH(FUNC, EXIT))                                               \
+  return
+
+#endif // LLVM_LIBC_UTILS_UNITTEST_H

diff  --git a/libc/utils/testutils/CMakeLists.txt b/libc/utils/testutils/CMakeLists.txt
new file mode 100644
index 000000000000..966c44802c18
--- /dev/null
+++ b/libc/utils/testutils/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_library(
+  libc_test_utils
+  ExecuteFunction.h
+)
+
+if(CMAKE_HOST_UNIX)
+  target_sources(libc_test_utils PRIVATE ExecuteFunctionUnix.cpp)
+endif()

diff  --git a/libc/utils/testutils/ExecuteFunction.h b/libc/utils/testutils/ExecuteFunction.h
new file mode 100644
index 000000000000..f3ba4aeda59b
--- /dev/null
+++ b/libc/utils/testutils/ExecuteFunction.h
@@ -0,0 +1,36 @@
+//===---------------------- ExecuteFunction.h -------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H
+#define LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H
+
+namespace __llvm_libc {
+namespace testutils {
+
+class FunctionCaller {
+public:
+  virtual ~FunctionCaller() {}
+  virtual void operator()() = 0;
+};
+
+struct ProcessStatus {
+  int PlatformDefined;
+
+  bool exitedNormally();
+  int getExitCode();
+  int getFatalSignal();
+};
+
+ProcessStatus invokeInSubprocess(FunctionCaller *Func);
+
+const char *signalAsString(int Signum);
+
+} // namespace testutils
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H

diff  --git a/libc/utils/testutils/ExecuteFunctionUnix.cpp b/libc/utils/testutils/ExecuteFunctionUnix.cpp
new file mode 100644
index 000000000000..afc4ca1f1473
--- /dev/null
+++ b/libc/utils/testutils/ExecuteFunctionUnix.cpp
@@ -0,0 +1,52 @@
+//===------- ExecuteFunction implementation for Unix-like Systems ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ExecuteFunction.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cassert>
+#include <cstdlib>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+namespace __llvm_libc {
+namespace testutils {
+
+bool ProcessStatus::exitedNormally() { return WIFEXITED(PlatformDefined); }
+
+int ProcessStatus::getExitCode() {
+  assert(exitedNormally() && "Abnormal termination, no exit code");
+  return WEXITSTATUS(PlatformDefined);
+}
+
+int ProcessStatus::getFatalSignal() {
+  if (exitedNormally())
+    return 0;
+  return WTERMSIG(PlatformDefined);
+}
+
+ProcessStatus invokeInSubprocess(FunctionCaller *Func) {
+  // Don't copy the buffers into the child process and print twice.
+  llvm::outs().flush();
+  llvm::errs().flush();
+  pid_t Pid = ::fork();
+  if (!Pid) {
+    (*Func)();
+    std::exit(0);
+  }
+
+  int WStatus;
+  ::waitpid(Pid, &WStatus, 0);
+  delete Func;
+  return {WStatus};
+}
+
+const char *signalAsString(int Signum) { return ::strsignal(Signum); }
+
+} // namespace testutils
+} // namespace __llvm_libc


        


More information about the libc-commits mailing list