Precommit (#1)

* first commit

* cleanup
This commit is contained in:
tompzf
2025-11-04 13:28:06 +01:00
committed by GitHub
parent dba45dc636
commit 6ed4b1534e
898 changed files with 256340 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
# Only valid for Linux
if(UNIX)
project(UnitTestCanComSocket)
# Include directories
include_directories(../../export)
include_directories(${CMAKE_SOURCE_DIR}/../sdv_services/can_communication_socket_can)
# Add the executable
add_executable(UnitTest_CANComSocket
src/can_com_test_socket.cpp
${CMAKE_SOURCE_DIR}/sdv_services/can_communication_socket_can/can_com_sockets.cpp
)
# Link libraries
target_link_libraries(UnitTest_CANComSocket ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} rt GTest::GTest)
# Add test
add_test(NAME UnitTest_CANComSocket COMMAND UnitTest_CANComSocket)
# Execute the test
add_custom_command(TARGET UnitTest_CANComSocket POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$<TARGET_FILE:UnitTest_CANComSocket>" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unitTest_CANComSocket.xml
VERBATIM
)
# Copy the config files
file (COPY ${PROJECT_SOURCE_DIR}/config/test_can_socket.toml DESTINATION ${CMAKE_BINARY_DIR}/tests/bin/config)
# Build dependencies
add_dependencies(UnitTest_CANComSocket dependency_sdv_components)
add_dependencies(UnitTest_CANComSocket can_com_sockets)
endif()

View File

@@ -0,0 +1,77 @@
# Virtual CAN Interface Setup and Testing
This guide explains how to set up virtual CAN interfaces (`vcan0` and `vcan1`) on a Linux system and how to run tests using these interfaces.
## Prerequisites
- A Linux system with `iproute2` and `can-utils` installed.
- Root or sudo privileges.
## Setup Virtual CAN Interfaces
1. **Load the `vcan` kernel module:**
```bash
sudo modprobe vcan
```
2. **Create the virtual CAN interfaces:**
```bash
sudo ip link add dev vcan0 type vcan
sudo ip link add dev vcan1 type vcan
sudo ip link add dev vcan2 type vcan
sudo ip link add dev vcan3 type vcan
sudo ip link add dev vcan4 type vcan
```
3. **Bring the virtual CAN interfaces online:**
```bash
sudo ip link set up vcan0
sudo ip link set up vcan1
sudo ip link set up vcan2
sudo ip link set up vcan3
sudo ip link set up vcan4
```
4. **Verify the virtual CAN interfaces:**
```bash
ip addr | grep "vcan"
```
You should see `vcan0` and `vcan1` listed as available interfaces.
5. **Remove the virtual CAN interfaces:**
```bash
sudo ip link set down vcan0
sudo ip link delete vcan0
```
## Running Tests
1. **Listen for CAN messages on `vcan0`:**
```bash
candump vcan0
```
2. **Send a CAN message on `vcan0` (from another terminal):**
```bash
cansend vcan0 123#DEADBEEF
```
You should see the message `123#DEADBEEF` appear on `vcan0`.
## Explanation
This setup uses virtual CAN interfaces (`vcan0` and `vcan1`) to simulate a CAN bus environment. This is useful for testing CAN applications without the need for physical CAN hardware.
**After virtual can is ready uncomment this line `#add_subdirectory(manual_tests/socket_can_com_tests)` from `vapi-cpp-vehicle-api-platform/tests/CMakeLists.txt` and then reconfigure and build the project to run the tests.**
## Additional Resources
- SocketCAN Documentation
- [can-utils GitHub Repository](https://github.com/linux-can/can-utils)

View File

@@ -0,0 +1,7 @@
[Configuration]
Version = 100
[[Component]]
Path = "can_com_sockets.sdv"
Class = "CAN_Com_Sockets"
canSockets=["vcan3"]

View File

@@ -0,0 +1,188 @@
#ifndef CAN_TEST_HELPER_H
#define CAN_TEST_SOCKET_H
#include <iostream>
#include <mutex>
#include <deque>
#include <gtest/gtest.h>
#include <interfaces/can.h>
#include <support/app_control.h>
#include <support/toml.h>
#include <support/component_impl.h>
#include "../sdv_services/core/toml_parser/parser_toml.h"
#include "../sdv_services/can_communication_socket_can/can_com_sockets.h"
class CComTestHelper
{
public:
bool IsVcanInstalled()
{
std::string result = exec("ip addr | grep can");
if(!result.empty())
{
return true;
}
bool isInstalled = false;
exec("sudo ip link add dev vcan0 type vcan"); // try to create something
result = exec("ip addr | grep can");
if(!result.empty())
{
isInstalled = true;
}
exec("sudo ip link delete vcan0"); // revert it again
return isInstalled;
}
struct PcloseDeleter
{
void operator()(FILE* file) const
{
pclose(file);
}
};
std::string exec(const char* cmd)
{
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, PcloseDeleter> pipe(popen(cmd, "r"));
if (!pipe)
{
return "";
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
{
result += buffer.data();
}
return result;
}
bool ValidateVCanSetup(const std::string& name, bool expected)
{
bool found = false;
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) != -1)
{
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
if (name.compare(ifa->ifa_name) == 0)
{
found = true;
break;
}
}
}
freeifaddrs(ifaddr);
if (found == expected)
{
return true;
}
std::cout << "VCan setup: Interface " << name << " expected to be = " << std::to_string(expected)
<< " but is = " << std::to_string(found) << std::endl;
return false;
}
sdv::can::SMessage CreateTestData(uint8_t init, uint8_t dataSize)
{
uint8_t value = init;
sdv::can::SMessage testMsg;
testMsg.uiID = value;
testMsg.bCanFd = false;
testMsg.bExtended = false;
testMsg.seqData.resize(dataSize);
for (uint8_t i = 0; i < dataSize; i++)
{
testMsg.seqData[i] = value;
value += 2;
}
return testMsg;
}
sdv::can::SMessage CreateTestData2(uint8_t init, uint8_t dataSize)
{
uint8_t value = init;
sdv::can::SMessage testMsg;
testMsg.uiID = value;
testMsg.bCanFd = false;
testMsg.bExtended = false;
testMsg.seqData.resize(dataSize);
for (uint8_t i = 0; i < dataSize; i++)
{
testMsg.seqData[i] = value;
if(i==2)testMsg.seqData[i] = 7;
value += 2;
}
return testMsg;
}
bool ValidateInterfaces(const sdv::sequence<sdv::u8string>& seqExpectedInterfaces, const sdv::sequence<sdv::u8string>& seqInterfaces)
{
bool success = true;
if (seqExpectedInterfaces.size() != seqInterfaces.size())
{
std::cout << "Interfaces failure, expected: " << std::to_string(seqExpectedInterfaces.size()) << " got: " << std::to_string(seqInterfaces.size()) << std::endl;
success = false;
}
else
{
for (size_t nIndex = 0; nIndex < seqInterfaces.size(); nIndex++)
{
if (seqExpectedInterfaces[nIndex].compare(seqInterfaces[nIndex]) != 0)
{
success = false,
std::cout << "Interface mismatch, expected: " << seqExpectedInterfaces[nIndex].c_str() << " got: " << seqInterfaces[nIndex].c_str() << std::endl;
}
}
}
return success;
}
bool ValidateReceivedMessages(const std::deque<std::pair<std::string, sdv::can::SMessage>>& received,
const sdv::can::SMessage& testMessage, const std::string& interfaceName, size_t expectedSize)
{
uint32_t error = 0;
if (expectedSize == 0)
error++;
if (received.size() != expectedSize)
error++;
for (auto message : received)
{
if (interfaceName.compare(message.first) != 0)
error++;
if (message.second.uiID != testMessage.uiID)
error++;
if (message.second.seqData.size() != testMessage.seqData.size())
error++;
}
if (error)
{
std::cout << "Got " << std::to_string(error) << " errors from validation of "
<< interfaceName << ", expect " << std::to_string(expectedSize)
<< " messages, got " << std::to_string(received.size()) << std::endl;
return false;
}
return true;
}
std::string NormalizeWhitespace(const std::string& str)
{
std::string trimmed = str;
trimmed.erase(trimmed.find_last_not_of(" \t\n\r") + 1); // Trim trailing whitespace
trimmed.erase(0, trimmed.find_first_not_of(" \t\n\r")); // Trim leading whitespace
std::string result;
std::unique_copy(trimmed.begin(), trimmed.end(), std::back_inserter(result),
[](char a, char b) { return std::isspace(a) && std::isspace(b); });
return result;
}
};
#endif // ! defined CAN_TEST_HELPER_H

View File

@@ -0,0 +1,828 @@
#include "../../../../global/process_watchdog.h"
#include "../include/can_com_test_helper.h"
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
#endif
class CANSocketTest : public ::testing::Test
{
public:
virtual void SetUp() override
{
if (vcanIsInstalled)
{
CComTestHelper testhelper;
// interfaces required for the test
RequiredVCAN(testhelper, true, "vcan0", "sudo ip link add dev vcan0 type vcan", "sudo ip link set up vcan0");
RequiredVCAN(testhelper, true, "vcan1", "sudo ip link add dev vcan1 type vcan", "sudo ip link set up vcan1");
RequiredVCAN(testhelper, true, "vcan2", "sudo ip link add dev vcan2 type vcan", "sudo ip link set up vcan2");
RequiredVCAN(testhelper, true, "vcan3", "sudo ip link add dev vcan3 type vcan", "sudo ip link set up vcan3");
RequiredVCAN(testhelper, true, "vcan4", "sudo ip link add dev vcan4 type vcan", "sudo ip link set up vcan4");
// "vcan8" & "vcan9" were interfaces that must not exist.
RequiredVCAN(testhelper, false, "vcan8", "sudo ip link set down vcan8", "sudo ip link delete vcan8");
RequiredVCAN(testhelper, false, "vcan9", "sudo ip link set down vcan9", "sudo ip link delete vcan9");
}
else
{
GTEST_SKIP() << "Skipping test because vcan is not installed.";
}
}
virtual void TearDown() override
{
}
static void SetUpTestCase()
{
CComTestHelper testhelper;
vcanIsInstalled = testhelper.IsVcanInstalled();
}
static void TearDownTestSuite() {}
bool RequiredVCAN(CComTestHelper testhelper, bool expected, std::string name,
std::string addDevcommand, std::string setupCommand)
{
if (!testhelper.ValidateVCanSetup(name.c_str(), expected))
{
testhelper.exec(addDevcommand.c_str());
testhelper.exec(setupCommand.c_str());
if (!testhelper.ValidateVCanSetup(name.c_str(), expected))
{
return false;
}
}
return true;
}
void RemoveCanDumpFilesWithPrefix()
{
std::string directory = "."; // Current directory
std::string prefix = "candump_output_vcan";
for (const auto& entry : std::filesystem::directory_iterator(directory))
{
if (entry.is_regular_file() && entry.path().filename().string().find(prefix) == 0)
{
try
{
std::filesystem::remove(entry.path());
std::cout << "Deleted: " << entry.path() << std::endl;
} catch (const std::filesystem::filesystem_error&)
{
std::cout << "Failed to delete: " << entry.path() << std::endl;
}
}
}
}
static bool vcanIsInstalled;
};
// Define the static member variable
bool CANSocketTest::vcanIsInstalled = false;
class CTestCANSocket : public CCANSockets
{
public:
virtual void Initialize(const sdv::u8string& ssObjectConfig) override
{
CCANSockets::Initialize(ssObjectConfig);
}
virtual void Shutdown() override
{
CCANSockets::Shutdown();
}
virtual void Send(const sdv::can::SMessage& sMsg, uint32_t uiIfcIndex) override
{
CCANSockets::Send(sMsg, uiIfcIndex);
m_MessagesSent++;
}
virtual void RegisterReceiver(sdv::can::IReceive* pReceiver) override
{
CCANSockets::RegisterReceiver(pReceiver);
}
virtual void UnregisterReceiver(sdv::can::IReceive* pReceiver) override
{
CCANSockets::UnregisterReceiver(pReceiver);
}
virtual sdv::sequence<sdv::u8string> GetInterfaces() const override
{
return CCANSockets::GetInterfaces();
}
sdv::EObjectStatus GetTestStatus() const
{
return CCANSockets::GetStatus();
}
uint64_t GetMessagesSent() const
{
return m_MessagesSent;
}
uint64_t m_MessagesSent = 0;
};
class MockCANReceiver : public sdv::can::IReceive
{
public:
void Receive(const sdv::can::SMessage& msg, uint32_t ifIndex) override
{
std::lock_guard<std::mutex> lock(m_mutex);
m_receivedMessages.push_back(std::make_pair(GetInterfaceName(ifIndex), msg));
}
void Error(const sdv::can::SErrorFrame&, uint32_t) override {}
std::deque<std::pair<std::string, sdv::can::SMessage>>GetReceivedMessages() const
{
return m_receivedMessages;
}
private:
std::string GetInterfaceName (int ifIndex)
{
std::string name = "UnknownInterface";
// Retrieve the list of available interfaces
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) != -1)
{
std::vector<std::string> available;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
int indexFromName = if_nametoindex(ifa->ifa_name);
if (indexFromName == ifIndex)
{
name = ifa->ifa_name;
break;
}
}
}
return name;
}
std::deque<std::pair<std::string, sdv::can::SMessage>> m_receivedMessages; ///< Interface name, message
std::mutex m_mutex;
};
bool InitializeAppControl(sdv::app::CAppControl* appcontrol, const std::string& configFileName)
{
auto bResult = appcontrol->AddModuleSearchDir("../../bin");
bResult &= appcontrol->Startup("");
appcontrol->SetConfigMode();
bResult &= appcontrol->AddConfigSearchDir("../../tests/bin/config");
if (!configFileName.empty())
{
bResult &= appcontrol->LoadConfig(configFileName.c_str()) == sdv::core::EConfigProcessResult::successful;
}
return bResult;
}
void InitializeCanComObject(CTestCANSocket& canComObj, const std::string config, MockCANReceiver& mockRcv)
{
ASSERT_NO_THROW(canComObj.Initialize(config.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialized);
ASSERT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::configuring));
ASSERT_NO_THROW(canComObj.RegisterReceiver(&mockRcv));
EXPECT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::running));
}
void ShutDownCanComObject(CTestCANSocket& canComObj, MockCANReceiver& mockRcv)
{
EXPECT_NO_THROW(canComObj.UnregisterReceiver(&mockRcv));
ASSERT_NO_THROW(canComObj.Shutdown());
}
void SendThread(bool& stop, CTestCANSocket& canComObj, sdv::can::SMessage& testData)
{
while (!stop)
{
EXPECT_NO_THROW(canComObj.Send(testData, 1)); // Send to second configuration
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
}
TEST_F(CANSocketTest, ValidConfigString)
{
sdv::can::SMessage testMsg {1, 0, 0, {0x11, 0x22, 0x33, 0x44}};
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssObjectConfig = R"(canSockets = "vcan0")"; // vcan0 interface must exist
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str()));
ASSERT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialized);
ASSERT_NO_THROW(canComObj.Send(testMsg, 0));
ASSERT_NO_THROW(canComObj.Shutdown());
ASSERT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
TEST_F(CANSocketTest, InvalidConfigString)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssObjectConfig = R"(canSockets = "vcan08")";
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialization_failure);
ASSERT_NO_THROW(canComObj.Shutdown());
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
TEST_F(CANSocketTest, ValidConfigArray)
{
sdv::can::SMessage testMsg {1, 0, 0, {0x11, 0x22, 0x33, 0x44}};
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssObjectConfig = R"(canSockets = ["vcan0", "vcan1"])";
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialized);
ASSERT_NO_THROW(canComObj.Send(testMsg, 0));
EXPECT_NO_THROW(canComObj.Shutdown());
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
TEST_F(CANSocketTest, InvalidConfigArray)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssObjectConfig = R"(canSockets = ["vcan08", "vcan09"])";
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialization_failure);
ASSERT_NO_THROW(canComObj.Shutdown());
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
TEST_F(CANSocketTest, ValidConfigArrayButUnknownElement)
{
sdv::can::SMessage testMsg {1, 0, 0, {0x11, 0x22, 0x33, 0x44}};
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssObjectConfig = R"(canSockets = ["vcan0", "vcan08"])";
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialized);
ASSERT_NO_THROW(canComObj.Send(testMsg, 0));
ASSERT_NO_THROW(canComObj.Shutdown());
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
TEST_F(CANSocketTest, InvalidConfigIdentifier)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssObjectConfig = R"(invalidCanSockets = ["vcan0", "vcan1"])"; // Invalid config identifier
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssObjectConfig.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialization_failure);
ASSERT_NO_THROW(canComObj.Shutdown());
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
TEST_F(CANSocketTest, ReceiveTestDifferentDataSizes)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssConfig1 = R"(canSockets = ["vcan1", "vcan2"])";
sdv::u8string ssConfig2 = R"(canSockets = ["vcan2", "vcan1"])";
// Object1 sends to Object2; Object2 sends to Object1
CTestCANSocket canComObj1;
CTestCANSocket canComObj2;
MockCANReceiver mockRcv1;
MockCANReceiver mockRcv2;
InitializeCanComObject(canComObj1, ssConfig1, mockRcv1);
InitializeCanComObject(canComObj2, ssConfig2, mockRcv2);
uint32_t msgId1 = 102;
uint32_t msgId2 = 201;
uint32_t dataSize = 1;
uint32_t expectToBeSend = 8; // other data sizes are to large
uint32_t msgToBeSend = 16;
CComTestHelper testHelper;
while (dataSize < msgToBeSend)
{
auto testData1 = testHelper.CreateTestData(msgId1, dataSize);
auto testData2 = testHelper.CreateTestData(msgId2, dataSize);
EXPECT_NO_THROW(canComObj1.Send(testData1, 1)); // Send to vcan2
EXPECT_NO_THROW(canComObj2.Send(testData2, 1)); // Send to vcan1
std::this_thread::sleep_for(std::chrono::milliseconds(10));
dataSize++;
}
auto receivedMessages1 = mockRcv1.GetReceivedMessages();
auto receivedMessages2 = mockRcv2.GetReceivedMessages();
if ((expectToBeSend != receivedMessages1.size()) || (expectToBeSend != receivedMessages2.size()))
{
std::stringstream sstream;
sstream << " Failed, expected " << std::to_string(expectToBeSend ) << " messages, got from vcan1 to vcan2: "
<< std::to_string(receivedMessages1.size()) << " messages, from vcan2 to vcan1: "
<< std::to_string(receivedMessages2.size()) << " messages.";
FAIL() << sstream.str();
}
else
{
dataSize = 1;
for(auto receivedMessage : receivedMessages1)
{
auto testData = testHelper.CreateTestData(msgId2, dataSize);
EXPECT_EQ(msgId2, receivedMessage.second.uiID);
EXPECT_EQ(dataSize++, receivedMessage.second.seqData.size());
EXPECT_EQ(testData.seqData, receivedMessage.second.seqData);
}
dataSize = 1;
for(auto receivedMessage : receivedMessages2)
{
auto testData = testHelper.CreateTestData(msgId1, dataSize);
EXPECT_EQ(msgId1, receivedMessage.second.uiID);
EXPECT_EQ(dataSize++, receivedMessage.second.seqData.size());
EXPECT_EQ(testData.seqData, receivedMessage.second.seqData);
}
}
ShutDownCanComObject(canComObj1, mockRcv1);
ShutDownCanComObject(canComObj2, mockRcv2);
}
TEST_F(CANSocketTest, ReceiveTestDifferentDataSizesAndInvalidConfiguration)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssConfig1 = R"(canSockets = ["vcan1", "vcan8", "vcan9", "vcan2"])";
sdv::u8string ssConfig2 = R"(canSockets = ["vcan2", "vcan9", "vcan1"])";
// Object1 sends to Object2; Object2 sends to Object1
CTestCANSocket canComObj1;
CTestCANSocket canComObj2;
MockCANReceiver mockRcv1;
MockCANReceiver mockRcv2;
InitializeCanComObject(canComObj1, ssConfig1, mockRcv1);
InitializeCanComObject(canComObj2, ssConfig2, mockRcv2);
CComTestHelper testHelper;
uint32_t msgId1 = 102;
uint32_t msgId2 = 201;
uint32_t dataSize = 1;
uint32_t expectToBeSend = 8; // other data sizes are to large
uint32_t msgToBeSend = 16;
while (dataSize < msgToBeSend)
{
auto testData1 = testHelper.CreateTestData(msgId1, dataSize);
auto testData2 = testHelper.CreateTestData(msgId2, dataSize);
EXPECT_NO_THROW(canComObj1.Send(testData1, 3)); // Send to vcan2, index 1 & 2 are invalid in config
EXPECT_NO_THROW(canComObj2.Send(testData2, 2)); // Send to vcan1, index 1 in config
std::this_thread::sleep_for(std::chrono::milliseconds(10));
dataSize++;
}
auto receivedMessages1 = mockRcv1.GetReceivedMessages();
auto receivedMessages2 = mockRcv2.GetReceivedMessages();
if ((expectToBeSend != receivedMessages1.size()) || (expectToBeSend != receivedMessages2.size()))
{
std::stringstream sstream;
sstream << " Failed to send " << std::to_string(expectToBeSend ) << " messages, got from vcan1 to vcan2: "
<< std::to_string(receivedMessages1.size()) << " messages, from vcan2 to vcan1: "
<< std::to_string(receivedMessages2.size()) << " messages.";
FAIL() << sstream.str();
}
else
{
dataSize = 1;
for(auto receivedMessage : receivedMessages1)
{
auto testData = testHelper.CreateTestData(msgId2, dataSize);
EXPECT_EQ(msgId2, receivedMessage.second.uiID);
EXPECT_EQ(dataSize++, receivedMessage.second.seqData.size());
EXPECT_EQ(testData.seqData, receivedMessage.second.seqData);
}
dataSize = 1;
for(auto receivedMessage : receivedMessages2)
{
auto testData = testHelper.CreateTestData(msgId1, dataSize);
EXPECT_EQ(msgId1, receivedMessage.second.uiID);
EXPECT_EQ(dataSize++, receivedMessage.second.seqData.size());
EXPECT_EQ(testData.seqData, receivedMessage.second.seqData);
}
}
ShutDownCanComObject(canComObj1, mockRcv1);
ShutDownCanComObject(canComObj2, mockRcv2);
}
TEST_F(CANSocketTest, StressTestWith3Objects)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
sdv::u8string ssConfig1 = R"(canSockets = ["vcan1", "vcan2"])";
sdv::u8string ssConfig2 = R"(canSockets = ["vcan2", "vcan3"])";
sdv::u8string ssConfig3 = R"(canSockets = ["vcan3", "vcan1"])";
// Object1 sends to Object2; Object2 sends to Object3; Object3 sends to Object1
CTestCANSocket canComObj1;
CTestCANSocket canComObj2;
CTestCANSocket canComObj3;
MockCANReceiver mockRcv1;
MockCANReceiver mockRcv2;
MockCANReceiver mockRcv3;
InitializeCanComObject(canComObj1, ssConfig1, mockRcv1);
InitializeCanComObject(canComObj2, ssConfig2, mockRcv2);
InitializeCanComObject(canComObj3, ssConfig3, mockRcv3);
CComTestHelper testHelper;
auto testData1 = testHelper.CreateTestData(10, 6); // Testdata to send to vcan2, size = 6
auto testData2 = testHelper.CreateTestData(20, 7); // Testdata to send to vcan3, size = 7
auto testData3 = testHelper.CreateTestData(30, 8); // Testdata to send to vcan1, size = 8
bool stopSendThread = false;
std::cout << "Start thread sending messages..." << std::endl;
std::thread thSendThread1(SendThread, std::ref(stopSendThread), std::ref(canComObj1), std::ref(testData1));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::thread thSendThread2(SendThread, std::ref(stopSendThread), std::ref(canComObj2), std::ref(testData2));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::thread thSendThread3(SendThread, std::ref(stopSendThread), std::ref(canComObj3), std::ref(testData3));
std::this_thread::sleep_for(std::chrono::seconds(10));
stopSendThread = true;
if (thSendThread1.joinable())
thSendThread1.join();
if (thSendThread2.joinable())
thSendThread2.join();
if (thSendThread3.joinable())
thSendThread3.join();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
auto receivedMessages1 = mockRcv1.GetReceivedMessages();
auto receivedMessages2 = mockRcv2.GetReceivedMessages();
auto receivedMessages3 = mockRcv3.GetReceivedMessages();
std::cout << "Number of messages from vcan1 to vcan2: " << std::to_string(canComObj1.GetMessagesSent())
<< ", from vcan2 to vcan3: " << std::to_string(canComObj2.GetMessagesSent())
<< ", from vcan3 to vcan1: " << std::to_string(canComObj3.GetMessagesSent()) << std::endl;
EXPECT_EQ(testHelper.ValidateReceivedMessages(receivedMessages1, testData3, "vcan1", canComObj3.GetMessagesSent()), true);
EXPECT_EQ(testHelper.ValidateReceivedMessages(receivedMessages2, testData1, "vcan2", canComObj1.GetMessagesSent()), true);
EXPECT_EQ(testHelper.ValidateReceivedMessages(receivedMessages3, testData2, "vcan3", canComObj2.GetMessagesSent()), true);
ShutDownCanComObject(canComObj1, mockRcv1);
ShutDownCanComObject(canComObj2, mockRcv2);
ShutDownCanComObject(canComObj3, mockRcv3);
}
TEST_F(CANSocketTest, SendWithVAPICompTest)
{
sdv::app::CAppControl appControl;
auto bResult = InitializeAppControl(&appControl, "test_can_socket.toml");
EXPECT_EQ(bResult, true);
// Load SocketCAN
sdv::TInterfaceAccessPtr ptrCanComObj = sdv::core::GetObject("CAN_Communication_Object");
EXPECT_NE(&ptrCanComObj, nullptr);
sdv::can::ISend* pCanSend = ptrCanComObj.GetInterface<sdv::can::ISend>();
EXPECT_NE(&pCanSend, nullptr);
ASSERT_NO_THROW(appControl.SetRunningMode());
// Clear the candump output file if it exists
std::string candumpFile = "candump_vapi_send.txt";
try
{
if (std::filesystem::exists(candumpFile))
std::filesystem::remove(candumpFile);
} catch (const std::filesystem::filesystem_error&)
{}
// Start candump in the background
std::string candumpCommand = "candump vcan3 > " + candumpFile + " &";
system(candumpCommand.c_str());
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Ensure candump is ready
CComTestHelper testHelper;
uint32_t msgId = 999;
uint32_t dataSize = 1;
uint32_t msgToBeSend = 8;
while (dataSize < msgToBeSend)
{
auto testData1 = testHelper.CreateTestData(msgId, dataSize);
ASSERT_NO_THROW(pCanSend->Send(testData1, 0));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
msgId++;
dataSize++;
}
// Wait for messages to be written to the file
std::this_thread::sleep_for(std::chrono::seconds(2));
// Compare sent messages with the candump output
std::ifstream inputFile(candumpFile);
ASSERT_TRUE(inputFile.is_open()) << "Failed to open candump output file.";
msgId = 999;
dataSize = 1;
std::string line;
while (std::getline(inputFile, line) && dataSize < msgToBeSend)
{
auto expectedData = testHelper.CreateTestData(msgId, dataSize);
std::stringstream expectedLine;
// Format the CAN interface name ("vcan3") and the message ID (uiID) in hexadecimal,
// ensuring it is uppercase, 3 characters wide, and padded with zeros if necessary.
expectedLine << "vcan3 " << std::hex << std::uppercase << std::setw(3) << std::setfill('0') << expectedData.uiID
<< " [" << expectedData.seqData.size() << "] ";
// Loop through each byte in the data sequence (seqData) and format it as a
// 2-character wide, zero-padded hexadecimal value, appending it to the stringstream.
for (auto byte : expectedData.seqData)
{
expectedLine << std::setw(2) << std::setfill('0') << std::hex << (int)byte << " ";
}
std::string normalizedExpected = testHelper.NormalizeWhitespace(expectedLine.str());
std::string normalizedActual = testHelper.NormalizeWhitespace(line);
if (normalizedActual != normalizedExpected)
{
FAIL() << "Mismatch in candump output for message ID " << msgId
<< "\nExpected: " << normalizedExpected
<< "\nActual: " << normalizedActual;
}
msgId++;
dataSize++;
}
inputFile.close();
ASSERT_NO_THROW(appControl.Shutdown());
// Stop candump
system("pkill candump");
}
TEST_F(CANSocketTest, ReceiveWithVAPICompTest)
{
sdv::app::CAppControl appControl;
auto bResult = InitializeAppControl(&appControl, "test_can_socket.toml");
EXPECT_EQ(bResult, true);
// Load SocketCAN
sdv::TInterfaceAccessPtr ptrCanComObj = sdv::core::GetObject("CAN_Communication_Object");
EXPECT_NE(&ptrCanComObj, nullptr);
sdv::can::IRegisterReceiver* pCanRegRcvr = ptrCanComObj.GetInterface<sdv::can::IRegisterReceiver>();
EXPECT_NE(&pCanRegRcvr, nullptr);
MockCANReceiver mockRcv;
ASSERT_NO_THROW(pCanRegRcvr->RegisterReceiver(&mockRcv));
ASSERT_NO_THROW(appControl.SetRunningMode());
// Send messages from vcan3 using candump
CComTestHelper testHelper;
uint32_t msgId = 777;
uint32_t dataSize = 1;
uint32_t msgToBeSend = 5;
while (dataSize <= msgToBeSend)
{
auto testData = testHelper.CreateTestData(msgId, dataSize);
std::stringstream cansendCommand;
cansendCommand << "cansend vcan3 " << std::hex << std::uppercase << std::setw(3) << std::setfill('0') << testData.uiID << "#";
for (auto byte : testData.seqData)
{
cansendCommand << std::setw(2) << std::setfill('0') << std::hex << (int)byte;
}
system(cansendCommand.str().c_str());
std::this_thread::sleep_for(std::chrono::milliseconds(10));
msgId++;
dataSize++;
}
if (dataSize == msgToBeSend)
{
system("pkill cansend");
}
// Wait for messages to be received
std::this_thread::sleep_for(std::chrono::seconds(1));
auto receivedMessages = mockRcv.GetReceivedMessages();
dataSize = 1;
msgId = 777;
for (const auto& receivedMessage : receivedMessages)
{
auto expectedData = testHelper.CreateTestData(msgId, dataSize);
EXPECT_EQ(receivedMessage.second.uiID, expectedData.uiID) << "Mismatch in message ID for data size " << dataSize;
EXPECT_EQ(receivedMessage.second.seqData, expectedData.seqData) << "Mismatch in message data for ID " << msgId;
msgId++;
dataSize++;
}
ASSERT_NO_THROW(pCanRegRcvr->UnregisterReceiver(&mockRcv));
ASSERT_NO_THROW(appControl.Shutdown());
}
// Manual / Visual 'Send' & 'Received' test
// Configuration ["vcan0", "vcan3", "vcan8", "vcan1", "vcan8", "vcan4", "vcan2"])"
// Index 0 = vcan0
// Index 1 = vcan3
// Index 2 = vcan8 // should not exist, expect to be invalid
// Index 3 = vcan1
// Index 4 = vcan9 // should not exist, expect to be invalid
// Index 5 = vcan4
// Index 6 = vcan2
// Required: vcan0, vcan1, vcan2, vcan3, vcan4
// sudo ip link add dev vcan0 type vcan
// sudo ip link set up vcan0
// Show all can interfaces: ip addr | grep "vcan"
//
// Verifiy Send:
// On 5 terminals execute command with the correct interface to visualize the sended messages
// candump vcan0
// Expected: name of interface, MessageId represends interface
// vcan1 001 [x] 02 04 06 08 0A 0C 0E 10
//
// Verifiy Receive:
// On another terminal execute
// cansend vcan4 100#DEADBEEF
// Expected: name of interface, MessageId is +1 than the interface
// Got Message from: vcan4, MsgID: 256 '222' '173' '190' '239'
TEST_F(CANSocketTest, ManualSendAndReceiveTestOfMulitpleSockets)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
uint32_t vcan0Index = 0; // must fit to the configuration string
uint32_t vcan1Index = 3;
uint32_t vcan2Index = 6;
uint32_t vcan3Index = 1;
uint32_t vcan4Index = 5;
sdv::u8string ssConfig = R"(canSockets = ["vcan0", "vcan3", "vcan8", "vcan1", "vcan9", "vcan4", "vcan2"])";
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssConfig.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialized);
// Register a receiver.
MockCANReceiver mockRcv;
ASSERT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::configuring));
ASSERT_NO_THROW(canComObj.RegisterReceiver(&mockRcv));
EXPECT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::running));
CComTestHelper testHelper;
sdv::sequence<sdv::u8string> expectedInterfaces{"vcan0", "vcan3", "vcan1", "vcan4", "vcan2"};
EXPECT_TRUE(testHelper.ValidateInterfaces(expectedInterfaces, canComObj.GetInterfaces()));
uint32_t msgId = 0;
uint32_t dataSize = 8;
auto testData0 = testHelper.CreateTestData(msgId + 0, dataSize); // Testdata for vcan0
auto testData1 = testHelper.CreateTestData(msgId + 1, dataSize); // Testdata for vcan1
auto testData2 = testHelper.CreateTestData(msgId + 2, dataSize); // Testdata for vcan2
auto testData3 = testHelper.CreateTestData(msgId + 3, dataSize); // Testdata for vcan3
auto testData4 = testHelper.CreateTestData(msgId + 4, dataSize); // Testdata for vcan4
EXPECT_NO_THROW(canComObj.Send(testData0, vcan0Index));
EXPECT_NO_THROW(canComObj.Send(testData1, vcan1Index));
EXPECT_NO_THROW(canComObj.Send(testData2, vcan2Index));
EXPECT_NO_THROW(canComObj.Send(testData3, vcan3Index));
EXPECT_NO_THROW(canComObj.Send(testData4, vcan4Index));
// Wait for messages to be received
uint32_t waitForMessagesLoop = 1;
while (waitForMessagesLoop > 0)
{
waitForMessagesLoop--;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
auto receivedMessages = mockRcv.GetReceivedMessages();
for(auto receivedMessage : receivedMessages)
{
std::cout << "Got Message from: " << receivedMessage.first << ", MsgID: " << std::to_string(receivedMessage.second.uiID) ;
for (uint8_t i = 0; i < receivedMessage.second.seqData.size(); ++i)
{
std::cout << " '" << std::to_string(receivedMessage.second.seqData[i]) << "'";
}
std::cout << std::endl;
}
EXPECT_NO_THROW(canComObj.UnregisterReceiver(&mockRcv));
ASSERT_NO_THROW(canComObj.Shutdown()); // Shutdown the object after the test
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
// Test similar to ManualSendAndReceiveTestOfMulitpleSockets
// but with different messages size
// Result:
// vcan0 000 [x] 00 02 04 06
// vcan1 001 [x] 01 03 05 07 09
// vcan2 002 [x] 02 04 06 08 0A 0C
// vcan3 003 [x] 03 05 07 09 0B 0D 0F
// vcan4 no message
TEST_F(CANSocketTest, ManualSendAndReceiveTestWithDifferentDataSizes)
{
sdv::app::CAppControl appControl;
appControl.Startup("");
uint32_t vcan0Index = 0; // must fit to the configuration string
uint32_t vcan1Index = 3;
uint32_t vcan2Index = 6;
uint32_t vcan3Index = 1;
uint32_t vcan4Index = 5;
sdv::u8string ssConfig = R"(canSockets = ["vcan0", "vcan3", "vcan8", "vcan1", "vcan9", "vcan4", "vcan2"])";
CTestCANSocket canComObj;
ASSERT_NO_THROW(canComObj.Initialize(ssConfig.c_str()));
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::initialized);
// Register a receiver.
MockCANReceiver mockRcv;
ASSERT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::configuring));
ASSERT_NO_THROW(canComObj.RegisterReceiver(&mockRcv));
EXPECT_NO_THROW(canComObj.SetOperationMode(sdv::EOperationMode::running));
CComTestHelper testHelper;
sdv::sequence<sdv::u8string> expectedInterfaces{"vcan0", "vcan3", "vcan1", "vcan4", "vcan2"};
EXPECT_TRUE(testHelper.ValidateInterfaces(expectedInterfaces, canComObj.GetInterfaces()));
uint32_t msgId = 0;
uint32_t dataSize = 8;
auto testData0 = testHelper.CreateTestData(msgId + 0, dataSize -4); // Testdata for vcan0
auto testData1 = testHelper.CreateTestData(msgId + 1, dataSize -3); // Testdata for vcan1
auto testData2 = testHelper.CreateTestData(msgId + 2, dataSize -2); // Testdata for vcan2
auto testData3 = testHelper.CreateTestData(msgId + 3, dataSize -1); // Testdata for vcan3
auto testData4 = testHelper.CreateTestData(msgId + 4, dataSize +1); // Testdata for vcan4, is to large
EXPECT_NO_THROW(canComObj.Send(testData0, vcan0Index));
EXPECT_NO_THROW(canComObj.Send(testData1, vcan1Index));
EXPECT_NO_THROW(canComObj.Send(testData2, vcan2Index));
EXPECT_NO_THROW(canComObj.Send(testData3, vcan3Index));
EXPECT_NO_THROW(canComObj.Send(testData4, vcan4Index));
// Wait for messages to be received
uint32_t waitForMessagesLoop = 1;
while (waitForMessagesLoop > 0)
{
waitForMessagesLoop--;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
auto receivedMessages = mockRcv.GetReceivedMessages();
for(auto receivedMessage : receivedMessages)
{
std::cout << "Got Message from: " << receivedMessage.first << ", MsgID: " << std::to_string(receivedMessage.second.uiID) ;
for (uint8_t i = 0; i < receivedMessage.second.seqData.size(); ++i)
{
std::cout << " '" << std::to_string(receivedMessage.second.seqData[i]) << "'";
}
std::cout << std::endl;
}
EXPECT_NO_THROW(canComObj.UnregisterReceiver(&mockRcv));
ASSERT_NO_THROW(canComObj.Shutdown()); // Shutdown the object after the test
EXPECT_EQ(canComObj.GetStatus(), sdv::EObjectStatus::destruction_pending);
}
extern "C" int main(int argc, char* argv[])
{
CProcessWatchdog watchdog;
::testing::InitGoogleTest(&argc, argv);
int result = RUN_ALL_TESTS();
return result;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif