Improve failure reporting and add more assertions

Adds BV_NOTHROW, BV_THROWS, and BV_THROWS_ANY.

Improves failure reporting.
This commit is contained in:
2026-06-27 05:41:58 +02:00
parent ee17a68d3d
commit 9361f6094c
10 changed files with 343 additions and 395 deletions
+5 -1
View File
@@ -40,6 +40,10 @@ namespace bitshift::validate
o << "\033[0m";
switch (ansi.get_color()) {
case ansi::kNone:
o << "\033[0";
break;
case ansi::kBlack:
o << "\033[30";
break;
@@ -109,7 +113,7 @@ namespace bitshift::validate
ostream&
operator<<(ostream& o, ansi::style const& style)
{
return o << ansi{ansi::color::kBlack, style};
return o << ansi{ansi::color::kNone, style};
}
} // namespace bitshift::validate
+1
View File
@@ -16,6 +16,7 @@ namespace bitshift::validate
enum color
: uint8_t
{
kNone,
kBlack,
kRed,
kGreen,
+17 -1
View File
@@ -1,9 +1,10 @@
#ifndef bitshift__validate__assert_hxx_
#define bitshift__validate__assert_hxx_
#include <bitshift/validate/types.hxx>
#include <bitshift/validate/failure.hxx>
#include <bitshift/validate/reporting.hxx>
#include <bitshift/validate/traits.hxx>
#include <bitshift/validate/types.hxx>
namespace bitshift::validate::assertion
{
@@ -42,6 +43,21 @@ namespace bitshift::validate::assertion
string const& rhs_str,
source_location const& origin = source_location::current());
template<typename F>
void
assert_nothrow(F const& f,
source_location const& origin = source_location::current());
template<typename T, typename F>
void
assert_throws(F const& f,
source_location const& origin = source_location::current());
template<typename F>
void
assert_throws_any(F const& f,
source_location const& origin = source_location::current());
} // namespace bitshift::validate::assertion
#include <bitshift/validate/assert.txx>
+122 -79
View File
@@ -11,20 +11,18 @@ namespace bitshift::validate::assertion
return;
}
string value_str;
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expected expression to be true; was false",
origin.file_name(),
origin.line()
);
if constexpr (traits::has_output_operator<T>) {
stringstream str;
str << expr;
value_str = str.str();
}
throw unary_assertion_failure{
"expected expression to be true; was false",
origin.file_name(),
origin.line(),
std::move(expr_str),
std::move(value_str)
p << "expression: " << expr_str << '\n';
p << "value : " << expr << '\n';
}
};
}
@@ -38,20 +36,18 @@ namespace bitshift::validate::assertion
return;
}
string value_str;
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expected expression to be false; was true",
origin.file_name(),
origin.line()
);
if constexpr (traits::has_output_operator<T>) {
stringstream str;
str << expr;
value_str = str.str();
}
throw unary_assertion_failure{
"expected expression to be false; was true",
origin.file_name(),
origin.line(),
std::move(expr_str),
std::move(value_str)
p << "expression: " << expr_str << '\n';
p << "value : " << expr << '\n';
}
};
}
@@ -65,20 +61,18 @@ namespace bitshift::validate::assertion
return;
}
string value_str;
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expected expression to be null; was not",
origin.file_name(),
origin.line()
);
if constexpr (traits::has_output_operator<T>) {
stringstream str;
str << expr;
value_str = str.str();
}
throw unary_assertion_failure{
"expected expression to be null; was not null",
origin.file_name(),
origin.line(),
std::move(expr_str),
std::move(value_str)
p << "expression: " << expr_str << '\n';
p << "value : " << expr << '\n';
}
};
}
@@ -94,29 +88,20 @@ namespace bitshift::validate::assertion
return;
}
std::string lhs_val;
std::string rhs_val;
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expected lhs to equal rhs",
origin.file_name(),
origin.line()
);
if constexpr (traits::has_output_operator<T1>) {
std::stringstream str;
str << lhs;
lhs_val = str.str();
}
if constexpr (traits::has_output_operator<T2>) {
std::stringstream str;
str << rhs;
rhs_val = str.str();
}
throw binary_assertion_failure{
"expected lhs to equal rhs",
origin.file_name(),
origin.line(),
std::move(lhs_str),
std::move(lhs_val),
std::move(rhs_str),
std::move(rhs_val)
p << "lhs : " << lhs_str << '\n';
p << "lhs value: " << lhs << '\n';
p << "rhs : " << rhs_str << '\n';
p << "rhs value: " << rhs << '\n';
}
};
}
@@ -132,29 +117,87 @@ namespace bitshift::validate::assertion
return;
}
std::string lhs_val;
std::string rhs_val;
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expected lhs to not equal rhs",
origin.file_name(),
origin.line()
);
if constexpr (traits::has_output_operator<T1>) {
std::stringstream str;
str << lhs;
lhs_val = str.str();
p << "lhs : " << lhs_str << '\n';
p << "lhs value: " << lhs << '\n';
p << "rhs : " << rhs_str << '\n';
p << "rhs value: " << rhs << '\n';
}
};
}
template<typename F>
void
assert_nothrow(F const& f, source_location const& origin)
{
try {
f();
}
catch (...) {
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expression unexpectedly threw an exception",
origin.file_name(),
origin.line()
);
}
};
}
}
template<typename T, typename F>
void
assert_throws(F const& f, source_location const& origin)
{
try {
f();
}
catch (T const&) {
return;
}
if constexpr (traits::has_output_operator<T2>) {
std::stringstream str;
str << rhs;
rhs_val = str.str();
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expected expression was not thrown",
origin.file_name(),
origin.line()
);
}
};
}
template<typename F>
void
assert_throws_any(F const& f, source_location const& origin)
{
try {
f();
}
catch (...) {
return;
}
throw binary_assertion_failure{
"expected lhs to not equal rhs",
origin.file_name(),
origin.line(),
std::move(lhs_str),
std::move(lhs_val),
std::move(rhs_str),
std::move(rhs_val)
throw failure{
[=](reporter& r)
{
auto p = r.log_failure(
"expected expression was not thrown",
origin.file_name(),
origin.line()
);
}
};
}
+6 -2
View File
@@ -73,11 +73,15 @@ namespace bitshift::validate
try {
tc.run();
}
catch (ignore const&) {
catch (ignored const&) {
execute_teardown(r);
return kExecuteIgnored;
}
catch (assertion_failure const& failure) {
catch (passed const&) {
// No-op.
//
}
catch (failure const& failure) {
failure.report(r);
execute_teardown(r);
return kExecuteFailed;
-130
View File
@@ -1,130 +0,0 @@
#include <bitshift/validate/failure.hxx>
#include <bitshift/validate/reporting.hxx>
namespace bitshift::validate
{
unary_assertion_failure::
unary_assertion_failure(string message,
string file,
uint32_t line,
string expr,
string val)
: _message{std::move(message)}
, _file {std::move(file)}
, _line {line}
, _expr {std::move(expr)}
, _val {std::move(val)}
{}
string const&
unary_assertion_failure::
get_file() const
{
return _file;
}
uint32_t
unary_assertion_failure::
get_line() const
{
return _line;
}
string const&
unary_assertion_failure::
get_expr() const
{
return _expr;
}
string const&
unary_assertion_failure::
get_val() const
{
return _val;
}
void
unary_assertion_failure::
report(reporter& r) const
{
auto p = r.log_failure(_message, _file, _line);
p << "expression: " << _expr << '\n';
p << "value : " << _val << '\n';
}
binary_assertion_failure::
binary_assertion_failure(string message,
string file,
uint32_t line,
string lhs_expr,
string lhs_val,
string rhs_expr,
string rhs_val)
: _message {std::move(message)}
, _file {std::move(file)}
, _line {line}
, _lhs_expr{std::move(lhs_expr)}
, _lhs_val {std::move(lhs_val)}
, _rhs_expr{std::move(rhs_expr)}
, _rhs_val {std::move(rhs_val)}
{}
string const&
binary_assertion_failure::
get_file() const
{
return _file;
}
uint32_t
binary_assertion_failure::
get_line() const
{
return _line;
}
string const&
binary_assertion_failure::
get_lhs_expr() const
{
return _lhs_expr;
}
string const&
binary_assertion_failure::
get_lhs_val() const
{
return _lhs_val;
}
string const&
binary_assertion_failure::
get_rhs_expr() const
{
return _rhs_expr;
}
string const&
binary_assertion_failure::
get_rhs_val() const
{
return _rhs_val;
}
void
binary_assertion_failure::
report(reporter& r) const
{
auto p = r.log_failure(_message, _file, _line);
p << "lhs expression: " << _lhs_expr << '\n';
p << "lhs value : " << _lhs_val << '\n';
p << "rhs expression: " << _rhs_expr << '\n';
p << "rhs value : " << _rhs_val << '\n';
}
} // namespace bitshift::validate
+49 -174
View File
@@ -2,204 +2,79 @@
#define bitshift__validate__failure_hxx_
#include <bitshift/validate/types.hxx>
#include <bitshift/validate/reporting.hxx>
namespace bitshift::validate
{
struct ignore { };
class reporter;
/// @brief Class used to indicate an assertion failure.
/// @brief Class used to explicitly ignore a test.
///
class assertion_failure
struct ignored {};
/// @brief Class used to explicitly pass a test.
///
struct passed {};
/// @brief Class used to explicitly fail a test.
///
class failure
{
public:
/// @brief Destructor.
/// @brief Constructor.
/// @param rf The reporter function.
///
virtual
~assertion_failure() noexcept = default;
explicit
failure(function<void(reporter&)> rf)
: _rf{rf}
{}
/// @brief Copy-construction is disabled.
///
failure(failure const&) = delete;
/// @brief Move-construction is disabled.
///
failure(failure&&) = delete;
/// @brief Report assertion failure.
///
/// @param r The reporter.
///
virtual
void
report(reporter& r) const = 0;
protected:
/// @brief Constructor.
///
assertion_failure() = default;
/// @brief Copy-construction is disabled.
///
assertion_failure(assertion_failure const&) = delete;
/// @brief Move-construction is disabled.
///
assertion_failure(assertion_failure&&) = delete;
report(reporter& r) const
{
_rf(r);
}
/// @brief Copy-assignment is disabled.
///
assertion_failure& operator=(assertion_failure const&) = delete;
failure& operator=(failure const&) = delete;
/// @brief Move-assignment is disabled.
///
assertion_failure& operator=(assertion_failure&&) = delete;
failure& operator=(failure&&) = delete;
};
/// @brief Indicates a unary assertion failure.
///
class unary_assertion_failure
: public assertion_failure
{
public:
/// @brief Constructor.
/// @brief Helper function to throw a failure exception.
///
/// @param message The assertion failure message.
/// @param file The file in which the assertion failure occurred.
/// @param line The line on which the assertion failure occurred.
/// @param expr The expression that caused the assertion failure.
/// @param val The expression value that caused the assertion failure.
/// @param message The message describing the failure.
///
unary_assertion_failure(string message,
string file,
uint32_t line,
string expr,
string val);
/// @brief Copy-construction is disabled.
///
unary_assertion_failure(unary_assertion_failure const&) = delete;
/// @brief Move-construction is disabled.
///
unary_assertion_failure(unary_assertion_failure&&) = delete;
/// @brief Get file.
///
string const&
get_file() const;
/// @brief Get line.
///
uint32_t
get_line() const;
/// @brief Get expression.
///
string const&
get_expr() const;
/// @brief Get expression value.
///
string const&
get_val() const;
static
void
report(reporter&) const override;
/// @brief Copy-assignment is disabled.
///
unary_assertion_failure& operator=(unary_assertion_failure const&) = delete;
/// @brief Move-assignment is disabled.
///
unary_assertion_failure& operator=(unary_assertion_failure&&) = delete;
fail(string const& message,
source_location const& origin = source_location::current())
{
throw failure{
[=](reporter& r)
{
r.log_failure(
message,
origin.file_name(),
origin.line()
);
}
};
}
private:
string _message;
string _file;
uint32_t _line{};
string _expr;
string _val;
};
/// @brief Indicates a binary assertion failure.
///
class binary_assertion_failure
: public assertion_failure
{
public:
/// @brief Constructor.
///
/// @param message The assertion failure message.
/// @param file The file in which the assertion failure occurred.
/// @param line The line on which the assertion failure occurred.
/// @param lhs_expr The left-hand side expression that caused the assertion failure.
/// @param lhs_val The left-hand side expression value that caused the assertion failure.
/// @param rhs_expr The right-hand side expression that caused the assertion failure.
/// @param rhs_val The right-hand side expression value that caused the assertion failure.
///
binary_assertion_failure(string message,
string file,
uint32_t line,
string lhs_expr,
string lhs_val,
string rhs_expr,
string rhs_val);
/// @brief Copy-construction is disabled.
///
binary_assertion_failure(binary_assertion_failure const&) = delete;
/// @brief Move-construction is disabled.
///
binary_assertion_failure(binary_assertion_failure&&) = delete;
/// @brief Get file.
///
string const&
get_file() const;
/// @brief Get line.
///
uint32_t
get_line() const;
/// @brief Get left-hand side expression.
///
string const&
get_lhs_expr() const;
/// @brief Get left-hand side expression value.
///
string const&
get_lhs_val() const;
/// @brief Get right-hand side expression.
///
string const&
get_rhs_expr() const;
/// @brief Get right-hand side expression value.
///
string const&
get_rhs_val() const;
void
report(reporter&) const override;
/// @brief Copy-assignment is disabled.
///
binary_assertion_failure& operator=(binary_assertion_failure const&) = delete;
/// @brief Move-assignment is disabled.
///
binary_assertion_failure& operator=(binary_assertion_failure&&) = delete;
private:
string _message;
string _file;
uint32_t _line{};
string _lhs_expr;
string _lhs_val;
string _rhs_expr;
string _rhs_val;
function<void(reporter&)> _rf;
};
+28 -1
View File
@@ -103,7 +103,20 @@
/// @brief Ignore a test.
/// @hideinitializer
///
#define BV_IGNORE throw bitshift::validate::ignore {};
#define BV_IGNORE throw bitshift::validate::ignored{};
/// @ingroup assertions
/// @brief Explicitly pass a test.
/// @hideinitializer
///
#define BV_PASS throw bitshift::validate::passed{};
/// @ingroup assertions
/// @brief Explicitly fail a test.
/// @hideinitializer
///
#define BV_FAIL(msg) \
bitshift::validate::failure::fail(msg);
/// @ingroup assertions
/// @brief Assert a @c true value.
@@ -199,4 +212,18 @@
#define BV_ASSERT_NOT_EQUAL(lhs, rhs) \
bitshift::validate::assertion::assert_not_equal((lhs), (rhs), #lhs, #rhs)
/// @ingroup assertions
/// @brief Assert @a expr does not throw.
/// @param expr The expression to test.
/// @hideinitializer
///
#define BV_ASSERT_NOTHROW(expr) \
bitshift::validate::assertion::assert_nothrow([] { (expr); })
#define BV_ASSERT_THROWS(except, expr) \
bitshift::validate::assertion::assert_throws<except>([] { (expr); })
#define BV_ASSERT_THROWS_ANY(expr) \
bitshift::validate::assertion::assert_throws_any([] { (expr); })
#endif
+8 -1
View File
@@ -6,6 +6,7 @@
#include <bitshift/validate/setup.hxx>
#include <bitshift/validate/teardown.hxx>
#include <bitshift/validate/test-case.hxx>
#include <bitshift/validate/traits.hxx>
namespace bitshift::validate
{
@@ -174,7 +175,13 @@ namespace bitshift::validate
proxy&
operator<<(U const& other)
{
_detail << std::boolalpha << other;
if constexpr (traits::has_output_operator<U>) {
_detail << std::boolalpha << other;
}
else {
_detail << "<no output operator defined>";
}
return *this;
}
+103 -2
View File
@@ -9,9 +9,110 @@ BV_TEST(sanity, "basic sanity check")
BV_ASSERT_NOT_EQUAL(0, 1);
}
BV_TEST(ignored, "ignored test")
BV_TEST(ignore, "test BV_IGNORE")
{
BV_IGNORE;
try {
BV_IGNORE;
}
catch (bitshift::validate::ignored const&) {
BV_PASS;
}
catch (...) {
// Fall-through.
//
}
BV_FAIL("expected exception not thrown");
}
BV_TEST(pass, "test BV_PASS")
{
try {
BV_PASS;
}
catch (bitshift::validate::passed const&) {
BV_PASS;
}
catch (...) {
// Fall-through.
//
}
BV_FAIL("expected exception not thrown");
}
BV_TEST(fail, "test BV_FAIL")
{
try {
BV_FAIL("message");
}
catch (bitshift::validate::failure const&) {
BV_PASS;
}
catch (...) {
// Fall-through.
//
}
BV_FAIL("expected exception not thrown");
}
BV_TEST(nothrow, "test BV_ASSERT_NOTHROW")
{
struct except {};
try {
// This assertion should fail.
//
BV_ASSERT_NOTHROW(throw except{});
}
catch (bitshift::validate::failure const&) {
BV_PASS;
}
catch (...) {
// Fall-through.
//
}
BV_FAIL("expected exception not thrown");
}
BV_TEST(throws, "test BV_ASSERT_THROWS")
{
struct except {};
try {
// This assertion should fail.
//
BV_ASSERT_THROWS(except, (void)0);
}
catch (bitshift::validate::failure const&) {
BV_PASS;
}
catch (...) {
// Fall-through.
//
}
BV_FAIL("expected exception not thrown");
}
BV_TEST(throws_any, "test BV_ASSERT_THROWS_ANY")
{
try {
// This assertion should fail.
//
BV_ASSERT_THROWS_ANY((void)0);
}
catch (bitshift::validate::failure const&) {
BV_PASS;
}
catch (...) {
// Fall-through.
//
}
BV_FAIL("expected exception not thrown");
}
int