[Tests] Add thunder_test_support static library for plugin integration testing#2084
[Tests] Add thunder_test_support static library for plugin integration testing#2084smanes0213 wants to merge 8 commits intomasterfrom
Conversation
|
There was a problem hiding this comment.
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::ThunderTestRuntimeAPI 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 (excludingPluginHost.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(); |
| 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; |
| 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(); |
| #ifndef MODULE_NAME | ||
| #define MODULE_NAME Application | ||
| #endif | ||
|
|
| // | ||
| // TEST_F(MyTest, JsonRpc) { | ||
| // string resp; | ||
| // _runtime.InvokeJSONRPC("Callsign", "Method", params, resp); |
| │ │ ├── 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 │ | ||
| └─────────────────────────────────────┘ └─────────────────────────────────────────┘ |
|
Hi @smanes0213 : This is draft so not looking closely, but if you append: 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. |
There was a problem hiding this comment.
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.
|
|
||
| // 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, |
There was a problem hiding this comment.
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);
| string CommunicatorPath() const; | ||
|
|
||
| // Stop the server, release config, and clean up temp directories. | ||
| void Shutdown(); |
There was a problem hiding this comment.
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?
Tests/test_support/Module.cpp
Outdated
|
|
||
| #include <core/core.h> | ||
|
|
||
| MODULE_NAME_DECLARATION(BUILD_REFERENCE) |
There was a problem hiding this comment.
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| #define MODULE_NAME Application | ||
| #endif | ||
|
|
||
| #include <plugins/plugins.h> |
There was a problem hiding this comment.
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.
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
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.