blob: 711c805e89c8cf9589cc5fe170838b9aee297bf5 [file] [log] [blame]
/*
* Copyright 2019 The Chromium Authors. All rights reserved.
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "rtc_base/strong_alias.h"
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include "rtc_base/containers/flat_map.h"
#include "rtc_base/gunit.h"
#include "test/gmock.h"
// This is a copy of
// https://source.chromium.org/chromium/chromium/src/+/main:base/types/strong_alias_unittest.cc
// but adapted to use WebRTC's includes, remove unit tests that test the ostream
// operator (it's removed in this port) and other adaptations to pass lint.
namespace webrtc {
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);
}
} // 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<typename std::remove_reference<
decltype(const_alias.value())>::type>::value,
"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<FooAlias>::value,
"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, 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<FooAlias>::value,
"Should be possible to default-construct a StrongAlias.");
static_assert(
std::is_trivially_default_constructible<FooAlias>::value ==
std::is_trivially_default_constructible<TypeParam>::value,
"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<FooAlias>::value,
"int-based alias should have standard layout. ");
static_assert(std::is_trivially_copyable<FooAlias>::value,
"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<FooAlias, BarAlias>::value,
"Should be impossible to construct FooAlias from a BarAlias.");
static_assert(!std::is_convertible<BarAlias, FooAlias>::value,
"Should be impossible to convert a BarAlias into FooAlias.");
}
TYPED_TEST(StrongAliasTest, CannotBeImplicitlyConverterToUnderlyingValue) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
static_assert(!std::is_convertible<FooAlias, TypeParam>::value,
"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:
explicit 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, CanBeKeysInFlatMap) {
using FooAlias = StrongAlias<class FooTag, TypeParam>;
webrtc::flat_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, 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>;
// Check constructors.
static constexpr FooAlias kZero{};
static constexpr FooAlias kOne(1);
// Check operator*.
static_assert(*kZero == 0, "");
static_assert(*kOne == 1, "");
// 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, "");
}
TEST(StrongAliasTest, BooleansAreEvaluatedAsBooleans) {
using BoolAlias = StrongAlias<class BoolTag, bool>;
BoolAlias happy(true);
BoolAlias sad(false);
EXPECT_TRUE(happy);
EXPECT_FALSE(sad);
EXPECT_TRUE(*happy);
EXPECT_FALSE(*sad);
}
} // namespace webrtc