| //===----------------------------------------------------------------------===// |
| // |
| // 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 <array> |
| #include <cassert> |
| #include <csignal> |
| #include <cstdarg> |
| #include <cstddef> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <exception> |
| #include <functional> |
| #include <regex> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include <unistd.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <sys/wait.h> |
| |
| #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<bool, std::string>; |
| using Matcher = std::function<MatchResult(const std::string& /*text*/)>; |
| |
| 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 "<invalid cause (child did not die)>"; |
| case DeathCause::SetupFailure: |
| return "<invalid cause (child failed to set up test environment)>"; |
| case DeathCause::Unknown: |
| return "<invalid cause (cause unknown)>"; |
| } |
| |
| assert(false && "Unreachable"); |
| } |
| |
| template <std::size_t N> |
| std::string ToString(std::array<DeathCause, N> 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<int>(cause)); } |
| |
| DeathCause ConvertToDeathCause(int val) { |
| if (val < static_cast<int>(DeathCause::VerboseAbort) || val > static_cast<int>(DeathCause::Unknown)) { |
| return DeathCause::Unknown; |
| } |
| return static_cast<DeathCause>(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 <std::size_t N, class Func> |
| DeathTestResult Run(const std::array<DeathCause, N>& 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 <class Func> |
| 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<Func>(f)); |
| assert(false && "unreachable"); |
| } |
| return RunForParent(); |
| } |
| |
| 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) |
| 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 <std::size_t N, class Func> |
| bool ExpectDeath( |
| const std::array<DeathCause, N>& 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 <class Func> |
| bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func, const Matcher& matcher) { |
| return ExpectDeath(std::array<DeathCause, 1>{expected_cause}, stmt, func, matcher); |
| } |
| |
| template <std::size_t N, class Func> |
| bool ExpectDeath(const std::array<DeathCause, N>& expected_causes, const char* stmt, Func&& func) { |
| return ExpectDeath(expected_causes, stmt, func, MakeAnyMatcher()); |
| } |
| |
| template <class Func> |
| bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func) { |
| return ExpectDeath(std::array<DeathCause, 1>{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, 4>{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 |