// Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. // Copyright (c) 2020 - 2022 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_binding_c/internal/cpp2c_enum_translation.hpp" #include "iceoryx_binding_c/internal/cpp2c_publisher.hpp" #include "iceoryx_posh/internal/popo/building_blocks/chunk_queue_popper.hpp" #include "iceoryx_posh/internal/popo/ports/publisher_port_roudi.hpp" #include "iceoryx_posh/internal/popo/ports/publisher_port_user.hpp" #include "iceoryx_posh/mepoo/mepoo_config.hpp" #include "iceoryx_posh/testing/roudi_environment/roudi_environment.hpp" using namespace iox; using namespace iox::popo; extern "C" { #include "iceoryx_binding_c/chunk.h" #include "iceoryx_binding_c/publisher.h" #include "iceoryx_binding_c/runtime.h" } #include "test.hpp" namespace { using namespace ::testing; using namespace iox::capro; using namespace iox::cxx; using namespace iox::mepoo; using namespace iox::posix; class iox_pub_test : public Test { protected: struct DummySample { uint64_t dummy{42}; }; iox_pub_test() { m_mempoolconf.addMemPool({CHUNK_SIZE, NUM_CHUNKS_IN_POOL}); m_memoryManager.configureMemoryManager(m_mempoolconf, m_memoryAllocator, m_memoryAllocator); } ~iox_pub_test() { } void SetUp() { m_sut.m_portData = &m_publisherPortData; ::testing::internal::CaptureStderr(); } void TearDown() { std::string output = ::testing::internal::GetCapturedStderr(); if (Test::HasFailure()) { std::cout << output << std::endl; } } void Subscribe(popo::PublisherPortData* ptr) { PublisherPortUser userPort(ptr); PublisherPortRouDi roudiPort(ptr); roudiPort.tryGetCaProMessage(); iox::capro::CaproMessage caproMessage(iox::capro::CaproMessageType::SUB, iox::capro::ServiceDescription("a", "b", "c")); caproMessage.m_chunkQueueData = &m_chunkQueueData; auto maybeCaProMessage = roudiPort.dispatchCaProMessageAndGetPossibleResponse(caproMessage); } void Unsubscribe(popo::PublisherPortData* ptr) { PublisherPortRouDi roudiPort(ptr); iox::capro::CaproMessage caproMessage(iox::capro::CaproMessageType::UNSUB, iox::capro::ServiceDescription("a", "b", "c")); caproMessage.m_chunkQueueData = &m_chunkQueueData; auto maybeCaProMessage = roudiPort.dispatchCaProMessageAndGetPossibleResponse(caproMessage); } static constexpr size_t MEMORY_SIZE = 1024 * 1024; uint8_t m_memory[MEMORY_SIZE]; static constexpr uint32_t NUM_CHUNKS_IN_POOL = 20; static constexpr uint32_t CHUNK_SIZE = 256; using ChunkQueueData_t = popo::ChunkQueueData; ChunkQueueData_t m_chunkQueueData{iox::popo::QueueFullPolicy::DISCARD_OLDEST_DATA, iox::cxx::VariantQueueTypes::SoFi_SingleProducerSingleConsumer}; Allocator m_memoryAllocator{m_memory, MEMORY_SIZE}; MePooConfig m_mempoolconf; MemoryManager m_memoryManager; // publisher port w/o history PublisherPortData m_publisherPortData{ ServiceDescription("a", "b", "c"), "myApp", &m_memoryManager, PublisherOptions()}; // publisher port w/ history PublisherOptions m_publisherOptions{MAX_PUBLISHER_HISTORY}; PublisherPortData m_publisherPortDataHistory{ capro::ServiceDescription("x", "y", "z"), "myApp", &m_memoryManager, m_publisherOptions}; cpp2c_Publisher m_sut; }; TEST_F(iox_pub_test, initPublisherWithNullptrForStorageReturnsNullptr) { ::testing::Test::RecordProperty("TEST_ID", "8ac8921a-957a-4bd4-8943-7294defc78d3"); iox_pub_options_t options; iox_pub_options_init(&options); EXPECT_EQ(iox_pub_init(nullptr, "all", "glory", "hypnotoad", &options), nullptr); } // this crashes if the fixture is used, therefore a test without a fixture TEST(iox_pub_test_DeathTest, initPublisherWithNotInitializedPublisherOptionsTerminates) { ::testing::Test::RecordProperty("TEST_ID", "c8768fd8-e02c-4f87-99a0-32a2c36af4b1"); iox_pub_options_t options; iox_pub_storage_t storage; EXPECT_DEATH({ iox_pub_init(&storage, "a", "b", "c", &options); }, ".*"); } TEST_F(iox_pub_test, initPublisherWithDefaultOptionsWorks) { ::testing::Test::RecordProperty("TEST_ID", "d2e677cd-2fcc-47a2-80e6-2d08245b7c1a"); iox::roudi::RouDiEnvironment roudiEnv; iox_runtime_init("hypnotoad"); iox_pub_options_t options; iox_pub_options_init(&options); iox_pub_storage_t storage; auto sut = iox_pub_init(&storage, "a", "b", "c", &options); EXPECT_THAT(sut, Ne(nullptr)); iox_pub_deinit(sut); } TEST_F(iox_pub_test, initialStateOfIsOfferedIsAsExpected) { ::testing::Test::RecordProperty("TEST_ID", "fa757a54-a8df-420e-b32d-a9d5724a7d20"); PublisherOptions iGotOptions; auto expectedIsOffered = iGotOptions.offerOnCreate; EXPECT_EQ(expectedIsOffered, iox_pub_is_offered(&m_sut)); } TEST_F(iox_pub_test, is_offeredAfterOffer) { ::testing::Test::RecordProperty("TEST_ID", "719c76be-e6b5-48a7-bd87-a7daea702983"); iox_pub_offer(&m_sut); EXPECT_TRUE(iox_pub_is_offered(&m_sut)); } TEST_F(iox_pub_test, isNotOfferedAfterStopOffer) { ::testing::Test::RecordProperty("TEST_ID", "0da3afd6-a8f5-489d-b44a-83c89a626e82"); iox_pub_offer(&m_sut); iox_pub_stop_offer(&m_sut); EXPECT_FALSE(iox_pub_is_offered(&m_sut)); } TEST_F(iox_pub_test, initialStateIsNoSubscribers) { ::testing::Test::RecordProperty("TEST_ID", "1cd0f66b-cab8-4b2f-8e53-679b415dc745"); EXPECT_FALSE(iox_pub_has_subscribers(&m_sut)); } TEST_F(iox_pub_test, has_subscribersAfterSubscription) { ::testing::Test::RecordProperty("TEST_ID", "b970de58-c253-4e07-a6a0-61df061e8173"); iox_pub_offer(&m_sut); this->Subscribe(&m_publisherPortData); EXPECT_TRUE(iox_pub_has_subscribers(&m_sut)); } TEST_F(iox_pub_test, noSubscribersAfterUnsubscribe) { ::testing::Test::RecordProperty("TEST_ID", "d3de1ea2-64ed-48b6-bc1c-967e010fee32"); iox_pub_offer(&m_sut); this->Subscribe(&m_publisherPortData); this->Unsubscribe(&m_publisherPortData); EXPECT_FALSE(iox_pub_has_subscribers(&m_sut)); } TEST_F(iox_pub_test, allocateChunkForOneChunkIsSuccessful) { ::testing::Test::RecordProperty("TEST_ID", "4a8f2275-f153-469e-8699-f36a136608a1"); void* chunk = nullptr; EXPECT_EQ(AllocationResult_SUCCESS, iox_pub_loan_chunk(&m_sut, &chunk, sizeof(DummySample))); } TEST_F(iox_pub_test, allocateChunkUserPayloadAlignmentIsSuccessful) { ::testing::Test::RecordProperty("TEST_ID", "c6e7e2ea-fa76-43e7-b217-5bfc4b94837c"); constexpr uint32_t USER_PAYLOAD_ALIGNMENT{128U}; void* chunk = nullptr; ASSERT_EQ(AllocationResult_SUCCESS, iox_pub_loan_aligned_chunk(&m_sut, &chunk, sizeof(DummySample), USER_PAYLOAD_ALIGNMENT)); EXPECT_TRUE(reinterpret_cast(chunk) % USER_PAYLOAD_ALIGNMENT == 0U); } TEST_F(iox_pub_test, allocateChunkWithUserHeaderIsSuccessful) { ::testing::Test::RecordProperty("TEST_ID", "53c07218-2ac9-4e2b-8ca3-7df856cd1cc5"); constexpr uint32_t USER_HEADER_SIZE = 4U; constexpr uint32_t USER_HEADER_ALIGNMENT = 2U; void* chunk = nullptr; ASSERT_EQ(AllocationResult_SUCCESS, iox_pub_loan_aligned_chunk_with_user_header( &m_sut, &chunk, sizeof(DummySample), alignof(DummySample), USER_HEADER_SIZE, USER_HEADER_ALIGNMENT)); auto chunkHeader = iox_chunk_header_from_user_payload(chunk); auto spaceBetweenChunkHeaderAndUserPaylod = reinterpret_cast(chunk) - reinterpret_cast(chunkHeader); EXPECT_GT(spaceBetweenChunkHeaderAndUserPaylod, sizeof(iox::mepoo::ChunkHeader)); } TEST_F(iox_pub_test, allocateChunkWithUserHeaderAndUserPayloadAlignmentFails) { ::testing::Test::RecordProperty("TEST_ID", "a2bcb0d4-b207-436f-9909-9e67815f525a"); constexpr uint32_t USER_PAYLOAD_ALIGNMENT{128U}; constexpr uint32_t USER_HEADER_SIZE = 4U; constexpr uint32_t USER_HEADER_ALIGNMENT = 3U; void* chunk = nullptr; ASSERT_EQ( AllocationResult_INVALID_PARAMETER_FOR_USER_PAYLOAD_OR_USER_HEADER, iox_pub_loan_aligned_chunk_with_user_header( &m_sut, &chunk, sizeof(DummySample), USER_PAYLOAD_ALIGNMENT, USER_HEADER_SIZE, USER_HEADER_ALIGNMENT)); } TEST_F(iox_pub_test, chunkHeaderCanBeObtainedFromChunk) { ::testing::Test::RecordProperty("TEST_ID", "a4b620af-4ec8-4638-b81b-6759ed048d36"); void* chunk = nullptr; ASSERT_EQ(AllocationResult_SUCCESS, iox_pub_loan_chunk(&m_sut, &chunk, sizeof(DummySample))); auto chunkHeader = iox_chunk_header_from_user_payload(chunk); EXPECT_NE(chunkHeader, nullptr); } TEST_F(iox_pub_test, chunkHeaderCanBeConvertedBackToUserPayload) { ::testing::Test::RecordProperty("TEST_ID", "01dbfd4c-7345-4291-aa2c-2404f15c4bda"); void* chunk = nullptr; ASSERT_EQ(AllocationResult_SUCCESS, iox_pub_loan_chunk(&m_sut, &chunk, sizeof(DummySample))); auto chunkHeader = iox_chunk_header_from_user_payload(chunk); auto userPayloadFromRoundtrip = iox_chunk_header_to_user_payload(chunkHeader); EXPECT_EQ(userPayloadFromRoundtrip, chunk); } TEST_F(iox_pub_test, allocate_chunkFailsWhenHoldingToManyChunksInParallel) { ::testing::Test::RecordProperty("TEST_ID", "77271a1c-242d-438b-8f52-e4819fdf1033"); void* chunk = nullptr; for (uint32_t i = 0U; i < iox::MAX_CHUNKS_ALLOCATED_PER_PUBLISHER_SIMULTANEOUSLY; ++i) { EXPECT_EQ(AllocationResult_SUCCESS, iox_pub_loan_chunk(&m_sut, &chunk, 100)); } EXPECT_EQ(AllocationResult_TOO_MANY_CHUNKS_ALLOCATED_IN_PARALLEL, iox_pub_loan_chunk(&m_sut, &chunk, 100)); } TEST_F(iox_pub_test, allocate_chunkFailsWhenOutOfChunks) { ::testing::Test::RecordProperty("TEST_ID", "7563ed4c-a6d5-487d-9c6c-937d8e8c3d1d"); constexpr uint32_t USER_PAYLOAD_SIZE{100U}; auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); std::vector chunkStore; while (!m_memoryManager.getChunk(chunkSettings) .and_then([&](auto& chunk) { chunkStore.emplace_back(chunk); }) .has_error()) { } void* chunk = nullptr; EXPECT_EQ(AllocationResult_RUNNING_OUT_OF_CHUNKS, iox_pub_loan_chunk(&m_sut, &chunk, 100)); } TEST_F(iox_pub_test, allocatingChunkAcquiresMemory) { ::testing::Test::RecordProperty("TEST_ID", "5a779236-0302-48b5-add7-0a2c33a9d86b"); void* chunk = nullptr; iox_pub_loan_chunk(&m_sut, &chunk, 100); EXPECT_THAT(m_memoryManager.getMemPoolInfo(0).m_usedChunks, Eq(1u)); } TEST_F(iox_pub_test, freeingAnAllocatedChunkReleasesTheMemory) { ::testing::Test::RecordProperty("TEST_ID", "8d3901bc-28a1-4a6d-9d81-d3a456e1f806"); void* chunk = nullptr; iox_pub_loan_chunk(&m_sut, &chunk, 100); iox_pub_release_chunk(&m_sut, chunk); EXPECT_THAT(m_memoryManager.getMemPoolInfo(0).m_usedChunks, Eq(0u)); } TEST_F(iox_pub_test, sendDeliversChunk) { ::testing::Test::RecordProperty("TEST_ID", "187d552a-6903-40cd-88a0-7722eb2a40a8"); void* chunk = nullptr; iox_pub_offer(&m_sut); this->Subscribe(&m_publisherPortData); iox_pub_loan_chunk(&m_sut, &chunk, 100); static_cast(chunk)->dummy = 4711; iox_pub_publish_chunk(&m_sut, chunk); iox::popo::ChunkQueuePopper m_chunkQueuePopper(&m_chunkQueueData); auto maybeSharedChunk = m_chunkQueuePopper.tryPop(); ASSERT_TRUE(maybeSharedChunk.has_value()); EXPECT_TRUE(*maybeSharedChunk == chunk); EXPECT_TRUE(static_cast(maybeSharedChunk->getUserPayload())->dummy == 4711); } TEST_F(iox_pub_test, correctServiceDescriptionReturned) { ::testing::Test::RecordProperty("TEST_ID", "4f91cb12-fbfa-4bad-ad59-ab2579f83fbe"); auto serviceDescription = iox_pub_get_service_description(&m_sut); EXPECT_THAT(std::string(serviceDescription.serviceString), Eq("a")); EXPECT_THAT(std::string(serviceDescription.instanceString), Eq("b")); EXPECT_THAT(std::string(serviceDescription.eventString), Eq("c")); } TEST(iox_pub_options_test, publisherOptionsAreInitializedCorrectly) { ::testing::Test::RecordProperty("TEST_ID", "dfc9e8b7-efb8-4fe6-a5a3-ebd9c2ad45ac"); iox_pub_options_t sut; sut.historyCapacity = 37; sut.nodeName = "Dr.Gonzo"; sut.offerOnCreate = false; sut.subscriberTooSlowPolicy = ConsumerTooSlowPolicy_WAIT_FOR_CONSUMER; PublisherOptions options; // set offerOnCreate to the opposite of the expected default to check if it gets overwritten to default sut.offerOnCreate = (options.offerOnCreate == false) ? true : false; iox_pub_options_init(&sut); EXPECT_EQ(sut.historyCapacity, options.historyCapacity); EXPECT_EQ(sut.nodeName, nullptr); EXPECT_EQ(sut.offerOnCreate, options.offerOnCreate); EXPECT_EQ(sut.subscriberTooSlowPolicy, cpp2c::consumerTooSlowPolicy(options.subscriberTooSlowPolicy)); EXPECT_TRUE(iox_pub_options_is_initialized(&sut)); } TEST(iox_pub_options_test, publisherOptionsInitializationCheckReturnsTrueAfterDefaultInit) { ::testing::Test::RecordProperty("TEST_ID", "e95c8f2b-c15a-4fb5-9de8-719b2c61a926"); iox_pub_options_t sut; iox_pub_options_init(&sut); EXPECT_TRUE(iox_pub_options_is_initialized(&sut)); } TEST(iox_pub_options_test, publisherOptionsInitializationCheckReturnsFalseWithoutDefaultInit) { ::testing::Test::RecordProperty("TEST_ID", "f3c7c69e-6946-4da4-9c9e-93129cae9d61"); iox_pub_options_t sut; #if (defined(__GNUC__) && __GNUC__ >= 7 && !defined(__clang__)) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif EXPECT_FALSE(iox_pub_options_is_initialized(&sut)); #if (defined(__GNUC__) && __GNUC__ >= 7 && !defined(__clang__)) #pragma GCC diagnostic pop #endif } TEST(iox_pub_options_test, publisherOptionInitializationWithNullptrDoesNotCrash) { ::testing::Test::RecordProperty("TEST_ID", "fe415d38-eaaf-466e-b7d8-d220612cb344"); EXPECT_EXIT( { iox_pub_options_init(nullptr); exit(0); }, ::testing::ExitedWithCode(0), ".*"); } } // namespace