diff --git a/.claude/rules/architecture-map.md b/.claude/rules/architecture-map.md index 6e9e6cdf..25d7f392 100644 --- a/.claude/rules/architecture-map.md +++ b/.claude/rules/architecture-map.md @@ -64,7 +64,9 @@ shell (or, in parallel, in per-test `.result` files aggregated at the end). | `env.sh` | all `BASHUNIT_*` defaults/config files, scratch dirs (`_BASHUNIT_RUN_OUTPUT_DIR` + EXIT-trap cleanup) | | `parallel.sh` | worker temp tree, aggregation, stop-on-failure flag file | | `console_header.sh` / `console_results.sh` | header/totals rendering, deferred failed/skipped/incomplete/risky blocks (scratch files under the run dir) | -| `assert*.sh` | assertions; `assertions.sh` re-exports; per-assertion path must stay fork-free | +| `assert/index.sh` | aggregator only — sources the `src/assert/` module below, plus `skip_todo.sh` and `test_doubles.sh` | +| `assert/core.sh` | `assert::should_skip`, `assert::fail_with`, `assert::join_to_slot` and the comparison assertions the other files build on | +| `assert/{arrays,assertions,dates,duration,files,folders,json,once,snapshot}.sh` | the per-topic assertions; the per-assertion path must stay fork-free | | `clock.sh` | time impl selection (EPOCHREALTIME > date > perl > …), return-slot reads | | `str.sh` / `math.sh` / `io.sh` / `globals.sh` | pure-bash utilities; `globals.sh` has `temp_file`/`temp_dir` (public test API) | | `test_doubles.sh` | spy/mock state via `_BASHUNIT_SPY_*` globals + files | @@ -102,7 +104,7 @@ shell (or, in parallel, in per-test `.result` files aggregated at the end). - **The build flattens the source graph in DFS order** (`build.sh` `build::process_file`): a file's body is emitted, *then* its `source` lines are recursed into. That equals dev-mode order only if a module aggregator - (`src/.sh`, e.g. `src/assertions.sh`) contains **nothing but `source` + (`src//index.sh`, ADR-010) contains **nothing but `source` lines and comments** — any other top-level statement would run before its dependencies in the built artifact but after them in dev mode. Files are deduped by repo-relative path, so `src/` may hold module dirs and two files may diff --git a/bashunit b/bashunit index cf71d475..ae6cf529 100755 --- a/bashunit +++ b/bashunit @@ -83,7 +83,7 @@ source "$BASHUNIT_ROOT_DIR/src/helpers.sh" source "$BASHUNIT_ROOT_DIR/src/test_title.sh" source "$BASHUNIT_ROOT_DIR/src/upgrade.sh" source "$BASHUNIT_ROOT_DIR/src/watch.sh" -source "$BASHUNIT_ROOT_DIR/src/assertions.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/index.sh" source "$BASHUNIT_ROOT_DIR/src/doc.sh" source "$BASHUNIT_ROOT_DIR/src/reports/index.sh" source "$BASHUNIT_ROOT_DIR/src/rerun.sh" diff --git a/src/assert_arrays.sh b/src/assert/arrays.sh similarity index 100% rename from src/assert_arrays.sh rename to src/assert/arrays.sh diff --git a/src/assert_assertions.sh b/src/assert/assertions.sh similarity index 100% rename from src/assert_assertions.sh rename to src/assert/assertions.sh diff --git a/src/assert.sh b/src/assert/core.sh similarity index 100% rename from src/assert.sh rename to src/assert/core.sh diff --git a/src/assert_dates.sh b/src/assert/dates.sh similarity index 100% rename from src/assert_dates.sh rename to src/assert/dates.sh diff --git a/src/assert_duration.sh b/src/assert/duration.sh similarity index 100% rename from src/assert_duration.sh rename to src/assert/duration.sh diff --git a/src/assert_files.sh b/src/assert/files.sh similarity index 100% rename from src/assert_files.sh rename to src/assert/files.sh diff --git a/src/assert_folders.sh b/src/assert/folders.sh similarity index 100% rename from src/assert_folders.sh rename to src/assert/folders.sh diff --git a/src/assert/index.sh b/src/assert/index.sh new file mode 100644 index 00000000..0b5e7839 --- /dev/null +++ b/src/assert/index.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Entry point for the src/assert/ module: only `source` lines and comments +# belong here. build.sh emits a file's body before recursing into its `source` +# lines, so any statement here would run before its dependencies in the built +# binary (adrs/adr-010-src-module-directories.md). +# +# core.sh first: the other files call its shared helpers (assert::should_skip, +# assert::fail_with, assert::join_to_slot). +source "$BASHUNIT_ROOT_DIR/src/assert/core.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/arrays.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/assertions.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/once.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/dates.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/duration.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/files.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/folders.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/json.sh" +source "$BASHUNIT_ROOT_DIR/src/assert/snapshot.sh" + +# Not assertions, but part of the same public test surface the runner loads. +source "$BASHUNIT_ROOT_DIR/src/skip_todo.sh" +source "$BASHUNIT_ROOT_DIR/src/test_doubles.sh" diff --git a/src/assert_json.sh b/src/assert/json.sh similarity index 100% rename from src/assert_json.sh rename to src/assert/json.sh diff --git a/src/assert_once.sh b/src/assert/once.sh similarity index 100% rename from src/assert_once.sh rename to src/assert/once.sh diff --git a/src/assert_snapshot.sh b/src/assert/snapshot.sh similarity index 100% rename from src/assert_snapshot.sh rename to src/assert/snapshot.sh diff --git a/src/assertions.sh b/src/assertions.sh deleted file mode 100644 index 9d2bdde2..00000000 --- a/src/assertions.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -source "$BASHUNIT_ROOT_DIR/src/assert.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_arrays.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_assertions.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_once.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_dates.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_duration.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_files.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_folders.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_json.sh" -source "$BASHUNIT_ROOT_DIR/src/assert_snapshot.sh" -source "$BASHUNIT_ROOT_DIR/src/skip_todo.sh" -source "$BASHUNIT_ROOT_DIR/src/test_doubles.sh" diff --git a/tests/unit/build_test.sh b/tests/unit/build_test.sh index dc97650e..281f4bb8 100644 --- a/tests/unit/build_test.sh +++ b/tests/unit/build_test.sh @@ -95,11 +95,11 @@ function test_build_embed_docs_fails_on_missing_markers() { # # Discovered by glob, never by a hand-maintained list: the previous list named # src/assertions.sh and src/runner.sh, and src/coverage.sh was added in #928 -# without being appended, so the rule silently stopped covering it. A module's -# aggregator is src//index.sh (ADR-010); src/assertions.sh is the one -# flat-file aggregator, which has no directory of its own. +# without being appended, so the rule silently stopped covering it. Every +# aggregator is now src//index.sh (ADR-010), so the glob covers them all +# -- src/assertions.sh was the last flat-file exception and became +# src/assert/index.sh in #940. function build_aggregators() { - echo "src/assertions.sh" local index for index in "$ROOT_DIR"/src/*/index.sh; do [ -f "$index" ] || continue @@ -126,7 +126,7 @@ function test_module_aggregator_discovery_finds_every_module() { assert_contains "src/runner/index.sh" "$found" assert_contains "src/coverage/index.sh" "$found" - assert_contains "src/assertions.sh" "$found" + assert_contains "src/assert/index.sh" "$found" } function test_build_process_file_embeds_a_file_only_once() { diff --git a/tests/unit/completions_test.sh b/tests/unit/completions_test.sh index 0ad81011..4c1cb039 100644 --- a/tests/unit/completions_test.sh +++ b/tests/unit/completions_test.sh @@ -27,7 +27,7 @@ function completions_expected_doc_flags() { } function completions_expected_assert_functions() { - grep -hoE '^function assert_[a-z_0-9]+' src/assert*.sh | + grep -hoE '^function assert_[a-z_0-9]+' src/assert/*.sh | sed 's/^function //' | LC_ALL=C sort -u }