mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-04-21 03:38:15 +00:00
166
tests/unit_tests/shared_mem/CMakeLists.txt
Normal file
166
tests/unit_tests/shared_mem/CMakeLists.txt
Normal file
@@ -0,0 +1,166 @@
|
||||
# Define project (multiple EXEs)
|
||||
project(CommunicationTests VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Surrogate repeater executable
|
||||
add_executable(UnitTest_SharedMemTests_App_Repeater
|
||||
"app_repeater.cpp"
|
||||
"pattern_gen.cpp"
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Repeater ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Repeater Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Repeater ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Repeater Ws2_32 Winmm Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
# Surrogate repeater executable
|
||||
add_executable(UnitTest_SharedMemTests_App_Connect
|
||||
"app_connect.cpp"
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Connect ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Connect Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Connect ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemTests_App_Connect Ws2_32 Winmm Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
# In process mem test
|
||||
add_executable(UnitTest_InprocMemTests
|
||||
"in_process_mem_buffer_tests.cpp"
|
||||
"pattern_gen.cpp"
|
||||
"pattern_gen.h"
|
||||
"main.cpp"
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(UnitTest_InprocMemTests GTest::GTest ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(UnitTest_InprocMemTests Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(UnitTest_InprocMemTests ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(UnitTest_InprocMemTests GTest::GTest Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
# Add the communication unittest
|
||||
add_test(NAME UnitTest_InprocMemTests COMMAND UnitTest_InprocMemTests)
|
||||
|
||||
#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring
|
||||
# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a
|
||||
# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134
|
||||
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
|
||||
# Execute the test
|
||||
add_custom_command(TARGET UnitTest_InprocMemTests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$<TARGET_FILE:UnitTest_InprocMemTests>" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_InprocMemTests.xml
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
# Shared mem buffer test
|
||||
add_executable(UnitTest_SharedMemBufferTests
|
||||
"pattern_gen.cpp"
|
||||
"pattern_gen.h"
|
||||
"main.cpp"
|
||||
"shared_mem_buffer_tests.cpp"
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(UnitTest_SharedMemBufferTests GTest::GTest ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(UnitTest_SharedMemBufferTests Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemBufferTests ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemBufferTests GTest::GTest Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
# Add the communication unittest
|
||||
add_test(NAME UnitTest_SharedMemBufferTests COMMAND UnitTest_SharedMemBufferTests)
|
||||
|
||||
#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring
|
||||
# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a
|
||||
# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134
|
||||
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
|
||||
# Execute the test
|
||||
add_custom_command(TARGET UnitTest_SharedMemBufferTests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$<TARGET_FILE:UnitTest_SharedMemBufferTests>" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_SharedMemBufferTests.xml
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
# Shared mem connection test
|
||||
add_executable(UnitTest_SharedMemConnectTests
|
||||
"main.cpp"
|
||||
"shared_mem_connect.cpp"
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(UnitTest_SharedMemConnectTests GTest::GTest ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(UnitTest_SharedMemConnectTests Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemConnectTests ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemConnectTests GTest::GTest Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
# Add the communication unittest
|
||||
add_test(NAME UnitTest_SharedMemConnectTests COMMAND UnitTest_SharedMemConnectTests)
|
||||
|
||||
#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring
|
||||
# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a
|
||||
# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134
|
||||
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
|
||||
# Execute the test
|
||||
add_custom_command(TARGET UnitTest_SharedMemConnectTests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$<TARGET_FILE:UnitTest_SharedMemConnectTests>" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_SharedMemConnectTests.xml
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
# Shared mem large data test
|
||||
add_executable(UnitTest_SharedMemLargeDataTests
|
||||
"main.cpp"
|
||||
"shared_mem_large_data_tests.cpp"
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(UnitTest_SharedMemLargeDataTests GTest::GTest ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(UnitTest_SharedMemLargeDataTests Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemLargeDataTests ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(UnitTest_SharedMemLargeDataTests GTest::GTest Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
# Add the communication unittest
|
||||
add_test(NAME UnitTest_SharedMemLargeDataTests COMMAND UnitTest_SharedMemLargeDataTests)
|
||||
|
||||
#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring
|
||||
# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a
|
||||
# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134
|
||||
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
|
||||
# Execute the test
|
||||
add_custom_command(TARGET UnitTest_SharedMemLargeDataTests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$<TARGET_FILE:UnitTest_SharedMemLargeDataTests>" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_SharedMemLargeDataTests.xml
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(UnitTest_SharedMemTests_App_Repeater core_services)
|
||||
add_dependencies(UnitTest_SharedMemTests_App_Connect core_services)
|
||||
add_dependencies(UnitTest_InprocMemTests core_services)
|
||||
add_dependencies(UnitTest_SharedMemBufferTests UnitTest_SharedMemTests_App_Repeater)
|
||||
add_dependencies(UnitTest_SharedMemConnectTests UnitTest_SharedMemTests_App_Connect)
|
||||
add_dependencies(UnitTest_SharedMemLargeDataTests UnitTest_SharedMemTests_App_Connect)
|
||||
387
tests/unit_tests/shared_mem/app_connect.cpp
Normal file
387
tests/unit_tests/shared_mem/app_connect.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#include <support/mem_access.h>
|
||||
#include <support/app_control.h>
|
||||
#include "../../../global/base64.h"
|
||||
|
||||
#define TIME_TRACKING
|
||||
#include "../../../sdv_services/ipc_shared_mem/channel_mgnt.cpp"
|
||||
#include "../../../sdv_services/ipc_shared_mem/connection.cpp"
|
||||
#include "../../../sdv_services/ipc_shared_mem/watchdog.cpp"
|
||||
#include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.cpp"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <queue>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Receiver helper class.
|
||||
*/
|
||||
class CReceiver : public sdv::IInterfaceAccess, public sdv::ipc::IDataReceiveCallback, public sdv::ipc::IConnectEventCallback
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CReceiver()
|
||||
{
|
||||
m_threadSender = std::thread(&CReceiver::SendThreadFunc, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CReceiver()
|
||||
{
|
||||
m_bShutdown = true;
|
||||
if (m_threadSender.joinable())
|
||||
m_threadSender.join();
|
||||
}
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Assign a sender interface.
|
||||
* @param[in] pSend Pointer to the sending interface.
|
||||
*/
|
||||
void AssignSender(sdv::ipc::IDataSend* pSend)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
m_pSend = pSend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback to be called by the IPC connection when receiving a data packet. Overload
|
||||
* sdv::ipc::IDataReceiveCallback::ReceiveData.
|
||||
* @param[inout] seqData Sequence of data buffers to received. The sequence might be changed to optimize the communication
|
||||
* without having to copy the data.
|
||||
*/
|
||||
virtual void ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override
|
||||
{
|
||||
// Send the same data back again (if needed).
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
m_queueSendData.push(seqData);
|
||||
lock.unlock();
|
||||
|
||||
m_cvReceived.notify_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send thread for sending data
|
||||
*/
|
||||
void SendThreadFunc()
|
||||
{
|
||||
while (!m_bShutdown && !m_bDisconnect)
|
||||
{
|
||||
// Wait for data to be received
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
if (m_queueSendData.empty())
|
||||
{
|
||||
m_cvReceived.wait_for(lock, std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqData = std::move(m_queueSendData.front());
|
||||
m_queueSendData.pop();
|
||||
lock.unlock();
|
||||
|
||||
// Send the data back to the sender
|
||||
if (m_pSend)
|
||||
m_pSend->SendData(seqData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
|
||||
* @param[in] eConnectStatus The connection status.
|
||||
*/
|
||||
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override
|
||||
{
|
||||
switch (eConnectStatus)
|
||||
{
|
||||
case sdv::ipc::EConnectStatus::disconnected:
|
||||
// Disconnect only when connected before
|
||||
if (m_bConnected)
|
||||
{
|
||||
std::cout << "Forced disconnect communicated..." << std::endl;
|
||||
m_bDisconnect = true;
|
||||
m_cvDisconnect.notify_all();
|
||||
}
|
||||
break;
|
||||
case sdv::ipc::EConnectStatus::disconnected_forced:
|
||||
// Disconnect only when connected before
|
||||
if (m_bConnected)
|
||||
{
|
||||
std::cout << "Disconnect communicated..." << std::endl;
|
||||
m_bDisconnect = true;
|
||||
m_cvDisconnect.notify_all();
|
||||
}
|
||||
break;
|
||||
case sdv::ipc::EConnectStatus::connected:
|
||||
m_bConnected = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait until the disconnect is called.
|
||||
* @param[in] uiDurationMs The wait duration until timeout.
|
||||
* @return Returns whether a disconnect event was triggered or otherwise a timeout occurred.
|
||||
*/
|
||||
bool WaitUntilDisconnect(uint32_t uiDurationMs = 5000)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
m_cvDisconnect.wait_for(lock, std::chrono::milliseconds(uiDurationMs));
|
||||
lock.unlock();
|
||||
|
||||
// Shutdown the thread already
|
||||
m_bShutdown = true;
|
||||
if (m_threadSender.joinable())
|
||||
m_threadSender.join();
|
||||
|
||||
return m_bDisconnect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait until the caller hasn't sent anything anymore for 1 second.
|
||||
*/
|
||||
void WaitForNoActivity()
|
||||
{
|
||||
CConnection* pConnection = dynamic_cast<CConnection*>(m_pSend);
|
||||
|
||||
// Wait until there is no activity any more.
|
||||
std::chrono::high_resolution_clock::time_point tpTickSent = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::high_resolution_clock::time_point tpTickReceive = std::chrono::high_resolution_clock::now();
|
||||
while (!m_bDisconnect)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
m_cvReceived.wait_for(lock, std::chrono::milliseconds(25));
|
||||
|
||||
// Does the connection have a better time?
|
||||
if (pConnection) tpTickSent = pConnection->GetLastSentTime();
|
||||
if (pConnection) tpTickReceive = pConnection->GetLastReceiveTime();
|
||||
std::chrono::high_resolution_clock::time_point tpNow = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// A duration of more than a second should not occur.
|
||||
if (std::chrono::duration<double>(tpNow - tpTickSent).count() > 1.0 &&
|
||||
std::chrono::duration<double>(tpNow - tpTickReceive).count() > 1.0)
|
||||
break;
|
||||
}
|
||||
|
||||
TRACE("No new data for 1 second...");
|
||||
|
||||
// Shutdown the thread already
|
||||
m_bShutdown = true;
|
||||
if (m_threadSender.joinable())
|
||||
m_threadSender.join();
|
||||
}
|
||||
|
||||
private:
|
||||
sdv::ipc::IDataSend* m_pSend = nullptr; ///< Send interface to implement repeating function.
|
||||
mutable std::mutex m_mtxData; ///< Protect data access.
|
||||
std::queue<sdv::sequence<sdv::pointer<uint8_t>>> m_queueSendData; ///< Queue for sending data.
|
||||
std::condition_variable m_cvDisconnect; ///< Disconnect event.
|
||||
std::condition_variable m_cvReceived; ///< Receive event.
|
||||
std::thread m_threadSender; ///< Thread to send data.
|
||||
bool m_bConnected = false; ///< Set when connected was triggered.
|
||||
bool m_bDisconnect = false; ///< Set when shutdown was triggered.
|
||||
bool m_bShutdown = false; ///< Set when shutdown is processed.
|
||||
};
|
||||
|
||||
|
||||
#if defined(_WIN32) && defined(_UNICODE)
|
||||
extern "C" int wmain(int argc, wchar_t* argv[])
|
||||
#else
|
||||
extern "C" int main(int argc, char* argv[])
|
||||
#endif
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::cout << "Missing connection string..." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sdv::app::CAppControl appcontrol;
|
||||
if (!appcontrol.Startup("")) return -1;
|
||||
appcontrol.SetRunningMode();
|
||||
|
||||
std::string logName("appRepeater.log");
|
||||
|
||||
// Create a connection string from the command line arguments separated by spaces. Skip the first argument, since it holds the
|
||||
// module file name.
|
||||
|
||||
// The control connection information needs to be defined as the seconds argument.
|
||||
// It could hold the string "NONE"... which means it uses a data connection only.
|
||||
std::string ssControlConnectString;
|
||||
if (argc > 1)
|
||||
{
|
||||
auto ssArgv = sdv::MakeUtf8String(argv[1]);
|
||||
if (ssArgv != "NONE")
|
||||
ssControlConnectString = Base64DecodePlainText(ssArgv);
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
|
||||
// The third argument could be either the data connection string or additional parameters.
|
||||
std::string ssDataConnectString;
|
||||
bool bForceTerminate = false;
|
||||
bool bLongLife = false;
|
||||
bool bServer = true;
|
||||
for (int i = 2; i < argc; i++)
|
||||
{
|
||||
auto ssArgv = sdv::MakeUtf8String(argv[i]);
|
||||
|
||||
// Check when forcefully termination is requested
|
||||
if (ssArgv == "FORCE_TERMINATE")
|
||||
{
|
||||
bForceTerminate = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for long life
|
||||
if (ssArgv == "LONG_LIFE")
|
||||
{
|
||||
bLongLife = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to convert to connect string (allowed only once)
|
||||
if (ssDataConnectString.empty())
|
||||
{
|
||||
ssDataConnectString = Base64DecodePlainText(ssArgv);
|
||||
bServer = ssDataConnectString.empty();
|
||||
continue;
|
||||
}
|
||||
|
||||
TRACE("Unexpected command line parameter: ", ssArgv);
|
||||
}
|
||||
|
||||
// At least one connection must be supplied - either control or data.
|
||||
if (ssControlConnectString.empty() && ssDataConnectString.empty()) return 10;
|
||||
|
||||
TRACE("Start of connect process as ", bServer ? "server" : "client", " (", getpid(), ")...");
|
||||
TRACE("Forced termination of app ", bServer ? "server" : "client", " process is ", bForceTerminate ? "enabled" : "disabled");
|
||||
TRACE("Long life of app ",bServer ? "server" : "client", " process is ", bLongLife ? "enabled" : "disabled");
|
||||
|
||||
// Create an control management channel (if required).
|
||||
CSharedMemChannelMgnt mgntControlMgntChannel;
|
||||
mgntControlMgntChannel.Initialize("");
|
||||
if (mgntControlMgntChannel.GetStatus() != sdv::EObjectStatus::initialized) return -11;
|
||||
mgntControlMgntChannel.SetOperationMode(sdv::EOperationMode::running);
|
||||
if (mgntControlMgntChannel.GetStatus() != sdv::EObjectStatus::running) return -11;
|
||||
|
||||
// Open the control channel endpoint
|
||||
sdv::TObjectPtr ptrControlConnection;
|
||||
CReceiver receiverControl;
|
||||
if (!ssControlConnectString.empty())
|
||||
{
|
||||
TRACE(bServer ? "Server" : "Client", ": Start of control channel connection...");
|
||||
ptrControlConnection = mgntControlMgntChannel.Access(ssControlConnectString);
|
||||
if (!ptrControlConnection) return -12;
|
||||
sdv::ipc::IConnect* pControlConnect = ptrControlConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
if (!pControlConnect) return -13;
|
||||
if (!pControlConnect->RegisterStatusEventCallback(&receiverControl)) return -20;
|
||||
if (!pControlConnect->AsyncConnect(&receiverControl)) return -14;
|
||||
if (!pControlConnect->WaitForConnection(250)) return -5; // Note: Connection should be possible within 250ms.
|
||||
if (pControlConnect->GetStatus() != sdv::ipc::EConnectStatus::connected) return -15;
|
||||
TRACE(bServer ? "Server" : "Client", ": Connected to control channel...");
|
||||
}
|
||||
|
||||
// Create the data management channel.
|
||||
CSharedMemChannelMgnt mgntDataMgntChannel;
|
||||
mgntDataMgntChannel.Initialize("");
|
||||
if (mgntDataMgntChannel.GetStatus() != sdv::EObjectStatus::initialized) return -1;
|
||||
mgntDataMgntChannel.SetOperationMode(sdv::EOperationMode::running);
|
||||
if (mgntDataMgntChannel.GetStatus() != sdv::EObjectStatus::running) return -1;
|
||||
|
||||
// If this is a server, create a data endpoint and communicate this endpoint over the control channel.
|
||||
// If not, open the data endpoint.
|
||||
sdv::TObjectPtr ptrDataConnection;
|
||||
if (bServer)
|
||||
{
|
||||
TRACE("Server: Create data endpoint...");
|
||||
sdv::ipc::SChannelEndpoint sEndpoint = mgntDataMgntChannel.CreateEndpoint(R"code(
|
||||
[IpcChannel]
|
||||
Size = 1024000
|
||||
)code");
|
||||
ptrDataConnection = sEndpoint.pConnection;
|
||||
sdv::pointer<uint8_t> ptrConnectInfoData;
|
||||
ptrConnectInfoData.resize(sEndpoint.ssConnectString.size());
|
||||
size_t n = 0;
|
||||
for (char c : sEndpoint.ssConnectString)
|
||||
ptrConnectInfoData[n++] = static_cast<uint8_t>(c);
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqData({ ptrConnectInfoData });
|
||||
sdv::ipc::IDataSend* pControlDataSend = ptrControlConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
if (!pControlDataSend) return -16;
|
||||
if (!pControlDataSend->SendData(seqData))
|
||||
return -17;
|
||||
TRACE("Server: Communicated data endpoint...");
|
||||
}
|
||||
else // Open the data endpoint
|
||||
{
|
||||
TRACE("Client: Accessed data endpoint...");
|
||||
ptrDataConnection = mgntDataMgntChannel.Access(ssDataConnectString);
|
||||
}
|
||||
if (!ptrDataConnection) return -2;
|
||||
|
||||
CReceiver receiverData;
|
||||
sdv::ipc::IDataSend* pDataSend = ptrDataConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
if (!pDataSend) return -2;
|
||||
receiverData.AssignSender(pDataSend);
|
||||
|
||||
// Establish the connection
|
||||
sdv::ipc::IConnect* pDataConnect = ptrDataConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
if (!pDataConnect) return -3;
|
||||
if (!pDataConnect->RegisterStatusEventCallback(&receiverData)) return -21;
|
||||
if (!pDataConnect->AsyncConnect(&receiverData)) return -4;
|
||||
if (!pDataConnect->WaitForConnection(10000)) return -5; // Note: Connection should be possible within 10000ms.
|
||||
if (pDataConnect->GetStatus() != sdv::ipc::EConnectStatus::connected) return -5;
|
||||
|
||||
TRACE("Connected to data channel... waiting for data exchange");
|
||||
|
||||
// Check for long life
|
||||
if (bLongLife)
|
||||
{
|
||||
// Wait until the sender doesn't send anything any more.
|
||||
receiverData.WaitForNoActivity();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Repeat data until disconnect occurrs (differentiate between forced and not forced to allow two apps to start at the
|
||||
// same time).
|
||||
receiverData.WaitUntilDisconnect(bForceTerminate ? 800 : 1600);
|
||||
std::cout << "App " << (bServer ? "server" : "client") << " connect process disconnecting..." << std::endl;
|
||||
}
|
||||
|
||||
if (bForceTerminate)
|
||||
{
|
||||
std::cout << "Forced termination of app " << (bServer ? "server" : "client") << " connect process..." << std::endl;
|
||||
#ifdef _MSC_VER
|
||||
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||
#endif
|
||||
std::_Exit(0); // Force exit without cleaning up...
|
||||
}
|
||||
|
||||
// Initiate shutdown
|
||||
ptrDataConnection.Clear();
|
||||
mgntDataMgntChannel.Shutdown();
|
||||
if (mgntDataMgntChannel.GetStatus() != sdv::EObjectStatus::destruction_pending) return -6;
|
||||
ptrControlConnection.Clear();
|
||||
mgntControlMgntChannel.Shutdown();
|
||||
if (mgntControlMgntChannel.GetStatus() != sdv::EObjectStatus::destruction_pending) return -16;
|
||||
|
||||
TRACE("Shutdown of app ", bServer ? "server" : "client", " connect process...");
|
||||
|
||||
appcontrol.Shutdown();
|
||||
|
||||
// Done....
|
||||
return 0;
|
||||
}
|
||||
123
tests/unit_tests/shared_mem/app_repeater.cpp
Normal file
123
tests/unit_tests/shared_mem/app_repeater.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
// App process providing repeating functionality
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include "pattern_gen.h"
|
||||
#include <support/mem_access.h>
|
||||
#include <support/app_control.h>
|
||||
#include <../global/base64.h>
|
||||
#include "../../../sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.cpp"
|
||||
|
||||
#if defined(_WIN32) && defined(_UNICODE)
|
||||
extern "C" int wmain(int argc, wchar_t* argv[])
|
||||
#else
|
||||
extern "C" int main(int argc, char* argv[])
|
||||
#endif
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::cout << "Missing connection string..." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sdv::app::CAppControl appcontrol;
|
||||
if (!appcontrol.Startup("")) return -1;
|
||||
appcontrol.SetRunningMode();
|
||||
|
||||
std::string logName("appRepeater.log");
|
||||
|
||||
// Create a connection string from the command line arguments separated by spaces. Skip the first argument, since it holds the
|
||||
// module file name.
|
||||
|
||||
// The connection information needs to be defined as the seconds argument.
|
||||
std::string ssConnectString;
|
||||
if (argc > 1)
|
||||
ssConnectString = Base64DecodePlainText(sdv::MakeUtf8String(argv[1]));
|
||||
|
||||
// The "SURROGATE_TRANSMITTER" argument, if provided, must be provided as third argument.
|
||||
bool bTransmitter = argc > 2 && sdv::MakeUtf8String(argv[2]) == "SURROGATE_TRANSMITTER";
|
||||
if (bTransmitter)
|
||||
{
|
||||
logName = "AppTransmitter.log";
|
||||
std::cout << "Start of transmitter process..." << std::endl;
|
||||
} else
|
||||
std::cout << "Start of repeater process..." << std::endl;
|
||||
|
||||
std::ofstream log(logName.c_str(), std::ios::trunc);
|
||||
log << "Start of app process..." << std::endl;
|
||||
|
||||
CSharedMemBufferRx bufferRX(ssConnectString);
|
||||
CSharedMemBufferTx bufferTX(ssConnectString);
|
||||
if (!bufferRX.IsValid() || !bufferTX.IsValid())
|
||||
{
|
||||
log << "Invalid connection string..." << std::endl;
|
||||
log << "Connection string: " << std::endl << ssConnectString << std::endl;
|
||||
log << "RX Error: " << bufferRX.GetError() << std::endl;
|
||||
log << "TX Error: " << bufferTX.GetError() << std::endl;
|
||||
std::cout << "Error app process..." << std::endl;
|
||||
std::cout << "Process param: " << sdv::MakeUtf8String(argv[1]) << std::endl;
|
||||
std::cout << " Connection string: " << std::endl << ssConnectString << std::endl;
|
||||
std::cout << " RX Error: " << bufferRX.GetError() << std::endl;
|
||||
std::cout << " TX Error: " << bufferTX.GetError() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bTransmitter)
|
||||
{
|
||||
log << "App process acts like transmitter of the pattern:\n" << std::endl;
|
||||
|
||||
CPatternReceiver pattern_inspector(bufferRX);
|
||||
CPatternSender pattern_generator(bufferTX);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
log << "Pattern generator " << pattern_generator.GetCycleCnt() << " cycles..." << std::endl;
|
||||
log << "Pattern generator " << pattern_generator.GetByteCnt() << " bytes..." << std::endl;
|
||||
log << "Pattern inspector " << pattern_inspector.GetCycleCnt() << " cycles..." << std::endl;
|
||||
log << "Pattern inspector " << pattern_inspector.GetByteCnt() << " bytes..." << std::endl;
|
||||
|
||||
if (!pattern_generator.GetByteCnt() && !pattern_inspector.GetByteCnt() && pattern_inspector.GetErrorCnt())
|
||||
{
|
||||
log << "Error: unexpected " << pattern_generator.GetByteCnt() << " generator bytes, " << pattern_inspector.GetErrorCnt() << " inspector errors,"
|
||||
<< pattern_inspector.GetByteCnt() << " inspector bytes." << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
CPatternRepeater pattern_repeater(bufferRX, bufferTX);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S * 1));
|
||||
|
||||
pattern_repeater.Shutdown();
|
||||
|
||||
log << "Processed " << pattern_repeater.GetCycleCnt() << " cycles..." << std::endl;
|
||||
log << "Processed " << pattern_repeater.GetPacketCnt() << " packets..." << std::endl;
|
||||
log << "Processed " << pattern_repeater.GetByteCnt() << " bytes..." << std::endl;
|
||||
log << "Detected " << pattern_repeater.GetErrorCnt() << " errors..." << std::endl;
|
||||
|
||||
if (!pattern_repeater.GetPacketCnt() && !pattern_repeater.GetByteCnt() && pattern_repeater.GetErrorCnt())
|
||||
{
|
||||
log << "Error: unexpected " << pattern_repeater.GetPacketCnt() << " packets, "
|
||||
<< pattern_repeater.GetByteCnt() << " bytes, " << pattern_repeater.GetErrorCnt() << " errors." << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Pattern repeater: " << pattern_repeater.GetCycleCnt() << " cyles, " << pattern_repeater.GetPacketCnt()
|
||||
<< " packets, " << pattern_repeater.GetByteCnt() << " bytes, " << pattern_repeater.GetErrorCnt()
|
||||
<< " errors..." << std::endl;
|
||||
}
|
||||
|
||||
appcontrol.Shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
585
tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp
Normal file
585
tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp
Normal file
@@ -0,0 +1,585 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/in_process_mem_buffer.h"
|
||||
#include <support/app_control.h>
|
||||
#include "pattern_gen.h"
|
||||
|
||||
TEST(InProcessMemoryBufferTest, Instantiate)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, TriggerTestRx)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
bool bShutdown = false;
|
||||
size_t nCorrectCnt = 0;
|
||||
std::condition_variable cvStart;
|
||||
std::mutex mtxStart;
|
||||
auto fnWaitForTrigger = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtxStart);
|
||||
lock.unlock();
|
||||
cvStart.notify_all();
|
||||
while (!bShutdown)
|
||||
{
|
||||
bool bResult = receiver.WaitForFreeSpace(200);
|
||||
if (bShutdown) break;
|
||||
if (bResult)
|
||||
nCorrectCnt++;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lockStart(mtxStart);
|
||||
std::thread thread(fnWaitForTrigger);
|
||||
cvStart.wait(lockStart);
|
||||
|
||||
for (size_t n = 0; n < 20; n++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
sender.TriggerDataReceive();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
// Let the buffer finish its sending.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
||||
|
||||
bShutdown = true;
|
||||
thread.join();
|
||||
|
||||
EXPECT_GE(nCorrectCnt, 20);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, TriggerTestTx)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
bool bShutdown = false;
|
||||
size_t nCorrectCnt = 0;
|
||||
std::condition_variable cvStart;
|
||||
std::mutex mtxStart;
|
||||
auto fnWaitForTrigger = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtxStart);
|
||||
lock.unlock();
|
||||
cvStart.notify_all();
|
||||
while (!bShutdown)
|
||||
{
|
||||
bool bResult = receiver.WaitForData(200);
|
||||
if (bShutdown) break;
|
||||
if (bResult)
|
||||
nCorrectCnt++;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lockStart(mtxStart);
|
||||
std::thread thread(fnWaitForTrigger);
|
||||
cvStart.wait(lockStart);
|
||||
|
||||
for (size_t n = 0; n < 20; n++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
sender.TriggerDataSend();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
// Let the buffer finish its sending.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
||||
|
||||
bShutdown = true;
|
||||
thread.join();
|
||||
|
||||
EXPECT_GE(nCorrectCnt, 20);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, TriggerTestRxTx)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
bool bShutdown = false;
|
||||
size_t nCorrectCnt = 0;
|
||||
std::condition_variable cvSenderStart, cvReceiverStart;
|
||||
std::mutex mtxReceiverStart;
|
||||
std::mutex mtxSenderStart;
|
||||
auto fnWaitForTriggerReceiver = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lockReceiver(mtxReceiverStart);
|
||||
lockReceiver.unlock();
|
||||
cvReceiverStart.notify_all();
|
||||
while (!bShutdown)
|
||||
{
|
||||
if (receiver.WaitForData(200))
|
||||
receiver.TriggerDataReceive();
|
||||
}
|
||||
};
|
||||
auto fnWaitForTriggerSender = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lockSender(mtxSenderStart);
|
||||
lockSender.unlock();
|
||||
cvSenderStart.notify_all();
|
||||
std::unique_lock<std::mutex> lockReceiver(mtxReceiverStart);
|
||||
cvReceiverStart.wait(lockReceiver);
|
||||
lockReceiver.unlock();
|
||||
while (!bShutdown)
|
||||
{
|
||||
bool bResult = sender.WaitForFreeSpace(200);
|
||||
if (bShutdown) break;
|
||||
if (bResult)
|
||||
nCorrectCnt++;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lockStartSender(mtxSenderStart);
|
||||
std::thread threadSender(fnWaitForTriggerSender);
|
||||
cvSenderStart.wait(lockStartSender);
|
||||
lockStartSender.unlock();
|
||||
std::unique_lock<std::mutex> lockStartReceiver(mtxReceiverStart);
|
||||
std::thread threadReceiver(fnWaitForTriggerReceiver);
|
||||
cvReceiverStart.wait(lockStartReceiver);
|
||||
lockStartReceiver.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(25)); // Needed for the threads to enter their loop.
|
||||
for (size_t n = 0; n < 20; n++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
sender.TriggerDataSend();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
// Let the buffer finish its sending.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
||||
|
||||
bShutdown = true;
|
||||
threadSender.join();
|
||||
threadReceiver.join();
|
||||
|
||||
EXPECT_GE(nCorrectCnt, 20);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, SimpleSynchronousWriteRead)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 6));
|
||||
auto optRxPacket = receiver.TryRead();
|
||||
ASSERT_TRUE(optRxPacket);
|
||||
EXPECT_EQ(strcmp(optRxPacket->GetData<char>(), "HELLO"), 0);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, ReadWithoutSending)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
auto optRxPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, RequestReadPacketSize)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 5));
|
||||
|
||||
auto optRxPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
EXPECT_EQ(optRxPacket->GetSize(), 5u);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, FragmentRead)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 6));
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO2", 7));
|
||||
|
||||
auto optRxPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
ASSERT_EQ(optRxPacket->GetSize(), 6u);
|
||||
EXPECT_EQ(strcmp(optRxPacket->GetData<char>(), "HELLO"), 0);
|
||||
|
||||
optRxPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
ASSERT_EQ(optRxPacket->GetSize(), 7u);
|
||||
EXPECT_EQ(strcmp(optRxPacket->GetData<char>(), "HELLO2"), 0);
|
||||
|
||||
optRxPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, BufferBoundary)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender(256);
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
// The buffer header has 16 bytes
|
||||
// Each allocation is 8 bytes header, 6 bytes data and 2 bytes alignment
|
||||
for (int32_t iIndex = 0; iIndex < 14; iIndex++)
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 6));
|
||||
EXPECT_FALSE(sender.TryWrite("HELLO", 6));
|
||||
|
||||
// Read packets again
|
||||
std::optional<CAccessorRxPacket> optRxPacket;
|
||||
for (int32_t iIndex = 0; iIndex < 14; iIndex++)
|
||||
{
|
||||
optRxPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
ASSERT_EQ(optRxPacket->GetSize(), 6u);
|
||||
EXPECT_EQ(strcmp(optRxPacket->GetData<char>(), "HELLO"), 0);
|
||||
optRxPacket->Accept();
|
||||
}
|
||||
optRxPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
|
||||
// Try storing a large packet
|
||||
char szLargeText[256] = {};
|
||||
EXPECT_FALSE(sender.TryWrite(szLargeText, 241));
|
||||
|
||||
// Loop 100 times storing a 100 byte packet
|
||||
for (int32_t iIndex = 0; iIndex < 100; iIndex++)
|
||||
{
|
||||
EXPECT_TRUE(sender.TryWrite(szLargeText, 100));
|
||||
optRxPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
EXPECT_EQ(optRxPacket->GetSize(), 100u);
|
||||
optRxPacket->Accept();
|
||||
}
|
||||
|
||||
// Loop 1000 times storing a 10 byte packet
|
||||
for (int32_t iIndex = 0; iIndex < 1000; iIndex++)
|
||||
{
|
||||
EXPECT_TRUE(sender.TryWrite(szLargeText, 10));
|
||||
optRxPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
EXPECT_EQ(optRxPacket->GetSize(), 10u);
|
||||
optRxPacket->Accept();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, ReserveCommitAccessReleaseNonChronologicalOrder)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender(256);
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
// Reserve buffers for strings
|
||||
// The buffer header has 16 bytes
|
||||
// Each allocation is 8 bytes header, 5 bytes data and 3 bytes alignment
|
||||
CAccessorTxPacket rgTxPackets[32] = {};
|
||||
for (int32_t iIndex = 0; iIndex < 15; iIndex++)
|
||||
{
|
||||
auto optTxPacket = sender.Reserve(5);
|
||||
if (iIndex == 14) // 14th allocation should fail (buffer full)
|
||||
{
|
||||
EXPECT_FALSE(optTxPacket);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(optTxPacket);
|
||||
rgTxPackets[iIndex] = std::move(*optTxPacket);
|
||||
EXPECT_NE(rgTxPackets[iIndex].GetDataPtr(), nullptr);
|
||||
if (rgTxPackets[iIndex])
|
||||
strcpy(rgTxPackets[iIndex].GetDataPtr<char>(), std::to_string(iIndex).c_str());
|
||||
}
|
||||
|
||||
// Reading should fail, nothing is committed
|
||||
auto optRxPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
|
||||
// Commit the buffers in reverse order
|
||||
for (int32_t iIndex = 13; iIndex >= 0; iIndex--)
|
||||
{
|
||||
rgTxPackets[iIndex].Commit();
|
||||
|
||||
// Reading succeeds only after the index equals 0 (being the first entry).
|
||||
optRxPacket = receiver.TryRead();
|
||||
if (iIndex == 0)
|
||||
{
|
||||
// The text should contain the number 0
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
EXPECT_NE(optRxPacket->GetData(), nullptr);
|
||||
EXPECT_EQ(std::to_string(0), optRxPacket->GetData<char>());
|
||||
optRxPacket->Accept();
|
||||
}
|
||||
else
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
}
|
||||
|
||||
// Since one packet was read, only one can be written again
|
||||
for (int32_t iIndex = 14; iIndex < 16; iIndex++)
|
||||
{
|
||||
auto optTxPacket = sender.Reserve(5);
|
||||
if (iIndex >= 15) // 15th allocation should fail (buffer full)
|
||||
{
|
||||
EXPECT_FALSE(optTxPacket);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(optTxPacket);
|
||||
rgTxPackets[iIndex] = std::move(*optTxPacket);
|
||||
EXPECT_NE(rgTxPackets[iIndex].GetDataPtr(), nullptr);
|
||||
if (rgTxPackets[iIndex])
|
||||
strcpy(rgTxPackets[iIndex].GetDataPtr<char>(), std::to_string(iIndex).c_str());
|
||||
rgTxPackets[iIndex].Commit();
|
||||
}
|
||||
|
||||
// Access the packets
|
||||
CAccessorRxPacket rgRxPackets[32];
|
||||
for (int32_t iIndex = 1; iIndex < 16; iIndex++)
|
||||
{
|
||||
// 13 allocations are stored; the allocation with index 15 should fail
|
||||
optRxPacket = receiver.TryRead();
|
||||
if (iIndex == 15)
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
else
|
||||
{
|
||||
rgRxPackets[iIndex] = std::move(CAccessorRxPacket(std::move(*optRxPacket)));
|
||||
EXPECT_TRUE(rgRxPackets[iIndex]);
|
||||
EXPECT_NE(rgRxPackets[iIndex].GetData(), nullptr);
|
||||
EXPECT_NE(rgRxPackets[iIndex].GetSize(), 0u);
|
||||
}
|
||||
if (rgRxPackets[iIndex])
|
||||
{
|
||||
EXPECT_EQ(std::to_string(iIndex), rgRxPackets[iIndex].GetData<char>());
|
||||
}
|
||||
}
|
||||
|
||||
// It should not be possible to reserve another packet
|
||||
EXPECT_FALSE(sender.Reserve(5));
|
||||
|
||||
// Release the read packets in reverse order. Only after the packet with the first index has been released it will be possible
|
||||
// to write packets again.
|
||||
for (int32_t iIndex = 14; iIndex >= 1; iIndex--)
|
||||
{
|
||||
rgRxPackets[iIndex].Accept();
|
||||
|
||||
if (iIndex == 1)
|
||||
EXPECT_TRUE(sender.Reserve(5));
|
||||
else
|
||||
EXPECT_FALSE(sender.Reserve(5));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, SendReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
CPatternReceiver pattern_inspector(receiver);
|
||||
CPatternSender pattern_generator(sender);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, DelayedSendReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
CPatternReceiver pattern_inspector(receiver);
|
||||
CPatternSender pattern_generator(sender, 10);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, SendDelayedReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CInProcMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CInProcMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
CPatternReceiver pattern_inspector(receiver, 10);
|
||||
CPatternSender pattern_generator(sender);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
}
|
||||
|
||||
TEST(InProcessMemoryBufferTest, SendRepeatReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
// The first process creates a sender and receiver
|
||||
CInProcMemBufferTx bufferTX;
|
||||
EXPECT_TRUE(bufferTX.IsValid());
|
||||
CInProcMemBufferRx bufferRX;
|
||||
EXPECT_TRUE(bufferRX.IsValid());
|
||||
|
||||
// The connection string containing the RX and TX strings for the repeater
|
||||
std::string ssConnectionString = bufferTX.GetConnectionString() + "\n" + bufferRX.GetConnectionString();
|
||||
|
||||
// The repeater process creates a receiver and sender
|
||||
CInProcMemBufferRx bufferRepeaterRX(ssConnectionString);
|
||||
EXPECT_TRUE(bufferRepeaterRX.IsValid());
|
||||
CInProcMemBufferTx bufferRepeaterTX(ssConnectionString);
|
||||
EXPECT_TRUE(bufferRepeaterTX.IsValid());
|
||||
|
||||
// Connect the pattern generator and inspector
|
||||
CPatternReceiver pattern_inspector(bufferRX);
|
||||
CPatternRepeater pattern_repeater(bufferRepeaterRX, bufferRepeaterTX);
|
||||
CPatternSender pattern_generator(bufferTX);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_repeater.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
std::cout << "Pattern repeater: " << pattern_repeater.GetCycleCnt() << " cyles, " << pattern_repeater.GetPacketCnt()
|
||||
<< " packets, " << pattern_repeater.GetByteCnt() << " bytes, " << pattern_repeater.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_repeater.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_repeater.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_repeater.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_repeater.GetByteCnt(), 0ull);
|
||||
}
|
||||
35
tests/unit_tests/shared_mem/main.cpp
Normal file
35
tests/unit_tests/shared_mem/main.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "../../../global/process_watchdog.h"
|
||||
#define TIME_TRACKING
|
||||
#include "../../../sdv_services/ipc_shared_mem/connection.cpp"
|
||||
#include "../../../sdv_services/ipc_shared_mem/channel_mgnt.cpp"
|
||||
#include "../../../sdv_services/ipc_shared_mem/watchdog.cpp"
|
||||
#include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.cpp"
|
||||
|
||||
/**
|
||||
* @brief Load support modules to publish the needed services.
|
||||
*/
|
||||
void LoadSupportServices()
|
||||
{
|
||||
// Load the IPC modules
|
||||
sdv::core::IModuleControl* pModuleControl = sdv::core::GetObject<sdv::core::IModuleControl>("ModuleControlService");
|
||||
ASSERT_NE(pModuleControl, nullptr);
|
||||
EXPECT_NE(pModuleControl->Load("process_control.sdv"), 0u);
|
||||
|
||||
// Create the services
|
||||
sdv::core::IRepositoryControl* pRepositoryControl = sdv::core::GetObject<sdv::core::IRepositoryControl>("RepositoryService");
|
||||
ASSERT_NE(pRepositoryControl, nullptr);
|
||||
EXPECT_NE(pRepositoryControl->CreateObject("ProcessControlService", {}, {}), 0u);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(_UNICODE)
|
||||
extern "C" int wmain(int argc, wchar_t* argv[])
|
||||
#else
|
||||
extern "C" int main(int argc, char* argv[])
|
||||
#endif
|
||||
{
|
||||
CProcessWatchdog watchdog;
|
||||
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
171
tests/unit_tests/shared_mem/pattern_gen.cpp
Normal file
171
tests/unit_tests/shared_mem/pattern_gen.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include "pattern_gen.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.h"
|
||||
|
||||
CPatternSender::CPatternSender(CMemBufferAccessorTx& raccessorOut, uint32_t uiDelayMs /*= 0*/) :
|
||||
m_raccessorOut(raccessorOut), m_uiDelayMs(uiDelayMs)
|
||||
{
|
||||
// Start the thread
|
||||
m_thread = std::thread(&CPatternSender::Process, this);
|
||||
|
||||
// Wait for the thread to run
|
||||
while (!m_bStarted) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
void CPatternSender::Process()
|
||||
{
|
||||
m_bStarted = true;
|
||||
std::srand(static_cast<unsigned int>(std::time(0))); // use current time as seed for random generator
|
||||
uint8_t uiCounter = 0;
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
m_uiCycleCnt++;
|
||||
|
||||
// Allocate space for a random sized packet
|
||||
uint32_t uiLen = static_cast<uint32_t>(std::rand() % 8191);
|
||||
auto optPacket = m_raccessorOut.Reserve(uiLen);
|
||||
if (!optPacket)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
m_uiPacketCnt++;
|
||||
m_uiByteCnt += uiLen;
|
||||
|
||||
// Fill the packet
|
||||
for (uint32_t uiIndex = 0; uiIndex < uiLen; uiIndex++)
|
||||
optPacket->GetDataPtr()[uiIndex] = uiCounter++;
|
||||
|
||||
// Commit...
|
||||
optPacket->Commit();
|
||||
|
||||
// Sleep if necessary
|
||||
if (m_uiDelayMs)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(m_uiDelayMs));
|
||||
}
|
||||
}
|
||||
|
||||
CPatternReceiver::CPatternReceiver(CMemBufferAccessorRx& raccessorIn, uint32_t uiDelayMs /*= 0*/) :
|
||||
m_raccessorIn(raccessorIn),m_uiDelayMs(uiDelayMs)
|
||||
{
|
||||
// Start the thread
|
||||
m_thread = std::thread(& CPatternReceiver::Process, this);
|
||||
|
||||
// Wait for the thread to run
|
||||
while (!m_bStarted) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
void CPatternReceiver::Process()
|
||||
{
|
||||
m_bStarted = true;
|
||||
uint8_t uiCounter = 0;
|
||||
bool bInitial = true;
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
m_uiCycleCnt++;
|
||||
|
||||
// Check for a packet
|
||||
auto optPacket = m_raccessorIn.TryRead();
|
||||
if (!optPacket)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
const uint8_t* pData = optPacket->GetData();
|
||||
uint32_t uiLen = optPacket->GetSize();
|
||||
m_uiPacketCnt++;
|
||||
m_uiByteCnt += uiLen;
|
||||
|
||||
// The first byte of the first packet sets the counter
|
||||
if (bInitial)
|
||||
uiCounter = pData[0];
|
||||
bInitial = false;
|
||||
|
||||
// Check the counter values
|
||||
for (uint32_t uiIndex = 0; uiIndex < uiLen; uiIndex++)
|
||||
{
|
||||
if (uiCounter != pData[uiIndex])
|
||||
{
|
||||
m_uiErrorCnt++;
|
||||
uiCounter = pData[uiIndex];
|
||||
}
|
||||
uiCounter++; // Expecting new counter value
|
||||
}
|
||||
|
||||
// Release
|
||||
optPacket->Accept();
|
||||
|
||||
// Sleep if necessary
|
||||
if (m_uiDelayMs)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(m_uiDelayMs));
|
||||
}
|
||||
}
|
||||
|
||||
CPatternRepeater::CPatternRepeater(CMemBufferAccessorRx& raccessorIn, CMemBufferAccessorTx& raccessorOut, uint32_t uiDelayMs /*= 0*/) :
|
||||
m_raccessorIn(raccessorIn), m_raccessorOut(raccessorOut), m_uiDelayMs(uiDelayMs)
|
||||
{
|
||||
// Start the thread
|
||||
m_thread = std::thread(&CPatternRepeater::Process, this);
|
||||
|
||||
// Wait for the thread to run
|
||||
while (!m_bStarted) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
void CPatternRepeater::Process()
|
||||
{
|
||||
m_bStarted = true;
|
||||
uint8_t uiCounter = 0;
|
||||
bool bInitial = true;
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
m_uiCycleCnt++;
|
||||
|
||||
// Check for a packet
|
||||
auto optPacket = m_raccessorIn.TryRead();
|
||||
if (!optPacket)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
const uint8_t* pData = optPacket->GetData();
|
||||
uint32_t uiLen = optPacket->GetSize();
|
||||
m_uiPacketCnt++;
|
||||
m_uiByteCnt += uiLen;
|
||||
|
||||
// The first byte of the first packet sets the counter
|
||||
if (bInitial)
|
||||
uiCounter = pData[0];
|
||||
bInitial = false;
|
||||
|
||||
// Check the counter values
|
||||
for (uint32_t uiIndex = 0; uiIndex < uiLen; uiIndex++)
|
||||
{
|
||||
if (uiCounter != pData[uiIndex])
|
||||
{
|
||||
m_uiErrorCnt++;
|
||||
uiCounter = pData[uiIndex];
|
||||
}
|
||||
uiCounter++; // Expecting new counter value
|
||||
}
|
||||
|
||||
// Send away again
|
||||
while (!m_raccessorOut.TryWrite(pData, uiLen))
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
if (m_bShutdown)
|
||||
break;
|
||||
}
|
||||
|
||||
// Release
|
||||
optPacket->Accept();
|
||||
|
||||
// Sleep if necessary
|
||||
if (m_uiDelayMs)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(m_uiDelayMs));
|
||||
}
|
||||
}
|
||||
|
||||
144
tests/unit_tests/shared_mem/pattern_gen.h
Normal file
144
tests/unit_tests/shared_mem/pattern_gen.h
Normal file
@@ -0,0 +1,144 @@
|
||||
#ifndef PATTERN_GEN_H
|
||||
#define PATTERN_GEN_H
|
||||
|
||||
#include <thread>
|
||||
#include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.h"
|
||||
|
||||
/**
|
||||
* The pattern test time in seconds.
|
||||
*/
|
||||
#define PATTERN_TEST_TIME_S 2
|
||||
|
||||
class CPatternSender
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] raccessorOut Reference to the output accessor.
|
||||
* @param[in] uiDelayMs The amount of delay (in ms) between calls. If 0, there is no delay.
|
||||
*/
|
||||
CPatternSender(CMemBufferAccessorTx& raccessorOut, uint32_t uiDelayMs = 0);
|
||||
|
||||
/**
|
||||
* @brief Shutdown
|
||||
*/
|
||||
void Shutdown() { m_bShutdown = true; m_thread.join(); }
|
||||
|
||||
/**
|
||||
* @brief Processing thread function sending packets with variable length and incrementing counter for every byte.
|
||||
*/
|
||||
void Process();
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Statistics
|
||||
*/
|
||||
uint32_t GetCycleCnt() const { return m_uiCycleCnt; }
|
||||
uint32_t GetPacketCnt() const { return m_uiPacketCnt; }
|
||||
uint64_t GetByteCnt() const { return m_uiByteCnt; }
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
private:
|
||||
CMemBufferAccessorTx& m_raccessorOut; //!< Reference to the output accessor
|
||||
std::thread m_thread; //!< Processing thread
|
||||
bool m_bStarted = false; //!< Set by the thread when started.
|
||||
bool m_bShutdown = false; //!< When set, shutdown the thread.
|
||||
uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing.
|
||||
uint32_t m_uiCycleCnt = 0u; //!< Amount of packets
|
||||
uint32_t m_uiPacketCnt = 0u; //!< Amount of packets
|
||||
uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes
|
||||
};
|
||||
|
||||
class CPatternReceiver
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] raccessorIn Reference to the input accessor.
|
||||
* @param[in] uiDelayMs The amount of delay (in ms) between calls. If 0, there is no delay.
|
||||
*/
|
||||
CPatternReceiver(CMemBufferAccessorRx& raccessorIn, uint32_t uiDelayMs = 0);
|
||||
|
||||
/**
|
||||
* @brief Shutdown
|
||||
*/
|
||||
void Shutdown() { m_bShutdown = true; m_thread.join(); }
|
||||
|
||||
/**
|
||||
* @brief Processing thread function receiving packets of variable length and incrementing counter for every byte.
|
||||
*/
|
||||
void Process();
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Statistics
|
||||
*/
|
||||
uint32_t GetCycleCnt() const { return m_uiCycleCnt; }
|
||||
uint32_t GetErrorCnt() const { return m_uiErrorCnt; }
|
||||
uint32_t GetPacketCnt() const { return m_uiPacketCnt; }
|
||||
uint64_t GetByteCnt() const { return m_uiByteCnt; }
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
private:
|
||||
CMemBufferAccessorRx& m_raccessorIn; //!< Reference to the input accessor
|
||||
std::thread m_thread; //!< Processing thread
|
||||
bool m_bStarted = false; //!< Set by the thread when started.
|
||||
bool m_bShutdown = false; //!< When set, shutdown the thread.
|
||||
uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing.
|
||||
uint32_t m_uiCycleCnt = 0u; //!< Amount of packets
|
||||
uint32_t m_uiErrorCnt = 0u; //!< Amount of counter errors
|
||||
uint32_t m_uiPacketCnt = 0u; //!< Amount of packets
|
||||
uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes
|
||||
};
|
||||
|
||||
class CPatternRepeater
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] raccessorIn Reference to the input accessor.
|
||||
* @param[in] raccessorOut Reference to the output accessor.
|
||||
* @param[in] uiDelayMs The amount of delay (in ms) between calls. If 0, there is no delay.
|
||||
*/
|
||||
CPatternRepeater(CMemBufferAccessorRx& raccessorIn, CMemBufferAccessorTx& raccessorOut, uint32_t uiDelayMs = 0);
|
||||
|
||||
/**
|
||||
* @brief Shutdown
|
||||
*/
|
||||
void Shutdown() { m_bShutdown = true; m_thread.join(); }
|
||||
|
||||
/**
|
||||
* @brief Processing thread function receiving and dispatching packets of variable length.
|
||||
*/
|
||||
void Process();
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Statistics
|
||||
*/
|
||||
uint32_t GetCycleCnt() const { return m_uiCycleCnt; }
|
||||
uint32_t GetErrorCnt() const { return m_uiErrorCnt; }
|
||||
uint32_t GetPacketCnt() const { return m_uiPacketCnt; }
|
||||
uint64_t GetByteCnt() const { return m_uiByteCnt; }
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
private:
|
||||
CMemBufferAccessorRx& m_raccessorIn; //!< Reference to the input accessor
|
||||
CMemBufferAccessorTx& m_raccessorOut; //!< Reference to the output accessor
|
||||
std::thread m_thread; //!< Processing thread
|
||||
bool m_bStarted = false; //!< Set by the thread when started.
|
||||
bool m_bShutdown = false; //!< When set, shutdown the thread.
|
||||
uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing.
|
||||
uint32_t m_uiCycleCnt = 0u; //!< Amount of packets
|
||||
uint32_t m_uiErrorCnt = 0u; //!< Amount of counter errors
|
||||
uint32_t m_uiPacketCnt = 0u; //!< Amount of packets
|
||||
uint64_t m_uiByteCnt = 0ull; //!< Amount of bytes
|
||||
};
|
||||
|
||||
#endif // !defined(PATTERN_GEN_H)
|
||||
716
tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp
Normal file
716
tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp
Normal file
@@ -0,0 +1,716 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "gtest/gtest.h"
|
||||
#include "pattern_gen.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h"
|
||||
#include "../../include/logger_test_helper.h"
|
||||
#include <../global/base64.h>
|
||||
#include <support/sdv_core.h>
|
||||
#include <support/app_control.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/process.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(_WIN32)
|
||||
#include <unistd.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Load support modules to publish the needed services.
|
||||
*/
|
||||
void LoadSupportServices();
|
||||
|
||||
TEST(SharedMemoryBufferTest, CreateBuffer)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, TriggerTestRx)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
bool bShutdown = false;
|
||||
size_t nCorrectCnt = 0;
|
||||
std::condition_variable cvStart;
|
||||
std::mutex mtxStart;
|
||||
auto fnWaitForTrigger = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtxStart);
|
||||
lock.unlock();
|
||||
cvStart.notify_all();
|
||||
while (!bShutdown)
|
||||
{
|
||||
bool bResult = receiver.WaitForFreeSpace(200);
|
||||
if (bShutdown) break;
|
||||
if (bResult)
|
||||
nCorrectCnt++;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lockStart(mtxStart);
|
||||
std::thread thread(fnWaitForTrigger);
|
||||
cvStart.wait(lockStart);
|
||||
|
||||
for (size_t n = 0; n < 20; n++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
sender.TriggerDataReceive();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
// Let the buffer finish its sending.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
bShutdown = true;
|
||||
thread.join();
|
||||
|
||||
EXPECT_GE(nCorrectCnt, 20);
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, TriggerTestTx)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
bool bShutdown = false;
|
||||
size_t nCorrectCnt = 0;
|
||||
std::condition_variable cvStart;
|
||||
std::mutex mtxStart;
|
||||
auto fnWaitForTrigger = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtxStart);
|
||||
lock.unlock();
|
||||
cvStart.notify_all();
|
||||
while (!bShutdown)
|
||||
{
|
||||
bool bResult = receiver.WaitForData(200);
|
||||
if (bShutdown) break;
|
||||
if (bResult)
|
||||
nCorrectCnt++;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lockStart(mtxStart);
|
||||
std::thread thread(fnWaitForTrigger);
|
||||
cvStart.wait(lockStart);
|
||||
|
||||
for (size_t n = 0; n < 20; n++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
sender.TriggerDataSend();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
// Let the buffer finish its sending.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
bShutdown = true;
|
||||
thread.join();
|
||||
|
||||
EXPECT_GE(nCorrectCnt, 20);
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, TriggerTestRxTx)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
bool bShutdown = false;
|
||||
size_t nCorrectCnt = 0;
|
||||
std::condition_variable cvSenderStart, cvReceiverStart;
|
||||
std::mutex mtxReceiverStart;
|
||||
std::mutex mtxSenderStart;
|
||||
auto fnWaitForTriggerReceiver = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lockReceiver(mtxReceiverStart);
|
||||
lockReceiver.unlock();
|
||||
cvReceiverStart.notify_all();
|
||||
while (!bShutdown)
|
||||
{
|
||||
if (receiver.WaitForData(200))
|
||||
receiver.TriggerDataReceive();
|
||||
}
|
||||
};
|
||||
auto fnWaitForTriggerSender = [&]()
|
||||
{
|
||||
std::unique_lock<std::mutex> lockSender(mtxSenderStart);
|
||||
lockSender.unlock();
|
||||
cvSenderStart.notify_all();
|
||||
std::unique_lock<std::mutex> lockReceiver(mtxReceiverStart);
|
||||
cvReceiverStart.wait(lockReceiver);
|
||||
lockReceiver.unlock();
|
||||
while (!bShutdown)
|
||||
{
|
||||
bool bResult = sender.WaitForFreeSpace(200);
|
||||
if (bShutdown) break;
|
||||
if (bResult)
|
||||
nCorrectCnt++;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lockStartSender(mtxSenderStart);
|
||||
std::thread threadSender(fnWaitForTriggerSender);
|
||||
cvSenderStart.wait(lockStartSender);
|
||||
lockStartSender.unlock();
|
||||
std::unique_lock<std::mutex> lockStartReceiver(mtxReceiverStart);
|
||||
std::thread threadReceiver(fnWaitForTriggerReceiver);
|
||||
cvReceiverStart.wait(lockStartReceiver);
|
||||
lockStartReceiver.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(25)); // Needed for the threads to enter their loop.
|
||||
for (size_t n = 0; n < 200; n++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
sender.TriggerDataSend();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
// Let the buffer finish its sending.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
bShutdown = true;
|
||||
threadSender.join();
|
||||
threadReceiver.join();
|
||||
|
||||
EXPECT_GE(nCorrectCnt, 200);
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, SimpleSynchronousWriteRead)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 6));
|
||||
auto optPacket = receiver.TryRead();
|
||||
ASSERT_TRUE(optPacket);
|
||||
EXPECT_EQ(strcmp(optPacket->GetData<char>(), "HELLO"), 0);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, ReadWithoutSending)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
auto optPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optPacket);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, RequestReadPacketSize)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 5));
|
||||
|
||||
auto optPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optPacket);
|
||||
EXPECT_EQ(optPacket->GetSize(), 5u);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, FragmentRead)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 6));
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO2", 7));
|
||||
|
||||
auto optPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optPacket);
|
||||
ASSERT_EQ(optPacket->GetSize(), 6u);
|
||||
EXPECT_EQ(strcmp(optPacket->GetData<char>(), "HELLO"), 0);
|
||||
|
||||
optPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optPacket);
|
||||
ASSERT_EQ(optPacket->GetSize(), 7u);
|
||||
EXPECT_EQ(strcmp(optPacket->GetData<char>(), "HELLO2"), 0);
|
||||
|
||||
optPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optPacket);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, BufferBoundary)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender(256);
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
// The buffer header has 16 bytes
|
||||
// Each allocation is 8 bytes header, 6 bytes data and 2 bytes alignment
|
||||
for (int32_t iIndex = 0; iIndex < 14; iIndex++)
|
||||
EXPECT_TRUE(sender.TryWrite("HELLO", 6));
|
||||
EXPECT_FALSE(sender.TryWrite("HELLO", 6));
|
||||
|
||||
// Read packets again
|
||||
std::optional<CAccessorRxPacket> optPacket;
|
||||
for (int32_t iIndex = 0; iIndex < 14; iIndex++)
|
||||
{
|
||||
optPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optPacket);
|
||||
ASSERT_EQ(optPacket->GetSize(), 6u);
|
||||
EXPECT_EQ(strcmp(optPacket->GetData<char>(), "HELLO"), 0);
|
||||
optPacket->Accept();
|
||||
}
|
||||
optPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optPacket);
|
||||
|
||||
// Try storing a large packet
|
||||
char szLargeText[256] = {};
|
||||
EXPECT_FALSE(sender.TryWrite(szLargeText, 241));
|
||||
|
||||
// Loop 100 times storing a 100 byte packet
|
||||
for (int32_t iIndex = 0; iIndex < 100; iIndex++)
|
||||
{
|
||||
EXPECT_TRUE(sender.TryWrite(szLargeText, 100));
|
||||
optPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optPacket);
|
||||
EXPECT_EQ(optPacket->GetSize(), 100u);
|
||||
optPacket->Accept();
|
||||
}
|
||||
|
||||
// Loop 1000 times storing a 10 byte packet
|
||||
for (int32_t iIndex = 0; iIndex < 1000; iIndex++)
|
||||
{
|
||||
EXPECT_TRUE(sender.TryWrite(szLargeText, 10));
|
||||
optPacket = receiver.TryRead();
|
||||
EXPECT_TRUE(optPacket);
|
||||
EXPECT_EQ(optPacket->GetSize(), 10u);
|
||||
optPacket->Accept();
|
||||
}
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, ReserveCommitAccessReleaseNonChronologicalOrder)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender(256);
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
// Reserve buffers for strings
|
||||
// The buffer header has 16 bytes
|
||||
// Each allocation is 8 bytes header, 5 bytes data and 3 bytes alignment
|
||||
CAccessorTxPacket rgTxPackets[32] = {};
|
||||
for (int32_t iIndex = 0; iIndex < 15; iIndex++)
|
||||
{
|
||||
auto optTxPacket = sender.Reserve(5);
|
||||
if (iIndex == 14) // 14th allocation should fail (buffer full)
|
||||
{
|
||||
EXPECT_FALSE(optTxPacket);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(optTxPacket);
|
||||
rgTxPackets[iIndex] = std::move(*optTxPacket);
|
||||
EXPECT_NE(rgTxPackets[iIndex].GetDataPtr(), nullptr);
|
||||
if (rgTxPackets[iIndex])
|
||||
strcpy(rgTxPackets[iIndex].GetDataPtr<char>(), std::to_string(iIndex).c_str());
|
||||
}
|
||||
|
||||
// Reading should fail, nothing is committed
|
||||
auto optRxPacket = receiver.TryRead();
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
|
||||
// Commit the buffers in reverse order
|
||||
for (int32_t iIndex = 13; iIndex >= 0; iIndex--)
|
||||
{
|
||||
rgTxPackets[iIndex].Commit();
|
||||
|
||||
// Reading succeeds only after the index equals 0 (being the first entry).
|
||||
optRxPacket = receiver.TryRead();
|
||||
if (iIndex == 0)
|
||||
{
|
||||
// The text should contain the number 0
|
||||
EXPECT_TRUE(optRxPacket);
|
||||
EXPECT_NE(optRxPacket->GetData(), nullptr);
|
||||
EXPECT_EQ(std::to_string(0), optRxPacket->GetData<char>());
|
||||
optRxPacket->Accept();
|
||||
}
|
||||
else
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
}
|
||||
|
||||
// Since one packet was read, only one can be written again
|
||||
for (int32_t iIndex = 14; iIndex < 16; iIndex++)
|
||||
{
|
||||
auto optTxPacket = sender.Reserve(5);
|
||||
if (iIndex >= 15) // 15th allocation should fail (buffer full)
|
||||
{
|
||||
EXPECT_FALSE(optTxPacket);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(optTxPacket);
|
||||
rgTxPackets[iIndex] = std::move(*optTxPacket);
|
||||
EXPECT_NE(rgTxPackets[iIndex].GetDataPtr(), nullptr);
|
||||
if (rgTxPackets[iIndex])
|
||||
strcpy(rgTxPackets[iIndex].GetDataPtr<char>(), std::to_string(iIndex).c_str());
|
||||
rgTxPackets[iIndex].Commit();
|
||||
}
|
||||
|
||||
// Access the packets
|
||||
CAccessorRxPacket rgRxPackets[32];
|
||||
for (int32_t iIndex = 1; iIndex < 16; iIndex++)
|
||||
{
|
||||
// 13 allocations are stored; the allocation with index 15 should fail
|
||||
optRxPacket = receiver.TryRead();
|
||||
if (iIndex == 15)
|
||||
EXPECT_FALSE(optRxPacket);
|
||||
else
|
||||
{
|
||||
rgRxPackets[iIndex] = std::move(CAccessorRxPacket(std::move(*optRxPacket)));
|
||||
EXPECT_TRUE(rgRxPackets[iIndex]);
|
||||
EXPECT_NE(rgRxPackets[iIndex].GetData(), nullptr);
|
||||
EXPECT_NE(rgRxPackets[iIndex].GetSize(), 0u);
|
||||
}
|
||||
if (rgRxPackets[iIndex])
|
||||
{
|
||||
EXPECT_EQ(std::to_string(iIndex), rgRxPackets[iIndex].GetData<char>());
|
||||
}
|
||||
}
|
||||
|
||||
// It should not be possible to reserve another packet
|
||||
EXPECT_FALSE(sender.Reserve(5));
|
||||
|
||||
// Release the read packets in reverse order. Only after the packet with the first index has been released it will be possible
|
||||
// to write packets again.
|
||||
for (int32_t iIndex = 14; iIndex >= 1; iIndex--)
|
||||
{
|
||||
rgRxPackets[iIndex].Accept();
|
||||
|
||||
if (iIndex == 1)
|
||||
EXPECT_TRUE(sender.Reserve(5));
|
||||
else
|
||||
EXPECT_FALSE(sender.Reserve(5));
|
||||
}
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, SendReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
CPatternReceiver pattern_inspector(receiver);
|
||||
CPatternSender pattern_generator(sender);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, DelayedSendReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
CPatternReceiver pattern_inspector(receiver);
|
||||
CPatternSender pattern_generator(sender, 10);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, SendDelayedReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx sender;
|
||||
EXPECT_TRUE(sender.IsValid());
|
||||
|
||||
CSharedMemBufferRx receiver(sender.GetConnectionString());
|
||||
EXPECT_TRUE(receiver.IsValid());
|
||||
|
||||
CPatternReceiver pattern_inspector(receiver, 10);
|
||||
CPatternSender pattern_generator(sender);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, SendRepeatReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
|
||||
CSharedMemBufferTx bufferTX;
|
||||
EXPECT_TRUE(bufferTX.IsValid());
|
||||
CSharedMemBufferRx bufferRX;
|
||||
EXPECT_TRUE(bufferRX.IsValid());
|
||||
|
||||
// The connection string containing the RX and TX strings for the repeater
|
||||
std::string ssConnectionString = bufferTX.GetConnectionString() + "\n" + bufferRX.GetConnectionString();
|
||||
|
||||
CSharedMemBufferRx bufferRepeaterRX(ssConnectionString);
|
||||
EXPECT_TRUE(bufferRepeaterRX.IsValid());
|
||||
CSharedMemBufferTx bufferRepeaterTX(ssConnectionString);
|
||||
EXPECT_TRUE(bufferRepeaterTX.IsValid());
|
||||
|
||||
CPatternReceiver pattern_inspector(bufferRX);
|
||||
CPatternRepeater pattern_repeater(bufferRepeaterRX, bufferRepeaterTX);
|
||||
CPatternSender pattern_generator(bufferTX);
|
||||
|
||||
// Wait for 2 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_repeater.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
std::cout << "Pattern repeater: " << pattern_repeater.GetCycleCnt() << " cyles, " << pattern_repeater.GetPacketCnt()
|
||||
<< " packets, " << pattern_repeater.GetByteCnt() << " bytes, " << pattern_repeater.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_repeater.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_repeater.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_repeater.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_repeater.GetByteCnt(), 0ull);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, AppProcessSendRepeatReceivePattern)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(R"code([Application]
|
||||
Mode = "Essential")code"));
|
||||
LoadSupportServices();
|
||||
|
||||
CSharedMemBufferTx bufferTX;
|
||||
EXPECT_TRUE(bufferTX.IsValid());
|
||||
CSharedMemBufferRx bufferRX;
|
||||
EXPECT_TRUE(bufferRX.IsValid());
|
||||
|
||||
// Start process
|
||||
sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject<sdv::process::IProcessControl>("ProcessControlService");
|
||||
sdv::process::IProcessLifetime* pProcessLifetime = sdv::core::GetObject<sdv::process::IProcessLifetime>("ProcessControlService");
|
||||
ASSERT_NE(pProcessControl, nullptr);
|
||||
ASSERT_NE(pProcessLifetime, nullptr);
|
||||
sdv::sequence<sdv::u8string> seqArgs = {Base64EncodePlainText(bufferTX.GetConnectionString() + "\n" + bufferRX.GetConnectionString())};
|
||||
sdv::process::TProcessID tProcessID = pProcessControl->Execute("UnitTest_SharedMemTests_App_Repeater", seqArgs, sdv::process::EProcessRights::parent_rights);
|
||||
EXPECT_NE(tProcessID, 0u);
|
||||
|
||||
CPatternSender pattern_generator(bufferTX);
|
||||
CPatternReceiver pattern_inspector(bufferRX);
|
||||
|
||||
// Wait for 4 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S * 2));
|
||||
|
||||
// Shutdown
|
||||
pattern_generator.Shutdown();
|
||||
pattern_inspector.Shutdown();
|
||||
|
||||
// Wait for process termination
|
||||
pProcessLifetime->WaitForTerminate(tProcessID, 0xffffffff);
|
||||
|
||||
std::cout << "Pattern generator: " << pattern_generator.GetCycleCnt() << " cyles, " << pattern_generator.GetPacketCnt()
|
||||
<< " packets, " << pattern_generator.GetByteCnt() << " bytes" << std::endl;
|
||||
std::cout << "Pattern inspector: " << pattern_inspector.GetCycleCnt() << " cyles, " << pattern_inspector.GetPacketCnt()
|
||||
<< " packets, " << pattern_inspector.GetByteCnt() << " bytes, " << pattern_inspector.GetErrorCnt()
|
||||
<< " errors, " << std::endl;
|
||||
EXPECT_NE(pattern_generator.GetCycleCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_generator.GetByteCnt(), 0ull);
|
||||
EXPECT_NE(pattern_inspector.GetCycleCnt(), 0u);
|
||||
EXPECT_EQ(pattern_inspector.GetErrorCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetPacketCnt(), 0u);
|
||||
EXPECT_NE(pattern_inspector.GetByteCnt(), 0ull);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
|
||||
TEST(SharedMemoryBufferTest, SendRepeatReceivePatternBetweenTwoAppProcesses)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(R"code([Application]
|
||||
Mode = "Essential")code"));
|
||||
LoadSupportServices();
|
||||
|
||||
// test starts 2 app processes, one should be the sender of the pattern, the other is the repeater
|
||||
// the log file must not contain any error
|
||||
DeleteLoggerFile("appTransmitter.log");
|
||||
DeleteLoggerFile("appReceiver.log");
|
||||
|
||||
CSharedMemBufferTx bufferTransmitterTx;
|
||||
EXPECT_TRUE(bufferTransmitterTx.IsValid());
|
||||
CSharedMemBufferRx bufferTransmitterRx(bufferTransmitterTx.GetConnectionString());
|
||||
EXPECT_TRUE(bufferTransmitterRx.IsValid());
|
||||
CSharedMemBufferTx bufferRepeaterTx;
|
||||
EXPECT_TRUE(bufferRepeaterTx.IsValid());
|
||||
CSharedMemBufferRx bufferRepeaterRx(bufferRepeaterTx.GetConnectionString());
|
||||
EXPECT_TRUE(bufferRepeaterRx.IsValid());
|
||||
|
||||
// Start processes
|
||||
sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject<sdv::process::IProcessControl>("ProcessControlService");
|
||||
sdv::process::IProcessLifetime* pProcessLifetime = sdv::core::GetObject<sdv::process::IProcessLifetime>("ProcessControlService");
|
||||
ASSERT_NE(pProcessControl, nullptr);
|
||||
ASSERT_NE(pProcessLifetime, nullptr);
|
||||
sdv::sequence<sdv::u8string> seqArgs1 = {Base64EncodePlainText(bufferTransmitterRx.GetConnectionString() + "\n" + bufferRepeaterTx.GetConnectionString()), "SURROGATE_TRANSMITTER"};
|
||||
sdv::process::TProcessID tProcessID1 = pProcessControl->Execute("UnitTest_SharedMemTests_App_Repeater", seqArgs1, sdv::process::EProcessRights::parent_rights);
|
||||
EXPECT_NE(tProcessID1, 0u);
|
||||
sdv::sequence<sdv::u8string> seqArgs2 = {Base64EncodePlainText(bufferTransmitterTx.GetConnectionString() + "\n" + bufferRepeaterRx.GetConnectionString())};
|
||||
sdv::process::TProcessID tProcessID2 = pProcessControl->Execute("UnitTest_SharedMemTests_App_Repeater", seqArgs2, sdv::process::EProcessRights::parent_rights);
|
||||
EXPECT_NE(tProcessID2, 0u);
|
||||
|
||||
// Wait for 4 seconds
|
||||
std::this_thread::sleep_for(std::chrono::seconds(PATTERN_TEST_TIME_S * 3 ));
|
||||
|
||||
// Wait for process termination
|
||||
pProcessLifetime->WaitForTerminate(tProcessID1, 0xffffffff);
|
||||
pProcessLifetime->WaitForTerminate(tProcessID2, 0xffffffff);
|
||||
|
||||
appcontrol.Shutdown();
|
||||
}
|
||||
1244
tests/unit_tests/shared_mem/shared_mem_connect.cpp
Normal file
1244
tests/unit_tests/shared_mem/shared_mem_connect.cpp
Normal file
File diff suppressed because it is too large
Load Diff
782
tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp
Normal file
782
tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp
Normal file
@@ -0,0 +1,782 @@
|
||||
#include "gtest/gtest.h"
|
||||
#define TIME_TRACKING
|
||||
#include "../../../sdv_services/ipc_shared_mem/channel_mgnt.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/connection.h"
|
||||
#include "../../../sdv_services/ipc_shared_mem/watchdog.h"
|
||||
#include "../../include/logger_test_helper.h"
|
||||
#include <../global/base64.h>
|
||||
#include <support/sdv_core.h>
|
||||
#include <support/app_control.h>
|
||||
#include <support/sdv_test_macro.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
|
||||
/**
|
||||
* @brief Load support modules to publish the needed services.
|
||||
*/
|
||||
void LoadSupportServices();
|
||||
|
||||
/**
|
||||
* @brief Receiver helper class.
|
||||
*/
|
||||
class CLargeDataReceiver : public sdv::IInterfaceAccess, public sdv::ipc::IDataReceiveCallback, public sdv::ipc::IConnectEventCallback
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] bEnableEvent When set, enable the connection event callback interface.
|
||||
*/
|
||||
CLargeDataReceiver(bool bEnableEvent = false) : m_bEnableEvent(bEnableEvent),
|
||||
m_threadDecoupledSend(&CLargeDataReceiver::DecoupledSendThread, this)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CLargeDataReceiver()
|
||||
{
|
||||
m_bShutdown = true;
|
||||
if (m_threadDecoupledSend.joinable())
|
||||
m_threadDecoupledSend.join();
|
||||
}
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
|
||||
SDV_INTERFACE_CHECK_CONDITION(m_bEnableEvent)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Assign a sender interface.
|
||||
* @param[in] pSend Pointer to the sending interface.
|
||||
*/
|
||||
void AssignSender(sdv::ipc::IDataSend* pSend)
|
||||
{
|
||||
m_pSend = pSend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback to be called by the IPC connection when receiving a data packet. Overload
|
||||
* sdv::ipc::IDataReceiveCallback::ReceiveData.
|
||||
* @param[inout] seqData Sequence of data buffers to received. The sequence might be changed to optimize the communication
|
||||
* without having to copy the data.
|
||||
*/
|
||||
virtual void ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override
|
||||
{
|
||||
m_nCount++;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
|
||||
// Copy the data
|
||||
for (const sdv::pointer<uint8_t>& rptrData : seqData)
|
||||
m_queueDataCopy.push(rptrData);
|
||||
|
||||
// Store data into the queue for sending.
|
||||
m_queueDecoupledSend.push(std::move(seqData));
|
||||
m_cvDecoupledSend.notify_all();
|
||||
|
||||
// Send the same data back again (if needed).
|
||||
if (m_pSend)
|
||||
m_pSend->SendData(seqData);
|
||||
|
||||
m_cvReceived.notify_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait until the caller hasn't sent anything anymore for 1 second.
|
||||
*/
|
||||
void WaitForNoActivity(sdv::IInterfaceAccess* pSender, size_t nCount = 1, uint32_t uiTimeoutMs = 20000)
|
||||
{
|
||||
CConnection* pConnection = dynamic_cast<CConnection*>(pSender);
|
||||
double dTimeout = static_cast<double>(uiTimeoutMs) / 1000.0;
|
||||
if (dTimeout < 1.0) dTimeout = 1.0;
|
||||
|
||||
// Wait until there is no activity any more.
|
||||
std::chrono::high_resolution_clock::time_point tpTickSent = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::high_resolution_clock::time_point tpTickReceive = std::chrono::high_resolution_clock::now();
|
||||
while (m_eStatus != sdv::ipc::EConnectStatus::disconnected)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
m_cvReceived.wait_for(lock, std::chrono::milliseconds(50));
|
||||
|
||||
// Does the connection have a better time?
|
||||
if (pConnection) tpTickSent = pConnection->GetLastSentTime();
|
||||
if (pConnection) tpTickReceive = pConnection->GetLastReceiveTime();
|
||||
std::chrono::high_resolution_clock::time_point tpNow = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Amount reached
|
||||
if (GetReceiveCount() >= nCount) break;
|
||||
|
||||
// A duration of more than a second should not occur.
|
||||
if (std::chrono::duration<double>(tpNow - tpTickSent).count() > dTimeout &&
|
||||
std::chrono::duration<double>(tpNow - tpTickReceive).count() > dTimeout)
|
||||
break;
|
||||
}
|
||||
|
||||
TRACE("No new data for ", uiTimeoutMs, " milliseconds...");
|
||||
|
||||
if (pConnection)
|
||||
TRACE("Largest receive loop time is ", pConnection->GetLargestReceiveLoopDuration().count(), " seconds");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
|
||||
* @param[in] eConnectStatus The connection status.
|
||||
*/
|
||||
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override
|
||||
{
|
||||
switch (eConnectStatus)
|
||||
{
|
||||
case sdv::ipc::EConnectStatus::connection_error:
|
||||
m_bConnectError = true;
|
||||
break;
|
||||
case sdv::ipc::EConnectStatus::communication_error:
|
||||
m_bCommError = true;
|
||||
break;
|
||||
case sdv::ipc::EConnectStatus::disconnected_forced:
|
||||
m_bForcedDisconnect = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
m_eStatus = eConnectStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the received connection status.
|
||||
* @return The received connection status.
|
||||
*/
|
||||
sdv::ipc::EConnectStatus GetReceivedStatus() const { return m_eStatus; }
|
||||
|
||||
/**
|
||||
* @brief Has a connection error occurred?
|
||||
* @return Connection error flag.
|
||||
*/
|
||||
bool ConnectionErrorOccurred() const { return m_bConnectError; }
|
||||
|
||||
/**
|
||||
* @brief Has a communication error occurred?
|
||||
* @return Communication error flag.
|
||||
*/
|
||||
bool CommunicationbErrorOccurred() const { return m_bCommError; }
|
||||
|
||||
/**
|
||||
* @brief Was a disconnect triggered forcefully?
|
||||
* @return Forced disconnect flag.
|
||||
*/
|
||||
bool ForcedDisconnectOccurred() const { return m_bForcedDisconnect; }
|
||||
|
||||
/**
|
||||
* @brief Return the receive count.
|
||||
* @return The receive count.
|
||||
*/
|
||||
size_t GetReceiveCount() const
|
||||
{
|
||||
return m_nCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the data pointer count.
|
||||
* @return The data count.
|
||||
*/
|
||||
size_t GetDataCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
return m_queueDataCopy.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the received status event flags.
|
||||
*/
|
||||
void ResetStatusEvents()
|
||||
{
|
||||
m_bConnectError = false;
|
||||
m_bCommError = false;
|
||||
m_bForcedDisconnect = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the data
|
||||
* @return Copy of the data.
|
||||
*/
|
||||
sdv::pointer<uint8_t> GetData()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
if (m_queueDataCopy.empty()) return sdv::pointer<uint8_t>();
|
||||
sdv::pointer<uint8_t> ptrData = std::move(m_queueDataCopy.front());
|
||||
m_queueDataCopy.pop();
|
||||
return ptrData;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Decoupled send thread function.
|
||||
*/
|
||||
void DecoupledSendThread()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxData);
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
if (m_queueDecoupledSend.empty())
|
||||
{
|
||||
m_cvDecoupledSend.wait_for(lock, std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto seqData = std::move(m_queueDecoupledSend.front());
|
||||
m_queueDecoupledSend.pop();
|
||||
|
||||
if (m_pSend)
|
||||
m_pSend->SendData(seqData);
|
||||
}
|
||||
}
|
||||
|
||||
bool m_bEnableEvent = false; ///< When set, enable the event interface.
|
||||
sdv::ipc::IDataSend* m_pSend = nullptr; ///< Send interface to implement repeating function.
|
||||
mutable std::mutex m_mtxData; ///< Protect data access.
|
||||
std::queue<sdv::pointer<uint8_t>> m_queueDataCopy; ///< Copy of the data.
|
||||
sdv::ipc::EConnectStatus m_eStatus = sdv::ipc::EConnectStatus::uninitialized; ///< Current received status.
|
||||
bool m_bConnectError = false; ///< Connection error ocurred.
|
||||
bool m_bCommError = false; ///< Communication error occurred.
|
||||
bool m_bForcedDisconnect = false; ///< Force disconnect.
|
||||
size_t m_nCount = 0; ///< Receive counter.
|
||||
std::condition_variable m_cvReceived; ///< Receive event.
|
||||
std::thread m_threadDecoupledSend; ///< Decoupled send thread.
|
||||
std::queue<sdv::sequence<sdv::pointer<uint8_t>>> m_queueDecoupledSend; ///< Data queue for sending.
|
||||
std::condition_variable m_cvDecoupledSend; ///< Trigger decoupled sending.
|
||||
bool m_bShutdown = false; ///< Shutdown send thread.
|
||||
};
|
||||
|
||||
TEST(SharedMemChannelService, CommunicateOneLargeBlock)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(R"code(
|
||||
[Console]
|
||||
Report = "Silent"
|
||||
)code"));
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
CSharedMemChannelMgnt mgntServer, mgntClient;
|
||||
|
||||
// Create an endpoint.
|
||||
EXPECT_NO_THROW(mgntServer.Initialize(""));
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntServer.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::running);
|
||||
EXPECT_NO_THROW(mgntClient.Initialize("service = \"client\""));
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntClient.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::running);
|
||||
sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code(
|
||||
[IpcChannel]
|
||||
Size = 1024000
|
||||
)code");
|
||||
EXPECT_NE(sChannelEndpoint.pConnection, nullptr);
|
||||
EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty());
|
||||
|
||||
sdv::TObjectPtr ptrServerConnection(sChannelEndpoint.pConnection);
|
||||
sdv::TObjectPtr ptrClientConnection = mgntClient.Access(sChannelEndpoint.ssConnectString);
|
||||
EXPECT_TRUE(ptrServerConnection);
|
||||
EXPECT_TRUE(ptrClientConnection);
|
||||
|
||||
CLargeDataReceiver receiverServer, receiverClient;
|
||||
|
||||
// Get sending interfaces and assign the server send to the client to get a repeater function.
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqPattern;
|
||||
sdv::pointer<uint8_t> ptr;
|
||||
const size_t nCount = 10 * 1024 * 1024;
|
||||
ptr.resize(nCount * sizeof(uint32_t));
|
||||
uint32_t* pData = reinterpret_cast<uint32_t*>(ptr.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
pData[n] = static_cast<uint32_t>(n);
|
||||
seqPattern.push_back(ptr);
|
||||
sdv::ipc::IDataSend* pServerSend = ptrServerConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pServerSend, nullptr);
|
||||
sdv::ipc::IDataSend* pClientSend = ptrClientConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pClientSend, nullptr);
|
||||
receiverClient.AssignSender(pClientSend);
|
||||
|
||||
// Establish the server connection
|
||||
sdv::ipc::IConnect* pServerConnection = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pServerConnection, nullptr);
|
||||
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
|
||||
EXPECT_FALSE(pServerConnection->WaitForConnection(50)); // Note: 50ms will not get a connection.
|
||||
EXPECT_TRUE(pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
|
||||
pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
|
||||
|
||||
// Establish the client connection
|
||||
sdv::ipc::IConnect* pClientConnection = ptrClientConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pClientConnection, nullptr);
|
||||
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
|
||||
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
|
||||
EXPECT_TRUE(pServerConnection->WaitForConnection(50)); // Note: 50ms to also receive the connection at the server.
|
||||
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
appcontrol.SetRunningMode();
|
||||
|
||||
// Try send; should succeed, since connected
|
||||
EXPECT_TRUE(pServerSend->SendData(seqPattern));
|
||||
receiverServer.WaitForNoActivity(ptrServerConnection);
|
||||
auto ptrServerPattern = receiverServer.GetData();
|
||||
ASSERT_EQ(ptrServerPattern.size(), nCount * sizeof(uint32_t));
|
||||
auto ptrClientPattern = receiverClient.GetData();
|
||||
ASSERT_EQ(ptrClientPattern.size(), nCount * sizeof(uint32_t));
|
||||
pData = reinterpret_cast<uint32_t*>(ptrServerPattern.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(n));
|
||||
if (pData[n] != static_cast<uint32_t>(n)) break;
|
||||
}
|
||||
pData = reinterpret_cast<uint32_t*>(ptrClientPattern.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(n));
|
||||
if (pData[n] != static_cast<uint32_t>(n)) break;
|
||||
}
|
||||
|
||||
appcontrol.SetConfigMode();
|
||||
EXPECT_NO_THROW(ptrClientConnection.Clear());
|
||||
EXPECT_NO_THROW(ptrServerConnection.Clear());
|
||||
|
||||
EXPECT_NO_THROW(mgntServer.Shutdown());
|
||||
EXPECT_NO_THROW(mgntClient.Shutdown());
|
||||
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
}
|
||||
|
||||
TEST(SharedMemChannelService, CommunicateMultiLargeBlock)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
CSharedMemChannelMgnt mgntServer, mgntClient;
|
||||
|
||||
// Create an endpoint.
|
||||
EXPECT_NO_THROW(mgntServer.Initialize(""));
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntServer.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::running);
|
||||
EXPECT_NO_THROW(mgntClient.Initialize("service = \"client\""));
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntClient.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::running);
|
||||
sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code(
|
||||
[IpcChannel]
|
||||
Size = 1024000
|
||||
)code");
|
||||
EXPECT_NE(sChannelEndpoint.pConnection, nullptr);
|
||||
EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty());
|
||||
|
||||
sdv::TObjectPtr ptrServerConnection(sChannelEndpoint.pConnection);
|
||||
sdv::TObjectPtr ptrClientConnection = mgntClient.Access(sChannelEndpoint.ssConnectString);
|
||||
EXPECT_TRUE(ptrServerConnection);
|
||||
EXPECT_TRUE(ptrClientConnection);
|
||||
|
||||
CLargeDataReceiver receiverServer, receiverClient;
|
||||
|
||||
// Get sending interfaces and assign the server send to the client to get a repeater function.
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqPattern;
|
||||
sdv::pointer<uint8_t> ptr;
|
||||
const size_t nCount = 10 * 1024 * 1024;
|
||||
ptr.resize(nCount * sizeof(uint32_t));
|
||||
uint32_t* pData = reinterpret_cast<uint32_t*>(ptr.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
pData[n] = static_cast<uint32_t>(n);
|
||||
seqPattern.push_back(ptr);
|
||||
sdv::ipc::IDataSend* pServerSend = ptrServerConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pServerSend, nullptr);
|
||||
sdv::ipc::IDataSend* pClientSend = ptrClientConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pClientSend, nullptr);
|
||||
receiverClient.AssignSender(pClientSend);
|
||||
|
||||
// Establish the server connection
|
||||
sdv::ipc::IConnect* pServerConnection = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pServerConnection, nullptr);
|
||||
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
|
||||
EXPECT_FALSE(pServerConnection->WaitForConnection(50)); // Note: 50ms will not get a connection.
|
||||
EXPECT_TRUE(pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
|
||||
pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
|
||||
|
||||
// Establish the client connection
|
||||
sdv::ipc::IConnect* pClientConnection = ptrClientConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pClientConnection, nullptr);
|
||||
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
|
||||
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
|
||||
EXPECT_TRUE(pServerConnection->WaitForConnection(50)); // Note: 50ms to also receive the connection at the server.
|
||||
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
appcontrol.SetRunningMode();
|
||||
|
||||
// Try send; should succeed, since connected
|
||||
for (size_t nCnt = 0; nCnt < 30; nCnt++)
|
||||
EXPECT_TRUE(pServerSend->SendData(seqPattern));
|
||||
receiverServer.WaitForNoActivity(ptrServerConnection, 30);
|
||||
|
||||
// Check server
|
||||
size_t nCnt = 0;
|
||||
bool bCorrect = true;
|
||||
EXPECT_EQ(receiverServer.GetReceiveCount(), 30u);
|
||||
while (bCorrect)
|
||||
{
|
||||
auto ptrServerPattern = receiverServer.GetData();
|
||||
if (!ptrServerPattern) break;
|
||||
nCnt++;
|
||||
ASSERT_EQ(ptrServerPattern.size(), nCount * sizeof(uint32_t));
|
||||
pData = reinterpret_cast<uint32_t*>(ptrServerPattern.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(n));
|
||||
if (pData[n] != static_cast<uint32_t>(n))
|
||||
{
|
||||
bCorrect = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(nCnt, 30);
|
||||
|
||||
nCnt = 0;
|
||||
bCorrect = true;
|
||||
EXPECT_EQ(receiverClient.GetReceiveCount(), 30u);
|
||||
while (bCorrect)
|
||||
{
|
||||
auto ptrClientPattern = receiverClient.GetData();
|
||||
if (!ptrClientPattern) break;
|
||||
nCnt++;
|
||||
ASSERT_EQ(ptrClientPattern.size(), nCount * sizeof(uint32_t));
|
||||
pData = reinterpret_cast<uint32_t*>(ptrClientPattern.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(n));
|
||||
if (pData[n] != static_cast<uint32_t>(n))
|
||||
{
|
||||
bCorrect = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(nCnt, 30);
|
||||
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
EXPECT_NO_THROW(ptrClientConnection.Clear());
|
||||
EXPECT_NO_THROW(ptrServerConnection.Clear());
|
||||
|
||||
EXPECT_NO_THROW(mgntServer.Shutdown());
|
||||
EXPECT_NO_THROW(mgntClient.Shutdown());
|
||||
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
}
|
||||
|
||||
TEST(SharedMemChannelService, CommunicateFragmentedLargeBlock)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(""));
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
CSharedMemChannelMgnt mgntServer, mgntClient;
|
||||
|
||||
// Create an endpoint.
|
||||
EXPECT_NO_THROW(mgntServer.Initialize(""));
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntServer.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::running);
|
||||
EXPECT_NO_THROW(mgntClient.Initialize("service = \"client\""));
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntClient.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::running);
|
||||
sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code(
|
||||
[IpcChannel]
|
||||
Size = 1024000
|
||||
)code");
|
||||
EXPECT_NE(sChannelEndpoint.pConnection, nullptr);
|
||||
EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty());
|
||||
|
||||
sdv::TObjectPtr ptrServerConnection(sChannelEndpoint.pConnection);
|
||||
sdv::TObjectPtr ptrClientConnection = mgntClient.Access(sChannelEndpoint.ssConnectString);
|
||||
EXPECT_TRUE(ptrServerConnection);
|
||||
EXPECT_TRUE(ptrClientConnection);
|
||||
|
||||
CLargeDataReceiver receiverServer, receiverClient;
|
||||
|
||||
// Get sending interfaces and assign the server send to the client to get a repeater function.
|
||||
const size_t nCol = 10 * 1024;
|
||||
const size_t nRow = 512;
|
||||
size_t nCount = 0;
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqPattern(nRow);
|
||||
uint32_t* pData = nullptr;
|
||||
for (size_t nRowCnt = 0; nRowCnt < nRow; nRowCnt++)
|
||||
{
|
||||
seqPattern[nRowCnt].resize(nCol * sizeof(uint32_t));
|
||||
pData = reinterpret_cast<uint32_t*>(seqPattern[nRowCnt].get());
|
||||
for (size_t nColCnt = 0; nColCnt < nCol; nColCnt++)
|
||||
pData[nColCnt] = static_cast<uint32_t>(nCount++);
|
||||
}
|
||||
EXPECT_EQ(nCount, nRow * nCol);
|
||||
sdv::ipc::IDataSend* pServerSend = ptrServerConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pServerSend, nullptr);
|
||||
sdv::ipc::IDataSend* pClientSend = ptrClientConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pClientSend, nullptr);
|
||||
receiverClient.AssignSender(pClientSend);
|
||||
|
||||
// Establish the server connection
|
||||
sdv::ipc::IConnect* pServerConnection = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pServerConnection, nullptr);
|
||||
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
|
||||
EXPECT_FALSE(pServerConnection->WaitForConnection(50)); // Note: 50ms will not get a connection.
|
||||
EXPECT_TRUE(pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
|
||||
pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
|
||||
|
||||
// Establish the client connection
|
||||
sdv::ipc::IConnect* pClientConnection = ptrClientConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pClientConnection, nullptr);
|
||||
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
|
||||
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
|
||||
EXPECT_TRUE(pServerConnection->WaitForConnection(50)); // Note: 50ms to also receive the connection at the server.
|
||||
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
appcontrol.SetRunningMode();
|
||||
|
||||
// Try send; should succeed, since connected
|
||||
EXPECT_TRUE(pServerSend->SendData(seqPattern));
|
||||
receiverServer.WaitForNoActivity(ptrServerConnection);
|
||||
EXPECT_EQ(receiverServer.GetReceiveCount(), 1);
|
||||
EXPECT_EQ(receiverClient.GetReceiveCount(), 1u);
|
||||
EXPECT_EQ(receiverServer.GetDataCount(), 512u);
|
||||
EXPECT_EQ(receiverClient.GetDataCount(), 512u);
|
||||
size_t nCountServer = 0, nCountClient = 0;
|
||||
while (true)
|
||||
{
|
||||
auto ptrServerPattern = receiverServer.GetData();
|
||||
if (!ptrServerPattern) break;
|
||||
EXPECT_EQ(ptrServerPattern.size(), nCol * sizeof(uint32_t));
|
||||
auto ptrClientPattern = receiverClient.GetData();
|
||||
EXPECT_EQ(ptrClientPattern.size(), nCol * sizeof(uint32_t));
|
||||
if (!ptrClientPattern) break;
|
||||
|
||||
pData = reinterpret_cast<uint32_t*>(ptrServerPattern.get());
|
||||
for (size_t n = 0; n < ptrServerPattern.size() / sizeof(uint32_t); n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(nCountServer));
|
||||
if (pData[n] != static_cast<uint32_t>(nCountServer)) break;
|
||||
nCountServer++;
|
||||
}
|
||||
|
||||
pData = reinterpret_cast<uint32_t*>(ptrClientPattern.get());
|
||||
for (size_t n = 0; n < ptrClientPattern.size() / sizeof(uint32_t); n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(nCountClient));
|
||||
if (pData[n] != static_cast<uint32_t>(nCountClient)) break;
|
||||
nCountClient++;
|
||||
}
|
||||
}
|
||||
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
EXPECT_NO_THROW(ptrClientConnection.Clear());
|
||||
EXPECT_NO_THROW(ptrServerConnection.Clear());
|
||||
|
||||
EXPECT_NO_THROW(mgntServer.Shutdown());
|
||||
EXPECT_NO_THROW(mgntClient.Shutdown());
|
||||
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
EXPECT_EQ(mgntClient.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
}
|
||||
|
||||
TEST(SharedMemChannelService, AppCommunicateOneLargeBlock)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(R"code(
|
||||
[Application]
|
||||
Mode="Essential")code"));
|
||||
LoadSupportServices();
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
CSharedMemChannelMgnt mgntServer;
|
||||
|
||||
// Create an endpoint.
|
||||
EXPECT_NO_THROW(mgntServer.Initialize(""));
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntServer.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::running);
|
||||
sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code(
|
||||
[IpcChannel]
|
||||
Size = 1024000
|
||||
)code");
|
||||
EXPECT_NE(sChannelEndpoint.pConnection, nullptr);
|
||||
EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty());
|
||||
|
||||
sdv::TObjectPtr ptrServerConnection(sChannelEndpoint.pConnection);
|
||||
EXPECT_TRUE(ptrServerConnection);
|
||||
|
||||
// Get sending interfaces and assign the server send to the client to get a repeater function.
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqPattern;
|
||||
sdv::pointer<uint8_t> ptr;
|
||||
const size_t nCount = 10 * 1024 * 1024;
|
||||
ptr.resize(nCount * sizeof(uint32_t));
|
||||
uint32_t* pData = reinterpret_cast<uint32_t*>(ptr.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
pData[n] = static_cast<uint32_t>(n);
|
||||
seqPattern.push_back(ptr);
|
||||
|
||||
// Start process
|
||||
sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject<sdv::process::IProcessControl>("ProcessControlService");
|
||||
sdv::process::IProcessLifetime* pProcessLifetime = sdv::core::GetObject<sdv::process::IProcessLifetime>("ProcessControlService");
|
||||
ASSERT_NE(pProcessControl, nullptr);
|
||||
ASSERT_NE(pProcessLifetime, nullptr);
|
||||
sdv::sequence<sdv::u8string> seqArgs = {"NONE", Base64EncodePlainText(sChannelEndpoint.ssConnectString)};
|
||||
sdv::process::TProcessID tProcessID = pProcessControl->Execute("UnitTest_SharedMemTests_App_Connect", seqArgs, sdv::process::EProcessRights::parent_rights);
|
||||
EXPECT_NE(tProcessID, 0u);
|
||||
|
||||
CLargeDataReceiver receiverServer;
|
||||
|
||||
sdv::ipc::IDataSend* pServerSend = ptrServerConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pServerSend, nullptr);
|
||||
|
||||
// Establish the server connection
|
||||
sdv::ipc::IConnect* pServerConnection = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pServerConnection, nullptr);
|
||||
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
|
||||
EXPECT_TRUE(pServerConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
|
||||
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
TRACE("Connection estabished...");
|
||||
appcontrol.SetRunningMode();
|
||||
|
||||
// Try send; should succeed, since connected
|
||||
EXPECT_TRUE(pServerSend->SendData(seqPattern));
|
||||
receiverServer.WaitForNoActivity(ptrServerConnection);
|
||||
auto ptrServerPattern = receiverServer.GetData();
|
||||
ASSERT_EQ(ptrServerPattern.size(), nCount * sizeof(uint32_t));
|
||||
pData = reinterpret_cast<uint32_t*>(ptrServerPattern.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(n));
|
||||
if (pData[n] != static_cast<uint32_t>(n)) break;
|
||||
}
|
||||
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
EXPECT_NO_THROW(ptrServerConnection.Clear());
|
||||
|
||||
EXPECT_NO_THROW(mgntServer.Shutdown());
|
||||
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
|
||||
// Wait for process termination
|
||||
pProcessLifetime->WaitForTerminate(tProcessID, 0xffffffff);
|
||||
}
|
||||
|
||||
TEST(SharedMemChannelService, AppCommunicateMultiLargeBlock)
|
||||
{
|
||||
sdv::app::CAppControl appcontrol;
|
||||
ASSERT_TRUE(appcontrol.Startup(R"code(
|
||||
[Application]
|
||||
Mode="Essential")code"));
|
||||
LoadSupportServices();
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
CSharedMemChannelMgnt mgntServer;
|
||||
|
||||
// Create an endpoint.
|
||||
EXPECT_NO_THROW(mgntServer.Initialize(""));
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::initialized);
|
||||
mgntServer.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::running);
|
||||
sdv::ipc::SChannelEndpoint sChannelEndpoint = mgntServer.CreateEndpoint(R"code(
|
||||
[IpcChannel]
|
||||
Size = 1024000
|
||||
)code");
|
||||
EXPECT_NE(sChannelEndpoint.pConnection, nullptr);
|
||||
EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty());
|
||||
|
||||
sdv::TObjectPtr ptrServerConnection(sChannelEndpoint.pConnection);
|
||||
EXPECT_TRUE(ptrServerConnection);
|
||||
|
||||
// Get sending interfaces and assign the server send to the client to get a repeater function.
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqPattern;
|
||||
sdv::pointer<uint8_t> ptr;
|
||||
const size_t nCount = 10 * 1024 * 1024;
|
||||
ptr.resize(nCount * sizeof(uint32_t));
|
||||
uint32_t* pData = reinterpret_cast<uint32_t*>(ptr.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
pData[n] = static_cast<uint32_t>(n);
|
||||
seqPattern.push_back(ptr);
|
||||
|
||||
// Start process
|
||||
sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject<sdv::process::IProcessControl>("ProcessControlService");
|
||||
sdv::process::IProcessLifetime* pProcessLifetime = sdv::core::GetObject<sdv::process::IProcessLifetime>("ProcessControlService");
|
||||
ASSERT_NE(pProcessControl, nullptr);
|
||||
ASSERT_NE(pProcessLifetime, nullptr);
|
||||
sdv::sequence<sdv::u8string> seqArgs = {"NONE", Base64EncodePlainText(sChannelEndpoint.ssConnectString), "LONG_LIFE"};
|
||||
sdv::process::TProcessID tProcessID = pProcessControl->Execute("UnitTest_SharedMemTests_App_Connect", seqArgs, sdv::process::EProcessRights::parent_rights);
|
||||
EXPECT_NE(tProcessID, 0u);
|
||||
|
||||
CLargeDataReceiver receiverServer;
|
||||
|
||||
sdv::ipc::IDataSend* pServerSend = ptrServerConnection.GetInterface<sdv::ipc::IDataSend>();
|
||||
ASSERT_NE(pServerSend, nullptr);
|
||||
|
||||
// Establish the server connection
|
||||
sdv::ipc::IConnect* pServerConnection = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(pServerConnection, nullptr);
|
||||
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
|
||||
EXPECT_TRUE(pServerConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
|
||||
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
|
||||
TRACE("Connection estabished...");
|
||||
appcontrol.SetRunningMode();
|
||||
|
||||
// Try send; should succeed, since connected
|
||||
for (size_t nCnt = 0; nCnt < 30; nCnt++)
|
||||
{
|
||||
if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
|
||||
SDV_TIMING_EXPECT_EQ(pServerSend->SendData(seqPattern), true, sdv::TEST::WARNING_REDUCED);
|
||||
else
|
||||
SDV_TIMING_EXPECT_EQ(pServerSend->SendData(seqPattern), true, sdv::TEST::WARNING_ENABLED);
|
||||
}
|
||||
receiverServer.WaitForNoActivity(ptrServerConnection, 30);
|
||||
|
||||
// Check server
|
||||
size_t nCnt = 0;
|
||||
bool bCorrect = true;
|
||||
if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
|
||||
SDV_TIMING_EXPECT_EQ(receiverServer.GetReceiveCount(), 30u, sdv::TEST::WARNING_REDUCED);
|
||||
else
|
||||
SDV_TIMING_EXPECT_EQ(receiverServer.GetReceiveCount(), 30u, sdv::TEST::WARNING_ENABLED);
|
||||
while (bCorrect)
|
||||
{
|
||||
auto ptrServerPattern = receiverServer.GetData();
|
||||
if (!ptrServerPattern) break;
|
||||
nCnt++;
|
||||
ASSERT_EQ(ptrServerPattern.size(), nCount * sizeof(uint32_t));
|
||||
pData = reinterpret_cast<uint32_t*>(ptrServerPattern.get());
|
||||
for (size_t n = 0; n < nCount; n++)
|
||||
{
|
||||
EXPECT_EQ(pData[n], static_cast<uint32_t>(n));
|
||||
if (pData[n] != static_cast<uint32_t>(n))
|
||||
{
|
||||
bCorrect = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
|
||||
SDV_TIMING_EXPECT_EQ(nCnt, 30, sdv::TEST::WARNING_REDUCED);
|
||||
else
|
||||
SDV_TIMING_EXPECT_EQ(nCnt, 30, sdv::TEST::WARNING_ENABLED);
|
||||
|
||||
appcontrol.SetConfigMode();
|
||||
|
||||
EXPECT_NO_THROW(ptrServerConnection.Clear());
|
||||
|
||||
EXPECT_NO_THROW(mgntServer.Shutdown());
|
||||
|
||||
EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending);
|
||||
|
||||
// Wait for process termination
|
||||
pProcessLifetime->WaitForTerminate(tProcessID, 0xffffffff);
|
||||
}
|
||||
Reference in New Issue
Block a user