|
@@ -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
|