add test framework to scaffolded module

This commit is contained in:
Iuri Matias
2026-04-14 10:38:58 -04:00
parent 8924915e6d
commit d35cf0f136
8 changed files with 445 additions and 148 deletions
+7 -5
View File
@@ -124,7 +124,9 @@ crypto_utils/
├── CMakeLists.txt # logos_module() with generated_code sources
├── flake.nix # preConfigure runs logos-cpp-generator --from-header
├── tests/
── test_crypto_utils.cpp # Unit tests against impl class directly
── main.cpp # LOGOS_TEST_MAIN() entry point
│ ├── test_crypto_utils.cpp # Unit tests using LOGOS_TEST() and assertions
│ └── CMakeLists.txt # logos_test() macro (auto-detected by builder)
├── CLAUDE.md # Generated: knows this is a universal module
├── AGENTS.md # Universal context for any AI tool
└── .mcp.json # MCP server registration
@@ -178,10 +180,10 @@ logoscore -m ./result/lib -l crypto_utils \
**Step 5: Unit test (no logoscore needed)**
```bash
nix flake check -L
nix build .#unit-tests -L
```
Unit tests instantiate `CryptoUtilsImpl` directly — it is a plain C++ class with no framework dependencies.
Unit tests use logos-test-framework (`LOGOS_TEST()` macros, `LOGOS_ASSERT_*`) and instantiate `CryptoUtilsImpl` directly — it is a plain C++ class with no framework dependencies. `logos-module-builder` auto-detects `tests/CMakeLists.txt` and creates the `unit-tests` target.
**Step 6: Inter-module communication**
@@ -208,7 +210,7 @@ lgx verify crypto_utils.lgx
- Step 1: `init` command scaffolds from universal module template; generated CLAUDE.md/AGENTS.md teach agents the universal pattern
- Step 2: Guidelines ensure pure C++, no Qt types; the type mapping table is always available
- Step 3: Build help explains the codegen pipeline; troubleshooting for common generator errors
- Steps 4-5: Testing skill covers logoscore integration tests and direct unit tests
- Steps 4-5: Testing skill covers logos-test-framework unit tests (LOGOS_TEST, LogosTestContext, mocking) and logoscore integration tests
- Step 6: Inter-module comm skill explains LogosAPI patterns and dependency declaration
- Step 7: Packaging skill covers the full LGX workflow
@@ -392,7 +394,7 @@ What happens when a developer tells an AI agent "create a module that provides e
5. Agent builds with `nix build` — build-help guidelines explain the pipeline. If errors occur, agent knows common fixes: generator type mapping issues, missing `find_package`, `metadata.json`/header class name mismatch.
6. Agent tests with `logoscore` — testing skill provides exact commands and expected output patterns.
6. Agent runs unit tests with `nix build .#unit-tests -L` — the scaffolded `tests/` directory uses logos-test-framework (`LOGOS_TEST()`, `LOGOS_ASSERT_*`). Tests are auto-detected by `logos-module-builder`. Agent also tests with `logoscore` for integration testing — testing skill provides exact commands and expected output patterns.
**Without logos-dev-boost:** Agent would write `Q_INVOKABLE` methods, use `QString` everywhere, try `cmake --build` instead of `nix build`, hallucinate a `LogosPlugin` base class that doesn't exist, and have no idea about the code generator pipeline.
+110 -35
View File
@@ -1,63 +1,136 @@
# Testing Logos Modules
## Two Testing Approaches
## Unit Tests with logos-test-framework
### 1. Unit Tests (Universal Modules)
Universal modules have a plain C++ impl class. Test it using the logos-test-framework, which is provided automatically by `logos-module-builder`.
Universal modules have a plain C++ impl class with no framework dependencies. Test it directly:
### Test File Structure
```cpp
#include "my_module_impl.h"
#include <cassert>
int main() {
MyModuleImpl impl;
assert(impl.hash("hello") == "expected_hash");
assert(impl.verify("hello", "expected_hash") == true);
return 0;
}
```
tests/
├── main.cpp # LOGOS_TEST_MAIN() entry point
├── test_my_module.cpp # Test cases using LOGOS_TEST()
└── CMakeLists.txt # logos_test() macro
```
For the SDK test framework, use `LOGOS_TEST_MAIN()`:
### Writing Tests
```cpp
#include "my_module_impl.h"
#include "logos_test.h"
// tests/main.cpp
#include <logos_test.h>
LOGOS_TEST_MAIN()
```
TEST(MyModule, HashWorks) {
```cpp
// tests/test_my_module.cpp
#include <logos_test.h>
#include "../src/my_module_impl.h"
LOGOS_TEST(hash_returns_nonempty_string) {
MyModuleImpl impl;
EXPECT_FALSE(impl.hash("hello").empty());
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));
}
```
Add unit tests to the flake by including a `checks` output or a `tests/` directory with its own `CMakeLists.txt`.
### CMakeLists.txt for Tests
### 2. Integration Tests with logoscore
```cmake
# 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:
```cpp
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:
```cpp
// 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:
```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
```bash
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:
```bash
# Load module and call a method
logoscore -m ./result/lib -l my_module \
-c "my_module.doSomething(test_input)"
# Multiple sequential calls
logoscore -m ./result/lib -l my_module \
-c "my_module.init(config)" \
-c "my_module.process(data)"
# Load multiple modules (deps resolved automatically)
logoscore -m ./result/lib -l my_module,other_module \
-c "my_module.callOther(hello)"
```
logoscore arguments:
- `-m <path>` Directory to scan for module plugins (repeatable)
- `-l <mod1,mod2>` Comma-separated modules to load
- `-c "<module>.<method>(args)"` Call a method (repeatable, sequential)
- `--quit-on-finish` Exit after calls complete (for CI)
- `-m <path>` -- Directory to scan for module plugins (repeatable)
- `-l <mod1,mod2>` -- Comma-separated modules to load
- `-c "<module>.<method>(args)"` -- Call a method (repeatable, sequential)
- `--quit-on-finish` -- Exit after calls complete (for CI)
Type auto-detection in `-c` args: `true`/`false` -> bool, `42` -> int, `3.14` -> double, else -> string. Use `@filename` to load file content as an argument.
@@ -74,18 +147,20 @@ TEST_GROUPS=basic,ipc,errors ws test logos-test-modules --auto-local
### Running Tests via Nix
```bash
# Run all checks defined in the flake
nix flake check -L
nix build .#unit-tests -L # Run unit tests
nix flake check -L # Run all checks defined in the flake
# In the workspace
ws test my-module
ws test my-module --auto-local # with local dep overrides
ws test --all --type cpp # all C++ repos
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
```
## Key Testing Rules
- Unit tests should NOT require logoscore — instantiate the impl class directly
- 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)
- Always test with `--quit-on-finish` in CI to ensure the process exits
- 30-second timeout per `-c` call; exit code 1 on failure
+20 -3
View File
@@ -116,13 +116,24 @@ function buildHelp(project: { isUniversal: boolean; name: string; hasFlake: bool
function testHelp(project: { isUniversal: boolean; name: string }): string {
const lines = ["## Test Commands\n"];
const pascal = project.name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
if (project.isUniversal) {
lines.push("### Unit Tests (direct impl class testing)\n");
lines.push("### Unit Tests (logos-test-framework)\n");
lines.push("```bash");
lines.push("nix flake check -L # Run Nix-defined checks");
lines.push("nix build .#unit-tests -L # Build and run unit tests");
lines.push("nix flake check -L # Run all Nix checks including tests");
lines.push("```\n");
lines.push(`Unit tests instantiate \`${pascal}Impl\` directly — no logoscore needed.\n`);
lines.push("Tests use `LOGOS_TEST()` macros and `LogosTestContext` for mocking.");
lines.push("Test files live in `tests/` with `CMakeLists.txt` using `logos_test()`.");
lines.push("`logos-module-builder` auto-detects `tests/CMakeLists.txt` and creates the `unit-tests` target.\n");
lines.push("### Test Runner CLI\n");
lines.push("```bash");
lines.push(`./${project.name}_tests --filter <pattern> # Run matching tests only`);
lines.push(`./${project.name}_tests --json # JSON output for CI/agents`);
lines.push(`./${project.name}_tests --no-color # Disable colored output`);
lines.push("```\n");
lines.push("Unit tests instantiate `" + project.name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") + "Impl` directly — no logoscore needed.\n");
}
lines.push("### Integration Tests with logoscore\n");
@@ -219,6 +230,10 @@ function troubleshoot(error: string, project: { isUniversal: boolean; name: stri
lines.push("**Fix:** Store references in the output mean it's a dev build, not portable. Use `nix build .#portable` for distribution.");
} else if (errorLower.includes("git") || errorLower.includes("not a git")) {
lines.push("**Fix:** Nix flakes require git tracking. Run `git add -A` before `nix build`.");
} else if (errorLower.includes("logostest") || errorLower.includes("logos_test") || errorLower.includes("logos_test.h")) {
lines.push("**Fix:** Test framework not found. Ensure `tests/CMakeLists.txt` uses `include(LogosTest)` and that `logos-module-builder` is your flake input (it provides the test framework automatically).");
} else if (errorLower.includes("unit-tests") || errorLower.includes("unit_tests")) {
lines.push("**Fix:** The `unit-tests` target requires `tests/CMakeLists.txt` in your project root. Create it with `include(LogosTest)` and `logos_test()`. The builder auto-detects it.");
} else {
lines.push("**General tips:**");
lines.push("- Build with `-L` for full logs: `nix build -L`");
@@ -247,6 +262,8 @@ function commonIssues(project: { isUniversal: boolean }): string {
lines.push("6. **Generated files not found** — Check `preConfigure` runs logos-cpp-generator");
lines.push("7. **Unknown type mapped to any** — Use types from the supported mapping table");
lines.push("8. **Impl class not found** — `--impl-class` must exactly match the class name");
lines.push("9. **unit-tests not found** — Add `tests/CMakeLists.txt` with `include(LogosTest)` + `logos_test()`");
lines.push("10. **LogosTest.cmake not found** — Ensure you build via `nix build .#unit-tests`, not raw cmake");
}
return lines.join("\n");
+42 -12
View File
@@ -94,9 +94,12 @@ export function handleScaffold(args: Record<string, unknown>) {
`cd ${path.basename(projectDir)}`,
"git init && git add -A",
"nix build",
type === "module"
? `logoscore -m ./result/lib -l ${name} -c "${name}.methodName(args)"`
: "cp -r result/* ~/.local/share/Logos/LogosBasecampDev/plugins/" + name + "/",
...(type === "module"
? [
"nix build .#unit-tests -L",
`logoscore -m ./result/lib -l ${name} -c "${name}.methodName(args)"`,
]
: ["cp -r result/* ~/.local/share/Logos/LogosBasecampDev/plugins/" + name + "/"]),
],
},
null,
@@ -244,20 +247,47 @@ logos_module(
filesCreated
);
writeFile(
path.join(dir, "tests/main.cpp"),
`#include <logos_test.h>
LOGOS_TEST_MAIN()
`,
filesCreated
);
writeFile(
path.join(dir, `tests/test_${name}.cpp`),
`#include "../src/${name}_impl.h"
#include <cassert>
#include <iostream>
`#include <logos_test.h>
#include "../src/${name}_impl.h"
int main() {
LOGOS_TEST(echo_returns_prefixed_input) {
${pascal}Impl impl;
assert(impl.echo("test") == "echo: test");
std::cout << "All tests passed" << std::endl;
return 0;
LOGOS_ASSERT_EQ(impl.echo("hello"), std::string("echo: hello"));
}
LOGOS_TEST(echo_handles_empty_input) {
${pascal}Impl impl;
LOGOS_ASSERT_EQ(impl.echo(""), std::string("echo: "));
}
`,
filesCreated
);
writeFile(
path.join(dir, "tests/CMakeLists.txt"),
`cmake_minimum_required(VERSION 3.14)
project(${pascal}Tests LANGUAGES CXX)
include(LogosTest)
logos_test(
NAME ${name}_tests
MODULE_SOURCES ../src/${name}_impl.cpp
TEST_SOURCES
main.cpp
test_${name}.cpp
)
`,
filesCreated
);
+57 -7
View File
@@ -167,17 +167,65 @@ nix build
Files must be tracked by git before Nix can see them.
## Step 8: Test
## Step 8: Add Unit Tests
```bash
# Integration test via logoscore
logoscore -m ./result/lib -l <name> -c "<name>.exampleMethod(test)"
The scaffold creates test files using logos-test-framework. If adding tests manually:
# Unit test (if tests/ directory exists)
nix flake check -L
```
tests/
├── main.cpp # Test runner entry point
├── test_<name>.cpp # Test cases
└── CMakeLists.txt # logos_test() integration
```
## Step 9: Inspect
```cpp
// tests/main.cpp
#include <logos_test.h>
LOGOS_TEST_MAIN()
```
```cpp
// tests/test_<name>.cpp
#include <logos_test.h>
#include "../src/<name>_impl.h"
LOGOS_TEST(example_method_works) {
<ImplClassName> impl;
LOGOS_ASSERT_FALSE(impl.exampleMethod("test").empty());
}
```
```cmake
# tests/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(<PascalName>Tests LANGUAGES CXX)
include(LogosTest)
logos_test(
NAME <name>_tests
MODULE_SOURCES ../src/<name>_impl.cpp
TEST_SOURCES
main.cpp
test_<name>.cpp
)
```
`logos-module-builder` auto-detects `tests/CMakeLists.txt` and creates the `unit-tests` target.
## Step 9: Run Tests
```bash
# Unit tests (direct impl class testing, no logoscore needed)
nix build .#unit-tests -L
# Integration test via logoscore
logoscore -m ./result/lib -l <name> -c "<name>.exampleMethod(test)"
```
For mocking other modules or C libraries in tests, see the `testing-modules` skill.
## Step 10: Inspect
```bash
lm ./result/lib/<name>_plugin.so
@@ -191,6 +239,8 @@ lm methods ./result/lib/<name>_plugin.so --json
- [ ] Impl class name matches `--impl-class` in flake.nix preConfigure
- [ ] `CMakeLists.txt` lists generated_code files in SOURCES
- [ ] `flake.nix` has preConfigure with logos-cpp-generator
- [ ] `tests/CMakeLists.txt` uses `include(LogosTest)` + `logos_test()`
- [ ] All files tracked by git (`git add -A`)
- [ ] `nix build` succeeds
- [ ] `nix build .#unit-tests -L` succeeds
- [ ] `logoscore` can load and call the module
+181 -80
View File
@@ -1,6 +1,6 @@
---
name: testing-modules
description: Activate when writing tests for Logos modules. Covers unit testing universal modules (direct impl class testing), logoscore integration tests, TEST_GROUPS, mock transport, and Nix check configuration.
description: Activate when writing tests for Logos modules. Covers the logos-test-framework (LOGOS_TEST macros, LogosTestContext, module mocking, C library mocking, event testing), logos_test() CMake integration, logoscore integration tests, and Nix check configuration.
---
# Testing Logos Modules
@@ -9,90 +9,211 @@ description: Activate when writing tests for Logos modules. Covers unit testing
Use this skill when:
- Writing unit tests for a universal module
- Setting up test infrastructure (tests/CMakeLists.txt, test files)
- Mocking calls to other modules or external C libraries in tests
- Writing integration tests with logoscore
- Adding test infrastructure to a module's flake.nix
- Debugging test failures
## Unit Tests (Universal Modules)
## logos-test-framework (Unit Tests)
Universal modules have a plain C++ impl class with no framework dependencies. Test it directly:
The test framework is provided by `logos-module-builder` automatically. No extra flake inputs needed.
### Basic Assert-Based Tests
### File Structure
```cpp
// tests/test_my_module.cpp
#include "../src/my_module_impl.h"
#include <cassert>
#include <iostream>
int main() {
MyModuleImpl impl;
// Test basic functionality
std::string result = impl.doSomething("test");
assert(!result.empty());
assert(impl.validate("valid_input") == true);
assert(impl.validate("") == false);
assert(impl.count() >= 0);
std::cout << "All tests passed" << std::endl;
return 0;
}
```
tests/
├── main.cpp # LOGOS_TEST_MAIN() entry point
├── test_my_module.cpp # Test cases using LOGOS_TEST()
└── CMakeLists.txt # logos_test() macro
```
### SDK Test Framework
### Test Entry Point
```cpp
// tests/test_my_module.cpp
#include "../src/my_module_impl.h"
#include "logos_test.h"
// tests/main.cpp
#include <logos_test.h>
LOGOS_TEST_MAIN()
```
TEST(MyModule, DoSomethingWorks) {
### Writing Tests
```cpp
// tests/test_my_module.cpp
#include <logos_test.h>
#include "../src/my_module_impl.h"
LOGOS_TEST(echo_returns_expected_value) {
MyModuleImpl impl;
EXPECT_FALSE(impl.doSomething("test").empty());
LOGOS_ASSERT_EQ(impl.echo("hello"), std::string("echo: hello"));
}
TEST(MyModule, ValidateRejectsEmpty) {
LOGOS_TEST(validate_rejects_empty_input) {
MyModuleImpl impl;
EXPECT_FALSE(impl.validate(""));
LOGOS_ASSERT_FALSE(impl.validate(""));
}
```
### CMakeLists.txt for Tests
### CMakeLists.txt
```cmake
# tests/CMakeLists.txt
add_executable(test_my_module test_my_module.cpp)
target_include_directories(test_my_module PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_link_libraries(test_my_module PRIVATE my_module_impl_objects)
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
)
```
### Running Tests
```bash
nix build .#unit-tests -L # Build and run unit tests
nix flake check -L # Run all Nix checks including tests
```
`logos-module-builder` auto-detects `tests/CMakeLists.txt` and creates `checks.<system>.unit-tests` and `packages.<system>.unit-tests`.
### Test CLI Options
```bash
./my_module_tests --filter <pattern> # Run only matching tests
./my_module_tests --json # JSON output for CI/agents
./my_module_tests --no-color # Disable colored output
./my_module_tests --help # Show help
```
## Assertions
| Macro | Description |
|-------|-------------|
| `LOGOS_ASSERT(expr)` | Expression is truthy |
| `LOGOS_ASSERT_TRUE(expr)` | Alias for LOGOS_ASSERT |
| `LOGOS_ASSERT_FALSE(expr)` | Expression is falsy |
| `LOGOS_ASSERT_EQ(a, b)` | `a == b` with diff on failure |
| `LOGOS_ASSERT_NE(a, b)` | `a != b` |
| `LOGOS_ASSERT_GT(a, b)` | `a > b` |
| `LOGOS_ASSERT_GE(a, b)` | `a >= b` |
| `LOGOS_ASSERT_LT(a, b)` | `a < b` |
| `LOGOS_ASSERT_LE(a, b)` | `a <= b` |
| `LOGOS_ASSERT_CONTAINS(haystack, needle)` | String contains substring |
| `LOGOS_ASSERT_THROWS(expr)` | Expression throws |
## Mocking Other Modules
Use `LogosTestContext` to mock calls to other Logos modules:
```cpp
LOGOS_TEST(calls_other_module) {
auto t = LogosTestContext("my_module");
t.mockModule("other_module", "getData").returns(42);
MyModuleImpl impl;
t.init(&impl);
auto result = impl.fetchData();
LOGOS_ASSERT_EQ(result, 42);
LOGOS_ASSERT(t.moduleCalled("other_module", "getData"));
LOGOS_ASSERT_EQ(t.moduleCallCount("other_module", "getData"), 1);
}
```
## Mocking C Libraries
For modules wrapping external C/C++ libraries:
### 1. Write mock stubs
```cpp
// 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");
}
```
### 2. Reference in CMake
```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
)
```
### 3. Use in tests
```cpp
LOGOS_TEST(add_returns_mocked_value) {
auto t = LogosTestContext("calc_module");
t.mockCFunction("calc_add").returns(99);
CalcModuleImpl impl;
t.init(&impl);
auto result = impl.add(10, 20);
LOGOS_ASSERT_EQ(result, 99);
LOGOS_ASSERT(t.cFunctionCalled("calc_add"));
}
```
### 4. Configure Nix for C library mocking
```nix
logos-module-builder.lib.mkLogosModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
tests = {
dir = ./tests;
mockCLibs = ["mylib"];
};
};
```
## Event Testing
```cpp
LOGOS_TEST(method_emits_event) {
auto t = LogosTestContext("my_module");
t.captureEvents();
MyModuleImpl impl;
t.init(&impl);
impl.doSomething("data");
LOGOS_ASSERT(t.eventEmitted("myEvent"));
LOGOS_ASSERT_EQ(t.eventCount("myEvent"), 1);
LOGOS_ASSERT_EQ(t.lastEventData("myEvent").at(0).toString(), "data");
}
```
## Integration Tests with logoscore
Test the module as a loaded plugin:
Test the module as a loaded plugin via the headless runtime:
```bash
# Basic: load and call a method
logoscore -m ./result/lib -l my_module \
-c "my_module.doSomething(test_input)"
# Multiple sequential calls
logoscore -m ./result/lib -l my_module \
-c "my_module.init(config)" \
-c "my_module.process(data)"
# With multiple modules (tests IPC)
logoscore -m ./result/lib -l my_module,other_module \
-c "my_module.callOther(hello)"
# For CI: exit after calls complete
logoscore -m ./result/lib -l my_module \
-c "my_module.doSomething(test)" \
--quit-on-finish
```
### logoscore Argument Types
@@ -104,54 +225,34 @@ Arguments in `-c` calls are auto-detected:
- Everything else -> string
- `@filename` -> file content as string argument
### Asserting on Output
In shell-based test scripts, assert on logoscore stdout:
```bash
OUTPUT=$(logoscore -m ./result/lib -l my_module \
-c "my_module.doSomething(test)" --quit-on-finish 2>&1)
if echo "$OUTPUT" | grep -q "expected_result"; then
echo "PASS"
else
echo "FAIL: unexpected output"
exit 1
fi
```
## TEST_GROUPS
### TEST_GROUPS
For repos with many tests, group them:
```bash
TEST_GROUPS=basic ws test my-module --auto-local
TEST_GROUPS=ipc ws test my-module --auto-local
TEST_GROUPS=basic,ipc,errors ws test my-module --auto-local
```
## Running Tests via Nix
## Running Tests via Nix / Workspace
```bash
# All checks in the flake
nix flake check -L
nix build .#unit-tests -L # Run unit tests
nix flake check -L # All checks in the flake
# In the workspace
ws test my-module
ws test my-module --auto-local
# All C++ repos
ws test --all --type cpp
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
```
## Adding Tests to flake.nix
After adding `checks` outputs to a repo's `flake.nix`, run `ws sync-graph` so the workspace discovers them.
## Key Rules
- Unit tests instantiate the impl class directly — no logoscore, no Qt
- Unit tests use `LOGOS_TEST()` macro, not raw `assert()` or GoogleTest
- Instantiate the impl class directly in unit tests -- no logoscore, no Qt needed
- Use `LogosTestContext` for mocking module calls and C library functions
- `tests/CMakeLists.txt` must use `include(LogosTest)` + `logos_test()`
- Integration tests verify the full plugin lifecycle (load, call, response)
- Always use `--quit-on-finish` in CI
- Always use `--quit-on-finish` in CI for logoscore integration tests
- 30-second timeout per `-c` call; exit code 1 on failure
- The logoscore binary auto-builds from source on first use
+18 -6
View File
@@ -6,12 +6,24 @@ The scaffold tool in `mcp-server/tools/scaffold.ts` generates files programmatic
## Generated files
- `src/<name>_impl.h` Pure C++ implementation header (module API)
- `src/<name>_impl.cpp` Implementation file
- `metadata.json` Module identity with `"interface": "universal"`
- `CMakeLists.txt` Uses `logos_module()` macro with generated_code sources
- `flake.nix` `mkLogosModule` with `preConfigure` running `logos-cpp-generator`
- `tests/test_<name>.cpp` — Unit tests against impl class
- `src/<name>_impl.h` -- Pure C++ implementation header (module API)
- `src/<name>_impl.cpp` -- Implementation file
- `metadata.json` -- Module identity with `"interface": "universal"`
- `CMakeLists.txt` -- Uses `logos_module()` macro with generated_code sources
- `flake.nix` -- `mkLogosModule` with `preConfigure` running `logos-cpp-generator`
- `tests/main.cpp` -- Test runner entry point (`LOGOS_TEST_MAIN()`)
- `tests/test_<name>.cpp` -- Unit tests using `LOGOS_TEST()` macros and assertions
- `tests/CMakeLists.txt` -- `logos_test()` macro integration (auto-detected by `logos-module-builder`)
## Testing
The generated tests use logos-test-framework. Run with:
```bash
nix build .#unit-tests -L
```
`logos-module-builder` auto-detects `tests/CMakeLists.txt` and creates `checks.<system>.unit-tests` and `packages.<system>.unit-tests`.
## Reference
+10
View File
@@ -6,6 +6,7 @@
# 2. `git init`s the scaffolded project (flakes only see tracked files).
# 3. Runs `nix build` on the scaffolded project.
# 4. Asserts the expected plugin binary exists under ./result/lib/.
# 5. For modules: runs `nix build .#unit-tests -L` to verify generated tests compile and pass.
#
# Usage:
# tests/run-scaffold-tests.sh <type> # one of: module, ui-app
@@ -84,6 +85,15 @@ run_one() {
fi
echo " OK: ${name}_plugin.$ext built"
if [ "$type" = "module" ]; then
echo " running unit tests..."
(
cd "$proj"
nix build .#unit-tests -L
)
echo " OK: unit tests passed"
fi
}
if [ "$#" -ne 1 ]; then