add tests for cpp-generator and sdk

This commit is contained in:
Iuri Matias
2026-03-23 20:57:17 -04:00
parent 4197ee1830
commit d687418b00
25 changed files with 1872 additions and 407 deletions
+15
View File
@@ -62,6 +62,21 @@ The nix build system is organized into modular files in the `/nix` directory:
- `nix/bin.nix` - Generator binary compilation
- `nix/lib.nix` - SDK library compilation
- `nix/include.nix` - Header installation
- `nix/tests.nix` - Test suite (build + run via `nix build '.#tests'`)
#### Run Tests
```bash
# Build and run all tests (build fails if any test fails)
nix build '.#tests'
```
The test binaries are available in `result/bin/` and can be re-run with filters:
```bash
./result/bin/sdk_tests --gtest_filter="LogosResultTest.*"
./result/bin/generator_tests --gtest_filter="*PascalCase*"
```
### Manual Build
+1
View File
@@ -11,6 +11,7 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)
add_executable(logos-cpp-generator
main.cpp
generator_lib.cpp
)
target_link_libraries(logos-cpp-generator PRIVATE Qt${QT_VERSION_MAJOR}::Core)
+411
View File
@@ -0,0 +1,411 @@
#include "generator_lib.h"
#include <QFile>
#include <QJsonObject>
#include <QRegularExpression>
#include <QSet>
#include <QStringList>
QString toPascalCase(const QString& name)
{
QString out;
bool cap = true;
for (QChar c : name) {
if (!c.isLetterOrNumber()) { cap = true; continue; }
if (cap) { out.append(c.toUpper()); cap = false; }
else { out.append(c.toLower()); }
}
if (out.isEmpty()) return QString("Module");
return out;
}
QString normalizeType(QString t)
{
t = t.trimmed();
if (t.startsWith("const ")) t = t.mid(6);
t = t.trimmed();
// Drop reference and pointer qualifiers
if (t.endsWith('&') || t.endsWith('*')) t.chop(1);
t = t.trimmed();
return t;
}
QString mapParamType(const QString& qtType)
{
const QString base = normalizeType(qtType);
static const QSet<QString> known = {
"void","bool","int","double","float","QString","QStringList","QJsonArray","QVariant"
};
if (known.contains(base)) return base;
// Fallback to QVariant for unknown types
return QString("QVariant");
}
QString mapReturnType(const QString& qtType)
{
const QString base = normalizeType(qtType);
if (base.isEmpty() || base == "void") return QString("void");
static const QSet<QString> known = {
"bool","int","double","float","QString","QStringList","QJsonArray","QVariant","LogosResult"
};
if (known.contains(base)) return base;
return QString("QVariant");
}
QString toQVariantConversion(const QString& type, const QString& argExpr)
{
if (type == "int") return argExpr + ".toInt()";
if (type == "bool") return argExpr + ".toBool()";
if (type == "double") return argExpr + ".toDouble()";
if (type == "float") return argExpr + ".toFloat()";
if (type == "QString") return argExpr + ".toString()";
if (type == "QStringList") return argExpr + ".toStringList()";
if (type == "QJsonArray") return "qvariant_cast<QJsonArray>(" + argExpr + ")";
if (type == "QVariant") return argExpr;
if (type == "LogosResult") return argExpr + ".value<LogosResult>()";
return argExpr + ".toString()";
}
QString makeHeader(const QString& moduleName, const QString& className, const QJsonArray& methods)
{
QString h;
QTextStream s(&h);
s << "#pragma once\n";
s << "#include <QString>\n";
s << "#include <QVariant>\n";
s << "#include <QStringList>\n";
s << "#include <QJsonArray>\n";
s << "#include <functional>\n";
s << "#include <utility>\n";
s << "#include \"logos_types.h\"\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n";
s << "#include \"logos_object.h\"\n\n";
s << "class " << className << " {\n";
s << "public:\n";
s << " explicit " << className << "(LogosAPI* api);\n\n";
s << " using RawEventCallback = std::function<void(const QString&, const QVariantList&)>;\n";
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
s << " bool on(const QString& eventName, EventCallback callback);\n";
s << " void setEventSource(LogosObject* source);\n";
s << " LogosObject* eventSource() const;\n";
s << " void trigger(const QString& eventName);\n";
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, Args&&... args) {\n";
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n";
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n\n";
// Methods
for (const QJsonValue& v : methods) {
const QJsonObject o = v.toObject();
const bool invokable = o.value("isInvokable").toBool();
if (!invokable) continue;
const QString name = o.value("name").toString();
const QString ret = mapReturnType(o.value("returnType").toString());
s << " " << ret << " " << name << "(";
QJsonArray params = o.value("parameters").toArray();
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
s << ");\n";
// Async overload: same params + callback + optional Timeout
QString asyncCallbackType = (ret == "void") ? QString("std::function<void()>") : QString("std::function<void(") + ret + ")>";
s << " void " << name << "Async(";
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
if (params.size() > 0) s << ", ";
s << asyncCallbackType << " callback, Timeout timeout = Timeout());\n";
}
s << "\nprivate:\n";
s << " LogosObject* ensureReplica();\n";
s << " template<typename... Args>\n";
s << " static QVariantList packVariantList(Args&&... args) {\n";
s << " QVariantList list;\n";
s << " list.reserve(sizeof...(Args));\n";
s << " using Expander = int[];\n";
s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward<Args>(args))), 0)...};\n";
s << " return list;\n";
s << " }\n";
s << " LogosAPI* m_api;\n";
s << " LogosAPIClient* m_client;\n";
s << " QString m_moduleName;\n";
s << " LogosObject* m_eventReplica = nullptr;\n";
s << " LogosObject* m_eventSource = nullptr;\n";
s << "};\n";
return h;
}
QString makeSource(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods)
{
QString c;
QTextStream s(&c);
s << "#include \"" << headerBaseName << "\"\n\n";
s << "#include <QDebug>\n\n";
s << className << "::" << className << "(LogosAPI* api) : m_api(api), m_client(api->getClient(\"" << moduleName << "\")), m_moduleName(QStringLiteral(\"" << moduleName << "\")) {}\n\n";
s << "LogosObject* " << className << "::ensureReplica() {\n";
s << " if (!m_eventReplica) {\n";
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
s << " if (!replica) {\n";
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
s << " return nullptr;\n";
s << " }\n";
s << " m_eventReplica = replica;\n";
s << " }\n";
s << " return m_eventReplica;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " LogosObject* origin = ensureReplica();\n";
s << " if (!origin) {\n";
s << " return false;\n";
s << " }\n";
s << " m_client->onEvent(origin, eventName, callback);\n";
s << " return true;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n";
s << " callback(data);\n";
s << " });\n";
s << "}\n\n";
s << "void " << className << "::setEventSource(LogosObject* source) {\n";
s << " m_eventSource = source;\n";
s << "}\n\n";
s << "LogosObject* " << className << "::eventSource() const {\n";
s << " return m_eventSource;\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName) {\n";
s << " trigger(eventName, QVariantList{});\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, const QVariantList& data) {\n";
s << " if (!m_eventSource) {\n";
s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
s << " if (!source) {\n";
s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(source, eventName, data);\n";
s << "}\n\n";
for (const QJsonValue& v : methods) {
const QJsonObject o = v.toObject();
const bool invokable = o.value("isInvokable").toBool();
if (!invokable) continue;
const QString name = o.value("name").toString();
const QString ret = mapReturnType(o.value("returnType").toString());
QJsonArray params = o.value("parameters").toArray();
// Signature
s << ret << " " << className << "::" << name << "(";
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
s << ") {\n";
// Body: perform call
if (ret != "void") {
s << " QVariant _result = ";
} else {
s << " ";
}
if (params.size() == 0) {
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\");\n";
} else if (params.size() == 1) {
QJsonObject p = params.at(0).toObject();
QString pn = p.value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << pn << ");\n";
} else if (params.size() == 2) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ");\n";
} else if (params.size() == 3) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
QString p2 = params.at(2).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ");\n";
} else if (params.size() == 4) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
QString p2 = params.at(2).toObject().value("name").toString();
QString p3 = params.at(3).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ", " << p3 << ");\n";
} else if (params.size() == 5) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
QString p2 = params.at(2).toObject().value("name").toString();
QString p3 = params.at(3).toObject().value("name").toString();
QString p4 = params.at(4).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ", " << p3 << ", " << p4 << ");\n";
} else {
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", QVariantList{";
for (int i = 0; i < params.size(); ++i) {
QString pn = params.at(i).toObject().value("name").toString();
s << pn;
if (i + 1 < params.size()) s << ", ";
}
s << "});\n";
}
// Return conversion
if (ret == "void") {
// nothing
} else if (ret == "bool") {
s << " return _result.toBool();\n";
} else if (ret == "int") {
s << " return _result.toInt();\n";
} else if (ret == "double") {
s << " return _result.toDouble();\n";
} else if (ret == "float") {
s << " return _result.toFloat();\n";
} else if (ret == "QString") {
s << " return _result.toString();\n";
} else if (ret == "QStringList") {
s << " return _result.toStringList();\n";
} else if (ret == "QJsonArray") {
s << " return qvariant_cast<QJsonArray>(_result);\n";
}else if (ret == "LogosResult") {
s << " return _result.value<LogosResult>();\n";
} else { // QVariant
s << " return _result;\n";
}
s << "}\n\n";
// Async implementation
s << "void " << className << "::" << name << "Async(";
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
if (params.size() > 0) s << ", ";
s << "std::function<void(" << (ret == "void" ? "void" : ret) << ")> callback, Timeout timeout) {\n";
s << " if (!callback) return;\n";
s << " m_client->invokeRemoteMethodAsync(\"" << moduleName << "\", \"" << name << "\", ";
if (params.size() == 0) {
s << "QVariantList()";
} else if (params.size() == 1) {
s << "QVariantList() << " << params.at(0).toObject().value("name").toString();
} else {
s << "QVariantList{";
for (int i = 0; i < params.size(); ++i) {
s << params.at(i).toObject().value("name").toString();
if (i + 1 < params.size()) s << ", ";
}
s << "}";
}
s << ", [callback](QVariant v) {\n";
if (ret == "void") {
s << " callback();\n";
} else {
QString defaultVal;
if (ret == "bool") defaultVal = "false";
else if (ret == "int" || ret == "double" || ret == "float") defaultVal = "0";
else if (ret == "QString") defaultVal = "QString()";
else if (ret == "QStringList") defaultVal = "QStringList()";
else if (ret == "QJsonArray") defaultVal = "QJsonArray()";
else defaultVal = ret + "{}";
if (ret == "QVariant") {
s << " callback(v);\n";
} else {
s << " callback(v.isValid() ? qvariant_cast<" << ret << ">(v) : " << defaultVal << ");\n";
}
}
s << " }, timeout);\n";
s << "}\n\n";
}
return c;
}
QVector<ParsedMethod> parseProviderHeader(const QString& headerPath, QTextStream& err)
{
QVector<ParsedMethod> methods;
QFile file(headerPath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
err << "Cannot open header file: " << headerPath << "\n";
return methods;
}
QTextStream in(&file);
QRegularExpression re(
R"(^\s*LOGOS_METHOD\s+(.+?)\s+(\w+)\s*\(([^)]*)\)\s*;)"
);
while (!in.atEnd()) {
QString line = in.readLine();
auto match = re.match(line);
if (!match.hasMatch()) continue;
ParsedMethod m;
m.returnType = normalizeType(match.captured(1));
m.name = match.captured(2);
QString paramStr = match.captured(3).trimmed();
if (!paramStr.isEmpty()) {
QStringList paramParts = paramStr.split(',');
for (const QString& part : paramParts) {
QString trimmed = part.trimmed();
int eqIdx = trimmed.indexOf('=');
if (eqIdx > 0) trimmed = trimmed.left(eqIdx).trimmed();
int lastSpace = trimmed.lastIndexOf(' ');
int lastAmp = trimmed.lastIndexOf('&');
int splitAt = qMax(lastSpace, lastAmp);
if (splitAt > 0) {
QString type = normalizeType(trimmed.left(splitAt + 1));
QString pname = trimmed.mid(splitAt + 1).trimmed();
m.params.append({type, pname});
} else {
m.params.append({normalizeType(trimmed), QString("arg%1").arg(m.params.size())});
}
}
}
methods.append(m);
}
file.close();
return methods;
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef GENERATOR_LIB_H
#define GENERATOR_LIB_H
#include <QString>
#include <QJsonArray>
#include <QTextStream>
#include <QVector>
#include <QPair>
struct ParsedMethod {
QString returnType;
QString name;
QVector<QPair<QString, QString>> params; // (type, name)
};
QString toPascalCase(const QString& name);
QString normalizeType(QString t);
QString mapParamType(const QString& qtType);
QString mapReturnType(const QString& qtType);
QString toQVariantConversion(const QString& type, const QString& argExpr);
QString makeHeader(const QString& moduleName, const QString& className, const QJsonArray& methods);
QString makeSource(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods);
QVector<ParsedMethod> parseProviderHeader(const QString& headerPath, QTextStream& err);
#endif // GENERATOR_LIB_H
+5 -407
View File
@@ -15,6 +15,7 @@
#include <QRegularExpression>
#include <QtGlobal>
#include "logos_provider_object.h"
#include "generator_lib.h"
static QJsonArray enumerateMethods(QObject* moduleInstance)
{
@@ -62,344 +63,11 @@ static QJsonArray enumerateMethods(QObject* moduleInstance)
return methodsArray;
}
static QString toPascalCase(const QString& name)
{
QString out;
bool cap = true;
for (QChar c : name) {
if (!c.isLetterOrNumber()) { cap = true; continue; }
if (cap) { out.append(c.toUpper()); cap = false; }
else { out.append(c.toLower()); }
}
if (out.isEmpty()) return QString("Module");
return out;
}
// toPascalCase, normalizeType, mapParamType, mapReturnType -> generator_lib.h/cpp
static QString normalizeType(QString t)
{
t = t.trimmed();
if (t.startsWith("const ")) t = t.mid(6);
t = t.trimmed();
// Drop reference and pointer qualifiers
if (t.endsWith('&') || t.endsWith('*')) t.chop(1);
t = t.trimmed();
return t;
}
// makeHeader -> generator_lib.h/cpp
static QString mapParamType(const QString& qtType)
{
const QString base = normalizeType(qtType);
static const QSet<QString> known = {
"void","bool","int","double","float","QString","QStringList","QJsonArray","QVariant"
};
if (known.contains(base)) return base;
// Fallback to QVariant for unknown types
return QString("QVariant");
}
static QString mapReturnType(const QString& qtType)
{
const QString base = normalizeType(qtType);
if (base.isEmpty() || base == "void") return QString("void");
static const QSet<QString> known = {
"bool","int","double","float","QString","QStringList","QJsonArray","QVariant","LogosResult"
};
if (known.contains(base)) return base;
return QString("QVariant");
}
static QString makeHeader(const QString& moduleName, const QString& className, const QJsonArray& methods)
{
QString h;
QTextStream s(&h);
s << "#pragma once\n";
s << "#include <QString>\n";
s << "#include <QVariant>\n";
s << "#include <QStringList>\n";
s << "#include <QJsonArray>\n";
s << "#include <functional>\n";
s << "#include <utility>\n";
s << "#include \"logos_types.h\"\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n";
s << "#include \"logos_object.h\"\n\n";
s << "class " << className << " {\n";
s << "public:\n";
s << " explicit " << className << "(LogosAPI* api);\n\n";
s << " using RawEventCallback = std::function<void(const QString&, const QVariantList&)>;\n";
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
s << " bool on(const QString& eventName, EventCallback callback);\n";
s << " void setEventSource(LogosObject* source);\n";
s << " LogosObject* eventSource() const;\n";
s << " void trigger(const QString& eventName);\n";
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, Args&&... args) {\n";
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n";
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n\n";
// Methods
for (const QJsonValue& v : methods) {
const QJsonObject o = v.toObject();
const bool invokable = o.value("isInvokable").toBool();
if (!invokable) continue;
const QString name = o.value("name").toString();
const QString ret = mapReturnType(o.value("returnType").toString());
s << " " << ret << " " << name << "(";
QJsonArray params = o.value("parameters").toArray();
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
s << ");\n";
// Async overload: same params + callback + optional Timeout
QString asyncCallbackType = (ret == "void") ? QString("std::function<void()>") : QString("std::function<void(") + ret + ")>";
s << " void " << name << "Async(";
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
if (params.size() > 0) s << ", ";
s << asyncCallbackType << " callback, Timeout timeout = Timeout());\n";
}
s << "\nprivate:\n";
s << " LogosObject* ensureReplica();\n";
s << " template<typename... Args>\n";
s << " static QVariantList packVariantList(Args&&... args) {\n";
s << " QVariantList list;\n";
s << " list.reserve(sizeof...(Args));\n";
s << " using Expander = int[];\n";
s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward<Args>(args))), 0)...};\n";
s << " return list;\n";
s << " }\n";
s << " LogosAPI* m_api;\n";
s << " LogosAPIClient* m_client;\n";
s << " QString m_moduleName;\n";
s << " LogosObject* m_eventReplica = nullptr;\n";
s << " LogosObject* m_eventSource = nullptr;\n";
s << "};\n";
return h;
}
static QString makeSource(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods)
{
QString c;
QTextStream s(&c);
s << "#include \"" << headerBaseName << "\"\n\n";
s << "#include <QDebug>\n\n";
s << className << "::" << className << "(LogosAPI* api) : m_api(api), m_client(api->getClient(\"" << moduleName << "\")), m_moduleName(QStringLiteral(\"" << moduleName << "\")) {}\n\n";
s << "LogosObject* " << className << "::ensureReplica() {\n";
s << " if (!m_eventReplica) {\n";
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
s << " if (!replica) {\n";
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
s << " return nullptr;\n";
s << " }\n";
s << " m_eventReplica = replica;\n";
s << " }\n";
s << " return m_eventReplica;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " LogosObject* origin = ensureReplica();\n";
s << " if (!origin) {\n";
s << " return false;\n";
s << " }\n";
s << " m_client->onEvent(origin, eventName, callback);\n";
s << " return true;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n";
s << " callback(data);\n";
s << " });\n";
s << "}\n\n";
s << "void " << className << "::setEventSource(LogosObject* source) {\n";
s << " m_eventSource = source;\n";
s << "}\n\n";
s << "LogosObject* " << className << "::eventSource() const {\n";
s << " return m_eventSource;\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName) {\n";
s << " trigger(eventName, QVariantList{});\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, const QVariantList& data) {\n";
s << " if (!m_eventSource) {\n";
s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
s << " if (!source) {\n";
s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(source, eventName, data);\n";
s << "}\n\n";
for (const QJsonValue& v : methods) {
const QJsonObject o = v.toObject();
const bool invokable = o.value("isInvokable").toBool();
if (!invokable) continue;
const QString name = o.value("name").toString();
const QString ret = mapReturnType(o.value("returnType").toString());
QJsonArray params = o.value("parameters").toArray();
// Signature
s << ret << " " << className << "::" << name << "(";
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
s << ") {\n";
// Body: perform call
if (ret != "void") {
s << " QVariant _result = ";
} else {
s << " ";
}
if (params.size() == 0) {
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\");\n";
} else if (params.size() == 1) {
QJsonObject p = params.at(0).toObject();
QString pn = p.value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << pn << ");\n";
} else if (params.size() == 2) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ");\n";
} else if (params.size() == 3) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
QString p2 = params.at(2).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ");\n";
} else if (params.size() == 4) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
QString p2 = params.at(2).toObject().value("name").toString();
QString p3 = params.at(3).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ", " << p3 << ");\n";
} else if (params.size() == 5) {
QString p0 = params.at(0).toObject().value("name").toString();
QString p1 = params.at(1).toObject().value("name").toString();
QString p2 = params.at(2).toObject().value("name").toString();
QString p3 = params.at(3).toObject().value("name").toString();
QString p4 = params.at(4).toObject().value("name").toString();
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ", " << p3 << ", " << p4 << ");\n";
} else {
s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", QVariantList{";
for (int i = 0; i < params.size(); ++i) {
QString pn = params.at(i).toObject().value("name").toString();
s << pn;
if (i + 1 < params.size()) s << ", ";
}
s << "});\n";
}
// Return conversion
if (ret == "void") {
// nothing
} else if (ret == "bool") {
s << " return _result.toBool();\n";
} else if (ret == "int") {
s << " return _result.toInt();\n";
} else if (ret == "double") {
s << " return _result.toDouble();\n";
} else if (ret == "float") {
s << " return _result.toFloat();\n";
} else if (ret == "QString") {
s << " return _result.toString();\n";
} else if (ret == "QStringList") {
s << " return _result.toStringList();\n";
} else if (ret == "QJsonArray") {
s << " return qvariant_cast<QJsonArray>(_result);\n";
}else if (ret == "LogosResult") {
s << " return _result.value<LogosResult>();\n";
} else { // QVariant
s << " return _result;\n";
}
s << "}\n\n";
// Async implementation
s << "void " << className << "::" << name << "Async(";
for (int i = 0; i < params.size(); ++i) {
QJsonObject p = params.at(i).toObject();
QString pt = mapParamType(p.value("type").toString());
QString pn = p.value("name").toString();
if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") {
s << "const " << pt << "& " << pn;
} else {
s << pt << " " << pn;
}
if (i + 1 < params.size()) s << ", ";
}
if (params.size() > 0) s << ", ";
s << "std::function<void(" << (ret == "void" ? "void" : ret) << ")> callback, Timeout timeout) {\n";
s << " if (!callback) return;\n";
s << " m_client->invokeRemoteMethodAsync(\"" << moduleName << "\", \"" << name << "\", ";
if (params.size() == 0) {
s << "QVariantList()";
} else if (params.size() == 1) {
s << "QVariantList() << " << params.at(0).toObject().value("name").toString();
} else {
s << "QVariantList{";
for (int i = 0; i < params.size(); ++i) {
s << params.at(i).toObject().value("name").toString();
if (i + 1 < params.size()) s << ", ";
}
s << "}";
}
s << ", [callback](QVariant v) {\n";
if (ret == "void") {
s << " callback();\n";
} else {
QString defaultVal;
if (ret == "bool") defaultVal = "false";
else if (ret == "int" || ret == "double" || ret == "float") defaultVal = "0";
else if (ret == "QString") defaultVal = "QString()";
else if (ret == "QStringList") defaultVal = "QStringList()";
else if (ret == "QJsonArray") defaultVal = "QJsonArray()";
else defaultVal = ret + "{}";
if (ret == "QVariant") {
s << " callback(v);\n";
} else {
s << " callback(v.isValid() ? qvariant_cast<" << ret << ">(v) : " << defaultVal << ");\n";
}
}
s << " }, timeout);\n";
s << "}\n\n";
}
return c;
}
// makeSource -> generator_lib.h/cpp
static QString makeCoreManagerHeader()
{
@@ -744,77 +412,7 @@ static bool writeUmbrellaSourceFromDeps(const QString& genDirPath, const QJsonAr
}
// ── Provider-header mode: scan LOGOS_METHOD markers and generate dispatch ────
struct ParsedMethod {
QString returnType;
QString name;
QVector<QPair<QString, QString>> params; // (type, name)
};
static QVector<ParsedMethod> parseProviderHeader(const QString& headerPath, QTextStream& err)
{
QVector<ParsedMethod> methods;
QFile file(headerPath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
err << "Cannot open header file: " << headerPath << "\n";
return methods;
}
QTextStream in(&file);
QRegularExpression re(
R"(^\s*LOGOS_METHOD\s+(.+?)\s+(\w+)\s*\(([^)]*)\)\s*;)"
);
while (!in.atEnd()) {
QString line = in.readLine();
auto match = re.match(line);
if (!match.hasMatch()) continue;
ParsedMethod m;
m.returnType = normalizeType(match.captured(1));
m.name = match.captured(2);
QString paramStr = match.captured(3).trimmed();
if (!paramStr.isEmpty()) {
QStringList paramParts = paramStr.split(',');
for (const QString& part : paramParts) {
QString trimmed = part.trimmed();
int eqIdx = trimmed.indexOf('=');
if (eqIdx > 0) trimmed = trimmed.left(eqIdx).trimmed();
int lastSpace = trimmed.lastIndexOf(' ');
int lastAmp = trimmed.lastIndexOf('&');
int splitAt = qMax(lastSpace, lastAmp);
if (splitAt > 0) {
QString type = normalizeType(trimmed.left(splitAt + 1));
QString pname = trimmed.mid(splitAt + 1).trimmed();
m.params.append({type, pname});
} else {
m.params.append({normalizeType(trimmed), QString("arg%1").arg(m.params.size())});
}
}
}
methods.append(m);
}
file.close();
return methods;
}
static QString toQVariantConversion(const QString& type, const QString& argExpr)
{
if (type == "int") return argExpr + ".toInt()";
if (type == "bool") return argExpr + ".toBool()";
if (type == "double") return argExpr + ".toDouble()";
if (type == "float") return argExpr + ".toFloat()";
if (type == "QString") return argExpr + ".toString()";
if (type == "QStringList") return argExpr + ".toStringList()";
if (type == "QJsonArray") return "qvariant_cast<QJsonArray>(" + argExpr + ")";
if (type == "QVariant") return argExpr;
if (type == "LogosResult") return argExpr + ".value<LogosResult>()";
return argExpr + ".toString()";
}
// ParsedMethod, parseProviderHeader, toQVariantConversion -> generator_lib.h/cpp
static int generateProviderDispatch(const QString& headerPath, const QString& outputDir, QTextStream& out, QTextStream& err)
{
+3
View File
@@ -22,6 +22,7 @@
bin = import ./nix/bin.nix { inherit pkgs common src; };
lib = import ./nix/lib.nix { inherit pkgs common src; };
include = import ./nix/include.nix { inherit pkgs common src; };
tests = import ./nix/tests.nix { inherit pkgs common src; };
# Combined SDK package
sdk = pkgs.symlinkJoin {
@@ -34,6 +35,7 @@
logos-cpp-bin = bin;
logos-cpp-lib = lib;
logos-cpp-include = include;
inherit tests;
# Combined outputs (for backward compatibility)
logos-cpp-sdk = sdk;
@@ -54,6 +56,7 @@
buildInputs = [
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
pkgs.gtest
];
};
});
+48
View File
@@ -0,0 +1,48 @@
# Builds and runs the test suite
{ pkgs, common, src }:
pkgs.stdenv.mkDerivation {
pname = "${common.pname}-tests";
version = common.version;
inherit src;
nativeBuildInputs = common.nativeBuildInputs;
buildInputs = common.buildInputs ++ [ pkgs.gtest ];
cmakeFlags = common.cmakeFlags;
dontUseCmakeConfigure = true;
buildPhase = ''
runHook preBuild
mkdir -p build-tests
cd build-tests
cmake ../tests -GNinja $cmakeFlags
ninja
cd ..
runHook postBuild
'';
doCheck = true;
checkPhase = ''
runHook preCheck
cd build-tests
ctest --output-on-failure
cd ..
runHook postCheck
'';
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp build-tests/sdk/sdk_tests $out/bin/
cp build-tests/generator/generator_tests $out/bin/
runHook postInstall
'';
inherit (common) meta;
}
+15
View File
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.14)
project(LogosCppSdkTests)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
find_package(GTest REQUIRED)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core RemoteObjects)
enable_testing()
add_subdirectory(sdk)
add_subdirectory(generator)
+23
View File
@@ -0,0 +1,23 @@
add_executable(generator_tests
${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/generator_lib.cpp
test_to_pascal_case.cpp
test_normalize_type.cpp
test_map_param_type.cpp
test_map_return_type.cpp
test_to_qvariant_conversion.cpp
test_make_header.cpp
test_make_source.cpp
test_parse_provider_header.cpp
)
target_include_directories(generator_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator
${CMAKE_CURRENT_SOURCE_DIR}/../../cpp
)
target_link_libraries(generator_tests PRIVATE
GTest::gtest_main
Qt${QT_VERSION_MAJOR}::Core
)
gtest_discover_tests(generator_tests)
+141
View File
@@ -0,0 +1,141 @@
#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\""));
}
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)"));
EXPECT_TRUE(h.contains("void reset()"));
// 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"));
}
+158
View File
@@ -0,0 +1,158 @@
#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\")"));
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\", p0)"));
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\", p0, p1)"));
}
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, 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"));
}
+33
View File
@@ -0,0 +1,33 @@
#include <gtest/gtest.h>
#include "generator_lib.h"
TEST(MapParamTypeTest, KnownTypes)
{
EXPECT_EQ(mapParamType("bool"), "bool");
EXPECT_EQ(mapParamType("int"), "int");
EXPECT_EQ(mapParamType("double"), "double");
EXPECT_EQ(mapParamType("float"), "float");
EXPECT_EQ(mapParamType("QString"), "QString");
EXPECT_EQ(mapParamType("QStringList"), "QStringList");
EXPECT_EQ(mapParamType("QJsonArray"), "QJsonArray");
EXPECT_EQ(mapParamType("QVariant"), "QVariant");
EXPECT_EQ(mapParamType("void"), "void");
}
TEST(MapParamTypeTest, ConstRef)
{
EXPECT_EQ(mapParamType("const QString&"), "QString");
EXPECT_EQ(mapParamType("const int&"), "int");
}
TEST(MapParamTypeTest, UnknownType)
{
EXPECT_EQ(mapParamType("MyCustomType"), "QVariant");
EXPECT_EQ(mapParamType("SomeClass*"), "QVariant");
}
TEST(MapParamTypeTest, LogosResultFallsBack)
{
// LogosResult is NOT in the param known set
EXPECT_EQ(mapParamType("LogosResult"), "QVariant");
}
+31
View File
@@ -0,0 +1,31 @@
#include <gtest/gtest.h>
#include "generator_lib.h"
TEST(MapReturnTypeTest, KnownTypes)
{
EXPECT_EQ(mapReturnType("bool"), "bool");
EXPECT_EQ(mapReturnType("int"), "int");
EXPECT_EQ(mapReturnType("double"), "double");
EXPECT_EQ(mapReturnType("float"), "float");
EXPECT_EQ(mapReturnType("QString"), "QString");
EXPECT_EQ(mapReturnType("QStringList"), "QStringList");
EXPECT_EQ(mapReturnType("QJsonArray"), "QJsonArray");
EXPECT_EQ(mapReturnType("QVariant"), "QVariant");
EXPECT_EQ(mapReturnType("LogosResult"), "LogosResult");
}
TEST(MapReturnTypeTest, VoidHandling)
{
EXPECT_EQ(mapReturnType("void"), "void");
EXPECT_EQ(mapReturnType(""), "void");
}
TEST(MapReturnTypeTest, ConstRef)
{
EXPECT_EQ(mapReturnType("const QString&"), "QString");
}
TEST(MapReturnTypeTest, UnknownType)
{
EXPECT_EQ(mapReturnType("MyCustomType"), "QVariant");
}
+42
View File
@@ -0,0 +1,42 @@
#include <gtest/gtest.h>
#include "generator_lib.h"
TEST(NormalizeTypeTest, PlainType)
{
EXPECT_EQ(normalizeType("int"), "int");
}
TEST(NormalizeTypeTest, ConstRef)
{
EXPECT_EQ(normalizeType("const QString&"), "QString");
}
TEST(NormalizeTypeTest, ConstValue)
{
EXPECT_EQ(normalizeType("const int"), "int");
}
TEST(NormalizeTypeTest, Pointer)
{
EXPECT_EQ(normalizeType("QObject*"), "QObject");
}
TEST(NormalizeTypeTest, Reference)
{
EXPECT_EQ(normalizeType("QString&"), "QString");
}
TEST(NormalizeTypeTest, WithWhitespace)
{
EXPECT_EQ(normalizeType(" const QString & "), "QString");
}
TEST(NormalizeTypeTest, EmptyString)
{
EXPECT_EQ(normalizeType(""), "");
}
TEST(NormalizeTypeTest, NoQualifiers)
{
EXPECT_EQ(normalizeType("QVariant"), "QVariant");
}
@@ -0,0 +1,143 @@
#include <gtest/gtest.h>
#include <QTemporaryFile>
#include <QTextStream>
#include "generator_lib.h"
static QString writeTempHeader(const QString& content)
{
QTemporaryFile* tmp = new QTemporaryFile();
tmp->setAutoRemove(true);
tmp->open();
QTextStream out(tmp);
out << content;
out.flush();
QString path = tmp->fileName();
// Keep file open so it stays on disk; caller reads it then it auto-removes
// Actually we need to close so parseProviderHeader can open it
tmp->close();
// Keep the object alive by leaking — tests are short-lived
// Actually QTemporaryFile removes on close by default, so disable auto-remove
// and manage manually. Let's use a simpler approach:
return path;
}
class ParseProviderHeaderTest : public ::testing::Test {
protected:
void writeFile(const QString& content)
{
m_file.setAutoRemove(true);
m_file.open();
QTextStream out(&m_file);
out << content;
out.flush();
// Don't close — keep the file on disk
}
QVector<ParsedMethod> parse()
{
QString path = m_file.fileName();
QString errStr;
QTextStream err(&errStr);
return parseProviderHeader(path, err);
}
QString parseError()
{
QString path = m_file.fileName();
QString errStr;
QTextStream err(&errStr);
parseProviderHeader(path, err);
return errStr;
}
QTemporaryFile m_file;
};
TEST_F(ParseProviderHeaderTest, SimpleMethod)
{
writeFile(" LOGOS_METHOD void doStuff();\n");
auto methods = parse();
ASSERT_EQ(methods.size(), 1);
EXPECT_EQ(methods[0].returnType, "void");
EXPECT_EQ(methods[0].name, "doStuff");
EXPECT_TRUE(methods[0].params.isEmpty());
}
TEST_F(ParseProviderHeaderTest, MethodWithParams)
{
writeFile(" LOGOS_METHOD int add(int a, int b);\n");
auto methods = parse();
ASSERT_EQ(methods.size(), 1);
EXPECT_EQ(methods[0].returnType, "int");
EXPECT_EQ(methods[0].name, "add");
ASSERT_EQ(methods[0].params.size(), 2);
EXPECT_EQ(methods[0].params[0].first, "int");
EXPECT_EQ(methods[0].params[0].second, "a");
EXPECT_EQ(methods[0].params[1].first, "int");
EXPECT_EQ(methods[0].params[1].second, "b");
}
TEST_F(ParseProviderHeaderTest, ConstRefParam)
{
writeFile(" LOGOS_METHOD QString greet(const QString& name);\n");
auto methods = parse();
ASSERT_EQ(methods.size(), 1);
ASSERT_EQ(methods[0].params.size(), 1);
EXPECT_EQ(methods[0].params[0].first, "QString");
EXPECT_EQ(methods[0].params[0].second, "name");
}
TEST_F(ParseProviderHeaderTest, DefaultArgStripped)
{
writeFile(" LOGOS_METHOD void setVal(int x = 10);\n");
auto methods = parse();
ASSERT_EQ(methods.size(), 1);
ASSERT_EQ(methods[0].params.size(), 1);
EXPECT_EQ(methods[0].params[0].first, "int");
EXPECT_EQ(methods[0].params[0].second, "x");
}
TEST_F(ParseProviderHeaderTest, MultipleMethodsParsed)
{
writeFile(
"class Foo : public LogosProviderBase {\n"
" LOGOS_METHOD void a();\n"
" LOGOS_METHOD int b(int x);\n"
" LOGOS_METHOD QString c(const QString& s, bool flag);\n"
"};\n"
);
auto methods = parse();
EXPECT_EQ(methods.size(), 3);
}
TEST_F(ParseProviderHeaderTest, NonLogosMethods_Ignored)
{
writeFile(
"void normalFunction();\n"
" LOGOS_METHOD void tracked();\n"
"int other(int x);\n"
);
auto methods = parse();
EXPECT_EQ(methods.size(), 1);
EXPECT_EQ(methods[0].name, "tracked");
}
TEST_F(ParseProviderHeaderTest, MissingFileReturnsEmpty)
{
QString errStr;
QTextStream err(&errStr);
auto methods = parseProviderHeader("/nonexistent/path.h", err);
EXPECT_TRUE(methods.isEmpty());
EXPECT_TRUE(errStr.contains("Cannot open"));
}
TEST_F(ParseProviderHeaderTest, FallbackParamNames)
{
writeFile(" LOGOS_METHOD void fn(int, QString);\n");
auto methods = parse();
ASSERT_EQ(methods.size(), 1);
// Single-token params get fallback names
ASSERT_EQ(methods[0].params.size(), 2);
EXPECT_EQ(methods[0].params[0].second, "arg0");
EXPECT_EQ(methods[0].params[1].second, "arg1");
}
+52
View File
@@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include "generator_lib.h"
TEST(ToPascalCaseTest, SnakeCase)
{
EXPECT_EQ(toPascalCase("my_module"), "MyModule");
}
TEST(ToPascalCaseTest, KebabCase)
{
EXPECT_EQ(toPascalCase("my-module"), "MyModule");
}
TEST(ToPascalCaseTest, DotSeparated)
{
EXPECT_EQ(toPascalCase("my.module"), "MyModule");
}
TEST(ToPascalCaseTest, SingleWord)
{
EXPECT_EQ(toPascalCase("hello"), "Hello");
}
TEST(ToPascalCaseTest, EmptyInput)
{
EXPECT_EQ(toPascalCase(""), "Module");
}
TEST(ToPascalCaseTest, AllCaps)
{
EXPECT_EQ(toPascalCase("MY_MODULE"), "MyModule");
}
TEST(ToPascalCaseTest, AlreadyPascal)
{
EXPECT_EQ(toPascalCase("MyModule"), "Mymodule");
}
TEST(ToPascalCaseTest, MultipleDelimiters)
{
EXPECT_EQ(toPascalCase("a_b-c.d"), "ABCD");
}
TEST(ToPascalCaseTest, LeadingDelimiters)
{
EXPECT_EQ(toPascalCase("__test"), "Test");
}
TEST(ToPascalCaseTest, OnlyDelimiters)
{
EXPECT_EQ(toPascalCase("___"), "Module");
}
@@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include "generator_lib.h"
TEST(ToQVariantConversionTest, Int)
{
EXPECT_EQ(toQVariantConversion("int", "v"), "v.toInt()");
}
TEST(ToQVariantConversionTest, Bool)
{
EXPECT_EQ(toQVariantConversion("bool", "v"), "v.toBool()");
}
TEST(ToQVariantConversionTest, Double)
{
EXPECT_EQ(toQVariantConversion("double", "v"), "v.toDouble()");
}
TEST(ToQVariantConversionTest, Float)
{
EXPECT_EQ(toQVariantConversion("float", "v"), "v.toFloat()");
}
TEST(ToQVariantConversionTest, QString)
{
EXPECT_EQ(toQVariantConversion("QString", "v"), "v.toString()");
}
TEST(ToQVariantConversionTest, QStringList)
{
EXPECT_EQ(toQVariantConversion("QStringList", "v"), "v.toStringList()");
}
TEST(ToQVariantConversionTest, QJsonArray)
{
EXPECT_EQ(toQVariantConversion("QJsonArray", "arg"), "qvariant_cast<QJsonArray>(arg)");
}
TEST(ToQVariantConversionTest, QVariantPassthrough)
{
EXPECT_EQ(toQVariantConversion("QVariant", "v"), "v");
}
TEST(ToQVariantConversionTest, LogosResult)
{
EXPECT_EQ(toQVariantConversion("LogosResult", "v"), "v.value<LogosResult>()");
}
TEST(ToQVariantConversionTest, UnknownFallsBackToString)
{
EXPECT_EQ(toQVariantConversion("SomeCustomType", "x"), "x.toString()");
}
+23
View File
@@ -0,0 +1,23 @@
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../cpp ${CMAKE_CURRENT_BINARY_DIR}/logos_sdk)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Test)
add_executable(sdk_tests
test_main.cpp
test_logos_result.cpp
test_token_manager.cpp
test_mock_store.cpp
test_logos_mode.cpp
test_logos_mock_setup.cpp
test_logos_api_client_mock.cpp
)
target_link_libraries(sdk_tests PRIVATE
logos_sdk
GTest::gtest
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::RemoteObjects
Qt${QT_VERSION_MAJOR}::Test
)
gtest_discover_tests(sdk_tests)
+99
View File
@@ -0,0 +1,99 @@
#include <gtest/gtest.h>
#include "logos_mock.h"
#include "logos_api.h"
#include "logos_api_client.h"
class LogosApiClientMockTest : public ::testing::Test {
protected:
void SetUp() override
{
m_mock = new LogosMockSetup();
}
void TearDown() override
{
delete m_mock;
}
LogosMockSetup* m_mock = nullptr;
};
TEST_F(LogosApiClientMockTest, InvokeRemoteMethodReturnsMockedValue)
{
m_mock->when("test_module", "getValue").thenReturn(QVariant(42));
LogosAPI api("client_module");
LogosAPIClient* client = api.getClient("test_module");
ASSERT_NE(client, nullptr);
QVariant result = client->invokeRemoteMethod("test_module", "getValue");
EXPECT_EQ(result.toInt(), 42);
EXPECT_TRUE(m_mock->wasCalled("test_module", "getValue"));
}
TEST_F(LogosApiClientMockTest, InvokeWithSingleArg)
{
m_mock->when("mod", "echo")
.withArgs({QVariant("hello")})
.thenReturn(QVariant("hello back"));
LogosAPI api("origin");
LogosAPIClient* client = api.getClient("mod");
ASSERT_NE(client, nullptr);
QVariant result = client->invokeRemoteMethod("mod", "echo", QVariant("hello"));
EXPECT_EQ(result.toString(), "hello back");
}
TEST_F(LogosApiClientMockTest, InvokeWithMultipleArgs)
{
m_mock->when("math", "add").thenReturn(QVariant(30));
LogosAPI api("origin");
LogosAPIClient* client = api.getClient("math");
ASSERT_NE(client, nullptr);
QVariant result = client->invokeRemoteMethod("math", "add", QVariant(10), QVariant(20));
EXPECT_EQ(result.toInt(), 30);
EXPECT_TRUE(m_mock->wasCalled("math", "add"));
}
TEST_F(LogosApiClientMockTest, InvokeWithArgsList)
{
m_mock->when("mod", "multi").thenReturn(QVariant("done"));
LogosAPI api("origin");
LogosAPIClient* client = api.getClient("mod");
ASSERT_NE(client, nullptr);
QVariantList args;
args << QVariant(1) << QVariant(2) << QVariant(3);
QVariant result = client->invokeRemoteMethod("mod", "multi", args);
EXPECT_EQ(result.toString(), "done");
}
TEST_F(LogosApiClientMockTest, NoExpectationReturnsInvalid)
{
// Token still pre-seeded by when(), but no expectation for this method
m_mock->when("mod", "other").thenReturn(QVariant(1));
LogosAPI api("origin");
LogosAPIClient* client = api.getClient("mod");
ASSERT_NE(client, nullptr);
QVariant result = client->invokeRemoteMethod("mod", "unregistered");
EXPECT_FALSE(result.isValid());
}
TEST_F(LogosApiClientMockTest, CallCountTracking)
{
m_mock->when("mod", "fn").thenReturn(QVariant(true));
LogosAPI api("origin");
LogosAPIClient* client = api.getClient("mod");
ASSERT_NE(client, nullptr);
client->invokeRemoteMethod("mod", "fn");
client->invokeRemoteMethod("mod", "fn");
client->invokeRemoteMethod("mod", "fn");
EXPECT_EQ(m_mock->callCount("mod", "fn"), 3);
}
+102
View File
@@ -0,0 +1,102 @@
#include <gtest/gtest.h>
#include "logos_mock.h"
class LogosMockSetupTest : public ::testing::Test {
protected:
void SetUp() override
{
// Ensure we start in Remote mode
LogosModeConfig::setMode(LogosMode::Remote);
MockStore::instance().reset();
TokenManager::instance().clearAllTokens();
}
void TearDown() override
{
LogosModeConfig::setMode(LogosMode::Remote);
}
};
TEST_F(LogosMockSetupTest, SwitchesToMockMode)
{
{
LogosMockSetup mock;
EXPECT_TRUE(LogosModeConfig::isMock());
}
// After destruction, mode is restored
EXPECT_TRUE(LogosModeConfig::isRemote());
}
TEST_F(LogosMockSetupTest, RestoresPreviousMode)
{
LogosModeConfig::setMode(LogosMode::Local);
{
LogosMockSetup mock;
EXPECT_TRUE(LogosModeConfig::isMock());
}
EXPECT_TRUE(LogosModeConfig::isLocal());
// Restore for teardown
LogosModeConfig::setMode(LogosMode::Remote);
}
TEST_F(LogosMockSetupTest, ResetsStoreOnConstruction)
{
MockStore::instance().when("mod", "fn").thenReturn(QVariant(1));
MockStore::instance().recordAndReturn("mod", "fn", {});
LogosMockSetup mock;
// Previous expectations and calls cleared
EXPECT_FALSE(MockStore::instance().wasCalled("mod", "fn"));
QVariant r = MockStore::instance().recordAndReturn("mod", "fn", {});
EXPECT_FALSE(r.isValid());
}
TEST_F(LogosMockSetupTest, ClearsTokensOnConstruction)
{
TokenManager::instance().saveToken("stale", "token");
LogosMockSetup mock;
EXPECT_FALSE(TokenManager::instance().hasToken("stale"));
}
TEST_F(LogosMockSetupTest, WhenPreSeedsToken)
{
LogosMockSetup mock;
mock.when("my_module", "doStuff").thenReturn(QVariant("ok"));
EXPECT_TRUE(TokenManager::instance().hasToken("my_module"));
}
TEST_F(LogosMockSetupTest, WhenSetsExpectation)
{
LogosMockSetup mock;
mock.when("mod", "fn").thenReturn(QVariant(99));
QVariant r = MockStore::instance().recordAndReturn("mod", "fn", {});
EXPECT_EQ(r.toInt(), 99);
}
TEST_F(LogosMockSetupTest, VerificationDelegates)
{
LogosMockSetup mock;
mock.when("mod", "fn").thenReturn(QVariant(1));
MockStore::instance().recordAndReturn("mod", "fn", {QVariant("arg")});
EXPECT_TRUE(mock.wasCalled("mod", "fn"));
EXPECT_TRUE(mock.wasCalledWith("mod", "fn", {QVariant("arg")}));
EXPECT_EQ(mock.callCount("mod", "fn"), 1);
EXPECT_EQ(mock.lastArgs("mod", "fn").size(), 1);
}
TEST_F(LogosMockSetupTest, ResetsStoreOnDestruction)
{
{
LogosMockSetup mock;
mock.when("mod", "fn").thenReturn(QVariant(1));
MockStore::instance().recordAndReturn("mod", "fn", {});
}
// After destruction, store is clean
EXPECT_FALSE(MockStore::instance().wasCalled("mod", "fn"));
}
+55
View File
@@ -0,0 +1,55 @@
#include <gtest/gtest.h>
#include "logos_mode.h"
class LogosModeTest : public ::testing::Test {
protected:
void SetUp() override
{
m_saved = LogosModeConfig::getMode();
}
void TearDown() override
{
LogosModeConfig::setMode(m_saved);
}
private:
LogosMode m_saved;
};
TEST_F(LogosModeTest, DefaultIsRemote)
{
LogosModeConfig::setMode(LogosMode::Remote);
EXPECT_EQ(LogosModeConfig::getMode(), LogosMode::Remote);
EXPECT_TRUE(LogosModeConfig::isRemote());
EXPECT_FALSE(LogosModeConfig::isLocal());
EXPECT_FALSE(LogosModeConfig::isMock());
}
TEST_F(LogosModeTest, SetLocal)
{
LogosModeConfig::setMode(LogosMode::Local);
EXPECT_EQ(LogosModeConfig::getMode(), LogosMode::Local);
EXPECT_TRUE(LogosModeConfig::isLocal());
EXPECT_FALSE(LogosModeConfig::isRemote());
EXPECT_FALSE(LogosModeConfig::isMock());
}
TEST_F(LogosModeTest, SetMock)
{
LogosModeConfig::setMode(LogosMode::Mock);
EXPECT_EQ(LogosModeConfig::getMode(), LogosMode::Mock);
EXPECT_TRUE(LogosModeConfig::isMock());
EXPECT_FALSE(LogosModeConfig::isRemote());
EXPECT_FALSE(LogosModeConfig::isLocal());
}
TEST_F(LogosModeTest, SwitchBetweenModes)
{
LogosModeConfig::setMode(LogosMode::Local);
EXPECT_TRUE(LogosModeConfig::isLocal());
LogosModeConfig::setMode(LogosMode::Mock);
EXPECT_TRUE(LogosModeConfig::isMock());
LogosModeConfig::setMode(LogosMode::Remote);
EXPECT_TRUE(LogosModeConfig::isRemote());
}
+174
View File
@@ -0,0 +1,174 @@
#include <gtest/gtest.h>
#include <QIODevice>
#include "logos_types.h"
TEST(LogosResultTest, GetValueSuccess)
{
LogosResult r;
r.success = true;
r.value = QVariant(42);
EXPECT_EQ(r.getValue<int>(), 42);
}
TEST(LogosResultTest, GetValueThrowsOnFailure)
{
LogosResult r;
r.success = false;
r.error = QVariant("something went wrong");
EXPECT_THROW(r.getValue<int>(), LogosResultException);
}
TEST(LogosResultTest, GetErrorOnFailure)
{
LogosResult r;
r.success = false;
r.error = QVariant("bad request");
EXPECT_EQ(r.getError<QString>(), "bad request");
}
TEST(LogosResultTest, GetErrorThrowsOnSuccess)
{
LogosResult r;
r.success = true;
r.value = QVariant(1);
EXPECT_THROW(r.getError<QString>(), LogosResultException);
}
TEST(LogosResultTest, GetString)
{
LogosResult r;
r.success = true;
r.value = QVariant("hello");
EXPECT_EQ(r.getString(), "hello");
}
TEST(LogosResultTest, GetBool)
{
LogosResult r;
r.success = true;
r.value = QVariant(true);
EXPECT_TRUE(r.getBool());
}
TEST(LogosResultTest, GetInt)
{
LogosResult r;
r.success = true;
r.value = QVariant(99);
EXPECT_EQ(r.getInt(), 99);
}
TEST(LogosResultTest, GetValueFromMapWithKey)
{
QVariantMap map;
map["name"] = "Alice";
map["age"] = 30;
LogosResult r;
r.success = true;
r.value = QVariant(map);
EXPECT_EQ(r.getString("name"), "Alice");
EXPECT_EQ(r.getInt("age"), 30);
}
TEST(LogosResultTest, GetValueFromMapWithDefault)
{
QVariantMap map;
map["name"] = "Bob";
LogosResult r;
r.success = true;
r.value = QVariant(map);
EXPECT_EQ(r.getString("missing", "default"), "default");
EXPECT_EQ(r.getInt("missing", -1), -1);
}
TEST(LogosResultTest, GetValueFromListByIndex)
{
QVariantMap item0;
item0["id"] = 1;
item0["label"] = "first";
QVariantMap item1;
item1["id"] = 2;
item1["label"] = "second";
QVariantList list;
list << QVariant(item0) << QVariant(item1);
LogosResult r;
r.success = true;
r.value = QVariant(list);
EXPECT_EQ(r.getString(0, "label"), "first");
EXPECT_EQ(r.getInt(1, "id"), 2);
}
TEST(LogosResultTest, GetValueFromListOutOfBoundsReturnsDefault)
{
QVariantList list;
list << QVariant(QVariantMap{{"k", "v"}});
LogosResult r;
r.success = true;
r.value = QVariant(list);
EXPECT_EQ(r.getString(99, "k", "fallback"), "fallback");
EXPECT_EQ(r.getInt(-1, "k", -1), -1);
}
TEST(LogosResultTest, GetList)
{
QVariantList list;
list << 1 << 2 << 3;
LogosResult r;
r.success = true;
r.value = QVariant(list);
EXPECT_EQ(r.getList().size(), 3);
}
TEST(LogosResultTest, GetMap)
{
QVariantMap map;
map["a"] = 1;
LogosResult r;
r.success = true;
r.value = QVariant(map);
EXPECT_TRUE(r.getMap().contains("a"));
}
TEST(LogosResultTest, Serialization)
{
LogosResult original;
original.success = true;
original.value = QVariant("test_value");
original.error = QVariant();
QByteArray buffer;
{
QDataStream out(&buffer, QIODevice::WriteOnly);
out << original;
}
LogosResult restored;
{
QDataStream in(&buffer, QIODevice::ReadOnly);
in >> restored;
}
EXPECT_EQ(restored.success, original.success);
EXPECT_EQ(restored.value.toString(), "test_value");
}
+9
View File
@@ -0,0 +1,9 @@
#include <QCoreApplication>
#include <gtest/gtest.h>
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+111
View File
@@ -0,0 +1,111 @@
#include <gtest/gtest.h>
#include "mock_store.h"
class MockStoreTest : public ::testing::Test {
protected:
void SetUp() override
{
MockStore::instance().reset();
}
};
TEST_F(MockStoreTest, WhenThenReturn)
{
MockStore::instance().when("mod", "method").thenReturn(QVariant(42));
QVariant result = MockStore::instance().recordAndReturn("mod", "method", {});
EXPECT_EQ(result.toInt(), 42);
}
TEST_F(MockStoreTest, WithArgsMatching)
{
MockStore::instance().when("mod", "greet")
.withArgs({QVariant("hello")})
.thenReturn(QVariant("world"));
QVariant hit = MockStore::instance().recordAndReturn("mod", "greet", {QVariant("hello")});
EXPECT_EQ(hit.toString(), "world");
QVariant miss = MockStore::instance().recordAndReturn("mod", "greet", {QVariant("bye")});
EXPECT_FALSE(miss.isValid());
}
TEST_F(MockStoreTest, MatchAnyArgsByDefault)
{
MockStore::instance().when("mod", "fn").thenReturn(QVariant(true));
QVariant r = MockStore::instance().recordAndReturn("mod", "fn", {QVariant(1), QVariant(2)});
EXPECT_TRUE(r.toBool());
}
TEST_F(MockStoreTest, LIFOOrder)
{
MockStore::instance().when("mod", "fn").thenReturn(QVariant("first"));
MockStore::instance().when("mod", "fn").thenReturn(QVariant("second"));
QVariant r = MockStore::instance().recordAndReturn("mod", "fn", {});
EXPECT_EQ(r.toString(), "second");
}
TEST_F(MockStoreTest, NoMatchReturnsInvalid)
{
QVariant r = MockStore::instance().recordAndReturn("mod", "noSuchMethod", {});
EXPECT_FALSE(r.isValid());
}
TEST_F(MockStoreTest, WasCalled)
{
EXPECT_FALSE(MockStore::instance().wasCalled("mod", "fn"));
MockStore::instance().recordAndReturn("mod", "fn", {});
EXPECT_TRUE(MockStore::instance().wasCalled("mod", "fn"));
}
TEST_F(MockStoreTest, WasCalledWith)
{
MockStore::instance().recordAndReturn("mod", "fn", {QVariant(10)});
EXPECT_TRUE(MockStore::instance().wasCalledWith("mod", "fn", {QVariant(10)}));
EXPECT_FALSE(MockStore::instance().wasCalledWith("mod", "fn", {QVariant(20)}));
}
TEST_F(MockStoreTest, CallCount)
{
EXPECT_EQ(MockStore::instance().callCount("mod", "fn"), 0);
MockStore::instance().recordAndReturn("mod", "fn", {});
MockStore::instance().recordAndReturn("mod", "fn", {});
MockStore::instance().recordAndReturn("mod", "fn", {});
EXPECT_EQ(MockStore::instance().callCount("mod", "fn"), 3);
}
TEST_F(MockStoreTest, LastArgs)
{
MockStore::instance().recordAndReturn("mod", "fn", {QVariant("a")});
MockStore::instance().recordAndReturn("mod", "fn", {QVariant("b")});
QVariantList last = MockStore::instance().lastArgs("mod", "fn");
EXPECT_EQ(last.size(), 1);
EXPECT_EQ(last.at(0).toString(), "b");
}
TEST_F(MockStoreTest, AllCalls)
{
MockStore::instance().recordAndReturn("m1", "f1", {});
MockStore::instance().recordAndReturn("m2", "f2", {QVariant(1)});
auto calls = MockStore::instance().allCalls();
EXPECT_EQ(calls.size(), 2);
EXPECT_EQ(calls.at(0).module, "m1");
EXPECT_EQ(calls.at(1).module, "m2");
}
TEST_F(MockStoreTest, ResetClearsEverything)
{
MockStore::instance().when("mod", "fn").thenReturn(QVariant(1));
MockStore::instance().recordAndReturn("mod", "fn", {});
MockStore::instance().reset();
EXPECT_FALSE(MockStore::instance().wasCalled("mod", "fn"));
QVariant r = MockStore::instance().recordAndReturn("mod", "fn", {});
EXPECT_FALSE(r.isValid());
}
+101
View File
@@ -0,0 +1,101 @@
#include <gtest/gtest.h>
#include <QtTest/QSignalSpy>
#include "token_manager.h"
class TokenManagerTest : public ::testing::Test {
protected:
void SetUp() override
{
TokenManager::instance().clearAllTokens();
}
};
TEST_F(TokenManagerTest, SaveAndGetToken)
{
TokenManager::instance().saveToken("mod_a", "token_a");
EXPECT_EQ(TokenManager::instance().getToken("mod_a"), "token_a");
}
TEST_F(TokenManagerTest, GetMissingTokenReturnsEmpty)
{
EXPECT_TRUE(TokenManager::instance().getToken("nonexistent").isEmpty());
}
TEST_F(TokenManagerTest, HasToken)
{
EXPECT_FALSE(TokenManager::instance().hasToken("key"));
TokenManager::instance().saveToken("key", "val");
EXPECT_TRUE(TokenManager::instance().hasToken("key"));
}
TEST_F(TokenManagerTest, RemoveToken)
{
TokenManager::instance().saveToken("key", "val");
EXPECT_TRUE(TokenManager::instance().removeToken("key"));
EXPECT_FALSE(TokenManager::instance().hasToken("key"));
}
TEST_F(TokenManagerTest, RemoveNonexistentTokenReturnsFalse)
{
EXPECT_FALSE(TokenManager::instance().removeToken("missing"));
}
TEST_F(TokenManagerTest, ClearAllTokens)
{
TokenManager::instance().saveToken("a", "1");
TokenManager::instance().saveToken("b", "2");
TokenManager::instance().clearAllTokens();
EXPECT_EQ(TokenManager::instance().tokenCount(), 0);
}
TEST_F(TokenManagerTest, TokenCount)
{
EXPECT_EQ(TokenManager::instance().tokenCount(), 0);
TokenManager::instance().saveToken("x", "1");
EXPECT_EQ(TokenManager::instance().tokenCount(), 1);
TokenManager::instance().saveToken("y", "2");
EXPECT_EQ(TokenManager::instance().tokenCount(), 2);
}
TEST_F(TokenManagerTest, GetTokenKeys)
{
TokenManager::instance().saveToken("alpha", "1");
TokenManager::instance().saveToken("beta", "2");
QList<QString> keys = TokenManager::instance().getTokenKeys();
EXPECT_EQ(keys.size(), 2);
EXPECT_TRUE(keys.contains("alpha"));
EXPECT_TRUE(keys.contains("beta"));
}
TEST_F(TokenManagerTest, OverwriteToken)
{
TokenManager::instance().saveToken("key", "old");
TokenManager::instance().saveToken("key", "new");
EXPECT_EQ(TokenManager::instance().getToken("key"), "new");
EXPECT_EQ(TokenManager::instance().tokenCount(), 1);
}
TEST_F(TokenManagerTest, SignalTokenSaved)
{
QSignalSpy spy(&TokenManager::instance(), &TokenManager::tokenSaved);
TokenManager::instance().saveToken("k", "v");
EXPECT_EQ(spy.count(), 1);
EXPECT_EQ(spy.at(0).at(0).toString(), "k");
}
TEST_F(TokenManagerTest, SignalTokenRemoved)
{
TokenManager::instance().saveToken("k", "v");
QSignalSpy spy(&TokenManager::instance(), &TokenManager::tokenRemoved);
TokenManager::instance().removeToken("k");
EXPECT_EQ(spy.count(), 1);
EXPECT_EQ(spy.at(0).at(0).toString(), "k");
}
TEST_F(TokenManagerTest, SignalAllTokensCleared)
{
TokenManager::instance().saveToken("k", "v");
QSignalSpy spy(&TokenManager::instance(), &TokenManager::allTokensCleared);
TokenManager::instance().clearAllTokens();
EXPECT_EQ(spy.count(), 1);
}