Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 The Chromium Authors. All rights reserved. |
| 3 | * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. |
| 4 | * |
| 5 | * Use of this source code is governed by a BSD-style license |
| 6 | * that can be found in the LICENSE file in the root of the source |
| 7 | * tree. An additional intellectual property rights grant can be found |
| 8 | * in the file PATENTS. All contributing project authors may |
| 9 | * be found in the AUTHORS file in the root of the source tree. |
| 10 | */ |
Victor Boivie | b918230 | 2021-09-21 12:51:05 | [diff] [blame] | 11 | #include "rtc_base/strong_alias.h" |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 12 | |
| 13 | #include <cstdint> |
| 14 | #include <map> |
| 15 | #include <memory> |
| 16 | #include <string> |
| 17 | #include <type_traits> |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 18 | #include <utility> |
| 19 | |
Victor Boivie | 3ec9e03 | 2021-08-18 13:22:42 | [diff] [blame] | 20 | #include "rtc_base/containers/flat_map.h" |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 21 | #include "rtc_base/gunit.h" |
| 22 | #include "test/gmock.h" |
| 23 | |
| 24 | // This is a copy of |
Tony Herre | b0ed120 | 2021-07-22 15:40:44 | [diff] [blame] | 25 | // https://source.chromium.org/chromium/chromium/src/+/main:base/types/strong_alias_unittest.cc |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 26 | // but adapted to use WebRTC's includes, remove unit tests that test the ostream |
| 27 | // operator (it's removed in this port) and other adaptations to pass lint. |
| 28 | |
Victor Boivie | b918230 | 2021-09-21 12:51:05 | [diff] [blame] | 29 | namespace webrtc { |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 30 | namespace { |
| 31 | |
| 32 | // For test correctnenss, it's important that these getters return lexically |
Artem Titov | 68e98fb | 2021-07-26 11:37:13 | [diff] [blame] | 33 | // incrementing values as `index` grows. |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 34 | template <typename T> |
| 35 | T GetExampleValue(int index); |
| 36 | |
| 37 | template <> |
| 38 | int GetExampleValue<int>(int index) { |
| 39 | return 5 + index; |
| 40 | } |
| 41 | template <> |
| 42 | uint64_t GetExampleValue<uint64_t>(int index) { |
| 43 | return 500U + index; |
| 44 | } |
| 45 | |
| 46 | template <> |
| 47 | std::string GetExampleValue<std::string>(int index) { |
| 48 | return std::string('a', index); |
| 49 | } |
| 50 | |
| 51 | } // namespace |
| 52 | |
| 53 | template <typename T> |
| 54 | class StrongAliasTest : public ::testing::Test {}; |
| 55 | |
| 56 | using TestedTypes = ::testing::Types<int, uint64_t, std::string>; |
| 57 | TYPED_TEST_SUITE(StrongAliasTest, TestedTypes); |
| 58 | |
| 59 | TYPED_TEST(StrongAliasTest, ValueAccessesUnderlyingValue) { |
| 60 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 61 | |
| 62 | // Const value getter. |
| 63 | const FooAlias const_alias(GetExampleValue<TypeParam>(1)); |
| 64 | EXPECT_EQ(GetExampleValue<TypeParam>(1), const_alias.value()); |
| 65 | static_assert(std::is_const<typename std::remove_reference<decltype( |
| 66 | const_alias.value())>::type>::value, |
| 67 | "Reference returned by const value getter should be const."); |
| 68 | } |
| 69 | |
| 70 | TYPED_TEST(StrongAliasTest, ExplicitConversionToUnderlyingValue) { |
| 71 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 72 | |
| 73 | const FooAlias const_alias(GetExampleValue<TypeParam>(1)); |
| 74 | EXPECT_EQ(GetExampleValue<TypeParam>(1), static_cast<TypeParam>(const_alias)); |
| 75 | } |
| 76 | |
| 77 | TYPED_TEST(StrongAliasTest, CanBeCopyConstructed) { |
| 78 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 79 | FooAlias alias(GetExampleValue<TypeParam>(0)); |
| 80 | FooAlias copy_constructed = alias; |
| 81 | EXPECT_EQ(copy_constructed, alias); |
| 82 | |
| 83 | FooAlias copy_assigned; |
| 84 | copy_assigned = alias; |
| 85 | EXPECT_EQ(copy_assigned, alias); |
| 86 | } |
| 87 | |
| 88 | TYPED_TEST(StrongAliasTest, CanBeMoveConstructed) { |
| 89 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 90 | FooAlias alias(GetExampleValue<TypeParam>(0)); |
| 91 | FooAlias move_constructed = std::move(alias); |
| 92 | EXPECT_EQ(move_constructed, FooAlias(GetExampleValue<TypeParam>(0))); |
| 93 | |
| 94 | FooAlias alias2(GetExampleValue<TypeParam>(2)); |
| 95 | FooAlias move_assigned; |
| 96 | move_assigned = std::move(alias2); |
| 97 | EXPECT_EQ(move_assigned, FooAlias(GetExampleValue<TypeParam>(2))); |
| 98 | |
| 99 | // Check that FooAlias is nothrow move constructible. This matters for |
| 100 | // performance when used in std::vectors. |
| 101 | static_assert(std::is_nothrow_move_constructible<FooAlias>::value, |
| 102 | "Error: Alias is not nothow move constructible"); |
| 103 | } |
| 104 | |
| 105 | TYPED_TEST(StrongAliasTest, CanBeConstructedFromMoveOnlyType) { |
| 106 | // Note, using a move-only unique_ptr to T: |
| 107 | using FooAlias = StrongAlias<class FooTag, std::unique_ptr<TypeParam>>; |
| 108 | |
| 109 | FooAlias a(std::make_unique<TypeParam>(GetExampleValue<TypeParam>(0))); |
| 110 | EXPECT_EQ(*a.value(), GetExampleValue<TypeParam>(0)); |
| 111 | |
| 112 | auto bare_value = std::make_unique<TypeParam>(GetExampleValue<TypeParam>(1)); |
| 113 | FooAlias b(std::move(bare_value)); |
| 114 | EXPECT_EQ(*b.value(), GetExampleValue<TypeParam>(1)); |
| 115 | } |
| 116 | |
| 117 | TYPED_TEST(StrongAliasTest, MutableOperatorArrow) { |
| 118 | // Note, using a move-only unique_ptr to T: |
| 119 | using Ptr = std::unique_ptr<TypeParam>; |
| 120 | using FooAlias = StrongAlias<class FooTag, Ptr>; |
| 121 | |
| 122 | FooAlias a(std::make_unique<TypeParam>()); |
| 123 | EXPECT_TRUE(a.value()); |
| 124 | |
| 125 | // Check that `a` can be modified through the use of operator->. |
| 126 | a->reset(); |
| 127 | |
| 128 | EXPECT_FALSE(a.value()); |
| 129 | } |
| 130 | |
| 131 | TYPED_TEST(StrongAliasTest, MutableOperatorStar) { |
| 132 | // Note, using a move-only unique_ptr to T: |
| 133 | using Ptr = std::unique_ptr<TypeParam>; |
| 134 | using FooAlias = StrongAlias<class FooTag, Ptr>; |
| 135 | |
| 136 | FooAlias a(std::make_unique<TypeParam>()); |
| 137 | FooAlias b(std::make_unique<TypeParam>()); |
| 138 | EXPECT_TRUE(*a); |
| 139 | EXPECT_TRUE(*b); |
| 140 | |
| 141 | // Check that both the mutable l-value and r-value overloads work and we can |
| 142 | // move out of the aliases. |
| 143 | { Ptr ignore(*std::move(a)); } |
| 144 | { Ptr ignore(std::move(*b)); } |
| 145 | |
| 146 | EXPECT_FALSE(a.value()); |
| 147 | EXPECT_FALSE(b.value()); |
| 148 | } |
| 149 | |
| 150 | TYPED_TEST(StrongAliasTest, MutableValue) { |
| 151 | // Note, using a move-only unique_ptr to T: |
| 152 | using Ptr = std::unique_ptr<TypeParam>; |
| 153 | using FooAlias = StrongAlias<class FooTag, Ptr>; |
| 154 | |
| 155 | FooAlias a(std::make_unique<TypeParam>()); |
| 156 | FooAlias b(std::make_unique<TypeParam>()); |
| 157 | EXPECT_TRUE(a.value()); |
| 158 | EXPECT_TRUE(b.value()); |
| 159 | |
| 160 | // Check that both the mutable l-value and r-value overloads work and we can |
| 161 | // move out of the aliases. |
| 162 | { Ptr ignore(std::move(a).value()); } |
| 163 | { Ptr ignore(std::move(b.value())); } |
| 164 | |
| 165 | EXPECT_FALSE(a.value()); |
| 166 | EXPECT_FALSE(b.value()); |
| 167 | } |
| 168 | |
| 169 | TYPED_TEST(StrongAliasTest, SizeSameAsUnderlyingType) { |
| 170 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 171 | static_assert(sizeof(FooAlias) == sizeof(TypeParam), |
| 172 | "StrongAlias should be as large as the underlying type."); |
| 173 | } |
| 174 | |
| 175 | TYPED_TEST(StrongAliasTest, IsDefaultConstructible) { |
| 176 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 177 | static_assert(std::is_default_constructible<FooAlias>::value, |
| 178 | "Should be possible to default-construct a StrongAlias."); |
| 179 | static_assert( |
| 180 | std::is_trivially_default_constructible<FooAlias>::value == |
| 181 | std::is_trivially_default_constructible<TypeParam>::value, |
| 182 | "Should be possible to trivially default-construct a StrongAlias iff the " |
| 183 | "underlying type is trivially default constructible."); |
| 184 | } |
| 185 | |
| 186 | TEST(StrongAliasTest, TrivialTypeAliasIsStandardLayout) { |
| 187 | using FooAlias = StrongAlias<class FooTag, int>; |
| 188 | static_assert(std::is_standard_layout<FooAlias>::value, |
| 189 | "int-based alias should have standard layout. "); |
| 190 | static_assert(std::is_trivially_copyable<FooAlias>::value, |
| 191 | "int-based alias should be trivially copyable. "); |
| 192 | } |
| 193 | |
| 194 | TYPED_TEST(StrongAliasTest, CannotBeCreatedFromDifferentAlias) { |
| 195 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 196 | using BarAlias = StrongAlias<class BarTag, TypeParam>; |
| 197 | static_assert(!std::is_constructible<FooAlias, BarAlias>::value, |
| 198 | "Should be impossible to construct FooAlias from a BarAlias."); |
| 199 | static_assert(!std::is_convertible<BarAlias, FooAlias>::value, |
| 200 | "Should be impossible to convert a BarAlias into FooAlias."); |
| 201 | } |
| 202 | |
| 203 | TYPED_TEST(StrongAliasTest, CannotBeImplicitlyConverterToUnderlyingValue) { |
| 204 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 205 | static_assert(!std::is_convertible<FooAlias, TypeParam>::value, |
| 206 | "Should be impossible to implicitly convert a StrongAlias into " |
| 207 | "an underlying type."); |
| 208 | } |
| 209 | |
| 210 | TYPED_TEST(StrongAliasTest, ComparesEqualToSameValue) { |
| 211 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 212 | // Comparison to self: |
| 213 | const FooAlias a = FooAlias(GetExampleValue<TypeParam>(0)); |
| 214 | EXPECT_EQ(a, a); |
| 215 | EXPECT_FALSE(a != a); |
| 216 | EXPECT_TRUE(a >= a); |
| 217 | EXPECT_TRUE(a <= a); |
| 218 | EXPECT_FALSE(a > a); |
| 219 | EXPECT_FALSE(a < a); |
| 220 | // Comparison to other equal object: |
| 221 | const FooAlias b = FooAlias(GetExampleValue<TypeParam>(0)); |
| 222 | EXPECT_EQ(a, b); |
| 223 | EXPECT_FALSE(a != b); |
| 224 | EXPECT_TRUE(a >= b); |
| 225 | EXPECT_TRUE(a <= b); |
| 226 | EXPECT_FALSE(a > b); |
| 227 | EXPECT_FALSE(a < b); |
| 228 | } |
| 229 | |
| 230 | TYPED_TEST(StrongAliasTest, ComparesCorrectlyToDifferentValue) { |
| 231 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 232 | const FooAlias a = FooAlias(GetExampleValue<TypeParam>(0)); |
| 233 | const FooAlias b = FooAlias(GetExampleValue<TypeParam>(1)); |
| 234 | EXPECT_NE(a, b); |
| 235 | EXPECT_FALSE(a == b); |
| 236 | EXPECT_TRUE(b >= a); |
| 237 | EXPECT_TRUE(a <= b); |
| 238 | EXPECT_TRUE(b > a); |
| 239 | EXPECT_TRUE(a < b); |
| 240 | } |
| 241 | |
| 242 | TEST(StrongAliasTest, CanBeDerivedFrom) { |
| 243 | // Aliases can be enriched by custom operations or validations if needed. |
| 244 | // Ideally, one could go from a 'using' declaration to a derived class to add |
| 245 | // those methods without the need to change any other code. |
| 246 | class CountryCode : public StrongAlias<CountryCode, std::string> { |
| 247 | public: |
| 248 | explicit CountryCode(const std::string& value) |
| 249 | : StrongAlias<CountryCode, std::string>::StrongAlias(value) { |
| 250 | if (value_.length() != 2) { |
| 251 | // Country code invalid! |
| 252 | value_.clear(); // is_null() will return true. |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | bool is_null() const { return value_.empty(); } |
| 257 | }; |
| 258 | |
| 259 | CountryCode valid("US"); |
| 260 | EXPECT_FALSE(valid.is_null()); |
| 261 | |
| 262 | CountryCode invalid("United States"); |
| 263 | EXPECT_TRUE(invalid.is_null()); |
| 264 | } |
| 265 | |
| 266 | TEST(StrongAliasTest, CanWrapComplexStructures) { |
| 267 | // A pair of strings implements odering and can, in principle, be used as |
| 268 | // a base of StrongAlias. |
| 269 | using PairOfStrings = std::pair<std::string, std::string>; |
| 270 | using ComplexAlias = StrongAlias<class FooTag, PairOfStrings>; |
| 271 | |
| 272 | ComplexAlias a1{std::make_pair("aaa", "bbb")}; |
| 273 | ComplexAlias a2{std::make_pair("ccc", "ddd")}; |
| 274 | EXPECT_TRUE(a1 < a2); |
| 275 | |
| 276 | EXPECT_TRUE(a1.value() == PairOfStrings("aaa", "bbb")); |
| 277 | |
| 278 | // Note a caveat, an std::pair doesn't have an overload of operator<<, and it |
| 279 | // cannot be easily added since ADL rules would require it to be in the std |
| 280 | // namespace. So we can't print ComplexAlias. |
| 281 | } |
| 282 | |
Victor Boivie | 3ec9e03 | 2021-08-18 13:22:42 | [diff] [blame] | 283 | TYPED_TEST(StrongAliasTest, CanBeKeysInFlatMap) { |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 284 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
Victor Boivie | 3ec9e03 | 2021-08-18 13:22:42 | [diff] [blame] | 285 | webrtc::flat_map<FooAlias, std::string> map; |
Victor Boivie | a865519 | 2021-04-01 06:23:54 | [diff] [blame] | 286 | |
| 287 | FooAlias k1(GetExampleValue<TypeParam>(0)); |
| 288 | FooAlias k2(GetExampleValue<TypeParam>(1)); |
| 289 | |
| 290 | map[k1] = "value1"; |
| 291 | map[k2] = "value2"; |
| 292 | |
| 293 | EXPECT_EQ(map[k1], "value1"); |
| 294 | EXPECT_EQ(map[k2], "value2"); |
| 295 | } |
| 296 | |
| 297 | TYPED_TEST(StrongAliasTest, CanBeKeysInStdMap) { |
| 298 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 299 | std::map<FooAlias, std::string> map; |
| 300 | |
| 301 | FooAlias k1(GetExampleValue<TypeParam>(0)); |
| 302 | FooAlias k2(GetExampleValue<TypeParam>(1)); |
| 303 | |
| 304 | map[k1] = "value1"; |
| 305 | map[k2] = "value2"; |
| 306 | |
| 307 | EXPECT_EQ(map[k1], "value1"); |
| 308 | EXPECT_EQ(map[k2], "value2"); |
| 309 | } |
| 310 | |
| 311 | TYPED_TEST(StrongAliasTest, CanDifferentiateOverloads) { |
| 312 | using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| 313 | using BarAlias = StrongAlias<class BarTag, TypeParam>; |
| 314 | class Scope { |
| 315 | public: |
| 316 | static std::string Overload(FooAlias) { return "FooAlias"; } |
| 317 | static std::string Overload(BarAlias) { return "BarAlias"; } |
| 318 | }; |
| 319 | EXPECT_EQ("FooAlias", Scope::Overload(FooAlias())); |
| 320 | EXPECT_EQ("BarAlias", Scope::Overload(BarAlias())); |
| 321 | } |
| 322 | |
| 323 | TEST(StrongAliasTest, EnsureConstexpr) { |
| 324 | using FooAlias = StrongAlias<class FooTag, int>; |
| 325 | |
| 326 | // Check constructors. |
| 327 | static constexpr FooAlias kZero{}; |
| 328 | static constexpr FooAlias kOne(1); |
| 329 | |
| 330 | // Check operator*. |
| 331 | static_assert(*kZero == 0, ""); |
| 332 | static_assert(*kOne == 1, ""); |
| 333 | |
| 334 | // Check value(). |
| 335 | static_assert(kZero.value() == 0, ""); |
| 336 | static_assert(kOne.value() == 1, ""); |
| 337 | |
| 338 | // Check explicit conversions to underlying type. |
| 339 | static_assert(static_cast<int>(kZero) == 0, ""); |
| 340 | static_assert(static_cast<int>(kOne) == 1, ""); |
| 341 | |
| 342 | // Check comparison operations. |
| 343 | static_assert(kZero == kZero, ""); |
| 344 | static_assert(kZero != kOne, ""); |
| 345 | static_assert(kZero < kOne, ""); |
| 346 | static_assert(kZero <= kOne, ""); |
| 347 | static_assert(kOne > kZero, ""); |
| 348 | static_assert(kOne >= kZero, ""); |
| 349 | } |
Victor Boivie | 59d6e2a | 2021-04-20 12:25:06 | [diff] [blame] | 350 | |
| 351 | TEST(StrongAliasTest, BooleansAreEvaluatedAsBooleans) { |
| 352 | using BoolAlias = StrongAlias<class BoolTag, bool>; |
| 353 | |
| 354 | BoolAlias happy(true); |
| 355 | BoolAlias sad(false); |
| 356 | |
| 357 | EXPECT_TRUE(happy); |
| 358 | EXPECT_FALSE(sad); |
| 359 | EXPECT_TRUE(*happy); |
| 360 | EXPECT_FALSE(*sad); |
| 361 | } |
Victor Boivie | b918230 | 2021-09-21 12:51:05 | [diff] [blame] | 362 | } // namespace webrtc |