//===- ErrorTest.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 // //===----------------------------------------------------------------------===// // // This file is a part of the ORC runtime. // // Note: // This unit test was adapted from // llvm/unittests/Support/ErrorTest.cpp // //===----------------------------------------------------------------------===// #include "orc-rt/Error.h" #include "gtest/gtest.h" using namespace orc_rt; namespace { class CustomError : public RTTIExtends { public: CustomError(int Info) : Info(Info) {} std::string toString() const override { return "CustomError (" + std::to_string(Info) + ")"; } int getInfo() const { return Info; } protected: int Info; }; class CustomSubError : public RTTIExtends { public: CustomSubError(int Info, std::string ExtraInfo) : RTTIExtends(Info), ExtraInfo(std::move(ExtraInfo)) {} std::string toString() const override { return "CustomSubError (" + std::to_string(Info) + ", " + ExtraInfo + ")"; } const std::string &getExtraInfo() const { return ExtraInfo; } protected: std::string ExtraInfo; }; static Error handleCustomError(const CustomError &CE) { return Error::success(); } static void handleCustomErrorVoid(const CustomError &CE) {} static Error handleCustomErrorUP(std::unique_ptr CE) { return Error::success(); } static void handleCustomErrorUPVoid(std::unique_ptr CE) {} } // end anonymous namespace // Test that a checked success value doesn't cause any issues. TEST(Error, CheckedSuccess) { Error E = Error::success(); EXPECT_FALSE(E) << "Unexpected error while testing Error 'Success'"; } // Check that a consumed success value doesn't cause any issues. TEST(Error, ConsumeSuccess) { consumeError(Error::success()); } TEST(Error, ConsumeError) { Error E = make_error(42); if (E) { consumeError(std::move(E)); } else ADD_FAILURE() << "Error failure value should convert to true"; } // Test that unchecked success values cause an abort. TEST(Error, UncheckedSuccess) { EXPECT_DEATH( { Error E = Error::success(); }, "Error must be checked prior to destruction") << "Unchecked Error Succes value did not cause abort()"; } // Test that a checked but unhandled error causes an abort. TEST(Error, CheckedButUnhandledError) { auto DropUnhandledError = []() { Error E = make_error(42); (void)!E; }; EXPECT_DEATH(DropUnhandledError(), "Error must be checked prior to destruction") << "Unhandled Error failure value did not cause an abort()"; } // Check that we can handle a custom error. TEST(Error, HandleCustomError) { int CaughtErrorInfo = 0; handleAllErrors(make_error(42), [&](const CustomError &CE) { CaughtErrorInfo = CE.getInfo(); }); EXPECT_EQ(CaughtErrorInfo, 42) << "Wrong result from CustomError handler"; } // Check that handler type deduction also works for handlers // of the following types: // void (const Err&) // Error (const Err&) mutable // void (const Err&) mutable // Error (Err&) // void (Err&) // Error (Err&) mutable // void (Err&) mutable // Error (unique_ptr) // void (unique_ptr) // Error (unique_ptr) mutable // void (unique_ptr) mutable TEST(Error, HandlerTypeDeduction) { handleAllErrors(make_error(42), [](const CustomError &CE) {}); handleAllErrors( make_error(42), [](const CustomError &CE) mutable -> Error { return Error::success(); }); handleAllErrors(make_error(42), [](const CustomError &CE) mutable {}); handleAllErrors(make_error(42), [](CustomError &CE) -> Error { return Error::success(); }); handleAllErrors(make_error(42), [](CustomError &CE) {}); handleAllErrors( make_error(42), [](CustomError &CE) mutable -> Error { return Error::success(); }); handleAllErrors(make_error(42), [](CustomError &CE) mutable {}); handleAllErrors(make_error(42), [](std::unique_ptr CE) -> Error { return Error::success(); }); handleAllErrors(make_error(42), [](std::unique_ptr CE) {}); handleAllErrors(make_error(42), [](std::unique_ptr CE) mutable -> Error { return Error::success(); }); handleAllErrors(make_error(42), [](std::unique_ptr CE) mutable {}); // Check that named handlers of type 'Error (const Err&)' work. handleAllErrors(make_error(42), handleCustomError); // Check that named handlers of type 'void (const Err&)' work. handleAllErrors(make_error(42), handleCustomErrorVoid); // Check that named handlers of type 'Error (std::unique_ptr)' work. handleAllErrors(make_error(42), handleCustomErrorUP); // Check that named handlers of type 'Error (std::unique_ptr)' work. handleAllErrors(make_error(42), handleCustomErrorUPVoid); } // Test that we can handle errors with custom base classes. TEST(Error, HandleCustomErrorWithCustomBaseClass) { int CaughtErrorInfo = 0; std::string CaughtErrorExtraInfo; handleAllErrors(make_error(42, "foo"), [&](const CustomSubError &SE) { CaughtErrorInfo = SE.getInfo(); CaughtErrorExtraInfo = SE.getExtraInfo(); }); EXPECT_EQ(CaughtErrorInfo, 42) << "Wrong result from CustomSubError handler"; EXPECT_EQ(CaughtErrorExtraInfo, "foo") << "Wrong result from CustomSubError handler"; } // Check that we trigger only the first handler that applies. TEST(Error, FirstHandlerOnly) { int DummyInfo = 0; int CaughtErrorInfo = 0; std::string CaughtErrorExtraInfo; handleAllErrors( make_error(42, "foo"), [&](const CustomSubError &SE) { CaughtErrorInfo = SE.getInfo(); CaughtErrorExtraInfo = SE.getExtraInfo(); }, [&](const CustomError &CE) { DummyInfo = CE.getInfo(); }); EXPECT_EQ(CaughtErrorInfo, 42) << "Activated the wrong Error handler(s)"; EXPECT_EQ(CaughtErrorExtraInfo, "foo") << "Activated the wrong Error handler(s)"; EXPECT_EQ(DummyInfo, 0) << "Activated the wrong Error handler(s)"; } // Check that general handlers shadow specific ones. TEST(Error, HandlerShadowing) { int CaughtErrorInfo = 0; int DummyInfo = 0; std::string DummyExtraInfo; handleAllErrors( make_error(42, "foo"), [&](const CustomError &CE) { CaughtErrorInfo = CE.getInfo(); }, [&](const CustomSubError &SE) { DummyInfo = SE.getInfo(); DummyExtraInfo = SE.getExtraInfo(); }); EXPECT_EQ(CaughtErrorInfo, 42) << "General Error handler did not shadow specific handler"; EXPECT_EQ(DummyInfo, 0) << "General Error handler did not shadow specific handler"; EXPECT_EQ(DummyExtraInfo, "") << "General Error handler did not shadow specific handler"; } // ErrorAsOutParameter tester. static void errAsOutParamHelper(Error &Err) { ErrorAsOutParameter ErrAsOutParam(&Err); // Verify that checked flag is raised - assignment should not crash. Err = Error::success(); // Raise the checked bit manually - caller should still have to test the // error. (void)!!Err; } // Test that ErrorAsOutParameter sets the checked flag on construction. TEST(Error, ErrorAsOutParameterChecked) { Error E = Error::success(); errAsOutParamHelper(E); (void)!!E; } // Test that ErrorAsOutParameter clears the checked flag on destruction. TEST(Error, ErrorAsOutParameterUnchecked) { EXPECT_DEATH( { Error E = Error::success(); errAsOutParamHelper(E); }, "Error must be checked prior to destruction") << "ErrorAsOutParameter did not clear the checked flag on destruction."; } // Check 'Error::isA' method handling. TEST(Error, IsAHandling) { // Check 'isA' handling. Error E = make_error(42); Error F = make_error(42, "foo"); Error G = Error::success(); EXPECT_TRUE(E.isA()); EXPECT_FALSE(E.isA()); EXPECT_TRUE(F.isA()); EXPECT_TRUE(F.isA()); EXPECT_FALSE(G.isA()); consumeError(std::move(E)); consumeError(std::move(F)); consumeError(std::move(G)); } TEST(Error, StringError) { auto E = make_error("foo"); if (E.isA()) EXPECT_EQ(toString(std::move(E)), "foo") << "Unexpected StringError value"; else ADD_FAILURE() << "Expected StringError value"; } // Test Checked Expected in success mode. TEST(Error, CheckedExpectedInSuccessMode) { Expected A = 7; EXPECT_TRUE(!!A) << "Expected with non-error value doesn't convert to 'true'"; // Access is safe in second test, since we checked the error in the first. EXPECT_EQ(*A, 7) << "Incorrect Expected non-error value"; } // Test Expected with reference type. TEST(Error, ExpectedWithReferenceType) { int A = 7; Expected B = A; // 'Check' B. (void)!!B; int &C = *B; EXPECT_EQ(&A, &C) << "Expected failed to propagate reference"; } // Test Unchecked Expected in success mode. // We expect this to blow up the same way Error would. // Test runs in debug mode only. TEST(Error, UncheckedExpectedInSuccessModeDestruction) { EXPECT_DEATH( { Expected A = 7; }, "Expected must be checked before access or destruction.") << "Unchecekd Expected success value did not cause an abort()."; } // Test Unchecked Expected in success mode. // We expect this to blow up the same way Error would. // Test runs in debug mode only. TEST(Error, UncheckedExpectedInSuccessModeAccess) { EXPECT_DEATH( { Expected A = 7; *A; }, "Expected must be checked before access or destruction.") << "Unchecekd Expected success value did not cause an abort()."; } // Test Unchecked Expected in success mode. // We expect this to blow up the same way Error would. // Test runs in debug mode only. TEST(Error, UncheckedExpectedInSuccessModeAssignment) { EXPECT_DEATH( { Expected A = 7; A = 7; }, "Expected must be checked before access or destruction.") << "Unchecekd Expected success value did not cause an abort()."; } // Test Expected in failure mode. TEST(Error, ExpectedInFailureMode) { Expected A = make_error(42); EXPECT_FALSE(!!A) << "Expected with error value doesn't convert to 'false'"; Error E = A.takeError(); EXPECT_TRUE(E.isA()) << "Incorrect Expected error value"; consumeError(std::move(E)); } // Check that an Expected instance with an error value doesn't allow access to // operator*. // Test runs in debug mode only. TEST(Error, AccessExpectedInFailureMode) { Expected A = make_error(42); EXPECT_DEATH(*A, "Expected must be checked before access or destruction.") << "Incorrect Expected error value"; consumeError(A.takeError()); } // Check that an Expected instance with an error triggers an abort if // unhandled. // Test runs in debug mode only. TEST(Error, UnhandledExpectedInFailureMode) { EXPECT_DEATH( { Expected A = make_error(42); }, "Expected must be checked before access or destruction.") << "Unchecked Expected failure value did not cause an abort()"; } // Test covariance of Expected. TEST(Error, ExpectedCovariance) { class B {}; class D : public B {}; Expected A1(Expected(nullptr)); // Check A1 by converting to bool before assigning to it. (void)!!A1; A1 = Expected(nullptr); // Check A1 again before destruction. (void)!!A1; Expected> A2(Expected>(nullptr)); // Check A2 by converting to bool before assigning to it. (void)!!A2; A2 = Expected>(nullptr); // Check A2 again before destruction. (void)!!A2; } // Test that the ExitOnError utility works as expected. TEST(Error, CantFailSuccess) { cantFail(Error::success()); int X = cantFail(Expected(42)); EXPECT_EQ(X, 42) << "Expected value modified by cantFail"; int Dummy = 42; int &Y = cantFail(Expected(Dummy)); EXPECT_EQ(&Dummy, &Y) << "Reference mangled by cantFail"; } // Test that cantFail results in a crash if you pass it a failure value. TEST(Error, CantFailDeath) { EXPECT_DEATH(cantFail(make_error("foo")), "") << "cantFail(Error) did not cause an abort for failure value"; EXPECT_DEATH(cantFail(Expected(make_error("foo"))), "") << "cantFail(Expected) did not cause an abort for failure value"; }