diff --git a/tests/beman/expected/expected_hardened.test.cpp b/tests/beman/expected/expected_hardened.test.cpp index 1560d89..2242bc9 100644 --- a/tests/beman/expected/expected_hardened.test.cpp +++ b/tests/beman/expected/expected_hardened.test.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include using namespace beman::expected; @@ -89,6 +91,132 @@ TEST_CASE("hardened: error() on error-state expected", "[hardened]") { CHECK(std::move(ce2).error() == 13); } +// --------------------------------------------------------------------------- +// Reference specializations +// +// Before this section the hardened build only ever instantiated expected +// and expected with a value E, so the precondition checks in the +// expected 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: operator-> and operator* (one const overload each) --- + +TEST_CASE("hardened: observers on value-state expected", "[hardened]") { + std::string s = "hello"; + expected e(s); + CHECK(e->size() == 5); + CHECK(*e == "hello"); + CHECK(&*e == &s); + + // Shallow const: a const expected still hands out a mutable T& / T*. + const expected ce(s); + static_assert(std::is_same_v()), std::string*>); + static_assert(std::is_same_v); + CHECK(ce->size() == 5); + CHECK(&*ce == &s); +} + +// --- expected: error() --- + +TEST_CASE("hardened: error() on error-state expected", "[hardened]") { + expected e(unexpect, 42); + CHECK(e.error() == 42); + + const expected ce(unexpect, 99); + CHECK(ce.error() == 99); + + expected e2(unexpect, 7); + CHECK(std::move(e2).error() == 7); + + const expected ce2(unexpect, 13); + CHECK(std::move(ce2).error() == 13); +} + +// --- expected: primary template instantiated with a reference E --- + +TEST_CASE("hardened: observers on expected", "[hardened]") { + expected e(std::in_place, "world"); + CHECK(e->size() == 5); + CHECK(*e == "world"); + + const expected ce(std::in_place, "there"); + CHECK(ce->size() == 5); + CHECK(*ce == "there"); + + expected e2(std::in_place, "moved"); + CHECK(*std::move(e2) == "moved"); +} + +TEST_CASE("hardened: error() on error-state expected", "[hardened]") { + int err = 42; + expected 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); + CHECK(&e.error() == &err); + + const expected ce(unexpect, err); + static_assert(std::is_same_v); + CHECK(&ce.error() == &err); + + CHECK(&std::move(e).error() == &err); + CHECK(&std::move(ce).error() == &err); +} + +// --- expected: T& specialization instantiated with a reference E --- + +TEST_CASE("hardened: observers on expected", "[hardened]") { + int x = 5; + expected e(x); + CHECK(*e == 5); + CHECK(&*e == &x); + + const expected ce(x); + CHECK(&*ce == &x); +} + +TEST_CASE("hardened: error() on error-state expected", "[hardened]") { + int err = 7; + expected e(unexpect, err); + CHECK(&e.error() == &err); + + const expected ce(unexpect, err); + CHECK(&ce.error() == &err); + + CHECK(&std::move(e).error() == &err); + CHECK(&std::move(ce).error() == &err); +} + +// --- expected: void specialization with a reference E --- + +TEST_CASE("hardened: operator* on value-state expected", "[hardened]") { + expected e; + *e; + + const expected ce; + *ce; +} + +TEST_CASE("hardened: error() on error-state expected", "[hardened]") { + int err = 42; + expected e(unexpect, err); + CHECK(&e.error() == &err); + + const expected 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) // ---------------------------------------------------------------------------