mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
add tests for cpp-generator and sdk
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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()");
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user