Skip to content

Commit 67001fe

Browse files
test: Add unit tests for numeric conversion functions and risk detection
1 parent 423e8d9 commit 67001fe

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

tests/basic/test_conversion.cpp

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#include <cstdint>
2+
#include <gtest/gtest.h>
3+
#include <limits>
4+
#include <type_traits>
5+
6+
import mcpplibs.primitives;
7+
8+
using namespace mcpplibs::primitives;
9+
10+
namespace {
11+
12+
struct SignedBox {
13+
long long value;
14+
};
15+
16+
struct UnsignedBox {
17+
std::uint16_t value;
18+
};
19+
20+
struct FloatBox {
21+
double value;
22+
};
23+
24+
} // namespace
25+
26+
template <> struct underlying::traits<SignedBox> {
27+
using value_type = SignedBox;
28+
using rep_type = long long;
29+
30+
static constexpr bool enabled = true;
31+
static constexpr auto kind = underlying::category::integer;
32+
33+
static constexpr auto to_rep(value_type value) noexcept -> rep_type {
34+
return value.value;
35+
}
36+
37+
static constexpr auto from_rep(rep_type value) noexcept -> value_type {
38+
return SignedBox{value};
39+
}
40+
41+
static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; }
42+
};
43+
44+
template <> struct underlying::traits<UnsignedBox> {
45+
using value_type = UnsignedBox;
46+
using rep_type = std::uint16_t;
47+
48+
static constexpr bool enabled = true;
49+
static constexpr auto kind = underlying::category::integer;
50+
51+
static constexpr auto to_rep(value_type value) noexcept -> rep_type {
52+
return value.value;
53+
}
54+
55+
static constexpr auto from_rep(rep_type value) noexcept -> value_type {
56+
return UnsignedBox{value};
57+
}
58+
59+
static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; }
60+
};
61+
62+
template <> struct underlying::traits<FloatBox> {
63+
using value_type = FloatBox;
64+
using rep_type = double;
65+
66+
static constexpr bool enabled = true;
67+
static constexpr auto kind = underlying::category::floating;
68+
69+
static constexpr auto to_rep(value_type value) noexcept -> rep_type {
70+
return value.value;
71+
}
72+
73+
static constexpr auto from_rep(rep_type value) noexcept -> value_type {
74+
return FloatBox{value};
75+
}
76+
77+
static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; }
78+
};
79+
80+
TEST(ConversionRiskTest, NumericRiskDetectsOverflowAndUnderflow) {
81+
auto const overflow = conversion::numeric_risk<std::uint16_t>(70000);
82+
ASSERT_TRUE(overflow.has_value());
83+
EXPECT_EQ(*overflow, conversion::risk::kind::overflow);
84+
85+
auto const underflow = conversion::numeric_risk<std::uint16_t>(-1);
86+
ASSERT_TRUE(underflow.has_value());
87+
EXPECT_EQ(*underflow, conversion::risk::kind::underflow);
88+
}
89+
90+
TEST(ConversionRiskTest, NumericRiskDetectsDomainAndPrecisionLoss) {
91+
auto const domain =
92+
conversion::numeric_risk<int>(std::numeric_limits<double>::quiet_NaN());
93+
ASSERT_TRUE(domain.has_value());
94+
EXPECT_EQ(*domain, conversion::risk::kind::domain_error);
95+
96+
auto const precision =
97+
conversion::numeric_risk<float>(std::numeric_limits<std::int64_t>::max());
98+
ASSERT_TRUE(precision.has_value());
99+
EXPECT_EQ(*precision, conversion::risk::kind::precision_loss);
100+
}
101+
102+
TEST(ConversionCastTest, CheckedCastReportsErrorForInvalidInput) {
103+
auto const ok = conversion::checked_cast<int>(42u);
104+
ASSERT_TRUE(ok.has_value());
105+
EXPECT_EQ(*ok, 42);
106+
107+
auto const bad = conversion::checked_cast<std::uint16_t>(-7);
108+
ASSERT_FALSE(bad.has_value());
109+
EXPECT_EQ(bad.error(), conversion::risk::kind::underflow);
110+
}
111+
112+
TEST(ConversionCastTest, SaturatingCastClampsAndHandlesNaN) {
113+
EXPECT_EQ(conversion::saturating_cast<std::int16_t>(100000),
114+
std::numeric_limits<std::int16_t>::max());
115+
EXPECT_EQ(conversion::saturating_cast<std::int16_t>(-100000),
116+
std::numeric_limits<std::int16_t>::lowest());
117+
EXPECT_EQ(conversion::saturating_cast<int>(
118+
std::numeric_limits<double>::quiet_NaN()),
119+
0);
120+
}
121+
122+
TEST(ConversionCastTest, TruncatingCastHandlesFloatingInputs) {
123+
EXPECT_EQ(conversion::truncating_cast<int>(42.9), 42);
124+
EXPECT_EQ(conversion::truncating_cast<int>(
125+
std::numeric_limits<double>::infinity()),
126+
std::numeric_limits<int>::max());
127+
EXPECT_EQ(conversion::truncating_cast<int>(
128+
-std::numeric_limits<double>::infinity()),
129+
std::numeric_limits<int>::lowest());
130+
}
131+
132+
TEST(ConversionCastTest, ExactCastRejectsPrecisionLoss) {
133+
auto const ok = conversion::exact_cast<int>(42);
134+
ASSERT_TRUE(ok.has_value());
135+
EXPECT_EQ(*ok, 42);
136+
137+
auto const precision =
138+
conversion::exact_cast<float>(std::numeric_limits<std::int64_t>::max());
139+
ASSERT_FALSE(precision.has_value());
140+
EXPECT_EQ(precision.error(), conversion::risk::kind::precision_loss);
141+
}
142+
143+
TEST(ConversionUnderlyingTest, CheckedCastUsesRepBridge) {
144+
auto const ok = conversion::checked_cast<UnsignedBox>(SignedBox{42});
145+
ASSERT_TRUE(ok.has_value());
146+
EXPECT_EQ(ok->value, 42);
147+
148+
auto const bad = conversion::checked_cast<UnsignedBox>(SignedBox{-1});
149+
ASSERT_FALSE(bad.has_value());
150+
EXPECT_EQ(bad.error(), conversion::risk::kind::underflow);
151+
}
152+
153+
TEST(ConversionUnderlyingTest, SaturatingCastClampsByUnderlyingRep) {
154+
auto const saturated = conversion::saturating_cast<UnsignedBox>(SignedBox{
155+
std::numeric_limits<long long>::max()});
156+
EXPECT_EQ(saturated.value, std::numeric_limits<std::uint16_t>::max());
157+
}
158+
159+
TEST(ConversionUnderlyingTest, TruncatingAndExactCastSupportFloatingSources) {
160+
auto const truncated = conversion::truncating_cast<SignedBox>(FloatBox{12.75});
161+
EXPECT_EQ(truncated.value, 12);
162+
163+
auto const exact = conversion::exact_cast<SignedBox>(FloatBox{12.0});
164+
ASSERT_TRUE(exact.has_value());
165+
EXPECT_EQ(exact->value, 12);
166+
}
167+
168+
TEST(ConversionTypeTest, ResultTypesMatchContracts) {
169+
static_assert(std::same_as<decltype(conversion::checked_cast<int>(1)),
170+
conversion::cast_result<int>>);
171+
static_assert(std::same_as<decltype(conversion::exact_cast<UnsignedBox>(
172+
SignedBox{1})),
173+
conversion::cast_result<UnsignedBox>>);
174+
}

0 commit comments

Comments
 (0)