Skip to content

[Tests] Add thunder_test_support static library for plugin integration testing#2084

Open
smanes0213 wants to merge 13 commits intomasterfrom
development/Thunder_Test_Lib
Open

[Tests] Add thunder_test_support static library for plugin integration testing#2084
smanes0213 wants to merge 13 commits intomasterfrom
development/Thunder_Test_Lib

Conversation

@smanes0213
Copy link
Copy Markdown
Contributor

Adds a static library (libthunder_test_support.a) that embeds the Thunder PluginHost::Server into a linkable archive. Test binaries link against this library to boot a real Thunder runtime without launching the standalone daemon, enabling integration testing of Thunder plugins using GTest/GMock.

Copilot AI review requested due to automatic review settings March 17, 2026 11:04
@smanes0213 smanes0213 marked this pull request as draft March 17, 2026 11:04
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 17, 2026

PR Preview Action v1.1.1-25-g59e77e4
🛫 Deployed preview to https://rdkcentral.github.io/Thunder/pr-preview/pr-2084/
on branch gh-pages at 2026-04-16 09:33 UTC

@rdkcmf-jenkins
Copy link
Copy Markdown
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 4 files pending identification.

  • Protex Server Path: /home/blackduck/github/Thunder/2084/rdkcentral/Thunder

  • Commit: 47f1530

Report detail: gist'

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new thunder_test_support static library intended to enable in-process integration testing of Thunder plugins by embedding PluginHost::Server into test binaries (optionally built via ENABLE_TEST_RUNTIME).

Changes:

  • Introduces Thunder::TestCore::ThunderTestRuntime API to bootstrap an embedded Thunder server, invoke JSON-RPC in-process, and query COM-RPC interfaces.
  • Adds a new Tests/test_support/ CMake target (thunder_test_support) that statically compiles key Thunder server sources (excluding PluginHost.cpp / main()).
  • Adds end-user documentation describing architecture, build flags, and usage patterns for integration tests.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Tests/test_support/ThunderTestRuntime.h Public API for starting/stopping an embedded server and calling JSON-RPC / COM-RPC from tests
Tests/test_support/ThunderTestRuntime.cpp Implements temp-dir lifecycle, config generation/parsing, server bootstrap, and invocation helpers
Tests/test_support/Module.cpp Declares module name for tracing/logging for the static library
Tests/test_support/CMakeLists.txt Builds/installs the new thunder_test_support static library and links required Thunder components
Tests/CMakeLists.txt Adds ENABLE_TEST_RUNTIME option and conditionally includes test_support
docs/ThunderTestSupport/ThunderTestSupport.md Adds detailed documentation for the test support library

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

void ThunderTestRuntime::CleanupDirectories() const
{
if (!_tempDir.empty()) {
Core::Directory(_tempDir.c_str()).Destroy();
Comment on lines +66 to +90
std::ostringstream json;
json << "{"
<< "\"port\":0,"
<< "\"binding\":\"127.0.0.1\","
<< "\"idletime\":180,"
<< "\"persistentpath\":\"" << _tempDir << "persistent/\","
<< "\"volatilepath\":\"" << _tempDir << "volatile/\","
<< "\"datapath\":\"" << _tempDir << "data/\","
<< "\"systempath\":\"" << systemPath << "\","
<< "\"proxystubpath\":\"" << proxyStubPath << "\","
<< "\"communicator\":\"" << _tempDir << "communicator\","
<< "\"plugins\":[";

for (size_t i = 0; i < plugins.size(); ++i) {
const auto& p = plugins[i];
if (i > 0) json << ",";
json << "{"
<< "\"callsign\":\"" << p.callsign << "\","
<< "\"locator\":\"" << p.locator << "\","
<< "\"classname\":\"" << p.classname << "\","
<< "\"startuporder\":" << p.startuporder << ","
<< "\"autostart\":" << (p.autostart ? "true" : "false");

if (!p.configuration.empty()) {
json << ",\"configuration\":" << p.configuration;
Comment on lines +47 to +50
Core::Directory(_tempDir.c_str()).Create();
Core::Directory((_tempDir + "persistent/").c_str()).Create();
Core::Directory((_tempDir + "volatile/").c_str()).Create();
Core::Directory((_tempDir + "data/").c_str()).Create();
Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
Comment on lines +49 to +52
#ifndef MODULE_NAME
#define MODULE_NAME Application
#endif

Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
//
// TEST_F(MyTest, JsonRpc) {
// string resp;
// _runtime.InvokeJSONRPC("Callsign", "Method", params, resp);
Comment on lines +34 to +42
│ │ ├── PluginServer │ │ │ │ │ ├── Controller │ │
│ │ ├── SystemInfo │ │ │ │ │ ├── PluginServer │ │
│ │ └── HTTP/WS Listener │ │ │ │ │ ├── SystemInfo │ │
│ │ ↓ │ │ │ │ │ └── (no HTTP listener) │ │
│ │ Plugin .so (dynamic load) │ │ │ │ └── Plugin .so (dynamic load) │ │
│ └───────────────────────────────┘ │ │ └─────────────────────────────────┘ │
│ │ │ │
│ Communication: HTTP/WS/COMRPC │ │ Communication: Direct in-process calls │
└─────────────────────────────────────┘ └─────────────────────────────────────────┘
@mhughesacn
Copy link
Copy Markdown

Hi @smanes0213 : This is draft so not looking closely, but if you append:

Copyright 2024 RDK Management
Licensed under the Apache License, Version 2.0

to the end of NOTICE in top level, I will clear off blackduck. Thank you.

@smanes0213
Copy link
Copy Markdown
Contributor Author

Hi @smanes0213 : This is draft so not looking closely, but if you append:

Copyright 2024 RDK Management
Licensed under the Apache License, Version 2.0

to the end of NOTICE in top level, I will clear off blackduck. Thank you.

Hi @mhughesacn: The changes are not finalised yet, I have removed the license header for now. Will add the header once everything is confirmed.

Copy link
Copy Markdown
Contributor

@bramoosterhuis bramoosterhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we want a minimal main.cpp example that boots the runtime and exercises a basic JSON-RPC call against the built-in Controller — no external plugin .so needed. This would serve as a smoke test to verify the library links and initialises correctly end-to-end.

Comment thread Tests/test_support/ThunderTestRuntime.h Outdated

// Invoke a JSON-RPC method on a loaded plugin.
// Method format: "Callsign.Version.method" (e.g. "Counter.1.increment")
uint32_t InvokeJSONRPC(const string& callsign, const string& method,
Copy link
Copy Markdown
Contributor

@bramoosterhuis bramoosterhuis Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a specific reason for having both callsign and method as separate parameters here?

As it stands, the callsign is already embedded in the fully-qualified method string ("Counter.1.increment"), so the caller ends up saying the same thing twice — and there's nothing stopping them from passing mismatched values, which would produce a confusing failure.

Would it make sense to drop the callsign parameter and derive it from method in the implementation instead?

see Core::JSONRPC::Message , const string callsign = Core::JSONRPC::Message::Callsign(method);

Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
string CommunicatorPath() const;

// Stop the server, release config, and clean up temp directories.
void Shutdown();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a reason for pairing Initialize() with Shutdown() rather than following one of Thunder's own naming conventions?

Thunder is consistent elsewhere — IPlugin uses Initialize()/Deinitialize(), and PluginHost::Server uses Open()/Close(). Using Shutdown() introduces a third scheme that fits neither, which feels slightly off for a library that lives inside the Thunder repo and targets Thunder plugin developers who are already familiar with those conventions.

Would Initialize()/Deinitialize() or Open()/Close() be a better fit here?

Comment thread Tests/test_support/CMakeLists.txt
Comment thread Tests/test_support/Module.cpp Outdated

#include <core/core.h>

MODULE_NAME_DECLARATION(BUILD_REFERENCE)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thunder has a macro for static archives — MODULE_NAME_ARCHIVE_DECLARATION. Using it in Module.cpp instead of MODULE_NAME_DECLARATION(BUILD_REFERENCE) avoids conflicting definitions of ModuleBuildRef(), GetModuleServices() and SetModuleServices() with the consumer's own MODULE_NAME_DECLARATION. It also removes the need for the #ifndef guard here.

#define MODULE_NAME ThunderTestRuntime
#include <core/core.h>
MODULE_NAME_ARCHIVE_DECLARATION

Comment thread Tests/test_support/ThunderTestRuntime.cpp
Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
#define MODULE_NAME Application
#endif

#include <plugins/plugins.h>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: <plugins/plugins.h> can be replaced with <core/core.h> and <plugins/IShell.h> — the header only needs IShell and Core this avoids pulling in the entire plugins umbrella header into every consumer TU.

Comment thread cmake/common/CompileSettings.cmake Outdated
  original -Wno-psabi only setting
set CXX_STANDARD on thunder_test_support target so it respects the
  ENABLE_CXX17 flag like all other Thunder targets
Pass -DENABLE_CXX17=OFF in the smoke test CI workflow to avoid the
  SocketServer.h IteratorType move constructor bug
@MFransen69 MFransen69 marked this pull request as ready for review April 9, 2026 06:28
@MFransen69 MFransen69 requested review from Copilot and sebaszm April 9, 2026 06:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new thunder_test_support static library that embeds Thunder’s PluginHost::Server into a linkable archive so GTest-based binaries can boot an in-process Thunder runtime for plugin integration testing (without launching the daemon).

Changes:

  • Adds ThunderTestRuntime wrapper (API + implementation) to generate a minimal config, start/stop the embedded server, and invoke JSON-RPC/COM-RPC.
  • Adds a smoke-test executable that links against the new library and exercises the built-in Controller plugin.
  • Integrates build/install/docs/CI support behind a new ENABLE_TEST_RUNTIME CMake option.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
Tests/test_support/ThunderTestRuntime.h Public test-runtime API (plugin list + JSON-RPC/COM-RPC helpers).
Tests/test_support/ThunderTestRuntime.cpp Runtime implementation: temp dirs, generated config, server Open/Close, invocation helpers.
Tests/test_support/CMakeLists.txt Builds/installs thunder_test_support and adds whole-archive link behavior.
Tests/test_support/Module.cpp Static-archive module symbol setup for Thunder logging/module macros.
Tests/test_support/tests/SmokeTest.cpp Smoke tests that boot the embedded server and call Controller JSON-RPC.
Tests/test_support/tests/Module.cpp Module declaration for the smoke-test binary.
Tests/test_support/tests/CMakeLists.txt Builds the smoke-test executable and links GTest + test support.
Tests/CMakeLists.txt Adds ENABLE_TEST_RUNTIME option + subdirectory.
docs/ThunderTestSupport/ThunderTestSupport.md Documentation for the new library, usage, and rationale.
.github/workflows/Test_Thunder_Test_Support.yml CI workflow to build and run the smoke test (Debug/Release).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<< "\"locator\":" << JsonEscape(p.locator) << ","
<< "\"classname\":" << JsonEscape(p.classname) << ","
<< "\"startuporder\":" << p.startuporder << ","
<< "\"autostart\":" << (p.autostart ? "true" : "false");
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin::Config does not have an autostart field (it uses startmode), so this JSON key will be ignored during config parsing and plugins will always use the default startmode (Activated). This breaks the documented behavior of PluginConfig::autostart=false. Map this flag to "startmode":"Deactivated" (and optionally "startmode":"Activated" when true) or drop the field and expose startmode directly in PluginConfig.

Suggested change
<< "\"autostart\":" << (p.autostart ? "true" : "false");
<< "\"startmode\":" << JsonEscape(p.autostart ? "Activated" : "Deactivated");

Copilot uses AI. Check for mistakes.
<< "\"datapath\":" << JsonEscape(_tempDir + "data/") << ","
<< "\"systempath\":" << JsonEscape(systemPath) << ","
<< "\"proxystubpath\":" << JsonEscape(proxyStubPath) << ","
<< "\"communicator\":" << JsonEscape(_tempDir + "communicator") << ","
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated communicator NodeId omits the |<mode> suffix (e.g. |0777) used by Thunder configs to set domain-socket permissions. With the current value, the socket file permissions depend on umask and may prevent out-of-process plugin processes from connecting in some environments. Consider appending |0777 (or making it configurable) to match Thunder’s default /tmp/communicator|0777.

Suggested change
<< "\"communicator\":" << JsonEscape(_tempDir + "communicator") << ","
<< "\"communicator\":" << JsonEscape(_tempDir + "communicator|0777") << ","

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +65
json << "{"
<< "\"port\":0,"
<< "\"binding\":\"127.0.0.1\","
<< "\"idletime\":180,"
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using "port":0 will bind the listener to an OS-assigned port, but Thunder’s Config (and thus Accessor()/URLs derived from it) will still report port 0 because SocketPort::Open() does not update NodeId after binding. If any plugin/test relies on the configured accessor URL/port, this will be incorrect. Consider choosing a free port up front (or querying the bound port via getsockname() and updating the config/binder/URL accordingly) so the runtime reports the real port.

Copilot uses AI. Check for mistakes.
Comment thread Tests/test_support/CMakeLists.txt Outdated
Comment on lines +103 to +109
# Enforce --whole-archive for this target so consumers automatically pull in
# Thunder's static initializers (MODULE_NAME_DECLARATION, SERVICE_REGISTRATION).
# Scoped to this archive only via $<TARGET_FILE:...> — won't affect other libs.
target_link_options(thunder_test_support
INTERFACE
-Wl,--whole-archive $<TARGET_FILE:thunder_test_support> -Wl,--no-whole-archive
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--whole-archive/--no-whole-archive are GNU ld/LLD flags and will break on non-ELF linkers (e.g. Apple ld, MSVC) if ENABLE_TEST_RUNTIME is enabled. Please guard these link options by platform/toolchain (e.g. only on Linux/Android with GNU/Clang) and provide the equivalent flags where needed (e.g. -Wl,-force_load on Apple) or document the limitation.

Suggested change
# Enforce --whole-archive for this target so consumers automatically pull in
# Thunder's static initializers (MODULE_NAME_DECLARATION, SERVICE_REGISTRATION).
# Scoped to this archive only via $<TARGET_FILE:...> — won't affect other libs.
target_link_options(thunder_test_support
INTERFACE
-Wl,--whole-archive $<TARGET_FILE:thunder_test_support> -Wl,--no-whole-archive
)
# Enforce whole-archive semantics for this target so consumers automatically pull in
# Thunder's static initializers (MODULE_NAME_DECLARATION, SERVICE_REGISTRATION).
# Use linker-specific flags only on supported platforms/toolchains.
if(APPLE)
target_link_options(thunder_test_support
INTERFACE
"-Wl,-force_load,$<TARGET_FILE:thunder_test_support>"
)
elseif((CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") AND
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
# Scoped to this archive only via $<TARGET_FILE:...> — won't affect other libs.
target_link_options(thunder_test_support
INTERFACE
-Wl,--whole-archive $<TARGET_FILE:thunder_test_support> -Wl,--no-whole-archive
)
else()
message(WARNING
"Automatic whole-archive linking for thunder_test_support is not configured for "
"${CMAKE_SYSTEM_NAME} with ${CMAKE_CXX_COMPILER_ID}. Consumers may need to link "
"the archive with platform-specific whole-archive/force-load options to ensure "
"Thunder static initializers are included."
)
endif()

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +118
install(TARGETS thunder_test_support
ARCHIVE DESTINATION lib
)

install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/ThunderTestRuntime.h
DESTINATION include/thunder_test_support
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Install rules hardcode lib/include instead of using GNUInstallDirs variables (${CMAKE_INSTALL_LIBDIR}, ${CMAKE_INSTALL_INCLUDEDIR}) used elsewhere in the project. Using the standard variables improves portability across distros/multilib layouts.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +41
│ │ ↓ │ │ │ │ │ └── (no HTTP listener) │ │
│ │ Plugin .so (dynamic load) │ │ │ │ └── Plugin .so (dynamic load) │ │
│ └───────────────────────────────┘ │ │ └─────────────────────────────────┘ │
│ │ │ │
│ Communication: HTTP/WS/COMRPC │ │ Communication: Direct in-process calls │
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagram states “(no HTTP listener)”, but PluginHost::Server::Open() always calls _connections.Open(...) and starts the HTTP/WS listener (even if bound to localhost/port 0). Please update the documentation to reflect that the listener is still started (and clarify that tests typically bypass it via in-process dispatcher calls).

Suggested change
│ │ ↓ │ │ │ │ │ └── (no HTTP listener) │ │
│ │ Plugin .so (dynamic load) │ │ │ │ └── Plugin .so (dynamic load) │ │
│ └───────────────────────────────┘ │ │ └─────────────────────────────────┘ │
│ │ │ │
│ Communication: HTTP/WS/COMRPC │ │ Communication: Direct in-process calls │
│ │ ↓ │ │ │ │ │ └── HTTP/WS Listener │ │
│ │ Plugin .so (dynamic load) │ │ │ │ └── Plugin .so (dynamic load) │ │
│ └───────────────────────────────┘ │ │ └─────────────────────────────────┘ │
│ │ │ │
│ Communication: HTTP/WS/COMRPC │ │ Communication: In-process dispatcher │
│ │ │ calls (listener still started) │

Copilot uses AI. Check for mistakes.
### Why port 0?

Using port 0 in the config tells the OS to assign an available port, avoiding conflicts when multiple test processes run simultaneously.

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Why port 0?” section implies port 0 is a clean solution for parallel runs, but with the current implementation the configured accessor/URL will still show port 0 (the actual bound port is not propagated back into Config). This can confuse readers and may break plugins/tests that depend on the configured port. Consider documenting this caveat or switching to a strategy that discovers and records the assigned port.

Suggested change
**Caveat:** with the current implementation, the OS-assigned port is used only for binding; it is not propagated back into `Config`. As a result, config-derived accessors/URLs may still show port `0` rather than the actual bound port. This is still useful for avoiding bind conflicts in parallel runs, but tests and plugins should not rely on the configured port value when port `0` is used.

Copilot uses AI. Check for mistakes.
Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
Comment on lines +56 to +61
string callsign; // Plugin callsign (e.g. "Counter")
string locator; // Shared library name (e.g. "libThunderCounterImplementation.so")
string classname; // Class name registered via SERVICE_REGISTRATION
bool autostart = true; // Whether to auto-activate on startup
int startuporder = 50; // Activation priority (lower = earlier)
string configuration; // Optional JSON object for plugin-specific config
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PluginConfig::autostart suggests a supported way to prevent activation, but Thunder’s plugin config uses startmode (Activated/Deactivated/Unavailable) rather than an autostart boolean. Unless BuildConfigJSON() maps this flag to startmode, the public API will be misleading. Consider replacing autostart with a startmode enum in PluginConfig (or clearly document the mapping).

Suggested change
string callsign; // Plugin callsign (e.g. "Counter")
string locator; // Shared library name (e.g. "libThunderCounterImplementation.so")
string classname; // Class name registered via SERVICE_REGISTRATION
bool autostart = true; // Whether to auto-activate on startup
int startuporder = 50; // Activation priority (lower = earlier)
string configuration; // Optional JSON object for plugin-specific config
enum class StartMode {
Activated,
Deactivated,
Unavailable
};
string callsign; // Plugin callsign (e.g. "Counter")
string locator; // Shared library name (e.g. "libThunderCounterImplementation.so")
string classname; // Class name registered via SERVICE_REGISTRATION
StartMode startmode = StartMode::Activated; // Thunder plugin start mode
int startuporder = 50; // Activation priority (lower = earlier)
string configuration; // Optional JSON object for plugin-specific config

Copilot uses AI. Check for mistakes.
@rdkcmf-jenkins
Copy link
Copy Markdown
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 1 file pending identification.

  • Protex Server Path: /home/blackduck/github/Thunder/2084/rdkcentral/Thunder

  • Commit: 2e76a98

Report detail: gist'

Copilot AI review requested due to automatic review settings April 15, 2026 07:27
@rdkcmf-jenkins
Copy link
Copy Markdown
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 1 file pending identification.

  • Protex Server Path: /home/blackduck/github/Thunder/2084/rdkcentral/Thunder

  • Commit: 552ce60

Report detail: gist'

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Comment on lines +62 to +134
target_include_directories(thunder_test_support
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE
${THUNDER_SOURCE_DIR}
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/Source/plugins/generated/jsonrpc>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/Source/Thunder/generated>
)

target_link_libraries(thunder_test_support
PUBLIC
${NAMESPACE}Core::${NAMESPACE}Core
${NAMESPACE}Cryptalgo::${NAMESPACE}Cryptalgo
${NAMESPACE}COM::${NAMESPACE}COM
${NAMESPACE}Messaging::${NAMESPACE}Messaging
${NAMESPACE}WebSocket::${NAMESPACE}WebSocket
${NAMESPACE}Plugins::${NAMESPACE}Plugins
${NAMESPACE}COMProcess::${NAMESPACE}COMProcess
Threads::Threads
)

if(WARNING_REPORTING)
target_sources(thunder_test_support PRIVATE ${THUNDER_SOURCE_DIR}/WarningReportingCategories.cpp)
endif()

if(PROCESSCONTAINERS)
target_link_libraries(thunder_test_support
PUBLIC
${NAMESPACE}ProcessContainers::${NAMESPACE}ProcessContainers)
target_compile_definitions(thunder_test_support
PUBLIC
PROCESSCONTAINERS_ENABLED=1)
endif()

if(HIBERNATESUPPORT)
target_link_libraries(thunder_test_support PUBLIC
${NAMESPACE}Hibernate::${NAMESPACE}Hibernate)
target_compile_definitions(thunder_test_support PUBLIC
HIBERNATE_SUPPORT_ENABLED=1)
endif()

# Enforce whole-archive semantics for this target so consumers automatically pull in
# Thunder's static initializers (MODULE_NAME_DECLARATION, SERVICE_REGISTRATION).
# Use linker-specific flags only on supported platforms/toolchains.
if(APPLE)
target_link_options(thunder_test_support
INTERFACE
"-Wl,-force_load,$<TARGET_FILE:thunder_test_support>"
)
elseif((CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") AND
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
# Scoped to this archive only via $<TARGET_FILE:...> — won't affect other libs.
target_link_options(thunder_test_support
INTERFACE
-Wl,--whole-archive $<TARGET_FILE:thunder_test_support> -Wl,--no-whole-archive
)
else()
message(WARNING
"Automatic whole-archive linking for thunder_test_support is not configured for "
"${CMAKE_SYSTEM_NAME} with ${CMAKE_CXX_COMPILER_ID}. Consumers may need to link "
"the archive with platform-specific whole-archive/force-load options to ensure "
"Thunder static initializers are included."
)
endif()

# --- Install rules ---
install(TARGETS thunder_test_support
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/ThunderTestRuntime.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/thunder_test_support
)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs and this CMake target describe consumers linking against the thunder_test_support CMake target to pick up INTERFACE whole-archive link options, but the install rules here only install the .a and header—there is no target export / CMake package config for thunder_test_support, and the include dirs also lack an INSTALL_INTERFACE. If this library is intended for external consumers via find_package, it should be exported and install-interface include dirs should be set; otherwise the docs should avoid implying the imported target exists / carries link options.

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +178
// Create unique temp directory for this test run
char tempTemplate[] = "/tmp/thunder_test_XXXXXX";
char* tempResult = mkdtemp(tempTemplate);
if (tempResult == nullptr) {
return Core::ERROR_GENERAL;
}
_tempDir = string(tempResult) + "/";
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialize() hardcodes a POSIX /tmp/... path and uses mkdtemp(), which will not build on non-POSIX platforms (e.g., Windows) if ENABLE_TEST_RUNTIME is enabled. Either gate this feature to POSIX in CMake (with a clear message when enabled on unsupported platforms) or provide a platform-specific temp directory implementation.

Copilot uses AI. Check for mistakes.
Comment thread Tests/CMakeLists.txt Outdated
endif()

if(ENABLE_TEST_RUNTIME)
add_subdirectory(test_support)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ENABLE_TEST_RUNTIME can currently be enabled on any platform, but Tests/test_support/ThunderTestRuntime.cpp uses POSIX-only APIs (mkdtemp) and hardcoded /tmp paths. Consider adding a platform guard here (e.g., only add the test_support subdirectory on POSIX platforms, or emit a fatal error if the option is ON on unsupported systems) to avoid configuration/build failures.

Suggested change
add_subdirectory(test_support)
if(UNIX)
add_subdirectory(test_support)
else()
message(FATAL_ERROR "ENABLE_TEST_RUNTIME is only supported on POSIX/UNIX platforms because Tests/test_support uses POSIX-only APIs.")
endif()

Copilot uses AI. Check for mistakes.

```cpp
#include <gtest/gtest.h>
#include "ThunderTestRuntime.h"
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage snippet includes #include "ThunderTestRuntime.h", but the install rule places the header under ${CMAKE_INSTALL_INCLUDEDIR}/thunder_test_support/ThunderTestRuntime.h. For installed consumers, the include should match the installed layout (e.g. include via the thunder_test_support/ prefix) unless you also ensure the install exports an include directory that points directly at the thunder_test_support folder.

Suggested change
#include "ThunderTestRuntime.h"
#include <thunder_test_support/ThunderTestRuntime.h>

Copilot uses AI. Check for mistakes.
#### 2. Test fixture

```cpp
#include <gtest/gtest.h>
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example test fixture does not show defining MODULE_NAME before including ThunderTestRuntime.h, but the installed header currently includes <core/core.h> which emits a hard error unless MODULE_NAME is defined in that translation unit. Either update the example to show the required #define MODULE_NAME ... (or including a local Module.h that defines it) or adjust the public header to avoid pulling in <core/core.h> so consumers don't need to manage MODULE_NAME in every .cpp.

Suggested change
#include <gtest/gtest.h>
#include <gtest/gtest.h>
// `ThunderTestRuntime.h` currently pulls in Thunder core headers that require
// `MODULE_NAME` to be defined in this translation unit. If your project already
// has a local Module.h that defines it, include that instead of defining it here.
#define MODULE_NAME MyPluginTest

Copilot uses AI. Check for mistakes.
Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
Comment on lines +32 to +33

#include <core/core.h>
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThunderTestRuntime.h includes <core/core.h>, which hard-requires MODULE_NAME to be defined in every translation unit that includes this header. This makes the public API cumbersome to consume and also contradicts the usage example here (it doesn't define MODULE_NAME). Consider removing the <core/core.h> include from the public header (include only the minimal core headers you need, e.g. for Core::ProxyType and error codes), or explicitly document that consumers must #define MODULE_NAME before including this header in each .cpp that includes it.

Suggested change
#include <core/core.h>
#ifndef MODULE_NAME
#define MODULE_NAME ThunderTestRuntime
#define THUNDER_TEST_RUNTIME_DEFINED_MODULE_NAME
#endif
#include <core/core.h>
#ifdef THUNDER_TEST_RUNTIME_DEFINED_MODULE_NAME
#undef THUNDER_TEST_RUNTIME_DEFINED_MODULE_NAME
#undef MODULE_NAME
#endif

Copilot uses AI. Check for mistakes.
PRIVATE
NAMESPACE=${NAMESPACE}
APPLICATION_NAME=ThunderTestRuntime
MODULE_NAME=ThunderTestRuntime
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MODULE_NAME is set both via target_compile_definitions(... MODULE_NAME=ThunderTestRuntime) and again via #define MODULE_NAME ThunderTestRuntime in Tests/test_support/Module.cpp. This can trigger macro redefinition warnings and also forces the module name for all compiled Thunder server sources in this archive. Prefer defining MODULE_NAME only in the dedicated Module.cpp (and drop the compile definition here), similar to how the main Thunder target sets APPLICATION_NAME/THREADPOOL_COUNT but not MODULE_NAME.

Suggested change
MODULE_NAME=ThunderTestRuntime

Copilot uses AI. Check for mistakes.

set(THUNDER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../Source/Thunder")

set(THREADPOOL_COUNT "4" CACHE STRING "The number of threads in the thread pool for test runtime")
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THREADPOOL_COUNT is already defined as a CACHE variable in Source/Thunder/CMakeLists.txt, and this set(... CACHE ...) in a subdirectory will reuse/overwrite the same cache entry (including its help text). To avoid confusing cache UI/help strings, consider not re-declaring it here and just consuming the existing cache variable (or use a uniquely named cache var specific to test support).

Suggested change
set(THREADPOOL_COUNT "4" CACHE STRING "The number of threads in the thread pool for test runtime")
if(NOT DEFINED THREADPOOL_COUNT)
set(THREADPOOL_COUNT "4")
endif()

Copilot uses AI. Check for mistakes.
@rdkcmf-jenkins
Copy link
Copy Markdown
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 1 file pending identification.

  • Protex Server Path: /home/blackduck/github/Thunder/2084/rdkcentral/Thunder

  • Commit: 24fae29

Report detail: gist'

@mhughesacn
Copy link
Copy Markdown

Hi @smanes0213 : I have cleared off the blackduck issue in the tool but please add headers to the source code in this PR. Thank you!

Copy link
Copy Markdown
Contributor

@sebaszm sebaszm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments!

Additionally we need to think how to incorporate JSON-RPC event handlig into this solution and remove the need of knowing the callsign of the tested plugin (or at least repeating it with every JSON-RPC call).

Also please ensure this works with Thunder 5.3. (Autostart and 'wpeframework' in paths suggest it was checked with 4.4.) Thanks!

// Uses port 0 (OS-assigned) and binds to localhost only.
// Helper: JSON-escape a string value and return it quoted (e.g. "foo\"bar" → "\"foo\\\"bar\"")
// Uses Core::JSON::String serialization to handle escaping correctly.
static string JsonEscape(const string& value)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to use Core::JSON methods instead of creating JSON by hand. It will be cleaner, more flexible less error-prone and removes the need for manual escaping.

<< "\"locator\":" << JsonEscape(p.locator) << ","
<< "\"classname\":" << JsonEscape(p.classname) << ","
<< "\"startuporder\":" << p.startuporder << ","
<< "\"autostart\":" << (p.autostart ? "true" : "false");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no autostart in 5.x in anymore. Perhaps it's then better to actually reuse the plugin Configuration as defined in plugns/Configuration.h, that would've eliminate such issues.


// Create unique temp directory for this test run
char tempTemplate[] = "/tmp/thunder_test_XXXXXX";
char* tempResult = mkdtemp(tempTemplate);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For OS-abstraction use Thunder Core functionality (also to remove posix dependency).

// Determine system path for plugin .so files
string sysPath = systemPath;
if (sysPath.empty()) {
sysPath = "/usr/lib/wpeframework/plugins/";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such path must not be hardcoded! On 5.x the path is different.

// Default: look next to system path
_proxyStubPath = sysPath + "../proxystubs/";
}
if (_proxyStubPath.back() != '/') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Thunder-provided functionality for normalizing paths!


// Helper to escape a string for safe inclusion as a JSON string value.
// It escapes quotes, backslashes, and control characters (< 0x20).
static std::string JsonEscape(const std::string& input)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Thunder Core JSON for JSON handling, no need for manual escaping.

{
const string communicatorPath = _tempDir + "communicator|0777";

std::ostringstream json;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Thunder Core JSON for JSON manipulation!

Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
public:
// Describes a plugin to be loaded by the test runtime.
// Maps directly to a Thunder plugin JSON config entry.
struct PluginConfig {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't Thunder Configuration be reused here, insted of crafting another one?

if (dot == string::npos) {
return Core::ERROR_INVALID_SIGNATURE;
}
string callsign = method.substr(0, dot);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are Thunder Core JSON Message helper methods for this.

Comment thread Tests/test_support/ThunderTestRuntime.h Outdated
//
// TEST_F(MyTest, JsonRpc) {
// string resp;
// _runtime.InvokeJSONRPC("Callsign.1.method", params, resp);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all .1 from JSON-RPC method designators, we don't endorse versioning.

Copilot AI review requested due to automatic review settings April 16, 2026 09:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

string callsign = Core::JSONRPC::Message::Callsign(method);
string methodName = Core::JSONRPC::Message::Method(method);

if (callsign.empty() == true) {
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invoke() validates that the callsign is non-empty, but it doesn’t validate that the extracted method name is non-empty (e.g., "Controller." yields an empty method). In that case the error returned becomes ERROR_UNKNOWN_KEY via the exists check, which is misleading. Consider treating an empty methodName as an invalid signature and returning Core::ERROR_INVALID_SIGNATURE (or similar) early.

Suggested change
if (callsign.empty() == true) {
if ((callsign.empty() == true) || (methodName.empty() == true)) {

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +17
find_package(GTest REQUIRED)

add_executable(thunder_test_runtime_smoke
SmokeTest.cpp
Module.cpp
)

target_link_libraries(thunder_test_runtime_smoke
PRIVATE
thunder_test_support
GTest::GTest
GTest::Main
)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_package(GTest REQUIRED) makes configuring the project fail whenever ENABLE_TEST_RUNTIME=ON but GTest isn’t installed, even though the static library itself doesn’t require GTest. Consider using find_package(GTest QUIET) (or gating this subdirectory behind an option) and only adding the smoke-test target when GTest is found, so consumers can still build/install thunder_test_support without GTest.

Suggested change
find_package(GTest REQUIRED)
add_executable(thunder_test_runtime_smoke
SmokeTest.cpp
Module.cpp
)
target_link_libraries(thunder_test_runtime_smoke
PRIVATE
thunder_test_support
GTest::GTest
GTest::Main
)
find_package(GTest QUIET)
if(GTest_FOUND)
add_executable(thunder_test_runtime_smoke
SmokeTest.cpp
Module.cpp
)
target_link_libraries(thunder_test_runtime_smoke
PRIVATE
thunder_test_support
GTest::GTest
GTest::Main
)
endif()

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +121
JsonObject config;
JsonArray pluginList;

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation relies on the global aliases JsonObject/JsonArray/JsonValue, which are not available when DISABLE_USE_COMPLEMENTARY_CODE_SET is enabled (it defines __DISABLE_USE_COMPLEMENTARY_CODE_SET__). In that build mode this file won’t compile. Use the fully-qualified Core JSON types (e.g., Thunder::Core::JSON::VariantContainer / Thunder::Core::JSON::ArrayType<...> / Thunder::Core::JSON::Variant) instead of the global aliases so the test runtime builds with and without the complementary code set.

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +131
const string communicatorPath = _tempDir + "communicator|0777";

JsonObject config;
JsonArray pluginList;

config["port"] = 0;
config["binding"] = "127.0.0.1";
config["idletime"] = 180;
config["persistentpath"] = _tempDir + "persistent/";
config["volatilepath"] = _tempDir + "volatile/";
config["datapath"] = _tempDir + "data/";
config["systempath"] = systemPath;
config["proxystubpath"] = proxyStubPath;
config["communicator"] = communicatorPath;

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The communicator socket is configured with permissions 0777 ("communicator|0777"), which makes the UNIX domain socket world-accessible. Even for tests, this is unnecessarily permissive on multi-user systems and can allow other users/processes to connect to the embedded runtime. Consider defaulting to a more restrictive mode (e.g., 0700 or respecting umask) and/or making the permission configurable via Initialize().

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +230
// Initialize the messaging subsystem (must happen before Server creation,
// mirrors the MessagingInitialization() call in the real PluginHost main()).
Messaging::MessageUnit::Settings::Config messagingConfig;
uint32_t messagingResult = Messaging::MessageUnit::Instance().Open(
_config->VolatilePath(), messagingConfig, false, Messaging::MessageUnit::flush::OFF);

if (messagingResult != Core::ERROR_NONE) {
TRACE_L1("ThunderTestRuntime: Failed to initialize messaging subsystem (0x%08X)", messagingResult);
}

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Messaging::MessageUnit::Instance().Open(...) is called unconditionally. MessageUnit::Open asserts that it’s not already open (it expects internal buffers to be null), so this will trigger assertions if the test process (or another runtime instance) already initialized messaging. Also, Deinitialize() always closes the global singleton, which can break other Thunder components in the same process. Consider tracking whether this runtime performed the open and only closing in that case, or adding an Initialize() option to skip messaging initialization when it’s managed by the caller.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants