From a76e7f26cf4d3546cd9eb8852720583315585701 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sun, 13 Oct 2019 13:01:57 +0700 Subject: [PATCH] - expectThrows - expectFailsAssertion - fix asserts --- src/cpp-utils/CMakeLists.txt | 2 +- src/cpp-utils/assert/AssertFailed.h | 2 +- src/cpp-utils/assert/assert.cpp | 2 + src/cpp-utils/assert/assert.h | 42 ++++++++++++++++--- src/cpp-utils/testutils/ExpectThrows.h | 31 ++++++++++++++ test/cpp-utils/assert/assert_debug_test.cpp | 12 +++++- test/cpp-utils/assert/assert_release_test.cpp | 2 +- 7 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 src/cpp-utils/testutils/ExpectThrows.h diff --git a/src/cpp-utils/CMakeLists.txt b/src/cpp-utils/CMakeLists.txt index f66f99f8..a0acfd82 100644 --- a/src/cpp-utils/CMakeLists.txt +++ b/src/cpp-utils/CMakeLists.txt @@ -43,7 +43,7 @@ set(SOURCES data/DataFixture.cpp data/DataUtils.cpp data/Data.cpp - assert/assert.h + assert/assert.cpp assert/backtrace_nonwindows.cpp assert/backtrace_windows.cpp assert/AssertFailed.cpp diff --git a/src/cpp-utils/assert/AssertFailed.h b/src/cpp-utils/assert/AssertFailed.h index db1c5ae3..79b355ad 100644 --- a/src/cpp-utils/assert/AssertFailed.h +++ b/src/cpp-utils/assert/AssertFailed.h @@ -10,7 +10,7 @@ namespace cpputils { class AssertFailed final: public std::exception { public: - AssertFailed(std::string message) : _message(std::move(message)) { } + explicit AssertFailed(std::string message) : _message(std::move(message)) { } const char *what() const throw() override { return _message.c_str(); diff --git a/src/cpp-utils/assert/assert.cpp b/src/cpp-utils/assert/assert.cpp index e6adb8fd..4cf86d69 100644 --- a/src/cpp-utils/assert/assert.cpp +++ b/src/cpp-utils/assert/assert.cpp @@ -1 +1,3 @@ #include "assert.h" + +thread_local int cpputils::_assert::DisableAbortOnFailedAssertionRAII::num_instances_ = 0; diff --git a/src/cpp-utils/assert/assert.h b/src/cpp-utils/assert/assert.h index 68222e6b..b36fb7f2 100644 --- a/src/cpp-utils/assert/assert.h +++ b/src/cpp-utils/assert/assert.h @@ -10,36 +10,66 @@ #include "AssertFailed.h" #include +#include #include "backtrace.h" #include "../logging/logging.h" namespace cpputils { namespace _assert { + struct DisableAbortOnFailedAssertionRAII final { + explicit DisableAbortOnFailedAssertionRAII() + : thread_id_(std::this_thread::get_id()) { + ++num_instances_; + } + + ~DisableAbortOnFailedAssertionRAII() { + if (thread_id_ != std::this_thread::get_id()) { + using namespace logging; + LOG(ERR, "DisableAbortOnFailedAssertionRAII instance must be destructed in the same thread that created it"); + } + --num_instances_; + } + + static int num_instances() { + return num_instances_; + } + + private: + static thread_local int num_instances_; // initialized to zero in assert.cpp + + std::thread::id thread_id_; + }; + inline std::string format(const char *expr, const std::string &message, const char *file, int line) { std::string result = std::string()+"Assertion ["+expr+"] failed in "+file+":"+std::to_string(line)+": "+message+"\n\n" + backtrace(); return result; } inline void assert_fail_release [[noreturn]] (const char *expr, const std::string &message, const char *file, int line) { - auto msg = format(expr, message, file, line); using namespace logging; + auto msg = format(expr, message, file, line); LOG(ERR, msg); throw AssertFailed(msg); } inline void assert_fail_debug [[noreturn]] (const char *expr, const std::string &message, const char *file, int line) { using namespace logging; - LOG(ERR, format(expr, message, file, line)); - abort(); + auto msg = format(expr, message, file, line); + LOG(ERR, msg); + if (DisableAbortOnFailedAssertionRAII::num_instances() > 0) { + throw AssertFailed(msg); + } else { + abort(); + } } } } #ifdef NDEBUG -//TODO Check whether disabling assertions in prod affects speed. -# define ASSERT(expr, msg) (void)((expr) || (cpputils::_assert::assert_fail_release(#expr, msg, __FILE__, __LINE__),0)) + //TODO Check whether disabling assertions in prod affects speed. + #define ASSERT(expr, msg) (void)((expr) || (cpputils::_assert::assert_fail_release(#expr, msg, __FILE__, __LINE__),0)) #else -# define ASSERT(expr, msg) (void)((expr) || (cpputils::_assert::assert_fail_debug(#expr, msg, __FILE__, __LINE__),0)) + #define ASSERT(expr, msg) (void)((expr) || (cpputils::_assert::assert_fail_debug(#expr, msg, __FILE__, __LINE__),0)) #endif #endif diff --git a/src/cpp-utils/testutils/ExpectThrows.h b/src/cpp-utils/testutils/ExpectThrows.h new file mode 100644 index 00000000..b569aff6 --- /dev/null +++ b/src/cpp-utils/testutils/ExpectThrows.h @@ -0,0 +1,31 @@ +#pragma once + +#ifndef MESSMER_CPPUTILS_EXPECTTHROWS_H +#define MESSMER_CPPUTILS_EXPECTTHROWS_H + +#include +#include + +namespace cpputils { + +template +inline void expectThrows(Functor&& functor, const char* expectMessageContains) { + try { + std::forward(functor)(); + } catch (const Exception& e) { + EXPECT_THAT(e.what(), testing::HasSubstr(expectMessageContains)); + return; + } + ADD_FAILURE() << "Expected to throw exception containing \"" + << expectMessageContains << "\" but didn't throw"; +} + +template +inline void expectFailsAssertion(Functor&& functor, const char* expectMessageContains) { + cpputils::_assert::DisableAbortOnFailedAssertionRAII _disableAbortOnFailedAssertionRAII; + expectThrows(std::forward(functor), expectMessageContains); +} + +} + +#endif diff --git a/test/cpp-utils/assert/assert_debug_test.cpp b/test/cpp-utils/assert/assert_debug_test.cpp index 8a214992..379ee97b 100644 --- a/test/cpp-utils/assert/assert_debug_test.cpp +++ b/test/cpp-utils/assert/assert_debug_test.cpp @@ -21,11 +21,19 @@ TEST(AssertTest_DebugBuild, DiesIfFalse) { ); } +TEST(AssertTest_DebugBuild, whenDisablingAbort_thenThrowsIfFalse) { + cpputils::_assert::DisableAbortOnFailedAssertionRAII _disableAbort; + EXPECT_THROW( + ASSERT(false, "bla"), + cpputils::AssertFailed + ); +} + TEST(AssertTest_DebugBuild, AssertMessage) { #if defined(_MSC_VER) -constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:\d+: my message)"; + constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:\d+: my message)"; #else -constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:[0-9]+: my message)"; + constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:[0-9]+: my message)"; #endif EXPECT_DEATH( ASSERT(2==5, "my message"), diff --git a/test/cpp-utils/assert/assert_release_test.cpp b/test/cpp-utils/assert/assert_release_test.cpp index 17a43fb3..2aa0b6ee 100644 --- a/test/cpp-utils/assert/assert_release_test.cpp +++ b/test/cpp-utils/assert/assert_release_test.cpp @@ -8,7 +8,7 @@ //Include the ASSERT macro for a release build #ifndef NDEBUG -#define NDEBUG +#define NDEBUG 1 #endif #include "cpp-utils/assert/assert.h"