mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 17:51:07 +00:00
Both C++ generators emitted, at all three subscription sites (the generic
on(QString, RawEventCallback), its EventCallback overload, and every typed
on<Event>):
LogosObject* origin = ensureReplica(); // blocking requestObject
if (!origin) return false; // PERMANENT -- never retried
m_client->onEvent(origin, eventName, callback);
That asks "is the module reachable right now" at the one moment the answer is
no. Every C++ consumer subscribes from init(), onContextReady() or a backend
constructor, all of which run while the dependency's host has been spawned but
has not called listen() yet. The guard inside requestObject was dead code for
years -- isConnected() returned a latch that was always true -- so the call fell
through to a blocking wait that usually succeeded, slowly. Making isConnected()
truthful turns the same code into an instant, permanent, silent failure: the
wrapper compiles, returns a bool, and never delivers.
All three sites now route through the deferred channel:
return m_client->onEventWhenAvailable(m_moduleName, eventName, callback) != 0;
and ensureReplica() / m_eventReplica are deleted from both generators. Keeping a
per-wrapper replica would reintroduce both halves at once -- a blocking acquire
on the subscriber's thread, and a permanent failure when the module had simply
not started yet.
The return becomes ACCEPTED rather than live, false only for errors no retry can
fix. That is stated in the emitted comment so it reaches every generated file
rather than only this message.
VERIFIED AT THREE LEVELS, because the first two prove less than they look:
emits -- 265/265 cpp-sdk tests. The goldens now pin the emitted CALL SITE and
EXPECT_FALSE the removed symbols; they are string comparisons and
would pass on code that does not compile, which is exactly how this
defect survived.
compiles-- both generators' output compiled against the local protocol branch
(EXIT=0), including a 15-event contract with a 3-parameter event.
defers -- real A/B on a live qt_remote transport with real generated code:
7/7 green on the migrated generator, 3/3 red in 0-8 ms on the
pristine one, with published-first controls green in both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
226 lines
6.3 KiB
C++
226 lines
6.3 KiB
C++
#include <gtest/gtest.h>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include "generator_lib.h"
|
|
|
|
static QJsonArray makeTestMethods()
|
|
{
|
|
QJsonArray methods;
|
|
|
|
// A simple invokable method: int add(int a, int b)
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "add";
|
|
m["returnType"] = "int";
|
|
m["isInvokable"] = true;
|
|
QJsonArray params;
|
|
{
|
|
QJsonObject p;
|
|
p["type"] = "int";
|
|
p["name"] = "a";
|
|
params.append(p);
|
|
}
|
|
{
|
|
QJsonObject p;
|
|
p["type"] = "int";
|
|
p["name"] = "b";
|
|
params.append(p);
|
|
}
|
|
m["parameters"] = params;
|
|
methods.append(m);
|
|
}
|
|
|
|
// A void method with no params
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "reset";
|
|
m["returnType"] = "void";
|
|
m["isInvokable"] = true;
|
|
m["parameters"] = QJsonArray();
|
|
methods.append(m);
|
|
}
|
|
|
|
// A non-invokable method (should be skipped)
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "internal";
|
|
m["returnType"] = "void";
|
|
m["isInvokable"] = false;
|
|
m["parameters"] = QJsonArray();
|
|
methods.append(m);
|
|
}
|
|
|
|
return methods;
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsPragmaOnce)
|
|
{
|
|
QString h = makeHeader("test_mod", "TestMod", QJsonArray());
|
|
EXPECT_TRUE(h.contains("#pragma once"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsClassName)
|
|
{
|
|
QString h = makeHeader("test_mod", "TestMod", QJsonArray());
|
|
EXPECT_TRUE(h.contains("class TestMod"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsConstructor)
|
|
{
|
|
QString h = makeHeader("test_mod", "TestMod", QJsonArray());
|
|
EXPECT_TRUE(h.contains("explicit TestMod(LogosAPI* api)"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsIncludes)
|
|
{
|
|
QString h = makeHeader("test_mod", "TestMod", QJsonArray());
|
|
EXPECT_TRUE(h.contains("#include \"logos_types.h\""));
|
|
EXPECT_TRUE(h.contains("#include \"logos_api.h\""));
|
|
EXPECT_TRUE(h.contains("#include \"logos_api_client.h\""));
|
|
EXPECT_TRUE(h.contains("#include \"logos_object.h\""));
|
|
EXPECT_TRUE(h.contains("#include <QVariantList>"));
|
|
EXPECT_TRUE(h.contains("#include <QVariantMap>"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsEventCallbackTypedefs)
|
|
{
|
|
QString h = makeHeader("test_mod", "TestMod", QJsonArray());
|
|
EXPECT_TRUE(h.contains("RawEventCallback"));
|
|
EXPECT_TRUE(h.contains("EventCallback"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsMethodDeclarations)
|
|
{
|
|
QJsonArray methods = makeTestMethods();
|
|
QString h = makeHeader("test_mod", "TestMod", methods);
|
|
|
|
EXPECT_TRUE(h.contains("int add(int a, int b, logos::CallError* err = nullptr, Timeout timeout = Timeout())"));
|
|
EXPECT_TRUE(h.contains("void reset(logos::CallError* err = nullptr, Timeout timeout = Timeout())"));
|
|
// Non-invokable should not appear
|
|
EXPECT_FALSE(h.contains("internal"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsAsyncOverloads)
|
|
{
|
|
QJsonArray methods = makeTestMethods();
|
|
QString h = makeHeader("test_mod", "TestMod", methods);
|
|
|
|
EXPECT_TRUE(h.contains("addAsync("));
|
|
EXPECT_TRUE(h.contains("resetAsync("));
|
|
// Async callback types
|
|
EXPECT_TRUE(h.contains("std::function<void(int)> callback"));
|
|
EXPECT_TRUE(h.contains("std::function<void()> callback"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ContainsPrivateMembers)
|
|
{
|
|
QString h = makeHeader("test_mod", "TestMod", QJsonArray());
|
|
EXPECT_TRUE(h.contains("m_api"));
|
|
EXPECT_TRUE(h.contains("m_client"));
|
|
EXPECT_TRUE(h.contains("m_moduleName"));
|
|
// ensureReplica()/m_eventReplica are gone: the deferred subscription
|
|
// channel owns the acquire, and a per-wrapper replica would bring back both
|
|
// the blocking acquire and the permanent failure it caused.
|
|
EXPECT_FALSE(h.contains("ensureReplica"));
|
|
EXPECT_FALSE(h.contains("m_eventReplica"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ConstRefForStringParams)
|
|
{
|
|
QJsonArray methods;
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "greet";
|
|
m["returnType"] = "QString";
|
|
m["isInvokable"] = true;
|
|
QJsonArray params;
|
|
QJsonObject p;
|
|
p["type"] = "QString";
|
|
p["name"] = "name";
|
|
params.append(p);
|
|
m["parameters"] = params;
|
|
methods.append(m);
|
|
}
|
|
|
|
QString h = makeHeader("mod", "Mod", methods);
|
|
EXPECT_TRUE(h.contains("const QString& name"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ConstRefForQVariantListParam)
|
|
{
|
|
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 h = makeHeader("mod", "Mod", methods);
|
|
EXPECT_TRUE(h.contains("const QVariantList& items"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, ConstRefForQVariantMapParam)
|
|
{
|
|
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 h = makeHeader("mod", "Mod", methods);
|
|
EXPECT_TRUE(h.contains("const QVariantMap& data"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, QVariantListAsyncOverload)
|
|
{
|
|
QJsonArray methods;
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "getItems";
|
|
m["returnType"] = "QVariantList";
|
|
m["isInvokable"] = true;
|
|
m["parameters"] = QJsonArray();
|
|
methods.append(m);
|
|
}
|
|
|
|
QString h = makeHeader("mod", "Mod", methods);
|
|
EXPECT_TRUE(h.contains("QVariantList getItems(logos::CallError* err = nullptr, Timeout timeout = Timeout())"));
|
|
EXPECT_TRUE(h.contains("getItemsAsync("));
|
|
EXPECT_TRUE(h.contains("std::function<void(QVariantList)> callback"));
|
|
}
|
|
|
|
TEST(MakeHeaderTest, QVariantMapAsyncOverload)
|
|
{
|
|
QJsonArray methods;
|
|
{
|
|
QJsonObject m;
|
|
m["name"] = "getData";
|
|
m["returnType"] = "QVariantMap";
|
|
m["isInvokable"] = true;
|
|
m["parameters"] = QJsonArray();
|
|
methods.append(m);
|
|
}
|
|
|
|
QString h = makeHeader("mod", "Mod", methods);
|
|
EXPECT_TRUE(h.contains("QVariantMap getData(logos::CallError* err = nullptr, Timeout timeout = Timeout())"));
|
|
EXPECT_TRUE(h.contains("getDataAsync("));
|
|
EXPECT_TRUE(h.contains("std::function<void(QVariantMap)> callback"));
|
|
}
|