Files
Dario LipicarandClaude Opus 4.8 52c41168a9 docs: emit daemon-client logoscore examples, drop inline (-c) mode (#10)
* 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>
2026-06-09 22:39:05 -03:00

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 startup
  • call <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() and LOGOS_ASSERT_* macros from <logos_test.h>
  • Unit tests should NOT require logoscore -- instantiate the impl class directly
  • tests/CMakeLists.txt must use include(LogosTest) + logos_test()
  • Use LogosTestContext for 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, then logoscore stop so the job exits
  • 30-second timeout per call; exit code non-zero on failure
  • After adding checks to a repo's flake.nix, run ws sync-graph so the workspace discovers them