// Copyright (c) 2019 - 2020 by Robert Bosch GmbH. All rights reserved. // 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/vector.hpp" #include "iceoryx_hoofs/testing/timing_test.hpp" #include "iceoryx_posh/internal/mepoo/segment_manager.hpp" #include "iceoryx_posh/internal/roudi/introspection/mempool_introspection.hpp" #include "iceoryx_posh/roudi/introspection_types.hpp" #include "iceoryx_posh/testing/mocks/chunk_mock.hpp" #include "mocks/mepoo_memory_manager_mock.hpp" #include "mocks/publisher_mock.hpp" #include "test.hpp" namespace { using namespace ::testing; class CallChecker { public: MOCK_METHOD0(offer, void(void)); }; CallChecker& callChecker() { static CallChecker cc; return cc; } class MockPublisherPortUserAccess : public MockPublisherPortUser { public: void offer() { callChecker().offer(); } }; class SegmentMock { public: SegmentMock() = default; SegmentMock(const SegmentMock&){}; MePooMemoryManager_MOCK& getMemoryManager() { return memoryManager; } iox::posix::PosixGroup getWriterGroup() const { return iox::posix::PosixGroup::getGroupOfCurrentProcess(); } iox::posix::PosixGroup getReaderGroup() const { return iox::posix::PosixGroup::getGroupOfCurrentProcess(); } private: MePooMemoryManager_MOCK memoryManager; }; class SegmentManagerMock { public: iox::cxx::vector m_segmentContainer; }; class MemPoolIntrospectionAccess : public iox::roudi::MemPoolIntrospection { public: MemPoolIntrospectionAccess(MePooMemoryManager_MOCK& memoryManager, SegmentManagerMock& segmentManager, MockPublisherPortUserAccess&& publisherPort) : iox::roudi::MemPoolIntrospection( memoryManager, segmentManager, std::move(publisherPort)) { } MockPublisherPortUserAccess& getPublisherPort() { return this->m_publisherPort; } using iox::roudi::MemPoolIntrospection:: send; }; class MemPoolIntrospection_test : public Test { public: using MemPoolInfoContainer = iox::roudi::MemPoolInfoContainer; using MemPoolInfo = iox::mepoo::MemPoolInfo; using Topic = iox::roudi::MemPoolIntrospectionInfoContainer; MemPoolIntrospection_test() { } ~MemPoolIntrospection_test() { } virtual void SetUp() { internal::CaptureStdout(); SegmentMock segmentMock; m_segmentManager_mock.m_segmentContainer.push_back(segmentMock); } virtual void TearDown() { std::string output = internal::GetCapturedStdout(); if (Test::HasFailure()) { std::cout << output << std::endl; } } template void initMemPoolInfo(int index, MemPoolInfoStruct& info) { info.m_chunkSize = index * 100 + 10; info.m_minFreeChunks = index * 100 + 45; info.m_numChunks = index * 100 + 50; info.m_usedChunks = index * 100 + 3; } // initializes the mempool info with a defined pattern void initMemPoolInfoContainer(MemPoolInfoContainer& memPoolInfoContainer) { int index = 0; for (auto& info : memPoolInfoContainer) { initMemPoolInfo(index, info); index++; } } // compares mempool info structs and returns true if they are equal template bool compareMemPoolInfo(MemPoolInfoType1& first, MemPoolInfoType2& second) { int index = 0; for (auto& info : first) { if (info.m_chunkSize != second[index].m_chunkSize) { return false; } if (info.m_minFreeChunks != second[index].m_minFreeChunks) { return false; } if (info.m_numChunks != second[index].m_numChunks) { return false; } if (info.m_usedChunks != second[index].m_usedChunks) { return false; } index++; } return true; } MePooMemoryManager_MOCK m_rouDiInternalMemoryManager_mock; SegmentManagerMock m_segmentManager_mock; MockPublisherPortUserAccess m_publisherPortImpl_mock; }; TEST_F(MemPoolIntrospection_test, CTOR) { ::testing::Test::RecordProperty("TEST_ID", "9da5951c-cbff-41b5-95e3-ae6921ce9331"); { EXPECT_CALL(callChecker(), offer()).Times(1); MemPoolIntrospectionAccess introspectionAccess( m_rouDiInternalMemoryManager_mock, m_segmentManager_mock, std::move(m_publisherPortImpl_mock)); EXPECT_CALL(introspectionAccess.getPublisherPort(), stopOffer()).Times(1); } } TEST_F(MemPoolIntrospection_test, send_noSubscribers) { ::testing::Test::RecordProperty("TEST_ID", "28af0288-b57e-4c49-b0a9-33809bf69c96"); EXPECT_CALL(callChecker(), offer()).Times(1); MemPoolIntrospectionAccess introspectionAccess( m_rouDiInternalMemoryManager_mock, m_segmentManager_mock, std::move(m_publisherPortImpl_mock)); MemPoolInfoContainer memPoolInfoContainer; initMemPoolInfoContainer(memPoolInfoContainer); EXPECT_CALL(introspectionAccess.getPublisherPort(), tryAllocateChunk(_, _, _, _)).Times(0); introspectionAccess.send(); } /// @todo test with multiple segments and also test the mempool info from RouDiInternalMemoryManager /// @todo This test is not very useful as it is highly implementation-dependent and fails if the implementation changes. /// Should be realized as an integration test with a roudi environment and less mocking classes instead. TEST_F(MemPoolIntrospection_test, DISABLED_send_withSubscribers) { ::testing::Test::RecordProperty("TEST_ID", "52c48ddb-e7b6-450d-b262-1e24401ac878"); EXPECT_CALL(callChecker(), offer()).Times(1); MemPoolIntrospectionAccess introspectionAccess( m_rouDiInternalMemoryManager_mock, m_segmentManager_mock, std::move(m_publisherPortImpl_mock)); MemPoolInfoContainer memPoolInfoContainer; MemPoolInfo memPoolInfo{0, 0, 0, 0}; initMemPoolInfoContainer(memPoolInfoContainer); EXPECT_CALL(m_segmentManager_mock.m_segmentContainer.front().getMemoryManager(), getMemPoolInfo(_)) .WillRepeatedly(Invoke([&](uint32_t index) { initMemPoolInfo(index, memPoolInfo); return memPoolInfo; })); ChunkMock chunk; const auto& sample = chunk.sample(); introspectionAccess.send(); /// @todo expect call to MemPoolHandler::getMemPoolInfo EXPECT_CALL(m_publisherPortImpl_mock, sendChunk(_)).Times(1); ASSERT_EQ(sample->size(), 1u); EXPECT_THAT(compareMemPoolInfo(memPoolInfoContainer, chunk.sample()->front().m_mempoolInfo), Eq(true)); } TIMING_TEST_F(MemPoolIntrospection_test, thread, Repeat(5), [&] { EXPECT_CALL(callChecker(), offer()).Times(1); MemPoolIntrospectionAccess introspectionAccess( m_rouDiInternalMemoryManager_mock, m_segmentManager_mock, std::move(m_publisherPortImpl_mock)); MemPoolInfoContainer memPoolInfoContainer; MemPoolInfo memPoolInfo(0, 0, 0, 0); initMemPoolInfoContainer(memPoolInfoContainer); EXPECT_CALL(m_rouDiInternalMemoryManager_mock, getMemPoolInfo(_)).WillRepeatedly(Invoke([&](uint32_t index) { initMemPoolInfo(index, memPoolInfo); return memPoolInfo; })); // we use the hasSubscribers call to check how often the thread calls the send method EXPECT_CALL(introspectionAccess.getPublisherPort(), hasSubscribers).Times(AtLeast(4)); using namespace iox::units::duration_literals; iox::units::Duration snapshotInterval(100_ms); introspectionAccess.setSendInterval(snapshotInterval); introspectionAccess.run(); std::this_thread::sleep_for(std::chrono::milliseconds( 6 * snapshotInterval.toMilliseconds())); // within this time, the thread should have run 6 times introspectionAccess.run(); std::this_thread::sleep_for(std::chrono::milliseconds( 6 * snapshotInterval.toMilliseconds())); // the thread should sleep, if not, we have 12 runs introspectionAccess.stop(); }); } // namespace