refactor(shell): extract helpers from run() to reduce complexity (S3776) #82

Open
Claude wants to merge 1 commit from Claude/backup-script:fix/issue-34-S3776-shell-complexity into master
First-time contributor

Closes #34

engine/shell.run() had cognitive complexity 27 (SonarQube ceiling: 15).

Refactor

  • _build_env() — isolate the env-merge tri-state: None (let subprocess inherit unchanged) vs dict(os.environ) + overlay vs hermetic {} + overlay.
  • _execute() — isolate the subprocess.run call plus the three exception branches (TimeoutExpired, FileNotFoundError, PermissionError), returning a flat (returncode, stdout, stderr, timed_out) tuple. run() no longer holds four uninitialized locals across a try-block.

run() itself is now a straight-line driver: build env, log start, dry-run short-circuit, execute, truncate, build ShellResult, log end, optionally raise.

Behavior

Byte-for-byte identical. All subprocess semantics preserved:

  • Same return codes for the three failure modes (-1 timeout, 127 ENOENT, 126 EACCES).
  • Same capture_output=True, check=False, input=stdin invocation — no change to stdout/stderr capture, no change to signal handling (still subprocess.run, not Popen — same SIGTERM behavior on parent exit).
  • Same timed_out flag semantics: only true when TimeoutExpired was raised.
  • Same dry-run short-circuit (logs subprocess.start then subprocess.end with dry_run=True, returns a synthetic ShellResult with duration_s=0.0).
  • Same logging events (subprocess.start / subprocess.end) with identical keyword payloads.
  • Same truncation order: raw → _truncateShellResult_format_for_log only for the log line.
  • Same check=TrueShellError raise condition (not result.ok).

Verification

  • pytest -q tests/unit/ — passes (207/207, including test_recover.py and test_restic_engine_audit_fixes.py which call shell.run via mocks at the call-site, exercising the patched signature).
  • mypy --strict src/gardien/core src/gardien/config src/gardien/engine src/gardien/steps — passes (Success: no issues found in 26 source files).
  • SonarQube analyze_code_snippet on the refactored file — issueCount: 0.
Closes #34 `engine/shell.run()` had cognitive complexity 27 (SonarQube ceiling: 15). ### Refactor - `_build_env()` — isolate the env-merge tri-state: `None` (let subprocess inherit unchanged) vs `dict(os.environ)` + overlay vs hermetic `{}` + overlay. - `_execute()` — isolate the `subprocess.run` call plus the three exception branches (`TimeoutExpired`, `FileNotFoundError`, `PermissionError`), returning a flat `(returncode, stdout, stderr, timed_out)` tuple. `run()` no longer holds four uninitialized locals across a try-block. `run()` itself is now a straight-line driver: build env, log start, dry-run short-circuit, execute, truncate, build `ShellResult`, log end, optionally raise. ### Behavior Byte-for-byte identical. All subprocess semantics preserved: - Same return codes for the three failure modes (`-1` timeout, `127` ENOENT, `126` EACCES). - Same `capture_output=True`, `check=False`, `input=stdin` invocation — no change to stdout/stderr capture, no change to signal handling (still `subprocess.run`, not `Popen` — same SIGTERM behavior on parent exit). - Same `timed_out` flag semantics: only true when `TimeoutExpired` was raised. - Same dry-run short-circuit (logs `subprocess.start` then `subprocess.end` with `dry_run=True`, returns a synthetic `ShellResult` with `duration_s=0.0`). - Same logging events (`subprocess.start` / `subprocess.end`) with identical keyword payloads. - Same truncation order: raw → `_truncate` → `ShellResult` → `_format_for_log` only for the log line. - Same `check=True` ⇒ `ShellError` raise condition (`not result.ok`). ### Verification - `pytest -q tests/unit/` — passes (207/207, including `test_recover.py` and `test_restic_engine_audit_fixes.py` which call `shell.run` via mocks at the call-site, exercising the patched signature). - `mypy --strict src/gardien/core src/gardien/config src/gardien/engine src/gardien/steps` — passes (`Success: no issues found in 26 source files`). - SonarQube `analyze_code_snippet` on the refactored file — `issueCount: 0`.
Split the subprocess wrapper's run() into two focused helpers:

- _build_env(): isolate env-merging logic (None vs overlay vs hermetic).
- _execute(): isolate subprocess.run + the timeout / missing-binary /
  permission-denied normalization, returning a flat
  (returncode, stdout, stderr, timed_out) tuple.

Behavior is byte-for-byte identical: same return codes, same dry-run
short-circuit, same logging events, same truncation order, same check=True
raise. mypy --strict and the full unit suite still pass.

Closes #34
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u fix/issue-34-S3776-shell-complexity:Claude-fix/issue-34-S3776-shell-complexity
git switch Claude-fix/issue-34-S3776-shell-complexity

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch master
git merge --no-ff Claude-fix/issue-34-S3776-shell-complexity
git switch Claude-fix/issue-34-S3776-shell-complexity
git rebase master
git switch master
git merge --ff-only Claude-fix/issue-34-S3776-shell-complexity
git switch Claude-fix/issue-34-S3776-shell-complexity
git rebase master
git switch master
git merge --no-ff Claude-fix/issue-34-S3776-shell-complexity
git switch master
git merge --squash Claude-fix/issue-34-S3776-shell-complexity
git switch master
git merge --ff-only Claude-fix/issue-34-S3776-shell-complexity
git switch master
git merge Claude-fix/issue-34-S3776-shell-complexity
git push origin master
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
bc1bb/backup-script!82
No description provided.