//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef TEST_SUPPORT_CHECK_ASSERTION_H #define TEST_SUPPORT_CHECK_ASSERTION_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test_macros.h" #include "test_allocator.h" #if TEST_STD_VER < 11 # error "C++11 or greater is required to use this header" #endif // When printing the assertion message to `stderr`, delimit it with a marker to make it easier to match the message // later. static constexpr const char* Marker = "###"; // (success, error-message-if-failed) using MatchResult = std::pair; using Matcher = std::function; MatchResult MatchAssertionMessage(const std::string& text, std::string_view expected_message) { // Extract information from the error message. This has to stay synchronized with how we format assertions in the // library. std::regex assertion_format(".*###\\n(.*):(\\d+): assertion (.*) failed: (.*)\\n###"); std::smatch match_result; bool has_match = std::regex_match(text, match_result, assertion_format); assert(has_match); assert(match_result.size() == 5); const std::string& file = match_result[1]; int line = std::stoi(match_result[2]); // Omitting `expression` in `match_result[3]` const std::string& assertion_message = match_result[4]; bool result = assertion_message == expected_message; if (!result) { std::stringstream matching_error; matching_error // << "Expected message: '" << expected_message.data() << "'\n" // << "Actual message: '" << assertion_message.c_str() << "'\n" // << "Source location: " << file << ":" << std::to_string(line) << "\n"; return MatchResult(/*success=*/false, matching_error.str()); } return MatchResult(/*success=*/true, /*maybe_error=*/""); } Matcher MakeAssertionMessageMatcher(std::string_view assertion_message) { return [=](const std::string& text) { // return MatchAssertionMessage(text, assertion_message); }; } Matcher MakeAnyMatcher() { return [](const std::string&) { // return MatchResult(/*success=*/true, /*maybe_error=*/""); }; } enum class DeathCause { // Valid causes VerboseAbort = 1, StdAbort, StdTerminate, Trap, // Invalid causes DidNotDie, SetupFailure, Unknown }; bool IsValidCause(DeathCause cause) { switch (cause) { case DeathCause::VerboseAbort: case DeathCause::StdAbort: case DeathCause::StdTerminate: case DeathCause::Trap: return true; default: return false; } } std::string ToString(DeathCause cause) { switch (cause) { case DeathCause::VerboseAbort: return "verbose abort"; case DeathCause::StdAbort: return "`std::abort`"; case DeathCause::StdTerminate: return "`std::terminate`"; case DeathCause::Trap: return "trap"; case DeathCause::DidNotDie: return ""; case DeathCause::SetupFailure: return ""; case DeathCause::Unknown: return ""; } assert(false && "Unreachable"); } template std::string ToString(std::array const& causes) { std::stringstream ss; ss << "{"; for (std::size_t i = 0; i != N; ++i) { ss << ToString(causes[i]); if (i + 1 != N) ss << ", "; } ss << "}"; return ss.str(); } TEST_NORETURN void StopChildProcess(DeathCause cause) { std::exit(static_cast(cause)); } DeathCause ConvertToDeathCause(int val) { if (val < static_cast(DeathCause::VerboseAbort) || val > static_cast(DeathCause::Unknown)) { return DeathCause::Unknown; } return static_cast(val); } enum class Outcome { Success, UnexpectedCause, UnexpectedErrorMessage, InvalidCause, }; std::string ToString(Outcome outcome) { switch (outcome) { case Outcome::Success: return "success"; case Outcome::UnexpectedCause: return "unexpected death cause"; case Outcome::UnexpectedErrorMessage: return "unexpected error message"; case Outcome::InvalidCause: return "invalid death cause"; } assert(false && "Unreachable"); } class DeathTestResult { public: DeathTestResult() = default; DeathTestResult(Outcome set_outcome, DeathCause set_cause, const std::string& set_failure_description = "") : outcome_(set_outcome), cause_(set_cause), failure_description_(set_failure_description) {} bool success() const { return outcome() == Outcome::Success; } Outcome outcome() const { return outcome_; } DeathCause cause() const { return cause_; } const std::string& failure_description() const { return failure_description_; } private: Outcome outcome_ = Outcome::Success; DeathCause cause_ = DeathCause::Unknown; std::string failure_description_; }; class DeathTest { public: DeathTest() = default; DeathTest(DeathTest const&) = delete; DeathTest& operator=(DeathTest const&) = delete; template DeathTestResult Run(const std::array& expected_causes, Func&& func, const Matcher& matcher) { std::signal(SIGABRT, [](int) { StopChildProcess(DeathCause::StdAbort); }); std::set_terminate([] { StopChildProcess(DeathCause::StdTerminate); }); DeathCause cause = Run(func); if (!IsValidCause(cause)) { return DeathTestResult(Outcome::InvalidCause, cause, ToString(cause)); } if (std::find(expected_causes.begin(), expected_causes.end(), cause) == expected_causes.end()) { std::stringstream failure_description; failure_description // << "Child died, but with a different death cause\n" // << "Expected cause(s): " << ToString(expected_causes) << "\n" // << "Actual cause: " << ToString(cause) << "\n"; return DeathTestResult(Outcome::UnexpectedCause, cause, failure_description.str()); } MatchResult match_result = matcher(GetChildStdErr()); if (!match_result.first) { auto failure_description = std::string("Child died, but with a different error message\n") + match_result.second; return DeathTestResult(Outcome::UnexpectedErrorMessage, cause, failure_description); } return DeathTestResult(Outcome::Success, cause); } void PrintFailureDetails(std::string_view failure_description, std::string_view stmt, DeathCause cause) const { std::fprintf( stderr, "Failure: EXPECT_DEATH( %s ) failed!\n(reason: %s)\n\n", stmt.data(), failure_description.data()); if (cause != DeathCause::Unknown) { std::fprintf(stderr, "child exit code: %d\n", GetChildExitCode()); } std::fprintf(stderr, "---------- standard err ----------\n%s", GetChildStdErr().c_str()); std::fprintf(stderr, "\n----------------------------------\n"); std::fprintf(stderr, "---------- standard out ----------\n%s", GetChildStdOut().c_str()); std::fprintf(stderr, "\n----------------------------------\n"); }; private: int GetChildExitCode() const { return exit_code_; } std::string const& GetChildStdOut() const { return stdout_from_child_; } std::string const& GetChildStdErr() const { return stderr_from_child_; } template DeathCause 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(f)); assert(false && "unreachable"); } return RunForParent(); } template 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) StopChildProcess(DeathCause::SetupFailure); }; DupFD(GetStdOutWriteFD(), STDOUT_FILENO); DupFD(GetStdErrWriteFD(), STDERR_FILENO); f(); StopChildProcess(DeathCause::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()); } DeathCause 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); return ConvertToDeathCause(exit_code_); } if (WIFSIGNALED(status_value)) { exit_code_ = WTERMSIG(status_value); // `__builtin_trap` generqtes `SIGILL` on x86 and `SIGTRAP` on ARM. if (exit_code_ == SIGILL || exit_code_ == SIGTRAP) { return DeathCause::Trap; } } return DeathCause::Unknown; } 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]; } 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_; }; #ifdef _LIBCPP_VERSION void std::__libcpp_verbose_abort(char const* format, ...) { va_list args; va_start(args, format); std::fprintf(stderr, "%s\n", Marker); std::vfprintf(stderr, format, args); std::fprintf(stderr, "%s", Marker); va_end(args); StopChildProcess(DeathCause::VerboseAbort); } #endif // _LIBCPP_VERSION template bool ExpectDeath( const std::array& expected_causes, const char* stmt, Func&& func, const Matcher& matcher) { for (auto cause : expected_causes) assert(IsValidCause(cause)); DeathTest test_case; DeathTestResult test_result = test_case.Run(expected_causes, func, matcher); if (!test_result.success()) { test_case.PrintFailureDetails(test_result.failure_description(), stmt, test_result.cause()); } return test_result.success(); } template bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func, const Matcher& matcher) { return ExpectDeath(std::array{expected_cause}, stmt, func, matcher); } template bool ExpectDeath(const std::array& expected_causes, const char* stmt, Func&& func) { return ExpectDeath(expected_causes, stmt, func, MakeAnyMatcher()); } template bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func) { return ExpectDeath(std::array{expected_cause}, stmt, func, MakeAnyMatcher()); } // clang-format off /// Assert that the specified expression aborts with the expected cause and, optionally, error message. #define EXPECT_ANY_DEATH(...) \ assert(( ExpectDeath(std::array{DeathCause::VerboseAbort, DeathCause::StdAbort, DeathCause::StdTerminate, DeathCause::Trap}, #__VA_ARGS__, [&]() { __VA_ARGS__; } ) )) #define EXPECT_DEATH(...) \ assert(( ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; } ) )) #define EXPECT_DEATH_MATCHES(matcher, ...) \ assert(( ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; }, matcher) )) #define EXPECT_STD_ABORT(...) \ assert( ExpectDeath(DeathCause::StdAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; }) ) #define EXPECT_STD_TERMINATE(...) \ assert( ExpectDeath(DeathCause::StdTerminate, #__VA_ARGS__, __VA_ARGS__) ) #if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG #define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \ assert(( ExpectDeath(DeathCause::VerboseAbort, #expr, [&]() { (void)(expr); }, MakeAssertionMessageMatcher(message)) )) #else #define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \ assert(( ExpectDeath(DeathCause::Trap, #expr, [&]() { (void)(expr); }) )) #endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG // clang-format on #endif // TEST_SUPPORT_CHECK_ASSERTION_H