mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-24 06:10:12 +00:00
[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
This commit is contained in:
parent
eefda18227
commit
0368997402
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
add_subdirectory(CPP)
|
||||
add_subdirectory(HdrGen)
|
||||
add_subdirectory(testutils)
|
||||
add_subdirectory(UnitTest)
|
||||
add_subdirectory(benchmarks)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 @@ protected:
|
||||
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 @@ private:
|
||||
#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
|
||||
|
8
libc/utils/testutils/CMakeLists.txt
Normal file
8
libc/utils/testutils/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
add_library(
|
||||
libc_test_utils
|
||||
ExecuteFunction.h
|
||||
)
|
||||
|
||||
if(CMAKE_HOST_UNIX)
|
||||
target_sources(libc_test_utils PRIVATE ExecuteFunctionUnix.cpp)
|
||||
endif()
|
36
libc/utils/testutils/ExecuteFunction.h
Normal file
36
libc/utils/testutils/ExecuteFunction.h
Normal file
@ -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
|
52
libc/utils/testutils/ExecuteFunctionUnix.cpp
Normal file
52
libc/utils/testutils/ExecuteFunctionUnix.cpp
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user