mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
The two consumer surfaces had complementary holes:
sync : T foo(params…, logos::CallError* err = nullptr) error yes, timeout NO
async: void fooAsync(params…, cb, Timeout = Timeout()) timeout yes, error NO
so an async caller could not tell a failed remote call from a provider that
legitimately returned 0 / "" / false — the exact ambiguity the sync path's
CallError* was added to resolve — and a sync caller could not say how long it
was willing to wait, even though the transport overload the generator already
calls takes both.
Both fixes are additive:
T foo(params…, logos::CallError* err = nullptr, Timeout timeout = Timeout());
void fooAsync(params…, std::function<void(T)> cb, Timeout timeout = Timeout()); // unchanged
void fooAsyncResult(params…, std::function<void(logos::AsyncResult<T>)> cb,
Timeout timeout = Timeout()); // new
logos::AsyncResult<T> (new, Qt-free, cpp/logos_async_result.h) is {value, error}
plus ok(); AsyncResult<void> carries only the error so every fooAsyncResult has
the same callback shape. The name is distinct rather than an overload because
std::function<void(AsyncResult<T>)> next to std::function<void(T)> is ambiguous
for a generic lambda.
Applied to both emitters that produce this surface — legacy/generator_lib.cpp
(the module-builder path) and experimental/lidl_gen_client.cpp (`--lidl
--module-only`, from a published contract) — since a consumer can reach either
for the same contract.
The Qt-free (ApiStyle::Lp) surface gets the sync timeout (spelled `int
timeout_ms`; `Timeout` lives behind a Qt header) but NOT fooAsyncResult:
logos-protocol's lp_invoke_async hard-codes `cb(1, …)`, so an AsyncResult there
would report ok() on a failed call. Measured, not assumed. See the note in
makeHeaderLp.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
222 lines
6.1 KiB
C++
222 lines
6.1 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"));
|
|
EXPECT_TRUE(h.contains("ensureReplica"));
|
|
}
|
|
|
|
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"));
|
|
}
|