// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. // // 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 // // http://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. // // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_hoofs/cxx/optional.hpp" #include "iceoryx_hoofs/cxx/vector.hpp" #include "iceoryx_hoofs/internal/concurrent/smart_lock.hpp" #include "iceoryx_hoofs/posix_wrapper/semaphore.hpp" #include "iceoryx_hoofs/testing/watch_dog.hpp" #include "test.hpp" #include #include using namespace ::testing; using namespace iox::concurrent; using namespace iox::cxx; namespace { class SmartLockTester { public: // just for clarity, we could also write ++m_a but we want // to highlight the possible race condition here when we call this from // multiple threads, therefore m_a = m_a + 1 SmartLockTester() { } SmartLockTester(const int32_t a) : m_a(a) { } SmartLockTester(const SmartLockTester& rhs) : m_a(rhs.m_a) { rhs.m_b = rhs.m_b + 1; } SmartLockTester(SmartLockTester&& rhs) : m_a(rhs.m_a) { rhs.isMoved = true; rhs.m_a = 0; rhs.m_b = rhs.m_b + 1; } SmartLockTester& operator=(const SmartLockTester& rhs) { if (this != &rhs) { rhs.m_b = rhs.m_b + 1; m_a = rhs.m_a; } return *this; } SmartLockTester& operator=(SmartLockTester&& rhs) { if (this != &rhs) { rhs.m_b = rhs.m_b + 1; m_a = rhs.m_a; rhs.m_a = 0; rhs.isMoved = true; } return *this; } ~SmartLockTester() { } int32_t getA() const { return m_a; } int32_t getB() const { return m_b; } void incrementA() { m_a = m_a + 1; } void constIncrementA() const { m_a = m_a + 1; } bool isMoved = false; private: mutable int32_t m_a = 0; mutable int32_t m_b = 0; }; class smart_lock_test : public Test { public: void SetUp() override { m_watchdog.watchAndActOnFailure([] { std::terminate(); }); } void TearDown() override { } void waitUntilThreadsHaveStarted(const uint64_t numberOfThreads) { ++m_numberOfThreadWaiter; while (m_numberOfThreadWaiter.load() != numberOfThreads) { } } Watchdog m_watchdog{iox::units::Duration::fromSeconds(60U)}; using SutType_t = smart_lock; optional m_sut; std::atomic m_numberOfThreadWaiter{0U}; }; constexpr uint64_t NUMBER_OF_RUNS_PER_THREAD = 100000U; constexpr uint64_t NUMBER_OF_THREADS = 4; } // namespace ////////////////////////////////////// // BEGIN single threaded api test ////////////////////////////////////// TEST_F(smart_lock_test, DefaultConstructionOfUnderlyingObjectWorks) { ::testing::Test::RecordProperty("TEST_ID", "7b70e351-0e34-4eed-ba02-fcea2b95aaa1"); m_sut.emplace(); EXPECT_THAT((*m_sut)->getA(), Eq(0)); } TEST_F(smart_lock_test, ConstructionWithOneValueCTorOfUnderlyingObjectWorks) { ::testing::Test::RecordProperty("TEST_ID", "1c464484-7e64-421a-84f0-1b399586deea"); constexpr int32_t CTOR_VALUE = 25; m_sut.emplace(ForwardArgsToCTor, CTOR_VALUE); EXPECT_THAT((*m_sut)->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, CopyConstructionOfUnderlyinObjectWorks) { ::testing::Test::RecordProperty("TEST_ID", "3c03837e-c5f9-4937-a5d3-77a507fe91a5"); constexpr int32_t CTOR_VALUE = 121; SmartLockTester tester(CTOR_VALUE); m_sut.emplace(ForwardArgsToCTor, tester); EXPECT_THAT((*m_sut)->getA(), Eq(CTOR_VALUE)); EXPECT_THAT(tester.getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, MoveConstructionOfUnderlyinObjectWorks) { ::testing::Test::RecordProperty("TEST_ID", "8ded7285-3608-4e55-b9e9-a97fb8b1345e"); constexpr int32_t CTOR_VALUE = 1211; SmartLockTester tester(CTOR_VALUE); m_sut.emplace(ForwardArgsToCTor, std::move(tester)); EXPECT_THAT((*m_sut)->getA(), Eq(CTOR_VALUE)); EXPECT_TRUE(tester.isMoved); } TEST_F(smart_lock_test, CopyConstructorWorks) { ::testing::Test::RecordProperty("TEST_ID", "04692305-6a72-45c4-bc4e-fc0cd8ed8dd7"); constexpr int32_t CTOR_VALUE = 1221; m_sut.emplace(ForwardArgsToCTor, CTOR_VALUE); SutType_t sut2(*m_sut); EXPECT_THAT((*m_sut)->getA(), Eq(CTOR_VALUE)); EXPECT_THAT(sut2->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, CopyAssignmentWorks) { ::testing::Test::RecordProperty("TEST_ID", "f8d68a06-00c5-4b68-a6b1-109b9d011ae8"); constexpr int32_t CTOR_VALUE = 2121; m_sut.emplace(ForwardArgsToCTor, CTOR_VALUE); SutType_t sut2; sut2 = m_sut.value(); EXPECT_THAT((*m_sut)->getA(), Eq(CTOR_VALUE)); EXPECT_THAT(sut2->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, MoveConstructorWorks) { ::testing::Test::RecordProperty("TEST_ID", "1a22291e-3e78-4335-9a9f-e704b767085f"); constexpr int32_t CTOR_VALUE = 41221; m_sut.emplace(ForwardArgsToCTor, CTOR_VALUE); SutType_t sut2(std::move(*m_sut)); EXPECT_TRUE((*m_sut)->isMoved); EXPECT_THAT(sut2->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, MoveAssignmentWorks) { ::testing::Test::RecordProperty("TEST_ID", "82342f30-d261-4611-a972-4acaeae6379f"); constexpr int32_t CTOR_VALUE = 21281; m_sut.emplace(ForwardArgsToCTor, CTOR_VALUE); SutType_t sut2; sut2 = std::move(m_sut.value()); EXPECT_TRUE((*m_sut)->isMoved); EXPECT_THAT(sut2->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, ConstArrowOperatorWorks) { ::testing::Test::RecordProperty("TEST_ID", "9d46c335-48e6-424a-aedb-128b124f7cc3"); constexpr int32_t CTOR_VALUE = 212818; const SutType_t constSut(ForwardArgsToCTor, CTOR_VALUE); EXPECT_THAT(constSut->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, AccessThroughConstScopeGuardWorks) { ::testing::Test::RecordProperty("TEST_ID", "f52e9b24-0819-487b-947a-5f18d24b55e6"); constexpr int32_t CTOR_VALUE = 6212818; const SutType_t constSut(ForwardArgsToCTor, CTOR_VALUE); auto guard = constSut.getScopeGuard(); EXPECT_THAT(guard->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, AccessThroughScopeGuardWorks) { ::testing::Test::RecordProperty("TEST_ID", "9d91a04f-d84e-4fa3-9ee0-95ebc4bfbed1"); constexpr int32_t CTOR_VALUE = 62818; SutType_t constSut(ForwardArgsToCTor, CTOR_VALUE); auto guard = constSut.getScopeGuard(); EXPECT_THAT(guard->getA(), Eq(CTOR_VALUE)); } TEST_F(smart_lock_test, AcquiringCopyWorks) { ::testing::Test::RecordProperty("TEST_ID", "47863174-e936-4b18-9617-c58bb956ccac"); constexpr int32_t CTOR_VALUE = 628189; m_sut.emplace(ForwardArgsToCTor, CTOR_VALUE); EXPECT_THAT(m_sut->getCopy().getA(), Eq(CTOR_VALUE)); } ////////////////////////////////////// // END single threaded api test ////////////////////////////////////// ////////////////////////////////////// // BEGIN thread safety tests ////////////////////////////////////// // the idea of all tests when the testAction incrementA or constIncrementA // would not be thread safe we would encounter either undefined behavior since // the variable is sometimes written while it is read or we face a race condition // since the increment is always number = number + 1. If this is not thread safe // we encounter a jumping in the number and not a steady increment. // // If no such thing occurs, since the operation is performed in a threadsafe // manner, (*m_sut)->getA() == totalSumOfIncrements void threadSafeOperationTest(smart_lock_test* test, const std::function testAction) { test->m_sut.emplace(ForwardArgsToCTor, 0); vector threads; for (uint64_t i = 0U; i < NUMBER_OF_THREADS; ++i) { threads.emplace_back([&] { test->waitUntilThreadsHaveStarted(NUMBER_OF_THREADS); for (uint64_t k = 0U; k < NUMBER_OF_RUNS_PER_THREAD; ++k) { testAction(); } }); } for (uint64_t i = 0U; i < NUMBER_OF_THREADS; ++i) { threads[i].join(); } } TEST_F(smart_lock_test, ThreadSafeAccessThroughArrowOperator) { ::testing::Test::RecordProperty("TEST_ID", "e6cfd20a-4450-46f7-80ca-b87f75a0462e"); threadSafeOperationTest(this, [=] { (*m_sut)->incrementA(); }); EXPECT_THAT((*m_sut)->getA(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } TEST_F(smart_lock_test, ThreadSafeAccessThroughConstArrowOperator) { ::testing::Test::RecordProperty("TEST_ID", "c0dedded-cf07-47dc-9032-e5de3db859d2"); threadSafeOperationTest(this, [=] { (const_cast(*m_sut))->constIncrementA(); }); EXPECT_THAT((*m_sut)->getA(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } TEST_F(smart_lock_test, ThreadSafeAccessThroughScopedGuard) { ::testing::Test::RecordProperty("TEST_ID", "2ffb9ba4-4df3-4851-8976-20f15a3bfa4b"); threadSafeOperationTest(this, [=] { auto guard = (*m_sut).getScopeGuard(); guard->incrementA(); }); EXPECT_THAT((*m_sut)->getA(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } TEST_F(smart_lock_test, ThreadSafeAccessThroughConstScopedGuard) { ::testing::Test::RecordProperty("TEST_ID", "dc2efd41-4c0a-4ac0-93ed-f8e9195e0dfd"); threadSafeOperationTest(this, [=] { auto guard = const_cast(*m_sut).getScopeGuard(); guard->incrementA(); }); EXPECT_THAT((*m_sut)->getA(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } TEST_F(smart_lock_test, ThreadSafeCopyCTor) { ::testing::Test::RecordProperty("TEST_ID", "23b27eda-17de-42b9-bdbc-81e7bae15fd6"); threadSafeOperationTest(this, [=] { SutType_t someCopy(*m_sut); }); EXPECT_THAT((*m_sut)->getB(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } TEST_F(smart_lock_test, ThreadSafeMoveCTor) { ::testing::Test::RecordProperty("TEST_ID", "e7368392-ff41-44a7-ad58-9d3b17637dd6"); threadSafeOperationTest(this, [=] { SutType_t movedSut(std::move(*m_sut)); }); EXPECT_THAT((*m_sut)->getB(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } TEST_F(smart_lock_test, ThreadSafeCopyAssignment) { ::testing::Test::RecordProperty("TEST_ID", "a9964501-9c88-4250-a1d5-e266171a670c"); threadSafeOperationTest(this, [=] { SutType_t someCopy; someCopy = *m_sut; }); EXPECT_THAT((*m_sut)->getB(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } TEST_F(smart_lock_test, ThreadSafeMoveAssignment) { ::testing::Test::RecordProperty("TEST_ID", "3f89e951-bf93-4230-b749-ec91416785ae"); threadSafeOperationTest(this, [=] { SutType_t someMovedSut; someMovedSut = std::move(*m_sut); }); EXPECT_THAT((*m_sut)->getB(), Eq(NUMBER_OF_RUNS_PER_THREAD * NUMBER_OF_THREADS)); } ////////////////////////////////////// // END thread safety tests //////////////////////////////////////