Skip to content

CXX-1110 add CHECK_THROWS_WITH_CODE #1300

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

Merged
merged 4 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
221 changes: 221 additions & 0 deletions src/bsoncxx/test/catch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,231 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <bsoncxx/test/catch.hh>

//

#include <system_error>

#include <bsoncxx/config/prelude.hpp>

#include <catch2/catch_session.hpp>

int BSONCXX_ABI_CDECL main(int argc, char* argv[]) {
return Catch::Session().run(argc, argv);
}

TEST_CASE("THROWS_WITH_CODE", "[bsoncxx][test]") {
SECTION("basic") {
CHECK_THROWS_WITH_CODE(
throw std::system_error(std::make_error_code(std::errc::invalid_argument)),
std::errc::invalid_argument);
}

// TEST_CHECK is evaluated when a `std::system_error` exception is thrown as expected and is
// used to evaluate the error code comparison check.
int checked = 0;

// TEST_CHECK_THROWS_AS is evaluated when an unexpected exception type is thrown and is used to
// trigger Catch test failure (always fails).
int checked_throws_as = 0;

SECTION("Catch::TestFailureException") {
#define TEST_CHECK(expr) \
if (1) { \
++checked; \
FAIL("Catch::TestFailureException did not throw"); \
} else \
((void)0)

#define TEST_CHECK_THROWS_AS(expr, type) \
if (1) { \
++checked_throws_as; \
CHECK_THROWS_AS(expr, Catch::TestFailureException); \
} else \
((void)0)

try {
THROWS_WITH_CODE_IMPL(
TEST_CHECK, throw Catch::TestFailureException(), std::errc::invalid_argument);
} catch (const Catch::TestFailureException&) {
SUCCEED("Catch::TestFailureException was propagated");
} catch (...) {
FAIL("unexpected exception was thrown");
}

#undef TEST_CHECK
#undef TEST_CHECK_THROWS_AS

CHECK(checked == 0);
CHECK(checked_throws_as == 0);
}

SECTION("Catch::TestSkipException") {
#define TEST_CHECK(expr) \
if (1) { \
++checked; \
FAIL("Catch::TestSkipException did not throw"); \
} else \
((void)0)

#define TEST_CHECK_THROWS_AS(expr, type) \
if (1) { \
++checked_throws_as; \
CHECK_THROWS_AS(expr, Catch::TestSkipException); \
} else \
((void)0)

try {
THROWS_WITH_CODE_IMPL(
TEST_CHECK, throw Catch::TestSkipException(), std::errc::invalid_argument);
} catch (const Catch::TestSkipException&) {
SUCCEED("Catch::TestSkipException was propagated");
} catch (...) {
FAIL("unexpected exception was thrown");
}

#undef TEST_CHECK
#undef TEST_CHECK_THROWS_AS

CHECK(checked == 0);
CHECK(checked_throws_as == 0);
}

SECTION("unrelated") {
struct unrelated {};

#define TEST_CHECK(expr) \
if (1) { \
++checked; \
FAIL("unrelated exception did not throw"); \
} else \
((void)0)

#define TEST_CHECK_THROWS_AS(expr, type) \
if (1) { \
++checked_throws_as; \
CHECK_THROWS_AS(expr, unrelated); \
} else \
((void)0)

THROWS_WITH_CODE_IMPL(TEST_CHECK, throw unrelated(), std::errc::invalid_argument);

#undef TEST_CHECK
#undef TEST_CHECK_THROWS_AS

CHECK(checked == 0);
CHECK(checked_throws_as == 1);
}

SECTION("std::system_error") {
#define TEST_CHECK(expr) \
if (1) { \
++checked; \
CHECK(expr); \
} else \
((void)0)

#define TEST_CHECK_THROWS_AS(expr, type) \
if (1) { \
++checked_throws_as; \
CHECK_THROWS_AS(expr, std::system_error); \
} else \
((void)0)

THROWS_WITH_CODE_IMPL(
TEST_CHECK,
throw std::system_error(std::make_error_code(std::errc::invalid_argument)),
std::make_error_code(std::errc::invalid_argument));

#undef TEST_CHECK
#undef TEST_CHECK_THROWS_AS

CHECK(checked == 1);
CHECK(checked_throws_as == 0);
}

SECTION("derived") {
struct derived : std::system_error {
using std::system_error::system_error;
};

#define TEST_CHECK(expr) \
if (1) { \
++checked; \
CHECK(expr); \
} else \
((void)0)

#define TEST_CHECK_THROWS_AS(expr, type) \
if (1) { \
++checked_throws_as; \
CHECK_THROWS_AS(expr, std::system_error); \
} else \
((void)0)

THROWS_WITH_CODE_IMPL(TEST_CHECK,
throw derived(std::make_error_code(std::errc::invalid_argument)),
std::errc::invalid_argument);

#undef TEST_CHECK
#undef TEST_CHECK_THROWS_AS

CHECK(checked == 1);
CHECK(checked_throws_as == 0);
}

SECTION("error code") {
#define TEST_CHECK(expr) \
if (1) { \
++checked; \
CHECK_FALSE(expr); /* invalid_argument != not_supported */ \
} else \
((void)0)

#define TEST_CHECK_THROWS_AS(expr, type) \
if (1) { \
++checked_throws_as; \
CHECK_THROWS_AS(expr, std::system_error); \
} else \
((void)0)

THROWS_WITH_CODE_IMPL(
TEST_CHECK,
throw std::system_error(std::make_error_code(std::errc::invalid_argument)),
std::errc::not_supported);

#undef TEST_CHECK
#undef TEST_CHECK_THROWS_AS

CHECK(checked == 1);
CHECK(checked_throws_as == 0);
}

SECTION("error condition") {
#define TEST_CHECK(expr) \
if (1) { \
++checked; \
CHECK(expr); \
} else \
((void)0)

#define TEST_CHECK_THROWS_AS(expr, type) \
if (1) { \
++checked_throws_as; \
CHECK_THROWS_AS(expr, std::system_error); \
} else \
((void)0)

THROWS_WITH_CODE_IMPL(
TEST_CHECK,
throw std::system_error(std::make_error_code(std::errc::invalid_argument)),
std::make_error_condition(std::errc::invalid_argument));

#undef TEST_CHECK
#undef TEST_CHECK_THROWS_AS

CHECK(checked == 1);
CHECK(checked_throws_as == 0);
}
}
37 changes: 36 additions & 1 deletion src/bsoncxx/test/catch.hh
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,44 @@
#include <catch2/catch_test_macros.hpp> // TEST_CASE, SECTION, CHECK, etc.
#include <catch2/catch_tostring.hpp> // Catch::StringMaker

#define THROWS_WITH_CODE_IMPL(_assertion, _expr, _code) \
if (1) { \
try { \
(void)(_expr); \
INFO("expected an exception to be thrown: " #_expr); \
_assertion(false); \
} catch (const Catch::TestFailureException&) { \
throw; /* Propagate Catch exceptions. */ \
} catch (const Catch::TestSkipException&) { \
throw; /* Propagate Catch exceptions. */ \
} catch (const std::system_error& ex) { \
using std::make_error_code; \
(void)ex; /* Avoid unused variable warnings. */ \
_assertion(ex.code() == (_code)); \
} catch (...) { \
/* Reuse `*_THROWS_AS` to handle the unexpected exception type. */ \
BSONCXX_CONCAT(_assertion, _THROWS_AS)(throw, std::system_error); \
} \
} else \
((void)0)

#define CHECK_THROWS_WITH_CODE(_expr, _code) THROWS_WITH_CODE_IMPL(CHECK, _expr, _code)
#define REQUIRE_THROWS_WITH_CODE(_expr, _code) THROWS_WITH_CODE_IMPL(REQUIRE, _expr, _code)

namespace Catch {

// Catch2 must be able to stringify documents, optionals, etc. if they're used in Catch2 macros.
template <>
struct StringMaker<std::error_condition> {
static std::string convert(const std::error_condition& value) {
std::string res;

res += value.category().name();
res += ':';
res += Catch::StringMaker<int>::convert(value.value());

return res;
}
};

template <>
struct StringMaker<bsoncxx::oid> {
Expand Down