mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 09:41:06 +00:00
* fix: read dependency entries declared in object form The manifest schema lets a dependency entry be an object carrying the name alongside the constraints an installer resolves it by, but every reader took the element as a plain string and skipped what came back empty, so an object entry disappeared: the module it names was left out of the generated LogosModules aggregate, and every call through it failed to compile. The rule lives in one place now, since the copies of it are how the gap spread. It ships in share/lidl-frontend alongside the parser that includes it, which consumers compile from there. * fix: read every dependency entry through one pass over the array The object form reached the umbrella's members and constructor but not its includes: that emitter still read each element as a plain string, so a module declared in object form came out as a member whose type was never included, and the aggregate no longer compiled. It is the Qt-free umbrella, which is what every universal core module and every cdylib module generates, so the form the previous commit set out to support failed there in a new way rather than working. Reading the array element by element is what let one pass disagree with the next, so no reader does that any more: dependencyNames() answers what an array declares, once, and the emitters walk names. That leaves the entry form knowable in exactly one place, and the includes and members of an aggregate can no longer be built from different answers. The umbrella emission moves to generator_lib alongside the per-module wrapper emitters it mirrors, returning the text instead of writing it, so what it generates can be asserted on directly; main.cpp writes what it returns. Output for string-form dependencies is byte-identical in both API styles, with and without interface dependencies. The listing mode (`--metadata` with no `--module-dir`) went the same way — it was the last reader still deciding on its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Dario Gabriel Lipicar <dario@status.im> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
130 lines
5.4 KiB
C++
130 lines
5.4 KiB
C++
#include <gtest/gtest.h>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QStringList>
|
|
#include "generator_lib.h"
|
|
|
|
// The umbrella aggregates a module's declared `metadata.json#dependencies` into
|
|
// `LogosModules`. A dependency entry is either a bare name or an object holding
|
|
// that name alongside the constraints an installer resolves it by, and the two
|
|
// forms have to generate identical code — the constraints are the installer's
|
|
// business, not the generator's.
|
|
//
|
|
// What makes this worth asserting on rather than trusting: the aggregate is
|
|
// emitted by several passes over the same array (includes, constructor
|
|
// initialisers, members), so a form only one pass understands yields a member
|
|
// whose type was never included — an aggregate that no longer compiles, and one
|
|
// that nothing catches until a module builds against it.
|
|
|
|
namespace {
|
|
|
|
QJsonArray depsMixedForms()
|
|
{
|
|
QJsonObject withVersion;
|
|
withVersion["name"] = "dep_b";
|
|
withVersion["version"] = "=1.2.3";
|
|
|
|
QJsonObject withSigner;
|
|
withSigner["name"] = "dep_c";
|
|
withSigner["version"] = "^2.0";
|
|
withSigner["signer"] = "did:jwk:abc";
|
|
|
|
QJsonArray deps;
|
|
deps.append("dep_a");
|
|
deps.append(withVersion);
|
|
deps.append(withSigner);
|
|
return deps;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Lp is the umbrella every `interface: universal` core module and every cdylib
|
|
// module generates (logos-plugin-qt picks --api-style lp for both).
|
|
TEST(MakeUmbrellaTest, LpAggregatesDependenciesDeclaredInEitherForm)
|
|
{
|
|
const QString h = makeUmbrellaHeaderFromDeps(depsMixedForms(), {}, ApiStyle::Lp, "sample_module");
|
|
|
|
EXPECT_TRUE(h.contains("#include \"dep_a_api.h\"")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("#include \"dep_b_api.h\"")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("#include \"dep_c_api.h\"")) << h.toStdString();
|
|
|
|
EXPECT_TRUE(h.contains("DepA dep_a;")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("DepB dep_b;")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("DepC dep_c;")) << h.toStdString();
|
|
|
|
// Lp wrappers self-create their lp_client on behalf of this module.
|
|
EXPECT_TRUE(h.contains("dep_b(\"sample_module\")")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("dep_c(\"sample_module\")")) << h.toStdString();
|
|
}
|
|
|
|
TEST(MakeUmbrellaTest, QtAggregatesDependenciesDeclaredInEitherForm)
|
|
{
|
|
const QString h = makeUmbrellaHeaderFromDeps(depsMixedForms(), {}, ApiStyle::Qt);
|
|
|
|
EXPECT_TRUE(h.contains("#include \"dep_a_api.h\"")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("#include \"dep_b_api.h\"")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("#include \"dep_c_api.h\"")) << h.toStdString();
|
|
|
|
EXPECT_TRUE(h.contains("DepA dep_a;")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("DepB dep_b;")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("DepC dep_c;")) << h.toStdString();
|
|
|
|
EXPECT_TRUE(h.contains("dep_b(api)")) << h.toStdString();
|
|
EXPECT_TRUE(h.contains("dep_c(api)")) << h.toStdString();
|
|
}
|
|
|
|
// Every dep a member declaration mentions must have been included, in both
|
|
// flavors — the pairing is the invariant, independent of which form declared it.
|
|
TEST(MakeUmbrellaTest, EveryMemberTypeIsIncluded)
|
|
{
|
|
for (ApiStyle style : {ApiStyle::Lp, ApiStyle::Qt}) {
|
|
const QString h = makeUmbrellaHeaderFromDeps(depsMixedForms(), {}, style, "sample_module");
|
|
for (const QString& dep : {QStringLiteral("dep_a"), QStringLiteral("dep_b"), QStringLiteral("dep_c")}) {
|
|
const bool included = h.contains("#include \"" + dep + "_api.h\"");
|
|
const bool member = h.contains(toPascalCase(dep) + " " + dep + ";");
|
|
EXPECT_EQ(included, member)
|
|
<< "'" << dep.toStdString() << "' is a member without an include (or vice versa):\n"
|
|
<< h.toStdString();
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(MakeUmbrellaTest, SourceIncludesEveryDependencyWrapper)
|
|
{
|
|
const QString c = makeUmbrellaSourceFromDeps(depsMixedForms(), {"some_iface"});
|
|
|
|
EXPECT_TRUE(c.contains("#include \"dep_a_api.cpp\"")) << c.toStdString();
|
|
EXPECT_TRUE(c.contains("#include \"dep_b_api.cpp\"")) << c.toStdString();
|
|
EXPECT_TRUE(c.contains("#include \"dep_c_api.cpp\"")) << c.toStdString();
|
|
EXPECT_TRUE(c.contains("#include \"some_iface_api.cpp\"")) << c.toStdString();
|
|
}
|
|
|
|
// An entry that names nothing is dropped rather than emitted as an empty
|
|
// member, and drops out of the aggregate entirely.
|
|
TEST(MakeUmbrellaTest, EntryNamingNothingIsDropped)
|
|
{
|
|
QJsonArray deps;
|
|
deps.append("dep_a");
|
|
deps.append(QJsonObject{});
|
|
deps.append(QJsonObject{{"version", "=1.0.0"}});
|
|
|
|
for (ApiStyle style : {ApiStyle::Lp, ApiStyle::Qt}) {
|
|
const QString h = makeUmbrellaHeaderFromDeps(deps, {}, style, "sample_module");
|
|
EXPECT_TRUE(h.contains("DepA dep_a;")) << h.toStdString();
|
|
EXPECT_FALSE(h.contains("#include \"_api.h\"")) << h.toStdString();
|
|
EXPECT_FALSE(h.contains("Module ;")) << h.toStdString();
|
|
}
|
|
}
|
|
|
|
// A module with no dependencies still gets a compilable, empty aggregate.
|
|
TEST(MakeUmbrellaTest, NoDependenciesStillEmitsTheAggregate)
|
|
{
|
|
const QString lp = makeUmbrellaHeaderFromDeps({}, {}, ApiStyle::Lp, "sample_module");
|
|
EXPECT_TRUE(lp.contains("struct LogosModules {")) << lp.toStdString();
|
|
EXPECT_TRUE(lp.contains("LogosModules() {}")) << lp.toStdString();
|
|
|
|
const QString qt = makeUmbrellaHeaderFromDeps({}, {}, ApiStyle::Qt);
|
|
EXPECT_TRUE(qt.contains("struct LogosModules {")) << qt.toStdString();
|
|
EXPECT_TRUE(qt.contains("LogosAPI* api;")) << qt.toStdString();
|
|
}
|