Add an Error and ResultOrError<T> type and tests
These types are meant to be used for computations that might but are not expected to fail in backend/, such that the error case can be much slower than the success case. The NXT_TRY and NXT_TRY_RESULT macros are added to help write more concise code that uses Error and ResultOrError.
This commit is contained in:
parent
44b5ca4aa1
commit
1fda980fa6
|
@ -30,6 +30,7 @@ Generate(
|
|||
)
|
||||
target_link_libraries(backend_utils_autogen nxtcpp)
|
||||
target_include_directories(backend_utils_autogen PUBLIC ${GENERATED_DIR})
|
||||
target_include_directories(backend_utils_autogen PRIVATE ${SRC_DIR})
|
||||
|
||||
function(GenerateProcTable backend)
|
||||
Generate(
|
||||
|
@ -342,6 +343,10 @@ list(APPEND BACKEND_SOURCES
|
|||
${BACKEND_DIR}/CommandBufferStateTracker.h
|
||||
${BACKEND_DIR}/Device.cpp
|
||||
${BACKEND_DIR}/Device.h
|
||||
${BACKEND_DIR}/Error.cpp
|
||||
${BACKEND_DIR}/Error.h
|
||||
${BACKEND_DIR}/ErrorData.cpp
|
||||
${BACKEND_DIR}/ErrorData.h
|
||||
${BACKEND_DIR}/Forward.h
|
||||
${BACKEND_DIR}/InputState.cpp
|
||||
${BACKEND_DIR}/InputState.h
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2018 The NXT Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "backend/Error.h"
|
||||
|
||||
#include "backend/ErrorData.h"
|
||||
|
||||
namespace backend {
|
||||
|
||||
ErrorData* MakeError(const char* message, const char* file, const char* function, int line) {
|
||||
ErrorData* error = new ErrorData(message);
|
||||
error->AppendBacktrace(file, function, line);
|
||||
return error;
|
||||
}
|
||||
|
||||
void AppendBacktrace(ErrorData* error, const char* file, const char* function, int line) {
|
||||
error->AppendBacktrace(file, function, line);
|
||||
}
|
||||
|
||||
} // namespace backend
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2018 The NXT Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef BACKEND_ERROR_H_
|
||||
#define BACKEND_ERROR_H_
|
||||
|
||||
#include "common/Result.h"
|
||||
|
||||
namespace backend {
|
||||
|
||||
// This is the content of an error value for MaybeError or ResultOrError, split off to its own
|
||||
// file to avoid having all files including headers like <string> and <vector>
|
||||
class ErrorData;
|
||||
|
||||
// MaybeError and ResultOrError are meant to be used as return value for function that are not
|
||||
// expected to, but might fail. The handling of error is potentially much slower than successes.
|
||||
using MaybeError = Result<void, ErrorData*>;
|
||||
|
||||
template <typename T>
|
||||
using ResultOrError = Result<T, ErrorData*>;
|
||||
|
||||
// Returning a success is done like so:
|
||||
// return {}; // for Error
|
||||
// return SomethingOfTypeT; // for ResultOrError<T>
|
||||
//
|
||||
// Returning an error is done via:
|
||||
// NXT_RETURN_ERROR("My error message");
|
||||
#define NXT_RETURN_ERROR(EXPR) return MakeError(EXPR, __FILE__, __func__, __LINE__)
|
||||
|
||||
#define NXT_CONCAT1(x, y) x##y
|
||||
#define NXT_CONCAT2(x, y) NXT_CONCAT1(x, y)
|
||||
#define NXT_LOCAL_VAR NXT_CONCAT2(_localVar, __LINE__)
|
||||
|
||||
// When Errors aren't handled explicitly, calls to functions returning errors should be
|
||||
// wrapped in an NXT_TRY. It will return the error if any, otherwise keep executing
|
||||
// the current function.
|
||||
#define NXT_TRY(EXPR) \
|
||||
{ \
|
||||
auto NXT_LOCAL_VAR = EXPR; \
|
||||
if (NXT_UNLIKELY(NXT_LOCAL_VAR.IsError())) { \
|
||||
ErrorData* error = NXT_LOCAL_VAR.AcquireError(); \
|
||||
AppendBacktrace(error, __FILE__, __func__, __LINE__); \
|
||||
return {error}; \
|
||||
} \
|
||||
} \
|
||||
for (;;) \
|
||||
break
|
||||
|
||||
// NXT_TRY_ASSIGN is the same as NXT_TRY for ResultOrError and assigns the success value, if
|
||||
// any, to VAR.
|
||||
#define NXT_TRY_ASSIGN(VAR, EXPR) \
|
||||
{ \
|
||||
auto NXT_LOCAL_VAR = EXPR; \
|
||||
if (NXT_UNLIKELY(NXT_LOCAL_VAR.IsError())) { \
|
||||
ErrorData* error = NXT_LOCAL_VAR.AcquireError(); \
|
||||
AppendBacktrace(error, __FILE__, __func__, __LINE__); \
|
||||
return {error}; \
|
||||
} \
|
||||
VAR = NXT_LOCAL_VAR.AcquireSuccess(); \
|
||||
} \
|
||||
for (;;) \
|
||||
break
|
||||
|
||||
// Implementation detail of NXT_TRY and NXT_TRY_ASSIGN's adding to the Error's backtrace.
|
||||
void AppendBacktrace(ErrorData* error, const char* file, const char* function, int line);
|
||||
|
||||
// Implementation detail of NXT_RETURN_ERROR
|
||||
ErrorData* MakeError(const char* message, const char* file, const char* function, int line);
|
||||
|
||||
} // namespace backend
|
||||
|
||||
#endif // BACKEND_ERROR_H_
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2018 The NXT Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "backend/ErrorData.h"
|
||||
|
||||
namespace backend {
|
||||
|
||||
ErrorData::ErrorData() = default;
|
||||
|
||||
ErrorData::ErrorData(std::string message) : mMessage(std::move(message)) {
|
||||
}
|
||||
|
||||
void ErrorData::AppendBacktrace(const char* file, const char* function, int line) {
|
||||
BacktraceRecord record;
|
||||
record.file = file;
|
||||
record.function = function;
|
||||
record.line = line;
|
||||
|
||||
mBacktrace.push_back(std::move(record));
|
||||
}
|
||||
|
||||
const std::string& ErrorData::GetMessage() const {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
const std::vector<ErrorData::BacktraceRecord>& ErrorData::GetBacktrace() const {
|
||||
return mBacktrace;
|
||||
}
|
||||
|
||||
} // namespace backend
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2018 The NXT Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef BACKEND_ERRORDATA_H_
|
||||
#define BACKEND_ERRORDATA_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace backend {
|
||||
|
||||
class ErrorData {
|
||||
public:
|
||||
ErrorData();
|
||||
ErrorData(std::string message);
|
||||
|
||||
struct BacktraceRecord {
|
||||
const char* file;
|
||||
const char* function;
|
||||
int line;
|
||||
};
|
||||
void AppendBacktrace(const char* file, const char* function, int line);
|
||||
|
||||
const std::string& GetMessage() const;
|
||||
const std::vector<BacktraceRecord>& GetBacktrace() const;
|
||||
|
||||
private:
|
||||
std::string mMessage;
|
||||
std::vector<BacktraceRecord> mBacktrace;
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
|
||||
#endif // BACKEND_ERRORDATA_H_
|
|
@ -33,6 +33,7 @@ list(APPEND UNITTEST_SOURCES
|
|||
${UNITTESTS_DIR}/BitSetIteratorTests.cpp
|
||||
${UNITTESTS_DIR}/CommandAllocatorTests.cpp
|
||||
${UNITTESTS_DIR}/EnumClassBitmasksTests.cpp
|
||||
${UNITTESTS_DIR}/ErrorTests.cpp
|
||||
${UNITTESTS_DIR}/MathTests.cpp
|
||||
${UNITTESTS_DIR}/ObjectBaseTests.cpp
|
||||
${UNITTESTS_DIR}/PerStageTests.cpp
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
// Copyright 2018 The NXT Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "backend/Error.h"
|
||||
#include "backend/ErrorData.h"
|
||||
|
||||
using namespace backend;
|
||||
|
||||
namespace {
|
||||
|
||||
int dummySuccess = 0xbeef;
|
||||
const char* dummyErrorMessage = "I am an error message :3";
|
||||
|
||||
// Check returning a success MaybeError with {};
|
||||
TEST(ErrorTests, Error_Success) {
|
||||
auto ReturnSuccess = []() -> MaybeError {
|
||||
return {};
|
||||
};
|
||||
|
||||
MaybeError result = ReturnSuccess();
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
}
|
||||
|
||||
// Check returning an error MaybeError with NXT_RETURN_ERROR
|
||||
TEST(ErrorTests, Error_Error) {
|
||||
auto ReturnError = []() -> MaybeError {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
MaybeError result = ReturnError();
|
||||
ASSERT_TRUE(result.IsError());
|
||||
|
||||
ErrorData* errorData = result.AcquireError();
|
||||
ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
|
||||
delete errorData;
|
||||
}
|
||||
|
||||
// Check returning a success ResultOrError with an implicit conversion
|
||||
TEST(ErrorTests, ResultOrError_Success) {
|
||||
auto ReturnSuccess = []() -> ResultOrError<int*> {
|
||||
return &dummySuccess;
|
||||
};
|
||||
|
||||
ResultOrError<int*> result = ReturnSuccess();
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
ASSERT_EQ(result.AcquireSuccess(), &dummySuccess);
|
||||
}
|
||||
|
||||
// Check returning an error ResultOrError with NXT_RETURN_ERROR
|
||||
TEST(ErrorTests, ResultOrError_Error) {
|
||||
auto ReturnError = []() -> ResultOrError<int*> {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
ResultOrError<int*> result = ReturnError();
|
||||
ASSERT_TRUE(result.IsError());
|
||||
|
||||
ErrorData* errorData = result.AcquireError();
|
||||
ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
|
||||
delete errorData;
|
||||
}
|
||||
|
||||
// Check NXT_TRY handles successes correctly.
|
||||
TEST(ErrorTests, TRY_Success) {
|
||||
auto ReturnSuccess = []() -> MaybeError {
|
||||
return {};
|
||||
};
|
||||
|
||||
// We need to check that NXT_TRY doesn't return on successes
|
||||
bool tryReturned = true;
|
||||
|
||||
auto Try = [ReturnSuccess, &tryReturned]() -> MaybeError {
|
||||
NXT_TRY(ReturnSuccess());
|
||||
tryReturned = false;
|
||||
return {};
|
||||
};
|
||||
|
||||
MaybeError result = Try();
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
ASSERT_FALSE(tryReturned);
|
||||
}
|
||||
|
||||
// Check NXT_TRY handles errors correctly.
|
||||
TEST(ErrorTests, TRY_Error) {
|
||||
auto ReturnError = []() -> MaybeError {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
auto Try = [ReturnError]() -> MaybeError {
|
||||
NXT_TRY(ReturnError());
|
||||
// NXT_TRY should return before this point
|
||||
EXPECT_FALSE(true);
|
||||
return {};
|
||||
};
|
||||
|
||||
MaybeError result = Try();
|
||||
ASSERT_TRUE(result.IsError());
|
||||
|
||||
ErrorData* errorData = result.AcquireError();
|
||||
ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
|
||||
delete errorData;
|
||||
}
|
||||
|
||||
// Check NXT_TRY adds to the backtrace.
|
||||
TEST(ErrorTests, TRY_AddsToBacktrace) {
|
||||
auto ReturnError = []() -> MaybeError {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
auto SingleTry = [ReturnError]() -> MaybeError {
|
||||
NXT_TRY(ReturnError());
|
||||
return {};
|
||||
};
|
||||
|
||||
auto DoubleTry = [SingleTry]() -> MaybeError {
|
||||
NXT_TRY(SingleTry());
|
||||
return {};
|
||||
};
|
||||
|
||||
MaybeError singleResult = SingleTry();
|
||||
ASSERT_TRUE(singleResult.IsError());
|
||||
|
||||
MaybeError doubleResult = DoubleTry();
|
||||
ASSERT_TRUE(doubleResult.IsError());
|
||||
|
||||
ErrorData* singleData = singleResult.AcquireError();
|
||||
ErrorData* doubleData = doubleResult.AcquireError();
|
||||
|
||||
ASSERT_EQ(singleData->GetBacktrace().size() + 1, doubleData->GetBacktrace().size());
|
||||
|
||||
delete singleData;
|
||||
delete doubleData;
|
||||
}
|
||||
|
||||
// Check NXT_TRY_ASSIGN handles successes correctly.
|
||||
TEST(ErrorTests, TRY_RESULT_Success) {
|
||||
auto ReturnSuccess = []() -> ResultOrError<int*> {
|
||||
return &dummySuccess;
|
||||
};
|
||||
|
||||
// We need to check that NXT_TRY doesn't return on successes
|
||||
bool tryReturned = true;
|
||||
|
||||
auto Try = [ReturnSuccess, &tryReturned]() -> ResultOrError<int*> {
|
||||
int* result = nullptr;
|
||||
NXT_TRY_ASSIGN(result, ReturnSuccess());
|
||||
tryReturned = false;
|
||||
|
||||
EXPECT_EQ(result, &dummySuccess);
|
||||
return result;
|
||||
};
|
||||
|
||||
ResultOrError<int*> result = Try();
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
ASSERT_FALSE(tryReturned);
|
||||
ASSERT_EQ(result.AcquireSuccess(), &dummySuccess);
|
||||
}
|
||||
|
||||
// Check NXT_TRY_ASSIGN handles errors correctly.
|
||||
TEST(ErrorTests, TRY_RESULT_Error) {
|
||||
auto ReturnError = []() -> ResultOrError<int*> {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
auto Try = [ReturnError]() -> ResultOrError<int*> {
|
||||
int* result = nullptr;
|
||||
NXT_TRY_ASSIGN(result, ReturnError());
|
||||
(void) result;
|
||||
|
||||
// NXT_TRY should return before this point
|
||||
EXPECT_FALSE(true);
|
||||
return &dummySuccess;
|
||||
};
|
||||
|
||||
ResultOrError<int*> result = Try();
|
||||
ASSERT_TRUE(result.IsError());
|
||||
|
||||
ErrorData* errorData = result.AcquireError();
|
||||
ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
|
||||
delete errorData;
|
||||
}
|
||||
|
||||
// Check NXT_TRY_ASSIGN adds to the backtrace.
|
||||
TEST(ErrorTests, TRY_RESULT_AddsToBacktrace) {
|
||||
auto ReturnError = []() -> ResultOrError<int*> {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
auto SingleTry = [ReturnError]() -> ResultOrError<int*> {
|
||||
NXT_TRY(ReturnError());
|
||||
return &dummySuccess;
|
||||
};
|
||||
|
||||
auto DoubleTry = [SingleTry]() -> ResultOrError<int*> {
|
||||
NXT_TRY(SingleTry());
|
||||
return &dummySuccess;
|
||||
};
|
||||
|
||||
ResultOrError<int*> singleResult = SingleTry();
|
||||
ASSERT_TRUE(singleResult.IsError());
|
||||
|
||||
ResultOrError<int*> doubleResult = DoubleTry();
|
||||
ASSERT_TRUE(doubleResult.IsError());
|
||||
|
||||
ErrorData* singleData = singleResult.AcquireError();
|
||||
ErrorData* doubleData = doubleResult.AcquireError();
|
||||
|
||||
ASSERT_EQ(singleData->GetBacktrace().size() + 1, doubleData->GetBacktrace().size());
|
||||
|
||||
delete singleData;
|
||||
delete doubleData;
|
||||
}
|
||||
|
||||
// Check a ResultOrError can be NXT_TRY_ASSIGNED in a function that returns an Error
|
||||
TEST(ErrorTests, TRY_RESULT_ConversionToError) {
|
||||
auto ReturnError = []() -> ResultOrError<int*> {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
auto Try = [ReturnError]() -> MaybeError {
|
||||
int* result = nullptr;
|
||||
NXT_TRY_ASSIGN(result, ReturnError());
|
||||
(void) result;
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
MaybeError result = Try();
|
||||
ASSERT_TRUE(result.IsError());
|
||||
|
||||
ErrorData* errorData = result.AcquireError();
|
||||
ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
|
||||
delete errorData;
|
||||
}
|
||||
|
||||
// Check a MaybeError can be NXT_TRIED in a function that returns an ResultOrError
|
||||
// Check NXT_TRY handles errors correctly.
|
||||
TEST(ErrorTests, TRY_ConversionToErrorOrResult) {
|
||||
auto ReturnError = []() -> MaybeError {
|
||||
NXT_RETURN_ERROR(dummyErrorMessage);
|
||||
};
|
||||
|
||||
auto Try = [ReturnError]() -> ResultOrError<int*>{
|
||||
NXT_TRY(ReturnError());
|
||||
return &dummySuccess;
|
||||
};
|
||||
|
||||
ResultOrError<int*> result = Try();
|
||||
ASSERT_TRUE(result.IsError());
|
||||
|
||||
ErrorData* errorData = result.AcquireError();
|
||||
ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
|
||||
delete errorData;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
Loading…
Reference in New Issue