// Copyright 2021 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. #include "pw_sync/borrow.h" #include #include #include "gtest/gtest.h" #include "pw_assert/check.h" #include "pw_sync/virtual_basic_lockable.h" namespace pw::sync { namespace { template class BorrowableTest : public ::testing::Test { protected: static constexpr int kInitialValue = 42; BorrowableTest() : foo_{.value = kInitialValue}, borrowable_foo_(foo_, lock_) {} void SetUp() override { EXPECT_FALSE(lock_.locked()); // Ensure it's not locked on construction. } struct Foo { int value; }; Lock lock_; Foo foo_; Borrowable borrowable_foo_; }; class BasicLockable : public VirtualBasicLockable { public: virtual ~BasicLockable() = default; bool locked() const { return locked_; } protected: bool locked_ = false; private: void DoLockOperation(Operation operation) override { switch (operation) { case Operation::kLock: PW_CHECK(!locked_, "Recursive lock detected"); locked_ = true; return; case Operation::kUnlock: default: PW_CHECK(locked_, "Unlock while unlocked detected"); locked_ = false; return; } } }; using BorrowableBasicLockableTest = BorrowableTest; TEST_F(BorrowableBasicLockableTest, Acquire) { { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. EXPECT_EQ(foo_.value, 13); } TEST_F(BorrowableBasicLockableTest, RepeatedAcquire) { { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, 13); } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableBasicLockableTest, Moveable) { Borrowable borrowable_foo = std::move(borrowable_foo_); { BorrowedPointer borrowed_foo = borrowable_foo.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableBasicLockableTest, Copyable) { const Borrowable& other = borrowable_foo_; Borrowable borrowable_foo(other); { BorrowedPointer borrowed_foo = borrowable_foo.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } class Lockable : public BasicLockable { public: bool try_lock() { if (locked()) { return false; } locked_ = true; return true; } }; using BorrowableLockableTest = BorrowableTest; TEST_F(BorrowableLockableTest, Acquire) { { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. EXPECT_EQ(foo_.value, 13); } TEST_F(BorrowableLockableTest, RepeatedAcquire) { { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, 13); } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableLockableTest, TryAcquireSuccess) { { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire(); ASSERT_TRUE(maybe_borrowed_foo.has_value()); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue); } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableLockableTest, TryAcquireFailure) { lock_.lock(); EXPECT_TRUE(lock_.locked()); { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire(); EXPECT_FALSE(maybe_borrowed_foo.has_value()); } EXPECT_TRUE(lock_.locked()); lock_.unlock(); } struct Clock { using rep = int64_t; using period = std::micro; using duration = std::chrono::duration; using time_point = std::chrono::time_point; }; class TimedLockable : public Lockable { public: bool try_lock() { if (locked()) { return false; } locked_ = true; return true; } bool try_lock_for(const Clock::duration&) { return try_lock(); } bool try_lock_until(const Clock::time_point&) { return try_lock(); } }; using BorrowableTimedLockableTest = BorrowableTest; TEST_F(BorrowableTimedLockableTest, Acquire) { { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. EXPECT_EQ(foo_.value, 13); } TEST_F(BorrowableTimedLockableTest, RepeatedAcquire) { { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, kInitialValue); borrowed_foo->value = 13; } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. { BorrowedPointer borrowed_foo = borrowable_foo_.acquire(); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(borrowed_foo->value, 13); } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableTimedLockableTest, TryAcquireSuccess) { { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire(); ASSERT_TRUE(maybe_borrowed_foo.has_value()); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue); } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableTimedLockableTest, TryAcquireFailure) { lock_.lock(); EXPECT_TRUE(lock_.locked()); { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire(); EXPECT_FALSE(maybe_borrowed_foo.has_value()); } EXPECT_TRUE(lock_.locked()); lock_.unlock(); } TEST_F(BorrowableTimedLockableTest, TryAcquireForSuccess) { { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire_for(std::chrono::seconds(0)); ASSERT_TRUE(maybe_borrowed_foo.has_value()); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue); } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableTimedLockableTest, TryAcquireForFailure) { lock_.lock(); EXPECT_TRUE(lock_.locked()); { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire_for(std::chrono::seconds(0)); EXPECT_FALSE(maybe_borrowed_foo.has_value()); } EXPECT_TRUE(lock_.locked()); lock_.unlock(); } TEST_F(BorrowableTimedLockableTest, TryAcquireUntilSuccess) { { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire_until( Clock::time_point(std::chrono::seconds(0))); ASSERT_TRUE(maybe_borrowed_foo.has_value()); EXPECT_TRUE(lock_.locked()); // Ensure the lock is held. EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue); } EXPECT_FALSE(lock_.locked()); // Ensure the lock is released. } TEST_F(BorrowableTimedLockableTest, TryAcquireUntilFailure) { lock_.lock(); EXPECT_TRUE(lock_.locked()); { std::optional> maybe_borrowed_foo = borrowable_foo_.try_acquire_until( Clock::time_point(std::chrono::seconds(0))); EXPECT_FALSE(maybe_borrowed_foo.has_value()); } EXPECT_TRUE(lock_.locked()); lock_.unlock(); } } // namespace } // namespace pw::sync