From f47651be69e39061dc5e0bb6d6b25c25f6a9fadd Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Thu, 22 Aug 2024 09:18:36 +0100 Subject: [PATCH] Better captures on handled exceptions --- include/snitch/snitch_capture.hpp | 13 +++- include/snitch/snitch_test_data.hpp | 9 +++ src/snitch_capture.cpp | 10 ++- src/snitch_registry.cpp | 15 ++++- tests/runtime_tests/capture.cpp | 95 +++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 6 deletions(-) diff --git a/include/snitch/snitch_capture.hpp b/include/snitch/snitch_capture.hpp index 7e024fda..9d95b674 100644 --- a/include/snitch/snitch_capture.hpp +++ b/include/snitch/snitch_capture.hpp @@ -12,7 +12,10 @@ namespace snitch::impl { struct scoped_capture { capture_state& captures; - std::size_t count = 0; +#if SNITCH_WITH_EXCEPTIONS + std::optional& held_captures; +#endif + std::size_t count = 0; SNITCH_EXPORT ~scoped_capture(); }; @@ -35,7 +38,11 @@ void add_capture(test_state& state, std::string_view& names, const T& arg) { template scoped_capture add_captures(test_state& state, std::string_view names, const Args&... args) { (add_capture(state, names, args), ...); +#if SNITCH_WITH_EXCEPTIONS + return {state.captures, state.held_captures, sizeof...(args)}; +#else return {state.captures, sizeof...(args)}; +#endif } // Requires: number of captures < max_captures. @@ -43,7 +50,11 @@ template scoped_capture add_info(test_state& state, const Args&... args) { auto& capture = add_capture(state); append_or_truncate(capture, args...); +#if SNITCH_WITH_EXCEPTIONS + return {state.captures, state.held_captures, 1}; +#else return {state.captures, 1}; +#endif } } // namespace snitch::impl diff --git a/include/snitch/snitch_test_data.hpp b/include/snitch/snitch_test_data.hpp index 920d95e7..31de353e 100644 --- a/include/snitch/snitch_test_data.hpp +++ b/include/snitch/snitch_test_data.hpp @@ -6,6 +6,7 @@ #include "snitch/snitch_vector.hpp" #include +#include #include namespace snitch { @@ -285,6 +286,10 @@ struct test_state { capture_state captures = {}; location_state locations = {}; +#if SNITCH_WITH_EXCEPTIONS + std::optional held_captures = {}; +#endif + std::size_t asserts = 0; std::size_t failures = 0; std::size_t allowed_failures = 0; @@ -292,6 +297,10 @@ struct test_state { bool should_fail = false; bool in_check = false; +#if SNITCH_WITH_EXCEPTIONS + bool unhandled_exception = false; +#endif + #if SNITCH_WITH_TIMINGS float duration = 0.0f; #endif diff --git a/src/snitch_capture.cpp b/src/snitch_capture.cpp index 2d3f1b0d..9b27185c 100644 --- a/src/snitch_capture.cpp +++ b/src/snitch_capture.cpp @@ -22,11 +22,11 @@ void trim(std::string_view& str, std::string_view patterns) noexcept { scoped_capture::~scoped_capture() { #if SNITCH_WITH_EXCEPTIONS - if (std::uncaught_exceptions() > 0) { + if (std::uncaught_exceptions() > 0 && !held_captures.has_value()) { // We are unwinding the stack because an exception has been thrown; - // avoid touching the capture state since we will want to preserve the information + // keep a copy of the full capture state since we will want to preserve the information // when reporting the exception. - return; + held_captures = captures; } #endif @@ -91,6 +91,10 @@ small_string& add_capture(test_state& state) { assertion_failed("max number of captures reached"); } +#if SNITCH_WITH_EXCEPTIONS + state.held_captures.reset(); +#endif + state.captures.grow(1); state.captures.back().clear(); return state.captures.back(); diff --git a/src/snitch_registry.cpp b/src/snitch_registry.cpp index 796b97fc..df65fa8e 100644 --- a/src/snitch_registry.cpp +++ b/src/snitch_registry.cpp @@ -413,8 +413,17 @@ void report_assertion_impl( register_assertion(success, state); - const auto captures_buffer = impl::make_capture_buffer(state.captures); - const auto& last_location = state.locations.back(); + const auto captures_buffer = +#if SNITCH_WITH_EXCEPTIONS + impl::make_capture_buffer( + state.unhandled_exception && state.held_captures.has_value() + ? state.held_captures.value() + : state.captures); +#else + impl::make_capture_buffer(state.captures); +#endif + + const auto& last_location = state.locations.back(); #if SNITCH_WITH_EXCEPTIONS const auto location = state.in_check @@ -552,8 +561,10 @@ impl::test_state registry::run(impl::test_case& test) noexcept { } catch (const impl::abort_exception&) { // Test aborted, assume its state was already set accordingly. } catch (const std::exception& e) { + state.unhandled_exception = true; report_assertion(false, "unexpected std::exception caught; message: ", e.what()); } catch (...) { + state.unhandled_exception = true; report_assertion(false, "unexpected unknown exception caught"); } #endif diff --git a/tests/runtime_tests/capture.cpp b/tests/runtime_tests/capture.cpp index c5787913..6ad3ff64 100644 --- a/tests/runtime_tests/capture.cpp +++ b/tests/runtime_tests/capture.cpp @@ -191,6 +191,101 @@ TEST_CASE("capture", "[test macros]") { REQUIRE(framework.get_num_failures() == 1u); CHECK_CAPTURES_FOR_FAILURE(0u, "i := 1"); } + + SECTION("with handled exception") { + framework.test_case.func = []() { + try { + int i = 1; + SNITCH_CAPTURE(i); + throw std::runtime_error("bad"); + } catch (...) { + } + + int j = 2; + SNITCH_CAPTURE(j); + SNITCH_CHECK(j == 1); + }; + + framework.run_test(); + CHECK_CAPTURES("j := 2"); + } + + SECTION("with handled exception no capture") { + framework.test_case.func = []() { + try { + int i = 1; + SNITCH_CAPTURE(i); + throw std::runtime_error("bad"); + } catch (...) { + } + + int j = 2; + SNITCH_CHECK(j == 1); + }; + + framework.run_test(); + CHECK_NO_CAPTURE; + } + + SECTION("with handled exceptions") { + framework.test_case.func = []() { + try { + int i = 1; + SNITCH_CAPTURE(i); + throw std::runtime_error("bad"); + } catch (...) { + } + + try { + int j = 2; + SNITCH_CAPTURE(j); + throw std::runtime_error("bad"); + } catch (...) { + } + + int k = 3; + SNITCH_CAPTURE(k); + SNITCH_CHECK(k == 1); + }; + + framework.run_test(); + CHECK_CAPTURES("k := 3"); + } + + SECTION("with handled exception then unhandled") { + framework.test_case.func = []() { + try { + int i = 1; + SNITCH_CAPTURE(i); + throw std::runtime_error("bad"); + } catch (...) { + } + + int j = 2; + SNITCH_CAPTURE(j); + throw std::runtime_error("bad"); + }; + + framework.run_test(); + CHECK_CAPTURES("j := 2"); + } + + SECTION("with handled exception then unhandled no capture") { + framework.test_case.func = []() { + try { + int i = 1; + SNITCH_CAPTURE(i); + throw std::runtime_error("bad"); + } catch (...) { + } + + throw std::runtime_error("bad"); + }; + + framework.run_test(); + // FIXME: expected nothing + CHECK_CAPTURES("i := 1"); + } #endif }