diff options
Diffstat (limited to 'libcxx/test/support/debug_mode_helper.h')
| -rw-r--r-- | libcxx/test/support/debug_mode_helper.h | 531 |
1 files changed, 224 insertions, 307 deletions
diff --git a/libcxx/test/support/debug_mode_helper.h b/libcxx/test/support/debug_mode_helper.h index 611b4dbb4e3..6a662d17e75 100644 --- a/libcxx/test/support/debug_mode_helper.h +++ b/libcxx/test/support/debug_mode_helper.h @@ -12,9 +12,6 @@ #ifndef _LIBCPP_DEBUG #error _LIBCPP_DEBUG must be defined before including this header #endif -#ifndef _LIBCPP_DEBUG_USE_EXCEPTIONS -#error _LIBCPP_DEBUG_USE_EXCEPTIONS must be defined before including this header -#endif #include <ciso646> #ifndef _LIBCPP_VERSION @@ -26,360 +23,280 @@ #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" -// These test make use of 'if constexpr'. -#if TEST_STD_VER <= 14 -#error This header may only be used in C++17 and greater -#endif -#ifdef TEST_HAS_NO_EXCEPTIONS -#error These tests require exceptions -#endif - -#ifndef __cpp_if_constexpr -#error These tests require if constexpr +#if TEST_STD_VER < 11 +# error "C++11 or greater is required to use this header" #endif -/// Assert that the specified expression throws a libc++ debug exception. -#define CHECK_DEBUG_THROWS(...) assert((CheckDebugThrows( [&]() { __VA_ARGS__; } ))) +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), file(any_file), line(any_line) { } + constexpr DebugInfoMatcher(const char* msg, const char* file = any_file, int line = any_line) + : is_empty(false), msg(msg), file(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; + } -template <class Func> -inline bool CheckDebugThrows(Func&& func) { - try { - func(); - } catch (std::__libcpp_debug_exception const&) { - return true; + 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(); } - return false; -} - -namespace IteratorDebugChecks { - -enum ContainerType { - CT_None, - CT_String, - CT_Vector, - CT_VectorBool, - CT_List, - CT_Deque, - CT_ForwardList, - CT_Map, - CT_Set, - CT_MultiMap, - CT_MultiSet, - CT_UnorderedMap, - CT_UnorderedSet, - CT_UnorderedMultiMap, - CT_UnorderedMultiSet -}; - -constexpr bool isSequential(ContainerType CT) { - return CT_Vector >= CT && CT_ForwardList <= CT; -} - -constexpr bool isAssociative(ContainerType CT) { - return CT_Map >= CT && CT_MultiSet <= CT; -} - -constexpr bool isUnordered(ContainerType CT) { - return CT_UnorderedMap >= CT && CT_UnorderedMultiSet <= CT; -} - -constexpr bool isSet(ContainerType CT) { - return CT == CT_Set - || CT == CT_MultiSet - || CT == CT_UnorderedSet - || CT == CT_UnorderedMultiSet; -} - -constexpr bool isMap(ContainerType CT) { - return CT == CT_Map - || CT == CT_MultiMap - || CT == CT_UnorderedMap - || CT == CT_UnorderedMultiMap; -} - -constexpr bool isMulti(ContainerType CT) { - return CT == CT_MultiMap - || CT == CT_MultiSet - || CT == CT_UnorderedMultiMap - || CT == CT_UnorderedMultiSet; -} - -template <class Container, class ValueType = typename Container::value_type> -struct ContainerDebugHelper { - static_assert(std::is_constructible<ValueType, int>::value, - "must be constructible from int"); - static ValueType makeValueType(int val = 0, int = 0) { - return ValueType(val); + bool empty() const { return is_empty; } +private: + bool CheckLineMatches(int got_line) const { + if (line == any_line) + return true; + return got_line == line; } -}; -template <class Container> -struct ContainerDebugHelper<Container, char> { - static char makeValueType(int = 0, int = 0) { - return 'A'; + 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; } -}; -template <class Container, class Key, class Value> -struct ContainerDebugHelper<Container, std::pair<const Key, Value> > { - using ValueType = std::pair<const Key, Value>; - static_assert(std::is_constructible<Key, int>::value, - "must be constructible from int"); - static_assert(std::is_constructible<Value, int>::value, - "must be constructible from int"); - - static ValueType makeValueType(int key = 0, int val = 0) { - return ValueType(key, val); + 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; }; -template <class Container, ContainerType CT, - class Helper = ContainerDebugHelper<Container> > -struct BasicContainerChecks { - using value_type = typename Container::value_type; - using iterator = typename Container::iterator; - using const_iterator = typename Container::const_iterator; - using allocator_type = typename Container::allocator_type; - using traits = std::iterator_traits<iterator>; - using category = typename traits::iterator_category; - - static_assert(std::is_same<test_allocator<value_type>, allocator_type>::value, - "the container must use a test allocator"); - - static constexpr bool IsBiDir = - std::is_convertible<category, std::bidirectional_iterator_tag>::value; - -public: - static void run() { - run_iterator_tests(); - run_container_tests(); - run_allocator_aware_tests(); - } +static constexpr DebugInfoMatcher AnyMatcher(DebugInfoMatcher::any_msg); - static void run_iterator_tests() { - try { - TestNullIterators<iterator>(); - TestNullIterators<const_iterator>(); - if constexpr (IsBiDir) { DecrementBegin(); } - IncrementEnd(); - DerefEndIterator(); - } catch (...) { - assert(false && "uncaught debug exception"); - } - } +inline DebugInfoMatcher& GlobalMatcher() { + static DebugInfoMatcher GMatch; + return GMatch; +} - static void run_container_tests() { - try { - CopyInvalidatesIterators(); - MoveInvalidatesIterators(); - if constexpr (CT != CT_ForwardList) { - EraseIter(); - EraseIterIter(); - } - } catch (...) { - assert(false && "uncaught debug exception"); +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 void run_allocator_aware_tests() { - try { - SwapNonEqualAllocators(); - if constexpr (CT != CT_ForwardList ) { - // FIXME: This should work for both forward_list and string - SwapInvalidatesIterators(); - } - } catch (...) { - assert(false && "uncaught debug exception"); - } + static bool IsValidResultKind(int val) { + return val >= RK_DidNotDie && val <= RK_Unknown; } - static Container makeContainer(int size, allocator_type A = allocator_type()) { - Container C(A); - if constexpr (CT == CT_ForwardList) { - for (int i = 0; i < size; ++i) - C.insert_after(C.before_begin(), Helper::makeValueType(i)); - } else { - for (int i = 0; i < size; ++i) - C.insert(C.end(), Helper::makeValueType(i)); - assert(C.size() == static_cast<std::size_t>(size)); + TEST_NORETURN static void DeathTestDebugHandler(std::__libcpp_debug_info const& info) { + assert(!GlobalMatcher().empty()); + if (GlobalMatcher().Matches(info)) { + std::exit(RK_MatchFound); } - return C; + std::exit(RK_MatchFailure); } - static value_type makeValueType(int value) { - return Helper::makeValueType(value); - } -private: - // Iterator tests - template <class Iter> - static void TestNullIterators() { - CHECKPOINT("testing null iterator"); - Iter it; - CHECK_DEBUG_THROWS( ++it ); - CHECK_DEBUG_THROWS( it++ ); - CHECK_DEBUG_THROWS( *it ); - if constexpr (CT != CT_VectorBool) { - CHECK_DEBUG_THROWS( it.operator->() ); - } - if constexpr (IsBiDir) { - CHECK_DEBUG_THROWS( --it ); - CHECK_DEBUG_THROWS( it-- ); + 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(); } - static void DecrementBegin() { - CHECKPOINT("testing decrement on begin"); - Container C = makeContainer(1); - iterator i = C.end(); - const_iterator ci = C.cend(); - --i; - --ci; - assert(i == C.begin()); - CHECK_DEBUG_THROWS( --i ); - CHECK_DEBUG_THROWS( i-- ); - CHECK_DEBUG_THROWS( --ci ); - CHECK_DEBUG_THROWS( ci-- ); + 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 void IncrementEnd() { - CHECKPOINT("testing increment on end"); - Container C = makeContainer(1); - iterator i = C.begin(); - const_iterator ci = C.begin(); - ++i; - ++ci; - assert(i == C.end()); - CHECK_DEBUG_THROWS( ++i ); - CHECK_DEBUG_THROWS( i++ ); - CHECK_DEBUG_THROWS( ++ci ); - CHECK_DEBUG_THROWS( ci++ ); + 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; } - static void DerefEndIterator() { - CHECKPOINT("testing deref end iterator"); - Container C = makeContainer(1); - iterator i = C.begin(); - const_iterator ci = C.cbegin(); - (void)*i; (void)*ci; - if constexpr (CT != CT_VectorBool) { - i.operator->(); - ci.operator->(); - } - ++i; ++ci; - assert(i == C.end()); - CHECK_DEBUG_THROWS( *i ); - CHECK_DEBUG_THROWS( *ci ); - if constexpr (CT != CT_VectorBool) { - CHECK_DEBUG_THROWS( i.operator->() ); - CHECK_DEBUG_THROWS( ci.operator->() ); - } + 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()); } - // Container tests - static void CopyInvalidatesIterators() { - CHECKPOINT("copy invalidates iterators"); - Container C1 = makeContainer(3); - iterator i = C1.begin(); - Container C2 = C1; - if constexpr (CT == CT_ForwardList) { - iterator i_next = i; - ++i_next; - (void)*i_next; - CHECK_DEBUG_THROWS( C2.erase_after(i) ); - C1.erase_after(i); - CHECK_DEBUG_THROWS( *i_next ); - } else { - CHECK_DEBUG_THROWS( C2.erase(i) ); - (void)*i; - C1.erase(i); - CHECK_DEBUG_THROWS( *i ); - } - } + 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"); - static void MoveInvalidatesIterators() { - CHECKPOINT("copy move invalidates iterators"); - Container C1 = makeContainer(3); - iterator i = C1.begin(); - Container C2 = std::move(C1); - (void) *i; - if constexpr (CT == CT_ForwardList) { - CHECK_DEBUG_THROWS( C1.erase_after(i) ); - C2.erase_after(i); - } else { - CHECK_DEBUG_THROWS( C1.erase(i) ); - C2.erase(i); - CHECK_DEBUG_THROWS(*i); + 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; } - static void EraseIter() { - CHECKPOINT("testing erase invalidation"); - Container C1 = makeContainer(2); - iterator it1 = C1.begin(); - iterator it1_next = it1; - ++it1_next; - Container C2 = C1; - CHECK_DEBUG_THROWS( C2.erase(it1) ); // wrong container - CHECK_DEBUG_THROWS( C2.erase(C2.end()) ); // erase with end - C1.erase(it1_next); - CHECK_DEBUG_THROWS( C1.erase(it1_next) ); // invalidated iterator - C1.erase(it1); - CHECK_DEBUG_THROWS( C1.erase(it1) ); // invalidated iterator - } + DeathTest(DeathTest const&) = delete; + DeathTest& operator=(DeathTest const&) = delete; - static void EraseIterIter() { - CHECKPOINT("testing erase iter iter invalidation"); - Container C1 = makeContainer(2); - iterator it1 = C1.begin(); - iterator it1_next = it1; - ++it1_next; - Container C2 = C1; - iterator it2 = C2.begin(); - iterator it2_next = it2; - ++it2_next; - CHECK_DEBUG_THROWS( C2.erase(it1, it1_next) ); // begin from wrong container - CHECK_DEBUG_THROWS( C2.erase(it1, it2_next) ); // end from wrong container - CHECK_DEBUG_THROWS( C2.erase(it2, it1_next) ); // both from wrong container - C2.erase(it2, it2_next); + int GetStdOutReadFD() const { + return stdout_pipe_fd_[0]; } - // Allocator aware tests - static void SwapInvalidatesIterators() { - CHECKPOINT("testing swap invalidates iterators"); - Container C1 = makeContainer(3); - Container C2 = makeContainer(3); - iterator it1 = C1.begin(); - iterator it2 = C2.begin(); - swap(C1, C2); - CHECK_DEBUG_THROWS( C1.erase(it1) ); - if (CT == CT_String) { - CHECK_DEBUG_THROWS(C1.erase(it2)); - } else - C1.erase(it2); - //C2.erase(it1); - CHECK_DEBUG_THROWS( C1.erase(it1) ); + int GetStdOutWriteFD() const { + return stdout_pipe_fd_[1]; } - static void SwapNonEqualAllocators() { - CHECKPOINT("testing swap with non-equal allocators"); - Container C1 = makeContainer(3, allocator_type(1)); - Container C2 = makeContainer(1, allocator_type(2)); - Container C3 = makeContainer(2, allocator_type(2)); - swap(C2, C3); - CHECK_DEBUG_THROWS( swap(C1, C2) ); + int GetStdErrReadFD() const { + return stderr_pipe_fd_[0]; } + int GetStdErrWriteFD() const { + return stderr_pipe_fd_[1]; + } private: - BasicContainerChecks() = delete; + 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_; }; -} // namespace IteratorDebugChecks +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 |

