From 187308699849982147487cbd99518ca7f77311c3 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sun, 2 Aug 2026 16:03:12 -0400 Subject: [PATCH] test: instantiate the reference specializations in the hardened build expected_hardened.test.cpp only ever used expected and expected with a value E. Consequently 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. Of the 21 BEMAN_EXPECTED_TRAP() sites in the header, only 15 were reachable by any translation unit in the hardened build; the six in expected were dead as far as the test suite was concerned. Adds happy-path coverage for expected, expected, expected and expected, in the same style as the existing sections. All 21 trap sites are now compiled. The checks are the same `if (!has_val_) TRAP` shape throughout, so this is mostly about compiling them at all. What it does verify beyond that is that the guarded bodies still return the right thing once T or E is a reference -- the shallow-const observers in particular, where a const expected still hands out a mutable T* / T& / E&, and where `const E&` with E = int& collapses back to int&. Happy paths only, matching the rest of the file: a violated precondition traps, and that cannot be observed from inside the process. --- .../beman/expected/expected_hardened.test.cpp | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) 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) // ---------------------------------------------------------------------------