| //===----- unittests/ErrorTest.cpp - Error.h tests ------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/Support/Error.h" |
| #include "llvm-c/Error.h" |
| |
| #include "llvm/ADT/Twine.h" |
| #include "llvm/Support/Errc.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gtest/gtest-spi.h" |
| #include "gtest/gtest.h" |
| #include <memory> |
| |
| using namespace llvm; |
| |
| namespace { |
| |
| // Custom error class with a default base class and some random 'info' attached. |
| class CustomError : public ErrorInfo<CustomError> { |
| public: |
| // Create an error with some info attached. |
| CustomError(int Info) : Info(Info) {} |
| |
| // Get the info attached to this error. |
| int getInfo() const { return Info; } |
| |
| // Log this error to a stream. |
| void log(raw_ostream &OS) const override { |
| OS << "CustomError {" << getInfo() << "}"; |
| } |
| |
| std::error_code convertToErrorCode() const override { |
| llvm_unreachable("CustomError doesn't support ECError conversion"); |
| } |
| |
| // Used by ErrorInfo::classID. |
| static char ID; |
| |
| protected: |
| // This error is subclassed below, but we can't use inheriting constructors |
| // yet, so we can't propagate the constructors through ErrorInfo. Instead |
| // we have to have a default constructor and have the subclass initialize all |
| // fields. |
| CustomError() : Info(0) {} |
| |
| int Info; |
| }; |
| |
| char CustomError::ID = 0; |
| |
| // Custom error class with a custom base class and some additional random |
| // 'info'. |
| class CustomSubError : public ErrorInfo<CustomSubError, CustomError> { |
| public: |
| // Create a sub-error with some info attached. |
| CustomSubError(int Info, int ExtraInfo) : ExtraInfo(ExtraInfo) { |
| this->Info = Info; |
| } |
| |
| // Get the extra info attached to this error. |
| int getExtraInfo() const { return ExtraInfo; } |
| |
| // Log this error to a stream. |
| void log(raw_ostream &OS) const override { |
| OS << "CustomSubError { " << getInfo() << ", " << getExtraInfo() << "}"; |
| } |
| |
| std::error_code convertToErrorCode() const override { |
| llvm_unreachable("CustomSubError doesn't support ECError conversion"); |
| } |
| |
| // Used by ErrorInfo::classID. |
| static char ID; |
| |
| protected: |
| int ExtraInfo; |
| }; |
| |
| char CustomSubError::ID = 0; |
| |
| static Error handleCustomError(const CustomError &CE) { |
| return Error::success(); |
| } |
| |
| static void handleCustomErrorVoid(const CustomError &CE) {} |
| |
| static Error handleCustomErrorUP(std::unique_ptr<CustomError> CE) { |
| return Error::success(); |
| } |
| |
| static void handleCustomErrorUPVoid(std::unique_ptr<CustomError> CE) {} |
| |
| // Test that success values implicitly convert to false, and don't cause crashes |
| // once they've been implicitly converted. |
| TEST(Error, CheckedSuccess) { |
| Error E = Error::success(); |
| EXPECT_FALSE(E) << "Unexpected error while testing Error 'Success'"; |
| } |
| |
| // Test that unchecked success values cause an abort. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, UncheckedSuccess) { |
| EXPECT_DEATH({ Error E = Error::success(); }, |
| "Program aborted due to an unhandled Error:") |
| << "Unchecked Error Succes value did not cause abort()"; |
| } |
| #endif |
| |
| // ErrorAsOutParameter tester. |
| 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. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, ErrorAsOutParameterUnchecked) { |
| EXPECT_DEATH({ Error E = Error::success(); errAsOutParamHelper(E); }, |
| "Program aborted due to an unhandled Error:") |
| << "ErrorAsOutParameter did not clear the checked flag on destruction."; |
| } |
| #endif |
| |
| // Check that we abort on unhandled failure cases. (Force conversion to bool |
| // to make sure that we don't accidentally treat checked errors as handled). |
| // Test runs in debug mode only. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, UncheckedError) { |
| auto DropUnhandledError = []() { |
| Error E = make_error<CustomError>(42); |
| (void)!E; |
| }; |
| EXPECT_DEATH(DropUnhandledError(), |
| "Program aborted due to an unhandled Error:") |
| << "Unhandled Error failure value did not cause abort()"; |
| } |
| #endif |
| |
| // Check 'Error::isA<T>' method handling. |
| TEST(Error, IsAHandling) { |
| // Check 'isA' handling. |
| Error E = make_error<CustomError>(1); |
| Error F = make_error<CustomSubError>(1, 2); |
| Error G = Error::success(); |
| |
| EXPECT_TRUE(E.isA<CustomError>()); |
| EXPECT_FALSE(E.isA<CustomSubError>()); |
| EXPECT_TRUE(F.isA<CustomError>()); |
| EXPECT_TRUE(F.isA<CustomSubError>()); |
| EXPECT_FALSE(G.isA<CustomError>()); |
| |
| consumeError(std::move(E)); |
| consumeError(std::move(F)); |
| consumeError(std::move(G)); |
| } |
| |
| // Check that we can handle a custom error. |
| TEST(Error, HandleCustomError) { |
| int CaughtErrorInfo = 0; |
| handleAllErrors(make_error<CustomError>(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<Err>) |
| // void (unique_ptr<Err>) |
| // Error (unique_ptr<Err>) mutable |
| // void (unique_ptr<Err>) mutable |
| TEST(Error, HandlerTypeDeduction) { |
| |
| handleAllErrors(make_error<CustomError>(42), [](const CustomError &CE) {}); |
| |
| handleAllErrors( |
| make_error<CustomError>(42), |
| [](const CustomError &CE) mutable -> Error { return Error::success(); }); |
| |
| handleAllErrors(make_error<CustomError>(42), |
| [](const CustomError &CE) mutable {}); |
| |
| handleAllErrors(make_error<CustomError>(42), |
| [](CustomError &CE) -> Error { return Error::success(); }); |
| |
| handleAllErrors(make_error<CustomError>(42), [](CustomError &CE) {}); |
| |
| handleAllErrors(make_error<CustomError>(42), |
| [](CustomError &CE) mutable -> Error { return Error::success(); }); |
| |
| handleAllErrors(make_error<CustomError>(42), [](CustomError &CE) mutable {}); |
| |
| handleAllErrors( |
| make_error<CustomError>(42), |
| [](std::unique_ptr<CustomError> CE) -> Error { return Error::success(); }); |
| |
| handleAllErrors(make_error<CustomError>(42), |
| [](std::unique_ptr<CustomError> CE) {}); |
| |
| handleAllErrors( |
| make_error<CustomError>(42), |
| [](std::unique_ptr<CustomError> CE) mutable -> Error { return Error::success(); }); |
| |
| handleAllErrors(make_error<CustomError>(42), |
| [](std::unique_ptr<CustomError> CE) mutable {}); |
| |
| // Check that named handlers of type 'Error (const Err&)' work. |
| handleAllErrors(make_error<CustomError>(42), handleCustomError); |
| |
| // Check that named handlers of type 'void (const Err&)' work. |
| handleAllErrors(make_error<CustomError>(42), handleCustomErrorVoid); |
| |
| // Check that named handlers of type 'Error (std::unique_ptr<Err>)' work. |
| handleAllErrors(make_error<CustomError>(42), handleCustomErrorUP); |
| |
| // Check that named handlers of type 'Error (std::unique_ptr<Err>)' work. |
| handleAllErrors(make_error<CustomError>(42), handleCustomErrorUPVoid); |
| } |
| |
| // Test that we can handle errors with custom base classes. |
| TEST(Error, HandleCustomErrorWithCustomBaseClass) { |
| int CaughtErrorInfo = 0; |
| int CaughtErrorExtraInfo = 0; |
| handleAllErrors(make_error<CustomSubError>(42, 7), |
| [&](const CustomSubError &SE) { |
| CaughtErrorInfo = SE.getInfo(); |
| CaughtErrorExtraInfo = SE.getExtraInfo(); |
| }); |
| |
| EXPECT_EQ(CaughtErrorInfo, 42) << "Wrong result from CustomSubError handler"; |
| EXPECT_EQ(CaughtErrorExtraInfo, 7) |
| << "Wrong result from CustomSubError handler"; |
| } |
| |
| // Check that we trigger only the first handler that applies. |
| TEST(Error, FirstHandlerOnly) { |
| int DummyInfo = 0; |
| int CaughtErrorInfo = 0; |
| int CaughtErrorExtraInfo = 0; |
| |
| handleAllErrors(make_error<CustomSubError>(42, 7), |
| [&](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, 7) << "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; |
| int DummyExtraInfo = 0; |
| |
| handleAllErrors( |
| make_error<CustomSubError>(42, 7), |
| [&](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, 0) |
| << "General Error handler did not shadow specific handler"; |
| } |
| |
| // Test joinErrors. |
| TEST(Error, CheckJoinErrors) { |
| int CustomErrorInfo1 = 0; |
| int CustomErrorInfo2 = 0; |
| int CustomErrorExtraInfo = 0; |
| Error E = |
| joinErrors(make_error<CustomError>(7), make_error<CustomSubError>(42, 7)); |
| |
| handleAllErrors(std::move(E), |
| [&](const CustomSubError &SE) { |
| CustomErrorInfo2 = SE.getInfo(); |
| CustomErrorExtraInfo = SE.getExtraInfo(); |
| }, |
| [&](const CustomError &CE) { |
| // Assert that the CustomError instance above is handled |
| // before the |
| // CustomSubError - joinErrors should preserve error |
| // ordering. |
| EXPECT_EQ(CustomErrorInfo2, 0) |
| << "CustomErrorInfo2 should be 0 here. " |
| "joinErrors failed to preserve ordering.\n"; |
| CustomErrorInfo1 = CE.getInfo(); |
| }); |
| |
| EXPECT_EQ(CustomErrorInfo1, 7) << "Failed handling compound Error."; |
| EXPECT_EQ(CustomErrorInfo2, 42) << "Failed handling compound Error."; |
| EXPECT_EQ(CustomErrorExtraInfo, 7) << "Failed handling compound Error."; |
| |
| // Test appending a single item to a list. |
| { |
| int Sum = 0; |
| handleAllErrors( |
| joinErrors( |
| joinErrors(make_error<CustomError>(7), |
| make_error<CustomError>(7)), |
| make_error<CustomError>(7)), |
| [&](const CustomError &CE) { |
| Sum += CE.getInfo(); |
| }); |
| EXPECT_EQ(Sum, 21) << "Failed to correctly append error to error list."; |
| } |
| |
| // Test prepending a single item to a list. |
| { |
| int Sum = 0; |
| handleAllErrors( |
| joinErrors( |
| make_error<CustomError>(7), |
| joinErrors(make_error<CustomError>(7), |
| make_error<CustomError>(7))), |
| [&](const CustomError &CE) { |
| Sum += CE.getInfo(); |
| }); |
| EXPECT_EQ(Sum, 21) << "Failed to correctly prepend error to error list."; |
| } |
| |
| // Test concatenating two error lists. |
| { |
| int Sum = 0; |
| handleAllErrors( |
| joinErrors( |
| joinErrors( |
| make_error<CustomError>(7), |
| make_error<CustomError>(7)), |
| joinErrors( |
| make_error<CustomError>(7), |
| make_error<CustomError>(7))), |
| [&](const CustomError &CE) { |
| Sum += CE.getInfo(); |
| }); |
| EXPECT_EQ(Sum, 28) << "Failed to correctly concatenate error lists."; |
| } |
| } |
| |
| // Test that we can consume success values. |
| TEST(Error, ConsumeSuccess) { |
| Error E = Error::success(); |
| consumeError(std::move(E)); |
| } |
| |
| TEST(Error, ConsumeError) { |
| Error E = make_error<CustomError>(7); |
| consumeError(std::move(E)); |
| } |
| |
| // Test that handleAllUnhandledErrors crashes if an error is not caught. |
| // Test runs in debug mode only. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, FailureToHandle) { |
| auto FailToHandle = []() { |
| handleAllErrors(make_error<CustomError>(7), [&](const CustomSubError &SE) { |
| errs() << "This should never be called"; |
| exit(1); |
| }); |
| }; |
| |
| EXPECT_DEATH(FailToHandle(), |
| "Failure value returned from cantFail wrapped call\n" |
| "CustomError \\{7\\}") |
| << "Unhandled Error in handleAllErrors call did not cause an " |
| "abort()"; |
| } |
| #endif |
| |
| // Test that handleAllUnhandledErrors crashes if an error is returned from a |
| // handler. |
| // Test runs in debug mode only. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, FailureFromHandler) { |
| auto ReturnErrorFromHandler = []() { |
| handleAllErrors(make_error<CustomError>(7), |
| [&](std::unique_ptr<CustomSubError> SE) { |
| return Error(std::move(SE)); |
| }); |
| }; |
| |
| EXPECT_DEATH(ReturnErrorFromHandler(), |
| "Failure value returned from cantFail wrapped call\n" |
| "CustomError \\{7\\}") |
| << " Error returned from handler in handleAllErrors call did not " |
| "cause abort()"; |
| } |
| #endif |
| |
| // Test that we can return values from handleErrors. |
| TEST(Error, CatchErrorFromHandler) { |
| int ErrorInfo = 0; |
| |
| Error E = handleErrors( |
| make_error<CustomError>(7), |
| [&](std::unique_ptr<CustomError> CE) { return Error(std::move(CE)); }); |
| |
| handleAllErrors(std::move(E), |
| [&](const CustomError &CE) { ErrorInfo = CE.getInfo(); }); |
| |
| EXPECT_EQ(ErrorInfo, 7) |
| << "Failed to handle Error returned from handleErrors."; |
| } |
| |
| TEST(Error, StringError) { |
| std::string Msg; |
| raw_string_ostream S(Msg); |
| logAllUnhandledErrors( |
| make_error<StringError>("foo" + Twine(42), inconvertibleErrorCode()), S); |
| EXPECT_EQ(S.str(), "foo42\n") << "Unexpected StringError log result"; |
| |
| auto EC = |
| errorToErrorCode(make_error<StringError>("", errc::invalid_argument)); |
| EXPECT_EQ(EC, errc::invalid_argument) |
| << "Failed to convert StringError to error_code."; |
| } |
| |
| TEST(Error, createStringError) { |
| static const char *Bar = "bar"; |
| static const std::error_code EC = errc::invalid_argument; |
| std::string Msg; |
| raw_string_ostream S(Msg); |
| logAllUnhandledErrors(createStringError(EC, "foo%s%d0x%" PRIx8, Bar, 1, 0xff), |
| S); |
| EXPECT_EQ(S.str(), "foobar10xff\n") |
| << "Unexpected createStringError() log result"; |
| |
| S.flush(); |
| Msg.clear(); |
| logAllUnhandledErrors(createStringError(EC, Bar), S); |
| EXPECT_EQ(S.str(), "bar\n") |
| << "Unexpected createStringError() (overloaded) log result"; |
| |
| S.flush(); |
| Msg.clear(); |
| auto Res = errorToErrorCode(createStringError(EC, "foo%s", Bar)); |
| EXPECT_EQ(Res, EC) |
| << "Failed to convert createStringError() result to error_code."; |
| } |
| |
| // Test that the ExitOnError utility works as expected. |
| TEST(ErrorDeathTest, ExitOnError) { |
| ExitOnError ExitOnErr; |
| ExitOnErr.setBanner("Error in tool:"); |
| ExitOnErr.setExitCodeMapper([](const Error &E) { |
| if (E.isA<CustomSubError>()) |
| return 2; |
| return 1; |
| }); |
| |
| // Make sure we don't bail on success. |
| ExitOnErr(Error::success()); |
| EXPECT_EQ(ExitOnErr(Expected<int>(7)), 7) |
| << "exitOnError returned an invalid value for Expected"; |
| |
| int A = 7; |
| int &B = ExitOnErr(Expected<int&>(A)); |
| EXPECT_EQ(&A, &B) << "ExitOnError failed to propagate reference"; |
| |
| // Exit tests. |
| EXPECT_EXIT(ExitOnErr(make_error<CustomError>(7)), |
| ::testing::ExitedWithCode(1), "Error in tool:") |
| << "exitOnError returned an unexpected error result"; |
| |
| EXPECT_EXIT(ExitOnErr(Expected<int>(make_error<CustomSubError>(0, 0))), |
| ::testing::ExitedWithCode(2), "Error in tool:") |
| << "exitOnError returned an unexpected error result"; |
| } |
| |
| // Test that the ExitOnError utility works as expected. |
| TEST(Error, CantFailSuccess) { |
| cantFail(Error::success()); |
| |
| int X = cantFail(Expected<int>(42)); |
| EXPECT_EQ(X, 42) << "Expected value modified by cantFail"; |
| |
| int Dummy = 42; |
| int &Y = cantFail(Expected<int&>(Dummy)); |
| EXPECT_EQ(&Dummy, &Y) << "Reference mangled by cantFail"; |
| } |
| |
| // Test that cantFail results in a crash if you pass it a failure value. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG) |
| TEST(Error, CantFailDeath) { |
| EXPECT_DEATH(cantFail(make_error<StringError>("Original error message", |
| inconvertibleErrorCode()), |
| "Cantfail call failed"), |
| "Cantfail call failed\n" |
| "Original error message") |
| << "cantFail(Error) did not cause an abort for failure value"; |
| |
| EXPECT_DEATH( |
| { |
| auto IEC = inconvertibleErrorCode(); |
| int X = cantFail(Expected<int>(make_error<StringError>("foo", IEC))); |
| (void)X; |
| }, |
| "Failure value returned from cantFail wrapped call") |
| << "cantFail(Expected<int>) did not cause an abort for failure value"; |
| } |
| #endif |
| |
| |
| // Test Checked Expected<T> in success mode. |
| TEST(Error, CheckedExpectedInSuccessMode) { |
| Expected<int> 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<int&> B = A; |
| // 'Check' B. |
| (void)!!B; |
| int &C = *B; |
| EXPECT_EQ(&A, &C) << "Expected failed to propagate reference"; |
| } |
| |
| // Test Unchecked Expected<T> in success mode. |
| // We expect this to blow up the same way Error would. |
| // Test runs in debug mode only. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, UncheckedExpectedInSuccessModeDestruction) { |
| EXPECT_DEATH({ Expected<int> A = 7; }, |
| "Expected<T> must be checked before access or destruction.") |
| << "Unchecked Expected<T> success value did not cause an abort()."; |
| } |
| #endif |
| |
| // Test Unchecked Expected<T> in success mode. |
| // We expect this to blow up the same way Error would. |
| // Test runs in debug mode only. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, UncheckedExpectedInSuccessModeAccess) { |
| EXPECT_DEATH( |
| { |
| const Expected<int> A = 7; |
| *A; |
| }, |
| "Expected<T> must be checked before access or destruction.") |
| << "Unchecked Expected<T> success value did not cause an abort()."; |
| } |
| #endif |
| |
| // Test Unchecked Expected<T> in success mode. |
| // We expect this to blow up the same way Error would. |
| // Test runs in debug mode only. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, UncheckedExpectedInSuccessModeAssignment) { |
| EXPECT_DEATH( |
| { |
| Expected<int> A = 7; |
| A = 7; |
| }, |
| "Expected<T> must be checked before access or destruction.") |
| << "Unchecked Expected<T> success value did not cause an abort()."; |
| } |
| #endif |
| |
| // Test Expected<T> in failure mode. |
| TEST(Error, ExpectedInFailureMode) { |
| Expected<int> A = make_error<CustomError>(42); |
| EXPECT_FALSE(!!A) << "Expected with error value doesn't convert to 'false'"; |
| Error E = A.takeError(); |
| EXPECT_TRUE(E.isA<CustomError>()) << "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. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, AccessExpectedInFailureMode) { |
| Expected<int> A = make_error<CustomError>(42); |
| EXPECT_DEATH(*A, "Expected<T> must be checked before access or destruction.") |
| << "Incorrect Expected error value"; |
| consumeError(A.takeError()); |
| } |
| #endif |
| |
| // Check that an Expected instance with an error triggers an abort if |
| // unhandled. |
| // Test runs in debug mode only. |
| #if LLVM_ENABLE_ABI_BREAKING_CHECKS |
| TEST(Error, UnhandledExpectedInFailureMode) { |
| EXPECT_DEATH({ Expected<int> A = make_error<CustomError>(42); }, |
| "Expected<T> must be checked before access or destruction.") |
| << "Unchecked Expected<T> failure value did not cause an abort()"; |
| } |
| #endif |
| |
| // Test covariance of Expected. |
| TEST(Error, ExpectedCovariance) { |
| class B {}; |
| class D : public B {}; |
| |
| Expected<B *> A1(Expected<D *>(nullptr)); |
| // Check A1 by converting to bool before assigning to it. |
| (void)!!A1; |
| A1 = Expected<D *>(nullptr); |
| // Check A1 again before destruction. |
| (void)!!A1; |
| |
| Expected<std::unique_ptr<B>> A2(Expected<std::unique_ptr<D>>(nullptr)); |
| // Check A2 by converting to bool before assigning to it. |
| (void)!!A2; |
| A2 = Expected<std::unique_ptr<D>>(nullptr); |
| // Check A2 again before destruction. |
| (void)!!A2; |
| } |
| |
| // Test that handleExpected just returns success values. |
| TEST(Error, HandleExpectedSuccess) { |
| auto ValOrErr = |
| handleExpected(Expected<int>(42), |
| []() { return Expected<int>(43); }); |
| EXPECT_TRUE(!!ValOrErr) |
| << "handleExpected should have returned a success value here"; |
| EXPECT_EQ(*ValOrErr, 42) |
| << "handleExpected should have returned the original success value here"; |
| } |
| |
| enum FooStrategy { Aggressive, Conservative }; |
| |
| static Expected<int> foo(FooStrategy S) { |
| if (S == Aggressive) |
| return make_error<CustomError>(7); |
| return 42; |
| } |
| |
| // Test that handleExpected invokes the error path if errors are not handled. |
| TEST(Error, HandleExpectedUnhandledError) { |
| // foo(Aggressive) should return a CustomError which should pass through as |
| // there is no handler for CustomError. |
| auto ValOrErr = |
| handleExpected( |
| foo(Aggressive), |
| []() { return foo(Conservative); }); |
| |
| EXPECT_FALSE(!!ValOrErr) |
| << "handleExpected should have returned an error here"; |
| auto Err = ValOrErr.takeError(); |
| EXPECT_TRUE(Err.isA<CustomError>()) |
| << "handleExpected should have returned the CustomError generated by " |
| "foo(Aggressive) here"; |
| consumeError(std::move(Err)); |
| } |
| |
| // Test that handleExpected invokes the fallback path if errors are handled. |
| TEST(Error, HandleExpectedHandledError) { |
| // foo(Aggressive) should return a CustomError which should handle triggering |
| // the fallback path. |
| auto ValOrErr = |
| handleExpected( |
| foo(Aggressive), |
| []() { return foo(Conservative); }, |
| [](const CustomError&) { /* do nothing */ }); |
| |
| EXPECT_TRUE(!!ValOrErr) |
| << "handleExpected should have returned a success value here"; |
| EXPECT_EQ(*ValOrErr, 42) |
| << "handleExpected returned the wrong success value"; |
| } |
| |
| TEST(Error, ErrorCodeConversions) { |
| // Round-trip a success value to check that it converts correctly. |
| EXPECT_EQ(errorToErrorCode(errorCodeToError(std::error_code())), |
| std::error_code()) |
| << "std::error_code() should round-trip via Error conversions"; |
| |
| // Round-trip an error value to check that it converts correctly. |
| EXPECT_EQ(errorToErrorCode(errorCodeToError(errc::invalid_argument)), |
| errc::invalid_argument) |
| << "std::error_code error value should round-trip via Error " |
| "conversions"; |
| |
| // Round-trip a success value through ErrorOr/Expected to check that it |
| // converts correctly. |
| { |
| auto Orig = ErrorOr<int>(42); |
| auto RoundTripped = |
| expectedToErrorOr(errorOrToExpected(ErrorOr<int>(42))); |
| EXPECT_EQ(*Orig, *RoundTripped) |
| << "ErrorOr<T> success value should round-trip via Expected<T> " |
| "conversions."; |
| } |
| |
| // Round-trip a failure value through ErrorOr/Expected to check that it |
| // converts correctly. |
| { |
| auto Orig = ErrorOr<int>(errc::invalid_argument); |
| auto RoundTripped = |
| expectedToErrorOr( |
| errorOrToExpected(ErrorOr<int>(errc::invalid_argument))); |
| EXPECT_EQ(Orig.getError(), RoundTripped.getError()) |
| << "ErrorOr<T> failure value should round-trip via Expected<T> " |
| "conversions."; |
| } |
| } |
| |
| // Test that error messages work. |
| TEST(Error, ErrorMessage) { |
| EXPECT_EQ(toString(Error::success()), ""); |
| |
| Error E1 = make_error<CustomError>(0); |
| EXPECT_EQ(toString(std::move(E1)), "CustomError {0}"); |
| |
| Error E2 = make_error<CustomError>(0); |
| handleAllErrors(std::move(E2), [](const CustomError &CE) { |
| EXPECT_EQ(CE.message(), "CustomError {0}"); |
| }); |
| |
| Error E3 = joinErrors(make_error<CustomError>(0), make_error<CustomError>(1)); |
| EXPECT_EQ(toString(std::move(E3)), "CustomError {0}\n" |
| "CustomError {1}"); |
| } |
| |
| TEST(Error, Stream) { |
| { |
| Error OK = Error::success(); |
| std::string Buf; |
| llvm::raw_string_ostream S(Buf); |
| S << OK; |
| EXPECT_EQ("success", S.str()); |
| consumeError(std::move(OK)); |
| } |
| { |
| Error E1 = make_error<CustomError>(0); |
| std::string Buf; |
| llvm::raw_string_ostream S(Buf); |
| S << E1; |
| EXPECT_EQ("CustomError {0}", S.str()); |
| consumeError(std::move(E1)); |
| } |
| } |
| |
| TEST(Error, SucceededMatcher) { |
| EXPECT_THAT_ERROR(Error::success(), Succeeded()); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_ERROR(make_error<CustomError>(0), Succeeded()), |
| "Expected: succeeded\n Actual: failed (CustomError {0})"); |
| |
| EXPECT_THAT_EXPECTED(Expected<int>(0), Succeeded()); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), |
| Succeeded()), |
| "Expected: succeeded\n Actual: failed (CustomError {0})"); |
| int a = 1; |
| EXPECT_THAT_EXPECTED(Expected<int &>(a), Succeeded()); |
| } |
| |
| TEST(Error, FailedMatcher) { |
| EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed()); |
| EXPECT_NONFATAL_FAILURE(EXPECT_THAT_ERROR(Error::success(), Failed()), |
| "Expected: failed\n Actual: succeeded"); |
| |
| EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed<CustomError>()); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_ERROR(Error::success(), Failed<CustomError>()), |
| "Expected: failed with Error of given type\n Actual: succeeded"); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed<CustomSubError>()), |
| "Error was not of given type"); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_ERROR( |
| joinErrors(make_error<CustomError>(0), make_error<CustomError>(1)), |
| Failed<CustomError>()), |
| "multiple errors"); |
| |
| EXPECT_THAT_ERROR( |
| make_error<CustomError>(0), |
| Failed<CustomError>(testing::Property(&CustomError::getInfo, 0))); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_ERROR( |
| make_error<CustomError>(0), |
| Failed<CustomError>(testing::Property(&CustomError::getInfo, 1))), |
| "Expected: failed with Error of given type and the error is an object " |
| "whose given property is equal to 1\n" |
| " Actual: failed (CustomError {0})"); |
| EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed<ErrorInfoBase>()); |
| |
| EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), Failed()); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(0), Failed()), |
| "Expected: failed\n Actual: succeeded with value 0"); |
| EXPECT_THAT_EXPECTED(Expected<int &>(make_error<CustomError>(0)), Failed()); |
| } |
| |
| TEST(Error, HasValueMatcher) { |
| EXPECT_THAT_EXPECTED(Expected<int>(0), HasValue(0)); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), |
| HasValue(0)), |
| "Expected: succeeded with value (is equal to 0)\n" |
| " Actual: failed (CustomError {0})"); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(1), HasValue(0)), |
| "Expected: succeeded with value (is equal to 0)\n" |
| " Actual: succeeded with value 1, (isn't equal to 0)"); |
| |
| int a = 1; |
| EXPECT_THAT_EXPECTED(Expected<int &>(a), HasValue(testing::Eq(1))); |
| |
| EXPECT_THAT_EXPECTED(Expected<int>(1), HasValue(testing::Gt(0))); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(0), HasValue(testing::Gt(1))), |
| "Expected: succeeded with value (is > 1)\n" |
| " Actual: succeeded with value 0, (isn't > 1)"); |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), |
| HasValue(testing::Gt(1))), |
| "Expected: succeeded with value (is > 1)\n" |
| " Actual: failed (CustomError {0})"); |
| } |
| |
| TEST(Error, FailedWithMessageMatcher) { |
| EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), |
| FailedWithMessage("CustomError {0}")); |
| |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(1)), |
| FailedWithMessage("CustomError {0}")), |
| "Expected: failed with Error whose message has 1 element that is equal " |
| "to \"CustomError {0}\"\n" |
| " Actual: failed (CustomError {1})"); |
| |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(0), |
| FailedWithMessage("CustomError {0}")), |
| "Expected: failed with Error whose message has 1 element that is equal " |
| "to \"CustomError {0}\"\n" |
| " Actual: succeeded with value 0"); |
| |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), |
| FailedWithMessage("CustomError {0}", "CustomError {0}")), |
| "Expected: failed with Error whose message has 2 elements where\n" |
| "element #0 is equal to \"CustomError {0}\",\n" |
| "element #1 is equal to \"CustomError {0}\"\n" |
| " Actual: failed (CustomError {0}), which has 1 element"); |
| |
| EXPECT_NONFATAL_FAILURE( |
| EXPECT_THAT_EXPECTED( |
| Expected<int>(joinErrors(make_error<CustomError>(0), |
| make_error<CustomError>(0))), |
| FailedWithMessage("CustomError {0}")), |
| "Expected: failed with Error whose message has 1 element that is equal " |
| "to \"CustomError {0}\"\n" |
| " Actual: failed (CustomError {0}; CustomError {0}), which has 2 elements"); |
| |
| EXPECT_THAT_ERROR( |
| joinErrors(make_error<CustomError>(0), make_error<CustomError>(0)), |
| FailedWithMessageArray(testing::SizeIs(2))); |
| } |
| |
| TEST(Error, C_API) { |
| EXPECT_THAT_ERROR(unwrap(wrap(Error::success())), Succeeded()) |
| << "Failed to round-trip Error success value via C API"; |
| EXPECT_THAT_ERROR(unwrap(wrap(make_error<CustomError>(0))), |
| Failed<CustomError>()) |
| << "Failed to round-trip Error failure value via C API"; |
| |
| auto Err = |
| wrap(make_error<StringError>("test message", inconvertibleErrorCode())); |
| EXPECT_EQ(LLVMGetErrorTypeId(Err), LLVMGetStringErrorTypeId()) |
| << "Failed to match error type ids via C API"; |
| char *ErrMsg = LLVMGetErrorMessage(Err); |
| EXPECT_STREQ(ErrMsg, "test message") |
| << "Failed to roundtrip StringError error message via C API"; |
| LLVMDisposeErrorMessage(ErrMsg); |
| |
| bool GotCSE = false; |
| bool GotCE = false; |
| handleAllErrors( |
| unwrap(wrap(joinErrors(make_error<CustomSubError>(42, 7), |
| make_error<CustomError>(42)))), |
| [&](CustomSubError &CSE) { |
| GotCSE = true; |
| }, |
| [&](CustomError &CE) { |
| GotCE = true; |
| }); |
| EXPECT_TRUE(GotCSE) << "Failed to round-trip ErrorList via C API"; |
| EXPECT_TRUE(GotCE) << "Failed to round-trip ErrorList via C API"; |
| } |
| |
| TEST(Error, FileErrorTest) { |
| #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST |
| EXPECT_DEATH( |
| { |
| Error S = Error::success(); |
| consumeError(createFileError("file.bin", std::move(S))); |
| }, |
| ""); |
| #endif |
| // Not allowed, would fail at compile-time |
| //consumeError(createFileError("file.bin", ErrorSuccess())); |
| |
| Error E1 = make_error<CustomError>(1); |
| Error FE1 = createFileError("file.bin", std::move(E1)); |
| EXPECT_EQ(toString(std::move(FE1)), "'file.bin': CustomError {1}"); |
| |
| Error E2 = make_error<CustomError>(2); |
| Error FE2 = createFileError("file.bin", std::move(E2)); |
| handleAllErrors(std::move(FE2), [](const FileError &F) { |
| EXPECT_EQ(F.message(), "'file.bin': CustomError {2}"); |
| }); |
| |
| Error E3 = make_error<CustomError>(3); |
| Error FE3 = createFileError("file.bin", std::move(E3)); |
| auto E31 = handleErrors(std::move(FE3), [](std::unique_ptr<FileError> F) { |
| return F->takeError(); |
| }); |
| handleAllErrors(std::move(E31), [](const CustomError &C) { |
| EXPECT_EQ(C.message(), "CustomError {3}"); |
| }); |
| |
| Error FE4 = |
| joinErrors(createFileError("file.bin", make_error<CustomError>(41)), |
| createFileError("file2.bin", make_error<CustomError>(42))); |
| EXPECT_EQ(toString(std::move(FE4)), "'file.bin': CustomError {41}\n" |
| "'file2.bin': CustomError {42}"); |
| |
| Error FE5 = createFileError("", make_error<CustomError>(5)); |
| EXPECT_EQ(toString(std::move(FE5)), "'': CustomError {5}"); |
| |
| Error FE6 = createFileError("unused", make_error<CustomError>(6)); |
| handleAllErrors(std::move(FE6), [](std::unique_ptr<FileError> F) { |
| EXPECT_EQ(F->messageWithoutFileInfo(), "CustomError {6}"); |
| }); |
| } |
| |
| TEST(Error, FileErrorErrorCode) { |
| for (std::error_code EC : { |
| make_error_code(std::errc::not_supported), |
| make_error_code(std::errc::invalid_argument), |
| make_error_code(std::errc::no_such_file_or_directory), |
| }) { |
| EXPECT_EQ(EC, errorToErrorCode( |
| createFileError("file.bin", EC))); |
| EXPECT_EQ(EC, errorToErrorCode( |
| createFileError("file.bin", /*Line=*/5, EC))); |
| EXPECT_EQ(EC, errorToErrorCode( |
| createFileError("file.bin", errorCodeToError(EC)))); |
| EXPECT_EQ(EC, errorToErrorCode( |
| createFileError("file.bin", /*Line=*/5, errorCodeToError(EC)))); |
| } |
| |
| // inconvertibleErrorCode() should be wrapped to avoid a fatal error. |
| EXPECT_EQ( |
| "A file error occurred.", |
| errorToErrorCode(createFileError("file.bin", inconvertibleErrorCode())) |
| .message()); |
| EXPECT_EQ( |
| "A file error occurred.", |
| errorToErrorCode(createFileError("file.bin", /*Line=*/5, inconvertibleErrorCode())) |
| .message()); |
| } |
| |
| enum class test_error_code { |
| unspecified = 1, |
| error_1, |
| error_2, |
| }; |
| |
| } // end anon namespace |
| |
| namespace std { |
| template <> |
| struct is_error_code_enum<test_error_code> : std::true_type {}; |
| } // namespace std |
| |
| namespace { |
| |
| const std::error_category &TErrorCategory(); |
| |
| inline std::error_code make_error_code(test_error_code E) { |
| return std::error_code(static_cast<int>(E), TErrorCategory()); |
| } |
| |
| class TestDebugError : public ErrorInfo<TestDebugError, StringError> { |
| public: |
| using ErrorInfo<TestDebugError, StringError >::ErrorInfo; // inherit constructors |
| TestDebugError(const Twine &S) : ErrorInfo(S, test_error_code::unspecified) {} |
| static char ID; |
| }; |
| |
| class TestErrorCategory : public std::error_category { |
| public: |
| const char *name() const noexcept override { return "error"; } |
| std::string message(int Condition) const override { |
| switch (static_cast<test_error_code>(Condition)) { |
| case test_error_code::unspecified: |
| return "An unknown error has occurred."; |
| case test_error_code::error_1: |
| return "Error 1."; |
| case test_error_code::error_2: |
| return "Error 2."; |
| } |
| llvm_unreachable("Unrecognized test_error_code"); |
| } |
| }; |
| |
| const std::error_category &TErrorCategory() { |
| static TestErrorCategory TestErrCategory; |
| return TestErrCategory; |
| } |
| |
| char TestDebugError::ID; |
| |
| TEST(Error, SubtypeStringErrorTest) { |
| auto E1 = make_error<TestDebugError>(test_error_code::error_1); |
| EXPECT_EQ(toString(std::move(E1)), "Error 1."); |
| |
| auto E2 = make_error<TestDebugError>(test_error_code::error_1, |
| "Detailed information"); |
| EXPECT_EQ(toString(std::move(E2)), "Error 1. Detailed information"); |
| |
| auto E3 = make_error<TestDebugError>(test_error_code::error_2); |
| handleAllErrors(std::move(E3), [](const TestDebugError &F) { |
| EXPECT_EQ(F.message(), "Error 2."); |
| }); |
| |
| auto E4 = joinErrors(make_error<TestDebugError>(test_error_code::error_1, |
| "Detailed information"), |
| make_error<TestDebugError>(test_error_code::error_2)); |
| EXPECT_EQ(toString(std::move(E4)), "Error 1. Detailed information\n" |
| "Error 2."); |
| } |
| |
| static Error createAnyError() { |
| return errorCodeToError(test_error_code::unspecified); |
| } |
| |
| struct MoveOnlyBox { |
| std::optional<int> Box; |
| |
| explicit MoveOnlyBox(int I) : Box(I) {} |
| MoveOnlyBox() = default; |
| MoveOnlyBox(MoveOnlyBox &&) = default; |
| MoveOnlyBox &operator=(MoveOnlyBox &&) = default; |
| |
| MoveOnlyBox(const MoveOnlyBox &) = delete; |
| MoveOnlyBox &operator=(const MoveOnlyBox &) = delete; |
| |
| bool operator==(const MoveOnlyBox &RHS) const { |
| if (bool(Box) != bool(RHS.Box)) |
| return false; |
| return Box ? *Box == *RHS.Box : false; |
| } |
| }; |
| |
| TEST(Error, moveInto) { |
| // Use MoveOnlyBox as the T in Expected<T>. |
| auto make = [](int I) -> Expected<MoveOnlyBox> { return MoveOnlyBox(I); }; |
| auto makeFailure = []() -> Expected<MoveOnlyBox> { return createAnyError(); }; |
| |
| { |
| MoveOnlyBox V; |
| |
| // Failure with no prior value. |
| EXPECT_THAT_ERROR(makeFailure().moveInto(V), Failed()); |
| EXPECT_EQ(std::nullopt, V.Box); |
| |
| // Success with no prior value. |
| EXPECT_THAT_ERROR(make(5).moveInto(V), Succeeded()); |
| EXPECT_EQ(5, V.Box); |
| |
| // Success with an existing value. |
| EXPECT_THAT_ERROR(make(7).moveInto(V), Succeeded()); |
| EXPECT_EQ(7, V.Box); |
| |
| // Failure with an existing value. Might be nice to assign a |
| // default-constructed value in this case, but for now it's being left |
| // alone. |
| EXPECT_THAT_ERROR(makeFailure().moveInto(V), Failed()); |
| EXPECT_EQ(7, V.Box); |
| } |
| |
| // Check that this works with optionals too. |
| { |
| // Same cases as above. |
| std::optional<MoveOnlyBox> MaybeV; |
| EXPECT_THAT_ERROR(makeFailure().moveInto(MaybeV), Failed()); |
| EXPECT_EQ(std::nullopt, MaybeV); |
| |
| EXPECT_THAT_ERROR(make(5).moveInto(MaybeV), Succeeded()); |
| EXPECT_EQ(MoveOnlyBox(5), MaybeV); |
| |
| EXPECT_THAT_ERROR(make(7).moveInto(MaybeV), Succeeded()); |
| EXPECT_EQ(MoveOnlyBox(7), MaybeV); |
| |
| EXPECT_THAT_ERROR(makeFailure().moveInto(MaybeV), Failed()); |
| EXPECT_EQ(MoveOnlyBox(7), MaybeV); |
| } |
| } |
| |
| } // namespace |