john.brawn created this revision. john.brawn added reviewers: ldionne, Mordante, michaelplatings. Herald added a project: All. john.brawn requested review of this revision. Herald added a project: libc++. Herald added a subscriber: libcxx-commits. Herald added a reviewer: libc++.
Currently check_assertion.h checks an assertion by forking and checking the exit code of the child process, but this means such tests don't work on targets where fork doesn't exist (e.g. bare metal targets). Instead call setjmp just before we call the function we want to test, then longjmp out of __libcpp_assertion_handler with a return value indicating whether the assert happened as expected. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D124255 Files: libcxx/test/support/check_assertion.h libcxx/test/support/test.support/test_check_assertion.pass.cpp
Index: libcxx/test/support/test.support/test_check_assertion.pass.cpp =================================================================== --- libcxx/test/support/test.support/test_check_assertion.pass.cpp +++ libcxx/test/support/test.support/test_check_assertion.pass.cpp @@ -22,12 +22,6 @@ DeathTest::ResultKind RK = DT.Run(func); auto OnFailure = [&](std::string msg) { std::fprintf(stderr, "EXPECT_DEATH( %s ) failed! (%s)\n\n", stmt, msg.c_str()); - if (!DT.getChildStdErr().empty()) { - std::fprintf(stderr, "---------- standard err ----------\n%s\n", DT.getChildStdErr().c_str()); - } - if (!DT.getChildStdOut().empty()) { - std::fprintf(stderr, "---------- standard out ----------\n%s\n", DT.getChildStdOut().c_str()); - } return false; }; if (RK != ExpectResult) Index: libcxx/test/support/check_assertion.h =================================================================== --- libcxx/test/support/check_assertion.h +++ libcxx/test/support/check_assertion.h @@ -17,9 +17,8 @@ #include <string_view> #include <utility> -#include <unistd.h> #include <errno.h> -#include <sys/wait.h> +#include <setjmp.h> #include "test_macros.h" #include "test_allocator.h" @@ -108,9 +107,16 @@ return GMatch; } +static jmp_buf& GetJmpBuf() { + static jmp_buf env; + return env; +} + struct DeathTest { enum ResultKind { - RK_DidNotDie, RK_MatchFound, RK_MatchFailure, RK_SetupFailure, RK_Unknown + // This enum starts from 1 and not 0 as we want to return the ResultKind + // through setjmp/longjmp, which doesn't permit 0. + RK_DidNotDie = 1, RK_MatchFound, RK_MatchFailure, RK_Unknown }; static const char* ResultKindToString(ResultKind RK) { @@ -118,7 +124,6 @@ switch (RK) { CASE(RK_MatchFailure); CASE(RK_DidNotDie); - CASE(RK_SetupFailure); CASE(RK_MatchFound); CASE(RK_Unknown); } @@ -133,114 +138,30 @@ template <class Func> ResultKind Run(Func&& f) { - int pipe_res = pipe(stdout_pipe_fd_); - assert(pipe_res != -1 && "failed to create pipe"); - pipe_res = pipe(stderr_pipe_fd_); - assert(pipe_res != -1 && "failed to create pipe"); - pid_t child_pid = fork(); - assert(child_pid != -1 && - "failed to fork a process to perform a death test"); - child_pid_ = child_pid; - if (child_pid_ == 0) { - RunForChild(std::forward<Func>(f)); - assert(false && "unreachable"); - } - return RunForParent(); - } - - int getChildExitCode() const { return exit_code_; } - std::string const& getChildStdOut() const { return stdout_from_child_; } - std::string const& getChildStdErr() const { return stderr_from_child_; } -private: - template <class Func> - TEST_NORETURN void RunForChild(Func&& f) { - close(GetStdOutReadFD()); // don't need to read from the pipe in the child. - close(GetStdErrReadFD()); - auto DupFD = [](int DestFD, int TargetFD) { - int dup_result = dup2(DestFD, TargetFD); - if (dup_result == -1) - std::exit(RK_SetupFailure); - }; - DupFD(GetStdOutWriteFD(), STDOUT_FILENO); - DupFD(GetStdErrWriteFD(), STDERR_FILENO); - GlobalMatcher() = matcher_; - f(); - std::exit(RK_DidNotDie); - } - - static std::string ReadChildIOUntilEnd(int FD) { - std::string error_msg; - char buffer[256]; - int num_read; - do { - while ((num_read = read(FD, buffer, 255)) > 0) { - buffer[num_read] = '\0'; - error_msg += buffer; - } - } while (num_read == -1 && errno == EINTR); - return error_msg; - } - - void CaptureIOFromChild() { - close(GetStdOutWriteFD()); // no need to write from the parent process - close(GetStdErrWriteFD()); - stdout_from_child_ = ReadChildIOUntilEnd(GetStdOutReadFD()); - stderr_from_child_ = ReadChildIOUntilEnd(GetStdErrReadFD()); - close(GetStdOutReadFD()); - close(GetStdErrReadFD()); - } - - ResultKind RunForParent() { - CaptureIOFromChild(); - - int status_value; - pid_t result = waitpid(child_pid_, &status_value, 0); - assert(result != -1 && "there is no child process to wait for"); - - if (WIFEXITED(status_value)) { - exit_code_ = WEXITSTATUS(status_value); - if (!IsValidResultKind(exit_code_)) - return RK_Unknown; - return static_cast<ResultKind>(exit_code_); + int n = setjmp(GetJmpBuf()); + if (n == 0) { + f(); + return RK_DidNotDie; } + if (IsValidResultKind(n)) + return (ResultKind)n; return RK_Unknown; } DeathTest(DeathTest const&) = delete; DeathTest& operator=(DeathTest const&) = delete; - int GetStdOutReadFD() const { - return stdout_pipe_fd_[0]; - } - - int GetStdOutWriteFD() const { - return stdout_pipe_fd_[1]; - } - - int GetStdErrReadFD() const { - return stderr_pipe_fd_[0]; - } - - int GetStdErrWriteFD() const { - return stderr_pipe_fd_[1]; - } private: AssertionInfoMatcher matcher_; - pid_t child_pid_ = -1; - int exit_code_ = -1; - int stdout_pipe_fd_[2]; - int stderr_pipe_fd_[2]; - std::string stdout_from_child_; - std::string stderr_from_child_; }; void std::__libcpp_assertion_handler(char const* file, int line, char const* /*expression*/, char const* message) { assert(!GlobalMatcher().empty()); if (GlobalMatcher().Matches(file, line, message)) { - std::exit(DeathTest::RK_MatchFound); + longjmp(GetJmpBuf(), DeathTest::RK_MatchFound); } - std::exit(DeathTest::RK_MatchFailure); + longjmp(GetJmpBuf(), DeathTest::RK_MatchFailure); } template <class Func> @@ -249,22 +170,11 @@ DeathTest::ResultKind RK = DT.Run(func); auto OnFailure = [&](const char* msg) { std::fprintf(stderr, "EXPECT_DEATH( %s ) failed! (%s)\n\n", stmt, msg); - if (RK != DeathTest::RK_Unknown) { - std::fprintf(stderr, "child exit code: %d\n", DT.getChildExitCode()); - } - if (!DT.getChildStdErr().empty()) { - std::fprintf(stderr, "---------- standard err ----------\n%s\n", DT.getChildStdErr().c_str()); - } - if (!DT.getChildStdOut().empty()) { - std::fprintf(stderr, "---------- standard out ----------\n%s\n", DT.getChildStdOut().c_str()); - } return false; }; switch (RK) { case DeathTest::RK_MatchFound: return true; - case DeathTest::RK_SetupFailure: - return OnFailure("child failed to setup test environment"); case DeathTest::RK_Unknown: return OnFailure("reason unknown"); case DeathTest::RK_DidNotDie:
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits