From 7da90c1154c68c52b6b6173ab22caf6ebbe4d6a9 Mon Sep 17 00:00:00 2001 From: manuelfehlhammer Date: Wed, 10 Jun 2026 23:00:17 +0200 Subject: [PATCH] score/containers: Extend unit tests Added test infrastructure for allocater/ pointer mocking/spying. Added unit tests for handling of bounds-checking support. --- score/containers/BUILD | 2 +- score/containers/dynamic_array_test.cpp | 4 +- score/containers/non_relocatable_vector.h | 1 - .../non_relocatable_vector_test.cpp | 135 ++++++++++ score/containers/test/BUILD | 18 +- score/containers/test/custom_allocator_mock.h | 86 ------- ...ocator_mock.cpp => mockable_allocator.cpp} | 2 +- score/containers/test/mockable_allocator.h | 88 +++++++ score/containers/test/mockable_pointer.cpp | 13 + score/containers/test/mockable_pointer.h | 238 ++++++++++++++++++ .../test/mockable_pointer_mock_guard.cpp | 13 + .../test/mockable_pointer_mock_guard.h | 50 ++++ score/containers/test/pointer_spy_mock.cpp | 13 + score/containers/test/pointer_spy_mock.h | 49 ++++ 14 files changed, 616 insertions(+), 96 deletions(-) delete mode 100644 score/containers/test/custom_allocator_mock.h rename score/containers/test/{custom_allocator_mock.cpp => mockable_allocator.cpp} (90%) create mode 100644 score/containers/test/mockable_allocator.h create mode 100644 score/containers/test/mockable_pointer.cpp create mode 100644 score/containers/test/mockable_pointer.h create mode 100644 score/containers/test/mockable_pointer_mock_guard.cpp create mode 100644 score/containers/test/mockable_pointer_mock_guard.h create mode 100644 score/containers/test/pointer_spy_mock.cpp create mode 100644 score/containers/test/pointer_spy_mock.h diff --git a/score/containers/BUILD b/score/containers/BUILD index 52a340fd0..36b861e9c 100644 --- a/score/containers/BUILD +++ b/score/containers/BUILD @@ -38,7 +38,6 @@ cc_unit_test( "@googletest//:gtest_main", "@score_baselibs//score/containers/test:allocator_test_type_helpers", "@score_baselibs//score/containers/test:container_test_types", - "@score_baselibs//score/containers/test:custom_allocator_mock", "@score_baselibs//score/language/futurecpp:futurecpp_test_support", "@score_baselibs//score/memory/shared:types", "@score_baselibs//score/memory/shared/fake:fake_memory_resources", @@ -59,6 +58,7 @@ cc_unit_test( "@googletest//:gtest_main", "@score_baselibs//score/containers/test:allocator_test_type_helpers", "@score_baselibs//score/containers/test:container_test_types", + "@score_baselibs//score/containers/test:mockable_allocator", "@score_baselibs//score/language/futurecpp:futurecpp_test_support", "@score_baselibs//score/memory/shared:types", "@score_baselibs//score/memory/shared/fake:fake_memory_resources", diff --git a/score/containers/dynamic_array_test.cpp b/score/containers/dynamic_array_test.cpp index 9c778206b..6064b1147 100644 --- a/score/containers/dynamic_array_test.cpp +++ b/score/containers/dynamic_array_test.cpp @@ -14,7 +14,6 @@ #include "score/containers/test/allocator_test_type_helpers.h" #include "score/containers/test/container_test_types.h" -#include "score/containers/test/custom_allocator_mock.h" #include "score/memory/shared/fake/my_memory_resource.h" #include "score/memory/shared/polymorphic_offset_ptr_allocator.h" @@ -341,7 +340,8 @@ TYPED_TEST(DynamicArrayTestFixture, ConstructingDynamicArrayWithTrivialTypeWithT }; // Since a std::exception is thrown by std::allocator_traits::allocate(), rather than by an AMP - // assertion / precondition, we capture this using the gtest framework instead of SCORE_LANGUAGE_FUTURECPP_ASSERT_CONTRACT_VIOLATED. + // assertion / precondition, we capture this using the gtest framework instead of + // SCORE_LANGUAGE_FUTURECPP_ASSERT_CONTRACT_VIOLATED. EXPECT_THROW(initialise_dynamic_array(), std::exception); } diff --git a/score/containers/non_relocatable_vector.h b/score/containers/non_relocatable_vector.h index d987a83ac..f6575e3b8 100644 --- a/score/containers/non_relocatable_vector.h +++ b/score/containers/non_relocatable_vector.h @@ -18,7 +18,6 @@ #include #include -#include #include #include #include diff --git a/score/containers/non_relocatable_vector_test.cpp b/score/containers/non_relocatable_vector_test.cpp index 54a3abd87..c7140da28 100644 --- a/score/containers/non_relocatable_vector_test.cpp +++ b/score/containers/non_relocatable_vector_test.cpp @@ -14,6 +14,8 @@ #include "score/containers/test/allocator_test_type_helpers.h" #include "score/containers/test/container_test_types.h" +#include "score/containers/test/mockable_allocator.h" +#include "score/containers/test/mockable_pointer_mock_guard.h" #include "score/memory/shared/fake/my_memory_resource.h" #include "score/memory/shared/polymorphic_offset_ptr_allocator.h" @@ -28,6 +30,7 @@ #include #include #include +#include namespace score::containers { @@ -237,4 +240,136 @@ TYPED_TEST(NonRelocatableVectorNonMoveableAndCopyableElementTypeFixture, SwapSwa } } +class NonRelocatableVectorPointerSpyFixture : public ::testing::Test +{ + void SetUp() override {} + void TearDown() override {} + + protected: + using ElementType = std::uint32_t; + using Allocator = typename test::MockableAllocator; + + NonRelocatableVectorPointerSpyFixture& GivenANonRelocatableVectorContainingNumberOfElements( + const std::size_t number_of_elements) + { + unit_ = std::make_unique>(number_of_elements); + for (std::size_t i = 0; i < number_of_elements; ++i) + { + score::cpp::ignore = this->unit_->emplace_back(i); + } + return *this; + } + + score::memory::shared::test::MyMemoryResource memory_resource_{}; + std::unique_ptr> unit_{nullptr}; +}; + +using NonRelocatableVectorPointerInteractionFixture = NonRelocatableVectorPointerSpyFixture; +using testing::_; + +TEST_F(NonRelocatableVectorPointerInteractionFixture, DataDereferencesBeginAndEnd) +{ + // Given a NonRelocatableVector which has been filled with elements + GivenANonRelocatableVectorContainingNumberOfElements(kNonZeroNumberElements); + + // and a registered pointer-spy mock, + testing::StrictMock> pointer_spy; + std::vector arrow_args; + + ElementType* data_ptr = nullptr; + { + test::MockablePointerMockGuard guard{&pointer_spy}; + + // expect, that the pointer to the start of the data is advanced to the last element (size - 1) ... + EXPECT_CALL(pointer_spy, PlusAssign(static_cast(kNonZeroNumberElements - 1))); + // ... and two pointer dereferences take place + EXPECT_CALL(pointer_spy, Arrow(_)).Times(2).WillRepeatedly(testing::Invoke([&arrow_args](ElementType* ptr) { + // capturing which addresses are dereferenced. + arrow_args.push_back(ptr); + })); + + // when calling data() on the NonRelocatableVector + data_ptr = unit_->data(); + EXPECT_NE(data_ptr, nullptr); + } + // Guard destructor clears the spy - destruction proceeds with normal pointer behavior! + + // Then two pointer-dereferences happened + ASSERT_EQ(arrow_args.size(), 2U); + // where the 1st deref comes from GetLastElement (pointer advanced to last element) + EXPECT_EQ(arrow_args[0], data_ptr + (kNonZeroNumberElements - 1)); + // and the 2nd deref call comes from GetFirstElement (pointer to first element) + EXPECT_EQ(arrow_args[1], data_ptr); +} + +TEST_F(NonRelocatableVectorPointerInteractionFixture, BeginDereferencesBeginAndEnd) +{ + // Given a NonRelocatableVector which has been filled with elements + GivenANonRelocatableVectorContainingNumberOfElements(kNonZeroNumberElements); + + // and a registered pointer-spy mock, + testing::StrictMock> pointer_spy; + std::vector arrow_args; + + ElementType* begin_it = nullptr; + { + test::MockablePointerMockGuard guard{&pointer_spy}; + + // expect, that the pointer to the start of the data is advanced to the last element (size - 1) ... + EXPECT_CALL(pointer_spy, PlusAssign(static_cast(kNonZeroNumberElements - 1))); + // ... and two pointer dereferences take place + EXPECT_CALL(pointer_spy, Arrow(_)).Times(2).WillRepeatedly(testing::Invoke([&arrow_args](ElementType* ptr) { + // capturing which addresses are dereferenced. + arrow_args.push_back(ptr); + })); + + // when calling begin() on the NonRelocatableVector + begin_it = unit_->begin(); + EXPECT_NE(begin_it, nullptr); + } + // Guard destructor clears the spy - destruction proceeds with normal pointer behavior! + + // Then two pointer-dereferences happened + ASSERT_EQ(arrow_args.size(), 2U); + // where the 1st deref comes from GetLastElement (pointer advanced to last element) + EXPECT_EQ(arrow_args[0], begin_it + (kNonZeroNumberElements - 1)); + // and the 2nd deref call comes from GetFirstElement (pointer to first element) + EXPECT_EQ(arrow_args[1], begin_it); +} + +TEST_F(NonRelocatableVectorPointerInteractionFixture, EndDereferencesBeginAndEnd) +{ + // Given a NonRelocatableVector which has been filled with elements + GivenANonRelocatableVectorContainingNumberOfElements(kNonZeroNumberElements); + + // and a registered pointer-spy mock, + testing::StrictMock> pointer_spy; + std::vector arrow_args; + + ElementType* end_it = nullptr; + { + test::MockablePointerMockGuard guard{&pointer_spy}; + + // expect, that the pointer to the start of the data is advanced to the last element (size - 1) ... + EXPECT_CALL(pointer_spy, PlusAssign(static_cast(kNonZeroNumberElements - 1))); + // ... and two pointer dereferences take place + EXPECT_CALL(pointer_spy, Arrow(_)).Times(2).WillRepeatedly(testing::Invoke([&arrow_args](ElementType* ptr) { + // capturing which addresses are dereferenced. + arrow_args.push_back(ptr); + })); + + // when calling end() on the NonRelocatableVector + end_it = unit_->end(); + EXPECT_NE(end_it, nullptr); + } + // Guard destructor clears the spy - destruction proceeds with normal pointer behavior! + + // Then two pointer-dereferences happened + ASSERT_EQ(arrow_args.size(), 2U); + // where the 1st deref comes from GetFirstElement (pointer to first element) + EXPECT_EQ(arrow_args[0], end_it - kNonZeroNumberElements); + // and the 2nd deref call comes from GetLastElement (pointer advanced to last element) + EXPECT_EQ(arrow_args[1], end_it - 1); +} + } // namespace score::containers diff --git a/score/containers/test/BUILD b/score/containers/test/BUILD index b61732227..6a2539819 100644 --- a/score/containers/test/BUILD +++ b/score/containers/test/BUILD @@ -17,18 +17,26 @@ load("@rules_cc//cc:defs.bzl", "cc_library") load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") cc_library( - name = "custom_allocator_mock", + name = "mockable_allocator", testonly = True, - srcs = ["custom_allocator_mock.cpp"], - hdrs = ["custom_allocator_mock.h"], + srcs = [ + "mockable_allocator.cpp", + "mockable_pointer.cpp", + "mockable_pointer_mock_guard.cpp", + "pointer_spy_mock.cpp", + ], + hdrs = [ + "mockable_allocator.h", + "mockable_pointer.h", + "mockable_pointer_mock_guard.h", + "pointer_spy_mock.h", + ], features = COMPILER_WARNING_FEATURES, visibility = [ "@score_baselibs//score/containers:__subpackages__", ], deps = [ "@googletest//:gtest_main", - "@score_baselibs//score/language/futurecpp", - "@score_baselibs//score/memory/shared:offset_ptr", ], ) diff --git a/score/containers/test/custom_allocator_mock.h b/score/containers/test/custom_allocator_mock.h deleted file mode 100644 index 7edc24616..000000000 --- a/score/containers/test/custom_allocator_mock.h +++ /dev/null @@ -1,86 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#ifndef SCORE_LIB_CONTAINERS_CUSTOM_ALLOCATOR_MOCK_H -#define SCORE_LIB_CONTAINERS_CUSTOM_ALLOCATOR_MOCK_H - -#include "score/memory/shared/offset_ptr.h" - -#include -#include - -#include - -namespace score -{ -namespace containers -{ - -/// \brief Mock for a CustomAllocatorMock needed for specific tests -/// \details Beware: We currently expect from custom allocators, that they deal with pointer types (like our -/// PolymorphicOffsetPtrAllocator does), which provide a get() method, to obtain the raw-pointer (which our -/// OffsetPtr supports). See also the detailed comment in DynamicArray::DynamicArrayDeleter::to_address()! -/// \tparam T element type to be allocated -template -class CustomAllocatorMock -{ - public: - using value_type = T; - using size_type = std::size_t; - using pointer = memory::shared::OffsetPtr; - - MOCK_METHOD(T*, allocate, (size_type), (noexcept)); - MOCK_METHOD(void, deallocate, (T*, size_type), (noexcept)); - MOCK_METHOD(void, construct, (T*), (noexcept)); - MOCK_METHOD(void, destroy, (T*), (noexcept)); -}; - -/// \brief We need this wrapper as allocators need to be copyable, but a google mock isn't! -/// \tparam T element type to be allocated -template -class CustomAllocatorMockWrapper -{ - public: - using value_type = typename CustomAllocatorMock::value_type; - using size_type = typename CustomAllocatorMock::size_type; - using pointer = typename CustomAllocatorMock::pointer; - - explicit CustomAllocatorMockWrapper(CustomAllocatorMock* mock) : mock_{mock} {} - - T* allocate(size_type num_of_elements) - { - return mock_->allocate(num_of_elements); - } - - void deallocate(T* ptr, size_type num_of_elements) - { - mock_->deallocate(ptr, num_of_elements); - } - - void construct(T* ptr) - { - mock_->construct(ptr); - } - - void destroy(T* ptr) - { - mock_->destroy(ptr); - } - - private: - CustomAllocatorMock* mock_; -}; - -} // namespace containers -} // namespace score - -#endif // SCORE_LIB_CONTAINERS_CUSTOM_ALLOCATOR_MOCK_H diff --git a/score/containers/test/custom_allocator_mock.cpp b/score/containers/test/mockable_allocator.cpp similarity index 90% rename from score/containers/test/custom_allocator_mock.cpp rename to score/containers/test/mockable_allocator.cpp index 9ae095356..24d2e3775 100644 --- a/score/containers/test/custom_allocator_mock.cpp +++ b/score/containers/test/mockable_allocator.cpp @@ -10,4 +10,4 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#include "score/containers/test/custom_allocator_mock.h" +#include "score/containers/test/mockable_allocator.h" diff --git a/score/containers/test/mockable_allocator.h b/score/containers/test/mockable_allocator.h new file mode 100644 index 000000000..eb92fc4b3 --- /dev/null +++ b/score/containers/test/mockable_allocator.h @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_LIB_CONTAINERS_TEST_MOCKABLE_ALLOCATOR_H +#define SCORE_LIB_CONTAINERS_TEST_MOCKABLE_ALLOCATOR_H + +#include "score/containers/test/mockable_pointer.h" + +#include +#include + +namespace score::containers::test +{ + +/// \brief An allocator wrapper around std::allocator that returns MockablePointer as its pointer type. +/// +/// This allocator behaves exactly like std::allocator but uses MockablePointer instead of raw T*. +/// This allows tests of allocator aware containers (like NonRelocatableVector) with real allocation/deallocation +/// behavior during normal operations (f.i. construction, emplace_back, and destruction), while still being able to +/// register a PointerSpyMock on MockablePointer to verify pointer operations during specific method calls +/// (e.g. data()). +/// +/// Usage: +/// // Construction and fill work without any mock setup: +/// NonRelocatableVector> vec{10}; +/// vec.emplace_back(42); +/// +/// // Only set the spy when you want to verify pointer interactions: +/// PointerSpyMock spy; +/// MockablePointerMockGuard guard{&spy}; +/// EXPECT_CALL(spy, PlusAssign(9)); +/// vec.data(); +template +class MockableAllocator +{ + public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = MockablePointer; + + MockableAllocator() noexcept = default; + + template + // Rationale : Non-explicit constructor is needed for implicit conversions in rebind cases. + // NOLINTNEXTLINE(google-explicit-constructor) - standard allocator rebind pattern + MockableAllocator(const MockableAllocator& /*unused*/) noexcept + { + } + + pointer allocate(size_type n) + { + T* raw = std_alloc_.allocate(n); + return MockablePointer(raw); + } + + void deallocate(pointer ptr, size_type n) + { + std_alloc_.deallocate(ptr.get(), n); + } + + bool operator==(const MockableAllocator& /*unused*/) const noexcept + { + return true; + } + + bool operator!=(const MockableAllocator& /*unused*/) const noexcept + { + return false; + } + + private: + std::allocator std_alloc_{}; +}; + +} // namespace score::containers::test + +#endif // SCORE_LIB_CONTAINERS_TEST_MOCKABLE_ALLOCATOR_H + diff --git a/score/containers/test/mockable_pointer.cpp b/score/containers/test/mockable_pointer.cpp new file mode 100644 index 000000000..5366a8c7d --- /dev/null +++ b/score/containers/test/mockable_pointer.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/containers/test/mockable_pointer.h" diff --git a/score/containers/test/mockable_pointer.h b/score/containers/test/mockable_pointer.h new file mode 100644 index 000000000..c4fe78e98 --- /dev/null +++ b/score/containers/test/mockable_pointer.h @@ -0,0 +1,238 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_LIB_CONTAINERS_TEST_MOCKABLE_POINTER_H +#define SCORE_LIB_CONTAINERS_TEST_MOCKABLE_POINTER_H + +#include "score/containers/test/pointer_spy_mock.h" + +#include +#include +#include +#include + +namespace score::containers::test +{ + +/// \brief A fancy-pointer wrapper around a raw T* that supports optional spy-based call recording. +/// +/// By default, MockablePointer behaves identically to a raw pointer. All arithmetic, compare and dereference +/// operations are forwarded to the underlying raw pointer. +/// +/// When a PointerSpyMock is registered via SetMock(), each operation additionally invokes the +/// corresponding spy method (for EXPECT_CALL verification), but the real pointer behavior is +/// always preserved. This enables "spy" testing: verify that specific pointer operations are +/// called (e.g. during NonRelocatableVector::data()) without having to mock the entire lifecycle. +/// +/// Usage: See example/usage in MockableAllocator, which uses MockablePointer as its pointer type. +/// +/// Or use the RAII guard MockablePointerMockGuard for exception-safe mock lifetime management. +template +class MockablePointer +{ + public: + // Iterator traits (required for std::advance, std::next, std::iterator_traits) + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using element_type = T; + using difference_type = std::ptrdiff_t; + using reference = T&; + using pointer = T*; + + /// \brief Register a spy mock. While set, all pointer operations will record calls to the spy. + static void SetMock(PointerSpyMock* mock) noexcept + { + mock_ = mock; + } + + /// \brief Clear the spy mock. Operations revert to pure pointer behavior (no recording). + static void ClearMock() noexcept + { + mock_ = nullptr; + } + + // --- Constructors --- + + MockablePointer() noexcept : ptr_{nullptr} {} + + // NOLINTNEXTLINE(google-explicit-constructor) - intentional implicit conversion from nullptr + MockablePointer(std::nullptr_t) noexcept : ptr_{nullptr} {} + + explicit MockablePointer(T* ptr) noexcept : ptr_{ptr} {} + + // --- Assignment --- + + MockablePointer& operator=(std::nullptr_t) noexcept + { + ptr_ = nullptr; + return *this; + } + + // --- Raw pointer access --- + + /// \brief Get the underlying raw pointer. + T* get() const noexcept + { + return ptr_; + } + + // --- Dereference operations --- + + reference operator*() const + { + if (mock_ != nullptr) + { + mock_->Dereference(); + } + return *ptr_; + } + + pointer operator->() const + { + if (mock_ != nullptr) + { + mock_->Arrow(ptr_); + } + return ptr_; + } + + reference operator[](difference_type idx) const + { + if (mock_ != nullptr) + { + mock_->Index(idx); + } + return ptr_[idx]; + } + + // --- Arithmetic operations --- + + MockablePointer& operator+=(difference_type offset) + { + if (mock_ != nullptr) + { + mock_->PlusAssign(offset); + } + ptr_ += offset; + return *this; + } + + MockablePointer& operator-=(difference_type offset) + { + if (mock_ != nullptr) + { + mock_->MinusAssign(offset); + } + ptr_ -= offset; + return *this; + } + + MockablePointer& operator++() + { + if (mock_ != nullptr) + { + mock_->PreIncrement(); + } + ++ptr_; + return *this; + } + + MockablePointer operator++(int) + { + if (mock_ != nullptr) + { + mock_->PostIncrement(); + } + MockablePointer tmp = *this; + ++ptr_; + return tmp; + } + + MockablePointer& operator--() + { + if (mock_ != nullptr) + { + mock_->PreDecrement(); + } + --ptr_; + return *this; + } + + MockablePointer operator--(int) + { + if (mock_ != nullptr) + { + mock_->PostDecrement(); + } + MockablePointer tmp = *this; + --ptr_; + return tmp; + } + + // --- Comparison operators --- + + bool operator==(const MockablePointer& other) const noexcept + { + return ptr_ == other.ptr_; + } + + bool operator!=(const MockablePointer& other) const noexcept + { + return ptr_ != other.ptr_; + } + + bool operator==(std::nullptr_t) const noexcept + { + return ptr_ == nullptr; + } + + bool operator!=(std::nullptr_t) const noexcept + { + return ptr_ != nullptr; + } + + friend bool operator==(std::nullptr_t, const MockablePointer& rhs) noexcept + { + return rhs.ptr_ == nullptr; + } + + friend bool operator!=(std::nullptr_t, const MockablePointer& rhs) noexcept + { + return rhs.ptr_ != nullptr; + } + + // --- Difference --- + + friend difference_type operator-(const MockablePointer& lhs, const MockablePointer& rhs) noexcept + { + return lhs.ptr_ - rhs.ptr_; + } + + // --- pointer_traits support --- + + static MockablePointer pointer_to(element_type& e) noexcept + { + return MockablePointer(&e); + } + + private: + T* ptr_; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - intentional static for test spy pattern + static inline PointerSpyMock* mock_ = nullptr; +}; + + + +} // namespace score::containers::test + +#endif // SCORE_LIB_CONTAINERS_TEST_MOCKABLE_POINTER_H + diff --git a/score/containers/test/mockable_pointer_mock_guard.cpp b/score/containers/test/mockable_pointer_mock_guard.cpp new file mode 100644 index 000000000..d096c975e --- /dev/null +++ b/score/containers/test/mockable_pointer_mock_guard.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/containers/test/mockable_pointer_mock_guard.h" diff --git a/score/containers/test/mockable_pointer_mock_guard.h b/score/containers/test/mockable_pointer_mock_guard.h new file mode 100644 index 000000000..8b1e53909 --- /dev/null +++ b/score/containers/test/mockable_pointer_mock_guard.h @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_LIB_CONTAINERS_TEST_MOCKABLE_POINTER_MOCK_GUARD_H +#define SCORE_LIB_CONTAINERS_TEST_MOCKABLE_POINTER_MOCK_GUARD_H + +#include "score/containers/test/mockable_pointer.h" +#include "score/containers/test/pointer_spy_mock.h" + + +namespace score::containers::test +{ + +/// \brief RAII guard for MockablePointer spy mock lifetime. +/// +/// Sets the spy mock on construction and clears it on destruction, ensuring the mock +/// is always properly cleaned up even if the test throws an exception. +/// +template +class MockablePointerMockGuard +{ + public: + explicit MockablePointerMockGuard(PointerSpyMock* mock) noexcept + { + MockablePointer::SetMock(mock); + } + + ~MockablePointerMockGuard() noexcept + { + MockablePointer::ClearMock(); + } + + MockablePointerMockGuard(const MockablePointerMockGuard&) = delete; + MockablePointerMockGuard& operator=(const MockablePointerMockGuard&) = delete; + MockablePointerMockGuard(MockablePointerMockGuard&&) = delete; + MockablePointerMockGuard& operator=(MockablePointerMockGuard&&) = delete; +}; + +} // namespace score::containers::test + +#endif // SCORE_LIB_CONTAINERS_TEST_MOCKABLE_POINTER_MOCK_GUARD_H \ No newline at end of file diff --git a/score/containers/test/pointer_spy_mock.cpp b/score/containers/test/pointer_spy_mock.cpp new file mode 100644 index 000000000..ddd720d4a --- /dev/null +++ b/score/containers/test/pointer_spy_mock.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/containers/test/pointer_spy_mock.h" diff --git a/score/containers/test/pointer_spy_mock.h b/score/containers/test/pointer_spy_mock.h new file mode 100644 index 000000000..bf9310da9 --- /dev/null +++ b/score/containers/test/pointer_spy_mock.h @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_LIB_CONTAINERS_POINTER_SPY_MOCK_H +#define SCORE_LIB_CONTAINERS_POINTER_SPY_MOCK_H + +#include + +#include + +namespace score::containers::test +{ + +/// \brief A spy mock for pointer operations used together with MockablePointer. +/// +/// All methods return void because MockablePointer always performs real pointer arithmetic; +/// the spy only records calls so that test expectations can verify which operations are invoked. +/// This avoids the need to set up return values (e.g. WillOnce(ReturnRef(...))) which makes +/// tests much less brittle. +template +class PointerSpyMock +{ + public: + using difference_type = std::ptrdiff_t; + + MOCK_METHOD(void, Arrow, (T*), (const)); + MOCK_METHOD(void, Dereference, (), (const)); + MOCK_METHOD(void, Index, (difference_type idx), (const)); + MOCK_METHOD(void, PlusAssign, (difference_type offset)); + MOCK_METHOD(void, MinusAssign, (difference_type offset)); + MOCK_METHOD(void, PreIncrement, ()); + MOCK_METHOD(void, PostIncrement, ()); + MOCK_METHOD(void, PreDecrement, ()); + MOCK_METHOD(void, PostDecrement, ()); +}; + +} // namespace score::containers::test + +#endif // SCORE_LIB_CONTAINERS_POINTER_SPY_MOCK_H +