mirror of
https://github.com/logos-co/logos-test-modules.git
synced 2026-08-27 18:21:07 +00:00
master
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
626fbaa032 |
Migrate the last Qt fixtures to interface "universal", retire test_ipc_module, and repoint onto logos-qt-host (#44)
* feat: migrate the two provider-header fixtures to interface "universal"
dummy_module_000000 and test_ipc_new_api_module were the last users of
`logos-cpp-generator --provider-header` (LOGOS_PROVIDER / LOGOS_METHOD).
They were invisible to the "does anything still declare interface: provider?"
check that motivated removing that mode: neither sets the field. Both invoked
the generator BY HAND from a preConfigure hook in this flake, so the only way
to find them was to remove the flag and watch what broke.
Both now derive their contract from a plain impl header:
* the hand-written *_loader.h plugin classes are gone (generated)
* PROVIDER_HEADER / logos_provider_dispatch.cpp drop out of CMakeLists
* test_ipc_new_api_module's 16 methods go from raw
LogosAPIClient::invokeRemoteMethod("mod", "method", ...) — a typo in
either string was a runtime null — to typed modules().<dep>.<method>()
* its `triggeredBasicEvent` becomes a DECLARED logos_events: event, so
consumers get a typed onTriggeredBasicEvent() accessor; it used to be
emitted dynamically by name with no accessor at all
Method surface preserved, checked with lm before/after:
* dummy: identical (noop(), 1 method)
* ipc-new-api: same 16 names and arity. The only difference is int ->
qlonglong on the three integer params, which is the LIDL 64-bit type
contract every universal module already follows (the generated
onMultiArgEvent in test_basic_module_cpp spells it the same way).
checks: ipc-new-api-tests PASS (the live end-to-end one), ipc-tests PASS,
unit-tests PASS.
KNOWN RED: unit-tests-new-api. Its 428-line suite drives the impl through the
legacy mock harness — impl.init(&api) plus a compiled logos_provider_dispatch.cpp
and qt_provider_object.cpp — none of which a universal module has. Porting it
means injecting LogosModules via _logosCoreSetLogosModulesPtr_ and switching
that derivation to --api-style lp. Left as its own change rather than deleted,
because dropping coverage is not a side effect to slip into this commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: port unit-tests-new-api to the universal/lp path
The suite drove the impl through the legacy provider harness — impl.init(&api)
plus a compiled logos_provider_dispatch.cpp — neither of which exists once the
module is `interface: "universal"`. Ported rather than deleted: 26 original
tests kept 1:1, 32 passing in total.
* new_api_fixture.h does what the generated plugin glue does in production:
injects the LogosModules umbrella via _logosCoreSetLogosModulesPtr_ and
installs an emit callback. modules() blindly dereferences that pointer, so
the injection is mandatory, not convenience.
* the derivation emits lp-typed wrappers (--api-style lp; it defaulted to qt,
which cannot bind to std-typed call sites) and consumes the deps'
`headers-lp` variant rather than `include` (= headers-qt).
* it also emits the generated `logos_events:` bodies. That is a LINK
requirement: the impl calls triggeredBasicEvent(), whose body is generated.
Only the events file is compiled in, not the 19K C-ABI export wrapper.
Verified the mock still intercepts, which was the whole risk — a non-
intercepting call returns a default-constructed value, so tests can pass
vacuously. It does: lp_invoke delegates to the SAME
LogosAPIClient::invokeRemoteMethod as the legacy path (logos_protocol.cpp:311),
and logos-protocol's own tests/protocol/test_lp_client.cpp already mocks
lp_invoke this way.
Two argument-representation notes, measured rather than assumed:
* args reach MockStore JSON-round-tripped, so a non-negative int is
QVariant(qulonglong), never QVariant(int). wasCalledWith still matches
because Qt6 QVariant equality falls back to numericCompare for numeric
metatypes — but numeric args are asserted via lastArgs() + a typed accessor
anyway, which is the idiom logos-protocol's own lp tests use.
* what WOULD break wasCalledWith is non-numeric drift (QStringList ->
QVariantList, LogosResult -> QVariantMap). No param here has those types.
Six tests added where the port exposed gaps:
* StdLogosResult success/error/map-field — the old suite asserted nothing
about the result-carrying methods, and the Qt LogosResult -> StdLogosResult
change (QVariant value -> nlohmann::json) is exactly what a type swap gets
silently wrong. A LogosResult crosses the wire as its {success,value,error}
OBJECT; my first attempt mocked a bare payload, which decodes to
success == false — and made the "missing key returns empty" test pass for
entirely the wrong reason. Both the positive case and a failed-call case
now pin that distinction.
* triggerBasicEvent asserts the typed emission and its payload; the event was
previously emitted dynamically by name and untested.
Untouched and still red for reasons that predate this branch, confirmed by
building each at the baseline pins: checks `tests`, `thread-safety-tests` and
`qml-modules` (the last dies on `no member named 'add' in 'TestBasicModule'`).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: restore the LEGACY unit-tests block to qt-typed dep headers
My D-a2 change switched basicInclude/extlibInclude to `headers-lp`, but the
replacement had no count and hit BOTH derivations — including the legacy
test_ipc_module one, which still generates its umbrella with the default
`--api-style qt`. The mismatch surfaced as `no matching constructor for
initialization of 'TestBasicModule'` in the generated logos_sdk.h.
Caught by re-running the check and attributing it with a control run rather
than assuming the newer capability_module override was to blame — it was not.
unit-tests PASSes again. Comment added so the two blocks are not "unified"
later without also moving the generator call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(extlib): migrate test_extlib_module to interface "universal"
The last external-library fixture still carrying a hand-written Qt plugin.
The impl is now a plain Qt-free class deriving LogosModuleContext; the
generator derives the LIDL from it and emits the glue, the dispatch and the
C-ABI exports. `test_extlib_module_interface.h` and `_plugin.{h,cpp}` are gone.
The external C library wiring — CMake builds lib/libstrutil.c into a static
`strutil` target and LINK_TARGETS it — is deliberately UNCHANGED. That is what
this fixture exists to exercise, and it is the part a universal migration
could plausibly have broken.
## Method surface, measured before and after with `lm methods --json`
Six methods, all preserved by name and arity. The only signature change is
`int` -> `qlonglong` on countChars/countChar, which is expected and correct:
LIDL numbers are 64-bit.
The two entries that disappear are Qt plumbing, not API — the `eventResponse`
signal and `initLogos(LogosAPI*)`. Neither is part of the module's contract,
and the already-migrated universal modules in this tree (dummy_module_000000,
test_ipc_new_api_module, test_basic_module_cpp) show the same shape.
## Verified
All six checks that reach this module, built by name through the workspace
flake with the local cpp-sdk / module-builder / plugin-qt / capability-module
overrides. Every one PASSes:
ipc-tests 23 passed (legacy test_ipc_module -> extlib, both raw
invokeRemoteMethod and the generated qt
wrapper)
ipc-new-api-tests 23 passed (universal consumer -> extlib over lp)
async-tests 8 passed (incl. asyncCallExtlibReverse)
unit-tests 26 assertions (LEGACY qt-typed consumer compiled against
the now-universal dep's `headers-qt` — the
binding most at risk here, since a universal
module's qt headers are generated from the
derived LIDL rather than copied)
unit-tests-new-api 32 assertions (lp-typed consumer, `headers-lp`)
fullapi-tests 7 passed (control; does not touch this module)
The module's OWN 12 integration assertions live in the `extlib` group of the
`tests` check, which is red at baseline for unrelated reasons. Rather than
skip that coverage, the group was run directly against the check's own
modulesDir and logoscore:
TEST_GROUPS=extlib -> 12 passed, 0 failed, 1 skipped
That covers the strutil round-trips by exact value (reverseString(hello) ->
olleh, uppercaseString(FooBar) -> FOOBAR, countChar(aabaa, a) -> 4,
libVersion() -> 1.0.0), so the C library really is still linked and called.
## NOT verified
* `tests`, `thread-safety-tests` and `qml-modules` were not run. All three are
red at baseline for reasons predating this branch (`qml-modules` dies on
`no member named 'add' in 'TestBasicModule'` — test-qml-backend-module calls
`.add()` on a module that exposes `addInts`). None of them is made better or
worse by this commit; the extlib group inside `tests` was run manually
instead, as described above.
* Linux. Everything here was built and run on aarch64-darwin only.
* countChar's degenerate empty-`ch` path is preserved by construction
(empty -> NUL, which never matches) but is not covered by a test either
before or after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(basic): migrate test_basic_module to interface "universal"
The provider fixture the whole suite is built around: one method per return
type, per parameter type and per argument count (0-5). Its plugin was the last
hand-written Qt loader among the non-QML fixtures. The impl is now a plain
Qt-free class deriving LogosModuleContext; `test_basic_module_interface.h` and
`_plugin.{h,cpp}` are gone, and the generator derives the LIDL from the impl
header and emits the glue, the dispatch, the typed event bodies and the C-ABI
exports.
## Method surface, measured before and after with `lm methods --json`
41 entries before, 39 after. Accounted for exactly:
23 byte-identical
13 `int` -> `qlonglong` only (expected; LIDL numbers are 64-bit)
2 Qt plumbing REMOVED, not API: the `eventResponse` signal and
`initLogos(LogosAPI*)`. Every already-migrated universal module in this
tree has the same shape.
3 REAL signature changes, unavoidable — see below.
The four `returnVariant*` methods KEEP their exact `QVariant` return type.
That is why they are declared `nlohmann::json` and not `LogosMap`/`LogosList`:
the parser maps the bare spelling to LIDL `any` (= QVariant), while the aliases
would have narrowed them to QVariantMap/QVariantList. Likewise
`std::vector<uint8_t>` keeps byteArraySize's `QByteArray` parameter and
`std::vector<std::string>` keeps the three `QStringList` slots.
### The three that could not be preserved
returnJsonArray() QJsonArray -> QVariantList
makeJsonArray(QString,QString) QJsonArray -> QVariantList
urlToString(QUrl) QUrl param -> QString param
`QJsonArray` and `QUrl` have no universal spelling: the C++-to-LIDL map has
`[any]` (-> QVariantList) and `tstr` (-> QString) as the nearest types, and
there is no way to ask for the Qt-specific ones from a Qt-free header. The
VALUES on the wire are unchanged — a JSON array is still a JSON array, and the
URL still crosses as its string form — but the declared types move, and
`urlToString` no longer runs QUrl's normalisation (it never had a caller that
exercised it; see below).
This is a deliberate narrowing of Qt-type coverage and should be reviewed as
such rather than waved through. Note the affected assertions:
returnJsonArray / makeJsonArray only assert "Method call successful", and
urlToString has ALWAYS been skipped ("logoscore cannot pass QUrl params"), so
no test observed the QUrl parameter before this commit either.
## Verified
Six checks, built by name through the workspace flake with the local
cpp-sdk / module-builder / plugin-qt / capability-module overrides. All PASS,
with totals identical to the pre-migration run:
ipc-tests 23 passed (legacy Qt consumer -> this module, raw
invokeRemoteMethod AND generated wrapper)
ipc-new-api-tests 23 passed (universal consumer over lp)
async-tests 8 passed (incl. the generated echoAsync wrapper,
which is generated from this module's
contract)
unit-tests 26 assertions (legacy qt-typed wrappers built from this
module's `headers-qt`, which is now
generated from the derived LIDL instead of
copied from a moc'd plugin — the binding
most at risk)
unit-tests-new-api 32 assertions (lp-typed, `headers-lp`)
fullapi-tests 7 passed (control; does not touch this module)
The module's own assertions live in the `tests` check, which is red at
baseline. Rather than skip them, run_tests.sh was driven directly against the
check's own logoscore and modulesDir, for BOTH the pre-migration and the
post-migration module set:
TEST_GROUPS=basic 41 passed, 1 failed, 10 skipped — both runs
whole suite 174 passed, 2 failed, 25 skipped — both runs
and `diff` of the per-assertion PASS/FAIL/SKIP lines across all 176 is EMPTY.
The one textual difference anywhere in the two logs is the random temp-dir
path quoted inside a failure message that fails in both.
The two failures are pre-existing, reproduced with the identical command
against the pre-migration modules:
* `addInts(3.7, 1.2) [double->int rounding]` — the dispatch rejects a
fractional JSON number for an int param ("expected integer at arg0, got
number"). Byte-identical message before and after; the legacy Qt module
did NOT round it either.
* `getInstancePersistencePath() rooted at temp dir` in
test_context_module_cpp — an expectation that does not match the path
layout the host provisions.
`qml-modules` was built BOTH ways and fails identically — same file, same
line 24, same `no member named 'add' in 'TestBasicModule'`
(test-qml-backend-module calls `.add()`; the contract has `addInts`). Not
caused by this commit and not fixed by it.
## NOT verified
* `tests` and `thread-safety-tests` were never built as derivations. `tests`
is covered above by running its script directly, both ways, which is
strictly more informative; `thread-safety-tests` uses only the dummy module
and cannot see this change.
* Linux. aarch64-darwin only.
* The event PAYLOAD of testEvent / multiArgEvent. The names and arity are
preserved and the emit drivers still return void with the same signatures,
but the `tests` event-system group only exercises the *_cpp twin's typed
events; nothing in the suite subscribes to THIS module's events, before or
after.
* `crashOnDemand` is not exercised here — its consumer is the crash-isolation
test in logos-logoscore-cli, which was not run.
* The three changed signatures are only argued to be value-preserving on the
wire; no test compares the actual serialised bytes of returnJsonArray
before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(basic,extlib): answer in CHARACTERS, and normalise the URL
The two fixtures migrated on this branch were counting, reversing and
reporting BYTES. Every assertion that touched those methods was ASCII, where
bytes and characters agree, so nothing in the suite could see it — that
blind spot is the actual defect, and it is closed here first.
Measured on a live logoscore daemon, both module sets built from this tree
(pre = HEAD, post = this commit; `logoscore call`, aarch64-darwin):
call pre post
stringLength("hello") 5 5
stringLength("héllo") 6 5
stringLength("😀") 4 1
validateInput("héllo").value.length 6 5
urlToString("HTTP://Example.COM/a/../b?x=1") verbatim http://example.com/a/../b?x=1
reverseString("hello") olleh olleh
reverseString("héllo") dispatch_failed: olléh
invalid UTF-8 byte 0xA9
reverseString("a😀b") dispatch_failed: b😀a
invalid UTF-8 byte 0x80
countChar("hello","l") 2 2
countChar("héllo","é") 1 1
countChars("héllo") 6 5
countChar("banana","na") 2 2
countChar("aaa","aa") 3 1
byteArraySize("héllo") 6 6
One character = one Unicode code point, everywhere. Neither predecessor
answered that: the Qt plugins answered QString::length() (UTF-16 units — 2
for the emoji), the universal port answered std::string::size() (UTF-8 bytes
— 4 for it). byteArraySize is the one method that still answers in bytes,
because a bstr IS bytes; it now has an assertion saying so, next to
stringLength's, so the two units are contrasted rather than confused.
## Per method
* stringLength / validateInput.length — count the bytes that are not UTF-8
continuation bytes. One helper, one definition, stated in the header.
* countChar — the needle is a STRING and all of it must match:
countChar("banana","na") == 2. Matches are counted left to right and do not
overlap (countChar("aaa","aa") == 1 — it used to say 3, counting the byte
'a'), and are only accepted where a character starts, so a match can never
be a fragment of a character. An empty needle matches nothing. All of that
is documented in the header and asserted in the suite. "é" answered 1
before too, but only because the FIRST BYTE of "é" (0xC3) happens to occur
exactly once in "héllo"; the Qt module before it took ch.at(0).toLatin1()
(0xE9), a byte that occurs in no UTF-8 string, and answered 0.
* countChars — same character definition as stringLength.
* reverseString — reverses by character and the result is well-formed UTF-8.
Byte reversal was always broken: it splits "é" (C3 A9) into A9 C3.
* urlToString — lowercases the parts that are defined to be case-insensitive
(scheme, host) and leaves the case-sensitive ones exactly as given, so
"/a/../b?x=1" comes back untouched. Userinfo is preserved
("HTTPS://User:Pw@EXAMPLE.com:8080/Path?Q=A#Frag" ->
"https://User:Pw@example.com:8080/Path?Q=A#Frag"), a string with no scheme
is returned unchanged, and an already-lowercase URL is unchanged — which is
what logos-logoscore-cli's test_integration.cpp asserts for it.
## What still goes through libstrutil
This fixture exists to exercise the external C library, so the calls were
kept wherever the C library can carry the work without producing a wrong or
ill-formed answer. Verified in the shipped dylib, not just in the source —
`nm` shows all six strutil symbols defined in the image and `otool -tvV`
shows the call sites:
reverseString strutil_reverse still does the reversal (`bl
_strutil_reverse` inside the impl); the module then
re-reverses each multi-byte run, which byte reversal
necessarily left backwards.
uppercaseString entirely strutil (ASCII-only mapping; bytes >= 0x80 pass
lowercaseString through untouched, so "héllo" -> "HéLLO" — documented,
not fixed: real Unicode casing needs a case table this
fixture has no reason to carry).
countChars strutil_count_chars still supplies the byte length (`bl
_strutil_count_chars`); the module discounts the
continuation bytes.
countChar strutil_count_char still counts a single ASCII needle
(`bl _strutil_count_char`) — an ASCII byte cannot occur
inside a multi-byte sequence, so its byte comparison IS a
character comparison there.
libVersion entirely strutil.
NOT through the C library any more: the multi-byte / multi-character needle
path of countChar, and the repair half of reverseString. strutil compares
and moves single bytes; neither answer can be expressed that way.
## Assertions
13 new rows in the `basic` and `extlib` groups, one per behaviour above plus
the byte/character contrast. Expectations changed (old -> new):
validateInput(hi) "Method call successful" -> '"length":2'
The old expectation asserted only that the call returned. It could not
tell 5 from 6, which is how the drift shipped. Strengthened, not moved.
No other expected value was changed, and no assertion was weakened: the
per-assertion diff of a full-suite run before and after contains only
additions, the three un-skips below, that one rename, and the random temp-dir
path quoted inside a failure that fails identically both ways.
Three skips were false and are now real assertions:
urlToString "logoscore cannot pass QUrl params" — it can, and since the
migration the parameter is a plain string anyway.
joinStrings "logoscore cannot pass QStringList params" — it can, as
`json:[…]`.
byteArraySize "logoscore cannot pass QByteArray params" — it can, as bytes.
## Verified
All 9 checks built BY NAME through the workspace flake with the local
cpp-sdk / module-builder / plugin-qt / capability-module overrides, at HEAD
(pristine copy) and at this tree:
check pre post
async-tests PASS PASS
fullapi-tests PASS PASS
ipc-new-api-tests PASS PASS
ipc-tests PASS PASS
unit-tests PASS PASS
unit-tests-new-api PASS PASS
tests FAIL FAIL 174/2/25 -> 187/2/22 (see below)
qml-modules FAIL FAIL identical error, unrelated
thread-safety-tests FAIL FAIL byte-identical derivation
`tests` is red on both sides on the same two assertions, neither touched
here and both reproduced against the pre-change modules:
* addInts(3.7, 1.2) — the dispatch rejects a fractional JSON number for an
int parameter ("expected integer at arg0, got number"). The expectation
encodes Qt's old lossy coercion; fixing either side belongs to whoever
decides that policy, and is not this commit's to decide.
* getInstancePersistencePath() in test_context_module_cpp — the expectation
does not match the path layout the host provisions.
`qml-modules` fails on test-qml-backend-module calling `.add()` on a module
whose contract has `addInts` — same file, same line, same message on both
sides. `thread-safety-tests` is the SAME derivation hash on both sides (it
only uses the dummy module) and is killed identically.
## NOT verified
* Linux. aarch64-darwin only.
* uppercaseString / lowercaseString on non-ASCII beyond "bytes pass through":
that pass-through holds in the C and UTF-8 locales, and a single-byte
toupper() in a Latin-1 locale could still corrupt a multi-byte character.
Not asserted, because the assertion would be locale-dependent.
* Grapheme clusters. A combining sequence is N code points here, by the
stated definition, and reversing it separates the marks from their base.
* No test compares serialised bytes before/after for the methods this commit
does not change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(extlib): make case mapping locale-independent, and correct a false claim
The header, README and commit message all claimed uppercaseString/lowercaseString
pass bytes >= 0x80 through untouched. An adversarial review measured that this
was FALSE in the locale the module actually runs in:
lowercaseString("HÉLLO") -> dispatch_failed: invalid UTF-8 byte at index 3
480 of 503 non-ASCII code points returned invalid UTF-8 from lowercaseString,
23 of 503 from uppercaseString (every 3- and 4-byte character), and 7 more
came back as a silently DIFFERENT character (µ->U+009C, ε->Μ, Cyrillic е->М).
Cause: strutil used toupper()/tolower(), which are LOCALE-DEPENDENT. In the C
locale the pass-through claim holds; in en_US.UTF-8 they map ~30 of the 128 high
bytes, including the UTF-8 lead bytes 0xC3/0xE4/0xF0 — so they destroy 2-, 3-
and 4-byte characters. The shipping locale is the corrupting one. "héllo" ->
"HéLLO" was a cherry-picked survivor: 0xC3/0xA9 have no UPPERcase mapping, which
is why the mirror-image lowercase call was the one that failed.
That is the same dispatch_failed/invalid-UTF-8 failure the previous commit was
written to eliminate, still live in two of six methods here and documented as
safe.
strutil now compares 'a'..'z' / 'A'..'Z' explicitly, so the pass-through is real
in every locale. The case mapping stays ASCII-only and stays inside the C
library, which is what this fixture exists to exercise. Three assertions added
for the non-ASCII round-trips — the previous reason for not asserting them
(locale-dependence) was backwards: the CALL FAILING is perfectly stable and
assertable.
VERIFIED: ipc-tests PASS with this in place.
NOT VERIFIED BY ME: a live-daemon measurement of the two case methods. My ad-hoc
daemon failed even on pure-ASCII uppercaseString("hello"), so the harness was
wrong and I did not trust any reading from it. The three new assertions live in
run_tests.sh, whose `tests` check is red for two unrelated pre-existing reasons,
so they have not been observed passing either. Someone should run them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(basic-cpp): answer stringLength in characters, mirroring test_basic_module
test_basic_module_cpp is the pure-C++ mirror of test_basic_module, but after the
universal migration the two disagreed on non-ASCII: the Qt-derived one counted
CHARACTERS while this one still counted BYTES (s.size()), so
stringLength("héllo") was 5 there and 6 here, and validateInput's length field
likewise. Same for the code-point definition of "character": one emoji is 1.
The blind spot is the point: this module's group only ever asserted ASCII, where
bytes and characters coincide, so two modules that exist to mirror each other
could drift apart invisibly. Two non-ASCII assertions added — the same pair the
sibling carries, so a future divergence fails on both sides.
The helper is copied rather than shared: these fixtures are deliberately
standalone (this one's whole point is that it compiles with no Qt anywhere), and
a shared header between them would weaken that. The comment on each says so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: link the unit tests against logos-qt-host, not logos-qt-sdk
The Qt host runtime the two IPC unit-test binaries link — LogosAPI,
LogosAPIProvider, the provider base classes and the legacy QMetaObject
adapter — now lives in logos-plugin-qt and is exported as
logos-qt-host::logos_qt_host. Point both tests/CMakeLists.txt at that
target and prefix, and give the flake its own logos-plugin-qt input.
logos-qt-sdk is dropped entirely from this repo: the only thing it ever
supplied here was the host runtime. Nothing consumes the qt consumer
emitter or the LpBridge headers from it — the pinned qt-sdk's
include/cpp does not even carry logos_qt_lp_bridge.h.
Two details worth keeping:
* logos-plugin-qt's logos-protocol follows logos-liblogos', so the
logos_qt_host archive and the protocol the test binary links are ONE
build. logos_qt_host is static and its exported target carries
logos-protocol::logos_protocol, so two protocol store paths would put
two protocol archives on a single link line.
* The source layout gains logos_qt_arg_decode.cpp, listed
unconditionally rather than behind an EXISTS guard:
qt_provider_object.cpp routes every incoming argument through
logos::qtArgDecode, and a guard would turn a missing checkout file
into a late undefined symbol instead of a configure error.
find_package(logos-qt-host) stays REQUIRED and the target reference stays
unguarded, so a rename can only produce a hard configure failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): rev-pin the B4 stack, and keep the qt seam headers reachable
Every input this repo reaches through now names an explicit rev, because none
of the four branches involved has landed on its master and a plain
`github:logos-co/<repo>` url lets `nix flake update` silently walk each one
back to a master that cannot satisfy this flake:
logos-module-builder c60d4a9 feat/sdk-codegen-b4-qt-host-repoint
logos-liblogos f2a15ef fix/b4-align-protocol-with-qt-host
logos-logoscore-cli 24ad063 feat/sdk-codegen-b4-qt-host
logos-plugin-qt cc24fa1 feat/b4-qt-host-windows-target
cc24fa1 is the SUPERSET sibling of feat/b4-qt-host-windows-target-8ccb1fc and
the exact rev logos-module-builder pins for BOTH logos-plugin-qt and
logos-plugin-core. With this lock, `logos-qt-host` evaluates to ONE derivation
(1xbf6fy9…-logos-qt-host-0.1.0.drv) whether you reach it through this flake's
logos-plugin-qt or through logos-module-builder's — the unit-test binaries and
the module plugins link the same runtime, not two copies of it.
Consequences of landing this repoint on top of master:
* master moved the SDK inputs (logos-cpp-sdk, logos-protocol) from
logos-liblogos.inputs to logos-module-builder.inputs so the generator and
the headers it emits move together. logos-plugin-qt's logos-protocol
follows the BUILDER's now rather than liblogos's — same invariant, new
address. logos_qt_host is a static archive whose exported target carries
logos-protocol::logos_protocol, so two protocol store paths would put two
protocol archives on one link line. Verified: both resolve to
yq3q02yp…-logos-protocol.drv.
* logos-qt-sdk no longer supplies the host runtime, but it does NOT go away.
It owns the Qt<->lp seam headers (logos_qt_lp_bridge.h, logos_qt_wire.h),
and the builder at c60d4a9 emits the Qt-typed dependency wrapper as a
VENEER over the lp path: the `headers-qt` output the LEGACY unit tests
compile now opens with `#include "logos_qt_lp_bridge.h"`. Dropping the
input outright — which is what "nothing here needs logos-qt-sdk" implied
when this branch was written against liblogos's older pin — made
`.#checks.<sys>.unit-tests` fail with a `file not found` inside generated
code. So logos-qt-sdk is back, headers-only: no archive is linked, no
source is compiled from it, and only the `unit-tests` derivation gets it.
`unit-tests-new-api` consumes `headers-lp`, whose wrapper has no such
include, and is deliberately left alone.
logos-nix moves e637a1f -> 6e0f4a7 as well, and it is not cosmetic: cc24fa1's
flake calls `logos-nix.lib.forAllTargets`, which e637a1f does not export, so
with `logos-plugin-qt.inputs.logos-nix.follows = "logos-nix"` in place every
check failed to EVALUATE ("attribute 'forAllTargets' missing"). 6e0f4a7 is the
rev logos-module-builder, logos-liblogos and logos-plugin-qt itself all lock,
and it pins the same nixpkgs (e9f00bd) e637a1f did — so no Qt moves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(qtproxy): a cdylib provider with Qt-typed consumers
Retires the LAST caller of --provider-header, a generator mode D11 removed.
This module invoked it by hand from a preConfigure hook and had NO `interface`
key, which is exactly why module-builder's guard never caught it — the guard
reads a field this module did not set.
It could not simply become `interface: "universal"`: it is the conformance
matrix's THIRD consumer surface (qtproxy-sync / qtproxy-async), the only
subject measuring the QT-TYPED consumer path. known.json records that the
C++/Rust proxies cannot substitute — both are lp-client consumers and share
py's blind spot — and that this surface is where Q1 was found. Making it
universal used to flip its wrappers to lp and delete that coverage.
It is now expressible: `interface: "universal"` + `codegen.consumer_api_style:
"qt"` — a cdylib provider (std logos_module_impl.h C ABI) whose CONSUMER
wrappers are Qt-typed, binding through LpBridge::forOrigin with this module's
own name as origin and no LogosAPI anywhere.
47 methods -> 45. The two removed are useWrapper/currentWrapper, which
existed to switch every forwarded call between the generated wrapper and a
hand-written veneer. Since B5 the Qt wrapper IS the veneer — both run over
lp_* — so that axis measured one thing twice. Retired deliberately, recorded
in known.json with the reason, not dropped. The FullApiVeneer went with it;
it was the only reason this module needed a raw LogosAPI*.
0 events -> 15. Emission moves from a dynamic emitEvent() on the Qt provider
object to a declared logos_events: block serialised through nlohmann into the
cdylib event bridge. That bridge has silently lost uint64 in this tree
before, so it was measured rather than assumed, at the values that would
expose it:
fireUintEvent(18446744073709551615) -> uintEvent:18446744073709551615
fireIntEvent(-9007199254740993) -> intEvent:-9007199254740993
Exact both ways, through the repo's own fire/getLastEvent round-trip idiom.
Verified: builds standalone with NO overrides; the plugin defines
logos_module_accept_token (the property the origin-binding gate keys on, i.e.
this image fills its own TokenManager by the C-ABI route rather than needing
LpBridge::syncTokens); fullapi-tests 7/7; lm methods diff accounts for every
difference; the emitted wrapper contains zero LogosAPI.
check_contract_copies.py had to change too — it parsed this header as kind
`cpp-qt`, and that machinery is now unreachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(ipc-new-api): cover async consumption from a Qt-free module
test_ipc_module was the only module asserting async inter-module calls, and it
is a Qt consumer — so migrating it off the legacy interface would have deleted
the repo's only async coverage. Its universal successor covered zero async.
This closes that hole, which is the precondition for retiring the Qt-consumer
original rather than a change to it.
Four methods mirroring the originals by name. On the Qt consumer three were RAW
invokeRemoteMethodAsync and one went through a generated wrapper; here all four
go through wrappers, because on the lp surface `<name>Async` IS the generated
wrapper and bottoms out in lp_invoke_async. So these assert something the Qt
ones cannot: async delivery into a module with no Qt in its own TUs.
WHY concurrency: "multi" IS REQUIRED, and is not incidental here. A Qt-affine
client is bound to the Qt main thread, and its completions are marshalled back
to that thread. Under `single`, dispatch runs on the main thread too — so a
method that blocks waiting for its own completion is blocking the exact thread
that has to deliver it, and every call burns its full timeout and returns a
default-constructed value. That is logos_thread_marshal.h's documented hazard
("only pumps events while it happens to be blocked inside a call") reached from
the other side. The Qt consumer never hit it because QEventLoop PUMPS while it
waits; std::future::wait_for does not. Under `multi` the handler runs on a
worker QThread and the main thread stays free to marshal.
Measured, not assumed: under `single` seven of the eight assertions failed with
empty/zero results. Note the eighth, asyncCallBasicAddInts(0, 0), PASSED —
the timeout sentinel is 0, which is also the expected answer. It is kept
deliberately, next to (3, 4), so the pair cannot both go green on a stall.
The promise is held by shared_ptr rather than by reference into the frame: on
the timeout path the frame is gone while the call is still outstanding, and a
late completion would otherwise write through a dangling reference — corrupting
memory precisely when something has already gone wrong. The wait is bounded so
a stall names the method that stalled instead of surfacing as the harness's own
timeout.
Requires the qt-host-generator capture-list fix: this module has a void method
and no result method, the combination that did not compile under multi.
Verified: 31 passed, 0 failed, 1 skipped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: retire test_ipc_module, now fully duplicated
It was the last `interface: "legacy"` provider in the repo, and it no longer
covers anything of its own. Its 21-method surface is mirrored 1:1 by
test_ipc_new_api_module, and the Qt-typed consumer style it exercised now has a
dedicated home in test_fullapi_qtproxy (`consumer_api_style: "qt"`).
Removing it is what makes an absent `interface` mean "not a provider" rather
than "a provider whose glue silently went missing" — see the throw added to
logos-module-builder alongside this.
Groups `ipc` and `async` go with it, and so does the `unit` group, whose binary
came from the deleted derivation and would otherwise have gone permanently SKIP.
The `multi` group is repointed at the successor rather than deleted: it tests
sequential -c chaining, not the module, and the successor answers the same three
calls.
The async group is NOT simply gone. Its assertions were ported to the
ipc-new-api group in
|
||
|
|
2a029fb0b1 |
feat: dependency interfaces — test-interface-module-cpp verification module (#20)
* feat: test-interface-module-cpp — dependency-interface smoke test Add a universal core module that declares a LOCAL basic_calc interface (interfaces/basic_calc.h, a subset of test_basic_module_cpp's API) and binds it to a module name at runtime via modules().bind_basic_calc(name), exercising the bound wrapper's typed sync + event accessors plus the no-validation path. Registered in flake.nix (modules + packages). Requires logos-cpp-sdk#74, logos-module-builder#108, logos-plugin-qt#8. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * address review: make basic_calc.h valid C++ standalone Include logos_module_context.h in the interface header so the logos_events token (it expands to public) is defined — the header is now valid C++ on its own, not only as generator input. (Copilot review, PR #20.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * bump module-builder --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |