diff --git a/adrs/adr-011-source-layout-and-build-pipeline.md b/adrs/adr-011-source-layout-and-build-pipeline.md index d3c0cb4c..5e248cf1 100644 --- a/adrs/adr-011-source-layout-and-build-pipeline.md +++ b/adrs/adr-011-source-layout-and-build-pipeline.md @@ -44,27 +44,35 @@ statement. That is load-bearing, not stylistic — see the build section — and ## The modules -Seventeen, in the order the `bashunit` entrypoint sources them. The order is the dependency -layering: leaves first. +Seventeen, in load order. The order is the dependency layering: leaves first. | # | Module | Files | Lines | Owns | |---|---|---|---|---| | 1 | `dev/` | 1 | 18 | debug helpers; **excluded from the build** | -| 2 | `system/` | 4 | 192 | capability probing: OS, `command -v`, small I/O | -| 3 | `util/` | 4 | 476 | computation: strings, arithmetic, time | -| 4 | `api/` | 5 | 207 | the surface a user's test file calls (except assertions) | -| 5 | `config/` | 4 | 964 | `BASHUNIT_*` defaults, scratch dirs, parallel mode, rerun cache | -| 6 | `coverage/` | 13 | 2644 | line/branch tracking and the four report formats | -| 7 | `state/` | 6 | 477 | counters, per-test context, result payload, parallel aggregation | -| 8 | `console/` | 9 | 1281 | everything printed: palette, header, per-test lines, totals | -| 9 | `helper/` | 8 | 976 | naming, discovery, data providers, tags, encoding | -| 10 | `cli/` | 5 | 447 | the `doc`/`init`/`upgrade`/`watch` subcommand implementations | -| 11 | `assert/` | 11 | 2336 | every assertion | -| 12 | `reports/` | 7 | 467 | JUnit, TAP, JSON, GHA and HTML writers | -| 13 | `runner/` | 11 | 2172 | the file loop, per-test execution, retry, result parsing | -| 14 | `benchmark/` | 4 | 221 | the bench implementation (`runner/bench.sh` is its loop) | -| 15 | `learn/` | 5 | 240 | the interactive tutorial | -| 16 | `main/` | 8 | 1475 | flag parsing per subcommand and the run lifecycle | +| 2 | `system/` | 4 | 189 | capability probing: OS, `command -v`, small I/O | +| 3 | `util/` | 4 | 474 | computation: strings, arithmetic, time | +| 4 | `api/` | 5 | 205 | the surface a user's test file calls (except assertions) | +| 5 | `config/` | 4 | 961 | `BASHUNIT_*` defaults, scratch dirs, parallel mode, rerun cache | +| 6 | `coverage/` | 13 | 2640 | line/branch tracking and the four report formats | +| 7 | `state/` | 6 | 474 | counters, per-test context, result payload, parallel aggregation | +| 8 | `console/` | 9 | 1278 | everything printed: palette, header, per-test lines, totals | +| 9 | `helper/` | 8 | 948 | naming, discovery, data providers, tags, encoding | +| 10 | `cli/` | 5 | 445 | the `doc`/`init`/`upgrade`/`watch` subcommand implementations | +| 11 | `assert/` | 11 | 2326 | every assertion | +| 12 | `doubles/` | 4 | 505 | spies and mocks — **sourced by `assert/index.sh`, not the entrypoint** | +| 13 | `reports/` | 7 | 465 | JUnit, TAP, JSON, GHA and HTML writers | +| 14 | `runner/` | 11 | 2190 | the file loop, per-test execution, retry, result parsing | +| 15 | `benchmark/` | 4 | 219 | the bench implementation (`runner/bench.sh` is its loop) | +| 16 | `learn/` | 14 | 1296 | the interactive tutorial (9 of those files are `learn/lessons/`) | +| 17 | `main/` | 8 | 1473 | flag parsing per subcommand and the run lifecycle | + +The file counts sum to 118, which is every `.sh` file in `src/` — that is the check to re-run +when editing this table, because both of its previous errors came from counting the wrong way. +`doubles/` was absent entirely: the table was generated from the entrypoint's `source` lines, +and `doubles/` is the one module the entrypoint does not source, so it fell through while the +prose above said "seventeen" from a directory count. And `learn/` was listed as 5 files / 240 +lines because a one-level `src/learn/*.sh` glob does not descend into `learn/lessons/` — the +same one-level-glob mistake that `Makefile:67` made with nested tests. Count from the tree. Namespaces track directories: `src/runner/` holds `bashunit::runner::*`. Two exceptions are deliberate — `assert/` holds bare `assert_*` (the public API is unprefixed) and `console/` diff --git a/docs/project-overview.md b/docs/project-overview.md index 2f78e6e4..d651504e 100644 --- a/docs/project-overview.md +++ b/docs/project-overview.md @@ -34,12 +34,16 @@ point holding **only `source` lines** — the code lives in the sibling files be | `helper` | naming, test discovery, data providers, tags, encoding | | `cli` | the `doc`, `init`, `upgrade` and `watch` subcommands | | `assert` | every assertion | +| `doubles` | spies and mocks | | `reports` | JUnit, TAP, JSON, GitHub Actions and HTML writers | | `runner` | the file loop, per-test execution, retry, result parsing | | `benchmark` | the bench implementation | | `learn` | the interactive tutorial | | `main` | flag parsing per subcommand and the run lifecycle | +There is also a `dev` module holding debug helpers, which is deliberately excluded from the +built binary. + The released `bashunit` is a **single file**: `build.sh` walks the `source` statements from the entrypoint, inlines every module in dependency order, and strips the `source` lines. diff --git a/tests/unit/project/adr_module_table_test.sh b/tests/unit/project/adr_module_table_test.sh new file mode 100644 index 00000000..38ee2556 --- /dev/null +++ b/tests/unit/project/adr_module_table_test.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# Anti-drift contract for ADR-011's module table. +# +# That table has been wrong twice, both times because it was counted with a +# one-level glob rather than from the tree: +# +# * `doubles/` was missing outright. The table was generated from the +# entrypoint's `source` lines, and `doubles/` is the one module the +# entrypoint does not source -- `assert/index.sh` does -- so it fell through +# while the prose above it said "seventeen" from a directory count. +# * `learn/` was listed at 5 files / 240 lines because `src/learn/*.sh` does +# not descend into `learn/lessons/`, which holds 9 more files. +# +# Neither is visible to a human reading the ADR, which is what makes a test the +# right tool. Checking the file counts *sum* to `src/` catches an under-count in +# any single row without pinning every number to an exact value. + +ADR="" +ROOT_DIR="" + +function set_up_before_script() { + ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" + ADR="$ROOT_DIR/adrs/adr-011-source-layout-and-build-pipeline.md" +} + +# Without this, renaming the ADR would make every other assertion here compare +# empty output against empty output and pass while checking nothing -- the +# failure mode that let a layering contract rot unnoticed in #946. +function test_the_adr_being_checked_exists() { + assert_file_exists "$ADR" +} + +function adr_table_modules() { + grep -E '^\| [0-9]+ \| `[a-z]+/`' "$ADR" | + sed 's/^| *[0-9]* *| *`\([a-z]*\)\/` *|.*/\1/' | LC_ALL=C sort +} + +# Enumerated with `find`, not `git ls-files`: the Bash 3.0 jobs run in a container +# where git refuses to read the repo (dubious-ownership), so `git ls-files` exits +# non-zero and yields nothing -- which reads here as "src/ has no modules" and +# fails for a reason that has nothing to do with the ADR. +function src_modules() { + (cd "$ROOT_DIR" && find src -name '*.sh' | cut -d/ -f2 | LC_ALL=C sort -u) +} + +function test_every_src_module_has_a_row_in_the_adr_table() { + assert_same "$(src_modules)" "$(adr_table_modules)" +} + +function adr_table_file_total() { + grep -E '^\| [0-9]+ \| `[a-z]+/`' "$ADR" | awk -F'|' '{ sum += $4 } END { print sum + 0 }' +} + +function src_file_total() { + (cd "$ROOT_DIR" && find src -name '*.sh' | wc -l | tr -d ' ') +} + +function test_adr_table_file_counts_account_for_every_src_file() { + assert_same "$(src_file_total)" "$(adr_table_file_total)" +}