Skip to content

feat: add isolated bats test suite for main.sh#37

Draft
hllvc wants to merge 8 commits into
mainfrom
test/main-sh-suite
Draft

feat: add isolated bats test suite for main.sh#37
hllvc wants to merge 8 commits into
mainfrom
test/main-sh-suite

Conversation

@hllvc

@hllvc hllvc commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an isolated, hermetic bats test suite (unit + smoke) for main.sh, the minimal seams in main.sh that make it testable, a lint setup that enforces modern Bash idioms, a Makefile, and a GitHub Actions workflow that runs on pull requests against main. The suite runs without root, network, Docker, or a real host, so it can gate every change.

Motivation & Context

main.sh is the private-runner registration entrypoint, yet it had no automated tests. Its most fragile surface — the proxy/runner-id injection guards, required-argument enforcement, and HTTP-status parsing (including the "a proxy inserts its own 200" case) — could regress silently. This introduces fast, deterministic tests that pin that behavior, mocking every host-affecting command so nothing touches the machine running them.

Changes Made

  • main.sh testability seams (behavior-preserving). Wrapped main "$@" in a [[ "${BASH_SOURCE[0]}" == "${0}" ]] guard so the script can be sourced without executing; made the hardcoded write paths (LOG_FILE, SG_DIAGNOSTIC_DIR, ECS_CONFIG_DIR, ECS_LOG_DIR, ECS_DATA_DIR, REGISTRATION_DIR) env-overridable with unchanged production defaults; added an SG_SKIP_ROOT_CHECK bypass in is_root() (off by default).
  • Modernized Bash idioms. Converted the remaining single-bracket tests to [[ ]] in main.sh and the mock stubs.
  • Vendored the test framework. bats-core, bats-support, bats-assert as git submodules under test/lib/; added a !test/lib/ negation to .gitignore (the existing lib/ rule was swallowing them).
  • Harness. test/helpers/load.bash (single entry point: redirects all paths to a per-test temp dir, prepends the mock PATH, sources main.sh on demand), test/helpers/mocks.bash assertions, and test/mocks/bin/* stubs for every external command (systemctl, docker, curl, iptables, crontab, ...).
  • Unit tier (test/unit/, 104 tests). Arg parsing/validation, api_call status/response parsing, patch_json, update_diagnostic, predicates, iptables_ensure idempotency, configure_local_data ecs.config rendering, and cgroupsv2.
  • Smoke tier (test/smoke/, 11 tests). Drives main.sh as a subprocess to assert the CLI contract (help output, command validation, required-argument enforcement) — the surface that exits before preflight(), so it needs no systemd, Docker, or root.
  • Lint / conventions. .shellcheckrc (enable=require-double-brackets, enable=deprecate-which, disable=SC2034) plus a make lint idiom gate — shellcheck --include=SC2292,SC2006 over every shell source — enforcing [[ ]] over [ ] and $(...) over backticks.
  • Tooling. Makefile (test, test-unit, test-smoke, lint) and .github/workflows/test.yml (lint + unit-smoke jobs, actions/checkout@v7 with submodules: recursive), triggered on pull requests against main.
  • Docs. Refreshed README.md (fixed the table-of-contents anchors, corrected the stale /tmp diagnostic/log paths to /var/lib/sg-runner and /var/log, fixed a cgroupsv2 typo, added a Development section) and added test/README.md documenting the harness.

Testing

  • make test — 115 tests, 0 failures, 1 intentional documenting skip.
  • make lintshellcheck correctness plus the modern-idiom gate, clean across main.sh, the mock stubs, and the helpers.

Risks & Edge Cases

  • main.sh is modified. The seams are behavior-preserving: overridable paths default to the original values, the source guard only affects source-ing, and the root check is unchanged unless SG_SKIP_ROOT_CHECK=true. No runtime behavior changes when the script is executed normally.
  • Submodules. bats is vendored as submodules, so any checkout (CI included) must use submodules: recursive; a plain clone yields no test runner.
  • .bats files are not shellchecked — their @test syntax isn't valid standalone Bash, so the idiom gate covers .sh/.bash sources only.
  • Platform boundary. On macOS preflight() requires /run/systemd/system, so subcommands that reach preflight aren't exercised end to end by this host-only suite (documented as a skip).

Deployment Notes

  • CI triggers only on pull_request targeting main; it pins actions/checkout@v7 and installs shellcheck on the runner. No AWS accounts, secrets, or prod wiring involved.
  • Anyone cloning the repo for tests must initialize submodules (git submodule update --init --recursive).

@hllvc hllvc self-assigned this Jul 20, 2026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant