test: retire test_ipc_module, now fully duplicated

It was the last `interface: "legacy"` provider in the repo, and it no longer
covers anything of its own. Its 21-method surface is mirrored 1:1 by
test_ipc_new_api_module, and the Qt-typed consumer style it exercised now has a
dedicated home in test_fullapi_qtproxy (`consumer_api_style: "qt"`).

Removing it is what makes an absent `interface` mean "not a provider" rather
than "a provider whose glue silently went missing" — see the throw added to
logos-module-builder alongside this.

Groups `ipc` and `async` go with it, and so does the `unit` group, whose binary
came from the deleted derivation and would otherwise have gone permanently SKIP.
The `multi` group is repointed at the successor rather than deleted: it tests
sequential -c chaining, not the module, and the successor answers the same three
calls.

The async group is NOT simply gone. Its assertions were ported to the
ipc-new-api group in bc942e2 first, and they assert more there — on the lp
surface `<name>Async` IS the generated wrapper, so they cover async into a
module with no Qt in its own TUs. That ordering was the point: the coverage
moved before the module was removed, never the other way round.

Verified with the full suite: 172 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-08-18 11:37:48 -03:00
co-authored by Claude Opus 5
parent bc942e2a37
commit af567c1cbc
16 changed files with 28 additions and 9419 deletions
+12 -13
View File
@@ -8,8 +8,7 @@ and a standalone thread-safety test suite:
|--------|---------|
| **test_basic_module** | Standalone module (no external libs, no IPC). Covers every supported parameter type, return type, argument count (05), LogosResult patterns, and events. |
| **test_extlib_module** | Wraps an external C library (`libstrutil`). Validates the external-library build pipeline. |
| **test_ipc_module** | Calls the two modules above via `LogosAPI`. Validates inter-module communication, generated type-safe wrappers, and event subscriptions. |
| **test_ipc_new_api_module** | Same as test_ipc_module but uses the new provider API (`LogosProviderBase` + `LOGOS_METHOD`). No `QObject` inheritance in the implementation class. |
| **test_ipc_new_api_module** | Calls the two modules above. Validates inter-module communication, generated type-safe wrappers (sync and async), cross-module chaining, and events — all from an `interface: "universal"` module with no Qt in its own translation units. |
| **test_dummy_module** | Minimal `LogosProviderBase` module (`noop()` only). Used as a binary template for the thread-safety tests — patched at the binary level to generate unique module copies. |
## SDK coverage matrix
@@ -18,7 +17,7 @@ and a standalone thread-safety test suite:
| Type | Tested in |
|------|-----------|
| `QString` | test_basic_module, test_extlib_module, test_ipc_module |
| `QString` | test_basic_module, test_extlib_module, test_ipc_new_api_module |
| `qlonglong` | test_basic_module |
| `bool` | test_basic_module |
| `QStringList` | test_basic_module |
@@ -31,8 +30,8 @@ and a standalone thread-safety test suite:
| `void` | test_basic_module |
| `bool` | test_basic_module |
| `qlonglong` | test_basic_module, test_extlib_module |
| `QString` | test_basic_module, test_extlib_module, test_ipc_module |
| `LogosResult` | test_basic_module, test_ipc_module |
| `QString` | test_basic_module, test_extlib_module, test_ipc_new_api_module |
| `LogosResult` | test_basic_module, test_ipc_new_api_module |
| `QVariant` | test_basic_module |
| `QVariantList` | test_basic_module |
| `QStringList` | test_basic_module |
@@ -104,16 +103,16 @@ they are ASCII-only case mappings performed by the external C library, and bytes
| Pattern | Tested in |
|---------|-----------|
| `LogosAPI::getClient` + `invokeRemoteMethod` | test_ipc_module |
| Generated `LogosModules` wrappers | test_ipc_module |
| Event subscription (`onEvent`) | test_ipc_module |
| Event emission — typed `logos_events:` | test_basic_module |
| Event emission — dynamic `eventResponse` | test_ipc_module |
| Cross-module chaining | test_ipc_module |
| Generated `modules().<dep>` wrappers (sync) | test_ipc_new_api_module |
| Generated `<name>Async` wrappers over `lp_invoke_async` | test_ipc_new_api_module |
| Event subscription | test_ipc_new_api_module |
| Event emission — typed `logos_events:` | test_basic_module, test_ipc_new_api_module |
| Cross-module chaining | test_ipc_new_api_module |
| Qt-typed consumer (`consumer_api_style: "qt"`) | test_fullapi_qtproxy |
## Running tests
The integration test suite exercises the core modules (`test_basic_module`, `test_extlib_module`, `test_ipc_module`, `test_ipc_new_api_module`) via `logoscore`:
The integration test suite exercises the core modules (`test_basic_module`, `test_extlib_module`, `test_ipc_new_api_module`) via `logoscore`:
```bash
# From logos-test-modules
@@ -212,7 +211,7 @@ nix build
# Individual modules
nix build .#test_basic_module
nix build .#test_extlib_module
nix build .#test_ipc_module
nix build .#test_ipc_new_api_module
nix build .#test_ipc_new_api_module
nix build .#test_dummy_module
```
+1 -173
View File
@@ -229,14 +229,6 @@
configFile = ./test-extlib-module/metadata.json;
};
ipc = mkModule {
src = ./test-ipc-module;
configFile = ./test-ipc-module/metadata.json;
flakeInputs = {
test_basic_module = basic;
test_extlib_module = extlib;
};
};
# interface: "universal" — the impl header IS the contract; the builder
# derives the LIDL and emits the glue. No preConfigure hook.
@@ -353,7 +345,6 @@
test_context_module_cpp = contextCpp.packages.${system};
test_interface_module_cpp = interfaceCpp.packages.${system};
test_extlib_module = extlib.packages.${system};
test_ipc_module = ipc.packages.${system};
test_ipc_new_api_module = ipc-new-api.packages.${system};
test_dummy_module = dummy.packages.${system};
test_qml_only = qmlOnly.packages.${system};
@@ -387,7 +378,6 @@
test_context_module_cpp = contextCpp.packages.${system}.default;
test_interface_module_cpp = interfaceCpp.packages.${system}.default;
test_extlib_module = extlib.packages.${system}.default;
test_ipc_module = ipc.packages.${system}.default;
test_ipc_new_api_module = ipc-new-api.packages.${system}.default;
test_dummy_module = dummy.packages.${system}.default;
test_qml_only = qmlOnly.packages.${system}.default;
@@ -403,7 +393,6 @@
basicCpp.packages.${system}.default
contextCpp.packages.${system}.default
extlib.packages.${system}.default
ipc.packages.${system}.default
ipc-new-api.packages.${system}.default
dummy.packages.${system}.default
];
@@ -420,7 +409,6 @@
basicCppInstall = basicCpp.packages.${system}.install;
contextCppInstall = contextCpp.packages.${system}.install;
extlibInstall = extlib.packages.${system}.install;
ipcInstall = ipc.packages.${system}.install;
ipcNewApiInstall = ipc-new-api.packages.${system}.install;
fullapiCppInstall = fullapiCpp.packages.${system}.install;
fullapiRustInstall = fullapiRust.packages.${system}.install;
@@ -470,7 +458,7 @@
modulesDir = pkgs.runCommand "test-modules-dir" {} ''
mkdir -p $out
for installed in ${basicInstall} ${basicCppInstall} ${contextCppInstall} ${extlibInstall} ${ipcInstall} ${ipcNewApiInstall} ${fullapiCppInstall} ${fullapiRustInstall} ${fullapiProxyInstall} ${fullapiProxyRustInstall}; do
for installed in ${basicInstall} ${basicCppInstall} ${contextCppInstall} ${extlibInstall} ${ipcNewApiInstall} ${fullapiCppInstall} ${fullapiRustInstall} ${fullapiProxyInstall} ${fullapiProxyRustInstall}; do
if [ -d "$installed/modules" ]; then
cp -rn "$installed/modules/." "$out/"
@@ -636,56 +624,6 @@
# ws run logos-standalone-app --local ... -l test_qml_backend
# Async-only tests (validates invokeRemoteMethodAsync + generated wrappers)
async-tests = pkgs.runCommand "logos-test-modules-async-tests" {
nativeBuildInputs = [
logoscorePkg
pkgs.jq
] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.qt6.qtbase ];
} ''
export QT_QPA_PLATFORM=offscreen
export QT_FORCE_STDERR_LOGGING=1
export TEST_GROUPS=async
export TEST_TIMEOUT=30
${pkgs.lib.optionalString pkgs.stdenv.isLinux ''
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"
''}
mkdir -p $out
echo "Running async-only tests..."
bash ${./tests/run_tests.sh} \
${logoscorePkg}/bin/logoscore \
${modulesDir} \
2>&1 | tee $out/test-results.txt
echo "Async tests completed."
'';
# IPC-only tests (faster iteration on inter-module communication)
ipc-tests = pkgs.runCommand "logos-test-modules-ipc-tests" {
nativeBuildInputs = [
logoscorePkg
pkgs.jq
] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.qt6.qtbase ];
} ''
export QT_QPA_PLATFORM=offscreen
export QT_FORCE_STDERR_LOGGING=1
export TEST_GROUPS=ipc
export TEST_TIMEOUT=30
${pkgs.lib.optionalString pkgs.stdenv.isLinux ''
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"
''}
mkdir -p $out
echo "Running IPC-only tests..."
bash ${./tests/run_tests.sh} \
${logoscorePkg}/bin/logoscore \
${modulesDir} \
2>&1 | tee $out/test-results.txt
echo "IPC tests completed."
'';
# IPC new-API tests (LogosProviderBase path)
ipc-new-api-tests = pkgs.runCommand "logos-test-modules-ipc-new-api-tests" {
nativeBuildInputs = [
logoscorePkg
@@ -711,116 +649,6 @@
'';
# Unit tests using the mock transport — no real IPC / logoscore required
unit-tests =
let
# test_ipc_module is still a LEGACY Qt module, so its test consumes
# the qt-typed header variant. (Its universal sibling next door
# uses headers-lp; do not unify these two without also switching
# this derivation's generator call to --api-style lp.)
basicInclude = basic.packages.${system}.include;
extlibInclude = extlib.packages.${system}.include;
# Build the test executable via CMake
testBin = pkgs.stdenv.mkDerivation {
pname = "test-ipc-module-unit-tests";
version = "1.0.0";
src = ./test-ipc-module;
nativeBuildInputs = [
pkgs.cmake
pkgs.ninja
pkgs.qt6.wrapQtAppsNoGuiHook
logosSdkPkg # provides logos-cpp-generator + SDK headers
logosQtHostPkg
logosQtSdkPkg # seam headers for the generated qt wrapper
logosProtocolPkg
];
buildInputs = [
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
];
env = {
LOGOS_CPP_SDK_ROOT = "${logosSdkPkg}";
LOGOS_QT_HOST_ROOT = "${logosQtHostPkg}";
LOGOS_QT_SDK_ROOT = "${logosQtSdkPkg}";
LOGOS_PROTOCOL_ROOT = "${logosProtocolPkg}";
LOGOS_LIBLOGOS_ROOT = "${logosLiblogosPkg}";
};
dontUseCmakeConfigure = true;
buildPhase = ''
runHook preBuild
# Generate logos_sdk.cpp (general mode)
mkdir -p generated_code
cat > metadata.json <<'METADATA_EOF'
{
"name": "test_ipc_module",
"version": "1.0.0",
"type": "core",
"category": "testing",
"description": "Test module exercising inter-module communication via LogosAPI",
"dependencies": ["test_basic_module", "test_extlib_module"]
}
METADATA_EOF
logos-cpp-generator --metadata metadata.json --general-only --output-dir ./generated_code
# Copy dependency-generated API headers alongside the umbrella headers
cp ${basicInclude}/include/*.h ./generated_code/ 2>/dev/null || true
cp ${basicInclude}/include/*.cpp ./generated_code/ 2>/dev/null || true
cp ${extlibInclude}/include/*.h ./generated_code/ 2>/dev/null || true
cp ${extlibInclude}/include/*.cpp ./generated_code/ 2>/dev/null || true
# MOC needs metadata.json next to the plugin header for Q_PLUGIN_METADATA
cp metadata.json src/metadata.json
# CMake configure + build (out-of-source, pointing at tests/ subdir)
mkdir -p build && cd build
cmake ../tests -GNinja \
-DLOGOS_CPP_SDK_ROOT=${logosSdkPkg} \
-DLOGOS_QT_HOST_ROOT=${logosQtHostPkg} \
-DLOGOS_QT_SDK_ROOT=${logosQtSdkPkg} \
-DLOGOS_PROTOCOL_ROOT=${logosProtocolPkg} \
-DLOGOS_LIBLOGOS_ROOT=${logosLiblogosPkg}
ninja test_ipc_module_tests
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp test_ipc_module_tests $out/bin/
runHook postInstall
'';
};
in
pkgs.runCommand "logos-test-modules-unit-tests" {
nativeBuildInputs = [ testBin ]
++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.qt6.qtbase ];
} ''
export QT_QPA_PLATFORM=offscreen
export QT_FORCE_STDERR_LOGGING=1
export TEST_GROUPS=unit
${pkgs.lib.optionalString pkgs.stdenv.isLinux ''
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"
''}
mkdir -p $out
export UNIT_TEST_BIN="${testBin}/bin/test_ipc_module_tests"
bash ${./tests/run_tests.sh} \
${logoscorePkg}/bin/logoscore \
${modulesDir} \
2>&1 | tee $out/unit-test-results.txt
echo "Unit tests completed."
'';
# Unit tests for the new provider API (mock transport, no logoscore)
unit-tests-new-api =
let
# The impl is now `interface: "universal"`, i.e. Qt-free and
-18
View File
@@ -1,18 +0,0 @@
cmake_minimum_required(VERSION 3.14)
project(TestIpcModulePlugin LANGUAGES CXX)
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/LogosModule.cmake")
include(cmake/LogosModule.cmake)
else()
message(FATAL_ERROR "LogosModule.cmake not found")
endif()
logos_module(
NAME test_ipc_module
SOURCES
src/test_ipc_module_interface.h
src/test_ipc_module_plugin.h
src/test_ipc_module_plugin.cpp
)
-7757
View File
File diff suppressed because it is too large Load Diff
-24
View File
@@ -1,24 +0,0 @@
{
description = "Test IPC Module exercises inter-module communication via LogosAPI";
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
test_basic_module = {
url = "github:logos-co/logos-test-modules?dir=test-basic-module";
inputs.logos-module-builder.follows = "logos-module-builder";
};
test_extlib_module = {
url = "github:logos-co/logos-test-modules?dir=test-extlib-module";
inputs.logos-module-builder.follows = "logos-module-builder";
};
};
outputs = inputs@{ logos-module-builder, ... }:
logos-module-builder.lib.mkLogosModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs; # test_basic_module and test_extlib_module resolved from dependencies[]
};
}
-21
View File
@@ -1,21 +0,0 @@
{
"name": "test_ipc_module",
"version": "1.0.0",
"type": "core",
"category": "testing",
"description": "Test module exercising inter-module communication via LogosAPI",
"main": "test_ipc_module_plugin",
"dependencies": ["test_basic_module", "test_extlib_module"],
"nix": {
"packages": {
"build": [],
"runtime": []
},
"external_libraries": [],
"cmake": {
"find_packages": [],
"extra_sources": []
}
}
}
@@ -1,90 +0,0 @@
#ifndef TEST_IPC_MODULE_INTERFACE_H
#define TEST_IPC_MODULE_INTERFACE_H
#include <QObject>
#include <QString>
#include "interface.h"
#include "logos_types.h"
class TestIpcModuleInterface : public PluginInterface
{
public:
virtual ~TestIpcModuleInterface() = default;
// ── Calls to test_basic_module ───────────────────────────────────────────
// Call echo(QString) -> QString via raw invokeRemoteMethod
Q_INVOKABLE virtual QString callBasicEcho(const QString& input) = 0;
// Call addInts(int, int) -> int via raw invokeRemoteMethod
Q_INVOKABLE virtual int callBasicAddInts(int a, int b) = 0;
// Call returnTrue() -> bool via raw invokeRemoteMethod
Q_INVOKABLE virtual bool callBasicReturnTrue() = 0;
// Call noArgs() -> QString (0 args)
Q_INVOKABLE virtual QString callBasicNoArgs() = 0;
// Call fiveArgs() -> QString (5 args)
Q_INVOKABLE virtual QString callBasicFiveArgs(const QString& a, int b, bool c, const QString& d, int e) = 0;
// Call successResult() -> LogosResult
Q_INVOKABLE virtual LogosResult callBasicSuccessResult() = 0;
// Call errorResult() -> LogosResult
Q_INVOKABLE virtual LogosResult callBasicErrorResult() = 0;
// Call resultWithMap() and extract a value
Q_INVOKABLE virtual QString callBasicResultMapField(const QString& key) = 0;
// ── Calls to test_extlib_module ──────────────────────────────────────────
// Call reverseString(QString) -> QString
Q_INVOKABLE virtual QString callExtlibReverse(const QString& input) = 0;
// Call uppercaseString(QString) -> QString
Q_INVOKABLE virtual QString callExtlibUppercase(const QString& input) = 0;
// Call countChars(QString) -> int
Q_INVOKABLE virtual int callExtlibCountChars(const QString& input) = 0;
// ── Cross-module chaining ────────────────────────────────────────────────
// Chain: echo from basic -> reverse from extlib
Q_INVOKABLE virtual QString chainEchoThenReverse(const QString& input) = 0;
// Chain: uppercase from extlib -> concat from basic
Q_INVOKABLE virtual QString chainUppercaseThenConcat(const QString& a, const QString& b) = 0;
// ── Generated wrappers (LogosModules) ────────────────────────────────────
// Call via generated type-safe wrapper: test_basic_module.echo
Q_INVOKABLE virtual QString wrapperBasicEcho(const QString& input) = 0;
// Call via generated type-safe wrapper: test_extlib_module.reverseString
Q_INVOKABLE virtual QString wrapperExtlibReverse(const QString& input) = 0;
// ── Events ───────────────────────────────────────────────────────────────
// Trigger an event on test_basic_module and report
Q_INVOKABLE virtual void triggerBasicEvent(const QString& data) = 0;
// ── Async calls ───────────────────────────────────────────────────────────
// Async echo via raw invokeRemoteMethodAsync, blocks with QEventLoop until callback
Q_INVOKABLE virtual QString asyncCallBasicEcho(const QString& input) = 0;
// Async addInts via raw invokeRemoteMethodAsync (exercises multi-arg async overload)
Q_INVOKABLE virtual int asyncCallBasicAddInts(int a, int b) = 0;
// Async reverseString via raw invokeRemoteMethodAsync (cross-module to extlib)
Q_INVOKABLE virtual QString asyncCallExtlibReverse(const QString& input) = 0;
// Async echo via generated type-safe echoAsync() wrapper
Q_INVOKABLE virtual QString asyncWrapperBasicEcho(const QString& input) = 0;
};
#define TestIpcModuleInterface_iid "org.logos.TestIpcModuleInterface"
Q_DECLARE_INTERFACE(TestIpcModuleInterface, TestIpcModuleInterface_iid)
#endif // TEST_IPC_MODULE_INTERFACE_H
@@ -1,247 +0,0 @@
#include "test_ipc_module_plugin.h"
#include "logos_api.h"
#include "logos_api_client.h"
#include <QDebug>
#include <QEventLoop>
#include <QVariantMap>
TestIpcModulePlugin::TestIpcModulePlugin(QObject* parent)
: QObject(parent)
{
qDebug() << "TestIpcModulePlugin: created";
}
TestIpcModulePlugin::~TestIpcModulePlugin()
{
qDebug() << "TestIpcModulePlugin: destroyed";
delete logos;
}
void TestIpcModulePlugin::initLogos(LogosAPI* logosAPIInstance)
{
delete logos;
logos = nullptr;
logosAPI = logosAPIInstance;
if (logosAPI) {
logos = new LogosModules(logosAPI);
basicClient = logosAPI->getClient("test_basic_module");
extlibClient = logosAPI->getClient("test_extlib_module");
}
qDebug() << "TestIpcModulePlugin: LogosAPI initialized";
}
// ── Raw invokeRemoteMethod calls to test_basic_module ────────────────────────
QString TestIpcModulePlugin::callBasicEcho(const QString& input)
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "echo", QVariant(input));
qDebug() << "TestIpcModulePlugin::callBasicEcho" << input << "->" << result;
return result.toString();
}
int TestIpcModulePlugin::callBasicAddInts(int a, int b)
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "addInts", QVariant(a), QVariant(b));
qDebug() << "TestIpcModulePlugin::callBasicAddInts" << a << b << "->" << result;
return result.toInt();
}
bool TestIpcModulePlugin::callBasicReturnTrue()
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "returnTrue");
qDebug() << "TestIpcModulePlugin::callBasicReturnTrue ->" << result;
return result.toBool();
}
QString TestIpcModulePlugin::callBasicNoArgs()
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "noArgs");
qDebug() << "TestIpcModulePlugin::callBasicNoArgs ->" << result;
return result.toString();
}
QString TestIpcModulePlugin::callBasicFiveArgs(const QString& a, int b, bool c, const QString& d, int e)
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "fiveArgs",
QVariant(a), QVariant(b), QVariant(c), QVariant(d), QVariant(e));
qDebug() << "TestIpcModulePlugin::callBasicFiveArgs ->" << result;
return result.toString();
}
LogosResult TestIpcModulePlugin::callBasicSuccessResult()
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "successResult");
if (result.canConvert<LogosResult>()) {
return result.value<LogosResult>();
}
return {true, result, QVariant()};
}
LogosResult TestIpcModulePlugin::callBasicErrorResult()
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "errorResult");
if (result.canConvert<LogosResult>()) {
return result.value<LogosResult>();
}
return {false, QVariant(), result};
}
QString TestIpcModulePlugin::callBasicResultMapField(const QString& key)
{
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "resultWithMap");
if (result.canConvert<LogosResult>()) {
LogosResult lr = result.value<LogosResult>();
if (lr.success) {
QVariantMap map = lr.getValue<QVariantMap>();
return map.value(key).toString();
}
}
return QString();
}
// ── Raw invokeRemoteMethod calls to test_extlib_module ───────────────────────
QString TestIpcModulePlugin::callExtlibReverse(const QString& input)
{
QVariant result = extlibClient->invokeRemoteMethod(
"test_extlib_module", "reverseString", QVariant(input));
qDebug() << "TestIpcModulePlugin::callExtlibReverse" << input << "->" << result;
return result.toString();
}
QString TestIpcModulePlugin::callExtlibUppercase(const QString& input)
{
QVariant result = extlibClient->invokeRemoteMethod(
"test_extlib_module", "uppercaseString", QVariant(input));
qDebug() << "TestIpcModulePlugin::callExtlibUppercase" << input << "->" << result;
return result.toString();
}
int TestIpcModulePlugin::callExtlibCountChars(const QString& input)
{
QVariant result = extlibClient->invokeRemoteMethod(
"test_extlib_module", "countChars", QVariant(input));
qDebug() << "TestIpcModulePlugin::callExtlibCountChars" << input << "->" << result;
return result.toInt();
}
// ── Cross-module chaining ────────────────────────────────────────────────────
QString TestIpcModulePlugin::chainEchoThenReverse(const QString& input)
{
QVariant echoed = basicClient->invokeRemoteMethod(
"test_basic_module", "echo", QVariant(input));
QVariant reversed = extlibClient->invokeRemoteMethod(
"test_extlib_module", "reverseString", echoed);
qDebug() << "TestIpcModulePlugin::chainEchoThenReverse" << input << "->" << reversed;
return reversed.toString();
}
QString TestIpcModulePlugin::chainUppercaseThenConcat(const QString& a, const QString& b)
{
QVariant upperA = extlibClient->invokeRemoteMethod(
"test_extlib_module", "uppercaseString", QVariant(a));
QVariant upperB = extlibClient->invokeRemoteMethod(
"test_extlib_module", "uppercaseString", QVariant(b));
QVariant result = basicClient->invokeRemoteMethod(
"test_basic_module", "concat", upperA, upperB);
qDebug() << "TestIpcModulePlugin::chainUppercaseThenConcat" << a << b << "->" << result;
return result.toString();
}
// ── Generated type-safe wrappers (LogosModules) ─────────────────────────────
QString TestIpcModulePlugin::wrapperBasicEcho(const QString& input)
{
if (!logos) return QString();
QString result = logos->test_basic_module.echo(input);
qDebug() << "TestIpcModulePlugin::wrapperBasicEcho" << input << "->" << result;
return result;
}
QString TestIpcModulePlugin::wrapperExtlibReverse(const QString& input)
{
if (!logos) return QString();
QString result = logos->test_extlib_module.reverseString(input);
qDebug() << "TestIpcModulePlugin::wrapperExtlibReverse" << input << "->" << result;
return result;
}
// ── Events ───────────────────────────────────────────────────────────────────
void TestIpcModulePlugin::triggerBasicEvent(const QString& data)
{
basicClient->invokeRemoteMethod(
"test_basic_module", "emitTestEvent", QVariant(data));
emit eventResponse("triggeredBasicEvent", QVariantList() << data);
qDebug() << "TestIpcModulePlugin::triggerBasicEvent" << data;
}
// ── Async calls ──────────────────────────────────────────────────────────────
// Each method uses a QEventLoop to block until the async callback fires,
// making the result observable by logoscore's synchronous -c interface.
QString TestIpcModulePlugin::asyncCallBasicEcho(const QString& input)
{
QString result;
QEventLoop loop;
basicClient->invokeRemoteMethodAsync("test_basic_module", "echo", QVariant(input),
[&result, &loop](QVariant v) {
result = v.toString();
loop.quit();
});
loop.exec();
qDebug() << "TestIpcModulePlugin::asyncCallBasicEcho" << input << "->" << result;
return result;
}
int TestIpcModulePlugin::asyncCallBasicAddInts(int a, int b)
{
int result = 0;
QEventLoop loop;
basicClient->invokeRemoteMethodAsync("test_basic_module", "addInts",
QVariantList{QVariant(a), QVariant(b)},
[&result, &loop](QVariant v) {
result = v.toInt();
loop.quit();
});
loop.exec();
qDebug() << "TestIpcModulePlugin::asyncCallBasicAddInts" << a << b << "->" << result;
return result;
}
QString TestIpcModulePlugin::asyncCallExtlibReverse(const QString& input)
{
QString result;
QEventLoop loop;
extlibClient->invokeRemoteMethodAsync("test_extlib_module", "reverseString", QVariant(input),
[&result, &loop](QVariant v) {
result = v.toString();
loop.quit();
});
loop.exec();
qDebug() << "TestIpcModulePlugin::asyncCallExtlibReverse" << input << "->" << result;
return result;
}
QString TestIpcModulePlugin::asyncWrapperBasicEcho(const QString& input)
{
if (!logos) return QString();
QString result;
QEventLoop loop;
logos->test_basic_module.echoAsync(input, [&result, &loop](QString v) {
result = v;
loop.quit();
});
loop.exec();
qDebug() << "TestIpcModulePlugin::asyncWrapperBasicEcho" << input << "->" << result;
return result;
}
@@ -1,66 +0,0 @@
#ifndef TEST_IPC_MODULE_PLUGIN_H
#define TEST_IPC_MODULE_PLUGIN_H
#include <QObject>
#include <QString>
#include "test_ipc_module_interface.h"
#include "logos_api.h"
#include "logos_sdk.h"
class TestIpcModulePlugin : public QObject, public TestIpcModuleInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID TestIpcModuleInterface_iid FILE "metadata.json")
Q_INTERFACES(TestIpcModuleInterface PluginInterface)
public:
explicit TestIpcModulePlugin(QObject* parent = nullptr);
~TestIpcModulePlugin() override;
QString name() const override { return "test_ipc_module"; }
QString version() const override { return "1.0.0"; }
Q_INVOKABLE void initLogos(LogosAPI* logosAPIInstance);
// ── Calls to test_basic_module ───────────────────────────────────────────
Q_INVOKABLE QString callBasicEcho(const QString& input) override;
Q_INVOKABLE int callBasicAddInts(int a, int b) override;
Q_INVOKABLE bool callBasicReturnTrue() override;
Q_INVOKABLE QString callBasicNoArgs() override;
Q_INVOKABLE QString callBasicFiveArgs(const QString& a, int b, bool c, const QString& d, int e) override;
Q_INVOKABLE LogosResult callBasicSuccessResult() override;
Q_INVOKABLE LogosResult callBasicErrorResult() override;
Q_INVOKABLE QString callBasicResultMapField(const QString& key) override;
// ── Calls to test_extlib_module ──────────────────────────────────────────
Q_INVOKABLE QString callExtlibReverse(const QString& input) override;
Q_INVOKABLE QString callExtlibUppercase(const QString& input) override;
Q_INVOKABLE int callExtlibCountChars(const QString& input) override;
// ── Cross-module chaining ────────────────────────────────────────────────
Q_INVOKABLE QString chainEchoThenReverse(const QString& input) override;
Q_INVOKABLE QString chainUppercaseThenConcat(const QString& a, const QString& b) override;
// ── Generated wrappers (LogosModules) ────────────────────────────────────
Q_INVOKABLE QString wrapperBasicEcho(const QString& input) override;
Q_INVOKABLE QString wrapperExtlibReverse(const QString& input) override;
// ── Events ───────────────────────────────────────────────────────────────
Q_INVOKABLE void triggerBasicEvent(const QString& data) override;
// ── Async calls ───────────────────────────────────────────────────────────
Q_INVOKABLE QString asyncCallBasicEcho(const QString& input) override;
Q_INVOKABLE int asyncCallBasicAddInts(int a, int b) override;
Q_INVOKABLE QString asyncCallExtlibReverse(const QString& input) override;
Q_INVOKABLE QString asyncWrapperBasicEcho(const QString& input) override;
signals:
void eventResponse(const QString& eventName, const QVariantList& args);
private:
LogosModules* logos = nullptr;
LogosAPIClient* basicClient = nullptr;
LogosAPIClient* extlibClient = nullptr;
};
#endif // TEST_IPC_MODULE_PLUGIN_H
-246
View File
@@ -1,246 +0,0 @@
cmake_minimum_required(VERSION 3.14)
project(TestIpcModuleTests LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
# ── Locate dependencies ──────────────────────────────────────────────────────
# logos-liblogos (for PluginInterface / interface.h)
if(NOT DEFINED LOGOS_LIBLOGOS_ROOT)
if(DEFINED ENV{LOGOS_LIBLOGOS_ROOT})
set(LOGOS_LIBLOGOS_ROOT "$ENV{LOGOS_LIBLOGOS_ROOT}")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-liblogos/interface.h")
get_filename_component(LOGOS_LIBLOGOS_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-liblogos" ABSOLUTE)
else()
message(FATAL_ERROR "logos-liblogos not found. Set LOGOS_LIBLOGOS_ROOT.")
endif()
endif()
# logos-cpp-sdk (Qt-free base SDK headers; logos_api.h lives in logos-qt-host)
if(NOT DEFINED LOGOS_CPP_SDK_ROOT)
if(DEFINED ENV{LOGOS_CPP_SDK_ROOT})
set(LOGOS_CPP_SDK_ROOT "$ENV{LOGOS_CPP_SDK_ROOT}")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-cpp-sdk/cpp/logos_module_context.h")
get_filename_component(LOGOS_CPP_SDK_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-cpp-sdk" ABSOLUTE)
else()
message(FATAL_ERROR "logos-cpp-sdk not found. Set LOGOS_CPP_SDK_ROOT.")
endif()
endif()
# logos-qt-host (Qt HOST RUNTIME: LogosAPI, LogosAPIProvider, the provider base
# classes and the legacy QMetaObject adapter). It is built from logos-plugin-qt
# and exported as the logos-qt-host::logos_qt_host target; logos-qt-sdk no
# longer carries it, so the source-checkout fallback names the REPO
# (logos-plugin-qt) while the installed prefix is the logos-qt-host package.
if(NOT DEFINED LOGOS_QT_HOST_ROOT)
if(DEFINED ENV{LOGOS_QT_HOST_ROOT})
set(LOGOS_QT_HOST_ROOT "$ENV{LOGOS_QT_HOST_ROOT}")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-plugin-qt/cpp/logos_api.h")
get_filename_component(LOGOS_QT_HOST_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-plugin-qt" ABSOLUTE)
else()
message(FATAL_ERROR
"logos-qt-host not found. Set LOGOS_QT_HOST_ROOT to an installed "
"logos-qt-host prefix or to a logos-plugin-qt source checkout.")
endif()
endif()
# logos-qt-sdk — SEAM HEADERS ONLY (logos_qt_lp_bridge.h, logos_qt_wire.h).
#
# NOT the host runtime; that is logos-qt-host above, and nothing here links a
# logos-qt-sdk library (the package ships headers, no archive). What needs it
# is the GENERATED Qt-typed dependency wrapper this binary compiles:
# test_basic_module_api.cpp is copied out of the dependency's `headers-qt`
# output, and since the builder started emitting that wrapper as a veneer over
# the lp path it opens with `#include "logos_qt_lp_bridge.h"`. Without this
# include dir the failure is a `file not found` deep inside generated code.
#
# The lp-typed sibling (test-ipc-module-new-api) consumes `headers-lp`, whose
# wrapper has no such include — which is why only this CMakeLists carries it.
if(NOT DEFINED LOGOS_QT_SDK_ROOT)
if(DEFINED ENV{LOGOS_QT_SDK_ROOT})
set(LOGOS_QT_SDK_ROOT "$ENV{LOGOS_QT_SDK_ROOT}")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-qt-sdk/cpp/logos_qt_lp_bridge.h")
get_filename_component(LOGOS_QT_SDK_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-qt-sdk" ABSOLUTE)
else()
message(FATAL_ERROR
"logos-qt-sdk not found. Set LOGOS_QT_SDK_ROOT to an installed "
"logos-qt-sdk prefix or a source checkout — the generated Qt-typed "
"dependency wrapper includes logos_qt_lp_bridge.h.")
endif()
endif()
# logos-protocol (transports, consumer core, mock implementations)
if(NOT DEFINED LOGOS_PROTOCOL_ROOT)
if(DEFINED ENV{LOGOS_PROTOCOL_ROOT})
set(LOGOS_PROTOCOL_ROOT "$ENV{LOGOS_PROTOCOL_ROOT}")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-protocol/cpp/logos_protocol.h")
get_filename_component(LOGOS_PROTOCOL_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../logos-protocol" ABSOLUTE)
else()
message(FATAL_ERROR "logos-protocol not found. Set LOGOS_PROTOCOL_ROOT.")
endif()
endif()
# Detect source vs installed layouts.
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/cpp/logos_module_context.h")
set(LOGOS_CPP_SDK_IS_SOURCE TRUE)
set(LOGOS_CPP_SDK_INCLUDE "${LOGOS_CPP_SDK_ROOT}/cpp")
else()
set(LOGOS_CPP_SDK_IS_SOURCE FALSE)
set(LOGOS_CPP_SDK_INCLUDE "${LOGOS_CPP_SDK_ROOT}/include/cpp")
endif()
if(EXISTS "${LOGOS_QT_HOST_ROOT}/cpp/logos_api.h")
set(LOGOS_QT_HOST_IS_SOURCE TRUE)
set(LOGOS_QT_HOST_INCLUDE "${LOGOS_QT_HOST_ROOT}/cpp")
set(LOGOS_QT_HOST_CORE_INCLUDE "${LOGOS_QT_HOST_ROOT}/core")
else()
set(LOGOS_QT_HOST_IS_SOURCE FALSE)
set(LOGOS_QT_HOST_INCLUDE "${LOGOS_QT_HOST_ROOT}/include/cpp")
set(LOGOS_QT_HOST_CORE_INCLUDE "${LOGOS_QT_HOST_ROOT}/include/core")
endif()
if(EXISTS "${LOGOS_QT_SDK_ROOT}/cpp/logos_qt_lp_bridge.h")
set(LOGOS_QT_SDK_INCLUDE "${LOGOS_QT_SDK_ROOT}/cpp")
else()
set(LOGOS_QT_SDK_INCLUDE "${LOGOS_QT_SDK_ROOT}/include/cpp")
endif()
if(EXISTS "${LOGOS_PROTOCOL_ROOT}/cpp/logos_protocol.h")
set(LOGOS_PROTOCOL_INCLUDE "${LOGOS_PROTOCOL_ROOT}/cpp")
set(LOGOS_PROTOCOL_IMPL_INCLUDE "${LOGOS_PROTOCOL_ROOT}/cpp/implementations")
else()
set(LOGOS_PROTOCOL_INCLUDE "${LOGOS_PROTOCOL_ROOT}/include")
set(LOGOS_PROTOCOL_IMPL_INCLUDE "${LOGOS_PROTOCOL_ROOT}/include/implementations")
endif()
# Mock transport headers live in logos-protocol since the extraction.
set(LOGOS_CPP_SDK_MOCK_INCLUDE "${LOGOS_PROTOCOL_IMPL_INCLUDE}/mock")
if(EXISTS "${LOGOS_LIBLOGOS_ROOT}/interface.h")
set(LOGOS_LIBLOGOS_INCLUDE "${LOGOS_LIBLOGOS_ROOT}")
elseif(EXISTS "${LOGOS_LIBLOGOS_ROOT}/include/core/interface.h")
set(LOGOS_LIBLOGOS_INCLUDE "${LOGOS_LIBLOGOS_ROOT}/include/core")
else()
set(LOGOS_LIBLOGOS_INCLUDE "${LOGOS_LIBLOGOS_ROOT}/include")
endif()
message(STATUS "logos-liblogos include : ${LOGOS_LIBLOGOS_INCLUDE}")
message(STATUS "logos-cpp-sdk include : ${LOGOS_CPP_SDK_INCLUDE}")
message(STATUS "logos-cpp-sdk is source: ${LOGOS_CPP_SDK_IS_SOURCE}")
# ── Qt ───────────────────────────────────────────────────────────────────────
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core RemoteObjects)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core RemoteObjects)
# ── Source lists ─────────────────────────────────────────────────────────────
set(TEST_SOURCES
main.cpp
test_call_basic.cpp
test_call_extlib.cpp
test_chaining.cpp
../src/test_ipc_module_plugin.cpp
)
if(LOGOS_QT_HOST_IS_SOURCE)
# Source layout: compile the Qt host runtime into the test binary.
# The transport/consumer core (types, api_client, token_manager, mock
# transports, ...) lives in the logos-protocol LIBRARY and is linked
# below instead of compiled in.
list(APPEND TEST_SOURCES
${LOGOS_QT_HOST_INCLUDE}/logos_api.cpp
${LOGOS_QT_HOST_INCLUDE}/logos_api_provider.cpp
${LOGOS_QT_HOST_INCLUDE}/logos_provider_object.cpp
${LOGOS_QT_HOST_INCLUDE}/qt_provider_object.cpp
# qt_provider_object.cpp decodes every incoming argument through
# logos::qtArgDecode, so the legacy adapter belongs to the source
# layout too. Listed unconditionally: a checkout that lacks it must
# fail at configure time, not silently drop the translation unit and
# then fail on an undefined symbol at link.
${LOGOS_QT_HOST_INCLUDE}/logos_qt_arg_decode.cpp
)
endif()
# logos_sdk.cpp is the generated wrapper; look in generated_code/ relative to the module.
# SKIP_AUTOMOC is required to avoid a ninja dependency cycle — this file has no Q_OBJECT macros.
set(GENERATED_CODE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../generated_code")
if(EXISTS "${GENERATED_CODE_DIR}/logos_sdk.cpp")
list(APPEND TEST_SOURCES "${GENERATED_CODE_DIR}/logos_sdk.cpp")
set_source_files_properties("${GENERATED_CODE_DIR}/logos_sdk.cpp"
PROPERTIES SKIP_AUTOMOC ON)
elseif(EXISTS "${GENERATED_CODE_DIR}/include/logos_sdk.cpp")
list(APPEND TEST_SOURCES "${GENERATED_CODE_DIR}/include/logos_sdk.cpp")
set_source_files_properties("${GENERATED_CODE_DIR}/include/logos_sdk.cpp"
PROPERTIES SKIP_AUTOMOC ON)
endif()
# ── Test executable ──────────────────────────────────────────────────────────
add_executable(test_ipc_module_tests ${TEST_SOURCES})
target_include_directories(test_ipc_module_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../src
${CMAKE_CURRENT_BINARY_DIR}
${LOGOS_LIBLOGOS_INCLUDE}
${LOGOS_CPP_SDK_INCLUDE}
${LOGOS_QT_HOST_INCLUDE}
${LOGOS_QT_HOST_CORE_INCLUDE}
${LOGOS_QT_SDK_INCLUDE}
${LOGOS_PROTOCOL_INCLUDE}
${LOGOS_PROTOCOL_IMPL_INCLUDE}/qt_local
${LOGOS_PROTOCOL_IMPL_INCLUDE}/qt_remote
${LOGOS_CPP_SDK_MOCK_INCLUDE}
${GENERATED_CODE_DIR}
${GENERATED_CODE_DIR}/include
)
target_link_libraries(test_ipc_module_tests PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::RemoteObjects
)
# Link the Qt host runtime via its exported CMake target so the consumer
# inherits the full transitive link interface (logos-protocol, and through it
# OpenSSL::SSL, OpenSSL::Crypto, Boost::system, nlohmann_json). A bare
# archive on the link line would leave Boost.Asio TLS symbols undefined.
#
# find_package(logos-qt-host) is REQUIRED and the target reference below is
# unconditional on purpose: no `if(TARGET ...)` guard, so a renamed or missing
# prefix is a hard configure error rather than a silently unlinked runtime.
if(NOT LOGOS_QT_HOST_IS_SOURCE)
find_package(logos-protocol REQUIRED CONFIG
PATHS ${LOGOS_PROTOCOL_ROOT}/lib/cmake/logos-protocol
NO_DEFAULT_PATH)
find_package(logos-qt-host REQUIRED CONFIG
PATHS ${LOGOS_QT_HOST_ROOT}/lib/cmake/logos-qt-host
NO_DEFAULT_PATH)
target_link_libraries(test_ipc_module_tests PRIVATE logos-qt-host::logos_qt_host)
else()
# Source-layout host runtime compiled in above; link the protocol library.
if(NOT TARGET logos_protocol)
add_subdirectory("${LOGOS_PROTOCOL_ROOT}/cpp"
"${CMAKE_BINARY_DIR}/logos-protocol-build")
endif()
target_link_libraries(test_ipc_module_tests PRIVATE logos_protocol)
endif()
# Qt-free base SDK headers (nlohmann include path for logos_json.h etc.)
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/cmake/logos-cpp-sdk")
find_package(logos-cpp-sdk REQUIRED CONFIG
PATHS ${LOGOS_CPP_SDK_ROOT}/lib/cmake/logos-cpp-sdk
NO_DEFAULT_PATH)
target_link_libraries(test_ipc_module_tests PRIVATE logos-cpp-sdk::logos_headers)
else()
find_package(nlohmann_json REQUIRED)
target_link_libraries(test_ipc_module_tests PRIVATE nlohmann_json::nlohmann_json)
endif()
# ── CTest integration ────────────────────────────────────────────────────────
enable_testing()
add_test(NAME logos_module_unit_tests COMMAND test_ipc_module_tests)
-181
View File
@@ -1,181 +0,0 @@
#ifndef LOGOS_TEST_H
#define LOGOS_TEST_H
/**
* @file logos_test.h
* @brief Lightweight unit-test framework for Logos module tests.
*
* No external dependencies. Tests self-register at static-initialisation
* time via the LOGOS_TEST macro, so just #include this header and define
* your tests — the test runner in main.cpp discovers them automatically.
*
* Usage:
*
* #include "logos_test.h"
* #include "logos_mock.h"
*
* LOGOS_TEST(my_feature_works) {
* LogosMockSetup mock;
* mock.when("mod", "method").thenReturn(QVariant(7));
* // ...
* LOGOS_ASSERT_EQ(someValue, 7);
* }
*/
#include <QCoreApplication>
#include <QString>
#include <QVariant>
#include <QList>
#include <functional>
#include <iostream>
#include <sstream>
#include <stdexcept>
// Allow QString and QVariant to be streamed into std::ostream (for assertion messages)
inline std::ostream& operator<<(std::ostream& os, const QString& s) { return os << s.toStdString(); }
inline std::ostream& operator<<(std::ostream& os, const QVariant& v) { return os << v.toString().toStdString(); }
// ── Test failure exception ───────────────────────────────────────────────────
class LogosTestFailure : public std::runtime_error {
public:
explicit LogosTestFailure(const std::string& msg) : std::runtime_error(msg) {}
};
// ── Assertion macros ─────────────────────────────────────────────────────────
#define LOGOS_ASSERT(expr) \
do { \
if (!(expr)) { \
std::ostringstream _oss; \
_oss << "ASSERT failed: " #expr \
<< " (" << __FILE__ << ":" << __LINE__ << ")"; \
throw LogosTestFailure(_oss.str()); \
} \
} while (false)
#define LOGOS_ASSERT_TRUE(expr) LOGOS_ASSERT(expr)
#define LOGOS_ASSERT_FALSE(expr) LOGOS_ASSERT(!(expr))
#define LOGOS_ASSERT_EQ(actual, expected) \
do { \
auto _a = (actual); \
auto _e = (expected); \
if (!(_a == _e)) { \
std::ostringstream _oss; \
_oss << "ASSERT_EQ failed: expected [" \
<< _e << "] but got [" << _a << "]" \
<< " (" << __FILE__ << ":" << __LINE__ << ")"; \
throw LogosTestFailure(_oss.str()); \
} \
} while (false)
#define LOGOS_ASSERT_NE(actual, expected) \
do { \
auto _a = (actual); \
auto _e = (expected); \
if (_a == _e) { \
std::ostringstream _oss; \
_oss << "ASSERT_NE failed: both equal [" << _a << "]" \
<< " (" << __FILE__ << ":" << __LINE__ << ")"; \
throw LogosTestFailure(_oss.str()); \
} \
} while (false)
// ── Test runner ──────────────────────────────────────────────────────────────
class LogosTestRunner {
public:
struct TestEntry {
QString name;
std::function<void()> fn;
};
static LogosTestRunner& instance() {
static LogosTestRunner runner;
return runner;
}
bool registerTest(const char* name, std::function<void()> fn) {
m_tests.append({QString::fromUtf8(name), fn});
return true;
}
/**
* @brief Run all registered tests.
* @return 0 if all pass, 1 if any fail.
*/
int runAll() {
int passed = 0;
int failed = 0;
std::cout << "\n=== Logos Module Unit Tests ===\n\n";
for (const TestEntry& t : m_tests) {
std::cout << " [ RUN ] " << t.name.toStdString() << "\n";
try {
t.fn();
std::cout << " [ PASS ] " << t.name.toStdString() << "\n";
++passed;
} catch (const LogosTestFailure& ex) {
std::cout << " [ FAIL ] " << t.name.toStdString()
<< "\n " << ex.what() << "\n";
++failed;
} catch (const std::exception& ex) {
std::cout << " [ FAIL ] " << t.name.toStdString()
<< "\n Unexpected exception: " << ex.what() << "\n";
++failed;
} catch (...) {
std::cout << " [ FAIL ] " << t.name.toStdString()
<< "\n Unknown exception\n";
++failed;
}
}
std::cout << "\n=== Results: " << passed << " passed, "
<< failed << " failed";
if (m_tests.isEmpty()) {
std::cout << " (no tests registered!)";
}
std::cout << " ===\n\n";
return failed > 0 ? 1 : 0;
}
private:
QList<TestEntry> m_tests;
};
// ── LOGOS_TEST macro ─────────────────────────────────────────────────────────
/**
* @brief Define a test function and auto-register it with LogosTestRunner.
*
* The body of the function follows the macro, like a regular function:
*
* LOGOS_TEST(my_test) {
* LOGOS_ASSERT_EQ(1 + 1, 2);
* }
*/
#define LOGOS_TEST(name) \
static void _logos_test_fn_##name(); \
static bool _logos_test_reg_##name = \
LogosTestRunner::instance().registerTest(#name, _logos_test_fn_##name); \
static void _logos_test_fn_##name()
// ── Test main ────────────────────────────────────────────────────────────────
/**
* @brief Generate a standard main() that sets up QCoreApplication and runs all
* registered tests. Place this in exactly one .cpp file (typically main.cpp).
*
* #include "logos_test.h"
* LOGOS_TEST_MAIN()
*/
#define LOGOS_TEST_MAIN() \
int main(int argc, char* argv[]) { \
QCoreApplication app(argc, argv); \
return LogosTestRunner::instance().runAll(); \
}
#endif // LOGOS_TEST_H
-3
View File
@@ -1,3 +0,0 @@
#include "logos_test.h"
LOGOS_TEST_MAIN()
-190
View File
@@ -1,190 +0,0 @@
#include "logos_test.h"
#include "logos_mock.h"
#include "test_ipc_module_plugin.h"
#include "logos_api.h"
// ── callBasicAddInts ─────────────────────────────────────────────────────────
LOGOS_TEST(callBasicAddInts_returns_mocked_sum) {
LogosMockSetup mock;
mock.when("test_basic_module", "addInts").thenReturn(QVariant(30));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
int result = plugin.callBasicAddInts(10, 20);
LOGOS_ASSERT_EQ(result, 30);
}
LOGOS_TEST(callBasicAddInts_records_correct_args) {
LogosMockSetup mock;
mock.when("test_basic_module", "addInts").thenReturn(QVariant(12));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callBasicAddInts(5, 7);
LOGOS_ASSERT(mock.wasCalled("test_basic_module", "addInts"));
LOGOS_ASSERT(mock.wasCalledWith("test_basic_module", "addInts",
{QVariant(5), QVariant(7)}));
}
LOGOS_TEST(callBasicAddInts_callCount_is_one) {
LogosMockSetup mock;
mock.when("test_basic_module", "addInts").thenReturn(QVariant(0));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callBasicAddInts(1, 2);
LOGOS_ASSERT_EQ(mock.callCount("test_basic_module", "addInts"), 1);
}
LOGOS_TEST(callBasicAddInts_called_multiple_times) {
LogosMockSetup mock;
mock.when("test_basic_module", "addInts").thenReturn(QVariant(0));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callBasicAddInts(1, 2);
plugin.callBasicAddInts(3, 4);
plugin.callBasicAddInts(5, 6);
LOGOS_ASSERT_EQ(mock.callCount("test_basic_module", "addInts"), 3);
}
LOGOS_TEST(callBasicAddInts_last_args_are_recorded) {
LogosMockSetup mock;
mock.when("test_basic_module", "addInts").thenReturn(QVariant(0));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callBasicAddInts(10, 20);
plugin.callBasicAddInts(99, 1);
QVariantList last = mock.lastArgs("test_basic_module", "addInts");
LOGOS_ASSERT_EQ(last.size(), 2);
LOGOS_ASSERT_EQ(last.at(0).toInt(), 99);
LOGOS_ASSERT_EQ(last.at(1).toInt(), 1);
}
// ── callBasicEcho ────────────────────────────────────────────────────────────
LOGOS_TEST(callBasicEcho_returns_mocked_string) {
LogosMockSetup mock;
mock.when("test_basic_module", "echo").thenReturn(QVariant(QString("hello back")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
QString result = plugin.callBasicEcho("hello");
LOGOS_ASSERT_EQ(result, QString("hello back"));
}
LOGOS_TEST(callBasicEcho_records_input_arg) {
LogosMockSetup mock;
mock.when("test_basic_module", "echo").thenReturn(QVariant(QString("")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callBasicEcho("test-input");
LOGOS_ASSERT(mock.wasCalledWith("test_basic_module", "echo",
{QVariant(QString("test-input"))}));
}
// ── callBasicReturnTrue ──────────────────────────────────────────────────────
LOGOS_TEST(callBasicReturnTrue_returns_mocked_true) {
LogosMockSetup mock;
mock.when("test_basic_module", "returnTrue").thenReturn(QVariant(true));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
bool result = plugin.callBasicReturnTrue();
LOGOS_ASSERT_TRUE(result);
}
LOGOS_TEST(callBasicReturnTrue_can_be_mocked_false) {
LogosMockSetup mock;
mock.when("test_basic_module", "returnTrue").thenReturn(QVariant(false));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
bool result = plugin.callBasicReturnTrue();
LOGOS_ASSERT_FALSE(result);
}
// ── callBasicNoArgs ──────────────────────────────────────────────────────────
LOGOS_TEST(callBasicNoArgs_is_called_with_no_args) {
LogosMockSetup mock;
mock.when("test_basic_module", "noArgs").thenReturn(QVariant(QString("ok")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callBasicNoArgs();
LOGOS_ASSERT(mock.wasCalled("test_basic_module", "noArgs"));
LOGOS_ASSERT(mock.wasCalledWith("test_basic_module", "noArgs", {}));
}
// ── callBasicFiveArgs ────────────────────────────────────────────────────────
LOGOS_TEST(callBasicFiveArgs_records_all_five_args) {
LogosMockSetup mock;
mock.when("test_basic_module", "fiveArgs").thenReturn(QVariant(QString("result")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callBasicFiveArgs("alpha", 2, true, "delta", 5);
QVariantList expected = {
QVariant(QString("alpha")),
QVariant(2),
QVariant(true),
QVariant(QString("delta")),
QVariant(5)
};
LOGOS_ASSERT(mock.wasCalledWith("test_basic_module", "fiveArgs", expected));
}
// ── wrapperBasicEcho (generated wrapper) ────────────────────────────────────
LOGOS_TEST(wrapperBasicEcho_calls_basic_echo) {
LogosMockSetup mock;
mock.when("test_basic_module", "echo").thenReturn(QVariant(QString("wrapped")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
QString result = plugin.wrapperBasicEcho("input");
LOGOS_ASSERT_EQ(result, QString("wrapped"));
LOGOS_ASSERT(mock.wasCalled("test_basic_module", "echo"));
}
-123
View File
@@ -1,123 +0,0 @@
#include "logos_test.h"
#include "logos_mock.h"
#include "test_ipc_module_plugin.h"
#include "logos_api.h"
// ── callExtlibReverse ────────────────────────────────────────────────────────
LOGOS_TEST(callExtlibReverse_returns_mocked_string) {
LogosMockSetup mock;
mock.when("test_extlib_module", "reverseString")
.thenReturn(QVariant(QString("dlrow olleh")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
QString result = plugin.callExtlibReverse("hello world");
LOGOS_ASSERT_EQ(result, QString("dlrow olleh"));
}
LOGOS_TEST(callExtlibReverse_records_correct_input) {
LogosMockSetup mock;
mock.when("test_extlib_module", "reverseString").thenReturn(QVariant(QString("")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callExtlibReverse("abc");
LOGOS_ASSERT(mock.wasCalledWith("test_extlib_module", "reverseString",
{QVariant(QString("abc"))}));
}
LOGOS_TEST(callExtlibReverse_called_once) {
LogosMockSetup mock;
mock.when("test_extlib_module", "reverseString").thenReturn(QVariant(QString("")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callExtlibReverse("test");
LOGOS_ASSERT_EQ(mock.callCount("test_extlib_module", "reverseString"), 1);
}
// ── callExtlibUppercase ──────────────────────────────────────────────────────
LOGOS_TEST(callExtlibUppercase_returns_mocked_string) {
LogosMockSetup mock;
mock.when("test_extlib_module", "uppercaseString")
.thenReturn(QVariant(QString("HELLO")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
QString result = plugin.callExtlibUppercase("hello");
LOGOS_ASSERT_EQ(result, QString("HELLO"));
}
LOGOS_TEST(callExtlibUppercase_records_correct_input) {
LogosMockSetup mock;
mock.when("test_extlib_module", "uppercaseString").thenReturn(QVariant(QString("")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callExtlibUppercase("lower");
LOGOS_ASSERT(mock.wasCalledWith("test_extlib_module", "uppercaseString",
{QVariant(QString("lower"))}));
}
// ── callExtlibCountChars ─────────────────────────────────────────────────────
LOGOS_TEST(callExtlibCountChars_returns_mocked_count) {
LogosMockSetup mock;
mock.when("test_extlib_module", "countChars").thenReturn(QVariant(5));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
int result = plugin.callExtlibCountChars("hello");
LOGOS_ASSERT_EQ(result, 5);
}
LOGOS_TEST(callExtlibCountChars_records_correct_input) {
LogosMockSetup mock;
mock.when("test_extlib_module", "countChars").thenReturn(QVariant(0));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.callExtlibCountChars("testing");
LOGOS_ASSERT(mock.wasCalledWith("test_extlib_module", "countChars",
{QVariant(QString("testing"))}));
}
// ── wrapperExtlibReverse (generated wrapper) ─────────────────────────────────
LOGOS_TEST(wrapperExtlibReverse_calls_reverseString) {
LogosMockSetup mock;
mock.when("test_extlib_module", "reverseString")
.thenReturn(QVariant(QString("reversed")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
QString result = plugin.wrapperExtlibReverse("input");
LOGOS_ASSERT_EQ(result, QString("reversed"));
LOGOS_ASSERT(mock.wasCalled("test_extlib_module", "reverseString"));
}
-118
View File
@@ -1,118 +0,0 @@
#include "logos_test.h"
#include "logos_mock.h"
#include "test_ipc_module_plugin.h"
#include "logos_api.h"
// ── chainEchoThenReverse ─────────────────────────────────────────────────────
LOGOS_TEST(chainEchoThenReverse_calls_both_modules) {
LogosMockSetup mock;
mock.when("test_basic_module", "echo")
.thenReturn(QVariant(QString("hello")));
mock.when("test_extlib_module", "reverseString")
.thenReturn(QVariant(QString("olleh")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
QString result = plugin.chainEchoThenReverse("hello");
LOGOS_ASSERT_EQ(result, QString("olleh"));
LOGOS_ASSERT(mock.wasCalled("test_basic_module", "echo"));
LOGOS_ASSERT(mock.wasCalled("test_extlib_module", "reverseString"));
}
LOGOS_TEST(chainEchoThenReverse_passes_echo_output_to_reverse) {
LogosMockSetup mock;
// echo returns a transformed string; reverse should receive that output
mock.when("test_basic_module", "echo")
.thenReturn(QVariant(QString("echoed_value")));
mock.when("test_extlib_module", "reverseString")
.thenReturn(QVariant(QString("final")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.chainEchoThenReverse("input");
// reverseString should have been called with the output of echo
LOGOS_ASSERT(mock.wasCalledWith("test_extlib_module", "reverseString",
{QVariant(QString("echoed_value"))}));
}
LOGOS_TEST(chainEchoThenReverse_each_module_called_once) {
LogosMockSetup mock;
mock.when("test_basic_module", "echo").thenReturn(QVariant(QString("x")));
mock.when("test_extlib_module", "reverseString").thenReturn(QVariant(QString("y")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.chainEchoThenReverse("abc");
LOGOS_ASSERT_EQ(mock.callCount("test_basic_module", "echo"), 1);
LOGOS_ASSERT_EQ(mock.callCount("test_extlib_module", "reverseString"), 1);
}
// ── chainUppercaseThenConcat ─────────────────────────────────────────────────
LOGOS_TEST(chainUppercaseThenConcat_calls_three_methods) {
LogosMockSetup mock;
mock.when("test_extlib_module", "uppercaseString")
.thenReturn(QVariant(QString("UPPER")));
mock.when("test_basic_module", "concat")
.thenReturn(QVariant(QString("UPPER UPPER")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
QString result = plugin.chainUppercaseThenConcat("hello", "world");
LOGOS_ASSERT_EQ(result, QString("UPPER UPPER"));
// uppercaseString called twice (once for each input)
LOGOS_ASSERT_EQ(mock.callCount("test_extlib_module", "uppercaseString"), 2);
LOGOS_ASSERT(mock.wasCalled("test_basic_module", "concat"));
}
LOGOS_TEST(chainUppercaseThenConcat_passes_uppercased_values_to_concat) {
LogosMockSetup mock;
// Return different values for the two uppercase calls based on call order
mock.when("test_extlib_module", "uppercaseString")
.thenReturn(QVariant(QString("MOCKED")));
mock.when("test_basic_module", "concat")
.thenReturn(QVariant(QString("MOCKED MOCKED")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.chainUppercaseThenConcat("a", "b");
// concat should have been called with the two uppercase results
LOGOS_ASSERT(mock.wasCalledWith("test_basic_module", "concat",
{QVariant(QString("MOCKED")),
QVariant(QString("MOCKED"))}));
}
LOGOS_TEST(chainUppercaseThenConcat_records_original_inputs_for_uppercase) {
LogosMockSetup mock;
mock.when("test_extlib_module", "uppercaseString")
.thenReturn(QVariant(QString("X")));
mock.when("test_basic_module", "concat")
.thenReturn(QVariant(QString("X X")));
TestIpcModulePlugin plugin;
LogosAPI api("test_ipc_module");
plugin.initLogos(&api);
plugin.chainUppercaseThenConcat("foo", "bar");
// First call should be "foo", last call (which we can verify) should be "bar"
QVariantList lastUpperArgs = mock.lastArgs("test_extlib_module", "uppercaseString");
LOGOS_ASSERT_EQ(lastUpperArgs.size(), 1);
LOGOS_ASSERT_EQ(lastUpperArgs.at(0).toString(), QString("bar"));
}
+15 -149
View File
@@ -9,7 +9,6 @@ set -uo pipefail
LOGOSCORE="${1:?Usage: run_tests.sh <logoscore> <modules-dir>}"
MODULES_DIR="${2:?}"
UNIT_TEST_BIN="${UNIT_TEST_BIN:-}" # optional: path to test_ipc_module_tests binary (env var)
# UNIT_NEW_API_TEST_BIN: set via env var; path to test_ipc_new_api_module_tests binary
@@ -19,9 +18,9 @@ UNIT_NEW_API_TEST_BIN="${UNIT_NEW_API_TEST_BIN:-}"
CALL_TIMEOUT="${TEST_TIMEOUT:-30}"
# TEST_GROUPS: comma-separated list of groups to run (default: all)
# Available groups: basic, basic-cpp, context-cpp, extlib, ipc, ipc-new-api,
# async, multi, errors, unit, unit-new-api
# Example: TEST_GROUPS=ipc or TEST_GROUPS=ipc,basic or TEST_GROUPS=ipc-new-api
# Available groups: basic, basic-cpp, context-cpp, extlib, ipc-new-api,
# multi, errors, unit-new-api
# Example: TEST_GROUPS=ipc-new-api or TEST_GROUPS=ipc-new-api,basic
if [[ -n "${TEST_GROUPS:-}" ]]; then
IFS=',' read -ra ENABLED_GROUPS <<< "$TEST_GROUPS"
else
@@ -118,7 +117,7 @@ echo " daemon ready (pid $DAEMON_PID)"
# Load every test module up front. The `load-module` subcommand DOES
# auto-resolve and load a module's declared dependencies (the daemon
# discovers every module under -m at startup, so the dependency closure is
# known), so loading e.g. test_ipc_module also brings up its deps
# known), so loading e.g. test_ipc_new_api_module also brings up its deps
# (test_basic_module, test_extlib_module). We still load each module
# explicitly because any single group can be selected on its own via
# TEST_GROUPS, and a standalone group's module (e.g. test_basic_module_cpp)
@@ -127,7 +126,7 @@ echo " daemon ready (pid $DAEMON_PID)"
# run; order is irrelevant since deps resolve automatically. (The daemon
# auto-loads capability_module itself.)
for _mod in test_basic_module test_basic_module_cpp test_extlib_module \
test_context_module_cpp test_ipc_module test_ipc_new_api_module \
test_context_module_cpp test_ipc_new_api_module \
test_fullapi_cpp test_fullapi_rust test_fullapi_proxy test_fullapi_proxy_rust; do
if "$LOGOSCORE" --config-dir "$LOGOSCORE_CONFIG_DIR" load-module "$_mod" >/dev/null 2>&1; then
echo " loaded: $_mod"
@@ -298,7 +297,7 @@ test_extlib() {
assert_call "$1" "$2" -m "$MODULES_DIR" -l test_extlib_module -c "$3"
}
test_ipc() {
assert_call "$1" "$2" -m "$MODULES_DIR" -l test_ipc_module -c "$3"
assert_call "$1" "$2" -m "$MODULES_DIR" -l test_ipc_new_api_module -c "$3"
}
# ── Banner ───────────────────────────────────────────────────────────────────
@@ -945,70 +944,6 @@ test_extlib "libVersion()" "Result: 1.0.0" "test_extlib_module.lib
fi # end extlib group
# ═════════════════════════════════════════════════════════════════════════════
# TEST GROUP 3: test_ipc_module (inter-module communication)
# ═════════════════════════════════════════════════════════════════════════════
if should_run_group "ipc"; then
echo ""
echo "-----------------------------------------------------------------"
echo " test_ipc_module (requires all 3 modules)"
echo "-----------------------------------------------------------------"
# NOTE: The capability_module must be bundled with logoscore for IPC to work.
# logoscore auto-discovers it from its default modules dir (../modules relative
# to the binary). When user-specified -m dirs are also provided, both the default
# and user dirs are scanned.
# ── Calls to test_basic_module via invokeRemoteMethod ─────────────────────────
echo ""
echo " -- IPC: calls to test_basic_module --"
test_ipc "callBasicEcho(hello)" "Result: hello" "test_ipc_module.callBasicEcho(hello)"
test_ipc "callBasicEcho(world)" "Result: world" "test_ipc_module.callBasicEcho(world)"
test_ipc "callBasicAddInts(10, 20)" "Result: 30" "test_ipc_module.callBasicAddInts(10, 20)"
test_ipc "callBasicAddInts(0, 0)" "Result: 0" "test_ipc_module.callBasicAddInts(0, 0)"
test_ipc "callBasicReturnTrue()" "Result: true" "test_ipc_module.callBasicReturnTrue()"
test_ipc "callBasicNoArgs()" "Result: noArgs()" "test_ipc_module.callBasicNoArgs()"
test_ipc "callBasicFiveArgs(a, 1, true, b, 2)" "Result: fiveArgs(a, 1, true, b, 2)" "test_ipc_module.callBasicFiveArgs(a, 1, true, b, 2)"
test_ipc "callBasicSuccessResult()" "Method call successful" "test_ipc_module.callBasicSuccessResult()"
test_ipc "callBasicErrorResult()" "Method call successful" "test_ipc_module.callBasicErrorResult()"
test_ipc "callBasicResultMapField(name)" "Result: test" "test_ipc_module.callBasicResultMapField(name)"
test_ipc "callBasicResultMapField(count)" "Result: 42" "test_ipc_module.callBasicResultMapField(count)"
# ── Calls to test_extlib_module ───────────────────────────────────────────────
echo ""
echo " -- IPC: calls to test_extlib_module --"
test_ipc "callExtlibReverse(hello)" "Result: olleh" "test_ipc_module.callExtlibReverse(hello)"
test_ipc "callExtlibReverse(abc)" "Result: cba" "test_ipc_module.callExtlibReverse(abc)"
test_ipc "callExtlibUppercase(hello)" "Result: HELLO" "test_ipc_module.callExtlibUppercase(hello)"
test_ipc "callExtlibCountChars(hello)" "Result: 5" "test_ipc_module.callExtlibCountChars(hello)"
# ── Cross-module chaining ─────────────────────────────────────────────────────
echo ""
echo " -- IPC: cross-module chaining --"
test_ipc "chainEchoThenReverse(hello)" "Result: olleh" "test_ipc_module.chainEchoThenReverse(hello)"
test_ipc "chainEchoThenReverse(abcdef)" "Result: fedcba" "test_ipc_module.chainEchoThenReverse(abcdef)"
test_ipc "chainUppercaseThenConcat(foo, bar)" "Result: FOOBAR" "test_ipc_module.chainUppercaseThenConcat(foo, bar)"
test_ipc "chainUppercaseThenConcat(hello, world)" "Result: HELLOWORLD" "test_ipc_module.chainUppercaseThenConcat(hello, world)"
# ── Generated type-safe wrappers ──────────────────────────────────────────────
echo ""
echo " -- IPC: generated wrappers (LogosModules) --"
test_ipc "wrapperBasicEcho(hello)" "Result: hello" "test_ipc_module.wrapperBasicEcho(hello)"
test_ipc "wrapperBasicEcho(test123)" "Result: test123" "test_ipc_module.wrapperBasicEcho(test123)"
test_ipc "wrapperExtlibReverse(hello)" "Result: olleh" "test_ipc_module.wrapperExtlibReverse(hello)"
test_ipc "wrapperExtlibReverse(abc)" "Result: cba" "test_ipc_module.wrapperExtlibReverse(abc)"
# ── Events ────────────────────────────────────────────────────────────────────
echo ""
echo " -- IPC: events --"
skip_test "triggerBasicEvent(data)" "void return → invalid QVariant → logoscore exit 1"
fi # end ipc group
# ═════════════════════════════════════════════════════════════════════════════
# TEST GROUP 3b: test_ipc_new_api_module (new LogosProviderBase API)
# ═════════════════════════════════════════════════════════════════════════════
@@ -1064,9 +999,10 @@ test_ipc_new_api "wrapperExtlibReverse(abc)" "Result: cba"
echo ""
echo " -- IPC new-API: async over the lp transport --"
# The async half of the same generated wrappers used above. On this surface
# `<name>Async` bottoms out in lp_invoke_async, so unlike the `async` group
# (which exercises the QT consumer through test_ipc_module) these assert async
# delivery into a module with NO Qt in its own translation units.
# `<name>Async` bottoms out in lp_invoke_async, so these assert async delivery
# into a module with NO Qt in its own translation units. The Qt-consumer side of
# async lives in test_fullapi_qtproxy, which reads completions from a separate
# call rather than blocking for them.
test_ipc_new_api "asyncCallBasicEcho(hello)" "Result: hello" "test_ipc_new_api_module.asyncCallBasicEcho(hello)"
test_ipc_new_api "asyncCallBasicEcho(world)" "Result: world" "test_ipc_new_api_module.asyncCallBasicEcho(world)"
test_ipc_new_api "asyncCallBasicAddInts(3, 4)" "Result: 7" "test_ipc_new_api_module.asyncCallBasicAddInts(3, 4)"
@@ -1143,14 +1079,14 @@ fi
TOTAL=$((TOTAL + 1))
# shellcheck disable=SC2086
printf " cmd: timeout %s %s %s -m %s -l test_ipc_module -c ... -c ... -c ...\n" \
printf " cmd: timeout %s %s %s -m %s -l test_ipc_new_api_module -c ... -c ... -c ...\n" \
"$CALL_TIMEOUT" "$LOGOSCORE" "$QUIT_FLAG" "$MODULES_DIR"
# shellcheck disable=SC2086
output=$(dcall_inline \
-m "$MODULES_DIR" -l test_ipc_module \
-c "test_ipc_module.callBasicEcho(chain)" \
-c "test_ipc_module.callExtlibReverse(hello)" \
-c "test_ipc_module.callBasicAddInts(5, 7)" \
-m "$MODULES_DIR" -l test_ipc_new_api_module \
-c "test_ipc_new_api_module.callBasicEcho(chain)" \
-c "test_ipc_new_api_module.callExtlibReverse(hello)" \
-c "test_ipc_new_api_module.callBasicAddInts(5, 7)" \
2>/dev/null) && rc=0 || rc=$?
if [[ $rc -eq 0 ]] && \
printf '%s' "$output" | grep -qF "Result: chain" && \
@@ -1190,76 +1126,6 @@ assert_call_fails "nonexistent module" \
fi # end errors group
# ═════════════════════════════════════════════════════════════════════════════
# TEST GROUP 6: Async calls (invokeRemoteMethodAsync + generated wrappers)
# ═════════════════════════════════════════════════════════════════════════════
if should_run_group "async"; then
echo ""
echo "-----------------------------------------------------------------"
echo " Async calls"
echo "-----------------------------------------------------------------"
echo ""
echo " -- Raw invokeRemoteMethodAsync --"
test_ipc "asyncCallBasicEcho(hello)" "Result: hello" "test_ipc_module.asyncCallBasicEcho(hello)"
test_ipc "asyncCallBasicEcho(world)" "Result: world" "test_ipc_module.asyncCallBasicEcho(world)"
test_ipc "asyncCallBasicAddInts(3, 4)" "Result: 7" "test_ipc_module.asyncCallBasicAddInts(3, 4)"
test_ipc "asyncCallBasicAddInts(0, 0)" "Result: 0" "test_ipc_module.asyncCallBasicAddInts(0, 0)"
echo ""
echo " -- Async cross-module (ipc -> extlib) --"
test_ipc "asyncCallExtlibReverse(hello)" "Result: olleh" "test_ipc_module.asyncCallExtlibReverse(hello)"
test_ipc "asyncCallExtlibReverse(abc)" "Result: cba" "test_ipc_module.asyncCallExtlibReverse(abc)"
echo ""
echo " -- Generated async wrapper (echoAsync) --"
test_ipc "asyncWrapperBasicEcho(hello)" "Result: hello" "test_ipc_module.asyncWrapperBasicEcho(hello)"
test_ipc "asyncWrapperBasicEcho(test123)" "Result: test123" "test_ipc_module.asyncWrapperBasicEcho(test123)"
fi # end async group
# ═════════════════════════════════════════════════════════════════════════════
# TEST GROUP 7: Unit tests (mock transport, no logoscore required)
# ═════════════════════════════════════════════════════════════════════════════
if should_run_group "unit"; then
echo ""
echo "-----------------------------------------------------------------"
echo " Unit tests (mock transport)"
echo "-----------------------------------------------------------------"
echo ""
if [[ -z "$UNIT_TEST_BIN" ]]; then
echo " SKIP unit tests (no unit test binary provided)"
echo " Set UNIT_TEST_BIN env var to the path of test_ipc_module_tests binary"
SKIP=$((SKIP + 1))
elif [[ ! -x "$UNIT_TEST_BIN" ]]; then
FAIL=$((FAIL + 1))
printf " FAIL unit tests — binary not found or not executable: %s\n" "$UNIT_TEST_BIN"
FAILURES="${FAILURES} FAIL unit tests: binary not found: ${UNIT_TEST_BIN}\n"
else
TOTAL=$((TOTAL + 1))
printf " cmd: %s\n" "$UNIT_TEST_BIN"
unit_output=$("$UNIT_TEST_BIN" 2>&1) && unit_rc=0 || unit_rc=$?
printf "%s\n" "$unit_output"
if [[ $unit_rc -eq 0 ]]; then
PASS=$((PASS + 1))
printf " PASS unit tests\n"
else
FAIL=$((FAIL + 1))
printf " FAIL unit tests (exit code %d)\n" "$unit_rc"
FAILURES="${FAILURES} FAIL unit tests: exit code ${unit_rc}\n"
fi
fi
fi # end unit group
# ═════════════════════════════════════════════════════════════════════════════
# TEST GROUP 7: Unit tests — new provider API (mock transport, no logoscore)
# ═════════════════════════════════════════════════════════════════════════════