[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:
Alex Brachet 2020-02-24 17:53:43 -05:00
parent eefda18227
commit 0368997402
8 changed files with 207 additions and 3 deletions

View File

@ -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}

View File

@ -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);
}

View File

@ -1,4 +1,5 @@
add_subdirectory(CPP)
add_subdirectory(HdrGen)
add_subdirectory(testutils)
add_subdirectory(UnitTest)
add_subdirectory(benchmarks)

View File

@ -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

View File

@ -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

View 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()

View 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

View 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