Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix capture/section state after unhandled exception(s) #178

Merged
merged 7 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions codecov.yaml → codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ coverage:
round: down
range: "80...95"
status:
project:
default:
target: auto
# adjust accordingly based on how flaky your tests are
# this allows a drop from the previous base commit coverage
threshold: 1%
project:
default:
target: auto
# adjust accordingly based on how flaky your tests are
# this allows a drop from the previous base commit coverage
threshold: 1%
patch:
default:
target: 0%
Expand Down
17 changes: 13 additions & 4 deletions include/snitch/snitch_capture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
namespace snitch::impl {
struct scoped_capture {
capture_state& captures;
std::size_t count = 0;
#if SNITCH_WITH_EXCEPTIONS
std::optional<capture_state>& held_captures;
#endif
std::size_t count = 0;

~scoped_capture() {
captures.resize(captures.size() - count);
}
SNITCH_EXPORT ~scoped_capture();
};

SNITCH_EXPORT std::string_view extract_next_name(std::string_view& names) noexcept;
Expand All @@ -37,15 +38,23 @@ void add_capture(test_state& state, std::string_view& names, const T& arg) {
template<string_appendable... Args>
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.
template<string_appendable... Args>
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

Expand Down
11 changes: 9 additions & 2 deletions include/snitch/snitch_string_utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,22 @@ constexpr small_string<N> resize_or_truncate(const small_string<M>& str) noexcep
return str;
} else if constexpr (N > M) {
small_string<N> out;
append(out, str);
append(out, std::string_view{str});
return out;
} else {
small_string<N> out;
append_or_truncate(out, str);
append_or_truncate(out, std::string_view{str});
return out;
}
}

template<std::size_t N, std::size_t M>
constexpr small_string<N> resize_or_truncate(std::string_view str) noexcept {
small_string<N> out;
append(out, str);
return out;
}

SNITCH_EXPORT [[nodiscard]] bool replace_all(
small_string_span string, std::string_view pattern, std::string_view replacement) noexcept;

Expand Down
9 changes: 9 additions & 0 deletions include/snitch/snitch_test_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "snitch/snitch_vector.hpp"

#include <cstddef>
#include <optional>
#include <string_view>

namespace snitch {
Expand Down Expand Up @@ -285,13 +286,21 @@ struct test_state {
capture_state captures = {};
location_state locations = {};

#if SNITCH_WITH_EXCEPTIONS
std::optional<capture_state> held_captures = {};
#endif

std::size_t asserts = 0;
std::size_t failures = 0;
std::size_t allowed_failures = 0;
bool may_fail = false;
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
Expand Down
17 changes: 17 additions & 0 deletions src/snitch_capture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ void trim(std::string_view& str, std::string_view patterns) noexcept {
}
} // namespace

scoped_capture::~scoped_capture() {
#if SNITCH_WITH_EXCEPTIONS
if (std::uncaught_exceptions() > 0 && !held_captures.has_value()) {
// We are unwinding the stack because an exception has been thrown;
// keep a copy of the full capture state since we will want to preserve the information
// when reporting the exception.
held_captures = captures;
}
#endif

captures.resize(captures.size() - count);
}

std::string_view extract_next_name(std::string_view& names) noexcept {
std::string_view result;

Expand Down Expand Up @@ -78,6 +91,10 @@ small_string<max_capture_length>& 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();
Expand Down
15 changes: 13 additions & 2 deletions src/snitch_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
134 changes: 134 additions & 0 deletions tests/runtime_tests/capture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#include "testing_event.hpp"

#include <string>
#if SNITCH_WITH_EXCEPTIONS
# include <stdexcept>
#endif

using namespace std::literals;

Expand Down Expand Up @@ -171,6 +174,119 @@ TEST_CASE("capture", "[test macros]") {
CHECK_CAPTURES_FOR_FAILURE(0u, "i := 1");
CHECK_CAPTURES_FOR_FAILURE(1u, "i := 1", "2 * i := 2");
}

#if SNITCH_WITH_EXCEPTIONS
SECTION("with exception") {
framework.test_case.func = []() {
for (std::size_t i = 0; i < 5; ++i) {
SNITCH_CAPTURE(i);

if (i % 2 == 1) {
throw std::runtime_error("bad");
}
}
};

framework.run_test();
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
}

TEST_CASE("info", "[test macros]") {
Expand Down Expand Up @@ -324,6 +440,24 @@ TEST_CASE("info", "[test macros]") {
framework.run_test();
CHECK_CAPTURES("1", "i := 1");
}

#if SNITCH_WITH_EXCEPTIONS
SECTION("with exception") {
framework.test_case.func = []() {
for (std::size_t i = 0; i < 5; ++i) {
SNITCH_INFO(i);

if (i % 2 == 1) {
throw std::runtime_error("bad");
}
}
};

framework.run_test();
REQUIRE(framework.get_num_failures() == 1u);
CHECK_CAPTURES_FOR_FAILURE(0u, "1");
}
#endif
}

SNITCH_WARNING_POP
2 changes: 1 addition & 1 deletion tests/runtime_tests/section.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ TEST_CASE("section readme example", "[test macros]") {
}
};

framework.registry.print_callback = print;
framework.registry.print_callback = print;

framework.test_case.func = []() {
auto& reg = snitch::impl::get_current_test().reg;
Expand Down
Loading