blob: 760306bf6cbacd43acfa49462f77f24376887234 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/types/strong_alias.h"
#include <cstdint>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include "base/types/supports_ostream_operator.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(ENABLE_BASE_TRACING)
#include "third_party/perfetto/include/perfetto/test/traced_value_test_support.h" // no-presubmit-check nogncheck
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
namespace base {
namespace {
// For test correctnenss, it's important that these getters return lexically
// incrementing values as |index| grows.
template <typename T>
T GetExampleValue(int index);
template <>
int GetExampleValue<int>(int index) {
return 5 + index;
}
template <>
uint64_t GetExampleValue<uint64_t>(int index) {
return 500U + index;
}
template <>
std::string GetExampleValue<std::string>(int index) {
return std::string('a', index);
}
template <typename T, typename U>
bool StreamOutputSame(const T& a, const U& b) {
std::stringstream ssa;
ssa << a;
std::stringstream ssb;
ssb << b;
return ssa.str() == ssb.str();
}
} // namespace
template <typename T>
class StrongAliasTest : public ::testing::Test {};
using TestedTypes = ::testing::Types<int, uint64_t, std::string>;
TYPED_TEST_SUITE(StrongAliasTest, TestedTypes);
TYPED_TEST(StrongAliasTest, ValueAccessesUnderlyingValue) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
// Const value getter.
const FooAlias const_alias(GetExampleValue<TypeParam>(1));
EXPECT_EQ(GetExampleValue<TypeParam>(1), const_alias.value());
static_assert(
std::is_const_v<
typename std::remove_reference<decltype(const_alias.value())>::type>,
"Reference returned by const value getter should be const.");
}
TYPED_TEST(StrongAliasTest, ExplicitConversionToUnderlyingValue) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
const FooAlias const_alias(GetExampleValue<TypeParam>(1));
EXPECT_EQ(GetExampleValue<TypeParam>(1), static_cast<TypeParam>(const_alias));
}
TYPED_TEST(StrongAliasTest, CanBeCopyConstructed) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
FooAlias alias(GetExampleValue<TypeParam>(0));
FooAlias copy_constructed = alias;
EXPECT_EQ(copy_constructed, alias);
FooAlias copy_assigned;
copy_assigned = alias;
EXPECT_EQ(copy_assigned, alias);
}
TYPED_TEST(StrongAliasTest, CanBeMoveConstructed) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
FooAlias alias(GetExampleValue<TypeParam>(0));
FooAlias move_constructed = std::move(alias);
EXPECT_EQ(move_constructed, FooAlias(GetExampleValue<TypeParam>(0)));
FooAlias alias2(GetExampleValue<TypeParam>(2));
FooAlias move_assigned;
move_assigned = std::move(alias2);
EXPECT_EQ(move_assigned, FooAlias(GetExampleValue<TypeParam>(2)));
// Check that FooAlias is nothrow move constructible. This matters for
// performance when used in std::vectors.
static_assert(std::is_nothrow_move_constructible_v<FooAlias>,
"Error: Alias is not nothow move constructible");
}
TYPED_TEST(StrongAliasTest, CanBeConstructedFromMoveOnlyType) {
// Note, using a move-only unique_ptr to T:
using FooAlias = StrongAlias<class FooTag, std::unique_ptr<TypeParam>>;
FooAlias a(std::make_unique<TypeParam>(GetExampleValue<TypeParam>(0)));
EXPECT_EQ(*a.value(), GetExampleValue<TypeParam>(0));
auto bare_value = std::make_unique<TypeParam>(GetExampleValue<TypeParam>(1));
FooAlias b(std::move(bare_value));
EXPECT_EQ(*b.value(), GetExampleValue<TypeParam>(1));
}
TYPED_TEST(StrongAliasTest, MutableOperatorArrow) {
// Note, using a move-only unique_ptr to T:
using Ptr = std::unique_ptr<TypeParam>;
using FooAlias = StrongAlias<class FooTag, Ptr>;
FooAlias a(std::make_unique<TypeParam>());
EXPECT_TRUE(a.value());
// Check that `a` can be modified through the use of operator->.
a->reset();
EXPECT_FALSE(a.value());
}
TYPED_TEST(StrongAliasTest, MutableOperatorStar) {
// Note, using a move-only unique_ptr to T:
using Ptr = std::unique_ptr<TypeParam>;
using FooAlias = StrongAlias<class FooTag, Ptr>;
FooAlias a(std::make_unique<TypeParam>());
FooAlias b(std::make_unique<TypeParam>());
EXPECT_TRUE(*a);
EXPECT_TRUE(*b);
// Check that both the mutable l-value and r-value overloads work and we can
// move out of the aliases.
{ Ptr ignore(*std::move(a)); }
{ Ptr ignore(std::move(*b)); }
EXPECT_FALSE(a.value());
EXPECT_FALSE(b.value());
}
TYPED_TEST(StrongAliasTest, MutableValue) {
// Note, using a move-only unique_ptr to T:
using Ptr = std::unique_ptr<TypeParam>;
using FooAlias = StrongAlias<class FooTag, Ptr>;
FooAlias a(std::make_unique<TypeParam>());
FooAlias b(std::make_unique<TypeParam>());
EXPECT_TRUE(a.value());
EXPECT_TRUE(b.value());
// Check that both the mutable l-value and r-value overloads work and we can
// move out of the aliases.
{ Ptr ignore(std::move(a).value()); }
{ Ptr ignore(std::move(b.value())); }
EXPECT_FALSE(a.value());
EXPECT_FALSE(b.value());
}
TYPED_TEST(StrongAliasTest, CanBeWrittenToOutputStream) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
const FooAlias a(GetExampleValue<TypeParam>(0));
EXPECT_TRUE(StreamOutputSame(GetExampleValue<TypeParam>(0), a)) << a;
}
TYPED_TEST(StrongAliasTest, SizeSameAsUnderlyingType) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
static_assert(sizeof(FooAlias) == sizeof(TypeParam),
"StrongAlias should be as large as the underlying type.");
}
TYPED_TEST(StrongAliasTest, IsDefaultConstructible) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
static_assert(std::is_default_constructible_v<FooAlias>,
"Should be possible to default-construct a StrongAlias.");
static_assert(
std::is_trivially_default_constructible_v<FooAlias> ==
std::is_trivially_default_constructible_v<TypeParam>,
"Should be possible to trivially default-construct a StrongAlias iff the "
"underlying type is trivially default constructible.");
}
TEST(StrongAliasTest, TrivialTypeAliasIsStandardLayout) {
using FooAlias = StrongAlias<class FooTag, int>;
static_assert(std::is_standard_layout_v<FooAlias>,
"int-based alias should have standard layout. ");
static_assert(std::is_trivially_copyable_v<FooAlias>,
"int-based alias should be trivially copyable. ");
}
TYPED_TEST(StrongAliasTest, CannotBeCreatedFromDifferentAlias) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
using BarAlias = StrongAlias<class BarTag, TypeParam>;
static_assert(!std::is_constructible_v<FooAlias, BarAlias>,
"Should be impossible to construct FooAlias from a BarAlias.");
static_assert(!std::is_convertible_v<BarAlias, FooAlias>,
"Should be impossible to convert a BarAlias into FooAlias.");
}
TYPED_TEST(StrongAliasTest, CannotBeImplicitlyConverterToUnderlyingValue) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
static_assert(!std::is_convertible_v<FooAlias, TypeParam>,
"Should be impossible to implicitly convert a StrongAlias into "
"an underlying type.");
}
TYPED_TEST(StrongAliasTest, ComparesEqualToSameValue) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
// Comparison to self:
const FooAlias a = FooAlias(GetExampleValue<TypeParam>(0));
EXPECT_EQ(a, a);
EXPECT_FALSE(a != a);
EXPECT_TRUE(a >= a);
EXPECT_TRUE(a <= a);
EXPECT_FALSE(a > a);
EXPECT_FALSE(a < a);
// Comparison to other equal object:
const FooAlias b = FooAlias(GetExampleValue<TypeParam>(0));
EXPECT_EQ(a, b);
EXPECT_FALSE(a != b);
EXPECT_TRUE(a >= b);
EXPECT_TRUE(a <= b);
EXPECT_FALSE(a > b);
EXPECT_FALSE(a < b);
}
TYPED_TEST(StrongAliasTest, ComparesCorrectlyToDifferentValue) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
const FooAlias a = FooAlias(GetExampleValue<TypeParam>(0));
const FooAlias b = FooAlias(GetExampleValue<TypeParam>(1));
EXPECT_NE(a, b);
EXPECT_FALSE(a == b);
EXPECT_TRUE(b >= a);
EXPECT_TRUE(a <= b);
EXPECT_TRUE(b > a);
EXPECT_TRUE(a < b);
}
TEST(StrongAliasTest, CanBeDerivedFrom) {
// Aliases can be enriched by custom operations or validations if needed.
// Ideally, one could go from a 'using' declaration to a derived class to add
// those methods without the need to change any other code.
class CountryCode : public StrongAlias<CountryCode, std::string> {
public:
CountryCode(const std::string& value)
: StrongAlias<CountryCode, std::string>::StrongAlias(value) {
if (value_.length() != 2) {
// Country code invalid!
value_.clear(); // is_null() will return true.
}
}
bool is_null() const { return value_.empty(); }
};
CountryCode valid("US");
EXPECT_FALSE(valid.is_null());
CountryCode invalid("United States");
EXPECT_TRUE(invalid.is_null());
}
TEST(StrongAliasTest, CanWrapComplexStructures) {
// A pair of strings implements odering and can, in principle, be used as
// a base of StrongAlias.
using PairOfStrings = std::pair<std::string, std::string>;
using ComplexAlias = StrongAlias<class FooTag, PairOfStrings>;
ComplexAlias a1{std::make_pair("aaa", "bbb")};
ComplexAlias a2{std::make_pair("ccc", "ddd")};
EXPECT_TRUE(a1 < a2);
EXPECT_TRUE(a1.value() == PairOfStrings("aaa", "bbb"));
// Note a caveat, an std::pair doesn't have an overload of operator<<, and it
// cannot be easily added since ADL rules would require it to be in the std
// namespace. So we can't print ComplexAlias.
}
TYPED_TEST(StrongAliasTest, CanBeKeysInStdUnorderedMap) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
std::unordered_map<FooAlias, std::string, typename FooAlias::Hasher> map;
FooAlias k1(GetExampleValue<TypeParam>(0));
FooAlias k2(GetExampleValue<TypeParam>(1));
map[k1] = "value1";
map[k2] = "value2";
EXPECT_EQ(map[k1], "value1");
EXPECT_EQ(map[k2], "value2");
}
TYPED_TEST(StrongAliasTest, CanBeKeysInStdMap) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
std::map<FooAlias, std::string> map;
FooAlias k1(GetExampleValue<TypeParam>(0));
FooAlias k2(GetExampleValue<TypeParam>(1));
map[k1] = "value1";
map[k2] = "value2";
EXPECT_EQ(map[k1], "value1");
EXPECT_EQ(map[k2], "value2");
}
TYPED_TEST(StrongAliasTest, CanDifferentiateOverloads) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
using BarAlias = StrongAlias<class BarTag, TypeParam>;
class Scope {
public:
static std::string Overload(FooAlias) { return "FooAlias"; }
static std::string Overload(BarAlias) { return "BarAlias"; }
};
EXPECT_EQ("FooAlias", Scope::Overload(FooAlias()));
EXPECT_EQ("BarAlias", Scope::Overload(BarAlias()));
}
TEST(StrongAliasTest, EnsureConstexpr) {
using FooAlias = StrongAlias<class FooTag, int>;
using BarAlias = StrongAlias<class BarTag, std::string_view>;
// Check constructors.
static constexpr FooAlias kZero{};
static constexpr FooAlias kOne(1);
static constexpr BarAlias kHello("Hello");
// Check operator->.
static_assert(kHello->size() == 5, "");
// Check operator*.
static_assert(*kZero == 0, "");
static_assert(*kOne == 1, "");
static_assert(*kHello == "Hello", "");
// Check value().
static_assert(kZero.value() == 0, "");
static_assert(kOne.value() == 1, "");
// Check explicit conversions to underlying type.
static_assert(static_cast<int>(kZero) == 0, "");
static_assert(static_cast<int>(kOne) == 1, "");
// Check comparison operations.
static_assert(kZero == kZero, "");
static_assert(kZero != kOne, "");
static_assert(kZero < kOne, "");
static_assert(kZero <= kOne, "");
static_assert(kOne > kZero, "");
static_assert(kOne >= kZero, "");
}
// This next test is compile-time, and thus not in an actual TEST since it would
// just result in running an empty test.
void StreamOperatorExists() {
// Aliases of ints should be streamable because ints are streamable.
using StreamableAlias = StrongAlias<class IntTag, int>;
static_assert(internal::SupportsOstreamOperator<StreamableAlias>);
// Aliases of a class which does not expose a stream operator should
// themselves not be streamable.
class Scope {
public:
Scope() = default;
};
using NonStreamableAlias = StrongAlias<class ScopeTag, Scope>;
static_assert(!internal::SupportsOstreamOperator<NonStreamableAlias>);
}
#if BUILDFLAG(ENABLE_BASE_TRACING)
TEST(StrongAliasTest, TracedValueSupport) {
using IntAlias = StrongAlias<class FooTag, int>;
EXPECT_EQ(perfetto::TracedValueToString(IntAlias(42)), "42");
}
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
} // namespace base