xref: /aosp_15_r20/external/pigweed/pw_sync/pw_sync_private/borrow_lockable_tests.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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