docs: split context into layered, agent-oriented files
Replace the single file-structure.md with a stratified layout designed
for AI/agent skill consumption: tables and concrete identifiers over
prose, files loaded on demand, content separated by churn rate.
Layers:
- architecture/ stable structural reference (overview, source-map,
test-map, data-flow)
- patterns/ memory, commands, autocomplete, events, xmpp,
encryption, ui, plugins
- testing/ unit-tests, stubs, functional-tests, bench
- build/ local, docker, ci
- playbooks/ add-command, add-test, add-autocomplete,
add-event-handler, add-encryption
- gotchas.md append-only dated entries (seven seed entries)
- wip/ branch-specific notes; deleted on merge to master
Stable layers describe cproof on master only. In-flight feature
branches (currently feat/ai) get a single file under wip/.
INDEX.md is the entry map with churn labels; SKILL.md is the
always-loaded skill hint pointing to it.
This commit is contained in:
49
testing/bench.md
Normal file
49
testing/bench.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Benchmarks
|
||||
|
||||
`tests/bench/` holds performance benches for storage / parser hot paths. Each
|
||||
runner is a separate binary built into its own subdirectory.
|
||||
|
||||
## Runners
|
||||
|
||||
| Runner | Path | Measures |
|
||||
|---|---|---|
|
||||
| `bench_runner` | `tests/bench/bench_runner/` | General-purpose runner; orchestrates other benches. |
|
||||
| `bench_export_import` | `tests/bench/bench_export_import/` | Database export/import throughput (SQLite + flatfile). |
|
||||
| `bench_failure_modes` | `tests/bench/bench_failure_modes/` | Flatfile parser error/recovery paths. |
|
||||
| `bench_long_messages` | `tests/bench/bench_long_messages/` | Flatfile parser on very long messages. |
|
||||
| `gen_history` | `tests/bench/gen_history/` | Generates synthetic chat history fixtures used by other benches. |
|
||||
|
||||
Each runner pulls in a small set of `src/*.c` files (`common.c`,
|
||||
`database.c`, `database_flatfile*.c`, etc.) — see the per-runner Makefile or
|
||||
top-level `Makefile.am` for exact dependencies.
|
||||
|
||||
## Stubs
|
||||
|
||||
Benches use their own minimal stubs (typically `bench_stubs.c`,
|
||||
`bench_common.c`, `bench_csv.c` per runner) — separate from
|
||||
`tests/unittests/` stubs to keep the bench builds small.
|
||||
|
||||
## Running
|
||||
|
||||
Benches are not part of `make check`. Build and invoke each runner directly
|
||||
once configured:
|
||||
|
||||
```sh
|
||||
make tests/bench/bench_runner/bench_runner
|
||||
./tests/bench/bench_runner/bench_runner [args]
|
||||
```
|
||||
|
||||
(Argument shape per-runner; check `--help`.)
|
||||
|
||||
## Output
|
||||
|
||||
Runners typically emit CSV (`bench_csv.c`) for downstream tooling. Treat
|
||||
output as machine-readable; do not parse the human header lines.
|
||||
|
||||
## When to add a bench
|
||||
|
||||
- A storage / parser hot path is sensitive to regression.
|
||||
- A perf claim in a PR needs to be reproducible.
|
||||
|
||||
If it's just a one-off measurement, run it locally and don't commit. The
|
||||
bench runners are for sustained, comparable measurement.
|
||||
62
testing/functional-tests.md
Normal file
62
testing/functional-tests.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Functional tests
|
||||
|
||||
`tests/functionaltests/` exercises a real cproof binary under a PTY against a
|
||||
real (test) XMPP server.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `functionaltests.c` | Runner (entry point). |
|
||||
| `proftest.c/h` | Fixture: spawn cproof under PTY, drive XMPP server, assertions. |
|
||||
| `test_<feature>.c` | Per-feature suites. |
|
||||
|
||||
Existing suites (grep for `test_*.c`):
|
||||
|
||||
- Connection / disconnection: `test_connect.c`, `test_disconnect.c`,
|
||||
`test_autoping.c`, `test_ping.c`.
|
||||
- Messaging: `test_message.c`, `test_chat_session.c`, `test_carbons.c`,
|
||||
`test_receipts.c`, `test_history.c`.
|
||||
- Presence and roster: `test_presence.c`, `test_roster.c`,
|
||||
`test_lastactivity.c`.
|
||||
- Discovery: `test_disco.c`, `test_software.c`.
|
||||
- MUC: `test_muc.c`, `test_rooms.c`.
|
||||
- Storage: `test_export_import.c`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A test XMPP server reachable from the harness (typically a local Prosody
|
||||
instance configured for the tests).
|
||||
- A pre-baked `.profrc` (recent perf work pre-bakes this for speed — see
|
||||
cproof commit `f84ed1bf6` for context).
|
||||
- PTY availability (`/dev/ptmx` on Linux).
|
||||
|
||||
## Running
|
||||
|
||||
Functional tests are not part of `make check` (which runs unit tests only).
|
||||
They are built and invoked via a separate target in `tests/functionaltests/`
|
||||
— inspect `tests/functionaltests/Makefile.am` for the current target name and
|
||||
arguments.
|
||||
|
||||
Recent perf work (commit `f84ed1bf6`) added:
|
||||
- Profrc pre-baking — fixture writes a known-good `.profrc` once, not per-test.
|
||||
- pty-close shutdown — fixture closes the PTY to terminate cproof faster than
|
||||
signalling.
|
||||
- Parallel port pools — separate XMPP servers / ports per parallel suite.
|
||||
|
||||
## When to add functional vs unit
|
||||
|
||||
| Test what | Where |
|
||||
|---|---|
|
||||
| Pure logic, internal API, given a state. | Unit. |
|
||||
| Stanza serialization / parsing round-trip with real libstrophe. | Functional. |
|
||||
| Connection state machine across multiple network events. | Functional. |
|
||||
| UI rendering (visible output). | **Neither** (UI stubbed in unit; not asserted in functional). |
|
||||
|
||||
## Failure-mode notes
|
||||
|
||||
- A leaked / orphan cproof process between suites poisons later runs. The
|
||||
fixture aggressively kills children — but if you abort a test by hand,
|
||||
`pkill -f tests/functionaltests` first.
|
||||
- Port-pool exhaustion under parallelism: bump the pool size, do not lower
|
||||
`-jN`.
|
||||
113
testing/stubs.md
Normal file
113
testing/stubs.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Stubs
|
||||
|
||||
Unit tests link against the unit-under-test plus a parallel set of "stub"
|
||||
implementations of every dependency. Stubs are organised by module under
|
||||
`tests/unittests/<module>/`.
|
||||
|
||||
## Layout
|
||||
|
||||
| Stub directory | Replaces |
|
||||
|---|---|
|
||||
| `tests/unittests/chatlog/` | `src/chatlog.c` |
|
||||
| `tests/unittests/command/` | parts of `src/command/` |
|
||||
| `tests/unittests/config/` | `src/config/` (accounts, cafile, ...) |
|
||||
| `tests/unittests/database/` | `src/database.c` |
|
||||
| `tests/unittests/event/` | `src/event/` |
|
||||
| `tests/unittests/log/` | `src/log.c` |
|
||||
| `tests/unittests/omemo/` | `src/omemo/` |
|
||||
| `tests/unittests/otr/` | `src/otr/` |
|
||||
| `tests/unittests/pgp/` | `src/pgp/` |
|
||||
| `tests/unittests/plugins/` | `src/plugins/` |
|
||||
| `tests/unittests/tools/` | `src/tools/` |
|
||||
| `tests/unittests/ui/` | `src/ui/` |
|
||||
| `tests/unittests/xmpp/` | `src/xmpp/` |
|
||||
| `tests/unittests/unittests/` | top-level utility stubs |
|
||||
|
||||
Files are named `stub_<module>.c` (sometimes split, e.g.
|
||||
`tests/unittests/config/stub_accounts.c`, `stub_cafile.c`).
|
||||
|
||||
## Two stub flavours
|
||||
|
||||
### Pass-through default
|
||||
|
||||
Returns a benign default (NULL, FALSE, 0). Used when the test does not care
|
||||
about the call.
|
||||
|
||||
```c
|
||||
void
|
||||
chatlog_msg_in(...)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
```
|
||||
|
||||
### `will_return`-driven
|
||||
|
||||
The stub pops a value queued by the test:
|
||||
|
||||
```c
|
||||
jabber_conn_status_t
|
||||
connection_get_status(void)
|
||||
{
|
||||
return (jabber_conn_status_t)mock();
|
||||
}
|
||||
```
|
||||
|
||||
Test side:
|
||||
|
||||
```c
|
||||
will_return(connection_get_status, JABBER_CONNECTED);
|
||||
```
|
||||
|
||||
If the stub is `will_return`-driven, **every test** that exercises a code
|
||||
path through it must queue a value, or cmocka aborts.
|
||||
|
||||
### `expect_*`-driven
|
||||
|
||||
The stub asserts argument values against expectations:
|
||||
|
||||
```c
|
||||
void
|
||||
cons_bad_cmd_usage(const char* const cmd)
|
||||
{
|
||||
check_expected(cmd);
|
||||
}
|
||||
```
|
||||
|
||||
Test side:
|
||||
|
||||
```c
|
||||
expect_string(cons_bad_cmd_usage, cmd, CMD_ACCOUNT);
|
||||
```
|
||||
|
||||
## When to add a stub
|
||||
|
||||
1. You call a new function from production code, and the call site is
|
||||
reachable from a unit test.
|
||||
2. `make check` link fails with an undefined-symbol error.
|
||||
|
||||
Procedure:
|
||||
|
||||
- Locate the matching stub module by callee path: `src/foo/bar.c` →
|
||||
`tests/unittests/foo/stub_bar.c`. Create the stub file if absent.
|
||||
- Stub the new function with the right flavour:
|
||||
- Pass-through if tests don't need to observe / drive it.
|
||||
- `mock()` if a test needs to inject a return value.
|
||||
- `check_expected()` if a test needs to assert an argument.
|
||||
- Wire the new stub `.c` into the build. See `tests/unittests/Makefile.am`
|
||||
(or whatever wires the stubs into the `unittests` target).
|
||||
|
||||
## When to extend an existing stub
|
||||
|
||||
If a stub already pass-through and a test now needs to drive it, **change the
|
||||
stub to `mock()`**. Then every test (existing and new) reaching that stub
|
||||
must `will_return` — be ready to update unrelated tests.
|
||||
|
||||
## Convention notes
|
||||
|
||||
- Stubs do **not** include real headers from `src/<module>/` if doing so
|
||||
would pull in the very symbol they replace. Forward-declare instead.
|
||||
- `tests/unittests/ui/stub_ai.c` is a UI-side stub for the AI client surface
|
||||
consumed by `src/ui/`.
|
||||
- The `tests/unittests/unittests/` directory holds top-level catch-alls that
|
||||
do not fit a single module.
|
||||
96
testing/unit-tests.md
Normal file
96
testing/unit-tests.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Unit tests
|
||||
|
||||
Framework: cmocka, accessed via `tests/prof_cmocka.h` (a thin compatibility
|
||||
wrapper). Run via `make check`.
|
||||
|
||||
## File layout
|
||||
|
||||
- One test file per topic: `tests/unittests/test_<topic>.c` + matching
|
||||
`test_<topic>.h`.
|
||||
- Header declares each test function (one per `cmocka_unit_test*`).
|
||||
- Runner: `tests/unittests/unittests.c` — `#include`s every header, calls
|
||||
`cmocka_run_group_tests()` over a single big array.
|
||||
|
||||
## Registration
|
||||
|
||||
Inside `unittests.c`, every test name appears in the array passed to
|
||||
`cmocka_run_group_tests`:
|
||||
|
||||
```c
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_caps_show_basic),
|
||||
cmocka_unit_test_setup_teardown(test_join_room, muc_before_test, muc_after_test),
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Convenience macros are defined near the top, e.g.:
|
||||
|
||||
```c
|
||||
#define muc_unit_test(f) cmocka_unit_test_setup_teardown(f, muc_before_test, muc_after_test)
|
||||
```
|
||||
|
||||
## Test function shape
|
||||
|
||||
```c
|
||||
void
|
||||
test_account_set_jid_succeeds(void** state)
|
||||
{
|
||||
// Arrange
|
||||
will_return(connection_get_status, JABBER_CONNECTED);
|
||||
will_return(session_get_account_name, "alice@example.com");
|
||||
|
||||
ProfAccount* account = account_new("alice@example.com", ...);
|
||||
will_return(accounts_get_account, account);
|
||||
|
||||
// Act
|
||||
gchar* args[] = { "set", "alice@example.com", "jid", "alice@example.org", NULL };
|
||||
gboolean ok = cmd_account(NULL, CMD_ACCOUNT, args);
|
||||
|
||||
// Assert
|
||||
assert_true(ok);
|
||||
}
|
||||
```
|
||||
|
||||
## cmocka primitives in heavy use
|
||||
|
||||
| Primitive | When |
|
||||
|---|---|
|
||||
| `will_return(fn, value)` | Queue a return value for a stubbed function. Each call to the stub pops one. |
|
||||
| `will_return_count(fn, value, n)` | Queue the same value `n` times. |
|
||||
| `expect_string(fn, param, "...")` | Stub asserts that the named argument equals this string. |
|
||||
| `expect_value(fn, param, value)` | Stub asserts equality on a non-string. |
|
||||
| `expect_any(fn, param)` | Stub accepts any value (still pops the queue). |
|
||||
| `assert_true / assert_false / assert_string_equal / assert_int_equal` | Assertions inside the test. |
|
||||
| `cmocka_unit_test(fn)` | Plain registration. |
|
||||
| `cmocka_unit_test_setup_teardown(fn, setup, teardown)` | With per-test fixture. |
|
||||
|
||||
`expect_*` queues are checked at test end — missing calls fail the test.
|
||||
|
||||
## Group setup / teardown
|
||||
|
||||
For sets of tests sharing fixture: define a `_setup` and `_teardown` and
|
||||
register via `cmocka_unit_test_setup_teardown`. Examples in `unittests.c`:
|
||||
`muc_before_test`, `muc_after_test`, `keyhandlers_setup`, `keyhandlers_teardown`.
|
||||
|
||||
## What gets stubbed
|
||||
|
||||
Tests run against the production `.c` files of the unit under test, with
|
||||
stubs replacing all dependencies. See `testing/stubs.md` for the layout.
|
||||
Standard idiom: every external function the unit calls must have a stub
|
||||
returning either a fixed value or a `will_return`-driven one.
|
||||
|
||||
## Running a single test
|
||||
|
||||
`make check` runs everything. To narrow:
|
||||
|
||||
```sh
|
||||
./tests/unittests/unittests --filter <test_name_pattern>
|
||||
```
|
||||
|
||||
(Confirm via `unittests --help`; cmocka filter support depends on version.)
|
||||
|
||||
## See also
|
||||
|
||||
- `testing/stubs.md` — adding/extending stubs.
|
||||
- `playbooks/add-test.md` — adding a new test file end-to-end.
|
||||
Reference in New Issue
Block a user