Skip to content

Commit 0368997

Browse files
committed
[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
1 parent eefda18 commit 0368997

File tree

8 files changed

+207
-3
lines changed

8 files changed

+207
-3
lines changed

libc/cmake/modules/LLVMLibCRules.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ function(add_libc_unittest target_name)
355355
${LIBC_UNITTEST_DEPENDS}
356356
)
357357

358-
target_link_libraries(${target_name} PRIVATE LibcUnitTest)
358+
target_link_libraries(${target_name} PRIVATE LibcUnitTest libc_test_utils)
359359

360360
add_custom_command(
361361
TARGET ${target_name}

libc/test/src/signal/raise_test.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ TEST(SignalTest, Raise) {
1414
// SIGCONT is ingored unless stopped, so we can use it to check the return
1515
// value of raise without needing to block.
1616
EXPECT_EQ(__llvm_libc::raise(SIGCONT), 0);
17+
18+
// SIGKILL is chosen because other fatal signals could be caught by sanitizers
19+
// for example and incorrectly report test failure.
20+
EXPECT_DEATH([] { __llvm_libc::raise(SIGKILL); }, SIGKILL);
1721
}

libc/utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
add_subdirectory(CPP)
22
add_subdirectory(HdrGen)
3+
add_subdirectory(testutils)
34
add_subdirectory(UnitTest)
45
add_subdirectory(benchmarks)

libc/utils/UnitTest/Test.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "Test.h"
1010

11+
#include "utils/testutils/ExecuteFunction.h"
1112
#include "llvm/ADT/StringRef.h"
1213
#include "llvm/Support/raw_ostream.h"
1314

@@ -228,6 +229,64 @@ bool Test::testStrNe(RunContext &Ctx, const char *LHS, const char *RHS,
228229
llvm::StringRef(RHS), LHSStr, RHSStr, File, Line);
229230
}
230231

232+
bool Test::testProcessKilled(RunContext &Ctx, testutils::FunctionCaller *Func,
233+
int Signal, const char *LHSStr, const char *RHSStr,
234+
const char *File, unsigned long Line) {
235+
testutils::ProcessStatus Result = testutils::invokeInSubprocess(Func);
236+
237+
if (Result.exitedNormally()) {
238+
Ctx.markFail();
239+
llvm::outs() << File << ":" << Line << ": FAILURE\n"
240+
<< "Expected " << LHSStr
241+
<< " to be killed by a signal\nBut it exited normally!\n";
242+
return false;
243+
}
244+
245+
int KilledBy = Result.getFatalSignal();
246+
assert(KilledBy != 0 && "Not killed by any signal");
247+
if (Signal == -1 || KilledBy == Signal)
248+
return true;
249+
250+
using testutils::signalAsString;
251+
Ctx.markFail();
252+
llvm::outs() << File << ":" << Line << ": FAILURE\n"
253+
<< " Expected: " << LHSStr << '\n'
254+
<< "To be killed by signal: " << Signal << '\n'
255+
<< " Which is: " << signalAsString(Signal) << '\n'
256+
<< " But it was killed by: " << KilledBy << '\n'
257+
<< " Which is: " << signalAsString(KilledBy)
258+
<< '\n';
259+
return false;
260+
}
261+
262+
bool Test::testProcessExits(RunContext &Ctx, testutils::FunctionCaller *Func,
263+
int ExitCode, const char *LHSStr,
264+
const char *RHSStr, const char *File,
265+
unsigned long Line) {
266+
testutils::ProcessStatus Result = testutils::invokeInSubprocess(Func);
267+
268+
if (!Result.exitedNormally()) {
269+
Ctx.markFail();
270+
llvm::outs() << File << ":" << Line << ": FAILURE\n"
271+
<< "Expected " << LHSStr << '\n'
272+
<< "to exit with exit code " << ExitCode << '\n'
273+
<< "But it exited abnormally!\n";
274+
return false;
275+
}
276+
277+
int ActualExit = Result.getExitCode();
278+
if (ActualExit == ExitCode)
279+
return true;
280+
281+
Ctx.markFail();
282+
llvm::outs() << File << ":" << Line << ": FAILURE\n"
283+
<< "Expected exit code of: " << LHSStr << '\n'
284+
<< " Which is: " << ActualExit << '\n'
285+
<< " To be equal to: " << RHSStr << '\n'
286+
<< " Which is: " << ExitCode << '\n';
287+
return false;
288+
}
289+
231290
} // namespace testing
232291
} // namespace __llvm_libc
233292

libc/utils/UnitTest/Test.h

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9-
// This file can only include headers from utils/CPP/. No other header should be
10-
// included.
9+
#ifndef LLVM_LIBC_UTILS_UNITTEST_H
10+
#define LLVM_LIBC_UTILS_UNITTEST_H
11+
12+
// This file can only include headers from utils/CPP/ or utils/testutils. No
13+
// other headers should be included.
1114

1215
#include "utils/CPP/TypeTraits.h"
16+
#include "utils/testutils/ExecuteFunction.h"
1317

1418
namespace __llvm_libc {
1519
namespace testing {
@@ -89,6 +93,26 @@ class Test {
8993
const char *LHSStr, const char *RHSStr,
9094
const char *File, unsigned long Line);
9195

96+
static bool testProcessExits(RunContext &Ctx, testutils::FunctionCaller *Func,
97+
int ExitCode, const char *LHSStr,
98+
const char *RHSStr, const char *File,
99+
unsigned long Line);
100+
101+
static bool testProcessKilled(RunContext &Ctx,
102+
testutils::FunctionCaller *Func, int Signal,
103+
const char *LHSStr, const char *RHSStr,
104+
const char *File, unsigned long Line);
105+
106+
template <typename Func> testutils::FunctionCaller *createCallable(Func f) {
107+
struct Callable : public testutils::FunctionCaller {
108+
Func f;
109+
Callable(Func f) : f(f) {}
110+
void operator()() override { f(); }
111+
};
112+
113+
return new Callable(f);
114+
}
115+
92116
private:
93117
virtual void Run(RunContext &Ctx) = 0;
94118
virtual const char *getName() const = 0;
@@ -187,3 +211,23 @@ class Test {
187211
#define ASSERT_FALSE(VAL) \
188212
if (!EXPECT_FALSE(VAL)) \
189213
return
214+
215+
#define EXPECT_EXITS(FUNC, EXIT) \
216+
__llvm_libc::testing::Test::testProcessExits( \
217+
Ctx, __llvm_libc::testing::Test::createCallable(FUNC), EXIT, #FUNC, \
218+
#EXIT, __FILE__, __LINE__)
219+
220+
#define ASSERT_EXITS(FUNC, EXIT) \
221+
if (!EXPECT_EXITS(FUNC, EXIT)) \
222+
return
223+
224+
#define EXPECT_DEATH(FUNC, SIG) \
225+
__llvm_libc::testing::Test::testProcessKilled( \
226+
Ctx, __llvm_libc::testing::Test::createCallable(FUNC), SIG, #FUNC, #SIG, \
227+
__FILE__, __LINE__)
228+
229+
#define ASSERT_DEATH(FUNC, EXIT) \
230+
if (!EXPECT_DEATH(FUNC, EXIT)) \
231+
return
232+
233+
#endif // LLVM_LIBC_UTILS_UNITTEST_H

libc/utils/testutils/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
add_library(
2+
libc_test_utils
3+
ExecuteFunction.h
4+
)
5+
6+
if(CMAKE_HOST_UNIX)
7+
target_sources(libc_test_utils PRIVATE ExecuteFunctionUnix.cpp)
8+
endif()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===---------------------- ExecuteFunction.h -------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H
10+
#define LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H
11+
12+
namespace __llvm_libc {
13+
namespace testutils {
14+
15+
class FunctionCaller {
16+
public:
17+
virtual ~FunctionCaller() {}
18+
virtual void operator()() = 0;
19+
};
20+
21+
struct ProcessStatus {
22+
int PlatformDefined;
23+
24+
bool exitedNormally();
25+
int getExitCode();
26+
int getFatalSignal();
27+
};
28+
29+
ProcessStatus invokeInSubprocess(FunctionCaller *Func);
30+
31+
const char *signalAsString(int Signum);
32+
33+
} // namespace testutils
34+
} // namespace __llvm_libc
35+
36+
#endif // LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===------- ExecuteFunction implementation for Unix-like Systems ---------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "ExecuteFunction.h"
10+
#include "llvm/Support/raw_ostream.h"
11+
#include <cassert>
12+
#include <cstdlib>
13+
#include <signal.h>
14+
#include <sys/wait.h>
15+
#include <unistd.h>
16+
17+
namespace __llvm_libc {
18+
namespace testutils {
19+
20+
bool ProcessStatus::exitedNormally() { return WIFEXITED(PlatformDefined); }
21+
22+
int ProcessStatus::getExitCode() {
23+
assert(exitedNormally() && "Abnormal termination, no exit code");
24+
return WEXITSTATUS(PlatformDefined);
25+
}
26+
27+
int ProcessStatus::getFatalSignal() {
28+
if (exitedNormally())
29+
return 0;
30+
return WTERMSIG(PlatformDefined);
31+
}
32+
33+
ProcessStatus invokeInSubprocess(FunctionCaller *Func) {
34+
// Don't copy the buffers into the child process and print twice.
35+
llvm::outs().flush();
36+
llvm::errs().flush();
37+
pid_t Pid = ::fork();
38+
if (!Pid) {
39+
(*Func)();
40+
std::exit(0);
41+
}
42+
43+
int WStatus;
44+
::waitpid(Pid, &WStatus, 0);
45+
delete Func;
46+
return {WStatus};
47+
}
48+
49+
const char *signalAsString(int Signum) { return ::strsignal(Signum); }
50+
51+
} // namespace testutils
52+
} // namespace __llvm_libc

0 commit comments

Comments
 (0)