mirror of
https://github.com/logos-co/logos-dev-boost.git
synced 2026-08-27 16:11:11 +00:00
add test framework to scaffolded module
This commit is contained in:
+7
-5
@@ -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
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user