1 // Copyright 2023 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 #pragma once 15 16 /// This file contains tests that can be used to verify a lock type can be 17 /// used in `pw::sync::Borrowable` to borrow types that use external locking. 18 /// 19 /// Locks must at least meet C++'s \em BasicLockable named requirement. Tests 20 /// should be added using the `ADD_BORROWABLE_...` macros from this file. 21 /// 22 /// * If a lock is not \em TimedLockable, use `ADD_BORROWABLE_LOCK_TESTS`, e.g. 23 /// `ADD_BORROWABLE_LOCK_TESTS(MyLock);`. 24 /// 25 /// * If a lock is \em TimedLockable, use `ADD_BORROWABLE_TIMED_LOCK_TESTS` and 26 /// provide the appropriate clock, e.g. 27 /// `ADD_BORROWABLE_TIMED_LOCK_TESTS(MyLock, pw::chrono::SystemClock);` 28 /// 29 /// * If the default test suite name is not suitable, use the `..._NAMED_TESTS` 30 /// variants, e.g. 31 /// `ADD_BORROWABLE_LOCK_NAMED_TESTS(MyTestSuite, pw::my_module::Lock);`. 32 33 #include "pw_sync/borrow.h" 34 #include "pw_sync/lock_traits.h" 35 #include "pw_unit_test/framework.h" 36 37 namespace pw::sync { 38 39 // Test fixtures. 40 41 /// Simple struct that wraps a value. 42 struct Base { 43 static constexpr int kInitialValue = 24; 44 int base_value = kInitialValue; 45 }; 46 47 /// Simple struct that derives from `Base` and wraps a value. 48 struct Derived : public Base { 49 static constexpr int kInitialValue = 42; 50 int value = kInitialValue; 51 }; 52 53 /// Checks if a type has a method `locked()`. 54 /// 55 /// Several fake locks are used in testing that simply update a bool instead of 56 /// actually locking. The lock state for these types can be accessed using 57 /// `locked()`. 58 /// 59 /// @{ 60 template <typename Lock, typename = void> 61 struct has_locked : std::false_type {}; 62 63 template <typename Lock> 64 struct has_locked<Lock, std::void_t<decltype(std::declval<Lock>().locked())>> 65 : std::true_type {}; 66 /// @} 67 68 /// Checks if a lock's state matches the expected state. 69 /// 70 /// This method can check fake locks used for testing as well as locks that 71 /// meet C++'s \em Lockable named requirement. This method is a no-op for lock 72 /// types that only meet \em BasicLockable. 73 /// 74 /// @param[in] lock The lock to check. 75 /// @param[in] expected Indicates if the lock is expected to be locked. 76 template <typename Lock> 77 void CheckLocked(Lock& lock, bool expected) { 78 if constexpr (has_locked<Lock>::value) { 79 EXPECT_EQ(lock.locked(), expected); 80 } else if constexpr (is_lockable_v<Lock>) { 81 bool locked = !lock.try_lock(); 82 EXPECT_EQ(locked, expected); 83 if (!locked) { 84 lock.unlock(); 85 } 86 } 87 } 88 89 template <typename Lock> 90 void TestAcquire() { 91 Lock lock; 92 Derived derived; 93 Borrowable<Derived, Lock> borrowable(derived, lock); 94 { 95 BorrowedPointer<Derived, Lock> borrowed = borrowable.acquire(); 96 CheckLocked(lock, true); // Ensure the lock is held. 97 EXPECT_EQ(borrowed->value, Derived::kInitialValue); 98 borrowed->value = 13; 99 } 100 CheckLocked(lock, false); // Ensure the lock is released. 101 EXPECT_EQ(derived.value, 13); 102 } 103 104 template <typename Lock> 105 void TestConstAcquire() { 106 Lock lock; 107 Derived derived; 108 Borrowable<Derived, Lock> borrowable(derived, lock); 109 const Borrowable<Derived, Lock> const_borrowable(borrowable); 110 { 111 BorrowedPointer<Derived, Lock> borrowed = const_borrowable.acquire(); 112 CheckLocked(lock, true); // Ensure the lock is held. 113 EXPECT_EQ(borrowed->value, Derived::kInitialValue); 114 borrowed->value = 13; 115 } 116 CheckLocked(lock, false); // Ensure the lock is released. 117 EXPECT_EQ(derived.value, 13); 118 } 119 120 template <typename Lock> 121 void TestRepeatedAcquire() { 122 Lock lock; 123 Derived derived; 124 Borrowable<Derived, Lock> borrowable(derived, lock); 125 { 126 BorrowedPointer<Derived, Lock> borrowed = borrowable.acquire(); 127 CheckLocked(lock, true); // Ensure the lock is held. 128 EXPECT_EQ(borrowed->value, Derived::kInitialValue); 129 borrowed->value = 13; 130 } 131 CheckLocked(lock, false); // Ensure the lock is released. 132 { 133 BorrowedPointer<Derived, Lock> borrowed = borrowable.acquire(); 134 CheckLocked(lock, true); // Ensure the lock is held. 135 EXPECT_EQ(borrowed->value, 13); 136 } 137 CheckLocked(lock, false); // Ensure the lock is released. 138 } 139 140 template <typename Lock> 141 void TestMoveable() { 142 Lock lock; 143 Derived derived; 144 Borrowable<Derived, Lock> borrowable(derived, lock); 145 Borrowable<Derived, Lock> moved = std::move(borrowable); 146 { 147 BorrowedPointer<Derived, Lock> borrowed = moved.acquire(); 148 CheckLocked(lock, true); // Ensure the lock is held. 149 EXPECT_EQ(borrowed->value, Derived::kInitialValue); 150 borrowed->value = 13; 151 } 152 CheckLocked(lock, false); // Ensure the lock is released. 153 } 154 155 template <typename Lock> 156 void TestCopyable() { 157 Lock lock; 158 Derived derived; 159 Borrowable<Derived, Lock> borrowable(derived, lock); 160 const Borrowable<Derived, Lock>& intermediate = borrowable; 161 Borrowable<Derived, Lock> copied(intermediate); 162 { 163 BorrowedPointer<Derived, Lock> borrowed = copied.acquire(); 164 CheckLocked(lock, true); // Ensure the lock is held. 165 EXPECT_EQ(borrowed->value, Derived::kInitialValue); 166 borrowed->value = 13; 167 } 168 CheckLocked(lock, false); // Ensure the lock is released. 169 EXPECT_EQ(derived.value, 13); 170 } 171 172 template <typename Lock> 173 void TestCopyableCovariant() { 174 Lock lock; 175 Derived derived; 176 Borrowable<Derived, Lock> borrowable(derived, lock); 177 const Borrowable<Derived, Lock>& intermediate = borrowable; 178 Borrowable<Base, Lock> copied_base(intermediate); 179 { 180 BorrowedPointer<Base, Lock> borrowed = copied_base.acquire(); 181 CheckLocked(lock, true); // Ensure the lock is held. 182 EXPECT_EQ(borrowed->base_value, Base::kInitialValue); 183 borrowed->base_value = 13; 184 } 185 CheckLocked(lock, false); // Ensure the lock is released. 186 EXPECT_EQ(derived.base_value, 13); 187 } 188 189 template <typename Lock> 190 void TestTryAcquireSuccess() { 191 Lock lock; 192 Derived derived; 193 Borrowable<Derived, Lock> borrowable(derived, lock); 194 if constexpr (is_lockable_v<Lock>) { 195 std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed = 196 borrowable.try_acquire(); 197 ASSERT_TRUE(maybe_borrowed.has_value()); 198 CheckLocked(lock, true); // Ensure the lock is held. 199 EXPECT_EQ(maybe_borrowed.value()->value, Derived::kInitialValue); 200 } 201 CheckLocked(lock, false); // Ensure the lock is released. 202 } 203 204 template <typename Lock> 205 void TestTryAcquireFailure() { 206 Lock lock; 207 Derived derived; 208 Borrowable<Derived, Lock> borrowable(derived, lock); 209 lock.lock(); 210 CheckLocked(lock, true); // Ensure the lock is held. 211 if constexpr (is_lockable_v<Lock>) { 212 std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed = 213 borrowable.try_acquire(); 214 EXPECT_FALSE(maybe_borrowed.has_value()); 215 } 216 CheckLocked(lock, true); // Ensure the lock is held. 217 lock.unlock(); 218 } 219 220 template <typename Lock> 221 void TestTryAcquireForSuccess() { 222 Lock lock; 223 Derived derived; 224 Borrowable<Derived, Lock> borrowable(derived, lock); 225 if constexpr (is_lockable_for_v<Lock, decltype(std::chrono::seconds(0))>) { 226 std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed = 227 borrowable.try_acquire_for(std::chrono::seconds(0)); 228 ASSERT_TRUE(maybe_borrowed.has_value()); 229 CheckLocked(lock, true); // Ensure the lock is held. 230 EXPECT_EQ(maybe_borrowed.value()->value, Derived::kInitialValue); 231 } 232 CheckLocked(lock, false); // Ensure the lock is released. 233 } 234 235 template <typename Lock> 236 void TestTryAcquireForFailure() { 237 Lock lock; 238 Derived derived; 239 Borrowable<Derived, Lock> borrowable(derived, lock); 240 lock.lock(); 241 CheckLocked(lock, true); // Ensure the lock is held. 242 if constexpr (is_lockable_for_v<Lock, decltype(std::chrono::seconds(0))>) { 243 std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed = 244 borrowable.try_acquire_for(std::chrono::seconds(0)); 245 EXPECT_FALSE(maybe_borrowed.has_value()); 246 } 247 CheckLocked(lock, true); // Ensure the lock is held. 248 lock.unlock(); 249 } 250 251 /// Fake clock for use with non-timed locks. 252 /// 253 /// This clock is guaranteed to fail `is_lockable_until<Lock, Clock>` and as 254 /// such is suitable to make the `TestTryAcquireUntilSuccess` and 255 /// `TestTryAcquireUntilFailure` tests pass trivially for lock types that do not 256 /// meet C++'s \em TimedLockable named requirement for any clock. 257 struct NoClock { 258 using duration = void; 259 using time_point = void; 260 }; 261 262 template <typename Lock, typename Clock = NoClock> 263 void TestTryAcquireUntilSuccess() { 264 Lock lock; 265 Derived derived; 266 Borrowable<Derived, Lock> borrowable(derived, lock); 267 if constexpr (is_lockable_until_v<Lock, Clock>) { 268 std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed = 269 borrowable.try_acquire_until(Clock::time_point()); 270 ASSERT_TRUE(maybe_borrowed.has_value()); 271 CheckLocked(lock, true); // Ensure the lock is held. 272 EXPECT_EQ(maybe_borrowed.value()->value, Derived::kInitialValue); 273 } 274 CheckLocked(lock, false); // Ensure the lock is released. 275 } 276 277 template <typename Lock, typename Clock = NoClock> 278 void TestTryAcquireUntilFailure() { 279 Lock lock; 280 Derived derived; 281 Borrowable<Derived, Lock> borrowable(derived, lock); 282 lock.lock(); 283 CheckLocked(lock, true); // Ensure the lock is held. 284 if constexpr (is_lockable_until_v<Lock, Clock>) { 285 std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed = 286 borrowable.try_acquire_until(Clock::time_point()); 287 EXPECT_FALSE(maybe_borrowed.has_value()); 288 } 289 CheckLocked(lock, true); // Ensure the lock is held. 290 lock.unlock(); 291 } 292 293 /// Register borrowable non-timed lock tests. 294 #define PW_SYNC_ADD_BORROWABLE_LOCK_TESTS(lock) \ 295 PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_TESTS(lock, NoClock) 296 297 /// Register borrowable non-timed lock tests in a named test suite. 298 #define PW_SYNC_ADD_BORROWABLE_LOCK_NAMED_TESTS(name, lock) \ 299 PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_NAMED_TESTS(name, lock, NoClock) 300 301 /// Register all borrowable lock tests. 302 #define PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_TESTS(lock, clock) \ 303 PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_NAMED_TESTS( \ 304 Borrowable##lock##Test, lock, clock) 305 306 /// Register all borrowable lock tests in a named test suite. 307 #define PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_NAMED_TESTS(name, lock, clock) \ 308 TEST(name, Acquire) { TestAcquire<lock>(); } \ 309 TEST(name, ConstAcquire) { TestConstAcquire<lock>(); } \ 310 TEST(name, RepeatedAcquire) { TestRepeatedAcquire<lock>(); } \ 311 TEST(name, Moveable) { TestMoveable<lock>(); } \ 312 TEST(name, Copyable) { TestCopyable<lock>(); } \ 313 TEST(name, CopyableCovariant) { TestCopyableCovariant<lock>(); } \ 314 TEST(name, TryAcquireForSuccess) { TestTryAcquireForSuccess<lock>(); } \ 315 TEST(name, TryAcquireForFailure) { TestTryAcquireForFailure<lock>(); } \ 316 TEST(name, TryAcquireUntilSuccess) { \ 317 TestTryAcquireUntilSuccess<lock, clock>(); \ 318 } \ 319 TEST(name, TryAcquireUntilFailure) { \ 320 TestTryAcquireUntilFailure<lock, clock>(); \ 321 } \ 322 static_assert(true, "trailing semicolon") 323 324 } // namespace pw::sync 325