Files
logos-cpp-sdk/tests/generator/test_make_source.cpp
T
Dario Gabriel Lipicar 818a3c6490 fix(codegen): pack Qt client args as one element each, not a spread list
The generated Qt client wrapper packed a method's arguments with
`QVariantList{a, b, ...}` (sync) and `QVariantList{...}` / `QVariantList() << a`
(async). For a QVariantList-typed argument -- every `[T]` list type (`[any]`,
`[int]`, `[uint]`, `[float64]`, `[bool]`) -- a braced `QVariantList{v}` and
`<< v` both CONCATENATE the list's elements into the args list, so
`echoList([1,2,3])` went out as three positional args instead of one array arg.
The receiver saw an arg-count mismatch and the list round-tripped empty; through
a UI->proxy->provider 2-hop it hung the call outright. This is the long-standing
"typed arrays empty over the Qt path" bug.

Fix both generators that emit the Qt client:
- legacy `generator_lib.cpp` (the production `logos-cpp-generator`): wrap each
  arg in `QVariant::fromValue(...)` in the sync and async call sites.
- experimental `lidl_gen_client.cpp`: route both paths through the existing
  `packVariantList` helper (which already wraps with `QVariant::fromValue`), the
  same helper the event `trigger` path uses.

`QVariant::fromValue` does not double-wrap an already-QVariant (`any`) arg, and
scalars/QString/QVariantMap/QByteArray were never affected (they don't
concatenate). Empirically: `QVariantList{v}` / `<< v` give size 3 for a 3-element
list; the wrapped forms give size 1.

Tests: legacy generator_tests gain ListArgWrappedAsOneElement and update the
param-packing assertions to the wrapped form; experimental gains
ListArgIsPackedAsOneElement. 167/167 green.
2026-07-18 11:15:48 -03:00

261 lines
8.6 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{QVariant::fromValue(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{QVariant::fromValue(p0), QVariant::fromValue(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("QVariant::fromValue(p0), QVariant::fromValue(p1), QVariant::fromValue(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("QVariant::fromValue(p0), QVariant::fromValue(p1), QVariant::fromValue(p2), QVariant::fromValue(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("QVariant::fromValue(p0), QVariant::fromValue(p1), QVariant::fromValue(p2), QVariant::fromValue(p3), QVariant::fromValue(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{QVariant::fromValue(p0), QVariant::fromValue(p1), QVariant::fromValue(p2), QVariant::fromValue(p3), QVariant::fromValue(p4), QVariant::fromValue(p5)}"));
EXPECT_TRUE(src.contains("return _result"));
}
// Regression: a QVariantList-typed ([any]/[int]/...) argument must be wrapped as
// ONE element via QVariant::fromValue. A bare `QVariantList{v}` concatenates the
// list's elements into the args list, sending [1,2,3] as three positional args
// — the "typed arrays empty over the Qt path" bug.
TEST(MakeSourceTest, ListArgWrappedAsOneElement)
{
QJsonObject m;
m["name"] = "echoList";
m["returnType"] = "QVariantList";
m["isInvokable"] = true;
QJsonObject p;
p["type"] = "QVariantList";
p["name"] = "v";
QJsonArray params;
params.append(p);
m["parameters"] = params;
QJsonArray methods;
methods.append(m);
QString src = makeSource("mod", "Mod", "mod.h", methods);
EXPECT_TRUE(src.contains("invokeRemoteMethod(\"mod\", \"echoList\", QVariantList{QVariant::fromValue(v)}, Timeout(), &_err)"));
EXPECT_TRUE(src.contains("invokeRemoteMethodAsync(\"mod\", \"echoList\", QVariantList{QVariant::fromValue(v)}"));
// The bare (spreading) form must not appear.
EXPECT_FALSE(src.contains("QVariantList{v}"));
}
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"));
}