[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:
Alex Brachet 2020-03-11 23:57:20 -04:00
parent d7c5037e6b
commit af0b0e00fb
3 changed files with 80 additions and 13 deletions

View File

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

View File

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

View File

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