blob: dd106f14ee6e51c612a87a20fd67f21315084288 [file]
//===- 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 <system_error>
using namespace orc_rt;
namespace {
class CustomError : public ErrorExtends<CustomError, ErrorInfoBase> {
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<decltype(Result), Error>,
"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<StringError>("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<T>.
EXCEPTION_TEST({
auto Result = runCapturingExceptions([]() { return 42; });
static_assert(std::is_same_v<decltype(Result), Expected<int>>,
"Expected return type to be Expected<int>");
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<int> { 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<ExceptionError>());
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<ErrorInfoBase> 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<StringError>("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<StringError>());
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<CustomError>();
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");
}
});
}