Check a repository

This tutorial runs the static check on a sample repository and breaks it in three ways. Every cell is live: it builds the repository in a temporary directory and calls the spotregen command line.

import subprocess
import sys
import tempfile
from pathlib import Path

from spotregen import make_sample_repo

tmp = tempfile.TemporaryDirectory()
root = make_sample_repo(Path(tmp.name) / "demo")


def spotregen(*args, drop=None):
    """Run `spotregen` in the sample repository; show the call and its output."""
    done = subprocess.run(
        [sys.executable, "-m", "spotregen", *args],
        cwd=root,
        capture_output=True,
        text=True,
        check=False,
    )
    print("$ spotregen", *args)
    lines = (done.stdout + done.stderr).splitlines()
    print("\n".join(x for x in lines if not (drop and drop in x)))
    print(f"[exit {done.returncode}]")

Definition 1 (The static check) spotregen check loads the whole regen/ tree, runs every check (architecture, each unit, each decision), prints one line per finding and a summary, and exits 1 if an error was found. It is deterministic and cheap, so it is the first step of make guard.

A clean repository

Example 1 (No findings)  

spotregen("check")
$ spotregen check
regen check: 1 units, 0 errors, 0 warnings
[exit 0]

The summary line is stable. A consistent repository prints nothing but the summary.

A hidden consumer

Example 2 (DEL01: a file depends on the unit without being declared) A script imports the unit. Nothing in regen/units/core.calc.toml says so, so a regeneration of the unit could break it unnoticed.

(root / "scripts").mkdir()
(root / "scripts" / "use_it.py").write_text("from samplepkg.core import calc\n")
spotregen("check")
$ spotregen check
ERROR   DEL01   samplepkg.core.calc: scripts/use_it.py depends on the unit but is not a declared consumer
regen check: 1 units, 1 errors, 0 warnings
[exit 1]

A finding line is LEVEL CODE where: message. The fix is to declare the script as a consumer of the unit ([[consumers]] with kind = "script") or to remove the import.

with (root / "regen" / "units" / "core.calc.toml").open("a") as unit:
    unit.write('\n[[consumers]]\nref = "scripts/use_it.py"\nkind = "script"\nnote = "demo"\n')
spotregen("check")
$ spotregen check
regen check: 1 units, 0 errors, 0 warnings
[exit 0]

A forbidden import

Example 3 (ARCH01 and ARCH04: the architecture is violated) regen/architecture.toml allows app to import core and nothing else. A new module in core that imports app breaks the rule, and closes an import cycle as well.

(root / "src" / "samplepkg" / "core" / "bad.py").write_text("from samplepkg.app import main\n")
spotregen("check")
$ spotregen check
ERROR   ARCH01  src/samplepkg/core/bad.py: 'core' imports 'samplepkg.app', but architecture.toml allows only []
ERROR   ARCH04  architecture.toml: new import cycle between 'app' and 'core' (not in known_cycles)
regen check: 1 units, 2 errors, 0 warnings
[exit 1]

Warnings and --strict

Example 4 (DEL03: a consumer that no longer references the unit) Warnings ask for attention. They fail the check only with --strict.

(root / "src" / "samplepkg" / "core" / "bad.py").unlink()
(root / "docs" / "guide.qmd").write_text("The guide no longer mentions the unit.\n")
spotregen("check")
spotregen("check", "--strict")
$ spotregen check
WARNING DEL03   samplepkg.core.calc: consumer docs/guide.qmd no longer references the unit
regen check: 1 units, 0 errors, 1 warnings
[exit 0]
$ spotregen check --strict
WARNING DEL03   samplepkg.core.calc: consumer docs/guide.qmd no longer references the unit
regen check: 1 units, 0 errors, 1 warnings
[exit 1]
TipExit codes

0 means no error (no finding at all with --strict), 1 means errors, 2 means that the spec itself is malformed: invalid TOML, a value outside a vocabulary or a missing regen/architecture.toml. See the schema page for every code.

(root / "regen" / "units" / "core.calc.toml").write_text("unit = [")
spotregen("check")
$ spotregen check
spotregen: /private/var/folders/dw/pvtj6mt91znd0hftcztqb0k00000gn/T/tmpgu1h6b5f/demo/regen/units/core.calc.toml: invalid TOML (Invalid value (at end of document))
[exit 2]

Next: the deletion test.