Testing
The repository ships a zero-dependency, pure-bash test harness under tests/ — no bats, shunit2, or any other framework required.
# Run the whole suite
bash tests/run.sh
# Run a single test file
bash tests/run.sh tests/test_common.sh
Layout: shared-library tests live at the tests/ root; per-script tests live under tests/scripts/. The runner discovers every test_*.sh under tests/ recursively. It prints a ✓/✗ line per assertion and exits non-zero if any assertion fails, so it doubles as a CI gate.
tests/run.sh— discovers and sources everytests/test_*.shfile, then prints a summary.tests/lib/assert.sh— assertion helpers (assert_eq,assert_contains,assert_match,assert_exit) plus sharedTESTS_RUN/TESTS_FAILEDcounters and a resolvedREPO_ROOT.tests/test_common.sh— unit tests for the shared librarysrc/lib/common.sh(UrlEncode, theLog*/EchoBoldhelpers, and theEnd/MissingRequiredArgumentsexit codes — the last run in subshells because they callexit).tests/scripts/test_generate_password.sh— behavioural tests drivinggenerate-password.shas a subprocess (help text, argument and length validation, output length, character-class coverage).
Adding a test
Drop a tests/scripts/test_<name>.sh file: use $REPO_ROOT for paths, run exit-calling code through assert_exit in a subshell, and assert whole-script behaviour by running it with bash "$SCRIPT" and checking the exit code plus the captured $ASSERT_OUTPUT.
Capture script output via a temp file rather than a pipe — generate-password.sh can leave a tr < /dev/urandom reader holding a pipe open, which hangs | sed / $() readers on EOF.
Continuous integration
Every push to master and every pull request runs the test suite (and an advisory shellcheck lint) via GitHub Actions — see .github/workflows/ci.yml.
Pre-commit hook
Run the same checks locally before each commit with a native git hook (no husky, npm, or other dependency). The hook lives at .githooks/pre-commit: it runs the test suite as a hard gate and shellcheck as an advisory step, mirroring CI so failures surface before you push.
Git does not enable repository hooks automatically on clone — enable them once per clone:
git config core.hooksPath .githooks
A failing test aborts the commit; a missing shellcheck is skipped. Bypass for a single commit with:
git commit --no-verify