//===- ErrorExceptionInterorTest.cpp --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Test interoperability between errors and exceptions. // //===----------------------------------------------------------------------===// #include "orc-rt-c/config.h" #include "orc-rt/Error.h" #include "gtest/gtest.h" #include using namespace orc_rt; namespace { class CustomError : public ErrorExtends { public: std::string toString() const noexcept override { return "CustomError"; } }; } // anonymous namespace #if ORC_RT_ENABLE_EXCEPTIONS #define EXCEPTION_TEST(X) \ do { \ X; \ } while (false) #else #define EXCEPTION_TEST(X) GTEST_SKIP() << "Exceptions disabled" #endif TEST(ErrorExceptionInterorTest, SuccessDoesntThrow) { // Test that Error::success values don't throw when throwOnFailure is called. EXCEPTION_TEST({ try { auto E = Error::success(); E.throwOnFailure(); } catch (...) { ADD_FAILURE() << "Unexpected throw"; } }); } TEST(ErrorExceptionInteropTest, VoidReturnSuccess) { // Test that runCapturingExceptions returns Error::success for void() // function objects. EXCEPTION_TEST({ bool Executed = false; auto Result = runCapturingExceptions([&]() { Executed = true; }); static_assert(std::is_same_v, "Expected return type to be Error"); EXPECT_FALSE(Result); // Error::success() evaluates to false EXPECT_TRUE(Executed); }); } TEST(ErrorExceptionInteropTest, ErrorReturnPreserved) { // Test that plain Errors returned from runCapturingExceptions are returned // as expected. EXCEPTION_TEST({ auto Result = runCapturingExceptions( []() -> Error { return make_error("original error"); }); EXPECT_TRUE(!!Result); EXPECT_EQ(toString(std::move(Result)), "original error"); }); } TEST(ErrorExceptionInteropTest, NonFallibleTReturnWrapped) { // Test that; for function types returning a non-Error, non-Expected type T; // runCapturingExceptions returns an Expected. EXCEPTION_TEST({ auto Result = runCapturingExceptions([]() { return 42; }); static_assert(std::is_same_v>, "Expected return type to be Expected"); EXPECT_TRUE(!!Result); EXPECT_EQ(*Result, 42); }); } TEST(ErrorExceptionInteropTest, ExpectedReturnPreserved) { // Test that Expected success values are returned as expected. EXCEPTION_TEST({ auto Result = runCapturingExceptions([]() -> Expected { return 42; }); EXPECT_TRUE(!!Result); EXPECT_EQ(*Result, 42); }); } TEST(ErrorExceptionInteropTest, CatchThrownInt) { // Check that we can capture a thrown exception into an Error and recognize it // as a thrown exception. EXCEPTION_TEST({ auto E = runCapturingExceptions([]() { throw 42; }); EXPECT_TRUE(!!E); EXPECT_TRUE(E.isA()); consumeError(std::move(E)); }); } TEST(ErrorExceptionInteropTest, RoundTripExceptionThroughError) { // Check that we can rethrow an exception that has been captured into an // error without affecting the dynamic type or value (e.g. we don't actually // rethrow the wrong type / value). EXCEPTION_TEST({ int Result = 0; try { auto E = runCapturingExceptions([]() { throw 42; }); EXPECT_TRUE(!!E); E.throwOnFailure(); } catch (int N) { Result = N; } catch (...) { ADD_FAILURE() << "Caught unexpected error type"; } EXPECT_EQ(Result, 42); }); } static std::string peekAtErrorMessage(Error &Err) { std::string Msg; Err = handleErrors(std::move(Err), [&](std::unique_ptr EIB) { Msg = EIB->toString(); return make_error(std::move(EIB)); }); return Msg; } TEST(ErrorExceptionInteropTest, RoundTripErrorThroughException) { // Test Error → Exception → Error preserves type and message EXCEPTION_TEST({ auto OriginalErr = make_error("hello, error!"); std::string OriginalMsg = peekAtErrorMessage(OriginalErr); Error RecoveredErr = Error::success(); try { OriginalErr.throwOnFailure(); } catch (ErrorInfoBase &EIB) { ErrorAsOutParameter _(RecoveredErr); RecoveredErr = restore_error(std::move(EIB)); } catch (...) { ADD_FAILURE() << "Caught unexpected error type"; } EXPECT_TRUE(RecoveredErr.isA()); EXPECT_EQ(toString(std::move(RecoveredErr)), OriginalMsg); }); } TEST(ErrorExceptionInteropTest, ThrowErrorAndCatchAsException) { // Check that we can create an Error value, throw it as an exception, and // match its dynamic type to a catch handler. EXCEPTION_TEST({ bool HandlerRan = false; std::string Msg; try { auto E = make_error(); E.throwOnFailure(); } catch (CustomError &E) { HandlerRan = true; } catch (ErrorInfoBase &E) { ADD_FAILURE() << "Failed to downcase error to dynamic type"; } catch (...) { ADD_FAILURE() << "Caught unexpected error type"; } }); } TEST(ErrorExceptionInteropTest, ErrorExceptionToString) { /// Check that exceptions can be converted to Strings as exepcted. EXCEPTION_TEST({ { // std::exception should be converted by calling `.what()`; class MyException : public std::exception { public: ~MyException() override {} const char *what() const noexcept override { return "what"; } }; EXPECT_EQ(toString(runCapturingExceptions([]() { throw MyException(); })), "what"); } { // std::error_code should be converted by calling `.message()`. auto EC = std::make_error_code(std::errc::cross_device_link); std::string ECErrMsg = EC.message(); EXPECT_EQ(toString(runCapturingExceptions([&]() { throw EC; })), ECErrMsg); } { // std::string should be converted by copying its value. std::string ErrMsg = "foo"; EXPECT_EQ(toString(runCapturingExceptions([&]() { throw ErrMsg; })), ErrMsg); } { // Check that exceptions of other types produce the expected // "unrecognized type" error message: EXPECT_EQ(toString(runCapturingExceptions([]() { throw 42; })), "C++ exception of unknown type"); } }); }