Files
wildminder-ComfyUI-KEEP/tools/ci_env_sim.py
T
WildAi 58649e749f chore: name the producing tool instead of the local document path
The tracked files still pointed at the development documents, which now live
in the untracked .dev tree: the usage headings promised the embedded
interpreter while the examples are interpreter agnostic, and the provenance
notes named a local document. The headings are reworded and the provenance
notes name the tool that produces the record instead.
2026-10-25 01:41:52 +03:00

92 lines
2.8 KiB
Python

"""P1.5 - simulate the CI environment and run the gate in it.
The CI job installs a **fixed** list of packages; everything else that happens
to be installed on a developer machine must not be needed by the suite. A
package that the suite needs but the CI install list omits turns the CI job
red on its first run (this is how PyYAML was missed: the vendored tree imports
``yaml`` eagerly at ``modules/deps/basicsr/utils/options.py:5``).
This tool reproduces the CI environment - it blocks the packages the CI
install list does **not** contain - and runs the gate command of the workflow
in it. The block list is the "dev machine only" set of the P1 audit; add more
with ``--block``.
Usage (embedded python, from the repository root):
python tools/ci_env_sim.py
python tools/ci_env_sim.py --block yaml
Exit code: the pytest exit code of the gate run.
"""
from __future__ import annotations
import argparse
import sys
from importlib.abc import MetaPathFinder
#: The gate command of ``.github/workflows/tests.yml`` (and of the plan).
GATE = "-m"
GATE_MARKERS = "unit or mock_integration or headless"
#: Packages that are installed on a development machine but **not** by the CI
#: install step (``.github/workflows/tests.yml``). The suite must not need
#: them - if it does, the CI install list is incomplete.
DEV_ONLY_PACKAGES = (
"addict",
"cupy",
"diffusers",
"ffmpeg",
"future",
"lmdb",
"mmcv",
"skimage",
"yapf",
)
class _BlockingFinder(MetaPathFinder):
"""Makes the blocked top-level modules unimportable."""
def __init__(self, names: tuple[str, ...]) -> None:
self.names = frozenset(names)
def find_spec(self, fullname, path=None, target=None): # noqa: ANN001, D102
if fullname.split(".")[0] in self.names:
raise ModuleNotFoundError(f"No module named {fullname!r}", name=fullname)
return None
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--block",
action="append",
default=[],
metavar="MODULE",
help="an additional top-level module to block (repeatable)",
)
parser.add_argument(
"--list-blocked",
action="store_true",
help="print the blocked modules and exit",
)
args = parser.parse_args(argv)
blocked = DEV_ONLY_PACKAGES + tuple(args.block)
if args.list_blocked:
for name in blocked:
print(name)
return 0
sys.meta_path.insert(0, _BlockingFinder(blocked))
print(f"[ci-env-sim] blocked modules: {', '.join(blocked)}")
import pytest # noqa: PLC0415 - imported after the blocker is installed
return pytest.main(["-q", GATE, GATE_MARKERS])
if __name__ == "__main__":
raise SystemExit(main())