Files
ortools-clone/ortools/base/strong_int_test.cc
Corentin Le Molgat 3067056625 backport from main branch
* bazel: add missing python typing-extensions requirements
* use [[maybe_unused]] standard attribute
* linear_solver: improve LpQuadraticTerm support
2025-04-30 15:15:39 +02:00

1010 lines
31 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Unit test cases for StrongInt.
#include "ortools/base/strong_int.h"
#include <cstddef>
#include <cstdint>
#include <limits>
#include <sstream>
#include <string>
#include <type_traits>
#include "absl/container/node_hash_map.h"
#include "absl/flags/marshalling.h"
#include "absl/hash/hash_testing.h"
#include "absl/numeric/int128.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/substitute.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/logging.h"
namespace util_intops {
namespace {
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::HasSubstr;
DEFINE_STRONG_INT_TYPE(StrongInt8, int8_t);
DEFINE_STRONG_INT_TYPE(StrongUInt8, uint8_t);
DEFINE_STRONG_INT_TYPE(StrongInt16, int16_t);
DEFINE_STRONG_INT_TYPE(StrongUInt16, uint16_t);
DEFINE_STRONG_INT_TYPE(StrongInt32, int32_t);
DEFINE_STRONG_INT_TYPE(StrongInt64, int64_t);
DEFINE_STRONG_INT_TYPE(StrongUInt32, uint32_t);
DEFINE_STRONG_INT_TYPE(StrongUInt64, uint64_t);
DEFINE_STRONG_INT_TYPE(StrongLong, long); // NOLINT
DEFINE_STRONG_INT_TYPE(StrongUInt128, absl::uint128);
DEFINE_STRONG_INT_TYPE(StrongInt128, absl::int128);
TEST(StrongIntTypeIdTest, TypeIdIsAsExpected) {
EXPECT_EQ("StrongInt8", StrongInt8::TypeName());
EXPECT_EQ("StrongLong", StrongLong::TypeName());
}
template <typename StrongIntTypeUnderTest>
class StrongIntTest : public ::testing::Test {
public:
using T = StrongIntTypeUnderTest;
using V = typename T::ValueType;
};
// All tests will be executed on the following StrongInt<> types.
using SupportedStrongIntTypes =
::testing::Types<StrongInt8, StrongUInt8, StrongInt16, StrongUInt16,
StrongInt32, StrongInt64, StrongUInt64, StrongLong,
StrongUInt128, StrongInt128>;
TYPED_TEST_SUITE(StrongIntTest, SupportedStrongIntTypes);
// NOTE: On all tests, we use the accessor value() as to not invoke the
// comparison operators which must themselves be tested.
TYPED_TEST(StrongIntTest, TestTraits) {
using T = typename TestFixture::T;
EXPECT_TRUE(std::is_standard_layout<T>::value);
EXPECT_TRUE(std::is_trivially_copy_constructible<T>::value);
EXPECT_TRUE(std::is_trivially_copy_assignable<T>::value);
EXPECT_TRUE(std::is_trivially_destructible<T>::value);
}
TYPED_TEST(StrongIntTest, TestCtors) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
{ // Test default construction.
T x;
EXPECT_EQ(V(), x.value());
}
{ // Test construction from a value.
T x(93);
EXPECT_EQ(V(93), x.value());
}
{ // Test construction from a negative value.
T x(-1);
EXPECT_EQ(V(-1), x.value());
}
{ // Test copy construction.
T x(76);
T y(x);
EXPECT_EQ(V(76), y.value());
}
{ // Test construction from int8_t.
constexpr int8_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
static_assert(static_cast<int8_t>(T(i).value()) == 93,
"value() is not constexpr");
int8_t j = -76;
T y(j);
EXPECT_EQ(V(-76), y.value());
}
{ // Test construction from uint8_t.
uint8_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
}
{ // Test construction from int16_t.
int16_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
int16_t j = -76;
T y(j);
EXPECT_EQ(V(-76), y.value());
}
{ // Test construction from uint16_t.
uint16_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
}
{ // Test construction from int32_t.
int32_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
int32_t j = -76;
T y(j);
EXPECT_EQ(V(-76), y.value());
}
{ // Test construction from uint32_t.
uint32_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
}
{ // Test construction from int64_t.
int64_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
int64_t j = -76;
T y(j);
EXPECT_EQ(V(-76), y.value());
}
{ // Test construction from uint64_t.
uint64_t i = 93;
T x(i);
EXPECT_EQ(V(93), x.value());
}
if constexpr (std::is_fundamental_v<V>) {
{ // Test construction from float.
float i = 93.1;
T x(i);
EXPECT_EQ(V(93), x.value());
// It is undefined to init an unsigned int from a negative float.
if constexpr (std::numeric_limits<V>::is_signed) {
float j = -76.1;
T y(j);
EXPECT_EQ(V(-76.1), y.value());
}
}
{ // Test construction from double.
double i = 93.1;
T x(i);
EXPECT_EQ(V(93), x.value());
// It is undefined to init an unsigned int from a negative double.
if constexpr (std::numeric_limits<V>::is_signed) {
double j = -76.1;
T y(j);
EXPECT_EQ(V(-76.1), y.value());
}
}
{ // Test construction from long double.
long double i = 93.1;
T x(i);
EXPECT_EQ(V(93), x.value());
// It is undefined to init an unsigned int from a negative long double.
if constexpr (std::numeric_limits<V>::is_signed) {
long double j = -76.1;
T y(j);
EXPECT_EQ(V(-76.1), y.value());
}
}
}
{ // Test constexpr assignment
constexpr T x(123);
EXPECT_EQ(V(123), x.value());
}
}
TYPED_TEST(StrongIntTest, TestAbslParseFlag) {
using T = typename TestFixture::T;
T t;
std::string error;
EXPECT_TRUE(absl::ParseFlag("123", &t, &error));
EXPECT_EQ(t, T(123));
EXPECT_EQ(absl::UnparseFlag(t), "123");
}
TYPED_TEST(StrongIntTest, TestAbslParseFlagNotAnInt) {
using T = typename TestFixture::T;
T t;
std::string error;
EXPECT_FALSE(absl::ParseFlag("not_an_int", &t, &error));
EXPECT_THAT(error,
AllOf(HasSubstr("'not_an_int'"), HasSubstr(T::TypeName())));
}
TYPED_TEST(StrongIntTest, TestAbslParseFlagEmptyString) {
using T = typename TestFixture::T;
T t;
std::string error;
EXPECT_FALSE(absl::ParseFlag("", &t, &error));
EXPECT_THAT(error, AllOf(HasSubstr("''"), HasSubstr(T::TypeName())));
}
TYPED_TEST(StrongIntTest, TestAbslParseFlagRangeLimits) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
const V max_int = std::numeric_limits<V>::max();
const std::string max = absl::StrCat(max_int);
const V min_int = std::numeric_limits<V>::min();
const std::string min = absl::StrCat(min_int);
std::string max_plus_one;
std::string min_minus_one;
if constexpr (std::is_same_v<V, absl::int128>) {
EXPECT_THAT(max, Eq("170141183460469231731687303715884105727"));
max_plus_one = "170141183460469231731687303715884105728";
EXPECT_THAT(min, Eq("-170141183460469231731687303715884105728"));
min_minus_one = "-170141183460469231731687303715884105729";
} else if constexpr (std::is_same_v<V, absl::uint128>) {
EXPECT_THAT(max, Eq("340282366920938463463374607431768211455"));
max_plus_one = "340282366920938463463374607431768211456";
EXPECT_THAT(min, Eq("0"));
min_minus_one = "-1";
} else {
max_plus_one = absl::StrCat(absl::int128(max_int) + 1);
min_minus_one = absl::StrCat(absl::int128(min_int) - 1);
}
T t;
std::string error;
EXPECT_TRUE(absl::ParseFlag(max, &t, &error));
EXPECT_EQ(t, T(max_int));
EXPECT_EQ(absl::UnparseFlag(t), max);
EXPECT_TRUE(absl::ParseFlag(min, &t, &error));
EXPECT_EQ(t, T(min_int));
EXPECT_EQ(absl::UnparseFlag(t), min);
EXPECT_FALSE(absl::ParseFlag(max_plus_one, &t, &error));
EXPECT_THAT(error, AllOf(HasSubstr(max_plus_one), HasSubstr(T::TypeName())));
EXPECT_FALSE(absl::ParseFlag(min_minus_one, &t, &error));
EXPECT_THAT(error, AllOf(HasSubstr(min_minus_one), HasSubstr(T::TypeName())));
}
namespace {
struct PositiveValidator {
template <class T, class U>
static bool ValidateInit(U i) {
if (i < 0) LOG(FATAL) << "PositiveValidator";
return true;
}
};
} // namespace
TYPED_TEST(StrongIntTest, TestAbslParseFlagInvalid) {
using V = typename TestFixture::V;
struct CustomTag {
static constexpr absl::string_view TypeName() { return "CustomTag"; }
};
using T = StrongInt<CustomTag, V, PositiveValidator>;
T t;
std::string error;
if constexpr (std::numeric_limits<V>::is_signed) {
EXPECT_DEATH(absl::ParseFlag("-123", &t, &error), "PositiveValidator");
} else {
EXPECT_FALSE(absl::ParseFlag("-123", &t, &error));
EXPECT_THAT(error, AllOf(HasSubstr("'-123'"), HasSubstr("CustomTag")));
}
}
TYPED_TEST(StrongIntTest, TestCtorDeath) {
using V = typename TestFixture::V;
if constexpr (std::numeric_limits<V>::is_signed) {
struct CustomTag {};
using T = StrongInt<CustomTag, V, PositiveValidator>;
EXPECT_DEATH(T(static_cast<V>(-123)), "PositiveValidator");
}
}
TYPED_TEST(StrongIntTest, TestMetadata) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
T t;
EXPECT_EQ(std::numeric_limits<V>::max(), t.Max().value());
EXPECT_EQ(std::numeric_limits<V>::min(), t.Min().value());
}
TYPED_TEST(StrongIntTest, TestUnaryOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
{ // Test unary plus and minus of positive values.
T x(123);
EXPECT_EQ(V(123), (+x).value());
EXPECT_EQ(V(-123), (-x).value());
}
{ // Test unary plus and minus of negative values.
T x(-123);
EXPECT_EQ(V(-123), (+x).value());
EXPECT_EQ(V(123), (-x).value());
}
{ // Test logical not of positive values.
T x(123);
EXPECT_EQ(false, !x);
EXPECT_EQ(true, !!x);
}
{ // Test logical not of negative values.
T x(-123);
EXPECT_EQ(false, !x);
EXPECT_EQ(true, !!x);
}
{ // Test logical not of zero.
T x(0);
EXPECT_EQ(true, !x);
EXPECT_EQ(false, !!x);
}
{ // Test bitwise not of positive values.
T x(123);
EXPECT_EQ(V(~(x.value())), (~x).value());
EXPECT_EQ(x.value(), (~~x).value());
}
{ // Test bitwise not of zero.
T x(0x00);
EXPECT_EQ(V(~(x.value())), (~x).value());
EXPECT_EQ(x.value(), (~~x).value());
}
}
TYPED_TEST(StrongIntTest, TestIncrementDecrementOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test simple increments and decrements.
T x(0);
EXPECT_EQ(V(0), x.value());
EXPECT_EQ(V(0), (x++).value());
EXPECT_EQ(V(1), x.value());
EXPECT_EQ(V(2), (++x).value());
EXPECT_EQ(V(2), x.value());
EXPECT_EQ(V(2), (x--).value());
EXPECT_EQ(V(1), x.value());
EXPECT_EQ(V(0), (--x).value());
EXPECT_EQ(V(0), x.value());
}
TYPED_TEST(StrongIntTest, TestAssignmentOperator) {
using T = typename TestFixture::T;
{ // Test simple assignment from the same type.
T x(12);
T y(34);
EXPECT_EQ(y.value(), (x = y).value());
EXPECT_EQ(y.value(), x.value());
}
}
#define TEST_T_OP_T(xval, op, yval) \
{ \
T x(xval); \
T y(yval); \
V expected = x.value() op y.value(); \
EXPECT_EQ(expected, (x op y).value()); \
EXPECT_EQ(expected, (x op## = y).value()); \
EXPECT_EQ(expected, x.value()); \
constexpr T cx_x(xval); \
constexpr T cx_y(yval); \
if constexpr (std::is_fundamental_v<V>) { \
constexpr V cx_expected = static_cast<V>(cx_x.value() op cx_y.value()); \
static_assert((cx_x op cx_y) == T(cx_expected), \
#xval " " #op " " #yval); \
} else { \
V cx_expected = static_cast<V>(cx_x.value() op cx_y.value()); \
EXPECT_EQ((cx_x op cx_y), T(cx_expected)) << #xval " " #op " " #yval; \
} \
}
TYPED_TEST(StrongIntTest, TestPlusOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test positive vs. positive addition.
TEST_T_OP_T(9, +, 3)
// Test negative vs. positive addition.
TEST_T_OP_T(-9, +, 3)
// Test positive vs. negative addition.
TEST_T_OP_T(9, +, -3)
// Test negative vs. negative addition.
TEST_T_OP_T(-9, +, -3)
// Test addition by zero.
TEST_T_OP_T(93, +, 0);
}
TYPED_TEST(StrongIntTest, TestMinusOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test positive vs. positive subtraction.
TEST_T_OP_T(9, -, 3)
// Test negative vs. positive subtraction.
TEST_T_OP_T(-9, -, 3)
// Test positive vs. negative subtraction.
TEST_T_OP_T(9, -, -3)
// Test negative vs. negative subtraction.
TEST_T_OP_T(-9, -, -3)
// Test positive vs. positive subtraction resulting in negative.
TEST_T_OP_T(3, -, 9);
// Test subtraction of zero.
TEST_T_OP_T(93, -, 0);
// Test subtraction from zero.
TEST_T_OP_T(0, -, 93);
}
#define TEST_T_OP_NUM(xval, op, numtype, yval) \
{ \
T x(xval); \
numtype y = yval; \
V expected = x.value() op y; \
EXPECT_EQ(expected, (x op y).value()); \
EXPECT_EQ(expected, (x op## = y).value()); \
EXPECT_EQ(expected, x.value()); \
constexpr T cx_x(xval); \
if constexpr (std::is_fundamental_v<V>) { \
constexpr V cx_expected = static_cast<V>(cx_x.value() op yval); \
static_assert((cx_x op yval) == T(cx_expected), \
#xval " " #op " " #yval); \
} else { \
V cx_expected = \
static_cast<V>(static_cast<numtype>(cx_x.value()) op yval); \
EXPECT_EQ((cx_x op yval), T(cx_expected)) << #xval " " #op " " #yval; \
} \
}
#define TEST_NUM_OP_T(numtype, xval, op, yval) \
{ \
numtype x = xval; \
T y(yval); \
V expected = x op y.value(); \
EXPECT_EQ(expected, (x op y).value()); \
constexpr T cx_y(yval); \
if constexpr (std::is_fundamental_v<V>) { \
constexpr V cx_expected = static_cast<V>(xval op cx_y.value()); \
static_assert((xval op cx_y) == T(cx_expected), \
#xval " " #op " " #yval); \
} else { \
V cx_expected = \
static_cast<V>(xval op static_cast<numtype>(cx_y.value())); \
EXPECT_EQ((xval op cx_y), T(cx_expected)) << #xval " " #op " " #yval; \
} \
}
// NOLINTNEXTLINE: google-readability-function-size
TYPED_TEST(StrongIntTest, TestMultiplyOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test positive vs. positive multiplication.
TEST_T_OP_NUM(9, *, V, 3);
TEST_NUM_OP_T(V, 9, *, 3);
if constexpr (std::is_signed<V>::value) {
// Test negative vs. positive multiplication.
TEST_T_OP_NUM(-9, *, V, 3);
TEST_NUM_OP_T(V, -9, *, 3);
// Test positive vs. negative multiplication.
TEST_T_OP_NUM(9, *, V, -3);
TEST_NUM_OP_T(V, 9, *, -3);
// Test negative vs. negative multiplication.
TEST_T_OP_NUM(-9, *, V, -3);
TEST_NUM_OP_T(V, -9, *, -3);
}
// Test multiplication by one.
TEST_T_OP_NUM(93, *, V, 1);
TEST_NUM_OP_T(V, 93, *, 1);
// Test multiplication by zero.
TEST_T_OP_NUM(93, *, V, 0);
TEST_NUM_OP_T(V, 93, *, 0);
if constexpr (std::is_signed<V>::value) {
// Test multiplication by a negative.
TEST_T_OP_NUM(93, *, V, -1);
TEST_NUM_OP_T(V, 93, *, -1);
}
// Test multiplication by int8_t.
TEST_T_OP_NUM(39, *, int8_t, 2);
TEST_NUM_OP_T(int8_t, 39, *, 2);
// Test multiplication by uint8_t.
TEST_T_OP_NUM(39, *, uint8_t, 2);
TEST_NUM_OP_T(uint8_t, 39, *, 2);
// Test multiplication by int16_t.
TEST_T_OP_NUM(39, *, int16_t, 2);
TEST_NUM_OP_T(int16_t, 39, *, 2);
// Test multiplication by uint16_t.
TEST_T_OP_NUM(39, *, uint16_t, 2);
TEST_NUM_OP_T(uint16_t, 39, *, 2);
// Test multiplication by int32_t.
TEST_T_OP_NUM(39, *, int32_t, 2);
TEST_NUM_OP_T(int32_t, 39, *, 2);
// Test multiplication by uint32_t.
TEST_T_OP_NUM(39, *, uint32_t, 2);
TEST_NUM_OP_T(uint32_t, 39, *, 2);
// Test multiplication by int64_t.
TEST_T_OP_NUM(39, *, int64_t, 2);
TEST_NUM_OP_T(int64_t, 39, *, 2);
// Test multiplication by uint64_t.
TEST_T_OP_NUM(39, *, uint64_t, 2);
TEST_NUM_OP_T(uint64_t, 39, *, 2);
if constexpr (std::is_fundamental_v<V>) {
// Test multiplication by float.
TEST_T_OP_NUM(39, *, float, 2.1);
TEST_NUM_OP_T(float, 39, *, 2.1);
// Test multiplication by double.
TEST_T_OP_NUM(39, *, double, 2.1);
TEST_NUM_OP_T(double, 39, *, 2.1);
// Test multiplication by long double.
TEST_T_OP_NUM(39, *, long double, 2.1);
TEST_NUM_OP_T(long double, 39, *, 2.1);
}
}
TYPED_TEST(StrongIntTest, TestDivideOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test positive vs. positive division.
TEST_T_OP_NUM(9, /, V, 3);
if constexpr (std::is_signed<V>::value) {
// Test negative vs. positive division.
TEST_T_OP_NUM(-9, /, V, 3);
// Test positive vs. negative division.
TEST_T_OP_NUM(9, /, V, -3);
// Test negative vs. negative division.
TEST_T_OP_NUM(-9, /, V, -3);
}
// Test division by one.
TEST_T_OP_NUM(93, /, V, 1);
if constexpr (std::is_signed<V>::value) {
// Test division by a negative.
TEST_T_OP_NUM(93, /, V, -1);
}
// Test division by int8_t.
TEST_T_OP_NUM(93, /, int8_t, 2);
// Test division by uint8_t.
TEST_T_OP_NUM(93, /, uint8_t, 2);
// Test division by int16_t.
TEST_T_OP_NUM(93, /, int16_t, 2);
// Test division by uint16_t.
TEST_T_OP_NUM(93, /, uint16_t, 2);
// Test division by int32_t.
TEST_T_OP_NUM(93, /, int32_t, 2);
// Test division by uint32_t.
TEST_T_OP_NUM(93, /, uint32_t, 2);
// Test division by int64_t.
TEST_T_OP_NUM(93, /, int64_t, 2);
// Test division by uint64_t.
TEST_T_OP_NUM(93, /, uint64_t, 2);
if constexpr (std::is_fundamental_v<V>) {
// Test division by float.
TEST_T_OP_NUM(93, /, float, 2.1);
// Test division by double.
TEST_T_OP_NUM(93, /, double, 2.1);
// Test division by long double.
TEST_T_OP_NUM(93, /, long double, 2.1);
}
}
TYPED_TEST(StrongIntTest, TestModuloOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test positive vs. positive modulo.
TEST_T_OP_NUM(7, %, V, 6);
// Test negative vs. positive modulo.
TEST_T_OP_NUM(-7, %, V, 6);
// Test positive vs. negative modulo.
TEST_T_OP_NUM(7, %, V, -6);
// Test negative vs. negative modulo.
TEST_T_OP_NUM(-7, %, V, -6);
// Test modulo by one.
TEST_T_OP_NUM(93, %, V, 1);
// Test modulo by a negative.
TEST_T_OP_NUM(93, %, V, -5);
// Test modulo by int8_t.
TEST_T_OP_NUM(93, %, int8_t, 5);
// Test modulo by uint8_t.
TEST_T_OP_NUM(93, %, uint8_t, 5);
// Test modulo by int16_t.
TEST_T_OP_NUM(93, %, int16_t, 5);
// Test modulo by uint16_t.
TEST_T_OP_NUM(93, %, uint16_t, 5);
// Test modulo by int32_t.
TEST_T_OP_NUM(93, %, int32_t, 5);
// Test modulo by uint32_t.
TEST_T_OP_NUM(93, %, uint32_t, 5);
// Test modulo by int64_t.
TEST_T_OP_NUM(93, %, int64_t, 5);
// Test modulo by uint64_t.
TEST_T_OP_NUM(93, %, uint64_t, 5);
// Test modulo by a larger value.
TEST_T_OP_NUM(93, %, V, 100);
}
TYPED_TEST(StrongIntTest, TestLeftShiftOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test basic shift.
TEST_T_OP_NUM(0x09, <<, int, 3);
// Test shift by zero.
TEST_T_OP_NUM(0x09, <<, int, 0);
}
TYPED_TEST(StrongIntTest, TestRightShiftOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test basic shift.
TEST_T_OP_NUM(0x09, >>, int, 3);
// Test shift by zero.
TEST_T_OP_NUM(0x09, >>, int, 0);
}
TYPED_TEST(StrongIntTest, TestBitAndOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test basic bit-and.
TEST_T_OP_T(0x09, &, 0x03);
// Test bit-and by zero.
TEST_T_OP_T(0x09, &, 0x00);
}
TYPED_TEST(StrongIntTest, TestBitOrOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test basic bit-or.
TEST_T_OP_T(0x09, |, 0x03);
// Test bit-or by zero.
TEST_T_OP_T(0x09, |, 0x00);
}
TYPED_TEST(StrongIntTest, TestBitXorOperators) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
// Test basic bit-xor.
TEST_T_OP_T(0x09, ^, 0x03);
// Test bit-xor by zero.
TEST_T_OP_T(0x09, ^, 0x00);
}
TYPED_TEST(StrongIntTest, TestComparisonOperators) {
using T = typename TestFixture::T;
T x(93);
EXPECT_TRUE(x == T(93));
EXPECT_TRUE(T(93) == x);
EXPECT_FALSE(x == T(76));
EXPECT_FALSE(T(76) == x);
EXPECT_TRUE(x != T(76));
EXPECT_TRUE(T(76) != x);
EXPECT_FALSE(x != T(93));
EXPECT_FALSE(T(93) != x);
EXPECT_TRUE(x < T(94));
EXPECT_FALSE(T(94) < x);
EXPECT_FALSE(x < T(76));
EXPECT_TRUE(T(76) < x);
EXPECT_TRUE(x <= T(94));
EXPECT_FALSE(T(94) <= x);
EXPECT_FALSE(x <= T(76));
EXPECT_TRUE(T(76) <= x);
EXPECT_TRUE(x <= T(93));
EXPECT_TRUE(T(93) <= x);
EXPECT_TRUE(x > T(76));
EXPECT_FALSE(T(76) > x);
EXPECT_FALSE(x > T(94));
EXPECT_TRUE(T(94) > x);
EXPECT_TRUE(x >= T(76));
EXPECT_FALSE(T(76) >= x);
EXPECT_FALSE(x >= T(94));
EXPECT_TRUE(T(94) >= x);
EXPECT_TRUE(x >= T(93));
EXPECT_TRUE(T(93) >= x);
}
TYPED_TEST(StrongIntTest, TestStreamOutputOperator) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
T x(93);
std::ostringstream out;
out << x;
EXPECT_EQ("93", out.str());
for (const T t :
{T(std::numeric_limits<V>::min()), T(std::numeric_limits<V>::min() + 1),
T(std::numeric_limits<V>::min() + 10),
// Note that we use types with at least 8 bits, so
// adding/subtracting 100 gives us valid values.
T(std::numeric_limits<V>::min() + 100),
T(std::numeric_limits<V>::max() - 100),
T(std::numeric_limits<V>::max() - 10),
T(std::numeric_limits<V>::max() - 1),
T(std::numeric_limits<V>::max())}) {
// Printing `t` should produce the same result as printing `t.value()`.
std::ostringstream out;
out << t;
if constexpr (std::is_same_v<V, int8_t> || std::is_same_v<V, uint8_t>) {
EXPECT_EQ(absl::StrFormat("%v", static_cast<int>(t.value())), out.str());
} else {
EXPECT_EQ(absl::StrFormat("%v", t.value()), out.str());
}
}
}
TYPED_TEST(StrongIntTest, AbslStringify) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
T x(93);
EXPECT_EQ("93", absl::StrCat(x));
EXPECT_EQ("93", absl::StrFormat("%v", x));
EXPECT_EQ("93", absl::Substitute("$0", x));
for (const T t :
{T(std::numeric_limits<V>::min()), T(std::numeric_limits<V>::min() + 1),
T(std::numeric_limits<V>::min() + 10),
// Note that we use types with at least 8 bits, so
// adding/subtracting 100 gives us valid values.
T(std::numeric_limits<V>::min() + 100),
T(std::numeric_limits<V>::max() - 100),
T(std::numeric_limits<V>::max() - 10),
T(std::numeric_limits<V>::max() - 1),
T(std::numeric_limits<V>::max())}) {
// Printing `t` should produce the same result as printing `t.value()`.
if constexpr (std::is_same_v<V, int8_t> || std::is_same_v<V, uint8_t>) {
EXPECT_EQ(absl::StrFormat("%v", static_cast<int>(t.value())),
absl::StrCat(t));
} else {
EXPECT_EQ(absl::StrFormat("%v", t.value()), absl::StrCat(t));
}
}
}
TYPED_TEST(StrongIntTest, TestHasher) {
using T = typename TestFixture::T;
using Hasher = typename T::Hasher;
EXPECT_EQ(Hasher()(T(0)), Hasher()(T(0)));
EXPECT_NE(Hasher()(T(1)), Hasher()(T(2)));
}
TYPED_TEST(StrongIntTest, TestHashFunctor) {
using T = typename TestFixture::T;
using Hasher = typename T::Hasher;
absl::node_hash_map<T, char, Hasher> map;
T a(0);
map[a] = 'c';
EXPECT_EQ('c', map[a]);
map[++a] = 'o';
EXPECT_EQ('o', map[a]);
}
TYPED_TEST(StrongIntTest, TestHash) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
T(std::numeric_limits<V>::min()),
T(std::numeric_limits<V>::min() + 1),
T(std::numeric_limits<V>::min() + 10),
// Note that we use types with at least 8 bits, so adding/subtracting 100
// gives us valid values.
T(std::numeric_limits<V>::min() + 100),
T(std::numeric_limits<V>::max() - 100),
T(std::numeric_limits<V>::max() - 10),
T(std::numeric_limits<V>::max() - 1),
T(std::numeric_limits<V>::max()),
}));
}
TYPED_TEST(StrongIntTest, TestStrongIntRange) {
using T = typename TestFixture::T;
const int64_t kMaxOuterIterations = 100;
for (int64_t to = 0; to < kMaxOuterIterations; ++to) {
int count = 0;
absl::uint128 sum = 0;
for (const auto x : MakeStrongIntRange(T(to))) {
++count;
sum += x.value();
}
EXPECT_EQ(to, count);
EXPECT_EQ(to * (to - 1) / 2, sum);
}
for (int64_t to = 0; to < kMaxOuterIterations; ++to) {
for (int64_t from = 0; from <= to; ++from) {
int count = 0;
absl::uint128 sum = 0;
for (const auto x : MakeStrongIntRange(T(from), T(to))) {
++count;
sum += x.value();
}
EXPECT_EQ(to - from, count);
EXPECT_EQ((to * (to - 1) / 2) - (from * (from - 1) / 2), sum);
}
}
}
// Test Min() and Max() can be used in constexpr.
TYPED_TEST(StrongIntTest, ConstexprMinMax) {
using T = typename TestFixture::T;
using V = typename TestFixture::V;
constexpr V max = T::Max().value();
constexpr V min = T::Min().value();
(void)max;
(void)min;
}
template <typename Ttest, typename Tbig>
bool ExhaustiveTest() {
using V = typename Ttest::ValueType;
Tbig v_min = std::numeric_limits<V>::min();
Tbig v_max = std::numeric_limits<V>::max();
for (Tbig lhs = v_min; lhs <= v_max; ++lhs) {
for (Tbig rhs = v_min; rhs <= v_max; ++rhs) {
{
Ttest t_lhs(lhs);
Ttest t_rhs(rhs);
EXPECT_EQ(Ttest(lhs + rhs), t_lhs + t_rhs);
}
{
Ttest t_lhs(lhs);
Ttest t_rhs(rhs);
EXPECT_EQ(Ttest(lhs - rhs), t_lhs - t_rhs);
}
{
Ttest t_lhs(lhs);
EXPECT_EQ(Ttest(lhs * rhs), t_lhs * rhs);
}
{
Ttest t_lhs(lhs);
if (rhs != 0) {
EXPECT_EQ(Ttest(lhs / rhs), t_lhs / rhs);
}
}
{
Ttest t_lhs(lhs);
if (rhs != 0) {
EXPECT_EQ(Ttest(lhs % rhs), t_lhs % rhs);
}
}
}
}
return true;
}
TEST(StrongIntTest, Exhaustive) {
EXPECT_TRUE((ExhaustiveTest<StrongInt8, int>()));
EXPECT_TRUE((ExhaustiveTest<StrongUInt8, int>()));
}
TEST(StrongIntTest, ExplicitCasting) {
StrongInt8 x(8);
EXPECT_THAT(static_cast<int8_t>(x), Eq(x.value()));
EXPECT_THAT(static_cast<size_t>(x), Eq(x.value()));
}
// Create some types outside the util_intops:: namespace to prove that
// conversions work.
namespace other_namespace {
DEFINE_STRONG_INT_TYPE(Inches, int64_t);
DEFINE_STRONG_INT_TYPE(Feet, int64_t);
DEFINE_STRONG_INT_TYPE(Centimeters, int32_t);
constexpr Feet StrongIntConvert(const Inches& arg, Feet* /* unused */) {
return Feet(arg.value() / 12);
}
constexpr Centimeters StrongIntConvert(const Inches& arg,
Centimeters* /* unused */) {
return Centimeters(arg.value() * 2.54);
}
TEST(StrongIntTest, TestConversion) {
{ // Test simple copy construction.
Inches in1(12);
Inches in2(in1);
EXPECT_EQ(12, in2.value());
}
{ // Test conversion from Inches to Feet.
Inches in(60);
Feet ft(in);
EXPECT_EQ(5, ft.value());
constexpr Inches kIn(60);
constexpr Feet kFt(kIn);
EXPECT_EQ(kFt, ft);
}
{ // Test conversion from Inches to Centimeters.
Inches in(10);
Centimeters cm(in);
EXPECT_EQ(25, cm.value());
constexpr Inches kIn(10);
constexpr Centimeters kCm(kIn);
EXPECT_EQ(kCm, cm);
}
}
// Test SFINAE on template<T> constexpr StrongInt(T init_value) constructor.
// Without it, the non-convertible case in the assertions below would become a
// hard compilation failure because of the compile-time evaluation of
// static_cast<ValueType>(init_value) in the _constexpr_ constructor body.
template <typename T>
struct StrongIntTestHelper {
template <typename U, typename = typename std::enable_if<
std::is_constructible<StrongInt<void, T>, U>::value,
void>::type>
StrongIntTestHelper(U /*x*/) {} // NOLINT
};
static_assert(!std::is_convertible<void, StrongIntTestHelper<int>>::value, "");
static_assert(std::is_convertible<int, StrongIntTestHelper<int>>::value, "");
// Test the IsStrongInt type trait.
static_assert(IsStrongInt<StrongInt8>::value, "");
static_assert(IsStrongInt<StrongUInt16>::value, "");
static_assert(!IsStrongInt<int8_t>::value, "");
static_assert(!IsStrongInt<long>::value, ""); // NOLINT
static_assert(!IsStrongInt<void>::value, "");
// Test we can define our own AbslStringify.
DEFINE_STRONG_INT_TYPE(CustomPrint, uint32_t);
template <typename Sink>
void AbslStringify(Sink& sink, CustomPrint arg) {
absl::Format(&sink, "CustomPrint(%v)", arg.value());
}
TEST(StrongIntTest, CustomPrint) {
CustomPrint val(42);
EXPECT_EQ(absl::StrCat(val), "CustomPrint(42)");
}
} // namespace other_namespace
} // namespace
} // namespace util_intops