[libc] [UnitTest] Add timeout to death tests
Summary: This patch adds a timeout of 500ms to death tests. As we add multithreaded code and locks, deadlocks become more likely so timeout will be useful. Additionally: - Better error handling in `invokeSubprocess` - Makes `ProcessStatus`'s methods const Reviewers: sivachandra, MaskRay, gchatelet, PaulkaToast Reviewed By: sivachandra, PaulkaToast Subscribers: tschuett, libc-commits Differential Revision: https://reviews.llvm.org/D75651
This commit is contained in:
parent
d7c5037e6b
commit
af0b0e00fb
|
@ -250,7 +250,20 @@ bool Test::testMatch(RunContext &Ctx, bool MatchResult, MatcherBase &Matcher,
|
|||
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);
|
||||
testutils::ProcessStatus Result = testutils::invokeInSubprocess(Func, 500);
|
||||
|
||||
if (const char *error = Result.getError()) {
|
||||
Ctx.markFail();
|
||||
llvm::outs() << File << ":" << Line << ": FAILURE\n" << error << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Result.timedOut()) {
|
||||
Ctx.markFail();
|
||||
llvm::outs() << File << ":" << Line << ": FAILURE\n"
|
||||
<< "Process timed out after " << 500 << " miliseconds.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Result.exitedNormally()) {
|
||||
Ctx.markFail();
|
||||
|
@ -281,7 +294,20 @@ 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);
|
||||
testutils::ProcessStatus Result = testutils::invokeInSubprocess(Func, 500);
|
||||
|
||||
if (const char *error = Result.getError()) {
|
||||
Ctx.markFail();
|
||||
llvm::outs() << File << ":" << Line << ": FAILURE\n" << error << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Result.timedOut()) {
|
||||
Ctx.markFail();
|
||||
llvm::outs() << File << ":" << Line << ": FAILURE\n"
|
||||
<< "Process timed out after " << 500 << " miliseconds.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Result.exitedNormally()) {
|
||||
Ctx.markFail();
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#ifndef LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H
|
||||
#define LLVM_LIBC_UTILS_TESTUTILS_EXECUTEFUNCTION_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace __llvm_libc {
|
||||
namespace testutils {
|
||||
|
||||
|
@ -20,13 +22,25 @@ public:
|
|||
|
||||
struct ProcessStatus {
|
||||
int PlatformDefined;
|
||||
const char *failure = nullptr;
|
||||
|
||||
bool exitedNormally();
|
||||
int getExitCode();
|
||||
int getFatalSignal();
|
||||
static constexpr uintptr_t timeout = -1L;
|
||||
|
||||
static ProcessStatus Error(const char *error) { return {0, error}; }
|
||||
static ProcessStatus TimedOut() {
|
||||
return {0, reinterpret_cast<const char *>(timeout)};
|
||||
}
|
||||
|
||||
bool timedOut() const {
|
||||
return failure == reinterpret_cast<const char *>(timeout);
|
||||
}
|
||||
const char *getError() const { return timedOut() ? nullptr : failure; }
|
||||
bool exitedNormally() const;
|
||||
int getExitCode() const;
|
||||
int getFatalSignal() const;
|
||||
};
|
||||
|
||||
ProcessStatus invokeInSubprocess(FunctionCaller *Func);
|
||||
ProcessStatus invokeInSubprocess(FunctionCaller *Func, unsigned TimeoutMS = -1);
|
||||
|
||||
const char *signalAsString(int Signum);
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
@ -17,32 +19,57 @@
|
|||
namespace __llvm_libc {
|
||||
namespace testutils {
|
||||
|
||||
bool ProcessStatus::exitedNormally() { return WIFEXITED(PlatformDefined); }
|
||||
bool ProcessStatus::exitedNormally() const {
|
||||
return WIFEXITED(PlatformDefined);
|
||||
}
|
||||
|
||||
int ProcessStatus::getExitCode() {
|
||||
int ProcessStatus::getExitCode() const {
|
||||
assert(exitedNormally() && "Abnormal termination, no exit code");
|
||||
return WEXITSTATUS(PlatformDefined);
|
||||
}
|
||||
|
||||
int ProcessStatus::getFatalSignal() {
|
||||
int ProcessStatus::getFatalSignal() const {
|
||||
if (exitedNormally())
|
||||
return 0;
|
||||
return WTERMSIG(PlatformDefined);
|
||||
}
|
||||
|
||||
ProcessStatus invokeInSubprocess(FunctionCaller *Func) {
|
||||
ProcessStatus invokeInSubprocess(FunctionCaller *Func, unsigned timeoutMS) {
|
||||
std::unique_ptr<FunctionCaller> X(Func);
|
||||
int pipeFDs[2];
|
||||
if (::pipe(pipeFDs) == -1)
|
||||
return ProcessStatus::Error("pipe(2) failed");
|
||||
|
||||
// Don't copy the buffers into the child process and print twice.
|
||||
llvm::outs().flush();
|
||||
llvm::errs().flush();
|
||||
pid_t Pid = ::fork();
|
||||
if (Pid == -1)
|
||||
return ProcessStatus::Error("fork(2) failed");
|
||||
|
||||
if (!Pid) {
|
||||
(*Func)();
|
||||
std::exit(0);
|
||||
}
|
||||
::close(pipeFDs[1]);
|
||||
|
||||
int WStatus;
|
||||
::waitpid(Pid, &WStatus, 0);
|
||||
delete Func;
|
||||
struct pollfd pollFD {
|
||||
pipeFDs[0], 0, 0
|
||||
};
|
||||
// No events requested so this call will only return after the timeout or if
|
||||
// the pipes peer was closed, signaling the process exited.
|
||||
if (::poll(&pollFD, 1, timeoutMS) == -1)
|
||||
return ProcessStatus::Error("poll(2) failed");
|
||||
// If the pipe wasn't closed by the child yet then timeout has expired.
|
||||
if (!(pollFD.revents & POLLHUP)) {
|
||||
::kill(Pid, SIGKILL);
|
||||
return ProcessStatus::TimedOut();
|
||||
}
|
||||
|
||||
int WStatus = 0;
|
||||
int status = ::waitpid(Pid, &WStatus, WNOHANG);
|
||||
assert(status == Pid && "wait call should not block here");
|
||||
(void)status;
|
||||
return {WStatus};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue