//===----------------------------------------------------------------------===// // // 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 #ifndef _LIBCPP_VERSION #error "This header may only be used for libc++ tests" #endif #include <__debug> #include #include #include #include #include #include #include #include #include #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 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(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 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(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 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 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