* docs: emit daemon-client logoscore examples, drop inline (-c) mode logos-logoscore-cli's inline mode (`logoscore -m <dir> -l <mod> -c "mod.method(args)" --quit-on-finish`) is being removed. Update everything dev-boost emits/ships so generated module docs and guidance use the daemon + client workflow instead: logoscore -D -m ./result/lib -l <module> & # start a daemon logoscore call <module> <method> [args...] # call a method (positional) logoscore stop # stop when done - Code generators: generate-agents-md.ts, build-help.ts, scaffold.ts now emit the daemon/start + `call` + `stop` sequence (scaffold derives positional method + space-separated args instead of a `module.method(args)` string). - Guidelines (testing.md, universal-module.md) and skills (testing-modules, create-universal-module, package-lgx, create-full-app) rewritten to the daemon/client flow; CI guidance is "background daemon, run calls, stop". - docs/spec.md and templates/full-app/README.md examples updated. - llms-full.txt re-synced to match (it aggregates the above). `tsc` still compiles (nix build of the default package passes). Note: doctests/outputs/* are regenerated end-to-end by the doctest harness (doctests/run.sh, which scaffolds + builds + executes the commands), so those snapshots refresh from the updated generators on the next doctest run rather than being hand-edited here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address review — omit empty call args; fix llms type-detection line - scaffold.ts: no-arg sample methods left `sampleCallArgs` empty, producing a trailing space / empty arg in the suggested `logoscore call`. Derive a `sampleCall` that omits the args when empty. - llms-full.txt: the type-auto-detection line still said "`-c` args" while the surrounding section documents the `call` client command — now "`call` args". tsc still compiles (nix build of .#default passes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: daemon starts clean — drop -l, load modules via load-module Follow-up to dropping inline mode: logoscore's daemon now starts clean (the -l/--load-modules autoload flag is removed). Update everything dev-boost emits/ships to start a clean daemon and load modules with `load-module`: logoscore -D -m <dir> & logoscore load-module <module> logoscore call <module> <method> [args] logoscore stop - Generators (generate-agents-md.ts, build-help.ts, scaffold.ts) emit a `load-module` step instead of `-D … -l <module>`. - Guidelines/skills/docs/template + llms-full.txt updated (comma-separated `-l a,b` examples become one `load-module` per module). tsc compiles (nix build of .#default passes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.7 KiB
Testing Logos Modules
Unit Tests with logos-test-framework
Universal modules have a plain C++ impl class. Test it using the logos-test-framework, which is provided automatically by logos-module-builder.
Test File Structure
tests/
├── main.cpp # LOGOS_TEST_MAIN() entry point
├── test_my_module.cpp # Test cases using LOGOS_TEST()
└── CMakeLists.txt # logos_test() macro
Writing Tests
// tests/main.cpp
#include <logos_test.h>
LOGOS_TEST_MAIN()
// tests/test_my_module.cpp
#include <logos_test.h>
#include "../src/my_module_impl.h"
LOGOS_TEST(hash_returns_nonempty_string) {
MyModuleImpl impl;
LOGOS_ASSERT_FALSE(impl.hash("hello").empty());
}
LOGOS_TEST(verify_matches_hash) {
MyModuleImpl impl;
auto hash = impl.hash("hello");
LOGOS_ASSERT_TRUE(impl.verify("hello", hash));
}
CMakeLists.txt for Tests
# tests/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(MyModuleTests LANGUAGES CXX)
include(LogosTest)
logos_test(
NAME my_module_tests
MODULE_SOURCES ../src/my_module_impl.cpp
TEST_SOURCES
main.cpp
test_my_module.cpp
)
The logos_test() CMake macro handles all framework wiring: Qt dependencies, SDK mock headers, include paths, and CTest registration.
Mocking Other Modules
Use LogosTestContext when your module calls other modules:
LOGOS_TEST(calls_waku_publish) {
auto t = LogosTestContext("chat_module");
t.mockModule("waku_module", "relayPublish").returns(true);
ChatImpl impl;
t.init(&impl);
impl.sendMessage("hello");
LOGOS_ASSERT(t.moduleCalled("waku_module", "relayPublish"));
}
Mocking C Libraries
For modules wrapping external C/C++ libraries, write mock stubs:
// tests/mocks/mock_libcalc.cpp
#include <logos_clib_mock.h>
extern "C" { #include "libcalc.h" }
extern "C" int calc_add(int a, int b) {
LOGOS_CMOCK_RECORD("calc_add");
return LOGOS_CMOCK_RETURN(int, "calc_add");
}
Reference them in CMake:
logos_test(
NAME calc_module_tests
MODULE_SOURCES ../src/calc_module_impl.cpp
TEST_SOURCES main.cpp test_calc.cpp
MOCK_C_SOURCES mocks/mock_libcalc.cpp
)
Running Unit Tests
nix build .#unit-tests -L # Build and run unit tests
nix flake check -L # All Nix checks including tests
logos-module-builder auto-detects tests/CMakeLists.txt and adds checks.<system>.unit-tests and packages.<system>.unit-tests automatically.
Integration Tests with logoscore
Test the module as a loaded plugin via the headless runtime. Start a clean daemon,
load the module(s), then call methods with the call client command:
# Start a clean daemon, then load the module(s) (deps resolved automatically)
logoscore -D -m ./result/lib &
logoscore load-module my_module
logoscore load-module other_module
# Call methods (positional args; @file reads a parameter from a file)
logoscore call my_module doSomething test_input
logoscore call my_module init config
logoscore call my_module process data
logoscore call my_module callOther hello
# Stop the daemon when done
logoscore stop
logoscore arguments:
-D-- Start the daemon-m <path>-- Directory to scan for module plugins (repeatable)-l <mod1,mod2>-- Comma-separated modules to pre-load on startupcall <module> <method> [args...]-- Call a method on a loaded module
Type auto-detection in call args: true/false -> bool, 42 -> int, 3.14 -> double, else -> string. Use @filename to load file content as an argument.
TEST_GROUPS
The test runner supports groups for selective testing:
TEST_GROUPS=basic ws test logos-test-modules --auto-local
TEST_GROUPS=ipc ws test logos-test-modules --auto-local
TEST_GROUPS=basic,ipc,errors ws test logos-test-modules --auto-local
Running Tests via Nix
nix build .#unit-tests -L # Run unit tests
nix flake check -L # Run all checks defined in the flake
ws test my-module # In the workspace
ws test my-module --auto-local # With local dep overrides
ws test --all --type cpp # All C++ repos
3. UI Integration Tests (QML Inspector)
UI apps (type: "ui_qml") can be tested via the QML Inspector MCP server built into logos-standalone-app. Tests interact with the live UI — clicking buttons, reading text, taking screenshots.
Test file pattern
// tests/smoke.mjs
const { resolve } = await import("node:path");
const { test, run } = await import(
resolve(process.env.LOGOS_QT_MCP || "./result-mcp", "test-framework/framework.mjs")
);
test("my_app: basic interaction", async (app) => {
await app.expectTexts(["My App"]);
await app.click("Add");
await app.expectTexts(["Result:"]);
});
run();
Test API
| Method | Description |
|---|---|
app.click(text, opts?) |
Find element by text and click it |
app.expectTexts(texts) |
Assert all texts are visible |
app.waitFor(fn, opts) |
Poll until fn succeeds (timeout, interval, description) |
app.screenshot() |
Capture current state |
app.findByType(type) |
Find elements by QML type |
app.findByProperty(prop, value) |
Find elements by property |
app.getTree() |
Get full QML element tree |
Running UI tests
# Interactive (app already running on localhost:3768)
node tests/smoke.mjs
# CI mode (launches app headless, tests, exits)
node tests/smoke.mjs --ci ./result/bin/logos-standalone-app --verbose
# Hermetic via Nix (offscreen, no display needed)
nix build .#integration-test
Modules with .mjs test files in tests/ automatically get nix build .#integration-test via mkPluginTest.
MCP tools for AI agents
When the app is running, the .mcp.json auto-registers these tools with Claude Code / Cursor:
qml_screenshot, qml_find_and_click, qml_find_by_type, qml_find_by_property, qml_list_interactive, qml_get_tree
This lets AI agents visually verify UI changes, click through workflows, and debug layout issues in real time.
Key Testing Rules
- Unit tests use
LOGOS_TEST()andLOGOS_ASSERT_*macros from<logos_test.h> - Unit tests should NOT require logoscore -- instantiate the impl class directly
tests/CMakeLists.txtmust useinclude(LogosTest)+logos_test()- Use
LogosTestContextfor mocking module calls and C library functions - Integration tests verify the full plugin lifecycle (load, call, response)
- In CI, start the daemon in the background, run your
calls, thenlogoscore stopso the job exits - 30-second timeout per
call; exit code non-zero on failure - After adding
checksto a repo'sflake.nix, runws sync-graphso the workspace discovers them