import errno
import os
import time
[docs]
class InjectedCrash(BaseException):
"""Test-only crash. A BaseException so ``except Exception`` does not swallow it."""
def __init__(self, step):
super().__init__(step)
self.step = step
[docs]
class FaultInjector:
"""Record each named step and, when asked, raise before that step runs."""
def __init__(self, fail_at=None):
self.fail_at = fail_at
self.reached = []
[docs]
def reach(self, step):
self.reached.append(step)
if self.fail_at is not None and step == self.fail_at:
raise InjectedCrash(step)
# A failed operation at each named step (a comma-separated list), for
# tests that drive the command line: the same I/O error the Zig test
# artifact returns there. No filename, so str() is
# "[Errno 5] injected failure at STEP".
if step in os.environ.get("EB_STAGED_REBUILD_FAIL_AT", "").split(","):
raise OSError(errno.EIO, f"injected failure at {step}")
# A real SIGKILL cannot be injected in-process. Tests set this to
# pause after the previous step has happened and before this one.
# A trailing "*" names every step that starts with what precedes it.
token = os.environ.get("EB_STAGED_REBUILD_PAUSE_AT")
if token and (token == step or (token.endswith("*") and step.startswith(token[:-1]))):
ready = os.environ.get("EB_STAGED_REBUILD_PAUSE_READY")
if ready:
fd = os.open(ready, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o644)
os.close(fd)
time.sleep(float(os.environ.get("EB_STAGED_REBUILD_PAUSE_SECONDS", "30")))