diff --git a/libc/cmake/modules/LLVMLibCRules.cmake b/libc/cmake/modules/LLVMLibCRules.cmake --- a/libc/cmake/modules/LLVMLibCRules.cmake +++ b/libc/cmake/modules/LLVMLibCRules.cmake @@ -355,7 +355,7 @@ ${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 --- a/libc/test/src/signal/raise_test.cpp +++ b/libc/test/src/signal/raise_test.cpp @@ -14,4 +14,8 @@ // 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 --- 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.h b/libc/utils/UnitTest/Test.h --- 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 @@ 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 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 @@ #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/UnitTest/Test.cpp b/libc/utils/UnitTest/Test.cpp --- 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 @@ 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/testutils/CMakeLists.txt b/libc/utils/testutils/CMakeLists.txt new file mode 100644 --- /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 --- /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 --- /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 +#include +#include +#include +#include + +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