summaryrefslogtreecommitdiffstats
path: root/libcxx/test/support/debug_mode_helper.h
diff options
context:
space:
mode:
Diffstat (limited to 'libcxx/test/support/debug_mode_helper.h')
-rw-r--r--libcxx/test/support/debug_mode_helper.h531
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
OpenPOWER on IntegriCloud