runner.py file-level

at main · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:6 fix(ISR): default require_independent_second_reviewer to require Opera… · aaronrene · Sep 2, 2026
1 """Injectable command runner for VCS subprocess calls."""
2
3 from __future__ import annotations
4
5 import shlex
6 import subprocess
7 from dataclasses import dataclass
8 from typing import Protocol
9
10
11 @dataclass(frozen=True)
12 class CommandResult:
13 stdout: str
14 stderr: str
15 exit_code: int
16
17 @property
18 def ok(self) -> bool:
19 return self.exit_code == 0
20
21
22 class CommandRunner(Protocol):
23 """Protocol for executing shell commands (real or mocked)."""
24
25 def run(self, command: str, *, cwd: str | None = None) -> CommandResult:
26 """Execute ``command`` and return stdout/stderr/exit_code."""
27
28
29 @dataclass
30 class SubprocessRunner:
31 """Production runner using ``subprocess``."""
32
33 def run(self, command: str, *, cwd: str | None = None) -> CommandResult:
34 completed = subprocess.run(
35 command,
36 shell=True,
37 cwd=cwd,
38 capture_output=True,
39 text=True,
40 )
41 return CommandResult(
42 stdout=completed.stdout.strip(),
43 stderr=completed.stderr.strip(),
44 exit_code=completed.returncode,
45 )
46
47
48 @dataclass
49 class RecordingRunner:
50 """Test runner that records commands and returns scripted responses."""
51
52 responses: dict[str, CommandResult]
53 calls: list[tuple[str, str | None]]
54
55 def run(self, command: str, *, cwd: str | None = None) -> CommandResult:
56 self.calls.append((command, cwd))
57 if command in self.responses:
58 return self.responses[command]
59 for pattern, result in self.responses.items():
60 if pattern in command:
61 return result
62 return CommandResult(stdout="", stderr="unmocked command", exit_code=127)
63
64
65 def quote_arg(value: str) -> str:
66 """Shell-quote a single argument."""
67 return shlex.quote(value)