mirror of
https://github.com/RPCSX/llvm.git
synced 2025-04-07 10:42:16 +00:00

This patch introduces the Error classs for lightweight, structured, recoverable error handling. It includes utilities for creating, manipulating and handling errors. The scheme is similar to exceptions, in that errors are described with user-defined types. Unlike exceptions however, errors are represented as ordinary return types in the API (similar to the way std::error_code is used). For usage notes see the LLVM programmer's manual, and the Error.h header. Usage examples can be found in unittests/Support/ErrorTest.cpp. Many thanks to David Blaikie, Mehdi Amini, Kevin Enderby and others on the llvm-dev and llvm-commits lists for lots of discussion and review. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@263609 91177308-0d34-0410-b5e6-96231b3b80d8
459 lines
14 KiB
C++
459 lines
14 KiB
C++
//===----- unittests/ErrorTest.cpp - Error.h tests ------------------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/Errc.h"
|
|
#include "gtest/gtest.h"
|
|
#include <memory>
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
|
|
// Test:
|
|
//
|
|
// Constructing success.
|
|
// - Silent acceptance of tested success.
|
|
// - Programmatic error on untested success.
|
|
//
|
|
// Custom error class.
|
|
// - Creation of a custom error class with a default base class.
|
|
// - Handling of a custom error class with a default base class.
|
|
// - Handler type deduction.
|
|
// - Failure to handle a custom error class.
|
|
// - Isa testing of a custom error class.
|
|
//
|
|
// - Creation of a custom error class with a custom base class.
|
|
// - Handling of a custom error class with a default base class.
|
|
// - Correct shadowing of handlers.
|
|
//
|
|
// Utility functions:
|
|
// - join_errors to defer errors.
|
|
// - consume_error to consume a "safe" error without any output.
|
|
// - handleAllUnhandledErrors to assert that all errors are handled.
|
|
// - logAllUnhandledErrors to log errors to a stream.
|
|
//
|
|
// Expected tests:
|
|
// - Expected<T> with T.
|
|
// - Expected<T> with Error.
|
|
// - Failure to handle an Expected<T> in failure mode.
|
|
// - Error extraction (Expected -> Error).
|
|
//
|
|
// std::error_code:
|
|
// - std::error_code to Error in success mode.
|
|
// - std::error_code to Error (ECError) in failure mode.
|
|
// - Error to std::error_code in success mode.
|
|
// - Error (ECError) to std::error_code in failure mode.
|
|
|
|
// 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() << "}";
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
// 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() << "}";
|
|
}
|
|
|
|
protected:
|
|
int ExtraInfo;
|
|
};
|
|
|
|
// Verify that success values that are checked (i.e. cast to 'bool') are
|
|
// destructed without error, and that unchecked success values cause an
|
|
// abort.
|
|
TEST(Error, CheckSuccess) {
|
|
// Test checked success.
|
|
{
|
|
Error E;
|
|
EXPECT_FALSE(E) << "Unexpected error while testing Error 'Success'";
|
|
}
|
|
|
|
// Test unchecked success.
|
|
// Test runs in debug mode only.
|
|
#ifndef NDEBUG
|
|
{
|
|
auto DropUncheckedSuccess = []() { Error E; };
|
|
EXPECT_DEATH(DropUncheckedSuccess(),
|
|
"Program aborted due to an unhandled Error:")
|
|
<< "Unchecked Error Succes value did not cause abort()";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static Error handleCustomError(const CustomError &CE) { return Error(); }
|
|
|
|
static void handleCustomErrorVoid(const CustomError &CE) {}
|
|
|
|
static Error handleCustomErrorUP(std::unique_ptr<CustomError> CE) {
|
|
return Error();
|
|
}
|
|
|
|
static void handleCustomErrorUPVoid(std::unique_ptr<CustomError> CE) {}
|
|
|
|
// Verify creation and handling of custom error classes.
|
|
TEST(Error, CheckCustomErrors) {
|
|
// 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.
|
|
#ifndef NDEBUG
|
|
{
|
|
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 'isA' handling.
|
|
{
|
|
Error E = make_error<CustomError>(1);
|
|
Error F = make_error<CustomSubError>(1, 2);
|
|
|
|
EXPECT_TRUE(E.isA<CustomError>());
|
|
EXPECT_FALSE(E.isA<CustomSubError>());
|
|
EXPECT_TRUE(F.isA<CustomError>());
|
|
EXPECT_TRUE(F.isA<CustomSubError>());
|
|
|
|
consumeError(std::move(E));
|
|
consumeError(std::move(F));
|
|
}
|
|
|
|
// Check that we can handle a custom error.
|
|
{
|
|
int CaughtErrorInfo = 0;
|
|
handleAllErrors(make_error<CustomError>(42), [&](const CustomError &CE) {
|
|
CaughtErrorInfo = CE.getInfo();
|
|
});
|
|
|
|
EXPECT_TRUE(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
|
|
|
|
handleAllErrors(make_error<CustomError>(42), [](const CustomError &CE) {});
|
|
|
|
handleAllErrors(
|
|
make_error<CustomError>(42),
|
|
[](const CustomError &CE) mutable { return Error::success(); });
|
|
|
|
handleAllErrors(make_error<CustomError>(42),
|
|
[](const CustomError &CE) mutable {});
|
|
|
|
handleAllErrors(make_error<CustomError>(42),
|
|
[](CustomError &CE) { return Error::success(); });
|
|
|
|
handleAllErrors(make_error<CustomError>(42), [](CustomError &CE) {});
|
|
|
|
handleAllErrors(make_error<CustomError>(42),
|
|
[](CustomError &CE) mutable { return Error::success(); });
|
|
|
|
handleAllErrors(make_error<CustomError>(42), [](CustomError &CE) mutable {});
|
|
|
|
handleAllErrors(
|
|
make_error<CustomError>(42),
|
|
[](std::unique_ptr<CustomError> CE) { return Error::success(); });
|
|
|
|
handleAllErrors(make_error<CustomError>(42),
|
|
[](std::unique_ptr<CustomError> CE) {});
|
|
|
|
handleAllErrors(
|
|
make_error<CustomError>(42),
|
|
[](std::unique_ptr<CustomError> CE) mutable { 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);
|
|
|
|
// Check that we can handle a custom error with a custom base class.
|
|
{
|
|
int CaughtErrorInfo = 0;
|
|
int CaughtErrorExtraInfo = 0;
|
|
handleAllErrors(make_error<CustomSubError>(42, 7),
|
|
[&](const CustomSubError &SE) {
|
|
CaughtErrorInfo = SE.getInfo();
|
|
CaughtErrorExtraInfo = SE.getExtraInfo();
|
|
});
|
|
|
|
EXPECT_TRUE(CaughtErrorInfo == 42 && CaughtErrorExtraInfo == 7)
|
|
<< "Wrong result from CustomSubError handler";
|
|
}
|
|
|
|
// Check that we trigger only the first handler that applies.
|
|
{
|
|
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_TRUE(CaughtErrorInfo == 42 && CaughtErrorExtraInfo == 7 &&
|
|
DummyInfo == 0)
|
|
<< "Activated the wrong Error handler(s)";
|
|
}
|
|
|
|
// Check that general handlers shadow specific ones.
|
|
{
|
|
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_TRUE(CaughtErrorInfo = 42 && DummyInfo == 0 && DummyExtraInfo == 0)
|
|
<< "General Error handler did not shadow specific handler";
|
|
}
|
|
}
|
|
|
|
// Test utility functions.
|
|
TEST(Error, CheckErrorUtilities) {
|
|
|
|
// Test joinErrors
|
|
{
|
|
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_TRUE(CustomErrorInfo1 == 7 && CustomErrorInfo2 == 42 &&
|
|
CustomErrorExtraInfo == 7)
|
|
<< "Failed handling compound Error.";
|
|
}
|
|
|
|
// Test consumeError for both success and error cases.
|
|
{
|
|
Error E;
|
|
consumeError(std::move(E));
|
|
}
|
|
{
|
|
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.
|
|
#ifndef NDEBUG
|
|
{
|
|
auto FailToHandle = []() {
|
|
handleAllErrors(make_error<CustomError>(7),
|
|
[&](const CustomSubError &SE) {
|
|
errs() << "This should never be called";
|
|
exit(1);
|
|
});
|
|
};
|
|
|
|
EXPECT_DEATH(FailToHandle(), "Program aborted due to an unhandled Error:")
|
|
<< "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.
|
|
#ifndef NDEBUG
|
|
{
|
|
auto ReturnErrorFromHandler = []() {
|
|
handleAllErrors(make_error<CustomError>(7),
|
|
[&](std::unique_ptr<CustomSubError> SE) {
|
|
return Error(std::move(SE));
|
|
});
|
|
};
|
|
|
|
EXPECT_DEATH(ReturnErrorFromHandler(),
|
|
"Program aborted due to an unhandled Error:")
|
|
<< " Error returned from handler in handleAllErrors call did not "
|
|
"cause abort()";
|
|
}
|
|
#endif
|
|
|
|
// Test that we can return values from handleErrors.
|
|
{
|
|
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 Expected behavior.
|
|
TEST(Error, CheckExpected) {
|
|
|
|
// Check that non-errors convert to 'true'.
|
|
{
|
|
Expected<int> A = 7;
|
|
EXPECT_TRUE(!!A)
|
|
<< "Expected with non-error value doesn't convert to 'true'";
|
|
}
|
|
|
|
// Check that non-error values are accessible via operator*.
|
|
{
|
|
Expected<int> A = 7;
|
|
EXPECT_EQ(*A, 7) << "Incorrect Expected non-error value";
|
|
}
|
|
|
|
// Check that errors convert to 'false'.
|
|
{
|
|
Expected<int> A = make_error<CustomError>(42);
|
|
EXPECT_FALSE(!!A) << "Expected with error value doesn't convert to 'false'";
|
|
consumeError(A.takeError());
|
|
}
|
|
|
|
// Check that error values are accessible via takeError().
|
|
{
|
|
Expected<int> A = make_error<CustomError>(42);
|
|
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.
|
|
#ifndef NDEBUG
|
|
{
|
|
Expected<int> A = make_error<CustomError>(42);
|
|
EXPECT_DEATH(*A, "\\(!HasError && \"Cannot get value "
|
|
"when an error exists!\"\\)")
|
|
<< "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.
|
|
#ifndef NDEBUG
|
|
EXPECT_DEATH({ Expected<int> A = make_error<CustomError>(42); },
|
|
"Program aborted due to an unhandled Error:")
|
|
<< "Unchecked Expected<T> failure value did not cause an abort()";
|
|
#endif
|
|
|
|
// Test covariance of Expected.
|
|
{
|
|
class B {};
|
|
class D : public B {};
|
|
|
|
Expected<B *> A1(Expected<D *>(nullptr));
|
|
A1 = Expected<D *>(nullptr);
|
|
|
|
Expected<std::unique_ptr<B>> A2(Expected<std::unique_ptr<D>>(nullptr));
|
|
A2 = Expected<std::unique_ptr<D>>(nullptr);
|
|
}
|
|
}
|
|
|
|
TEST(Error, ECError) {
|
|
|
|
// 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";
|
|
}
|
|
|
|
} // end anon namespace
|