DEV Community

Dakota Huang
Dakota Huang

Posted on

Pin Writes, Env Diffs, and Warnings Before One Module Split

Green tests on a god module often miss hidden I/O. A later extract can reorder writes and still pass. Pin writes, env diffs, and warnings first.

This article is a worked proposal, not a production case study. The harness below is labeled and runnable in a sandbox. It does not claim any live customer metrics.

The failure that stays green

God modules mix returns with filesystem and process state. Unit tests usually assert the return value only. The extract then moves a write or env mutation.

Call order can change without a failing assertion. Output files may land under a new relative directory. Warning text can vanish after a helper moves.

You need a snapshot of side effects, not another happy-path assert. Return-value checks alone will not catch those drifts.

What to pin before any extract

Use this decision table before you extract anything. Skip rows that the entry point never touches.

Observable Pin method Fail if
Return value equality or canonical JSON payload shape drifts
Files written relative paths plus sha256 path set or bytes change
Env diffs key set plus values extra keys or value edits
Cwd getcwd before and after directory moves
Warnings category plus message text new or missing warnings
Stdout and stderr captured text, stripped stream text drifts

Do not pin wall-clock time in the golden file. Do not pin unordered dict iteration on old runtimes. Do not pin absolute paths from the temp directory.

Artifact: a side-effect snapshot harness

The next module is a labeled proposal only. Place it beside the god module under test. Point the harness at one public entry point.

"""proposal: characterize one god-module call before a split."""
from __future__ import annotations

import hashlib
import io
import os
import sys
import tempfile
import warnings
from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path
from typing import Any, Callable, Dict, List, Tuple


def _sha256(path: Path) -> str:
    digest = hashlib.sha256()
    digest.update(path.read_bytes())
    return digest.hexdigest()


def snapshot_call(
    fn: Callable[..., Any],
    args: Tuple[Any, ...] = (),
    kwargs: Dict[str, Any] | None = None,
    extra_env: Dict[str, str] | None = None,
) -> Dict[str, Any]:
    kwargs = kwargs or {}
    extra_env = extra_env or {}
    before_cwd = os.getcwd()
    before_env = dict(os.environ)
    before_sys_path = list(sys.path)
    tmp_root = tempfile.TemporaryDirectory()
    try:
        work = Path(tmp_root.name) / "work"
        work.mkdir()
        os.chdir(work)
        os.environ.update(extra_env)
        stdout_buf = io.StringIO()
        stderr_buf = io.StringIO()
        with warnings.catch_warnings(record=True) as caught:
            warnings.simplefilter("always")
            with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
                result = fn(*args, **kwargs)
        after_files = {}
        for path in sorted(work.rglob("*")):
            if path.is_file():
                rel = str(path.relative_to(work)).replace("\\", "/")
                after_files[rel] = _sha256(path)
        after_env = dict(os.environ)
        env_added = {
            key: after_env[key]
            for key in sorted(set(after_env) - set(before_env))
        }
        env_changed = {
            key: after_env[key]
            for key in sorted(set(after_env) & set(before_env))
            if after_env[key] != before_env[key]
        }
        return {
            "result": result,
            "files": after_files,
            "env_added": env_added,
            "env_changed": env_changed,
            "cwd_changed": os.getcwd() != str(work),
            "stdout": stdout_buf.getvalue().rstrip(),
            "stderr": stderr_buf.getvalue().rstrip(),
            "warnings": sorted(
                f"{item.category.__name__}:{item.message}" for item in caught
            ),
            "sys_path_mutated": list(sys.path) != before_sys_path,
        }
    finally:
        os.chdir(before_cwd)
        os.environ.clear()
        os.environ.update(before_env)
        sys.path[:] = before_sys_path
        tmp_root.cleanup()
Enter fullscreen mode Exit fullscreen mode

Restore process state in a finally block in real use. The listing keeps the happy path intentionally short. Treat result as JSON-safe before you freeze it.

Add a tiny test that freezes one fixture. Keep that test on a single entry point.

# proposal: freeze one entry point; do not extract yet
import json
from pathlib import Path
from god_mod import run_report  # replace with the real symbol

GOLDEN = Path(__file__).with_name("run_report.snap.json")


def test_run_report_side_effects():
    snap = snapshot_call(
        run_report,
        args=("sample.csv",),
        extra_env={"REPORT_FMT": "md"},
    )
    payload = json.dumps(snap, sort_keys=True, default=str, indent=2)
    if not GOLDEN.exists():
        GOLDEN.write_text(payload)
        raise AssertionError("wrote golden; rerun to characterize")
    assert payload == GOLDEN.read_text()
Enter fullscreen mode Exit fullscreen mode

The first run writes the golden snapshot file. The second run fails when any field drifts. That frozen file is the extract contract.

Canonicalize before you freeze

Snapshot equality stays brittle on purpose here. Canonicalize only the fields that are allowed to move. Leave every other captured field strictly exact.

Convert Path values into posix relative strings only. Sort warning lists before the JSON dump. Drop trailing whitespace on captured stdout and stderr.

Do not canonicalize away the bug you need. If write order matters, keep list order. If write order does not matter, sort the file map keys only.

Numbered workflow

Follow these seven steps in strict order. Do not skip the golden freeze commit.

  1. Pick one public function and ignore private helpers.
  2. List candidate side effects from the decision table.
  3. Sandbox that call with the harness shown above.
  4. Commit the golden snapshot in the same change set.
  5. Extract one function that the snapshot already covers.
  6. Re-run the snapshot test and stop on drift.
  7. Repeat for the next extract and never batch splits.

A free coding model can draft the first snapshot test from the god file. Read that model draft as an untrusted proposal. It will miss branches you did not paste.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option. Those two facts are the only product claims used here.

Run the harness locally if you do not want a remote runner. A free server helps when the sandbox must leave the laptop.

Do not paste secrets into any model prompt. Strip environment values that look like access tokens. Golden snapshot files must never store raw credentials.

Smallest safe change after the freeze

Keep the first extract intentionally small and boring. Move one pure helper out of the god module. Leave every I/O call inside the original file.

# proposal: mixed I/O stays; only formatting moves
from pathlib import Path


def format_bullets(lines: list[str]) -> str:
    return "\n".join(f"- {line}" for line in lines)


def run_report(path: str) -> str:
    text = Path(path).read_text()
    lines = [line.strip() for line in text.splitlines() if line.strip()]
    out = Path("out") / "report.md"
    out.parent.mkdir(exist_ok=True)
    body = format_bullets(lines)
    out.write_text(body)
    return body
Enter fullscreen mode Exit fullscreen mode

The snapshot still sees the same file bytes. format_bullets has no env or cwd effect. That is the point of the smallest change.

If the extract must move a write, update the golden in a separate commit. Never mix an extract with snapshot golden edits.

Commands that keep the loop honest

Run a narrow pytest node on the snapshot test. Avoid the full suite while extracts are in flight.

python -m pytest tests/test_run_report_side_effects.py -q
git add tests/test_run_report_side_effects.py tests/run_report.snap.json
git commit -m "test: freeze run_report side effects before extract"
# perform the one-function extract, then:
python -m pytest tests/test_run_report_side_effects.py -q
git add src/god_mod.py
git commit -m "refactor: extract format_bullets; snapshot unchanged"
Enter fullscreen mode Exit fullscreen mode

Two small commits beat one mixed refactor commit. Git bisect then has a clean failure boundary.

What this does not prove

A matching snapshot is not a full specification. It only records the one fixture you executed. Unpinned branches can still break after the extract.

This harness does not prove thread safety at all. This harness does not prove remote network call behavior. This harness does not prove Windows versus POSIX permission errors.

sys.path mutation is stored as a boolean flag here. Order-sensitive path hacks need a fuller explicit pin. Add that pin only if the module edits sys.path.

Who should not use this approach

Skip this if the module is already pure. Skip this if tests already sandbox I/O. Skip this for crypto or auth code that must not snapshot secrets.

Do not use a remote runner when the god module reads production credentials. Do not let a model rewrite the god file with the test.

The workflow fails when the entry point is non-deterministic on purpose. Clock, UUID, and unordered sets need canonicalization first.

Close

Freeze writes, env diffs, and warnings before the first split. Extract one boring helper after the golden is committed. Re-run the same snapshot before any further split.

The harness stays local and small by design. Keep secrets out of goldens and out of prompts.

Top comments (0)