| //===----------------------------------------------------------------------===// |
| // |
| // 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_DEBUG_MODE_HELPER_H |
| #define TEST_SUPPORT_DEBUG_MODE_HELPER_H |
| |
| #ifndef _LIBCPP_DEBUG |
| #error _LIBCPP_DEBUG must be defined before including this header |
| #endif |
| |
| #include <ciso646> |
| #ifndef _LIBCPP_VERSION |
| #error "This header may only be used for libc++ tests" |
| #endif |
| |
| #include <__debug> |
| #include <utility> |
| #include <cstddef> |
| #include <cstdlib> |
| #include <cassert> |
| #include <string_view> |
| #include <sstream> |
| #include <iostream> |
| |
| #include <unistd.h> |
| #include <sys/wait.h> |
| #include "test_macros.h" |
| #include "assert_checkpoint.h" |
| #include "test_allocator.h" |
| |
| #if TEST_STD_VER < 11 |
| # error "C++11 or greater is required to use this header" |
| #endif |
| |
| struct DebugInfoMatcher { |
| static const int any_line = -1; |
| static constexpr const char* any_file = "*"; |
| static constexpr const char* any_msg = "*"; |
| |
| constexpr DebugInfoMatcher() : is_empty(true), msg(any_msg, __builtin_strlen(any_msg)), file(any_file, __builtin_strlen(any_file)), line(any_line) { } |
| constexpr DebugInfoMatcher(const char* msg, const char* file = any_file, int line = any_line) |
| : is_empty(false), msg(msg, __builtin_strlen(msg)), file(file, __builtin_strlen(file)), line(line) {} |
| |
| bool Matches(std::__libcpp_debug_info const& got) const { |
| assert(!empty() && "empty matcher"); |
| |
| if (CheckLineMatches(got.__line_) && CheckFileMatches(got.__file_) && |
| CheckMessageMatches(got.__msg_)) |
| return true; |
| // Write to stdout because that's the file descriptor captured by the parent |
| // process. |
| std::cout << "Failed to match debug info!\n" |
| << ToString() << "\n" |
| << "VS\n" |
| << got.what() << "\n"; |
| return false; |
| } |
| |
| std::string ToString() const { |
| std::stringstream ss; |
| ss << "msg = \"" << msg << "\"\n" |
| << "line = " << (line == any_line ? "'*'" : std::to_string(line)) << "\n" |
| << "file = " << (file == any_file ? "'*'" : any_file) << ""; |
| return ss.str(); |
| } |
| |
| bool empty() const { return is_empty; } |
| private: |
| bool CheckLineMatches(int got_line) const { |
| if (line == any_line) |
| return true; |
| return got_line == line; |
| } |
| |
| bool CheckFileMatches(std::string_view got_file) const { |
| assert(!empty() && "empty matcher"); |
| if (file == any_file) |
| return true; |
| std::size_t found_at = got_file.find(file); |
| if (found_at == std::string_view::npos) |
| return false; |
| // require the match start at the beginning of the file or immediately after |
| // a directory separator. |
| if (found_at != 0) { |
| char last_char = got_file[found_at - 1]; |
| if (last_char != '/' && last_char != '\\') |
| return false; |
| } |
| // require the match goes until the end of the string. |
| return got_file.substr(found_at) == file; |
| } |
| |
| bool CheckMessageMatches(std::string_view got_msg) const { |
| assert(!empty() && "empty matcher"); |
| if (msg == any_msg) |
| return true; |
| std::size_t found_at = got_msg.find(msg); |
| if (found_at == std::string_view::npos) |
| return false; |
| // Allow any match |
| return true; |
| } |
| private: |
| bool is_empty; |
| std::string_view msg; |
| std::string_view file; |
| int line; |
| }; |
| |
| static constexpr DebugInfoMatcher AnyMatcher(DebugInfoMatcher::any_msg); |
| |
| inline DebugInfoMatcher& GlobalMatcher() { |
| static DebugInfoMatcher GMatch; |
| return GMatch; |
| } |
| |
| struct DeathTest { |
| enum ResultKind { |
| RK_DidNotDie, RK_MatchFound, RK_MatchFailure, RK_SetupFailure, RK_Unknown |
| }; |
| |
| static const char* ResultKindToString(ResultKind RK) { |
| #define CASE(K) case K: return #K |
| switch (RK) { |
| CASE(RK_MatchFailure); |
| CASE(RK_DidNotDie); |
| CASE(RK_SetupFailure); |
| CASE(RK_MatchFound); |
| CASE(RK_Unknown); |
| } |
| return "not a result kind"; |
| } |
| |
| static bool IsValidResultKind(int val) { |
| return val >= RK_DidNotDie && val <= RK_Unknown; |
| } |
| |
| TEST_NORETURN static void DeathTestDebugHandler(std::__libcpp_debug_info const& info) { |
| assert(!GlobalMatcher().empty()); |
| if (GlobalMatcher().Matches(info)) { |
| std::exit(RK_MatchFound); |
| } |
| std::exit(RK_MatchFailure); |
| } |
| |
| |
| DeathTest(DebugInfoMatcher const& Matcher) : matcher_(Matcher) {} |
| |
| 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_; |
| std::__libcpp_set_debug_function(&DeathTestDebugHandler); |
| 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_); |
| } |
| 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: |
| DebugInfoMatcher 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_; |
| }; |
| |
| template <class Func> |
| inline bool ExpectDeath(const char* stmt, Func&& func, DebugInfoMatcher Matcher) { |
| DeathTest DT(Matcher); |
| DeathTest::ResultKind RK = DT.Run(func); |
| auto OnFailure = [&](const char* msg) { |
| std::cerr << "EXPECT_DEATH( " << stmt << " ) failed! (" << msg << ")\n\n"; |
| if (RK != DeathTest::RK_Unknown) { |
| std::cerr << "child exit code: " << DT.getChildExitCode() << "\n"; |
| } |
| if (!DT.getChildStdErr().empty()) { |
| std::cerr << "---------- standard err ----------\n"; |
| std::cerr << DT.getChildStdErr() << "\n"; |
| } |
| if (!DT.getChildStdOut().empty()) { |
| std::cerr << "---------- standard out ----------\n"; |
| std::cerr << DT.getChildStdOut() << "\n"; |
| } |
| 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: |
| return OnFailure("child did not die"); |
| case DeathTest::RK_MatchFailure: |
| return OnFailure("matcher failed"); |
| } |
| } |
| |
| template <class Func> |
| inline bool ExpectDeath(const char* stmt, Func&& func) { |
| return ExpectDeath(stmt, func, AnyMatcher); |
| } |
| |
| /// Assert that the specified expression throws a libc++ debug exception. |
| #define EXPECT_DEATH(...) assert((ExpectDeath(#__VA_ARGS__, [&]() { __VA_ARGS__; } ))) |
| |
| #define EXPECT_DEATH_MATCHES(Matcher, ...) assert((ExpectDeath(#__VA_ARGS__, [&]() { __VA_ARGS__; }, Matcher))) |
| |
| #endif // TEST_SUPPORT_DEBUG_MODE_HELPER_H |