Skip to content

Build1 publisher3 min readPublished

An except Exception clause lets the recorder script record its own AssertionError as the timeout exception

A dev.to walkthrough freezes exit code, stream types and exception class name in JSON fixtures before anyone touches a subprocess wrapper, on the argument that a green happy-path test certifies none of the three.

The Engineer · Build desk

Illustration accompanying An except Exception clause lets the recorder script record its own AssertionError as the timeout exception

What happened

  • A dev.to walkthrough argues that callers of a command wrapper depend on exit codes and stream types together, and on getting TimeoutExpired rather than CalledProcessError.
  • The proposed harness is three JSON records covering a zero exit, an exit 2 and a timeout, plus a pytest reader and a recorder script under tools/.
  • Its example target appends the JOB_OPTS environment variable to the command string and runs it with shell=True, behavior the article calls a contract and not a style issue.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • contradiction The walkthrough says characterization must see JOB_OPTS behavior before any extract, yet its recorder pops JOB_OPTS first, so all three committed records describe only the unset path.
  • exposure No recorded call sets check=True, leaving the branch where exit 2 raises CalledProcessError unpinned; an extract can change that branch and the suite still passes.
  • constraint Pinning exception identity as a name string means the fixture cannot distinguish the original class from a same-named replacement, which limits what the timeout record can certify.
  • decision Since the JSON is the source of truth gating the extract, someone has to read it by hand before assertions are written; the recorder alone will not refuse a bad record.

The recorder's timeout function is the part I would read twice. It calls the wrapper with `timeout=0.2` against a command that sleeps for five seconds, raises `AssertionError("timeout did not fire")` if the call returns anyway, and then catches `Exception` and writes `type(exc).__name__` into `python_timeout.json` [11]. The raise sits inside the try block. AssertionError is a subclass of Exception, and the except clause is not fussy about which one it got. When the timeout fails to fire, the handler catches the guard and dumps "AssertionError" as the exception field, and the script still exits zero [1].

The only thing standing between that and a committed fixture is the manual step the walkthrough puts after the run: inspect the JSON before writing any pytest assertions, confirm stdout is str and not bytes, confirm a non-zero exit does not raise, and confirm the timeout record stores TimeoutExpired by name [13]. I would raise the guard after the try/except instead of inside it, so a missed timeout fails the recorder rather than getting serialized.

The field list and the example payload do not agree. The article asks for eight fields per fixture command, including the argv or shell string actually passed through, the timeout value in seconds, the encoding name, and the env keys the wrapper reads [4]. The `python_ok` record dumps eight keys, and the shell string, the timeout, the encoding name and the env keys are not among them [14][3]. The shell string is the one I would add first. The wrapper builds it by appending `JOB_OPTS` to `cmd` and hands the result to `subprocess.run` with `shell=True` [9], so the string is the input, and a record that omits it cannot tell you what was run.

The sequencing advice is good and worth separating from the artifact. Do not open with a package-wide cleanup pass; pick one function that still calls `subprocess.run`, copy its signature into the recorder notes, and leave production code untouched in the first step [16]. That is the correct order because the record is the source of truth, and the extract is allowed to proceed only while the tests reading that record stay green [6].

Whether any of this JSON describes your wrapper depends on one thing the walkthrough fixes in its example module. `jobs/run_job.py` passes `text=True` and `capture_output=True` [9], which is why `stdout_type` in the recorded payload comes back as a str name [14]. Point the same recorder at a wrapper that omits `text=True` and the type fields will read bytes, and the fixture will be pinning a different function's contract [6]. Every file in the layout is labelled an unexecuted local example [8], so the types in it are a proposal about someone else's module until you run the recorder against yours.

What to watch

  • The layout names tests/characterization/test_run_job_record.py, but the reader's assertions are not shown, so the comparison it performs is unknown.
  • cwd is excluded as a separate failure class; a companion harness for path drift would test whether that split holds.
  • Every file is labelled an unexecuted local example, so a real run would confirm or contradict the recorded types and exception names.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories