Files
wildminder-ComfyUI-KEEP/tools/ci_env_sim.py
T
WildAi e7c08682d8 plan(P1): address review findings
Seven review findings of the P1 phase, each fixed at its root:

1. (high) The CI install list omitted PyYAML, but the vendored tree imports
   yaml eagerly (modules/deps/basicsr/utils/options.py:5), so the CI job
   failed on its first run. pyyaml is now installed and asserted by
   tests/test_ci_workflow.py.
2. (medium) tests/test_ci_workflow.py skipped the whole module without
   PyYAML. The module never skips now: a minimal indentation parser is the
   fallback, and it is cross-checked against the PyYAML parse.
3. (medium) Guard (e)'s self-test asserted that the tree still contains
   breakage (it would break exactly at P2.1). It now proves the guard
   instead; P2.1's work list is the scanner output and
   test_guard_e_flip_owner_covers_every_current_finding keeps plan and tree
   in sync.
4. (medium) The mock-fidelity tripwire skipped the comfy_api.latest
   symbols. Every mocked symbol is now checked against the real source
   (re-export / definition / instance attribute), including io.ComfyNode,
   ComfyExtension, ComfyAPI and ComfyAPISync, plus the reverse check that
   the mock invents nothing.
5. (low) The phase review listed committed work as outstanding and
   misstated the ruff ignore list; both are corrected.
6. (low) The reported whole-suite count was impossible; the phase review
   now carries the measured numbers.
7. (low) The root conftest installed the mocks unconditionally. The
   KEEP_NO_COMFY_MOCKS=1 opt-out lets the headless tier import the real
   comfy/comfy_api modules in-process.

tools/ci_env_sim.py runs the gate in an environment without the packages
the CI install list omits.
2026-10-23 16:34:37 +03:00

92 lines
2.9 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):
C:/_Dev/ComfyUI_dev/ComfyUI_t211c130p313/python_embeded/python.exe tools/ci_env_sim.py
C:/_Dev/ComfyUI_dev/ComfyUI_t211c130p313/python_embeded/python.exe 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())