mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-07-02 05:35:11 +00:00
tunnel component & update vehicle abstraction example (#8)
This commit is contained in:
134
tests/unit_tests/win_tunnel/CMakeLists.txt
Normal file
134
tests/unit_tests/win_tunnel/CMakeLists.txt
Normal file
@@ -0,0 +1,134 @@
|
||||
#*******************************************************************************
|
||||
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
|
||||
#
|
||||
# This program and the accompanying materials are made available under the
|
||||
# terms of the Apache License Version 2.0 which is available at
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Contributors:
|
||||
# Denisa Ros - initial API and implementation
|
||||
#*******************************************************************************
|
||||
if(WIN32)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(WinTunnelCommunicationTests LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Executable: GTest for UDS connect
|
||||
add_executable(UnitTest_WinTunnelConnectTests
|
||||
win_tunnel_connect_tests.cpp
|
||||
)
|
||||
|
||||
target_include_directories(UnitTest_WinTunnelConnectTests
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${PROJECT_SOURCE_DIR}/export
|
||||
${PROJECT_SOURCE_DIR}/export/support
|
||||
${PROJECT_SOURCE_DIR}/export/interfaces
|
||||
${PROJECT_SOURCE_DIR}/sdv_services
|
||||
)
|
||||
|
||||
target_link_libraries(UnitTest_WinTunnelConnectTests
|
||||
PRIVATE
|
||||
GTest::GTest
|
||||
Ws2_32
|
||||
Winmm
|
||||
Rpcrt4
|
||||
uds_win_tunnel
|
||||
uds_win_sockets
|
||||
)
|
||||
|
||||
add_test(NAME UnitTest_WinTunnelConnectTests
|
||||
COMMAND UnitTest_WinTunnelConnectTests)
|
||||
|
||||
add_dependencies(UnitTest_WinTunnelConnectTests dependency_sdv_components)
|
||||
|
||||
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
|
||||
add_custom_command(TARGET UnitTest_WinTunnelConnectTests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
|
||||
"$<TARGET_FILE:UnitTest_WinTunnelConnectTests>"
|
||||
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelConnectTests.xml
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(UnitTest_WinTunnelChannelMgntTests
|
||||
win_tunnel_channel_mgnt_tests.cpp
|
||||
)
|
||||
|
||||
target_include_directories(UnitTest_WinTunnelChannelMgntTests
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${PROJECT_SOURCE_DIR}/export
|
||||
${PROJECT_SOURCE_DIR}/export/support
|
||||
${PROJECT_SOURCE_DIR}/export/interfaces
|
||||
${PROJECT_SOURCE_DIR}/sdv_services
|
||||
)
|
||||
|
||||
target_link_libraries(UnitTest_WinTunnelChannelMgntTests
|
||||
PRIVATE
|
||||
GTest::GTest
|
||||
Ws2_32
|
||||
Winmm
|
||||
Rpcrt4
|
||||
uds_win_tunnel
|
||||
uds_win_sockets
|
||||
)
|
||||
|
||||
add_test(NAME UnitTest_WinTunnelChannelMgntTests
|
||||
COMMAND UnitTest_WinTunnelChannelMgntTests)
|
||||
|
||||
add_dependencies(UnitTest_WinTunnelChannelMgntTests dependency_sdv_components)
|
||||
|
||||
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
|
||||
add_custom_command(TARGET UnitTest_WinTunnelChannelMgntTests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
|
||||
"$<TARGET_FILE:UnitTest_WinTunnelChannelMgntTests>"
|
||||
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelChannelMgntTests.xml
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add negative/edge case tests
|
||||
add_executable(UnitTest_WinTunnelNegativeEdgeTests
|
||||
win_tunnel_negative_edge_tests.cpp
|
||||
)
|
||||
|
||||
target_include_directories(UnitTest_WinTunnelNegativeEdgeTests
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${PROJECT_SOURCE_DIR}/export
|
||||
${PROJECT_SOURCE_DIR}/export/support
|
||||
${PROJECT_SOURCE_DIR}/export/interfaces
|
||||
${PROJECT_SOURCE_DIR}/sdv_services
|
||||
)
|
||||
|
||||
target_link_libraries(UnitTest_WinTunnelNegativeEdgeTests
|
||||
PRIVATE
|
||||
GTest::GTest
|
||||
Ws2_32
|
||||
Winmm
|
||||
Rpcrt4
|
||||
uds_win_tunnel
|
||||
uds_win_sockets
|
||||
)
|
||||
|
||||
add_test(NAME UnitTest_WinTunnelNegativeEdgeTests
|
||||
COMMAND UnitTest_WinTunnelNegativeEdgeTests)
|
||||
|
||||
add_dependencies(UnitTest_WinTunnelNegativeEdgeTests dependency_sdv_components)
|
||||
|
||||
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
|
||||
add_custom_command(TARGET UnitTest_WinTunnelNegativeEdgeTests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
|
||||
"$<TARGET_FILE:UnitTest_WinTunnelNegativeEdgeTests>"
|
||||
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelNegativeEdgeTests.xml
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
442
tests/unit_tests/win_tunnel/win_tunnel_channel_mgnt_tests.cpp
Normal file
442
tests/unit_tests/win_tunnel/win_tunnel_channel_mgnt_tests.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
/********************************************************************************
|
||||
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Apache License Version 2.0 which is available at
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Denisa Ros - initial API and implementation
|
||||
********************************************************************************/
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <support/app_control.h>
|
||||
#include <interfaces/ipc.h>
|
||||
|
||||
#include "../../../sdv_services/uds_win_tunnel/channel_mgnt.h"
|
||||
#include "../../../sdv_services/uds_win_tunnel/connection.h"
|
||||
|
||||
// Reuse helpers from uds_win_sockets (path normalization, MakeShortUdsPath)
|
||||
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h"
|
||||
#include "../../../sdv_services/uds_win_sockets/connection.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <windows.h>
|
||||
#include <afunix.h>
|
||||
|
||||
// Helper namespace
|
||||
namespace tunnel_utils
|
||||
{
|
||||
inline std::string Expand(const std::string& in)
|
||||
{
|
||||
if (in.find('%') == std::string::npos)
|
||||
return in;
|
||||
char buf[4096] = {};
|
||||
const DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, sizeof(buf));
|
||||
return (n > 0 && n < sizeof(buf)) ? std::string(buf) : in;
|
||||
}
|
||||
|
||||
inline void EnsureParentDir(const std::string& full)
|
||||
{
|
||||
auto p = full.find_last_of("\\/");
|
||||
if (p == std::string::npos)
|
||||
return;
|
||||
std::string dir = full.substr(0, p);
|
||||
CreateDirectoryA(dir.c_str(), nullptr);
|
||||
}
|
||||
|
||||
inline std::string MakeShortUdsPath(const char* name)
|
||||
{
|
||||
std::string base = R"(%LOCALAPPDATA%\sdv\)";
|
||||
base = Expand(base);
|
||||
EnsureParentDir(base);
|
||||
return base + name;
|
||||
}
|
||||
|
||||
inline std::string RandomHex()
|
||||
{
|
||||
std::mt19937_64 r{std::random_device{}()};
|
||||
std::uniform_int_distribution<uint64_t> d;
|
||||
std::ostringstream oss;
|
||||
oss << std::hex << d(r);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
inline std::string Unique(const char* prefix)
|
||||
{
|
||||
return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str());
|
||||
}
|
||||
|
||||
inline void SpinUntilServerArmed(sdv::ipc::IConnect* server)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
const auto deadline = steady_clock::now() + milliseconds(500);
|
||||
while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized &&
|
||||
steady_clock::now() < deadline)
|
||||
{
|
||||
std::this_thread::sleep_for(milliseconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tunnel_utils
|
||||
|
||||
using namespace tunnel_utils;
|
||||
|
||||
// Unified receiver (state + data)
|
||||
class CTunnelMgrTestReceiver :
|
||||
public sdv::IInterfaceAccess,
|
||||
public sdv::ipc::IDataReceiveCallback,
|
||||
public sdv::ipc::IConnectEventCallback
|
||||
{
|
||||
public:
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
void SetConnectState(sdv::ipc::EConnectState s) override
|
||||
{
|
||||
{ std::lock_guard<std::mutex> lk(m_mtx); m_state = s; }
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& seq) override
|
||||
{
|
||||
{ std::lock_guard<std::mutex> lk(m_mtx);
|
||||
m_last = seq; m_has = true; }
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
bool WaitForState(sdv::ipc::EConnectState s, uint32_t ms = 2000)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mtx);
|
||||
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
|
||||
[&]{ return m_state == s; });
|
||||
}
|
||||
|
||||
bool WaitForData(uint32_t ms = 2000)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mtx);
|
||||
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
|
||||
[&]{ return m_has; });
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> Data()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mtx);
|
||||
return m_last;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mtx;
|
||||
std::condition_variable m_cv;
|
||||
sdv::ipc::EConnectState m_state{sdv::ipc::EConnectState::uninitialized};
|
||||
sdv::sequence<sdv::pointer<uint8_t>> m_last;
|
||||
bool m_has{false};
|
||||
};
|
||||
|
||||
// Helper to create server + client
|
||||
struct TunnelPair
|
||||
{
|
||||
sdv::TObjectPtr serverObj;
|
||||
sdv::ipc::IConnect* server = nullptr;
|
||||
|
||||
sdv::TObjectPtr clientObj;
|
||||
sdv::ipc::IConnect* client = nullptr;
|
||||
};
|
||||
|
||||
static TunnelPair CreateTunnelPair(CSocketsTunnelChannelMgnt& mgr,
|
||||
const std::string& cs)
|
||||
{
|
||||
TunnelPair out;
|
||||
out.serverObj = mgr.Access(cs);
|
||||
out.server = out.serverObj ?
|
||||
out.serverObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
|
||||
|
||||
out.clientObj = mgr.Access(cs);
|
||||
out.client = out.clientObj ?
|
||||
out.clientObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// Manager instantiate + lifecycle
|
||||
TEST(WinTunnelChannelMgnt, InstantiateAndLifecycle)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
|
||||
EXPECT_NO_THROW(mgr.Initialize(""));
|
||||
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
|
||||
|
||||
mgr.SetOperationMode(sdv::EOperationMode::configuring);
|
||||
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::configuring);
|
||||
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
|
||||
|
||||
mgr.Shutdown();
|
||||
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
|
||||
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Basic connect/disconnect using manager (server + client)
|
||||
TEST(WinTunnelChannelMgnt, BasicConnectDisconnect)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
|
||||
app.SetRunningMode();
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string uds = Unique("tunnel_mgr_basic");
|
||||
const std::string cs = "proto=tunnel;path=" + uds + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
ASSERT_FALSE(ep.ssConnectString.empty());
|
||||
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelMgrTestReceiver sr, cr;
|
||||
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
|
||||
EXPECT_TRUE(pair.server->WaitForConnection(5000));
|
||||
EXPECT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Simple hello (header stripped)
|
||||
TEST(WinTunnelChannelMgnt, DataPath_SimpleHello_ViaManager)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string cs = "proto=tunnel;path=" + Unique("hello_mgr") + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelMgrTestReceiver sr, cr;
|
||||
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
|
||||
ASSERT_TRUE(pair.server->WaitForConnection(5000));
|
||||
ASSERT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
sdv::pointer<uint8_t> msg;
|
||||
msg.resize(5);
|
||||
memcpy(msg.get(), "hello", 5);
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seq;
|
||||
seq.push_back(msg);
|
||||
|
||||
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
|
||||
ASSERT_NE(sender, nullptr);
|
||||
|
||||
EXPECT_TRUE(sender->SendData(seq));
|
||||
ASSERT_TRUE(sr.WaitForData(3000));
|
||||
|
||||
auto recv = sr.Data();
|
||||
ASSERT_EQ(recv.size(), 1u);
|
||||
ASSERT_EQ(recv[0].size(), 5u);
|
||||
EXPECT_EQ(memcmp(recv[0].get(), "hello", 5), 0);
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Multi-chunk
|
||||
TEST(WinTunnelChannelMgnt, DataPath_MultiChunk_ViaManager)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string cs = "proto=tunnel;path=" + Unique("mc_mgr") + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelMgrTestReceiver sr, cr;
|
||||
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
ASSERT_TRUE(pair.server->WaitForConnection(5000));
|
||||
ASSERT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
sdv::pointer<uint8_t> a, b;
|
||||
a.resize(3);
|
||||
memcpy(a.get(), "sdv", 3);
|
||||
b.resize(9);
|
||||
memcpy(b.get(), "framework", 9);
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seq;
|
||||
seq.push_back(a);
|
||||
seq.push_back(b);
|
||||
|
||||
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
|
||||
ASSERT_NE(sender, nullptr);
|
||||
|
||||
EXPECT_TRUE(sender->SendData(seq));
|
||||
ASSERT_TRUE(sr.WaitForData(3000));
|
||||
|
||||
auto recv = sr.Data();
|
||||
ASSERT_EQ(recv.size(), 2u);
|
||||
EXPECT_EQ(memcmp(recv[0].get(), "sdv", 3), 0);
|
||||
EXPECT_EQ(memcmp(recv[1].get(), "framework", 9), 0);
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Header stripping invariant
|
||||
TEST(WinTunnelChannelMgnt, HeaderStrippedInvariant)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string cs ="proto=tunnel;path=" + Unique("hdr_mgr") + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelMgrTestReceiver sr, cr;
|
||||
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
|
||||
ASSERT_TRUE(pair.server->WaitForConnection(5000));
|
||||
ASSERT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
const char* msg = "HDR_TEST";
|
||||
const size_t len = strlen(msg);
|
||||
|
||||
sdv::pointer<uint8_t> buf;
|
||||
buf.resize(len);
|
||||
memcpy(buf.get(), msg, len);
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seq;
|
||||
seq.push_back(buf);
|
||||
|
||||
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
|
||||
ASSERT_NE(sender, nullptr);
|
||||
|
||||
EXPECT_TRUE(sender->SendData(seq));
|
||||
ASSERT_TRUE(sr.WaitForData(3000));
|
||||
|
||||
auto recv = sr.Data();
|
||||
ASSERT_EQ(recv.size(), 1u);
|
||||
ASSERT_EQ(recv[0].size(), len);
|
||||
EXPECT_EQ(memcmp(recv[0].get(), msg, len), 0);
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Long path normalization
|
||||
TEST(WinTunnelChannelMgnt, CreateEndpoint_LongPath_Normalized)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
std::string veryLong(200, 'A');
|
||||
const std::string raw ="C:\\Users\\" + veryLong + "\\AppData\\Local\\sdv\\tunnel_long_" + RandomHex() + ".sock";
|
||||
const std::string cs = "proto=tunnel;path=" + raw + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
ASSERT_FALSE(ep.ssConnectString.empty());
|
||||
|
||||
// path must be normalized (basename only)
|
||||
EXPECT_NE(ep.ssConnectString.find("path=tunnel_long_"), std::string::npos);
|
||||
|
||||
sdv::TObjectPtr obj = mgr.Access(ep.ssConnectString);
|
||||
auto* server = obj.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(server, nullptr);
|
||||
|
||||
CTunnelMgrTestReceiver sr;
|
||||
server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(server);
|
||||
|
||||
// boot client
|
||||
sdv::TObjectPtr cobj = mgr.Access(ep.ssConnectString);
|
||||
auto* client = cobj.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(client, nullptr);
|
||||
|
||||
CTunnelMgrTestReceiver cr;
|
||||
client->AsyncConnect(&cr);
|
||||
|
||||
EXPECT_TRUE(server->WaitForConnection(5000));
|
||||
EXPECT_TRUE(client->WaitForConnection(5000));
|
||||
|
||||
client->Disconnect();
|
||||
server->Disconnect();
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
454
tests/unit_tests/win_tunnel/win_tunnel_connect_tests.cpp
Normal file
454
tests/unit_tests/win_tunnel/win_tunnel_connect_tests.cpp
Normal file
@@ -0,0 +1,454 @@
|
||||
/********************************************************************************
|
||||
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Apache License Version 2.0 which is available at
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Denisa Ros - initial API and implementation
|
||||
********************************************************************************/
|
||||
#include "gtest/gtest.h"
|
||||
#include <support/app_control.h>
|
||||
#include <interfaces/ipc.h>
|
||||
|
||||
// Include transport and tunnel manager
|
||||
#include "../../../sdv_services/uds_win_tunnel/channel_mgnt.h"
|
||||
#include "../../../sdv_services/uds_win_tunnel/connection.h"
|
||||
|
||||
// Include UDS Windows helpers for path generation
|
||||
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h" // ONLY FOR path helpers (MakeShortUdsPath)
|
||||
#include "../../../sdv_services/uds_win_sockets/connection.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <afunix.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
// Utility namespace from original tests
|
||||
namespace test_utils
|
||||
{
|
||||
inline std::string Expand(const std::string& in)
|
||||
{
|
||||
if (in.find('%') == std::string::npos)
|
||||
return in;
|
||||
char buf[4096] = {};
|
||||
DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, sizeof(buf));
|
||||
return (n > 0 && n < sizeof(buf)) ? std::string(buf) : in;
|
||||
}
|
||||
|
||||
inline void EnsureParentDir(const std::string& fullPath)
|
||||
{
|
||||
auto pos = fullPath.find_last_of("\\/");
|
||||
if (pos == std::string::npos)
|
||||
return;
|
||||
std::string dir = fullPath.substr(0, pos);
|
||||
CreateDirectoryA(dir.c_str(), nullptr);
|
||||
}
|
||||
|
||||
inline std::string MakeShortUdsPath(const char* name)
|
||||
{
|
||||
std::string base = R"(%LOCALAPPDATA%\sdv\)";
|
||||
base = Expand(base);
|
||||
EnsureParentDir(base);
|
||||
return base + name;
|
||||
}
|
||||
|
||||
inline std::string RandomHex()
|
||||
{
|
||||
std::mt19937_64 rng{std::random_device{}()};
|
||||
std::uniform_int_distribution<uint64_t> dist;
|
||||
std::ostringstream oss;
|
||||
oss << std::hex << dist(rng);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
inline std::string UniqueUds(const char* prefix)
|
||||
{
|
||||
return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str());
|
||||
}
|
||||
|
||||
inline void SpinUntilServerArmed(sdv::ipc::IConnect* server, uint32_t maxWaitMs = 300)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto deadline = steady_clock::now() + milliseconds(maxWaitMs);
|
||||
while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized &&
|
||||
steady_clock::now() < deadline)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
inline void SleepTiny(uint32_t ms = 20)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
}
|
||||
|
||||
} // namespace test_utils
|
||||
|
||||
using namespace test_utils;
|
||||
|
||||
// Unified test receiver (status + payload), identical to UDS tests
|
||||
class CTunnelTestReceiver :
|
||||
public sdv::IInterfaceAccess,
|
||||
public sdv::ipc::IConnectEventCallback,
|
||||
public sdv::ipc::IDataReceiveCallback
|
||||
{
|
||||
public:
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
void SetConnectState(sdv::ipc::EConnectState s) override
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mtx);
|
||||
m_state = s;
|
||||
}
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& seq) override
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mtx);
|
||||
m_data = seq;
|
||||
m_hasData = true;
|
||||
}
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
bool WaitForStatus(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mtx);
|
||||
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
|
||||
[&]{ return m_state == expected; });
|
||||
}
|
||||
|
||||
bool WaitForData(uint32_t ms = 2000)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mtx);
|
||||
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
|
||||
[&]{ return m_hasData; });
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> Data()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mtx);
|
||||
return m_data;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mtx;
|
||||
std::condition_variable m_cv;
|
||||
sdv::ipc::EConnectState m_state{sdv::ipc::EConnectState::uninitialized};
|
||||
sdv::sequence<sdv::pointer<uint8_t>> m_data;
|
||||
bool m_hasData{false};
|
||||
};
|
||||
|
||||
// Helper to create server + client via manager
|
||||
struct TunnelPair
|
||||
{
|
||||
sdv::TObjectPtr serverObj;
|
||||
sdv::ipc::IConnect* server = nullptr;
|
||||
|
||||
sdv::TObjectPtr clientObj;
|
||||
sdv::ipc::IConnect* client = nullptr;
|
||||
};
|
||||
|
||||
static TunnelPair CreateTunnelPair(CSocketsTunnelChannelMgnt& mgr, const std::string& cs)
|
||||
{
|
||||
TunnelPair out;
|
||||
|
||||
out.serverObj = mgr.Access(cs);
|
||||
out.server = out.serverObj ? out.serverObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
|
||||
|
||||
out.clientObj = mgr.Access(cs);
|
||||
out.client = out.clientObj ? out.clientObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// TEST SUITE START
|
||||
// Instantiate manager
|
||||
TEST(WinTunnelIPC, InstantiateManager)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
EXPECT_NO_THROW(mgr.Initialize(""));
|
||||
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
|
||||
|
||||
EXPECT_NO_THROW(mgr.Shutdown());
|
||||
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
|
||||
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Basic connect/disconnect
|
||||
TEST(WinTunnelIPC, BasicConnectDisconnect)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
|
||||
app.SetRunningMode();
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string path = UniqueUds("tunnel_basic");
|
||||
const std::string cs = "proto=tunnel;path=" + path + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
ASSERT_FALSE(ep.ssConnectString.empty());
|
||||
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelTestReceiver sr, cr;
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
|
||||
EXPECT_TRUE(pair.server->WaitForConnection(5000));
|
||||
EXPECT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Simple data (hello)
|
||||
TEST(WinTunnelIPC, DataPath_SimpleHello_Tunnel)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string cs = "proto=tunnel;path=" + UniqueUds("hello_t") + ";";
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelTestReceiver sr, cr;
|
||||
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
ASSERT_TRUE(pair.server->WaitForConnection(5000));
|
||||
ASSERT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
// Build payload = "hello"
|
||||
sdv::pointer<uint8_t> p;
|
||||
p.resize(5);
|
||||
memcpy(p.get(), "hello", 5);
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seq;
|
||||
seq.push_back(p);
|
||||
|
||||
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
|
||||
ASSERT_NE(sender, nullptr);
|
||||
EXPECT_TRUE(sender->SendData(seq));
|
||||
|
||||
EXPECT_TRUE(sr.WaitForData(3000));
|
||||
auto recv = sr.Data();
|
||||
|
||||
// header eliminated
|
||||
ASSERT_EQ(recv.size(), 1u);
|
||||
ASSERT_EQ(recv[0].size(), 5u);
|
||||
EXPECT_EQ(memcmp(recv[0].get(), "hello", 5), 0);
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Multi-chunk
|
||||
TEST(WinTunnelIPC, DataPath_MultiChunk_Tunnel)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string cs = "proto=tunnel;path=" + UniqueUds("mc_t") + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelTestReceiver sr, cr;
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
ASSERT_TRUE(pair.server->WaitForConnection(5000));
|
||||
ASSERT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
sdv::pointer<uint8_t> p1, p2;
|
||||
p1.resize(3);
|
||||
memcpy(p1.get(), "sdv", 3);
|
||||
p2.resize(9);
|
||||
memcpy(p2.get(), "framework", 9);
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seq;
|
||||
seq.push_back(p1);
|
||||
seq.push_back(p2);
|
||||
|
||||
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
|
||||
ASSERT_NE(sender, nullptr);
|
||||
EXPECT_TRUE(sender->SendData(seq));
|
||||
|
||||
ASSERT_TRUE(sr.WaitForData(3000));
|
||||
auto recv = sr.Data();
|
||||
|
||||
ASSERT_EQ(recv.size(), 2u);
|
||||
EXPECT_EQ(memcmp(recv[0].get(), "sdv", 3), 0);
|
||||
EXPECT_EQ(memcmp(recv[1].get(), "framework", 9), 0);
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Large fragmentation
|
||||
TEST(WinTunnelIPC, DataPath_LargePayload_Tunnel)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string cs = "proto=tunnel;path=" + UniqueUds("big_t") + ";";
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelTestReceiver sr, cr;
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
ASSERT_TRUE(pair.server->WaitForConnection(5000));
|
||||
ASSERT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
const size_t N = 256 * 1024;
|
||||
sdv::pointer<uint8_t> payload;
|
||||
payload.resize(N);
|
||||
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
payload.get()[i] = uint8_t(i & 0xFF);
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seq;
|
||||
seq.push_back(payload);
|
||||
|
||||
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
|
||||
ASSERT_NE(sender, nullptr);
|
||||
EXPECT_TRUE(sender->SendData(seq));
|
||||
|
||||
ASSERT_TRUE(sr.WaitForData(5000));
|
||||
auto recv = sr.Data();
|
||||
|
||||
ASSERT_EQ(recv.size(), 1u);
|
||||
ASSERT_EQ(recv[0].size(), N);
|
||||
EXPECT_EQ(memcmp(recv[0].get(), payload.get(), N), 0);
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Header stripping test (tunnel feature)
|
||||
TEST(WinTunnelIPC, DataPath_HeaderStripped_Tunnel)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
|
||||
app.SetRunningMode();
|
||||
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
|
||||
const std::string cs =
|
||||
"proto=tunnel;path=" + UniqueUds("headerstrip") + ";";
|
||||
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
|
||||
|
||||
ASSERT_NE(pair.server, nullptr);
|
||||
ASSERT_NE(pair.client, nullptr);
|
||||
|
||||
CTunnelTestReceiver sr, cr;
|
||||
|
||||
pair.server->AsyncConnect(&sr);
|
||||
SpinUntilServerArmed(pair.server);
|
||||
|
||||
pair.client->AsyncConnect(&cr);
|
||||
ASSERT_TRUE(pair.server->WaitForConnection(5000));
|
||||
ASSERT_TRUE(pair.client->WaitForConnection(5000));
|
||||
|
||||
const char* msg = "HEADER_TEST";
|
||||
const size_t len = strlen(msg);
|
||||
|
||||
sdv::pointer<uint8_t> p;
|
||||
p.resize(len);
|
||||
memcpy(p.get(), msg, len);
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seq;
|
||||
seq.push_back(p);
|
||||
|
||||
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
|
||||
ASSERT_NE(sender, nullptr);
|
||||
|
||||
EXPECT_TRUE(sender->SendData(seq));
|
||||
ASSERT_TRUE(sr.WaitForData(3000));
|
||||
|
||||
auto recv = sr.Data();
|
||||
|
||||
// check header stripped
|
||||
ASSERT_EQ(recv.size(), 1u);
|
||||
ASSERT_EQ(recv[0].size(), len);
|
||||
EXPECT_EQ(memcmp(recv[0].get(), msg, len), 0);
|
||||
|
||||
pair.client->Disconnect();
|
||||
pair.server->Disconnect();
|
||||
mgr.Shutdown();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
164
tests/unit_tests/win_tunnel/win_tunnel_negative_edge_tests.cpp
Normal file
164
tests/unit_tests/win_tunnel/win_tunnel_negative_edge_tests.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/********************************************************************************
|
||||
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Apache License Version 2.0 which is available at
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Denisa Ros - initial API and implementation
|
||||
********************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <support/app_control.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include "../../../sdv_services/uds_win_tunnel/channel_mgnt.h"
|
||||
#include "../../../sdv_services/uds_win_tunnel/connection.h"
|
||||
|
||||
// Include UDS Windows helpers for path generation
|
||||
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h" // ONLY FOR path helpers (MakeShortUdsPath)
|
||||
#include "../../../sdv_services/uds_win_sockets/connection.h"
|
||||
|
||||
// Helper for unique UDS path
|
||||
template<typename T=void>
|
||||
static std::string UniqueUds(const char* prefix) {
|
||||
char buf[64];
|
||||
sprintf(buf, "%s_%08x.sock", prefix, rand());
|
||||
return std::string("%LOCALAPPDATA%/sdv/") + buf;
|
||||
}
|
||||
|
||||
// Negative: invalid connect string
|
||||
TEST(WinTunnelNegative, InvalidConnectString)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
|
||||
ViewFilter = "Fatal")toml"));
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
// Missing proto, missing path
|
||||
auto obj = mgr.Access("role=server;");
|
||||
EXPECT_EQ(obj, nullptr);
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Negative: connect to non-existent server
|
||||
TEST(WinTunnelNegative, ConnectToNonExistentServer)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
|
||||
ViewFilter = "Fatal")toml"));
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
std::string cs = "proto=tunnel;path=" + UniqueUds("no_server") + ";";
|
||||
auto obj = mgr.Access(cs);
|
||||
if (!obj) {
|
||||
SUCCEED() << "Client object is nullptr as expected when server does not exist";
|
||||
} else {
|
||||
auto* client = obj->GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(client, nullptr);
|
||||
EXPECT_FALSE(client->WaitForConnection(200));
|
||||
}
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Edge: double disconnect
|
||||
TEST(WinTunnelEdge, DoubleDisconnect)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
std::string cs = "proto=tunnel;path=" + UniqueUds("double_disc") + ";";
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto obj = mgr.Access(ep.ssConnectString);
|
||||
auto* conn = obj->GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(conn, nullptr);
|
||||
conn->Disconnect();
|
||||
// Should not crash or throw
|
||||
EXPECT_NO_THROW(conn->Disconnect());
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Edge: repeated connect/disconnect cycles (recreate endpoint each time)
|
||||
TEST(WinTunnelEdge, RepeatedConnectDisconnect)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
|
||||
ViewFilter = "Fatal")toml"));
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
std::string cs = "proto=tunnel;path=" + UniqueUds("repeat") + ";";
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto ep = mgr.CreateEndpoint(cs); // recreate endpoint every time
|
||||
sdv::TObjectPtr obj = mgr.Access(ep.ssConnectString);
|
||||
auto* conn = obj.GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(conn, nullptr);
|
||||
conn->AsyncConnect(nullptr);
|
||||
conn->WaitForConnection(200);
|
||||
conn->Disconnect();
|
||||
// obj goes out of scope and cleans up
|
||||
}
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Edge: simultaneous multiple clients
|
||||
TEST(WinTunnelEdge, MultipleClients)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(""));
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
std::string cs = "proto=tunnel;path=" + UniqueUds("multi_client") + ";";
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto obj1 = mgr.Access(ep.ssConnectString);
|
||||
auto obj2 = mgr.Access(ep.ssConnectString);
|
||||
auto* c1 = obj1->GetInterface<sdv::ipc::IConnect>();
|
||||
auto* c2 = obj2->GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(c1, nullptr);
|
||||
ASSERT_NE(c2, nullptr);
|
||||
c1->AsyncConnect(nullptr);
|
||||
c2->AsyncConnect(nullptr);
|
||||
c1->WaitForConnection(500);
|
||||
c2->WaitForConnection(500);
|
||||
c1->Disconnect();
|
||||
c2->Disconnect();
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Edge: callback throws exception (should not crash)
|
||||
class ThrowingReceiver : public sdv::IInterfaceAccess, public sdv::ipc::IDataReceiveCallback, public sdv::ipc::IConnectEventCallback {
|
||||
public:
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
void SetConnectState(sdv::ipc::EConnectState) override { throw std::runtime_error("SetConnectState fail"); }
|
||||
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>&) override { throw std::runtime_error("ReceiveData fail"); }
|
||||
};
|
||||
|
||||
TEST(WinTunnelEdge, CallbackThrows)
|
||||
{
|
||||
sdv::app::CAppControl app;
|
||||
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
|
||||
ViewFilter = "Fatal")toml"));
|
||||
CSocketsTunnelChannelMgnt mgr;
|
||||
mgr.Initialize("");
|
||||
mgr.SetOperationMode(sdv::EOperationMode::running);
|
||||
std::string cs = "proto=tunnel;path=" + UniqueUds("cb_throw") + ";";
|
||||
auto ep = mgr.CreateEndpoint(cs);
|
||||
auto obj = mgr.Access(ep.ssConnectString);
|
||||
auto* conn = obj->GetInterface<sdv::ipc::IConnect>();
|
||||
ASSERT_NE(conn, nullptr);
|
||||
ThrowingReceiver rcv;
|
||||
// Should not crash even if callback throws
|
||||
EXPECT_NO_THROW(conn->AsyncConnect(&rcv));
|
||||
conn->Disconnect();
|
||||
app.Shutdown();
|
||||
}
|
||||
Reference in New Issue
Block a user