blob: 0277253a60c113730048e5b1b65829429f0edecc [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
/// This file contains tests that can be used to verify a lock type can be
/// used in `pw::sync::Borrowable` to borrow types that use external locking.
///
/// Locks must at least meet C++'s \em BasicLockable named requirement. Tests
/// should be added using the `ADD_BORROWABLE_...` macros from this file.
///
/// * If a lock is not \em TimedLockable, use `ADD_BORROWABLE_LOCK_TESTS`, e.g.
/// `ADD_BORROWABLE_LOCK_TESTS(MyLock);`.
///
/// * If a lock is \em TimedLockable, use `ADD_BORROWABLE_TIMED_LOCK_TESTS` and
/// provide the appropriate clock, e.g.
/// `ADD_BORROWABLE_TIMED_LOCK_TESTS(MyLock, pw::chrono::SystemClock);`
///
/// * If the default test suite name is not suitable, use the `..._NAMED_TESTS`
/// variants, e.g.
/// `ADD_BORROWABLE_LOCK_NAMED_TESTS(MyTestSuite, pw::my_module::Lock);`.
#include "pw_sync/borrow.h"
#include "pw_sync/lock_traits.h"
#include "pw_unit_test/framework.h"
namespace pw::sync {
// Test fixtures.
/// Simple struct that wraps a value.
struct Base {
static constexpr int kInitialValue = 24;
int base_value = kInitialValue;
};
/// Simple struct that derives from `Base` and wraps a value.
struct Derived : public Base {
static constexpr int kInitialValue = 42;
int value = kInitialValue;
};
/// Checks if a type has a method `locked()`.
///
/// Several fake locks are used in testing that simply update a bool instead of
/// actually locking. The lock state for these types can be accessed using
/// `locked()`.
///
/// @{
template <typename Lock, typename = void>
struct has_locked : std::false_type {};
template <typename Lock>
struct has_locked<Lock, std::void_t<decltype(std::declval<Lock>().locked())>>
: std::true_type{};
/// @}
/// Checks if a lock's state matches the expected state.
///
/// This method can check fake locks used for testing as well as locks that
/// meet C++'s \em Lockable named requirement. This method is a no-op for lock
/// types that only meet \em BasicLockable.
///
/// @param[in] lock The lock to check.
/// @param[in] expected Indicates if the lock is expected to be locked.
template <typename Lock>
void CheckLocked(Lock& lock, bool expected) {
if constexpr (has_locked<Lock>::value) {
EXPECT_EQ(lock.locked(), expected);
} else if constexpr (is_lockable_v<Lock>) {
bool locked = !lock.try_lock();
EXPECT_EQ(locked, expected);
if (!locked) {
lock.unlock();
}
}
}
template <typename Lock>
void TestAcquire() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
{
BorrowedPointer<Derived, Lock> borrowed = borrowable.acquire();
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(borrowed->value, Derived::kInitialValue);
borrowed->value = 13;
}
CheckLocked(lock, false); // Ensure the lock is released.
EXPECT_EQ(derived.value, 13);
}
template <typename Lock>
void TestConstAcquire() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
const Borrowable<Derived, Lock> const_borrowable(borrowable);
{
BorrowedPointer<Derived, Lock> borrowed = const_borrowable.acquire();
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(borrowed->value, Derived::kInitialValue);
borrowed->value = 13;
}
CheckLocked(lock, false); // Ensure the lock is released.
EXPECT_EQ(derived.value, 13);
}
template <typename Lock>
void TestRepeatedAcquire() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
{
BorrowedPointer<Derived, Lock> borrowed = borrowable.acquire();
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(borrowed->value, Derived::kInitialValue);
borrowed->value = 13;
}
CheckLocked(lock, false); // Ensure the lock is released.
{
BorrowedPointer<Derived, Lock> borrowed = borrowable.acquire();
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(borrowed->value, 13);
}
CheckLocked(lock, false); // Ensure the lock is released.
}
template <typename Lock>
void TestMoveable() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
Borrowable<Derived, Lock> moved = std::move(borrowable);
{
BorrowedPointer<Derived, Lock> borrowed = moved.acquire();
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(borrowed->value, Derived::kInitialValue);
borrowed->value = 13;
}
CheckLocked(lock, false); // Ensure the lock is released.
}
template <typename Lock>
void TestCopyable() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
const Borrowable<Derived, Lock>& intermediate = borrowable;
Borrowable<Derived, Lock> copied(intermediate);
{
BorrowedPointer<Derived, Lock> borrowed = copied.acquire();
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(borrowed->value, Derived::kInitialValue);
borrowed->value = 13;
}
CheckLocked(lock, false); // Ensure the lock is released.
EXPECT_EQ(derived.value, 13);
}
template <typename Lock>
void TestCopyableCovariant() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
const Borrowable<Derived, Lock>& intermediate = borrowable;
Borrowable<Base, Lock> copied_base(intermediate);
{
BorrowedPointer<Base, Lock> borrowed = copied_base.acquire();
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(borrowed->base_value, Base::kInitialValue);
borrowed->base_value = 13;
}
CheckLocked(lock, false); // Ensure the lock is released.
EXPECT_EQ(derived.base_value, 13);
}
template <typename Lock>
void TestTryAcquireSuccess() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
if constexpr (is_lockable_v<Lock>) {
std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed =
borrowable.try_acquire();
ASSERT_TRUE(maybe_borrowed.has_value());
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(maybe_borrowed.value()->value, Derived::kInitialValue);
}
CheckLocked(lock, false); // Ensure the lock is released.
}
template <typename Lock>
void TestTryAcquireFailure() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
lock.lock();
CheckLocked(lock, true); // Ensure the lock is held.
if constexpr (is_lockable_v<Lock>) {
std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed =
borrowable.try_acquire();
EXPECT_FALSE(maybe_borrowed.has_value());
}
CheckLocked(lock, true); // Ensure the lock is held.
lock.unlock();
}
template <typename Lock>
void TestTryAcquireForSuccess() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
if constexpr (is_lockable_for_v<Lock, decltype(std::chrono::seconds(0))>) {
std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed =
borrowable.try_acquire_for(std::chrono::seconds(0));
ASSERT_TRUE(maybe_borrowed.has_value());
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(maybe_borrowed.value()->value, Derived::kInitialValue);
}
CheckLocked(lock, false); // Ensure the lock is released.
}
template <typename Lock>
void TestTryAcquireForFailure() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
lock.lock();
CheckLocked(lock, true); // Ensure the lock is held.
if constexpr (is_lockable_for_v<Lock, decltype(std::chrono::seconds(0))>) {
std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed =
borrowable.try_acquire_for(std::chrono::seconds(0));
EXPECT_FALSE(maybe_borrowed.has_value());
}
CheckLocked(lock, true); // Ensure the lock is held.
lock.unlock();
}
/// Fake clock for use with non-timed locks.
///
/// This clock is guaranteed to fail `is_lockable_until<Lock, Clock>` and as
/// such is suitable to make the `TestTryAcquireUntilSuccess` and
/// `TestTryAcquireUntilFailure` tests pass trivially for lock types that do not
/// meet C++'s \em TimedLockable named requirement for any clock.
struct NoClock {
using duration = void;
using time_point = void;
};
template <typename Lock, typename Clock = NoClock>
void TestTryAcquireUntilSuccess() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
if constexpr (is_lockable_until_v<Lock, Clock>) {
std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed =
borrowable.try_acquire_until(Clock::time_point());
ASSERT_TRUE(maybe_borrowed.has_value());
CheckLocked(lock, true); // Ensure the lock is held.
EXPECT_EQ(maybe_borrowed.value()->value, Derived::kInitialValue);
}
CheckLocked(lock, false); // Ensure the lock is released.
}
template <typename Lock, typename Clock = NoClock>
void TestTryAcquireUntilFailure() {
Lock lock;
Derived derived;
Borrowable<Derived, Lock> borrowable(derived, lock);
lock.lock();
CheckLocked(lock, true); // Ensure the lock is held.
if constexpr (is_lockable_until_v<Lock, Clock>) {
std::optional<BorrowedPointer<Derived, Lock>> maybe_borrowed =
borrowable.try_acquire_until(Clock::time_point());
EXPECT_FALSE(maybe_borrowed.has_value());
}
CheckLocked(lock, true); // Ensure the lock is held.
lock.unlock();
}
/// Register borrowable non-timed lock tests.
#define PW_SYNC_ADD_BORROWABLE_LOCK_TESTS(lock) \
PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_TESTS(lock, NoClock)
/// Register borrowable non-timed lock tests in a named test suite.
#define PW_SYNC_ADD_BORROWABLE_LOCK_NAMED_TESTS(name, lock) \
PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_NAMED_TESTS(name, lock, NoClock)
/// Register all borrowable lock tests.
#define PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_TESTS(lock, clock) \
PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_NAMED_TESTS( \
Borrowable##lock##Test, lock, clock)
/// Register all borrowable lock tests in a named test suite.
#define PW_SYNC_ADD_BORROWABLE_TIMED_LOCK_NAMED_TESTS(name, lock, clock) \
TEST(name, Acquire) { TestAcquire<lock>(); } \
TEST(name, ConstAcquire) { TestConstAcquire<lock>(); } \
TEST(name, RepeatedAcquire) { TestRepeatedAcquire<lock>(); } \
TEST(name, Moveable) { TestMoveable<lock>(); } \
TEST(name, Copyable) { TestCopyable<lock>(); } \
TEST(name, CopyableCovariant) { TestCopyableCovariant<lock>(); } \
TEST(name, TryAcquireForSuccess) { TestTryAcquireForSuccess<lock>(); } \
TEST(name, TryAcquireForFailure) { TestTryAcquireForFailure<lock>(); } \
TEST(name, TryAcquireUntilSuccess) { \
TestTryAcquireUntilSuccess<lock, clock>(); \
} \
TEST(name, TryAcquireUntilFailure) { \
TestTryAcquireUntilFailure<lock, clock>(); \
} \
static_assert(true, "trailing semicolon")
} // namespace pw::sync