diff --git a/src/bsoncxx/test/catch.cpp b/src/bsoncxx/test/catch.cpp index a4d4abfd7f..be1e02324f 100644 --- a/src/bsoncxx/test/catch.cpp +++ b/src/bsoncxx/test/catch.cpp @@ -12,6 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + +// + +#include + #include #include @@ -19,3 +25,218 @@ 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); + } +} diff --git a/src/bsoncxx/test/catch.hh b/src/bsoncxx/test/catch.hh index cba3f7f20c..15d1b31f72 100644 --- a/src/bsoncxx/test/catch.hh +++ b/src/bsoncxx/test/catch.hh @@ -27,9 +27,44 @@ #include // TEST_CASE, SECTION, CHECK, etc. #include // 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 { + static std::string convert(const std::error_condition& value) { + std::string res; + + res += value.category().name(); + res += ':'; + res += Catch::StringMaker::convert(value.value()); + + return res; + } +}; template <> struct StringMaker {