Index: libc/utils/UnitTest/Test.cpp =================================================================== --- libc/utils/UnitTest/Test.cpp +++ libc/utils/UnitTest/Test.cpp @@ -235,7 +235,13 @@ 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.exitedNormally()) { Ctx.markFail(); @@ -266,7 +272,13 @@ 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.exitedNormally()) { Ctx.markFail(); Index: libc/utils/testutils/ExecuteFunction.h =================================================================== --- libc/utils/testutils/ExecuteFunction.h +++ libc/utils/testutils/ExecuteFunction.h @@ -20,13 +20,17 @@ struct ProcessStatus { int PlatformDefined; + const char *Failure = nullptr; - bool exitedNormally(); - int getExitCode(); - int getFatalSignal(); + static ProcessStatus Error(const char *Error) { return {0, Error}; } + + const char *getError() const { return 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); Index: libc/utils/testutils/ExecuteFunctionUnix.cpp =================================================================== --- libc/utils/testutils/ExecuteFunctionUnix.cpp +++ libc/utils/testutils/ExecuteFunctionUnix.cpp @@ -10,6 +10,8 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include +#include #include #include #include @@ -17,32 +19,55 @@ 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 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]); + + struct pollfd PollFD { + PipeFDs[0], 0, 0 + }; + 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::Error("Process timed out"); + } - int WStatus; - ::waitpid(Pid, &WStatus, 0); - delete Func; + int WStatus = 0; + int Status = ::waitpid(Pid, &WStatus, WNOHANG); + assert(Status == Pid && "wait call should not block here"); + (void)Status; return {WStatus}; }