diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f66711b..502625c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: build-windows: runs-on: windows-latest - timeout-minutes: 20 + timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 @@ -96,13 +96,13 @@ jobs: xmake-version: latest package-cache: true - - name: Build + - name: Build and test run: | - xmake f -m release -y -vv - xmake -y -vv -j$env:NUMBER_OF_PROCESSORS + xmake f -m release --ccache=n -y -vv + xmake b -y -vv -j2 primitives_test + xmake run primitives_test - - name: Test - run: xmake run primitives_test - - - name: Run examples - run: xmake run basic + - name: Build and run basic example + run: | + xmake b -y -vv -j2 basic + xmake run basic diff --git a/.gitignore b/.gitignore index 9b29fcb..6ee39a1 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ Makefile # IDE /.idea /.cache +/.vscode diff --git a/src/conversion/conversion.cppm b/src/conversion/conversion.cppm new file mode 100644 index 0000000..cdd5a47 --- /dev/null +++ b/src/conversion/conversion.cppm @@ -0,0 +1,6 @@ +module; + +export module mcpplibs.primitives.conversion; + +export import mcpplibs.primitives.conversion.traits; +export import mcpplibs.primitives.conversion.underlying; diff --git a/src/conversion/traits.cppm b/src/conversion/traits.cppm new file mode 100644 index 0000000..2057362 --- /dev/null +++ b/src/conversion/traits.cppm @@ -0,0 +1,34 @@ +module; + +#include +#include + +export module mcpplibs.primitives.conversion.traits; + +export namespace mcpplibs::primitives::conversion::risk { + +enum class kind : unsigned char { + none = 0, + overflow, + underflow, + domain_error, + precision_loss, + sign_loss, + invalid_type_combination, +}; + +template +struct traits { + kind code = kind::none; + std::optional source_value{}; + std::optional converted_value{}; +}; + +} // namespace mcpplibs::primitives::conversion::risk + +export namespace mcpplibs::primitives::conversion { + +template +using cast_result = std::expected; + +} // namespace mcpplibs::primitives::conversion diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm new file mode 100644 index 0000000..e610cf7 --- /dev/null +++ b/src/conversion/underlying.cppm @@ -0,0 +1,391 @@ +module; + +#include +#include +#include +#include +#include +#include + +export module mcpplibs.primitives.conversion.underlying; + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.underlying.traits; + +namespace mcpplibs::primitives::conversion::details { + +template +concept statically_castable = requires(SrcRep value) { + static_cast>(value); +}; + +template +concept builtin_numeric_pair = + std_numeric && std_numeric; + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (std::is_signed_v) { + auto const signed_value = static_cast(value); + if constexpr (std::is_signed_v) { + if (signed_value < + static_cast(std::numeric_limits::min())) { + return risk::kind::underflow; + } + if (signed_value > + static_cast(std::numeric_limits::max())) { + return risk::kind::overflow; + } + return std::nullopt; + } else { + if (signed_value < 0) { + return risk::kind::underflow; + } + + if (static_cast(signed_value) > + static_cast(std::numeric_limits::max())) { + return risk::kind::overflow; + } + return std::nullopt; + } + } else { + auto const unsigned_value = static_cast(value); + if (unsigned_value > + static_cast(std::numeric_limits::max())) { + return risk::kind::overflow; + } + return std::nullopt; + } +} + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if (std::isnan(value)) { + return risk::kind::domain_error; + } + if (std::isinf(value)) { + return value < static_cast(0) ? risk::kind::underflow + : risk::kind::overflow; + } + + auto const normalized = static_cast(value); + auto const min_value = + static_cast(std::numeric_limits::lowest()); + auto const max_value = + static_cast(std::numeric_limits::max()); + + if (normalized < min_value) { + return risk::kind::underflow; + } + if (normalized > max_value) { + return risk::kind::overflow; + } + return std::nullopt; +} + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + auto const converted = static_cast(value); + if (std::isinf(converted)) { + return value < static_cast(0) ? risk::kind::underflow + : risk::kind::overflow; + } + + auto const roundtrip = static_cast(converted); + if (roundtrip != value) { + return risk::kind::precision_loss; + } + + return std::nullopt; +} + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + using dest_type = std::remove_cvref_t; + + if (std::isnan(value)) { + return std::nullopt; + } + if (std::isinf(value)) { + return std::nullopt; + } + + auto const normalized = static_cast(value); + auto const min_value = + static_cast(std::numeric_limits::lowest()); + auto const max_value = + static_cast(std::numeric_limits::max()); + + if (normalized < min_value) { + return risk::kind::underflow; + } + if (normalized > max_value) { + return risk::kind::overflow; + } + + auto const converted = static_cast(value); + auto const roundtrip = static_cast>(converted); + if (roundtrip != value) { + return risk::kind::precision_loss; + } + + return std::nullopt; +} + +template + requires statically_castable +constexpr auto unchecked_rep_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + return static_cast(value); +} + +template + requires statically_castable +constexpr auto checked_rep_cast(SrcRep value) + -> cast_result> { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (builtin_numeric_pair) { + if (auto const kind = numeric_risk(value); kind.has_value()) { + return std::unexpected(*kind); + } + } + + return static_cast(value); +} + +template + requires statically_castable +constexpr auto saturating_rep_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (builtin_numeric_pair) { + if (auto const kind = numeric_risk(value); kind.has_value()) { + if (*kind == risk::kind::overflow) { + return std::numeric_limits::max(); + } + if (*kind == risk::kind::underflow) { + return std::numeric_limits::lowest(); + } + if (*kind == risk::kind::domain_error) { + return dest_type{}; + } + } + } + + return static_cast(value); +} + +template + requires statically_castable +constexpr auto truncating_rep_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (std_integer && std_floating) { + if (std::isnan(value)) { + return dest_type{}; + } + if (std::isinf(value)) { + return value < static_cast(0) + ? std::numeric_limits::lowest() + : std::numeric_limits::max(); + } + + auto const normalized = static_cast(value); + auto const min_value = + static_cast(std::numeric_limits::lowest()); + auto const max_value = + static_cast(std::numeric_limits::max()); + + if (normalized < min_value) { + return std::numeric_limits::lowest(); + } + if (normalized > max_value) { + return std::numeric_limits::max(); + } + } + + return static_cast(value); +} + +template + requires statically_castable +constexpr auto exact_rep_cast(SrcRep value) + -> cast_result> { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (!builtin_numeric_pair) { + return std::unexpected(risk::kind::invalid_type_combination); + } else { + if (auto const kind = numeric_risk(value); kind.has_value()) { + return std::unexpected(*kind); + } + return static_cast(value); + } +} + +template +constexpr auto cast_underlying_value(Src value, RepCaster rep_caster) -> Dest { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; + + auto const source_rep = underlying::traits::to_rep(value); + auto const target_rep = rep_caster.template operator()( + static_cast(source_rep)); + return underlying::traits::from_rep(target_rep); +} + +template +constexpr auto cast_underlying_result(Src value, RepCaster rep_caster) + -> cast_result { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; + + auto const source_rep = underlying::traits::to_rep(value); + auto const target_rep = rep_caster.template operator()( + static_cast(source_rep)); + if (!target_rep.has_value()) { + return std::unexpected(target_rep.error()); + } + + return underlying::traits::from_rep(*target_rep); +} + +} // namespace mcpplibs::primitives::conversion::details + +export namespace mcpplibs::primitives::conversion { + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + return details::numeric_risk(value); +} + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + return details::numeric_risk(value); +} + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + return details::numeric_risk(value); +} + +template +constexpr auto numeric_risk(SrcRep value) + -> std::optional { + return details::numeric_risk(value); +} + +template + requires details::statically_castable +constexpr auto unchecked_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + return details::unchecked_rep_cast(value); +} + +template + requires details::statically_castable +constexpr auto checked_cast(SrcRep value) + -> cast_result> { + return details::checked_rep_cast(value); +} + +template + requires details::statically_castable +constexpr auto saturating_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + return details::saturating_rep_cast(value); +} + +template + requires details::statically_castable +constexpr auto truncating_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + return details::truncating_rep_cast(value); +} + +template + requires details::statically_castable +constexpr auto exact_cast(SrcRep value) + -> cast_result> { + return details::exact_rep_cast(value); +} + +template + requires(!details::builtin_numeric_pair, + std::remove_cv_t>) +constexpr auto unchecked_cast(Src value) noexcept -> Dest { + return details::cast_underlying_value( + value, [](SrcRep rep) { + return details::unchecked_rep_cast(rep); + }); +} + +template + requires(!details::builtin_numeric_pair, + std::remove_cv_t>) +constexpr auto checked_cast(Src value) -> cast_result { + return details::cast_underlying_result( + value, [](SrcRep rep) { + return details::checked_rep_cast(rep); + }); +} + +template + requires(!details::builtin_numeric_pair, + std::remove_cv_t>) +constexpr auto saturating_cast(Src value) noexcept -> Dest { + return details::cast_underlying_value( + value, [](SrcRep rep) { + return details::saturating_rep_cast(rep); + }); +} + +template + requires(!details::builtin_numeric_pair, + std::remove_cv_t>) +constexpr auto truncating_cast(Src value) noexcept -> Dest { + return details::cast_underlying_value( + value, [](SrcRep rep) { + return details::truncating_rep_cast(rep); + }); +} + +template + requires(!details::builtin_numeric_pair, + std::remove_cv_t>) +constexpr auto exact_cast(Src value) -> cast_result { + return details::cast_underlying_result( + value, [](SrcRep rep) { + return details::exact_rep_cast(rep); + }); +} + +} // namespace mcpplibs::primitives::conversion diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 86bc2a6..012fc5e 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -13,16 +13,14 @@ import mcpplibs.primitives.operations.dispatcher; import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.primitive.traits; +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; import mcpplibs.primitives.policy.handler; import mcpplibs.primitives.policy.impl; import mcpplibs.primitives.underlying.traits; -namespace mcpplibs::primitives::operations { -template -concept underlying_operand = underlying_type>; - -namespace details { +namespace mcpplibs::primitives::operations::details { template struct three_way_ordering { using type = std::strong_ordering; }; @@ -62,9 +60,33 @@ constexpr auto decode_three_way_code(CommonRep const &code) -> Ordering { return Ordering::equivalent; } -} // namespace details +constexpr auto to_policy_error_kind(const conversion::risk::kind kind) + -> policy::error::kind { + switch (kind) { + case conversion::risk::kind::overflow: + return policy::error::kind::overflow; + case conversion::risk::kind::underflow: + return policy::error::kind::underflow; + case conversion::risk::kind::domain_error: + return policy::error::kind::domain_error; + default: + return policy::error::kind::unspecified; + } +} + +template +constexpr auto to_error_payload(policy::error::kind kind) -> ErrorPayload { + if constexpr (std::same_as) { + return kind; + } else { + static_cast(kind); + return ErrorPayload{}; + } +} + +} // namespace mcpplibs::primitives::operations::details + -} // namespace mcpplibs::primitives::operations export namespace mcpplibs::primitives::operations { @@ -510,6 +532,9 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) using lhs_value_type = lhs_traits::value_type; using lhs_value_policy = lhs_traits::value_policy; using lhs_rep = underlying::traits::rep_type; + using assign_meta = dispatcher_meta; + using common_value_type = assign_meta::common_rep; + using common_rep = underlying::traits::rep_type; auto out = apply(lhs, rhs); if (!out.has_value()) { @@ -517,18 +542,21 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) } auto const assigned_common = out->load(); + auto const assigned_common_rep = + underlying::traits::to_rep(assigned_common); if constexpr (std::same_as && - std::integral) { + std_integer && std_numeric) { if (auto const kind = - policy::details::narrow_numeric_error(assigned_common); + conversion::numeric_risk(assigned_common_rep); kind.has_value()) { return std::unexpected( - policy::details::to_error_payload(*kind)); + details::to_error_payload( + details::to_policy_error_kind(*kind))); } } auto const assigned_rep = - policy::details::safe_numeric_cast(assigned_common); + conversion::saturating_cast(assigned_common_rep); lhs.store(underlying::traits::from_rep(assigned_rep)); return out; } @@ -668,16 +696,14 @@ constexpr auto operator+(Lhs const &lhs, Rhs const &rhs) return operations::add(lhs, rhs); } -template +template constexpr auto operator+(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::add(lhs, rhs); } -template +template constexpr auto operator+(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Addition, Lhs, Rhs> { @@ -692,16 +718,14 @@ constexpr auto operator-(Lhs const &lhs, Rhs const &rhs) return operations::sub(lhs, rhs); } -template +template constexpr auto operator-(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::sub(lhs, rhs); } -template +template constexpr auto operator-(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Subtraction, Lhs, Rhs> { @@ -716,15 +740,14 @@ constexpr auto operator*(Lhs const &lhs, Rhs const &rhs) return operations::mul(lhs, rhs); } -template +template constexpr auto operator*(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::mul(lhs, rhs); } -template constexpr auto operator*(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< @@ -739,16 +762,14 @@ constexpr auto operator/(Lhs const &lhs, Rhs const &rhs) return operations::div(lhs, rhs); } -template +template constexpr auto operator/(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::div(lhs, rhs); } -template +template constexpr auto operator/(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Division, Lhs, Rhs> { @@ -762,16 +783,14 @@ constexpr auto operator%(Lhs const &lhs, Rhs const &rhs) return operations::mod(lhs, rhs); } -template +template constexpr auto operator%(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::mod(lhs, rhs); } -template +template constexpr auto operator%(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Modulus, Lhs, Rhs> { @@ -786,16 +805,14 @@ constexpr auto operator<<(Lhs const &lhs, Rhs const &rhs) return operations::shift_left(lhs, rhs); } -template +template constexpr auto operator<<(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::shift_left(lhs, rhs); } -template +template constexpr auto operator<<(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::LeftShift, Lhs, Rhs> { @@ -809,16 +826,14 @@ constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) return operations::shift_right(lhs, rhs); } -template +template constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::shift_right(lhs, rhs); } -template +template constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::RightShift, Lhs, Rhs> { @@ -832,16 +847,14 @@ constexpr auto operator&(Lhs const &lhs, Rhs const &rhs) return operations::bit_and(lhs, rhs); } -template +template constexpr auto operator&(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::bit_and(lhs, rhs); } -template +template constexpr auto operator&(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::BitwiseAnd, Lhs, Rhs> { @@ -855,16 +868,14 @@ constexpr auto operator|(Lhs const &lhs, Rhs const &rhs) return operations::bit_or(lhs, rhs); } -template +template constexpr auto operator|(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::bit_or(lhs, rhs); } -template +template constexpr auto operator|(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::BitwiseOr, Lhs, Rhs> { @@ -878,16 +889,14 @@ constexpr auto operator^(Lhs const &lhs, Rhs const &rhs) return operations::bit_xor(lhs, rhs); } -template +template constexpr auto operator^(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::bit_xor(lhs, rhs); } -template +template constexpr auto operator^(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::BitwiseXor, Lhs, Rhs> { @@ -903,16 +912,14 @@ constexpr auto operator==(Lhs const &lhs, Rhs const &rhs) return operations::equal(lhs, rhs); } -template +template constexpr auto operator==(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::equal(lhs, rhs); } -template +template constexpr auto operator==(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t { @@ -926,16 +933,14 @@ constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) return operations::not_equal(lhs, rhs); } -template +template constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::not_equal(lhs, rhs); } -template +template constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::NotEqual, Lhs, Rhs> { @@ -949,15 +954,13 @@ constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs) return operations::three_way_compare(lhs, rhs); } -template +template constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_three_way_dispatch_result_t { return operations::three_way_compare(lhs, rhs); } -template +template constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_three_way_dispatch_result_t { return operations::three_way_compare(lhs, rhs); diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index df1299e..69a0426 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -1,11 +1,9 @@ module; #include #include -#include #include #include #include -#include #include #include #include @@ -90,6 +88,16 @@ auto atomic_ref_load(T const &value, std::memory_order order) noexcept -> T { return ref.load(order); } +template +constexpr auto to_error_payload(error::kind kind) -> ErrorPayload { + if constexpr (std::same_as) { + return kind; + } else { + static_cast(kind); + return ErrorPayload{}; + } +} + } // namespace mcpplibs::primitives::policy::details export namespace mcpplibs::primitives::policy { @@ -526,115 +534,6 @@ struct value::handler { } }; -namespace details { -template -constexpr auto to_error_payload(error::kind kind) -> ErrorPayload { - if constexpr (std::same_as) { - return kind; - } else { - static_cast(kind); - return ErrorPayload{}; - } -} - -template -constexpr auto narrow_integral_error(SrcRep value) - -> std::optional { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; - - if constexpr (std::is_signed_v) { - auto const signed_value = static_cast(value); - if constexpr (std::is_signed_v) { - if (signed_value < - static_cast(std::numeric_limits::min())) { - return error::kind::underflow; - } - if (signed_value > - static_cast(std::numeric_limits::max())) { - return error::kind::overflow; - } - return std::nullopt; - } else { - if (signed_value < 0) { - return error::kind::underflow; - } - - if (static_cast(signed_value) > - static_cast(std::numeric_limits::max())) { - return error::kind::overflow; - } - return std::nullopt; - } - } else { - auto const unsigned_value = static_cast(value); - if (unsigned_value > - static_cast(std::numeric_limits::max())) { - return error::kind::overflow; - } - return std::nullopt; - } -} - -template -constexpr auto narrow_numeric_error(SrcRep value) - -> std::optional { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; - - if constexpr (std::integral && std::integral) { - return narrow_integral_error(value); - } else if constexpr (std::integral && - std::floating_point) { - if (std::isnan(value)) { - return error::kind::domain_error; - } - if (std::isinf(value)) { - return value < static_cast(0) ? error::kind::underflow - : error::kind::overflow; - } - - auto const normalized = static_cast(value); - auto const min_value = - static_cast(std::numeric_limits::lowest()); - auto const max_value = - static_cast(std::numeric_limits::max()); - - if (normalized < min_value) { - return error::kind::underflow; - } - if (normalized > max_value) { - return error::kind::overflow; - } - return std::nullopt; - } else { - static_cast(value); - return std::nullopt; - } -} - -template -constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; - - if constexpr (std::integral && std::floating_point) { - if (auto const kind = narrow_numeric_error(value); - kind.has_value()) { - if (*kind == error::kind::overflow) { - return std::numeric_limits::max(); - } - if (*kind == error::kind::underflow) { - return std::numeric_limits::lowest(); - } - return dest_type{}; - } - } - - return static_cast(value); -} -} // namespace details - template struct error::handler { diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 730d712..4b17d15 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -5,6 +5,7 @@ module; export module mcpplibs.primitives.primitive.impl; +import mcpplibs.primitives.conversion.underlying; import mcpplibs.primitives.underlying.traits; import mcpplibs.primitives.policy.traits; import mcpplibs.primitives.policy.handler; @@ -63,7 +64,7 @@ private: auto const source_rep = underlying::traits::to_rep(source); auto const target_rep = - policy::details::safe_numeric_cast( + conversion::saturating_cast( static_cast(source_rep)); return underlying::traits>::from_rep(target_rep); } diff --git a/src/primitive/traits.cppm b/src/primitive/traits.cppm index 8ecb4fe..bc3af30 100644 --- a/src/primitive/traits.cppm +++ b/src/primitive/traits.cppm @@ -6,7 +6,6 @@ export module mcpplibs.primitives.primitive.traits; import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.policy.traits; -import mcpplibs.primitives.policy.impl; import mcpplibs.primitives.policy.utility; import mcpplibs.primitives.underlying.traits; @@ -45,8 +44,10 @@ template using make_primitive_t = make_primitive::type; using default_policies = - std::tuple; + std::tuple, + policy::resolve_policy_t, + policy::resolve_policy_t, + policy::resolve_policy_t>; template using traits = details::primitive_traits_impl>; @@ -61,6 +62,9 @@ concept primitive_type = requires { typename traits::concurrency_policy; }; +template +concept primitive_operand = primitive_type>; + template concept boolean = primitive_type && @@ -88,20 +92,28 @@ concept floating = template concept numeric = integer || floating; -} // namespace mcpplibs::primitives::meta +template +concept primitive_like = + primitive_type || underlying_type>; -// Backward-compatible aliases for existing downstream users. -export namespace mcpplibs::primitives::traits { -using policy_category [[deprecated]] = meta::policy_category; +template +concept boolean_like = + boolean || boolean_underlying_type>; -template -using make_primitive [[deprecated]] = meta::make_primitive; +template +concept character_like = + character || character_underlying_type>; -template -using make_primitive_t [[deprecated]] = meta::make_primitive_t; +template +concept integer_like = + integer || integer_underlying_type>; -using default_policies [[deprecated]] = meta::default_policies; +template +concept floating_like = + floating || floating_underlying_type>; template -using primitive_traits [[deprecated]] = meta::traits; -} // namespace mcpplibs::primitives::traits +concept numeric_like = + numeric || numeric_underlying_type>; + +} // namespace mcpplibs::primitives::meta diff --git a/src/primitives.cppm b/src/primitives.cppm index 72e168e..6d53dc5 100644 --- a/src/primitives.cppm +++ b/src/primitives.cppm @@ -5,4 +5,5 @@ export module mcpplibs.primitives; export import mcpplibs.primitives.underlying; export import mcpplibs.primitives.policy; export import mcpplibs.primitives.primitive; -export import mcpplibs.primitives.operations; \ No newline at end of file +export import mcpplibs.primitives.operations; +export import mcpplibs.primitives.conversion; diff --git a/src/underlying/traits.cppm b/src/underlying/traits.cppm index 02b0e68..9c1b429 100644 --- a/src/underlying/traits.cppm +++ b/src/underlying/traits.cppm @@ -170,6 +170,9 @@ concept underlying_type = underlying::details::has_supported_rep_type && underlying::details::has_consistent_category; +template +concept underlying_operand = underlying_type>; + template concept boolean_underlying_type = underlying_type && (underlying::traits>::kind == diff --git a/tests/basic/CMakeLists.txt b/tests/basic/CMakeLists.txt index def624f..5e199b2 100644 --- a/tests/basic/CMakeLists.txt +++ b/tests/basic/CMakeLists.txt @@ -1,18 +1,43 @@ -# tests/basic/CMakeLists.txt - enable_testing() -file(GLOB BASIC_TESTS "test_*.cpp") - -add_executable(basic_tests ${BASIC_TESTS}) +function(add_basic_suite target_name suite_pattern) + file(GLOB_RECURSE suite_sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/${suite_pattern}" + ) -target_link_libraries(basic_tests PRIVATE GTest::gtest GTest::gmock GTest::gmock_main GTest::gtest_main) -target_compile_features(basic_tests PRIVATE cxx_std_23) + if(NOT suite_sources) + return() + endif() -target_link_libraries(basic_tests PRIVATE mcpplibs-primitives) + add_executable(${target_name} ${suite_sources}) + target_compile_features(${target_name} PRIVATE cxx_std_23) + target_link_libraries(${target_name} + PRIVATE + GTest::gtest + GTest::gmock + GTest::gmock_main + GTest::gtest_main + mcpplibs-primitives + ) -add_test( - NAME BASIC_TESTS - COMMAND $ + add_test( + NAME ${target_name} + COMMAND $ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endfunction() + +add_basic_suite(basic_conversion_tests "conversion/**/test_*.cpp") +add_basic_suite(basic_operations_tests "operations/**/test_*.cpp") +add_basic_suite(basic_policy_tests "policy/**/test_*.cpp") +add_basic_suite(basic_primitive_tests "primitive/**/test_*.cpp") +add_basic_suite(basic_underlying_tests "underlying/**/test_*.cpp") + +add_custom_target(basic_tests) +add_dependencies(basic_tests + basic_conversion_tests + basic_operations_tests + basic_policy_tests + basic_primitive_tests + basic_underlying_tests ) diff --git a/tests/basic/conversion/traits/test_numeric_risk.cpp b/tests/basic/conversion/traits/test_numeric_risk.cpp new file mode 100644 index 0000000..dac2918 --- /dev/null +++ b/tests/basic/conversion/traits/test_numeric_risk.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; + +using namespace mcpplibs::primitives; + +TEST(ConversionRiskTest, NumericRiskDetectsOverflowAndUnderflow) { + auto const overflow = conversion::numeric_risk(70000); + ASSERT_TRUE(overflow.has_value()); + EXPECT_EQ(*overflow, conversion::risk::kind::overflow); + + auto const underflow = conversion::numeric_risk(-1); + ASSERT_TRUE(underflow.has_value()); + EXPECT_EQ(*underflow, conversion::risk::kind::underflow); +} + +TEST(ConversionRiskTest, NumericRiskDetectsDomainAndPrecisionLoss) { + auto const domain = + conversion::numeric_risk(std::numeric_limits::quiet_NaN()); + ASSERT_TRUE(domain.has_value()); + EXPECT_EQ(*domain, conversion::risk::kind::domain_error); + + auto const precision = + conversion::numeric_risk(std::numeric_limits::max()); + ASSERT_TRUE(precision.has_value()); + EXPECT_EQ(*precision, conversion::risk::kind::precision_loss); +} diff --git a/tests/basic/conversion/traits/test_result_types.cpp b/tests/basic/conversion/traits/test_result_types.cpp new file mode 100644 index 0000000..235281a --- /dev/null +++ b/tests/basic/conversion/traits/test_result_types.cpp @@ -0,0 +1,11 @@ +#include +#include + +import mcpplibs.primitives.conversion.traits; + +using namespace mcpplibs::primitives; + +TEST(ConversionTypeTest, ResultTypesMatchContracts) { + static_assert(std::same_as, + std::expected>); +} diff --git a/tests/basic/conversion/underlying/test_builtin_casts.cpp b/tests/basic/conversion/underlying/test_builtin_casts.cpp new file mode 100644 index 0000000..a4a5ae3 --- /dev/null +++ b/tests/basic/conversion/underlying/test_builtin_casts.cpp @@ -0,0 +1,19 @@ +#include +#include +#include + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; +import mcpplibs.primitives.underlying; + +using namespace mcpplibs::primitives; + +TEST(ConversionCastTest, CheckedCastReportsErrorForInvalidInput) { + auto const ok = conversion::checked_cast(42u); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(*ok, 42); + + auto const bad = conversion::checked_cast(-7); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} diff --git a/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp b/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp new file mode 100644 index 0000000..d296685 --- /dev/null +++ b/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp @@ -0,0 +1,17 @@ +#include +#include +#include + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; +import mcpplibs.primitives.underlying; + +using namespace mcpplibs::primitives; + +TEST(ConversionCastTest, SaturatingCastClampsAndHandlesNaN) { + EXPECT_EQ(conversion::saturating_cast(100000), + std::numeric_limits::max()); + EXPECT_EQ(conversion::saturating_cast( + std::numeric_limits::quiet_NaN()), + 0); +} diff --git a/tests/basic/conversion/underlying/test_underlying_casts.cpp b/tests/basic/conversion/underlying/test_underlying_casts.cpp new file mode 100644 index 0000000..19580ac --- /dev/null +++ b/tests/basic/conversion/underlying/test_underlying_casts.cpp @@ -0,0 +1,37 @@ +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; + +#include "../../support/conversion_box_types.hpp" + +using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::test_support::conversion; + +TEST(ConversionUnderlyingTest, CheckedCastUsesRepBridge) { + auto const ok = conversion::checked_cast(SignedBox{42}); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(ok->value, 42); + + auto const bad = conversion::checked_cast(SignedBox{-1}); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} + +TEST(ConversionUnderlyingTest, SaturatingCastClampsByUnderlyingRep) { + auto const saturated = + conversion::saturating_cast(SignedBox{ + std::numeric_limits::max()}); + EXPECT_EQ(saturated.value, std::numeric_limits::max()); +} + +TEST(ConversionUnderlyingTest, TruncatingAndExactCastSupportFloatingSources) { + auto const truncated = conversion::truncating_cast(FloatBox{12.75}); + EXPECT_EQ(truncated.value, 12); + + auto const exact = conversion::exact_cast(FloatBox{12.0}); + ASSERT_TRUE(exact.has_value()); + EXPECT_EQ(exact->value, 12); +} diff --git a/tests/basic/main.cpp b/tests/basic/main.cpp new file mode 100644 index 0000000..8bd67e1 --- /dev/null +++ b/tests/basic/main.cpp @@ -0,0 +1,6 @@ +#include + +auto main(int argc, char **argv) -> int { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/basic/operations/dispatcher/test_core_runtime.cpp b/tests/basic/operations/dispatcher/test_core_runtime.cpp new file mode 100644 index 0000000..3b60297 --- /dev/null +++ b/tests/basic/operations/dispatcher/test_core_runtime.cpp @@ -0,0 +1,317 @@ +#include +#include +#include +#include +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.operations.operators; + +using namespace mcpplibs::primitives; + +TEST(OperationsTest, AddReturnsExpectedPrimitive) { + using lhs_t = primitive; + using rhs_t = primitive; + + auto const lhs = lhs_t{10}; + auto const rhs = rhs_t{32}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, DivisionByZeroReturnsError) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + auto const result = operations::div(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); +} + +TEST(OperationsTest, SaturatingAdditionClampsUnsignedOverflow) { + using value_t = primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), static_cast(65535)); +} + +TEST(OperationsTest, CheckedAdditionReportsUnsignedOverflow) { + using value_t = + primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::overflow); +} + +TEST(OperationsTest, UncheckedAdditionWrapsUnsignedOverflow) { + using value_t = primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), static_cast(14)); +} + +TEST(OperationsTest, UncheckedDivisionUsesRawArithmeticWhenValid) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{4}; + + auto const result = operations::div(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 25); +} + +TEST(OperationsTest, FencedPolicyPathReturnsExpectedValue) { + using value_t = + primitive; + + auto const lhs = value_t{12}; + auto const rhs = value_t{30}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, FencedPolicyConcurrentInvocationsRemainConsistent) { + using value_t = + primitive; + + constexpr int kThreadCount = 8; + constexpr int kIterationsPerThread = 20000; + + auto const lhs = value_t{12}; + auto const rhs = value_t{30}; + + std::atomic mismatch_count{0}; + std::atomic error_count{0}; + std::atomic start{false}; + + std::vector workers; + workers.reserve(kThreadCount); + + for (int i = 0; i < kThreadCount; ++i) { + workers.emplace_back([&]() { + while (!start.load(std::memory_order_acquire)) { + } + + for (int n = 0; n < kIterationsPerThread; ++n) { + auto const result = operations::add(lhs, rhs); + if (!result.has_value()) { + error_count.fetch_add(1, std::memory_order_relaxed); + continue; + } + + if (result->value() != 42) { + mismatch_count.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + start.store(true, std::memory_order_release); + + for (auto &worker : workers) { + worker.join(); + } + + EXPECT_EQ(error_count.load(std::memory_order_relaxed), 0); + EXPECT_EQ(mismatch_count.load(std::memory_order_relaxed), 0); +} + +TEST(OperationsTest, PrimitiveFencedLoadStoreAndCasWork) { + using value_t = + primitive; + + auto value = value_t{1}; + EXPECT_EQ(value.load(), 1); + + value.store(4); + EXPECT_EQ(value.load(), 4); + + auto expected = 4; + EXPECT_TRUE(value.compare_exchange(expected, 7)); + EXPECT_EQ(value.load(), 7); + + expected = 9; + EXPECT_FALSE(value.compare_exchange(expected, 11)); + EXPECT_EQ(expected, 7); +} + +TEST(OperationsTest, PrimitiveFencedCasSupportsConcurrentIncrements) { + using value_t = + primitive; + + constexpr int kThreadCount = 6; + constexpr int kIterationsPerThread = 5000; + + auto counter = value_t{0}; + std::vector workers; + workers.reserve(kThreadCount); + + for (int i = 0; i < kThreadCount; ++i) { + workers.emplace_back([&]() { + for (int n = 0; n < kIterationsPerThread; ++n) { + auto expected = counter.load(); + while (!counter.compare_exchange(expected, expected + 1)) { + } + } + }); + } + + for (auto &worker : workers) { + worker.join(); + } + + EXPECT_EQ(counter.load(), kThreadCount * kIterationsPerThread); +} + +TEST(OperationsTest, + BinaryOperationsWithLoadStoreRemainStableUnderHighConcurrency) { + using value_t = + primitive; + + constexpr int kWriterThreads = 6; + constexpr int kReaderThreads = 8; + constexpr int kIterationsPerThread = 25000; + constexpr int kMaxOperand = 100000; + + auto lhs = value_t{0}; + auto rhs = value_t{0}; + auto sink = value_t{0}; + + std::atomic add_error_count{0}; + std::atomic sub_error_count{0}; + std::atomic range_violation_count{0}; + std::atomic start{false}; + + std::vector workers; + workers.reserve(kWriterThreads + kReaderThreads); + + for (int writer = 0; writer < kWriterThreads; ++writer) { + workers.emplace_back([&, writer]() { + while (!start.load(std::memory_order_acquire)) { + } + + for (int n = 0; n < kIterationsPerThread; ++n) { + auto const v1 = (writer + n) % (kMaxOperand + 1); + auto const v2 = (writer * 3 + n * 7) % (kMaxOperand + 1); + lhs.store(v1); + rhs.store(v2); + } + }); + } + + for (int reader = 0; reader < kReaderThreads; ++reader) { + workers.emplace_back([&, reader]() { + while (!start.load(std::memory_order_acquire)) { + } + + for (int n = 0; n < kIterationsPerThread; ++n) { + if (((reader + n) & 1) == 0) { + auto const out = operations::add(lhs, rhs); + if (!out.has_value()) { + add_error_count.fetch_add(1, std::memory_order_relaxed); + continue; + } + + auto const v = out->load(); + if (v < 0 || v > (kMaxOperand * 2)) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + sink.store(v); + auto const snapshot = sink.load(); + if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + continue; + } + + auto const out = operations::sub(lhs, rhs); + if (!out.has_value()) { + sub_error_count.fetch_add(1, std::memory_order_relaxed); + continue; + } + + auto const v = out->load(); + if (v < -kMaxOperand || v > kMaxOperand) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + sink.store(v); + auto const snapshot = sink.load(); + if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + start.store(true, std::memory_order_release); + + for (auto &worker : workers) { + worker.join(); + } + + EXPECT_EQ(add_error_count.load(std::memory_order_relaxed), 0); + EXPECT_EQ(sub_error_count.load(std::memory_order_relaxed), 0); + EXPECT_EQ(range_violation_count.load(std::memory_order_relaxed), 0); +} + +TEST(OperationsTest, PrimitiveSupportsCopyAndMoveSpecialMembers) { + using value_t = primitive; + + static_assert(std::is_copy_constructible_v); + static_assert(std::is_copy_assignable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_move_assignable_v); + + auto original = value_t{42}; + auto copy_constructed = value_t{original}; + EXPECT_EQ(copy_constructed.load(), 42); + + auto copy_assigned = value_t{0}; + copy_assigned = original; + EXPECT_EQ(copy_assigned.load(), 42); + + auto move_constructed = value_t{std::move(copy_assigned)}; + EXPECT_EQ(move_constructed.load(), 42); + + auto move_assigned = value_t{0}; + move_assigned = std::move(move_constructed); + EXPECT_EQ(move_assigned.load(), 42); +} diff --git a/tests/basic/operations/dispatcher/test_type_policy.cpp b/tests/basic/operations/dispatcher/test_type_policy.cpp new file mode 100644 index 0000000..3bde487 --- /dev/null +++ b/tests/basic/operations/dispatcher/test_type_policy.cpp @@ -0,0 +1,378 @@ +#include +#include +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.operations.dispatcher; +import mcpplibs.primitives.operations.impl; +import mcpplibs.primitives.operations.operators; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; + +using namespace mcpplibs::primitives; + +template +concept constructible_from_underlying = + underlying_type && requires(U u) { + Primitive{u}; +}; + +template +concept storable_from_underlying = + underlying_type && requires(Primitive p, U u) { + p.store(u); +}; + +template +concept cas_from_underlying = + underlying_type && requires(Primitive p, U expected, U desired) { + { p.compare_exchange(expected, desired) } -> std::same_as; +}; + +TEST(OperationsTest, StrictTypeRejectsMixedTypesAtCompileTime) { + using lhs_t = primitive; + using rhs_t = primitive; + + using strict_handler = + policy::type::handler; + using strict_meta = operations::dispatcher_meta; + + static_assert(strict_handler::enabled); + static_assert(!strict_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(strict_handler::diagnostic_id, 1u); +} + +TEST(OperationsTest, StrictTypeAllowsSameTypeAtRuntime) { + using value_t = primitive; + + auto const lhs = value_t{19}; + auto const rhs = value_t{23}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, StrictTypePrimitiveConstructorRejectsCrossUnderlyingType) { + using strict_t = primitive; + + static_assert(constructible_from_underlying); + static_assert(!constructible_from_underlying); + + auto const value = strict_t{42}; + EXPECT_EQ(value.load(), 42); +} + +TEST(OperationsTest, CompatibleTypePrimitiveConstructorAllowsSameCategory) { + using compatible_t = + primitive; + + static_assert(constructible_from_underlying); + static_assert(!constructible_from_underlying); + + auto const value = compatible_t{static_cast(42)}; + EXPECT_EQ(value.load(), 42); +} + +TEST(OperationsTest, TransparentTypePrimitiveConstructorAllowsCrossCategory) { + using transparent_t = + primitive; + + static_assert(constructible_from_underlying); + + auto const value = transparent_t{42.75}; + EXPECT_EQ(value.load(), 42); +} + +TEST(OperationsTest, PrimitiveStoreAndCasRejectCrossUnderlyingWithStrictType) { + using strict_t = primitive; + + static_assert(!storable_from_underlying); + static_assert(!cas_from_underlying); +} + +TEST(OperationsTest, PrimitiveStoreAndCasAllowCompatibleSameCategory) { + using compatible_t = + primitive; + + static_assert(storable_from_underlying); + static_assert(cas_from_underlying); + static_assert(!storable_from_underlying); + static_assert(!cas_from_underlying); + + auto value = compatible_t{10}; + value.store(static_cast(12)); + EXPECT_EQ(value.load(), 12); + + short expected = 12; + EXPECT_TRUE(value.compare_exchange(expected, static_cast(20))); + EXPECT_EQ(value.load(), 20); + + expected = 11; + EXPECT_FALSE(value.compare_exchange(expected, static_cast(30))); + EXPECT_EQ(expected, 20); +} + +TEST(OperationsTest, PrimitiveStoreAndCasAllowTransparentCrossCategory) { + using transparent_t = + primitive; + + static_assert(storable_from_underlying); + static_assert(cas_from_underlying); + + auto value = transparent_t{10}; + value.store(42.75); + EXPECT_EQ(value.load(), 42); + + double expected = 42.0; + EXPECT_TRUE(value.compare_exchange(expected, 99.5)); + EXPECT_EQ(value.load(), 99); + + expected = 3.0; + EXPECT_FALSE(value.compare_exchange(expected, 1.0)); + EXPECT_EQ(expected, 99.0); +} + +TEST(OperationsTest, PrimitiveTransparentStoreClampsOutOfRangeFloatingInput) { + using transparent_t = + primitive; + + auto value = transparent_t{0}; + + value.store(std::numeric_limits::infinity()); + EXPECT_EQ(value.load(), std::numeric_limits::max()); + + value.store(-std::numeric_limits::infinity()); + EXPECT_EQ(value.load(), std::numeric_limits::lowest()); + + value.store(std::numeric_limits::quiet_NaN()); + EXPECT_EQ(value.load(), 0); +} + +TEST(OperationsTest, + PrimitiveSpecialMembersSupportCrossUnderlyingWithCompatibleType) { + using dst_t = + primitive; + using src_t = + primitive; + + static_assert(std::is_constructible_v); + static_assert(std::is_constructible_v); + static_assert(std::is_assignable_v); + static_assert(std::is_assignable_v); + + auto const source = src_t{40}; + auto copy_constructed = dst_t{source}; + EXPECT_EQ(copy_constructed.load(), 40); + + auto move_constructed = dst_t{src_t{41}}; + EXPECT_EQ(move_constructed.load(), 41); + + auto copy_assigned = dst_t{0}; + copy_assigned = source; + EXPECT_EQ(copy_assigned.load(), 40); + + auto move_assigned = dst_t{0}; + auto move_source = src_t{42}; + move_assigned = std::move(move_source); + EXPECT_EQ(move_assigned.load(), 42); +} + +TEST(OperationsTest, + PrimitiveSpecialMembersRejectCrossUnderlyingWithStrictType) { + using dst_t = primitive; + using src_t = primitive; + + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_assignable_v); + static_assert(!std::is_assignable_v); +} + +TEST(OperationsTest, DispatcherMetaTracksResolvedPolicyGroupConsistency) { + using aligned_lhs_t = primitive; + using aligned_rhs_t = primitive; + using aligned_meta = operations::dispatcher_meta; + + using mismatch_lhs_t = primitive; + using mismatch_rhs_t = primitive; + using mismatch_meta = + operations::dispatcher_meta; + + static_assert(aligned_meta::policy_group_consistent); + static_assert(!mismatch_meta::policy_group_consistent); + + EXPECT_TRUE(aligned_meta::policy_group_consistent); + EXPECT_FALSE(mismatch_meta::policy_group_consistent); +} + +TEST(OperationsTest, CompatibleTypeRequiresSameUnderlyingCategory) { + using same_category_handler = + policy::type::handler; + using cross_category_handler = + policy::type::handler; + + static_assert(same_category_handler::enabled); + static_assert(same_category_handler::allowed); + static_assert(cross_category_handler::enabled); + static_assert(!cross_category_handler::allowed); + + EXPECT_EQ(cross_category_handler::diagnostic_id, 2u); +} + +TEST(OperationsTest, TransparentTypeIgnoresCategoryWhenCommonRepIsValid) { + using transparent_handler = + policy::type::handler; + + static_assert(transparent_handler::enabled); + static_assert(transparent_handler::allowed); + static_assert( + std::is_same_v); + + EXPECT_EQ(transparent_handler::diagnostic_id, 0u); +} + +TEST(OperationsTest, BoolUnderlyingRejectsArithmeticOperationsAtCompileTime) { + using value_t = primitive; + using bool_handler = policy::type::handler; + using bool_meta = operations::dispatcher_meta; + + static_assert(bool_handler::enabled); + static_assert(!bool_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(bool_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, CharUnderlyingRejectsArithmeticEvenWithTransparentType) { + using value_t = primitive; + using char_handler = policy::type::handler; + using char_meta = operations::dispatcher_meta; + + static_assert(char_handler::enabled); + static_assert(!char_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(char_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, SignedAndUnsignedCharRejectArithmeticAtCompileTime) { + using signed_handler = + policy::type::handler; + using unsigned_handler = + policy::type::handler; + + static_assert(signed_handler::enabled); + static_assert(!signed_handler::allowed); + static_assert(unsigned_handler::enabled); + static_assert(!unsigned_handler::allowed); + + EXPECT_EQ(signed_handler::diagnostic_id, 3u); + EXPECT_EQ(unsigned_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, BoolUnderlyingAllowsComparisonOperations) { + using value_t = primitive; + + auto const lhs = value_t{true}; + auto const rhs = value_t{false}; + + auto const eq_result = operations::equal(lhs, rhs); + auto const ne_result = operations::not_equal(lhs, rhs); + + ASSERT_TRUE(eq_result.has_value()); + ASSERT_TRUE(ne_result.has_value()); + EXPECT_FALSE(eq_result->value()); + EXPECT_TRUE(ne_result->value()); +} + +TEST(OperationsTest, CharUnderlyingAllowsComparisonWithTransparentType) { + using value_t = primitive; + + auto const lhs = value_t{'a'}; + auto const rhs = value_t{'a'}; + + auto const eq_result = operations::equal(lhs, rhs); + + ASSERT_TRUE(eq_result.has_value()); + EXPECT_EQ(eq_result->value(), static_cast(1)); +} + +TEST(OperationsTest, MixedBinaryOperationsSupportUnderlyingOnBothSides) { + using value_t = primitive; + + auto const lhs = value_t{40}; + short const rhs = 2; + + auto const apply_lr = operations::apply(lhs, rhs); + auto const apply_rl = operations::apply(rhs, lhs); + auto const add_lr = operations::add(lhs, rhs); + auto const add_rl = operations::add(rhs, lhs); + auto const sub_lr = operations::sub(lhs, rhs); + auto const sub_rl = operations::sub(rhs, lhs); + auto const div_lr = operations::div(lhs, rhs); + auto const div_rl = operations::div(rhs, lhs); + auto const cmp_lr = operations::three_way_compare(lhs, rhs); + auto const cmp_rl = operations::three_way_compare(rhs, lhs); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(apply_lr.has_value()); + ASSERT_TRUE(apply_rl.has_value()); + ASSERT_TRUE(add_lr.has_value()); + ASSERT_TRUE(add_rl.has_value()); + ASSERT_TRUE(sub_lr.has_value()); + ASSERT_TRUE(sub_rl.has_value()); + ASSERT_TRUE(div_lr.has_value()); + ASSERT_TRUE(div_rl.has_value()); + ASSERT_TRUE(cmp_lr.has_value()); + ASSERT_TRUE(cmp_rl.has_value()); + + EXPECT_EQ(apply_lr->value(), 42); + EXPECT_EQ(apply_rl->value(), 42); + EXPECT_EQ(add_lr->value(), 42); + EXPECT_EQ(add_rl->value(), 42); + EXPECT_EQ(sub_lr->value(), 38); + EXPECT_EQ(sub_rl->value(), -38); + EXPECT_EQ(div_lr->value(), 20); + EXPECT_EQ(div_rl->value(), 0); + EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); + EXPECT_EQ(*cmp_rl, std::strong_ordering::less); +} diff --git a/tests/basic/operations/error/test_throwing_policy.cpp b/tests/basic/operations/error/test_throwing_policy.cpp new file mode 100644 index 0000000..6e71a9f --- /dev/null +++ b/tests/basic/operations/error/test_throwing_policy.cpp @@ -0,0 +1,38 @@ +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.operations.operators; + +using namespace mcpplibs::primitives; + +TEST(OperationsTest, ThrowErrorPolicyThrowsException) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + EXPECT_THROW((void)operations::div(lhs, rhs), std::runtime_error); +} + +TEST(OperationsTest, ThrowErrorPolicyExceptionHasReasonMessage) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + try { + (void)operations::div(lhs, rhs); + FAIL() << "Expected std::runtime_error to be thrown"; + } catch (std::runtime_error const &e) { + EXPECT_NE(std::string(e.what()).find("division by zero"), + std::string::npos); + } catch (...) { + FAIL() << "Expected std::runtime_error"; + } +} diff --git a/tests/basic/operations/operators/test_framework_and_compound.cpp b/tests/basic/operations/operators/test_framework_and_compound.cpp new file mode 100644 index 0000000..8200afa --- /dev/null +++ b/tests/basic/operations/operators/test_framework_and_compound.cpp @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.operations.operators; + +using namespace mcpplibs::primitives; + +TEST(OperationsTest, PrimitiveAliasWorksWithFrameworkOperators) { + using namespace mcpplibs::primitives::types; + using namespace mcpplibs::primitives::operators; + using value_t = I32; + + auto const lhs = value_t{20}; + auto const rhs = value_t{22}; + + auto const result = lhs + rhs; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, PrimitiveAliasMixesWithBuiltinArithmeticExplicitly) { + using namespace mcpplibs::primitives::types; + using namespace mcpplibs::primitives::operators; + using value_t = I32; + + static_assert(!std::is_convertible_v); + + auto const lhs = value_t{40}; + auto const mixed = static_cast(lhs) + 2; + EXPECT_EQ(mixed, 42); + + auto const wrapped = value_t{mixed}; + auto const result = wrapped + value_t{1}; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 43); +} + +TEST(OperationsTest, MixedFrameworkOperatorsSupportUnderlyingOnBothSides) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{40}; + short const rhs = 2; + + auto const add_lr = lhs + rhs; + auto const add_rl = rhs + lhs; + auto const sub_lr = lhs - rhs; + auto const sub_rl = rhs - lhs; + auto const eq_lr = lhs == static_cast(40); + auto const eq_rl = static_cast(40) == lhs; + auto const ne_lr = lhs != static_cast(41); + auto const ne_rl = static_cast(41) != lhs; + auto const cmp_lr = lhs <=> rhs; + auto const cmp_rl = rhs <=> lhs; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(add_lr.has_value()); + ASSERT_TRUE(add_rl.has_value()); + ASSERT_TRUE(sub_lr.has_value()); + ASSERT_TRUE(sub_rl.has_value()); + ASSERT_TRUE(eq_lr.has_value()); + ASSERT_TRUE(eq_rl.has_value()); + ASSERT_TRUE(ne_lr.has_value()); + ASSERT_TRUE(ne_rl.has_value()); + ASSERT_TRUE(cmp_lr.has_value()); + ASSERT_TRUE(cmp_rl.has_value()); + + EXPECT_EQ(add_lr->value(), 42); + EXPECT_EQ(add_rl->value(), 42); + EXPECT_EQ(sub_lr->value(), 38); + EXPECT_EQ(sub_rl->value(), -38); + EXPECT_EQ(eq_lr->value(), 1); + EXPECT_EQ(eq_rl->value(), 1); + EXPECT_EQ(ne_lr->value(), 1); + EXPECT_EQ(ne_rl->value(), 1); + EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); + EXPECT_EQ(*cmp_rl, std::strong_ordering::less); +} + +TEST(OperationsTest, OperatorEqualDelegatesToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{7}; + auto const rhs = value_t{7}; + + auto const result = (lhs == rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 1); +} + +TEST(OperationsTest, OperatorPlusDelegatesToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{7}; + auto const rhs = value_t{8}; + + auto const result = lhs + rhs; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 15); +} + +TEST(OperationsTest, UnaryOperatorsDelegateToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{10}; + + auto const inc = ++value; + ASSERT_TRUE(inc.has_value()); + EXPECT_EQ(inc->value(), 11); + EXPECT_EQ(value.load(), 11); + + auto const dec = --value; + ASSERT_TRUE(dec.has_value()); + EXPECT_EQ(dec->value(), 10); + EXPECT_EQ(value.load(), 10); + + auto const post_inc = value++; + ASSERT_TRUE(post_inc.has_value()); + EXPECT_EQ(post_inc->value(), 10); + EXPECT_EQ(value.load(), 11); + + auto const post_dec = value--; + ASSERT_TRUE(post_dec.has_value()); + EXPECT_EQ(post_dec->value(), 11); + EXPECT_EQ(value.load(), 10); + + auto const pos = +value; + auto const neg = -value; + auto const inv = ~value; + + ASSERT_TRUE(pos.has_value()); + ASSERT_TRUE(neg.has_value()); + ASSERT_TRUE(inv.has_value()); + EXPECT_EQ(pos->value(), 10); + EXPECT_EQ(neg->value(), -10); + EXPECT_EQ(inv->value(), ~10); +} + +TEST(OperationsTest, NewBinaryOperatorsDelegateToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const lhs = value_t{12}; + auto const rhs = value_t{5}; + + auto const mod = lhs % rhs; + auto const shl = rhs << value_t{1}; + auto const shr = lhs >> value_t{2}; + auto const bit_and = lhs & value_t{10}; + auto const bit_or = lhs | value_t{10}; + auto const bit_xor = lhs ^ value_t{10}; + + ASSERT_TRUE(mod.has_value()); + ASSERT_TRUE(shl.has_value()); + ASSERT_TRUE(shr.has_value()); + ASSERT_TRUE(bit_and.has_value()); + ASSERT_TRUE(bit_or.has_value()); + ASSERT_TRUE(bit_xor.has_value()); + EXPECT_EQ(mod->value(), 2); + EXPECT_EQ(shl->value(), 10); + EXPECT_EQ(shr->value(), 3); + EXPECT_EQ(bit_and->value(), 8); + EXPECT_EQ(bit_or->value(), 14); + EXPECT_EQ(bit_xor->value(), 6); +} + +TEST(OperationsTest, NewCompoundAssignmentOperatorsMutateLhsOnSuccess) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{12}; + + auto mod = (value %= value_t{5}); + ASSERT_TRUE(mod.has_value()); + EXPECT_EQ(value.load(), 2); + + auto shl = (value <<= value_t{2}); + ASSERT_TRUE(shl.has_value()); + EXPECT_EQ(value.load(), 8); + + auto shr = (value >>= value_t{1}); + ASSERT_TRUE(shr.has_value()); + EXPECT_EQ(value.load(), 4); + + auto bit_and = (value &= value_t{6}); + ASSERT_TRUE(bit_and.has_value()); + EXPECT_EQ(value.load(), 4); + + auto bit_or = (value |= value_t{1}); + ASSERT_TRUE(bit_or.has_value()); + EXPECT_EQ(value.load(), 5); + + auto bit_xor = (value ^= value_t{7}); + ASSERT_TRUE(bit_xor.has_value()); + EXPECT_EQ(value.load(), 2); +} + +TEST(OperationsTest, ModulusAndShiftReportExpectedErrors) { + using value_t = + primitive; + + auto const mod_zero = operations::mod(value_t{7}, value_t{0}); + ASSERT_FALSE(mod_zero.has_value()); + EXPECT_EQ(mod_zero.error(), policy::error::kind::divide_by_zero); + + auto const invalid_shift = operations::shift_left( + value_t{1}, value_t{std::numeric_limits::digits + 1}); + ASSERT_FALSE(invalid_shift.has_value()); + EXPECT_EQ(invalid_shift.error(), policy::error::kind::domain_error); +} + +TEST(OperationsTest, ThreeWayCompareReturnsStrongOrderingForIntegers) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const less = (value_t{3} <=> value_t{7}); + auto const equal = (value_t{5} <=> value_t{5}); + auto const greater = (value_t{9} <=> value_t{1}); + + static_assert( + std::is_same_v); + + ASSERT_TRUE(less.has_value()); + ASSERT_TRUE(equal.has_value()); + ASSERT_TRUE(greater.has_value()); + EXPECT_EQ(*less, std::strong_ordering::less); + EXPECT_EQ(*equal, std::strong_ordering::equal); + EXPECT_EQ(*greater, std::strong_ordering::greater); +} + +TEST(OperationsTest, ThreeWayCompareReturnsPartialOrderingForFloatingPoint) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const less = (value_t{1.0} <=> value_t{2.0}); + auto const nan = std::numeric_limits::quiet_NaN(); + auto const unordered = (value_t{nan} <=> value_t{1.0}); + + static_assert(std::is_same_v); + + ASSERT_TRUE(less.has_value()); + ASSERT_TRUE(unordered.has_value()); + EXPECT_EQ(*less, std::partial_ordering::less); + EXPECT_EQ(*unordered, std::partial_ordering::unordered); +} + +TEST(OperationsTest, ThreeWayCompareOnBoolReturnsError) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const result = (value_t{false} <=> value_t{true}); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::unspecified); +} + +TEST(OperationsTest, CompoundAssignmentOperatorsMutateLhsOnSuccess) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{20}; + + auto add_result = (value += value_t{22}); + ASSERT_TRUE(add_result.has_value()); + EXPECT_EQ(value.load(), 42); + EXPECT_EQ(add_result->value(), 42); + + auto sub_result = (value -= value_t{2}); + ASSERT_TRUE(sub_result.has_value()); + EXPECT_EQ(value.load(), 40); + EXPECT_EQ(sub_result->value(), 40); + + auto mul_result = (value *= value_t{3}); + ASSERT_TRUE(mul_result.has_value()); + EXPECT_EQ(value.load(), 120); + EXPECT_EQ(mul_result->value(), 120); + + auto div_result = (value /= value_t{4}); + ASSERT_TRUE(div_result.has_value()); + EXPECT_EQ(value.load(), 30); + EXPECT_EQ(div_result->value(), 30); +} + +TEST(OperationsTest, CompoundAssignmentKeepsLhsWhenOperationFails) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{100}; + + auto result = (value /= value_t{0}); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); + EXPECT_EQ(value.load(), 100); +} + +TEST(OperationsTest, + CompoundAssignmentCheckedRejectsFloatingToIntegralOutOfRange) { + using lhs_t = primitive; + using rhs_t = + primitive; + + auto value = lhs_t{7}; + + auto const overflow_result = + operations::add_assign(value, rhs_t{std::numeric_limits::max()}); + ASSERT_FALSE(overflow_result.has_value()); + EXPECT_EQ(overflow_result.error(), policy::error::kind::overflow); + EXPECT_EQ(value.load(), 7); + + auto const domain_result = operations::add_assign( + value, rhs_t{std::numeric_limits::quiet_NaN()}); + ASSERT_FALSE(domain_result.has_value()); + EXPECT_EQ(domain_result.error(), policy::error::kind::domain_error); + EXPECT_EQ(value.load(), 7); +} + +TEST(OperationsTest, + CompoundAssignmentSupportsMixedTypesWithCompatibleTypePolicy) { + using namespace mcpplibs::primitives::operators; + + using lhs_t = primitive; + using rhs_t = primitive; + + auto value = lhs_t{10}; + + auto add_result = (value += rhs_t{32}); + + ASSERT_TRUE(add_result.has_value()); + EXPECT_EQ(value.load(), 42); + EXPECT_EQ(add_result->value(), 42); +} + +TEST(OperationsTest, + CompoundAssignmentKeepsLhsOnMixedTypeOverflowWithCompatibleTypePolicy) { + using namespace mcpplibs::primitives::operators; + + using lhs_t = primitive; + using rhs_t = primitive; + + auto value = lhs_t{static_cast(32000)}; + + auto add_result = (value += rhs_t{1000}); + + ASSERT_FALSE(add_result.has_value()); + EXPECT_EQ(add_result.error(), policy::error::kind::overflow); + EXPECT_EQ(value.load(), static_cast(32000)); +} diff --git a/tests/basic/operations/traits/test_capability_traits.cpp b/tests/basic/operations/traits/test_capability_traits.cpp new file mode 100644 index 0000000..14cab90 --- /dev/null +++ b/tests/basic/operations/traits/test_capability_traits.cpp @@ -0,0 +1,56 @@ +#include + +import mcpplibs.primitives.operations.traits; +import mcpplibs.primitives.operations.impl; + +using namespace mcpplibs::primitives; + +namespace { +struct NullCapabilityProbe {}; +} // namespace + +template <> struct operations::traits { + using op_tag = NullCapabilityProbe; + + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::none; +}; + +TEST(OperationTraitsTest, BuiltinArithmeticOperationsExposeCapability) { + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_FALSE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); +} + +TEST(OperationTraitsTest, BuiltinOperationsHaveNonNullCapabilityMask) { + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); +} + +TEST(OperationTraitsTest, NullCapabilityMaskIsDetectedAtCompileTime) { + static_assert(!operations::op_capability_valid_v); + EXPECT_FALSE((operations::op_capability_valid_v)); +} diff --git a/tests/basic/policy/concurrency/test_handler_access.cpp b/tests/basic/policy/concurrency/test_handler_access.cpp new file mode 100644 index 0000000..6cdd960 --- /dev/null +++ b/tests/basic/policy/concurrency/test_handler_access.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +import mcpplibs.primitives.policy; +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.operations.impl; + +using namespace mcpplibs::primitives; + +namespace { + +struct NonTriviallyCopyableRep { + int value{0}; + ~NonTriviallyCopyableRep() {} +}; + +struct LowAlignmentRep { + unsigned char bytes[sizeof(std::uint64_t)]{}; +}; + +} // namespace + +TEST(PolicyConcurrencyTest, FencedInjectsFences) { + using fenced_handler = + policy::concurrency::handler; + using single_handler = policy::concurrency::handler; + + auto const fenced_injection = fenced_handler::inject(); + auto const single_injection = single_handler::inject(); + + EXPECT_TRUE(fenced_injection.fence_before); + EXPECT_TRUE(fenced_injection.fence_after); + EXPECT_EQ(fenced_injection.order_before, std::memory_order_seq_cst); + EXPECT_EQ(fenced_injection.order_after, std::memory_order_seq_cst); + EXPECT_FALSE(single_injection.fence_before); + EXPECT_FALSE(single_injection.fence_after); +} + +TEST(PolicyConcurrencyTest, FencedVariantsUseExpectedMemoryOrders) { + using relaxed_handler = + policy::concurrency::handler; + using acq_rel_handler = + policy::concurrency::handler; + + auto const relaxed = relaxed_handler::inject(); + auto const acq_rel = acq_rel_handler::inject(); + + EXPECT_EQ(relaxed.order_before, std::memory_order_relaxed); + EXPECT_EQ(relaxed.order_after, std::memory_order_relaxed); + EXPECT_EQ(acq_rel.order_before, std::memory_order_acquire); + EXPECT_EQ(acq_rel.order_after, std::memory_order_release); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessHandlerProtocolByPolicy) { + EXPECT_TRUE( + (policy::concurrency::handler_access_available)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, int>)); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessRejectsNonTriviallyCopyableRep) { + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, NonTriviallyCopyableRep>)); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessRespectsAtomicRefAlignmentGate) { + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::none, LowAlignmentRep>)); + + constexpr bool requires_stronger_alignment = + std::atomic_ref::required_alignment > + alignof(LowAlignmentRep); + + if constexpr (requires_stronger_alignment) { + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, LowAlignmentRep>)); + } else { + GTEST_SKIP() << "platform atomic_ref required_alignment does not exceed " + "alignof(LowAlignmentRep)"; + } +} diff --git a/tests/basic/policy/protocol/test_handler_protocols.cpp b/tests/basic/policy/protocol/test_handler_protocols.cpp new file mode 100644 index 0000000..e0c2034 --- /dev/null +++ b/tests/basic/policy/protocol/test_handler_protocols.cpp @@ -0,0 +1,32 @@ +#include + +import mcpplibs.primitives.policy; +import mcpplibs.primitives.operations.impl; + +using namespace mcpplibs::primitives; + +TEST(PolicyProtocolTest, BuiltinHandlersSatisfyProtocolConcepts) { + static_assert(policy::type::handler_protocol); + static_assert(policy::concurrency::handler_protocol); + static_assert(policy::value::handler_protocol); + static_assert(policy::error::handler_protocol); + + EXPECT_TRUE((policy::type::handler_protocol)); + EXPECT_TRUE((policy::concurrency::handler_protocol)); + EXPECT_TRUE((policy::value::handler_protocol)); + EXPECT_TRUE((policy::error::handler_protocol)); +} diff --git a/tests/basic/policy/traits/test_builtin_policies.cpp b/tests/basic/policy/traits/test_builtin_policies.cpp new file mode 100644 index 0000000..2edf590 --- /dev/null +++ b/tests/basic/policy/traits/test_builtin_policies.cpp @@ -0,0 +1,62 @@ +#include +#include + +import mcpplibs.primitives.policy; + +using namespace mcpplibs::primitives; + +TEST(PolicyTraitsTest, BuiltinPoliciesHaveCategories) { + using namespace policy; + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::value); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::type); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::type); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::error); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::error); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::value); + + EXPECT_TRUE((policy_type)); + EXPECT_TRUE((policy_type)); + EXPECT_FALSE((policy_type)); + + EXPECT_TRUE( + (std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); +} diff --git a/tests/basic/primitive/traits/test_meta_traits.cpp b/tests/basic/primitive/traits/test_meta_traits.cpp new file mode 100644 index 0000000..59a4fe1 --- /dev/null +++ b/tests/basic/primitive/traits/test_meta_traits.cpp @@ -0,0 +1,102 @@ +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; + +#include "../../support/underlying_custom_types.hpp" + +using namespace mcpplibs::primitives; + +TEST(PrimitiveTraitsTest, MetaTraitsExposeValueTypeAndPrimitiveMetadata) { + using value_t = + primitive; + using traits_t = meta::traits; + + static_assert( + std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert( + std::same_as); + static_assert( + std::same_as); + static_assert(std::same_as); + + SUCCEED(); +} + +TEST(PrimitiveTraitsTest, MetaPrimitiveConceptsMatchUnderlyingCategory) { + using boolean_t = primitive; + using character_t = primitive; + using integer_t = primitive; + using floating_t = primitive; + + static_assert(meta::primitive_type); + static_assert(meta::primitive_type); + static_assert(!meta::primitive_type); + + static_assert(meta::boolean); + static_assert(!meta::boolean); + + static_assert(meta::character); + static_assert(!meta::character); + + static_assert(meta::integer); + static_assert(!meta::integer); + + static_assert(meta::floating); + static_assert(!meta::floating); + + static_assert(meta::numeric); + static_assert(meta::numeric); + static_assert(!meta::numeric); + static_assert(!meta::numeric); + static_assert(!meta::numeric); + + EXPECT_TRUE((meta::boolean)); + EXPECT_TRUE((meta::character)); + EXPECT_TRUE((meta::integer)); + EXPECT_TRUE((meta::floating)); +} + +TEST(PrimitiveTraitsTest, MetaPrimitiveLikeConceptsAcceptPrimitiveOrUnderlying) { + using boolean_t = primitive; + using character_t = primitive; + using integer_t = primitive; + using floating_t = primitive; + + static_assert(meta::primitive_like); + static_assert(meta::primitive_like); + static_assert(!meta::primitive_like); + + static_assert(meta::boolean_like); + static_assert(meta::boolean_like); + static_assert(!meta::boolean_like); + + static_assert(meta::character_like); + static_assert(meta::character_like); + static_assert(!meta::character_like); + + static_assert(meta::integer_like); + static_assert(meta::integer_like); + static_assert(!meta::integer_like); + + static_assert(meta::floating_like); + static_assert(meta::floating_like); + static_assert(!meta::floating_like); + + static_assert(meta::numeric_like); + static_assert(meta::numeric_like); + static_assert(meta::numeric_like); + static_assert(meta::numeric_like); + static_assert(!meta::numeric_like); + static_assert(!meta::numeric_like); + + EXPECT_TRUE((meta::primitive_like)); + EXPECT_TRUE((meta::primitive_like)); +} diff --git a/tests/basic/support/conversion_box_types.hpp b/tests/basic/support/conversion_box_types.hpp new file mode 100644 index 0000000..504bdbb --- /dev/null +++ b/tests/basic/support/conversion_box_types.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +namespace mcpplibs::primitives::test_support::conversion { + +struct SignedBox { + long long value; +}; + +struct UnsignedBox { + std::uint16_t value; +}; + +struct FloatBox { + double value; +}; + +} // namespace mcpplibs::primitives::test_support::conversion + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::conversion::SignedBox> { + using value_type = mcpplibs::primitives::test_support::conversion::SignedBox; + using rep_type = long long; + + static constexpr bool enabled = true; + static constexpr auto kind = mcpplibs::primitives::underlying::category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::conversion::UnsignedBox> { + using value_type = mcpplibs::primitives::test_support::conversion::UnsignedBox; + using rep_type = std::uint16_t; + + static constexpr bool enabled = true; + static constexpr auto kind = mcpplibs::primitives::underlying::category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::conversion::FloatBox> { + using value_type = mcpplibs::primitives::test_support::conversion::FloatBox; + using rep_type = double; + + static constexpr bool enabled = true; + static constexpr auto kind = + mcpplibs::primitives::underlying::category::floating; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; diff --git a/tests/basic/support/underlying_custom_types.hpp b/tests/basic/support/underlying_custom_types.hpp new file mode 100644 index 0000000..3df58b5 --- /dev/null +++ b/tests/basic/support/underlying_custom_types.hpp @@ -0,0 +1,416 @@ +#pragma once + +namespace mcpplibs::primitives::test_support::underlying { + +struct UserInteger { + int value; +}; + +struct NotRegistered { + int value; +}; + +struct BadRep { + int value; +}; + +struct BadKind { + int value; +}; + +struct BigIntLike { + long long value; + + friend constexpr auto operator+(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(BigIntLike lhs, BigIntLike rhs) noexcept + -> bool { + return lhs.value == rhs.value; + } +}; + +struct BadCustomKind { + long long value; + + friend constexpr auto operator+(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct MissingDivisionLike { + long long value; + + friend constexpr auto operator+(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator==(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct NonNegativeInt { + int value; +}; + +struct ExplicitCommonLhs { + int value; +}; + +struct ExplicitCommonRhs { + int value; +}; + +struct ExplicitCommonRep { + long long value; + + friend constexpr auto operator+(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct VoidCommonLhs { + int value; +}; + +struct VoidCommonRhs { + int value; +}; + +} // namespace mcpplibs::primitives::test_support::underlying + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::UserInteger> { + using value_type = mcpplibs::primitives::test_support::underlying::UserInteger; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::BadRep> { + using value_type = mcpplibs::primitives::test_support::underlying::BadRep; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::BadKind> { + using value_type = mcpplibs::primitives::test_support::underlying::BadKind; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::floating; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::BigIntLike> { + using value_type = mcpplibs::primitives::test_support::underlying::BigIntLike; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::BadCustomKind> { + using value_type = + mcpplibs::primitives::test_support::underlying::BadCustomKind; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::boolean; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::MissingDivisionLike> { + using value_type = + mcpplibs::primitives::test_support::underlying::MissingDivisionLike; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::NonNegativeInt> { + using value_type = + mcpplibs::primitives::test_support::underlying::NonNegativeInt; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type value) noexcept -> bool { + return value >= 0; + } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::ExplicitCommonLhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::ExplicitCommonLhs; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits< + mcpplibs::primitives::test_support::underlying::ExplicitCommonLhs, + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs> { + using type = mcpplibs::primitives::test_support::underlying::ExplicitCommonRep; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits< + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs, + mcpplibs::primitives::test_support::underlying::ExplicitCommonLhs> { + using type = mcpplibs::primitives::test_support::underlying::ExplicitCommonRep; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::VoidCommonLhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::VoidCommonLhs; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::VoidCommonRhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::VoidCommonRhs; + using rep_type = short; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return static_cast(value.value); + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits { + using type = void; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits { + using type = void; + static constexpr bool enabled = true; +}; diff --git a/tests/basic/test_operations.cpp b/tests/basic/test_operations.cpp deleted file mode 100644 index 0b5e512..0000000 --- a/tests/basic/test_operations.cpp +++ /dev/null @@ -1,1075 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -import mcpplibs.primitives; - -using namespace mcpplibs::primitives; - -template -concept constructible_from_underlying = - underlying_type && requires(U u) { - Primitive{u}; -}; - -template -concept storable_from_underlying = - underlying_type && requires(Primitive p, U u) { - p.store(u); -}; - -template -concept cas_from_underlying = - underlying_type && requires(Primitive p, U expected, U desired) { - { p.compare_exchange(expected, desired) } -> std::same_as; -}; - -TEST(OperationsTest, AddReturnsExpectedPrimitive) { - using lhs_t = primitive; - using rhs_t = primitive; - - auto const lhs = lhs_t{10}; - auto const rhs = rhs_t{32}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, DivisionByZeroReturnsError) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{0}; - - auto const result = operations::div(lhs, rhs); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); -} - -TEST(OperationsTest, SaturatingAdditionClampsUnsignedOverflow) { - using value_t = primitive; - - auto const lhs = value_t{static_cast(65530)}; - auto const rhs = value_t{static_cast(20)}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), static_cast(65535)); -} - -TEST(OperationsTest, CheckedAdditionReportsUnsignedOverflow) { - using value_t = - primitive; - - auto const lhs = value_t{static_cast(65530)}; - auto const rhs = value_t{static_cast(20)}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::overflow); -} - -TEST(OperationsTest, UncheckedAdditionWrapsUnsignedOverflow) { - using value_t = primitive; - - auto const lhs = value_t{static_cast(65530)}; - auto const rhs = value_t{static_cast(20)}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), static_cast(14)); -} - -TEST(OperationsTest, UncheckedDivisionUsesRawArithmeticWhenValid) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{4}; - - auto const result = operations::div(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 25); -} - -TEST(OperationsTest, FencedPolicyPathReturnsExpectedValue) { - using value_t = - primitive; - - auto const lhs = value_t{12}; - auto const rhs = value_t{30}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, FencedPolicyConcurrentInvocationsRemainConsistent) { - using value_t = - primitive; - - constexpr int kThreadCount = 8; - constexpr int kIterationsPerThread = 20000; - - auto const lhs = value_t{12}; - auto const rhs = value_t{30}; - - std::atomic mismatch_count{0}; - std::atomic error_count{0}; - std::atomic start{false}; - - std::vector workers; - workers.reserve(kThreadCount); - - for (int i = 0; i < kThreadCount; ++i) { - workers.emplace_back([&]() { - while (!start.load(std::memory_order_acquire)) { - } - - for (int n = 0; n < kIterationsPerThread; ++n) { - auto const result = operations::add(lhs, rhs); - if (!result.has_value()) { - error_count.fetch_add(1, std::memory_order_relaxed); - continue; - } - - if (result->value() != 42) { - mismatch_count.fetch_add(1, std::memory_order_relaxed); - } - } - }); - } - - start.store(true, std::memory_order_release); - - for (auto &worker : workers) { - worker.join(); - } - - EXPECT_EQ(error_count.load(std::memory_order_relaxed), 0); - EXPECT_EQ(mismatch_count.load(std::memory_order_relaxed), 0); -} - -TEST(OperationsTest, PrimitiveFencedLoadStoreAndCasWork) { - using value_t = - primitive; - - auto value = value_t{1}; - EXPECT_EQ(value.load(), 1); - - value.store(4); - EXPECT_EQ(value.load(), 4); - - auto expected = 4; - EXPECT_TRUE(value.compare_exchange(expected, 7)); - EXPECT_EQ(value.load(), 7); - - expected = 9; - EXPECT_FALSE(value.compare_exchange(expected, 11)); - EXPECT_EQ(expected, 7); -} - -TEST(OperationsTest, PrimitiveFencedCasSupportsConcurrentIncrements) { - using value_t = - primitive; - - constexpr int kThreadCount = 6; - constexpr int kIterationsPerThread = 5000; - - auto counter = value_t{0}; - std::vector workers; - workers.reserve(kThreadCount); - - for (int i = 0; i < kThreadCount; ++i) { - workers.emplace_back([&]() { - for (int n = 0; n < kIterationsPerThread; ++n) { - auto expected = counter.load(); - while (!counter.compare_exchange(expected, expected + 1)) { - } - } - }); - } - - for (auto &worker : workers) { - worker.join(); - } - - EXPECT_EQ(counter.load(), kThreadCount * kIterationsPerThread); -} - -TEST(OperationsTest, - BinaryOperationsWithLoadStoreRemainStableUnderHighConcurrency) { - using value_t = - primitive; - - constexpr int kWriterThreads = 6; - constexpr int kReaderThreads = 8; - constexpr int kIterationsPerThread = 25000; - constexpr int kMaxOperand = 100000; - - auto lhs = value_t{0}; - auto rhs = value_t{0}; - auto sink = value_t{0}; - - std::atomic add_error_count{0}; - std::atomic sub_error_count{0}; - std::atomic range_violation_count{0}; - std::atomic start{false}; - - std::vector workers; - workers.reserve(kWriterThreads + kReaderThreads); - - for (int writer = 0; writer < kWriterThreads; ++writer) { - workers.emplace_back([&, writer]() { - while (!start.load(std::memory_order_acquire)) { - } - - for (int n = 0; n < kIterationsPerThread; ++n) { - auto const v1 = (writer + n) % (kMaxOperand + 1); - auto const v2 = (writer * 3 + n * 7) % (kMaxOperand + 1); - lhs.store(v1); - rhs.store(v2); - } - }); - } - - for (int reader = 0; reader < kReaderThreads; ++reader) { - workers.emplace_back([&, reader]() { - while (!start.load(std::memory_order_acquire)) { - } - - for (int n = 0; n < kIterationsPerThread; ++n) { - if (((reader + n) & 1) == 0) { - auto const out = operations::add(lhs, rhs); - if (!out.has_value()) { - add_error_count.fetch_add(1, std::memory_order_relaxed); - continue; - } - - auto const v = out->load(); - if (v < 0 || v > (kMaxOperand * 2)) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - sink.store(v); - auto const snapshot = sink.load(); - if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - continue; - } - - auto const out = operations::sub(lhs, rhs); - if (!out.has_value()) { - sub_error_count.fetch_add(1, std::memory_order_relaxed); - continue; - } - - auto const v = out->load(); - if (v < -kMaxOperand || v > kMaxOperand) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - sink.store(v); - auto const snapshot = sink.load(); - if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - } - }); - } - - start.store(true, std::memory_order_release); - - for (auto &worker : workers) { - worker.join(); - } - - EXPECT_EQ(add_error_count.load(std::memory_order_relaxed), 0); - EXPECT_EQ(sub_error_count.load(std::memory_order_relaxed), 0); - EXPECT_EQ(range_violation_count.load(std::memory_order_relaxed), 0); -} - -TEST(OperationsTest, PrimitiveSupportsCopyAndMoveSpecialMembers) { - using value_t = primitive; - - static_assert(std::is_copy_constructible_v); - static_assert(std::is_copy_assignable_v); - static_assert(std::is_move_constructible_v); - static_assert(std::is_move_assignable_v); - - auto original = value_t{42}; - auto copy_constructed = value_t{original}; - EXPECT_EQ(copy_constructed.load(), 42); - - auto copy_assigned = value_t{0}; - copy_assigned = original; - EXPECT_EQ(copy_assigned.load(), 42); - - auto move_constructed = value_t{std::move(copy_assigned)}; - EXPECT_EQ(move_constructed.load(), 42); - - auto move_assigned = value_t{0}; - move_assigned = std::move(move_constructed); - EXPECT_EQ(move_assigned.load(), 42); -} - -TEST(OperationsTest, StrictTypeRejectsMixedTypesAtCompileTime) { - using lhs_t = primitive; - using rhs_t = primitive; - - using strict_handler = - policy::type::handler; - using strict_meta = operations::dispatcher_meta; - - static_assert(strict_handler::enabled); - static_assert(!strict_handler::allowed); - static_assert(std::is_same_v); - - EXPECT_EQ(strict_handler::diagnostic_id, 1u); -} - -TEST(OperationsTest, StrictTypeAllowsSameTypeAtRuntime) { - using value_t = primitive; - - auto const lhs = value_t{19}; - auto const rhs = value_t{23}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, StrictTypePrimitiveConstructorRejectsCrossUnderlyingType) { - using strict_t = primitive; - - static_assert(constructible_from_underlying); - static_assert(!constructible_from_underlying); - - auto const value = strict_t{42}; - EXPECT_EQ(value.load(), 42); -} - -TEST(OperationsTest, CompatibleTypePrimitiveConstructorAllowsSameCategory) { - using compatible_t = - primitive; - - static_assert(constructible_from_underlying); - static_assert(!constructible_from_underlying); - - auto const value = compatible_t{static_cast(42)}; - EXPECT_EQ(value.load(), 42); -} - -TEST(OperationsTest, TransparentTypePrimitiveConstructorAllowsCrossCategory) { - using transparent_t = - primitive; - - static_assert(constructible_from_underlying); - - auto const value = transparent_t{42.75}; - EXPECT_EQ(value.load(), 42); -} - -TEST(OperationsTest, PrimitiveStoreAndCasRejectCrossUnderlyingWithStrictType) { - using strict_t = primitive; - - static_assert(!storable_from_underlying); - static_assert(!cas_from_underlying); -} - -TEST(OperationsTest, PrimitiveStoreAndCasAllowCompatibleSameCategory) { - using compatible_t = - primitive; - - static_assert(storable_from_underlying); - static_assert(cas_from_underlying); - static_assert(!storable_from_underlying); - static_assert(!cas_from_underlying); - - auto value = compatible_t{10}; - value.store(static_cast(12)); - EXPECT_EQ(value.load(), 12); - - short expected = 12; - EXPECT_TRUE(value.compare_exchange(expected, static_cast(20))); - EXPECT_EQ(value.load(), 20); - - expected = 11; - EXPECT_FALSE(value.compare_exchange(expected, static_cast(30))); - EXPECT_EQ(expected, 20); -} - -TEST(OperationsTest, PrimitiveStoreAndCasAllowTransparentCrossCategory) { - using transparent_t = - primitive; - - static_assert(storable_from_underlying); - static_assert(cas_from_underlying); - - auto value = transparent_t{10}; - value.store(42.75); - EXPECT_EQ(value.load(), 42); - - double expected = 42.0; - EXPECT_TRUE(value.compare_exchange(expected, 99.5)); - EXPECT_EQ(value.load(), 99); - - expected = 3.0; - EXPECT_FALSE(value.compare_exchange(expected, 1.0)); - EXPECT_EQ(expected, 99.0); -} - -TEST(OperationsTest, PrimitiveTransparentStoreClampsOutOfRangeFloatingInput) { - using transparent_t = - primitive; - - auto value = transparent_t{0}; - - value.store(std::numeric_limits::infinity()); - EXPECT_EQ(value.load(), std::numeric_limits::max()); - - value.store(-std::numeric_limits::infinity()); - EXPECT_EQ(value.load(), std::numeric_limits::lowest()); - - value.store(std::numeric_limits::quiet_NaN()); - EXPECT_EQ(value.load(), 0); -} - -TEST(OperationsTest, PrimitiveSpecialMembersSupportCrossUnderlyingWithCompatibleType) { - using dst_t = - primitive; - using src_t = - primitive; - - static_assert(std::is_constructible_v); - static_assert(std::is_constructible_v); - static_assert(std::is_assignable_v); - static_assert(std::is_assignable_v); - - auto const source = src_t{40}; - auto copy_constructed = dst_t{source}; - EXPECT_EQ(copy_constructed.load(), 40); - - auto move_constructed = dst_t{src_t{41}}; - EXPECT_EQ(move_constructed.load(), 41); - - auto copy_assigned = dst_t{0}; - copy_assigned = source; - EXPECT_EQ(copy_assigned.load(), 40); - - auto move_assigned = dst_t{0}; - auto move_source = src_t{42}; - move_assigned = std::move(move_source); - EXPECT_EQ(move_assigned.load(), 42); -} - -TEST(OperationsTest, - PrimitiveSpecialMembersRejectCrossUnderlyingWithStrictType) { - using dst_t = primitive; - using src_t = primitive; - - static_assert(!std::is_constructible_v); - static_assert(!std::is_constructible_v); - static_assert(!std::is_assignable_v); - static_assert(!std::is_assignable_v); -} - -TEST(OperationsTest, DispatcherMetaTracksResolvedPolicyGroupConsistency) { - using aligned_lhs_t = primitive; - using aligned_rhs_t = primitive; - using aligned_meta = operations::dispatcher_meta; - - using mismatch_lhs_t = primitive; - using mismatch_rhs_t = primitive; - using mismatch_meta = - operations::dispatcher_meta; - - static_assert(aligned_meta::policy_group_consistent); - static_assert(!mismatch_meta::policy_group_consistent); - - EXPECT_TRUE(aligned_meta::policy_group_consistent); - EXPECT_FALSE(mismatch_meta::policy_group_consistent); -} - -TEST(OperationsTest, CompatibleTypeRequiresSameUnderlyingCategory) { - using same_category_handler = - policy::type::handler; - using cross_category_handler = - policy::type::handler; - - static_assert(same_category_handler::enabled); - static_assert(same_category_handler::allowed); - static_assert(cross_category_handler::enabled); - static_assert(!cross_category_handler::allowed); - - EXPECT_EQ(cross_category_handler::diagnostic_id, 2u); -} - -TEST(OperationsTest, TransparentTypeIgnoresCategoryWhenCommonRepIsValid) { - using transparent_handler = - policy::type::handler; - - static_assert(transparent_handler::enabled); - static_assert(transparent_handler::allowed); - static_assert( - std::is_same_v); - - EXPECT_EQ(transparent_handler::diagnostic_id, 0u); -} - -TEST(OperationsTest, BoolUnderlyingRejectsArithmeticOperationsAtCompileTime) { - using value_t = primitive; - using bool_handler = policy::type::handler; - using bool_meta = operations::dispatcher_meta; - - static_assert(bool_handler::enabled); - static_assert(!bool_handler::allowed); - static_assert(std::is_same_v); - - EXPECT_EQ(bool_handler::diagnostic_id, 3u); -} - -TEST(OperationsTest, CharUnderlyingRejectsArithmeticEvenWithTransparentType) { - using value_t = primitive; - using char_handler = policy::type::handler; - using char_meta = operations::dispatcher_meta; - - static_assert(char_handler::enabled); - static_assert(!char_handler::allowed); - static_assert(std::is_same_v); - - EXPECT_EQ(char_handler::diagnostic_id, 3u); -} - -TEST(OperationsTest, SignedAndUnsignedCharRejectArithmeticAtCompileTime) { - using signed_handler = - policy::type::handler; - using unsigned_handler = - policy::type::handler; - - static_assert(signed_handler::enabled); - static_assert(!signed_handler::allowed); - static_assert(unsigned_handler::enabled); - static_assert(!unsigned_handler::allowed); - - EXPECT_EQ(signed_handler::diagnostic_id, 3u); - EXPECT_EQ(unsigned_handler::diagnostic_id, 3u); -} - -TEST(OperationsTest, BoolUnderlyingAllowsComparisonOperations) { - using value_t = primitive; - - auto const lhs = value_t{true}; - auto const rhs = value_t{false}; - - auto const eq_result = operations::equal(lhs, rhs); - auto const ne_result = operations::not_equal(lhs, rhs); - - ASSERT_TRUE(eq_result.has_value()); - ASSERT_TRUE(ne_result.has_value()); - EXPECT_FALSE(eq_result->value()); - EXPECT_TRUE(ne_result->value()); -} - -TEST(OperationsTest, CharUnderlyingAllowsComparisonWithTransparentType) { - using value_t = primitive; - - auto const lhs = value_t{'a'}; - auto const rhs = value_t{'a'}; - - auto const eq_result = operations::equal(lhs, rhs); - - ASSERT_TRUE(eq_result.has_value()); - EXPECT_EQ(eq_result->value(), static_cast(1)); -} - -TEST(OperationsTest, PrimitiveAliasWorksWithFrameworkOperators) { - using namespace mcpplibs::primitives::types; - using namespace mcpplibs::primitives::operators; - using value_t = I32; - - auto const lhs = value_t{20}; - auto const rhs = value_t{22}; - - auto const result = lhs + rhs; - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, PrimitiveAliasMixesWithBuiltinArithmeticExplicitly) { - using namespace mcpplibs::primitives::types; - using namespace mcpplibs::primitives::operators; - using value_t = I32; - - static_assert(!std::is_convertible_v); - - auto const lhs = value_t{40}; - auto const mixed = static_cast(lhs) + 2; - EXPECT_EQ(mixed, 42); - - auto const wrapped = value_t{mixed}; - auto const result = wrapped + value_t{1}; - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 43); -} - -TEST(OperationsTest, MixedBinaryOperationsSupportUnderlyingOnBothSides) { - using value_t = primitive; - - auto const lhs = value_t{40}; - short const rhs = 2; - - auto const apply_lr = operations::apply(lhs, rhs); - auto const apply_rl = operations::apply(rhs, lhs); - auto const add_lr = operations::add(lhs, rhs); - auto const add_rl = operations::add(rhs, lhs); - auto const sub_lr = operations::sub(lhs, rhs); - auto const sub_rl = operations::sub(rhs, lhs); - auto const div_lr = operations::div(lhs, rhs); - auto const div_rl = operations::div(rhs, lhs); - auto const cmp_lr = operations::three_way_compare(lhs, rhs); - auto const cmp_rl = operations::three_way_compare(rhs, lhs); - - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - - ASSERT_TRUE(apply_lr.has_value()); - ASSERT_TRUE(apply_rl.has_value()); - ASSERT_TRUE(add_lr.has_value()); - ASSERT_TRUE(add_rl.has_value()); - ASSERT_TRUE(sub_lr.has_value()); - ASSERT_TRUE(sub_rl.has_value()); - ASSERT_TRUE(div_lr.has_value()); - ASSERT_TRUE(div_rl.has_value()); - ASSERT_TRUE(cmp_lr.has_value()); - ASSERT_TRUE(cmp_rl.has_value()); - - EXPECT_EQ(apply_lr->value(), 42); - EXPECT_EQ(apply_rl->value(), 42); - EXPECT_EQ(add_lr->value(), 42); - EXPECT_EQ(add_rl->value(), 42); - EXPECT_EQ(sub_lr->value(), 38); - EXPECT_EQ(sub_rl->value(), -38); - EXPECT_EQ(div_lr->value(), 20); - EXPECT_EQ(div_rl->value(), 0); - EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); - EXPECT_EQ(*cmp_rl, std::strong_ordering::less); -} - -TEST(OperationsTest, MixedFrameworkOperatorsSupportUnderlyingOnBothSides) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto const lhs = value_t{40}; - short const rhs = 2; - - auto const add_lr = lhs + rhs; - auto const add_rl = rhs + lhs; - auto const sub_lr = lhs - rhs; - auto const sub_rl = rhs - lhs; - auto const eq_lr = lhs == static_cast(40); - auto const eq_rl = static_cast(40) == lhs; - auto const ne_lr = lhs != static_cast(41); - auto const ne_rl = static_cast(41) != lhs; - auto const cmp_lr = lhs <=> rhs; - auto const cmp_rl = rhs <=> lhs; - - static_assert(std::is_same_v); - static_assert(std::is_same_v); - - ASSERT_TRUE(add_lr.has_value()); - ASSERT_TRUE(add_rl.has_value()); - ASSERT_TRUE(sub_lr.has_value()); - ASSERT_TRUE(sub_rl.has_value()); - ASSERT_TRUE(eq_lr.has_value()); - ASSERT_TRUE(eq_rl.has_value()); - ASSERT_TRUE(ne_lr.has_value()); - ASSERT_TRUE(ne_rl.has_value()); - ASSERT_TRUE(cmp_lr.has_value()); - ASSERT_TRUE(cmp_rl.has_value()); - - EXPECT_EQ(add_lr->value(), 42); - EXPECT_EQ(add_rl->value(), 42); - EXPECT_EQ(sub_lr->value(), 38); - EXPECT_EQ(sub_rl->value(), -38); - EXPECT_EQ(eq_lr->value(), 1); - EXPECT_EQ(eq_rl->value(), 1); - EXPECT_EQ(ne_lr->value(), 1); - EXPECT_EQ(ne_rl->value(), 1); - EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); - EXPECT_EQ(*cmp_rl, std::strong_ordering::less); -} - -TEST(OperationsTest, OperatorEqualDelegatesToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto const lhs = value_t{7}; - auto const rhs = value_t{7}; - - auto const result = (lhs == rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 1); -} - -TEST(OperationsTest, OperatorPlusDelegatesToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto const lhs = value_t{7}; - auto const rhs = value_t{8}; - - auto const result = lhs + rhs; - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 15); -} - -TEST(OperationsTest, UnaryOperatorsDelegateToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto value = value_t{10}; - - auto const inc = ++value; - ASSERT_TRUE(inc.has_value()); - EXPECT_EQ(inc->value(), 11); - EXPECT_EQ(value.load(), 11); - - auto const dec = --value; - ASSERT_TRUE(dec.has_value()); - EXPECT_EQ(dec->value(), 10); - EXPECT_EQ(value.load(), 10); - - auto const post_inc = value++; - ASSERT_TRUE(post_inc.has_value()); - EXPECT_EQ(post_inc->value(), 10); - EXPECT_EQ(value.load(), 11); - - auto const post_dec = value--; - ASSERT_TRUE(post_dec.has_value()); - EXPECT_EQ(post_dec->value(), 11); - EXPECT_EQ(value.load(), 10); - - auto const pos = +value; - auto const neg = -value; - auto const inv = ~value; - - ASSERT_TRUE(pos.has_value()); - ASSERT_TRUE(neg.has_value()); - ASSERT_TRUE(inv.has_value()); - EXPECT_EQ(pos->value(), 10); - EXPECT_EQ(neg->value(), -10); - EXPECT_EQ(inv->value(), ~10); -} - -TEST(OperationsTest, NewBinaryOperatorsDelegateToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const lhs = value_t{12}; - auto const rhs = value_t{5}; - - auto const mod = lhs % rhs; - auto const shl = rhs << value_t{1}; - auto const shr = lhs >> value_t{2}; - auto const bit_and = lhs & value_t{10}; - auto const bit_or = lhs | value_t{10}; - auto const bit_xor = lhs ^ value_t{10}; - - ASSERT_TRUE(mod.has_value()); - ASSERT_TRUE(shl.has_value()); - ASSERT_TRUE(shr.has_value()); - ASSERT_TRUE(bit_and.has_value()); - ASSERT_TRUE(bit_or.has_value()); - ASSERT_TRUE(bit_xor.has_value()); - EXPECT_EQ(mod->value(), 2); - EXPECT_EQ(shl->value(), 10); - EXPECT_EQ(shr->value(), 3); - EXPECT_EQ(bit_and->value(), 8); - EXPECT_EQ(bit_or->value(), 14); - EXPECT_EQ(bit_xor->value(), 6); -} - -TEST(OperationsTest, NewCompoundAssignmentOperatorsMutateLhsOnSuccess) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto value = value_t{12}; - - auto mod = (value %= value_t{5}); - ASSERT_TRUE(mod.has_value()); - EXPECT_EQ(value.load(), 2); - - auto shl = (value <<= value_t{2}); - ASSERT_TRUE(shl.has_value()); - EXPECT_EQ(value.load(), 8); - - auto shr = (value >>= value_t{1}); - ASSERT_TRUE(shr.has_value()); - EXPECT_EQ(value.load(), 4); - - auto bit_and = (value &= value_t{6}); - ASSERT_TRUE(bit_and.has_value()); - EXPECT_EQ(value.load(), 4); - - auto bit_or = (value |= value_t{1}); - ASSERT_TRUE(bit_or.has_value()); - EXPECT_EQ(value.load(), 5); - - auto bit_xor = (value ^= value_t{7}); - ASSERT_TRUE(bit_xor.has_value()); - EXPECT_EQ(value.load(), 2); -} - -TEST(OperationsTest, ModulusAndShiftReportExpectedErrors) { - using value_t = - primitive; - - auto const mod_zero = operations::mod(value_t{7}, value_t{0}); - ASSERT_FALSE(mod_zero.has_value()); - EXPECT_EQ(mod_zero.error(), policy::error::kind::divide_by_zero); - - auto const invalid_shift = operations::shift_left( - value_t{1}, value_t{std::numeric_limits::digits + 1}); - ASSERT_FALSE(invalid_shift.has_value()); - EXPECT_EQ(invalid_shift.error(), policy::error::kind::domain_error); -} - -TEST(OperationsTest, ThreeWayCompareReturnsStrongOrderingForIntegers) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const less = (value_t{3} <=> value_t{7}); - auto const equal = (value_t{5} <=> value_t{5}); - auto const greater = (value_t{9} <=> value_t{1}); - - static_assert( - std::is_same_v); - - ASSERT_TRUE(less.has_value()); - ASSERT_TRUE(equal.has_value()); - ASSERT_TRUE(greater.has_value()); - EXPECT_EQ(*less, std::strong_ordering::less); - EXPECT_EQ(*equal, std::strong_ordering::equal); - EXPECT_EQ(*greater, std::strong_ordering::greater); -} - -TEST(OperationsTest, ThreeWayCompareReturnsPartialOrderingForFloatingPoint) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const less = (value_t{1.0} <=> value_t{2.0}); - auto const nan = std::numeric_limits::quiet_NaN(); - auto const unordered = (value_t{nan} <=> value_t{1.0}); - - static_assert(std::is_same_v); - - ASSERT_TRUE(less.has_value()); - ASSERT_TRUE(unordered.has_value()); - EXPECT_EQ(*less, std::partial_ordering::less); - EXPECT_EQ(*unordered, std::partial_ordering::unordered); -} - -TEST(OperationsTest, ThreeWayCompareOnBoolReturnsError) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const result = (value_t{false} <=> value_t{true}); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::unspecified); -} - -TEST(OperationsTest, CompoundAssignmentOperatorsMutateLhsOnSuccess) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto value = value_t{20}; - - auto add_result = (value += value_t{22}); - ASSERT_TRUE(add_result.has_value()); - EXPECT_EQ(value.load(), 42); - EXPECT_EQ(add_result->value(), 42); - - auto sub_result = (value -= value_t{2}); - ASSERT_TRUE(sub_result.has_value()); - EXPECT_EQ(value.load(), 40); - EXPECT_EQ(sub_result->value(), 40); - - auto mul_result = (value *= value_t{3}); - ASSERT_TRUE(mul_result.has_value()); - EXPECT_EQ(value.load(), 120); - EXPECT_EQ(mul_result->value(), 120); - - auto div_result = (value /= value_t{4}); - ASSERT_TRUE(div_result.has_value()); - EXPECT_EQ(value.load(), 30); - EXPECT_EQ(div_result->value(), 30); -} - -TEST(OperationsTest, CompoundAssignmentKeepsLhsWhenOperationFails) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto value = value_t{100}; - - auto result = (value /= value_t{0}); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); - EXPECT_EQ(value.load(), 100); -} - -TEST(OperationsTest, - CompoundAssignmentCheckedRejectsFloatingToIntegralOutOfRange) { - using lhs_t = primitive; - using rhs_t = - primitive; - - auto value = lhs_t{7}; - - auto const overflow_result = - operations::add_assign(value, rhs_t{std::numeric_limits::max()}); - ASSERT_FALSE(overflow_result.has_value()); - EXPECT_EQ(overflow_result.error(), policy::error::kind::overflow); - EXPECT_EQ(value.load(), 7); - - auto const domain_result = operations::add_assign( - value, rhs_t{std::numeric_limits::quiet_NaN()}); - ASSERT_FALSE(domain_result.has_value()); - EXPECT_EQ(domain_result.error(), policy::error::kind::domain_error); - EXPECT_EQ(value.load(), 7); -} - -TEST(OperationsTest, CompoundAssignmentSupportsMixedTypesWithCompatibleTypePolicy) { - using namespace mcpplibs::primitives::operators; - - using lhs_t = primitive; - using rhs_t = primitive; - - auto value = lhs_t{10}; - - auto add_result = (value += rhs_t{32}); - - ASSERT_TRUE(add_result.has_value()); - EXPECT_EQ(value.load(), 42); - EXPECT_EQ(add_result->value(), 42); -} - -TEST(OperationsTest, - CompoundAssignmentKeepsLhsOnMixedTypeOverflowWithCompatibleTypePolicy) { - using namespace mcpplibs::primitives::operators; - - using lhs_t = primitive; - using rhs_t = primitive; - - auto value = lhs_t{static_cast(32000)}; - - auto add_result = (value += rhs_t{1000}); - - ASSERT_FALSE(add_result.has_value()); - EXPECT_EQ(add_result.error(), policy::error::kind::overflow); - EXPECT_EQ(value.load(), static_cast(32000)); -} - -TEST(OperationsTest, ThrowErrorPolicyThrowsException) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{0}; - - EXPECT_THROW((void)operations::div(lhs, rhs), std::runtime_error); -} - -TEST(OperationsTest, ThrowErrorPolicyExceptionHasReasonMessage) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{0}; - - try { - (void)operations::div(lhs, rhs); - FAIL() << "Expected std::runtime_error to be thrown"; - } catch (std::runtime_error const &e) { - EXPECT_NE(std::string(e.what()).find("division by zero"), - std::string::npos); - } catch (...) { - FAIL() << "Expected std::runtime_error"; - } -} diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp deleted file mode 100644 index 36b0e66..0000000 --- a/tests/basic/test_policies.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include -#include -#include -import mcpplibs.primitives; - -using namespace mcpplibs::primitives; - -namespace { -struct NullCapabilityProbe {}; - -struct NonTriviallyCopyableRep { - int value{0}; - ~NonTriviallyCopyableRep() {} -}; - -struct LowAlignmentRep { - unsigned char bytes[sizeof(std::uint64_t)]{}; -}; -} // namespace - -template <> struct operations::traits { - using op_tag = NullCapabilityProbe; - - static constexpr bool enabled = true; - static constexpr auto arity = dimension::binary; - static constexpr auto capability_mask = capability::none; -}; - -TEST(PolicyTraitsTest, BuiltinPoliciesHaveCategories) { - using namespace policy; - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::value); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::type); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::type); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::error); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::error); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::value); - - EXPECT_TRUE((policy_type)); - EXPECT_TRUE((policy_type)); - EXPECT_FALSE((policy_type)); - - EXPECT_TRUE( - (std::is_same_v)); - EXPECT_TRUE((std::is_same_v)); -} - -TEST(PolicyConcurrencyTest, FencedInjectsFences) { - using fenced_handler = - policy::concurrency::handler; - using single_handler = policy::concurrency::handler; - - auto const fenced_injection = fenced_handler::inject(); - auto const single_injection = single_handler::inject(); - - EXPECT_TRUE(fenced_injection.fence_before); - EXPECT_TRUE(fenced_injection.fence_after); - EXPECT_EQ(fenced_injection.order_before, std::memory_order_seq_cst); - EXPECT_EQ(fenced_injection.order_after, std::memory_order_seq_cst); - EXPECT_FALSE(single_injection.fence_before); - EXPECT_FALSE(single_injection.fence_after); -} - -TEST(PolicyConcurrencyTest, FencedVariantsUseExpectedMemoryOrders) { - using relaxed_handler = - policy::concurrency::handler; - using acq_rel_handler = - policy::concurrency::handler; - - auto const relaxed = relaxed_handler::inject(); - auto const acq_rel = acq_rel_handler::inject(); - - EXPECT_EQ(relaxed.order_before, std::memory_order_relaxed); - EXPECT_EQ(relaxed.order_after, std::memory_order_relaxed); - EXPECT_EQ(acq_rel.order_before, std::memory_order_acquire); - EXPECT_EQ(acq_rel.order_after, std::memory_order_release); -} - -TEST(PolicyConcurrencyTest, PrimitiveAccessHandlerProtocolByPolicy) { - EXPECT_TRUE( - (policy::concurrency::handler_access_available)); - EXPECT_TRUE(( - policy::concurrency::handler_access_available)); - EXPECT_TRUE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_relaxed, int>)); - EXPECT_TRUE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_acq_rel, int>)); - EXPECT_TRUE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_seq_cst, int>)); -} - -TEST(PolicyConcurrencyTest, PrimitiveAccessRejectsNonTriviallyCopyableRep) { - EXPECT_FALSE(( - policy::concurrency::handler_access_available)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_relaxed, NonTriviallyCopyableRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_acq_rel, NonTriviallyCopyableRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_seq_cst, NonTriviallyCopyableRep>)); -} - -TEST(PolicyConcurrencyTest, PrimitiveAccessRespectsAtomicRefAlignmentGate) { - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::none, LowAlignmentRep>)); - - constexpr bool requires_stronger_alignment = - std::atomic_ref::required_alignment > - alignof(LowAlignmentRep); - - if constexpr (requires_stronger_alignment) { - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced, LowAlignmentRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_relaxed, LowAlignmentRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_acq_rel, LowAlignmentRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_seq_cst, LowAlignmentRep>)); - } else { - GTEST_SKIP() << "platform atomic_ref required_alignment does not exceed " - "alignof(LowAlignmentRep)"; - } -} - -TEST(PolicyProtocolTest, BuiltinHandlersSatisfyProtocolConcepts) { - static_assert(policy::type::handler_protocol); - static_assert(policy::concurrency::handler_protocol); - static_assert(policy::value::handler_protocol); - static_assert(policy::error::handler_protocol); - - EXPECT_TRUE((policy::type::handler_protocol)); - EXPECT_TRUE((policy::concurrency::handler_protocol)); - EXPECT_TRUE((policy::value::handler_protocol)); - EXPECT_TRUE((policy::error::handler_protocol)); -} - -TEST(OperationTraitsTest, BuiltinArithmeticOperationsExposeCapability) { - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_FALSE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); -} - -TEST(OperationTraitsTest, BuiltinOperationsHaveNonNullCapabilityMask) { - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); -} - -TEST(OperationTraitsTest, NullCapabilityMaskIsDetectedAtCompileTime) { - static_assert(!operations::op_capability_valid_v); - EXPECT_FALSE((operations::op_capability_valid_v)); -} - -// Use the existing test runner main from other test translation unit. diff --git a/tests/basic/test_underlying.cpp b/tests/basic/test_underlying.cpp deleted file mode 100644 index 8ab7540..0000000 --- a/tests/basic/test_underlying.cpp +++ /dev/null @@ -1,664 +0,0 @@ -#include - -import mcpplibs.primitives; - -namespace { - -struct UserInteger { - int value; -}; - -struct NotRegistered { - int value; -}; - -struct BadRep { - int value; -}; - -struct BadKind { - int value; -}; - -struct BigIntLike { - long long value; - - friend constexpr auto operator+(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(BigIntLike lhs, BigIntLike rhs) noexcept - -> bool { - return lhs.value == rhs.value; - } -}; - -struct BadCustomKind { - long long value; - - friend constexpr auto operator+(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(BadCustomKind lhs, - BadCustomKind rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct MissingDivisionLike { - long long value; - - friend constexpr auto operator+(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept - -> MissingDivisionLike { - return MissingDivisionLike{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept - -> MissingDivisionLike { - return MissingDivisionLike{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept - -> MissingDivisionLike { - return MissingDivisionLike{lhs.value * rhs.value}; - } - - friend constexpr auto operator==(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct NonNegativeInt { - int value; -}; - -struct ExplicitCommonLhs { - int value; -}; - -struct ExplicitCommonRhs { - int value; -}; - -struct ExplicitCommonRep { - long long value; - - friend constexpr auto operator+(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct VoidCommonLhs { - int value; - - friend constexpr auto operator+(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(VoidCommonLhs lhs, - VoidCommonLhs rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct VoidCommonRhs { - int value; - - friend constexpr auto operator+(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(VoidCommonRhs lhs, - VoidCommonRhs rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -} // namespace - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = UserInteger; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return UserInteger{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BadRep; - using rep_type = BadRep; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BadKind; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::floating; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return BadKind{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BigIntLike; - using rep_type = BigIntLike; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BadCustomKind; - using rep_type = BadCustomKind; - - static constexpr bool enabled = true; - static constexpr auto kind = category::boolean; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> -struct mcpplibs::primitives::underlying::traits { - using value_type = MissingDivisionLike; - using rep_type = MissingDivisionLike; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = NonNegativeInt; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return NonNegativeInt{value}; - } - - static constexpr bool is_valid_rep(rep_type value) noexcept { - return value >= 0; - } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = ExplicitCommonLhs; - using rep_type = ExplicitCommonLhs; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = ExplicitCommonRhs; - using rep_type = ExplicitCommonRhs; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = ExplicitCommonRep; - static constexpr bool enabled = true; -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = ExplicitCommonRep; - static constexpr bool enabled = true; -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = VoidCommonLhs; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return VoidCommonLhs{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = VoidCommonRhs; - using rep_type = short; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return static_cast(value.value); - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return VoidCommonRhs{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = void; - static constexpr bool enabled = true; -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = void; - static constexpr bool enabled = true; -}; - -TEST(PrimitiveTraitsTest, StandardTypeConcepts) { - EXPECT_TRUE((mcpplibs::primitives::std_integer)); - EXPECT_TRUE((mcpplibs::primitives::std_integer)); - EXPECT_FALSE((mcpplibs::primitives::std_integer)); - EXPECT_FALSE((mcpplibs::primitives::std_integer)); - - EXPECT_TRUE((mcpplibs::primitives::std_floating)); - EXPECT_TRUE((mcpplibs::primitives::std_floating)); - EXPECT_TRUE((mcpplibs::primitives::std_floating)); - - EXPECT_TRUE((mcpplibs::primitives::std_bool)); - EXPECT_FALSE((mcpplibs::primitives::std_bool)); - - EXPECT_TRUE((mcpplibs::primitives::std_char)); - EXPECT_TRUE((mcpplibs::primitives::std_char)); - EXPECT_TRUE((mcpplibs::primitives::std_char)); - - EXPECT_TRUE((mcpplibs::primitives::std_underlying_type)); - EXPECT_TRUE((mcpplibs::primitives::std_underlying_type)); - EXPECT_FALSE((mcpplibs::primitives::std_underlying_type)); -} - -TEST(PrimitiveTraitsTest, UnderlyingTraitsDefaultsAndCustomRegistration) { - EXPECT_TRUE((mcpplibs::primitives::underlying_type)); - EXPECT_EQ(mcpplibs::primitives::underlying::traits::kind, - mcpplibs::primitives::underlying::category::integer); - - EXPECT_TRUE((mcpplibs::primitives::underlying_type)); - EXPECT_EQ(mcpplibs::primitives::underlying::traits::to_rep( - UserInteger{7}), - 7); - - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); - EXPECT_FALSE( - (mcpplibs::primitives::underlying::traits::enabled)); -} - -TEST(PrimitiveTraitsTest, LegacyPrimitiveTraitsNamespaceAliasesRemainAvailable) { - using value_t = mcpplibs::primitives::primitive< - int, mcpplibs::primitives::policy::error::expected>; - using legacy_traits_t = - mcpplibs::primitives::traits::primitive_traits; - using meta_traits_t = mcpplibs::primitives::meta::traits; - - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as< - mcpplibs::primitives::traits::make_primitive_t< - int, typename legacy_traits_t::policies>, - value_t>); - SUCCEED(); -} - -TEST(PrimitiveTraitsTest, MetaTraitsExposeValueTypeAndPrimitiveMetadata) { - using value_t = mcpplibs::primitives::primitive< - short, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::type::compatible, - mcpplibs::primitives::policy::error::expected, - mcpplibs::primitives::policy::concurrency::fenced>; - using traits_t = mcpplibs::primitives::meta::traits; - - static_assert( - std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - - SUCCEED(); -} - -TEST(PrimitiveTraitsTest, MetaPrimitiveConceptsMatchUnderlyingCategory) { - using boolean_t = mcpplibs::primitives::primitive; - using character_t = mcpplibs::primitives::primitive; - using integer_t = mcpplibs::primitives::primitive; - using floating_t = mcpplibs::primitives::primitive; - - static_assert(mcpplibs::primitives::meta::primitive_type); - static_assert(mcpplibs::primitives::meta::primitive_type); - static_assert(!mcpplibs::primitives::meta::primitive_type); - - static_assert(mcpplibs::primitives::meta::boolean); - static_assert(!mcpplibs::primitives::meta::boolean); - - static_assert(mcpplibs::primitives::meta::character); - static_assert(!mcpplibs::primitives::meta::character); - - static_assert(mcpplibs::primitives::meta::integer); - static_assert(!mcpplibs::primitives::meta::integer); - - static_assert(mcpplibs::primitives::meta::floating); - static_assert(!mcpplibs::primitives::meta::floating); - - static_assert(mcpplibs::primitives::meta::numeric); - static_assert(mcpplibs::primitives::meta::numeric); - static_assert(!mcpplibs::primitives::meta::numeric); - static_assert(!mcpplibs::primitives::meta::numeric); - static_assert(!mcpplibs::primitives::meta::numeric); - - EXPECT_TRUE((mcpplibs::primitives::meta::boolean)); - EXPECT_TRUE((mcpplibs::primitives::meta::character)); - EXPECT_TRUE((mcpplibs::primitives::meta::integer)); - EXPECT_TRUE((mcpplibs::primitives::meta::floating)); -} - -TEST(PrimitiveTraitsTest, - UnderlyingTypeRequiresValidRepTypeAndCategoryConsistency) { - EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); - - EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, UnderlyingTypeAllowsCustomNumericLikeRepType) { - EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_TRUE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, CustomNumericLikeRepTypeRejectsInvalidCategory) { - EXPECT_TRUE( - (mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, CustomUnderlyingParticipatesInPrimitiveOperations) { - using value_t = mcpplibs::primitives::primitive< - BigIntLike, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::error::expected>; - - auto const lhs = value_t{BigIntLike{40}}; - auto const rhs = value_t{BigIntLike{2}}; - - auto const result = mcpplibs::primitives::operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value().value, 42); -} - -TEST(PrimitiveTraitsTest, CustomWrappedUnderlyingUsesRepBridgeForArithmetic) { - using value_t = mcpplibs::primitives::primitive< - UserInteger, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::error::expected>; - - auto const lhs = value_t{UserInteger{40}}; - auto const rhs = value_t{UserInteger{2}}; - - auto const result = mcpplibs::primitives::operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(PrimitiveTraitsTest, InvalidUnderlyingRepIsRejectedByDispatcher) { - using value_t = mcpplibs::primitives::primitive< - NonNegativeInt, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::error::expected>; - - auto const lhs = value_t{NonNegativeInt{-1}}; - auto const rhs = value_t{NonNegativeInt{2}}; - - auto const result = mcpplibs::primitives::operations::add(lhs, rhs); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), - mcpplibs::primitives::policy::error::kind::domain_error); -} - -TEST(PrimitiveTraitsTest, - CustomNumericLikeRepTypeRequiresDivisionOperatorForEligibility) { - EXPECT_TRUE( - (mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, UnderlyingCommonRepDefaultsToStdCommonType) { - static_assert(mcpplibs::primitives::has_common_rep); - static_assert(std::same_as, - long long>); - SUCCEED(); -} - -TEST(PrimitiveTraitsTest, UnderlyingCommonRepCanBeCustomizedViaTraits) { - static_assert(mcpplibs::primitives::has_common_rep); - static_assert( - std::same_as, - ExplicitCommonRep>); - - using handler_t = mcpplibs::primitives::policy::type::handler< - mcpplibs::primitives::policy::type::transparent, - mcpplibs::primitives::operations::Addition, ExplicitCommonLhs, - ExplicitCommonRhs>; - - static_assert(handler_t::enabled); - static_assert(handler_t::allowed); - static_assert( - std::same_as); - SUCCEED(); -} - -TEST(PrimitiveTraitsTest, TypePoliciesRequireNonVoidCommonRep) { - static_assert(std::is_arithmetic_v< - mcpplibs::primitives::underlying::traits::rep_type>); - static_assert(std::is_arithmetic_v< - mcpplibs::primitives::underlying::traits::rep_type>); - - using compatible_handler_t = mcpplibs::primitives::policy::type::handler< - mcpplibs::primitives::policy::type::compatible, - mcpplibs::primitives::operations::Addition, VoidCommonLhs, VoidCommonRhs>; - using transparent_handler_t = mcpplibs::primitives::policy::type::handler< - mcpplibs::primitives::policy::type::transparent, - mcpplibs::primitives::operations::Addition, VoidCommonLhs, VoidCommonRhs>; - - static_assert(compatible_handler_t::enabled); - static_assert(transparent_handler_t::enabled); - static_assert(!compatible_handler_t::allowed); - static_assert(!transparent_handler_t::allowed); - static_assert(std::same_as); - static_assert(std::same_as); - - EXPECT_EQ(compatible_handler_t::diagnostic_id, 2u); - EXPECT_EQ(transparent_handler_t::diagnostic_id, 3u); -} - -int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp b/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp new file mode 100644 index 0000000..d68b755 --- /dev/null +++ b/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp @@ -0,0 +1,54 @@ +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.operations.impl; + +#include "../../support/underlying_custom_types.hpp" + +using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::test_support::underlying; + +TEST(PrimitiveTraitsTest, UnderlyingCommonRepDefaultsToStdCommonType) { + static_assert(has_common_rep); + static_assert(std::same_as, long long>); + SUCCEED(); +} + +TEST(PrimitiveTraitsTest, UnderlyingCommonRepCanBeCustomizedViaTraits) { + static_assert(has_common_rep); + static_assert(std::same_as, + ExplicitCommonRep>); + + using handler_t = policy::type::handler; + + static_assert(handler_t::enabled); + static_assert(handler_t::allowed); + static_assert(std::same_as); + SUCCEED(); +} + +TEST(PrimitiveTraitsTest, TypePoliciesRequireNonVoidCommonRep) { + static_assert(std::is_arithmetic_v::rep_type>); + static_assert(std::is_arithmetic_v::rep_type>); + + using compatible_handler_t = policy::type::handler< + policy::type::compatible, operations::Addition, VoidCommonLhs, + VoidCommonRhs>; + using transparent_handler_t = policy::type::handler< + policy::type::transparent, operations::Addition, VoidCommonLhs, + VoidCommonRhs>; + + static_assert(compatible_handler_t::enabled); + static_assert(transparent_handler_t::enabled); + static_assert(!compatible_handler_t::allowed); + static_assert(!transparent_handler_t::allowed); + static_assert(std::same_as); + static_assert(std::same_as); + + EXPECT_EQ(compatible_handler_t::diagnostic_id, 2u); + EXPECT_EQ(transparent_handler_t::diagnostic_id, 3u); +} diff --git a/tests/basic/underlying/integration/test_operations_dispatcher.cpp b/tests/basic/underlying/integration/test_operations_dispatcher.cpp new file mode 100644 index 0000000..a243d0f --- /dev/null +++ b/tests/basic/underlying/integration/test_operations_dispatcher.cpp @@ -0,0 +1,152 @@ +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.operations.impl; +import mcpplibs.primitives.operations.dispatcher; + +using namespace mcpplibs::primitives; + +namespace { + +struct OpsBigIntLike { + long long value; + + friend constexpr auto operator+(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct OpsUserInteger { + int value; +}; + +struct OpsNonNegativeInt { + int value; +}; + +} // namespace + +template <> +struct mcpplibs::primitives::underlying::traits { + using value_type = OpsBigIntLike; + using rep_type = OpsBigIntLike; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits { + using value_type = OpsUserInteger; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return OpsUserInteger{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits { + using value_type = OpsNonNegativeInt; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return OpsNonNegativeInt{value}; + } + + static constexpr auto is_valid_rep(rep_type value) noexcept -> bool { + return value >= 0; + } +}; + +TEST(UnderlyingOperationsTest, CustomUnderlyingParticipatesInPrimitiveOperations) { + using value_t = + primitive; + + auto const lhs = value_t{OpsBigIntLike{40}}; + auto const rhs = value_t{OpsBigIntLike{2}}; + + auto const result = operations::dispatch(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value, 42); +} + +TEST(UnderlyingOperationsTest, CustomWrappedUnderlyingUsesRepBridgeForArithmetic) { + using value_t = + primitive; + + auto const lhs = value_t{OpsUserInteger{40}}; + auto const rhs = value_t{OpsUserInteger{2}}; + + auto const result = operations::dispatch(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 42); +} + +TEST(UnderlyingOperationsTest, InvalidUnderlyingRepIsRejectedByDispatcher) { + using value_t = primitive; + + auto const lhs = value_t{OpsNonNegativeInt{-1}}; + auto const rhs = value_t{OpsNonNegativeInt{2}}; + + auto const result = operations::dispatch(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::domain_error); +} diff --git a/tests/basic/underlying/traits/test_concepts_and_registration.cpp b/tests/basic/underlying/traits/test_concepts_and_registration.cpp new file mode 100644 index 0000000..fa0462e --- /dev/null +++ b/tests/basic/underlying/traits/test_concepts_and_registration.cpp @@ -0,0 +1,66 @@ +#include + +import mcpplibs.primitives.underlying; + +#include "../../support/underlying_custom_types.hpp" + +using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::test_support::underlying; + +TEST(PrimitiveTraitsTest, StandardTypeConcepts) { + EXPECT_TRUE((std_integer)); + EXPECT_TRUE((std_integer)); + EXPECT_FALSE((std_integer)); + EXPECT_FALSE((std_integer)); + + EXPECT_TRUE((std_floating)); + EXPECT_TRUE((std_floating)); + EXPECT_TRUE((std_floating)); + + EXPECT_TRUE((std_bool)); + EXPECT_FALSE((std_bool)); + + EXPECT_TRUE((std_char)); + EXPECT_TRUE((std_char)); + EXPECT_TRUE((std_char)); + + EXPECT_TRUE((std_underlying_type)); + EXPECT_TRUE((std_underlying_type)); + EXPECT_FALSE((std_underlying_type)); +} + +TEST(PrimitiveTraitsTest, UnderlyingTraitsDefaultsAndCustomRegistration) { + EXPECT_TRUE((underlying_type)); + EXPECT_EQ(underlying::traits::kind, underlying::category::integer); + + EXPECT_TRUE((underlying_type)); + EXPECT_EQ(underlying::traits::to_rep(UserInteger{7}), 7); + + EXPECT_FALSE((underlying_type)); + EXPECT_FALSE((underlying::traits::enabled)); +} + +TEST(PrimitiveTraitsTest, + UnderlyingTypeRequiresValidRepTypeAndCategoryConsistency) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); + + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); +} + +TEST(PrimitiveTraitsTest, UnderlyingTypeAllowsCustomNumericLikeRepType) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_TRUE((underlying_type)); +} + +TEST(PrimitiveTraitsTest, CustomNumericLikeRepTypeRejectsInvalidCategory) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); +} + +TEST(PrimitiveTraitsTest, + CustomNumericLikeRepTypeRequiresDivisionOperatorForEligibility) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); +} diff --git a/tests/xmake.lua b/tests/xmake.lua index fd1d671..45660f1 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua @@ -6,7 +6,9 @@ add_requires("gtest") target("primitives_test") set_kind("binary") - add_files("basic/*.cpp") + add_files("basic/main.cpp") + add_files("basic/**/test_*.cpp") add_deps("mcpplibs-primitives") add_packages("gtest") + add_links("gtest_main", "gmock_main") set_policy("build.c++.modules", true)