feat: add isolated bats test suite for main.sh - #37
Open
hllvc wants to merge 8 commits into
Open
Conversation
|
arunim2405
approved these changes
Aug 3, 2026
There was a problem hiding this comment.
Pull request overview
This PR introduces a hermetic bats-core test harness (unit + smoke) for main.sh, plus small “seams” in main.sh to enable safe sourcing and path/root overrides, and adds lint + CI plumbing so changes to the runner entrypoint can be gated automatically.
Changes:
- Add an isolated bats test harness with unit and smoke tiers, fixtures, and command mocks.
- Make
main.shsafely sourceable and allow key filesystem paths (and root check) to be overridden for tests. - Add
make lint/make test*targets and a PR workflow to run lint + tests (with submodules).
Reviewed changes
Copilot reviewed 38 out of 55 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
main.sh |
Adds source guard, env-overridable filesystem paths, and root-check bypass for testability. |
Makefile |
Adds test/lint targets to run bats tiers and shellcheck gates. |
.github/workflows/test.yml |
Runs lint and unit/smoke tiers on PRs to main (with recursive submodules). |
.shellcheckrc |
Centralizes shellcheck configuration and modern-idiom enforcement. |
.gitmodules |
Vendors bats-core/support/assert as git submodules under test/lib/. |
.gitignore |
Un-ignores test/lib/ so vendored bats submodules aren’t swallowed by lib/ ignore. |
README.md |
Updates docs (anchors/paths/typos) and adds development/test instructions. |
test/README.md |
Documents the test harness contract, mocks, tiers, and caveats. |
test/helpers/load.bash |
Harness entrypoint: temp dirs, PATH mocking, and load_main sourcing. |
test/helpers/mocks.bash |
Mock call-log assertion helpers (assert_called, refute_called, etc.). |
test/mocks/bin/curl |
Stubbed curl used by tests and parsing fixtures. |
test/mocks/bin/crontab |
Stubbed crontab with list/install behavior. |
test/mocks/bin/df |
Stubbed df output shaped for existing parsing. |
test/mocks/bin/docker |
Stubbed docker subcommands used by main.sh. |
test/mocks/bin/free |
Stubbed free -h output for parsing. |
test/mocks/bin/grubby |
Stubbed grubby for cgroupsv2 path. |
test/mocks/bin/ip |
Stubbed ip route output for node IP parsing. |
test/mocks/bin/iptables |
Stubbed iptables with controllable -C exit behavior. |
test/mocks/bin/nproc |
Stubbed CPU count. |
test/mocks/bin/reboot |
Stubbed reboot for safe cgroupsv2 testing. |
test/mocks/bin/systemctl |
Stubbed systemctl queries and no-op mutating operations. |
test/mocks/bin/sysctl |
Stubbed sysctl writes. |
test/mocks/bin/top |
Stubbed top output for CPU parsing pipeline. |
test/mocks/bin/uptime |
Stubbed uptime output including load averages. |
test/unit/api_call.bats |
Unit tests for HTTP status/body parsing and proxy double-status case. |
test/unit/cgroupsv2.bats |
Unit tests for cgroupsv2 enable/disable flows using mocks. |
test/unit/check_args.bats |
Unit tests for argument/value guard helpers. |
test/unit/configure_local_data.bats |
Unit tests for deterministic ecs.config rendering and proxy blocks. |
test/unit/harness.bats |
Canary tests validating harness + mocks behavior. |
test/unit/init_args_are_valid.bats |
Unit tests for subcommand/required-flag validation gate. |
test/unit/iptables_ensure.bats |
Unit tests for idempotent iptables rule ensure helper. |
test/unit/parse_arguments.bats |
Unit tests for option parsing side effects and failure paths. |
test/unit/patch_json.bats |
Unit tests for JSON deep-merge patch helper. |
test/unit/predicates.bats |
Unit tests for boolean predicate helpers. |
test/unit/update_diagnostic.bats |
Unit tests for diagnostic file init/update behavior. |
test/unit/validate_proxy_format.bats |
Unit tests for proxy format validation. |
test/unit/validate_runner_id.bats |
Unit tests for runner ID injection guard. |
test/smoke/command_validation.bats |
Smoke tests for CLI command validation as a subprocess. |
test/smoke/help.bats |
Smoke tests for help/no-args short-circuit behavior. |
test/smoke/required_args.bats |
Smoke tests for required-flag enforcement boundary (preflight not reached). |
test/smoke/.gitkeep |
Keeps smoke directory present even if empty in some contexts. |
test/fixtures/api/* |
HTTP response fixtures for api_call parsing tests. |
test/fixtures/ecs/ecs.config.v4.golden |
Golden ecs.config for deterministic diff-based assertions. |
test/fixtures/.gitkeep |
Keeps fixtures directory present. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
624
to
+626
| configure_local_data() { #{{{ | ||
| mkdir -p /var/log/ecs /etc/ecs /var/lib/ecs/data /var/log/registration/ | ||
| rm -rf /etc/ecs/ecs.config >/dev/null | ||
| mkdir -p "$ECS_LOG_DIR" "$ECS_CONFIG_DIR" "$ECS_DATA_DIR" "$REGISTRATION_DIR" | ||
| rm -rf "$ECS_CONFIG_DIR/ecs.config" >/dev/null |
Comment on lines
+98
to
+99
| refute [ -n "$(grep '^HTTP_PROXY=' "$f")" ] | ||
| refute [ -n "$(grep '^HTTPS_PROXY=' "$f")" ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Adds an isolated, hermetic bats test suite (unit + smoke) for
main.sh, the minimal seams inmain.shthat make it testable, a lint setup that enforces modern Bash idioms, aMakefile, and a GitHub Actions workflow that runs on pull requests againstmain. The suite runs without root, network, Docker, or a real host, so it can gate every change.Motivation & Context
main.shis 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.shtestability seams (behavior-preserving). Wrappedmain "$@"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 anSG_SKIP_ROOT_CHECKbypass inis_root()(off by default).[[ ]]inmain.shand the mock stubs.bats-core,bats-support,bats-assertas git submodules undertest/lib/; added a!test/lib/negation to.gitignore(the existinglib/rule was swallowing them).test/helpers/load.bash(single entry point: redirects all paths to a per-test temp dir, prepends the mockPATH, sourcesmain.shon demand),test/helpers/mocks.bashassertions, andtest/mocks/bin/*stubs for every external command (systemctl,docker,curl,iptables,crontab, ...).test/unit/, 104 tests). Arg parsing/validation,api_callstatus/response parsing,patch_json,update_diagnostic, predicates,iptables_ensureidempotency,configure_local_dataecs.config rendering, andcgroupsv2.test/smoke/, 11 tests). Drivesmain.shas a subprocess to assert the CLI contract (help output, command validation, required-argument enforcement) — the surface that exits beforepreflight(), so it needs no systemd, Docker, or root..shellcheckrc(enable=require-double-brackets,enable=deprecate-which,disable=SC2034) plus amake lintidiom gate —shellcheck --include=SC2292,SC2006over every shell source — enforcing[[ ]]over[ ]and$(...)over backticks.Makefile(test,test-unit,test-smoke,lint) and.github/workflows/test.yml(lint+unit-smokejobs,actions/checkout@v7withsubmodules: recursive), triggered on pull requests againstmain.README.md(fixed the table-of-contents anchors, corrected the stale/tmpdiagnostic/log paths to/var/lib/sg-runnerand/var/log, fixed acgroupsv2typo, added a Development section) and addedtest/README.mddocumenting the harness.Testing
make test— 115 tests, 0 failures, 1 intentional documenting skip.make lint—shellcheckcorrectness plus the modern-idiom gate, clean acrossmain.sh, the mock stubs, and the helpers.Risks & Edge Cases
main.shis modified. The seams are behavior-preserving: overridable paths default to the original values, the source guard only affectssource-ing, and the root check is unchanged unlessSG_SKIP_ROOT_CHECK=true. No runtime behavior changes when the script is executed normally.submodules: recursive; a plain clone yields no test runner..batsfiles are not shellchecked — their@testsyntax isn't valid standalone Bash, so the idiom gate covers.sh/.bashsources only.preflight()requires/run/systemd/system, so subcommands that reach preflight aren't exercised end to end by this host-only suite (documented as askip).Deployment Notes
pull_requesttargetingmain; it pinsactions/checkout@v7and installsshellcheckon the runner. No AWS accounts, secrets, or prod wiring involved.git submodule update --init --recursive).