mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
* Extract the protocol layer into logos-protocol; consume it as a flake input The transport/token/IPC layer (transports incl. QRO + plain TCP/TLS, consumer core LogosAPIClient/LogosAPIConsumer with the capability auto-requestModule flow, ModuleProxy, token manager, QVariant<->JSON conversion, the abstract LogosProviderObject interface) now lives in the logos-protocol repo behind the versioned lp_* C ABI. This SDK keeps the typed C++ developer layer (LogosAPI, provider base classes + Qt provider glue, module context, code generator) and still compiles the protocol sources INTO liblogos_sdk.a from the flake input, so the installed artifact (archive symbols, include/ + include/cpp layouts, cmake config) stays byte-compatible: existing consumers need no changes. Public headers are unchanged; logos_provider_object.h keeps its name and now re-exports the abstract interface from logos_provider_interface.h. Transport/protocol component tests moved to logos-protocol with the code; the remaining sdk/generator/experimental suites are unchanged (432/432 green against the local protocol checkout). * lock: add logos-protocol input * Make the base SDK Qt-free: move the Qt developer layer to logos-qt-sdk LogosAPI, LogosAPIProvider, LogosProviderBase/LOGOS_PROVIDER macros, the QObject provider glue (QtProviderObject) and the legacy PluginInterface (core/interface.h) move to the new logos-qt-sdk repo. The protocol sources are no longer compiled into a monolithic archive — consumers link logos-qt-sdk (which layers on logos-protocol) instead. What remains here is header-only std C++: logos_module_context.h, logos_result.h (StdLogosResult), logos_json.h — exported as the CMake INTERFACE target logos-cpp-sdk::logos_headers — plus the code generator (a build-time tool; its introspection mode now includes logos_provider_interface.h from logos-protocol, where LogosProviderPlugin moved). Mechanically verified Qt-free: the logos-cpp-lib / logos-cpp-include closures contain only nlohmann_json. Tests: 245/245 (module-context std suite + generator + experimental). * fix: accept the installed source-export layout in the protocol-root check The fail-fast only tested <root>/cpp/logos_protocol.h, but the LP_SRC selection right below (and the error message itself) support the installed export layout <root>/include/cpp as well. Pointing LOGOS_PROTOCOL_ROOT at an installed export tripped the FATAL_ERROR before that fallback could apply. Caught by Copilot review on #82. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * lock: pin logos-protocol to the qt-free-split branch head The Qt-free SDK (and the cdylib backend stacked on it) reference LogosProviderPlugin from protocol's logos_provider_interface.h, which lands on feat/qt-free-split — the P1-branch pin no longer compiles standalone. Temporary — drop when the chain PRs merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * doctest: pin the logoscore runtime via its {release} placeholder The spec built logoscore-cli at bare master with only the cpp-sdk inputs overridden — master's stack cannot compile against the qt-free SDK, so the suite failed on the chain branches. With the placeholder, CI's --release-for pins expand it to the workspace's logoscore commit (and local runs without a pin still fall back to master, unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * doctest: override the nested module builders to {release} too capability_module (via logoscore's lock) and the cloned accounts module resolve module-builder from their own locks — pre-split revs whose LogosModule.cmake still detects the SDK by logos_api.h, which the qt-free SDK no longer ships ('logos-cpp-sdk not found'). Overriding the builder itself to the workspace-pinned chain rev (keeping the nested cpp-sdk override) builds both modules with the split-aware builder. Verified end-to-end locally with the exact doctest command. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * doctest: apply the {release} + nested-builder overrides to all three specs The runtime spec got the treatment in 210eea1; the composition and worker-thread specs have the same logoscore/module build commands and failed identically (pre-split builders from the modules' own locks). All executed run: blocks now pin logoscore-cli{release} and override the nested module builders to logos-module-builder{release}; the displayed code_block: variants stay in their generic master form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * codegen: typed wrappers throw on call failure; dispatch catches escapes Generated sync client wrappers call the new err-out invokeRemoteMethod overload and throw logos::LogosCallError when the call fails (e.g. the bound module is missing) — previously the empty QVariant silently degraded to the return type's default and a caller could not tell failure from a legitimate 0 / "". Both generators (legacy + LIDL), both API styles. Async paths unchanged. Generated provider dispatch (universal qt glue + LOGOS_PROVIDER) wraps the method body in a catch-all that logs and returns an invalid QVariant — an escaped exception becomes an ordinary METHOD_FAILED instead of unwinding through Qt event dispatch and killing the module process. * codegen: CallError out-param instead of throwing wrappers Per review, the generated sync wrappers expose the error channel as an optional trailing parameter — add(a, b, &err) — rather than throwing: explicit, stateless, works on temporaries, and existing call sites compile unchanged (they keep default-on-failure, now with a qWarning so failures are visible in the module log). The dispatch catch-all from the previous commit stays: it contains author exceptions, it doesn't introduce any. * glue: fire onContextReady AFTER modules()/event wiring The generated onInit set the context (which fires the impl's onContextReady hook) before constructing the LogosModules aggregate and wiring typed event emission — so an impl doing its documented one-time setup there (typed dependency calls, event subscriptions) dereferenced a null aggregate and crashed the module process (signal 11). Found by the first module to subscribe to a dependency's typed event from onContextReady. Context now goes last. * ci: run workflows on stacked PRs + workflow_dispatch Both workflows filtered pull_request to master-based PRs, so stacked PRs (feat/qt-free-sdk -> feat/extract-logos-protocol, feat/cdylib-authoring -> feat/qt-free-sdk) ran NO checks at all. Drop the base-branch filter for pull_request and add workflow_dispatch for manual runs. Same fix as logos-module-builder 232b8a2. * lock: protocol at the typed-requestModule port (3de5398) * ci: chain pins for the doc-tests (drop at merge) In repo CI only cpp-sdk's {release} is the commit under test — logoscore-cli and module-builder expanded to master, which doesn't link against the chain SDK the specs override in ('Build the CLI with the SDK override' failed on every run since the stacked-PR triggers were enabled). Pin both to the extraction-chain heads; the workspace pipeline is unaffected (it pins every repo itself). * generator: distribute the LIDL frontend for external generators First step of moving ALL Qt glue emission out of this repo into logos-qt-sdk's logos-qt-generator (cpp-sdk's generator keeps only the Qt-free outputs: std typed wrappers, logos_sdk umbrella, cdylib impl-exports, LIDL derivation). - Shared emit helpers (lidlToPascalCase, lidlTypeToQt, lidlTypeToStd, lidlIsStdConvertible) move to a new lidl_emit_common.{h,cpp} unit, used by both generators. - The frontend set (AST, lexer, parser, serializer, validator, impl-header parser, emit-common) is installed under share/lidl-frontend/ — the qt generator compiles these sources in directly, so the two tools share one frontend without a binary ABI. * lock: protocol#3 merged — pin advances to protocol master --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
235 lines
7.1 KiB
C++
235 lines
7.1 KiB
C++
#include <gtest/gtest.h>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include "generator_lib.h"
|
|
|
|
static QJsonObject makeMethod(const QString& name, const QString& retType, int paramCount)
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = name;
|
|
m["returnType"] = retType;
|
|
m["isInvokable"] = true;
|
|
QJsonArray params;
|
|
for (int i = 0; i < paramCount; ++i) {
|
|
QJsonObject p;
|
|
p["type"] = "int";
|
|
p["name"] = QString("p%1").arg(i);
|
|
params.append(p);
|
|
}
|
|
m["parameters"] = params;
|
|
return m;
|
|
}
|
|
|
|
TEST(MakeSourceTest, ContainsInclude)
|
|
{
|
|
QString src = makeSource("mod", "Mod", "mod_api.h", QJsonArray());
|
|
EXPECT_TRUE(src.contains("#include \"mod_api.h\""));
|
|
}
|
|
|
|
TEST(MakeSourceTest, ConstructorInitializesClient)
|
|
{
|
|
QString src = makeSource("my_mod", "MyMod", "my_mod_api.h", QJsonArray());
|
|
EXPECT_TRUE(src.contains("MyMod::MyMod(LogosAPI* api)"));
|
|
EXPECT_TRUE(src.contains("api->getClient(\"my_mod\")"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, EnsureReplicaMethod)
|
|
{
|
|
QString src = makeSource("mod", "Mod", "mod.h", QJsonArray());
|
|
EXPECT_TRUE(src.contains("LogosObject* Mod::ensureReplica()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, ZeroParams)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("doStuff", "int", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"doStuff\", QVariantList{}, Timeout(), &_err)"));
|
|
EXPECT_TRUE(src.contains("return _result.toInt()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, OneParam)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("fn", "bool", 1));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"fn\", QVariantList{p0}, Timeout(), &_err)"));
|
|
EXPECT_TRUE(src.contains("return _result.toBool()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, TwoParams)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("fn", "void", 2));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"fn\", QVariantList{p0, p1}, Timeout(), &_err)"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, ThreeParams)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("fn", "QString", 3));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("p0, p1, p2"));
|
|
EXPECT_TRUE(src.contains("return _result.toString()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, FourParams)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("fn", "double", 4));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("p0, p1, p2, p3"));
|
|
EXPECT_TRUE(src.contains("return _result.toDouble()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, FiveParams)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("fn", "float", 5));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("p0, p1, p2, p3, p4"));
|
|
EXPECT_TRUE(src.contains("return _result.toFloat()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, MoreThanFiveParamsUsesVariantList)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("fn", "QVariant", 6));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("QVariantList{p0, p1, p2, p3, p4, p5}"));
|
|
EXPECT_TRUE(src.contains("return _result"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, VoidReturnNoConversion)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("doIt", "void", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_FALSE(src.contains("return _result"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QStringListReturn)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("getNames", "QStringList", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("return _result.toStringList()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QJsonArrayReturn)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("getData", "QJsonArray", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("qvariant_cast<QJsonArray>(_result)"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QVariantListReturn)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("getItems", "QVariantList", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("return _result.toList()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QVariantMapReturn)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("getData", "QVariantMap", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("return _result.toMap()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QVariantListAsync)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("getItems", "QVariantList", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("Mod::getItemsAsync("));
|
|
EXPECT_TRUE(src.contains("std::function<void(QVariantList)> callback"));
|
|
EXPECT_TRUE(src.contains("QVariantList()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QVariantMapAsync)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("getData", "QVariantMap", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("Mod::getDataAsync("));
|
|
EXPECT_TRUE(src.contains("std::function<void(QVariantMap)> callback"));
|
|
EXPECT_TRUE(src.contains("QVariantMap()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QVariantListConstRefParam)
|
|
{
|
|
QJsonArray methods;
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "process";
|
|
m["returnType"] = "void";
|
|
m["isInvokable"] = true;
|
|
QJsonArray params;
|
|
QJsonObject p;
|
|
p["type"] = "QVariantList";
|
|
p["name"] = "items";
|
|
params.append(p);
|
|
m["parameters"] = params;
|
|
methods.append(m);
|
|
}
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("const QVariantList& items"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, QVariantMapConstRefParam)
|
|
{
|
|
QJsonArray methods;
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "update";
|
|
m["returnType"] = "void";
|
|
m["isInvokable"] = true;
|
|
QJsonArray params;
|
|
QJsonObject p;
|
|
p["type"] = "QVariantMap";
|
|
p["name"] = "data";
|
|
params.append(p);
|
|
m["parameters"] = params;
|
|
methods.append(m);
|
|
}
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("const QVariantMap& data"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, LogosResultReturn)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("query", "LogosResult", 0));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("_result.value<LogosResult>()"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, AsyncImplementation)
|
|
{
|
|
QJsonArray methods;
|
|
methods.append(makeMethod("fn", "int", 1));
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_TRUE(src.contains("Mod::fnAsync("));
|
|
EXPECT_TRUE(src.contains("invokeRemoteMethodAsync"));
|
|
EXPECT_TRUE(src.contains("callback"));
|
|
}
|
|
|
|
TEST(MakeSourceTest, NonInvokableSkipped)
|
|
{
|
|
QJsonArray methods;
|
|
QJsonObject m;
|
|
m["name"] = "hidden";
|
|
m["returnType"] = "void";
|
|
m["isInvokable"] = false;
|
|
m["parameters"] = QJsonArray();
|
|
methods.append(m);
|
|
|
|
QString src = makeSource("mod", "Mod", "mod.h", methods);
|
|
EXPECT_FALSE(src.contains("hidden"));
|
|
}
|