Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions tests/beman/expected/expected_hardened.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <catch2/catch_test_macros.hpp>

#include <string>
#include <type_traits>
#include <utility>

using namespace beman::expected;

Expand Down Expand Up @@ -89,6 +91,132 @@ TEST_CASE("hardened: error() on error-state expected<void,int>", "[hardened]") {
CHECK(std::move(ce2).error() == 13);
}

// ---------------------------------------------------------------------------
// Reference specializations
//
// Before this section the hardened build only ever instantiated expected<T, E>
// and expected<void, E> with a value E, so the precondition checks in the
// expected<T&, E> specialization were never compiled at all, and neither the
// primary nor the void specialization was ever instantiated with a reference E.
// The checks themselves are the same `if (!has_val_) TRAP` shape throughout;
// what these add is proof that the guarded bodies still compile and return the
// right thing once T or E is a reference -- in particular that the shallow-const
// observers (T*, T&, E& from a const expected) are unaffected by hardening.
//
// Happy paths only, as above: a violated precondition traps, which cannot be
// observed from inside the process.
// ---------------------------------------------------------------------------

// --- expected<T&, E>: operator-> and operator* (one const overload each) ---

TEST_CASE("hardened: observers on value-state expected<T&,E>", "[hardened]") {
std::string s = "hello";
expected<std::string&, int> e(s);
CHECK(e->size() == 5);
CHECK(*e == "hello");
CHECK(&*e == &s);

// Shallow const: a const expected<T&, E> still hands out a mutable T& / T*.
const expected<std::string&, int> ce(s);
static_assert(std::is_same_v<decltype(ce.operator->()), std::string*>);
static_assert(std::is_same_v<decltype(*ce), std::string&>);
CHECK(ce->size() == 5);
CHECK(&*ce == &s);
}

// --- expected<T&, E>: error() ---

TEST_CASE("hardened: error() on error-state expected<T&,E>", "[hardened]") {
expected<int&, int> e(unexpect, 42);
CHECK(e.error() == 42);

const expected<int&, int> ce(unexpect, 99);
CHECK(ce.error() == 99);

expected<int&, int> e2(unexpect, 7);
CHECK(std::move(e2).error() == 7);

const expected<int&, int> ce2(unexpect, 13);
CHECK(std::move(ce2).error() == 13);
}

// --- expected<T, E&>: primary template instantiated with a reference E ---

TEST_CASE("hardened: observers on expected<T,E&>", "[hardened]") {
expected<std::string, int&> e(std::in_place, "world");
CHECK(e->size() == 5);
CHECK(*e == "world");

const expected<std::string, int&> ce(std::in_place, "there");
CHECK(ce->size() == 5);
CHECK(*ce == "there");

expected<std::string, int&> e2(std::in_place, "moved");
CHECK(*std::move(e2) == "moved");
}

TEST_CASE("hardened: error() on error-state expected<T,E&>", "[hardened]") {
int err = 42;
expected<int, int&> e(unexpect, err);
// Shallow const: error() yields int&, not const int&, even from a const
// expected -- `const E&` with E = int& collapses back to int&.
static_assert(std::is_same_v<decltype(e.error()), int&>);
CHECK(&e.error() == &err);

const expected<int, int&> ce(unexpect, err);
static_assert(std::is_same_v<decltype(ce.error()), int&>);
CHECK(&ce.error() == &err);

CHECK(&std::move(e).error() == &err);
CHECK(&std::move(ce).error() == &err);
}

// --- expected<T&, E&>: T& specialization instantiated with a reference E ---

TEST_CASE("hardened: observers on expected<T&,E&>", "[hardened]") {
int x = 5;
expected<int&, int&> e(x);
CHECK(*e == 5);
CHECK(&*e == &x);

const expected<int&, int&> ce(x);
CHECK(&*ce == &x);
}

TEST_CASE("hardened: error() on error-state expected<T&,E&>", "[hardened]") {
int err = 7;
expected<int&, int&> e(unexpect, err);
CHECK(&e.error() == &err);

const expected<int&, int&> ce(unexpect, err);
CHECK(&ce.error() == &err);

CHECK(&std::move(e).error() == &err);
CHECK(&std::move(ce).error() == &err);
}

// --- expected<void, E&>: void specialization with a reference E ---

TEST_CASE("hardened: operator* on value-state expected<void,E&>", "[hardened]") {
expected<void, int&> e;
*e;

const expected<void, int&> ce;
*ce;
}

TEST_CASE("hardened: error() on error-state expected<void,E&>", "[hardened]") {
int err = 42;
expected<void, int&> e(unexpect, err);
CHECK(&e.error() == &err);

const expected<void, int&> ce(unexpect, err);
CHECK(&ce.error() == &err);

CHECK(&std::move(e).error() == &err);
CHECK(&std::move(ce).error() == &err);
}

// ---------------------------------------------------------------------------
// unexpected friend swap: constraint check (beman-only)
// ---------------------------------------------------------------------------
Expand Down
Loading