/* * 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 #include #include #include #include #include #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 T GetExampleValue(int index); template <> int GetExampleValue(int index) { return 5 + index; } template <> uint64_t GetExampleValue(int index) { return 500U + index; } template <> std::string GetExampleValue(int index) { return std::string('a', index); } } // namespace template class StrongAliasTest : public ::testing::Test {}; using TestedTypes = ::testing::Types; TYPED_TEST_SUITE(StrongAliasTest, TestedTypes); TYPED_TEST(StrongAliasTest, ValueAccessesUnderlyingValue) { using FooAlias = StrongAlias; // Const value getter. const FooAlias const_alias(GetExampleValue(1)); EXPECT_EQ(GetExampleValue(1), const_alias.value()); static_assert(std::is_const::type>::value, "Reference returned by const value getter should be const."); } TYPED_TEST(StrongAliasTest, ExplicitConversionToUnderlyingValue) { using FooAlias = StrongAlias; const FooAlias const_alias(GetExampleValue(1)); EXPECT_EQ(GetExampleValue(1), static_cast(const_alias)); } TYPED_TEST(StrongAliasTest, CanBeCopyConstructed) { using FooAlias = StrongAlias; FooAlias alias(GetExampleValue(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; FooAlias alias(GetExampleValue(0)); FooAlias move_constructed = std::move(alias); EXPECT_EQ(move_constructed, FooAlias(GetExampleValue(0))); FooAlias alias2(GetExampleValue(2)); FooAlias move_assigned; move_assigned = std::move(alias2); EXPECT_EQ(move_assigned, FooAlias(GetExampleValue(2))); // Check that FooAlias is nothrow move constructible. This matters for // performance when used in std::vectors. static_assert(std::is_nothrow_move_constructible::value, "Error: Alias is not nothow move constructible"); } TYPED_TEST(StrongAliasTest, CanBeConstructedFromMoveOnlyType) { // Note, using a move-only unique_ptr to T: using FooAlias = StrongAlias>; FooAlias a(std::make_unique(GetExampleValue(0))); EXPECT_EQ(*a.value(), GetExampleValue(0)); auto bare_value = std::make_unique(GetExampleValue(1)); FooAlias b(std::move(bare_value)); EXPECT_EQ(*b.value(), GetExampleValue(1)); } TYPED_TEST(StrongAliasTest, MutableOperatorArrow) { // Note, using a move-only unique_ptr to T: using Ptr = std::unique_ptr; using FooAlias = StrongAlias; FooAlias a(std::make_unique()); 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; using FooAlias = StrongAlias; FooAlias a(std::make_unique()); FooAlias b(std::make_unique()); 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; using FooAlias = StrongAlias; FooAlias a(std::make_unique()); FooAlias b(std::make_unique()); 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; static_assert(sizeof(FooAlias) == sizeof(TypeParam), "StrongAlias should be as large as the underlying type."); } TYPED_TEST(StrongAliasTest, IsDefaultConstructible) { using FooAlias = StrongAlias; static_assert(std::is_default_constructible::value, "Should be possible to default-construct a StrongAlias."); static_assert( std::is_trivially_default_constructible::value == std::is_trivially_default_constructible::value, "Should be possible to trivially default-construct a StrongAlias iff the " "underlying type is trivially default constructible."); } TEST(StrongAliasTest, TrivialTypeAliasIsStandardLayout) { using FooAlias = StrongAlias; static_assert(std::is_standard_layout::value, "int-based alias should have standard layout. "); static_assert(std::is_trivially_copyable::value, "int-based alias should be trivially copyable. "); } TYPED_TEST(StrongAliasTest, CannotBeCreatedFromDifferentAlias) { using FooAlias = StrongAlias; using BarAlias = StrongAlias; static_assert(!std::is_constructible::value, "Should be impossible to construct FooAlias from a BarAlias."); static_assert(!std::is_convertible::value, "Should be impossible to convert a BarAlias into FooAlias."); } TYPED_TEST(StrongAliasTest, CannotBeImplicitlyConverterToUnderlyingValue) { using FooAlias = StrongAlias; static_assert(!std::is_convertible::value, "Should be impossible to implicitly convert a StrongAlias into " "an underlying type."); } TYPED_TEST(StrongAliasTest, ComparesEqualToSameValue) { using FooAlias = StrongAlias; // Comparison to self: const FooAlias a = FooAlias(GetExampleValue(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(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; const FooAlias a = FooAlias(GetExampleValue(0)); const FooAlias b = FooAlias(GetExampleValue(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 { public: explicit CountryCode(const std::string& value) : StrongAlias::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; using ComplexAlias = StrongAlias; 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; webrtc::flat_map map; FooAlias k1(GetExampleValue(0)); FooAlias k2(GetExampleValue(1)); map[k1] = "value1"; map[k2] = "value2"; EXPECT_EQ(map[k1], "value1"); EXPECT_EQ(map[k2], "value2"); } TYPED_TEST(StrongAliasTest, CanBeKeysInStdMap) { using FooAlias = StrongAlias; std::map map; FooAlias k1(GetExampleValue(0)); FooAlias k2(GetExampleValue(1)); map[k1] = "value1"; map[k2] = "value2"; EXPECT_EQ(map[k1], "value1"); EXPECT_EQ(map[k2], "value2"); } TYPED_TEST(StrongAliasTest, CanDifferentiateOverloads) { using FooAlias = StrongAlias; using BarAlias = StrongAlias; 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; // 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(kZero) == 0, ""); static_assert(static_cast(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; BoolAlias happy(true); BoolAlias sad(false); EXPECT_TRUE(happy); EXPECT_FALSE(sad); EXPECT_TRUE(*happy); EXPECT_FALSE(*sad); } } // namespace webrtc