From 3476bbf06b6073884e02596d790bd0e9b2679764 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 1 Aug 2026 02:56:33 +0200 Subject: [PATCH] refactor(assert): group the assert_*.sh files into a src/assert/ module Ten files and 2313 lines were already aggregated together by src/assertions.sh -- the concept existed, only the directory was missing. This is the first group under #940, the counterpart to #931: #931 splits files that are too large, this one collects files that belong together. src/assertions.sh -> src/assert/index.sh src/assert.sh -> src/assert/core.sh src/assert_.sh -> src/assert/.sh (9 files) The assert_ prefix drops inside the directory, which now carries the concept. No function is renamed and no line of any moved file changes: git records all ten as pure renames with a zero-line diff, and the aggregator's `source` order is preserved one-to-one. skip_todo.sh and test_doubles.sh stay flat and keep being sourced from the index, exactly as assertions.sh did. They are part of the same public test surface but they are not assertions, so they do not belong inside src/assert/. Two callers hardcoded these paths: - tests/unit/completions_test.sh derives the public assertion list from `src/assert*.sh` for the completions parity contract -> `src/assert/*.sh`. - tests/unit/build_test.sh special-cased src/assertions.sh as the one flat-file aggregator sitting outside the `src/*/index.sh` glob. That branch is deleted: every aggregator is now an index.sh, so the glob covers them all. This is the simplification ADR-010 predicted when the beside-convention argument was withdrawn. The built artifact's code content is identical -- every difference is a comment or an embed marker -- and `bash build.sh bin -v` prints "Build verified". Related #940 --- .claude/rules/architecture-map.md | 6 +++-- bashunit | 2 +- src/{assert_arrays.sh => assert/arrays.sh} | 0 .../assertions.sh} | 0 src/{assert.sh => assert/core.sh} | 0 src/{assert_dates.sh => assert/dates.sh} | 0 .../duration.sh} | 0 src/{assert_files.sh => assert/files.sh} | 0 src/{assert_folders.sh => assert/folders.sh} | 0 src/assert/index.sh | 23 +++++++++++++++++++ src/{assert_json.sh => assert/json.sh} | 0 src/{assert_once.sh => assert/once.sh} | 0 .../snapshot.sh} | 0 src/assertions.sh | 14 ----------- tests/unit/build_test.sh | 10 ++++---- tests/unit/completions_test.sh | 2 +- 16 files changed, 34 insertions(+), 23 deletions(-) rename src/{assert_arrays.sh => assert/arrays.sh} (100%) rename src/{assert_assertions.sh => assert/assertions.sh} (100%) rename src/{assert.sh => assert/core.sh} (100%) rename src/{assert_dates.sh => assert/dates.sh} (100%) rename src/{assert_duration.sh => assert/duration.sh} (100%) rename src/{assert_files.sh => assert/files.sh} (100%) rename src/{assert_folders.sh => assert/folders.sh} (100%) create mode 100644 src/assert/index.sh rename src/{assert_json.sh => assert/json.sh} (100%) rename src/{assert_once.sh => assert/once.sh} (100%) rename src/{assert_snapshot.sh => assert/snapshot.sh} (100%) delete mode 100644 src/assertions.sh 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 }