162 lines
6.0 KiB
C++
162 lines
6.0 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "common/Result.h"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
using eve::Diagnostic;
|
|
using eve::DiagnosticCode;
|
|
using eve::Result;
|
|
using eve::Severity;
|
|
using eve::Status;
|
|
using eve::StatusCode;
|
|
|
|
static_assert(!std::is_copy_constructible_v<Result<int>>);
|
|
static_assert(!std::is_copy_assignable_v<Result<int>>);
|
|
static_assert(std::is_move_constructible_v<Result<int>>);
|
|
static_assert(!std::is_copy_constructible_v<Result<void>>);
|
|
static_assert(std::is_convertible_v<Result<int>, Result<const int>>);
|
|
static_assert(!std::is_convertible_v<Result<const int>, Result<int>>);
|
|
static_assert(std::is_convertible_v<Result<int*>, Result<const int*>>);
|
|
static_assert(!std::is_convertible_v<Result<const int*>, Result<int*>>);
|
|
static_assert(!std::is_convertible_v<Result<void>, Result<int>>);
|
|
static_assert(!std::is_convertible_v<Result<int>, Result<void>>);
|
|
|
|
TEST_CASE("common.result.successAndObservationAccessors") {
|
|
auto result = Result<int>::success(42);
|
|
|
|
CHECK(result.ok());
|
|
CHECK(result.hasValue());
|
|
CHECK(result.code() == StatusCode::Ok);
|
|
CHECK(result.value() == 42);
|
|
CHECK(result.status().isSuccess());
|
|
CHECK(result.diagnostics().empty());
|
|
CHECK(result.error() == nullptr);
|
|
}
|
|
|
|
TEST_CASE("common.result.failureCarriesStructuredDiagnostic") {
|
|
auto diagnostic = Diagnostic::error(DiagnosticCode::InvalidArgument, "width must be positive", "window.width");
|
|
diagnostic.addDetail("received", "0");
|
|
auto result = Result<int>::failure(std::move(diagnostic));
|
|
|
|
CHECK(!result.ok());
|
|
CHECK(result.code() == StatusCode::Rejected);
|
|
REQUIRE(result.error() != nullptr);
|
|
CHECK(result.error()->code() == DiagnosticCode::InvalidArgument);
|
|
CHECK(result.error()->severity() == Severity::Error);
|
|
CHECK(result.error()->message() == "width must be positive");
|
|
CHECK(result.error()->path() == "window.width");
|
|
REQUIRE(result.error()->details().size() == 1u);
|
|
CHECK(result.error()->details()[0].first == "received");
|
|
CHECK(result.error()->details()[0].second == "0");
|
|
CHECK(result.status().describe() == "rejected: width must be positive [window.width]");
|
|
}
|
|
|
|
TEST_CASE("common.result.ignoreAndExpectAreExplicitConsumption") {
|
|
Result<void>::failure(Diagnostic::error(DiagnosticCode::Failed, "best effort cleanup"))
|
|
.ignore("cleanup is best effort");
|
|
|
|
const int value = Result<int>::success(7).expect("initialization must succeed");
|
|
CHECK(value == 7);
|
|
|
|
CHECK_THROWS((Result<void>::failure(Diagnostic::error(DiagnosticCode::Unsupported, "not available"))
|
|
.expect("feature must be available"),
|
|
0));
|
|
}
|
|
|
|
TEST_CASE("common.result.unobservedDestructionIsCaughtInDebug") {
|
|
#if !defined(ZEROERR_NO_ASSERT)
|
|
CHECK_THROWS((
|
|
[] {
|
|
auto result = Result<int>::success(1);
|
|
(void)result;
|
|
}(),
|
|
0));
|
|
#else
|
|
CHECK(true);
|
|
#endif
|
|
}
|
|
|
|
TEST_CASE("common.result.moveTransfersObservationResponsibility") {
|
|
auto source = Result<std::unique_ptr<int>>::success(std::make_unique<int>(9));
|
|
auto moved = std::move(source);
|
|
REQUIRE(moved.ok());
|
|
REQUIRE(moved.value().get() != nullptr);
|
|
CHECK(*moved.value() == 9);
|
|
|
|
auto assignedSource = Result<int>::success(11);
|
|
auto assignedTarget = Result<int>::success(13);
|
|
CHECK(assignedTarget.ok());
|
|
assignedTarget = std::move(assignedSource);
|
|
CHECK(assignedTarget.value() == 11);
|
|
}
|
|
|
|
TEST_CASE("common.result.moveAssignmentDoesNotOverwriteUncheckedTarget") {
|
|
#if !defined(ZEROERR_NO_ASSERT)
|
|
auto target = Result<int>::success(1);
|
|
auto source = Result<int>::success(2);
|
|
CHECK_THROWS((target = std::move(source), 0));
|
|
target.ignore("retain target after rejected move assignment");
|
|
source.ignore("retain source after rejected move assignment");
|
|
#else
|
|
CHECK(true);
|
|
#endif
|
|
}
|
|
|
|
TEST_CASE("common.result.compositionPreservesFailureStatus") {
|
|
auto success = Result<int>::success(3).andThen(
|
|
[](int value) { return Result<std::string>::success(std::to_string(value + 1)); });
|
|
REQUIRE(success.ok());
|
|
CHECK(success.value() == "4");
|
|
|
|
auto failure = Result<int>::failure(Diagnostic::error(DiagnosticCode::NotFound, "entity not found"));
|
|
auto recovered = std::move(failure).orElse([&](const Status& status) {
|
|
CHECK(status.code() == StatusCode::NotFound);
|
|
return Result<int>::success(99);
|
|
});
|
|
CHECK(recovered.value() == 99);
|
|
}
|
|
|
|
TEST_CASE("common.result.constConversionConstrainsValue") {
|
|
auto makeConstValue = []() -> Result<const int> { return Result<int>::success(4); };
|
|
auto constValue = makeConstValue();
|
|
REQUIRE(constValue.ok());
|
|
CHECK(constValue.value() == 4);
|
|
|
|
int storage = 8;
|
|
auto makeConstPointer = [](int* pointer) -> Result<const int*> { return Result<int*>::success(pointer); };
|
|
auto constPointer = makeConstPointer(&storage);
|
|
REQUIRE(constPointer.ok());
|
|
CHECK(*constPointer.value() == 8);
|
|
|
|
auto assigned = Result<const int>::failure(Diagnostic::error(DiagnosticCode::Failed, "placeholder"));
|
|
CHECK(!assigned.ok());
|
|
assigned = Result<int>::success(11);
|
|
CHECK(assigned.value() == 11);
|
|
assigned = Result<const int>::success(13);
|
|
CHECK(assigned.value() == 13);
|
|
|
|
auto makeConstFailure = []() -> Result<const int> {
|
|
return Result<int>::failure(Diagnostic::error(DiagnosticCode::NotFound, "missing"));
|
|
};
|
|
auto failed = makeConstFailure();
|
|
CHECK(!failed.ok());
|
|
CHECK(failed.code() == StatusCode::NotFound);
|
|
}
|
|
|
|
TEST_CASE("common.result.voidStatusAndBoolCheck") {
|
|
auto result = Result<void>::success(Status::success(StatusCode::Applied));
|
|
CHECK(static_cast<bool>(result));
|
|
CHECK(result.code() == StatusCode::Applied);
|
|
result.value();
|
|
|
|
auto failed = Result<void>::failure(
|
|
Status::failure(StatusCode::Conflict, Diagnostic::error(DiagnosticCode::Conflict, "revision changed")));
|
|
CHECK(!failed);
|
|
REQUIRE(failed.error() != nullptr);
|
|
CHECK(failed.error()->code() == DiagnosticCode::Conflict);
|
|
}
|