File size: 4,474 Bytes
0c85e62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""Guards on which modules ``dev-sync`` ships, so the loader can never point
at a file the install lacks.

The #183 incident (root cause behind #198): a module that ``__init__.py``
imported but ``scripts/sync_to_dev.py``'s ``RUNTIME_PATHS`` never copied raised
``ImportError`` at startup. After the #198 refactor the blast radius of such a
gap depends on *where* the import sits — a per-group node import degrades to
"that node absent", but the context probe (``koolook_versioning``) and the
install guard gate *all* registration. So the real invariant is: every module
``__init__.py`` imports at load must be shipped by ``RUNTIME_PATHS``. This test
derives those imports from ``__init__.py`` itself (rather than a ``k_*.py``
glob, which misses the non-``k_`` gates) so it can't drift.
"""
from __future__ import annotations

import ast
import importlib.util
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]
_SCRIPT = REPO_ROOT / "scripts" / "sync_to_dev.py"
_spec = importlib.util.spec_from_file_location("koolook_sync_to_dev_under_test", _SCRIPT)
assert _spec is not None and _spec.loader is not None
sync_to_dev = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(sync_to_dev)


def _init_load_time_module_deps() -> set[str]:
    """Top-level package modules/packages ``__init__.py`` imports at load.

    Covers relative ``from .X import ...`` / ``from . import X`` statements and
    the ``_merge_node_group(label, ".X")`` node-group imports. Returns the
    first path segment of each (e.g. ``forks.radiance_koolook`` -> ``forks``),
    the granularity ``RUNTIME_PATHS`` ships at. Absolute imports (the install
    guard's out-of-package fallback) are level 0 and intentionally ignored.
    """
    tree = ast.parse((REPO_ROOT / "__init__.py").read_text(encoding="utf-8"))
    deps: set[str] = set()
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and (node.level or 0) >= 1:
            if node.module:  # from .pkg import ...
                deps.add(node.module.split(".")[0])
            else:  # from . import a, b
                deps.update(alias.name.split(".")[0] for alias in node.names)
        elif (
            isinstance(node, ast.Call)
            and getattr(node.func, "id", None) == "_merge_node_group"
        ):
            for arg in node.args:
                if (
                    isinstance(arg, ast.Constant)
                    and isinstance(arg.value, str)
                    and arg.value.startswith(".")
                ):
                    deps.add(arg.value.lstrip(".").split(".")[0])
    return deps


def _route_load_time_module_deps() -> set[str]:
    tree = ast.parse((REPO_ROOT / "koolook_routes.py").read_text(encoding="utf-8"))
    deps: set[str] = set()
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom):
            if (node.level or 0) >= 1 and node.module:
                deps.add(node.module.split(".")[0])
            elif node.module and node.module.startswith("koolook_"):
                deps.add(node.module.split(".")[0])
    return deps


def test_init_load_deps_are_shipped_by_runtime_paths():
    runtime_paths = set(sync_to_dev.RUNTIME_PATHS)
    deps = _init_load_time_module_deps() | _route_load_time_module_deps()
    # Sanity: the parse actually found the imports (guards against a refactor
    # that renames _merge_node_group and silently makes this test vacuous).
    assert "koolook_versioning" in deps and "k_ai_pipeline" in deps, (
        f"__init__.py import parse looks broken — only found: {sorted(deps)}"
    )
    # A single-file module ships as "<dep>.py"; a package (e.g. forks) ships as
    # its top-level dir entry.
    missing = sorted(
        dep
        for dep in deps
        if f"{dep}.py" not in runtime_paths and dep not in runtime_paths
    )
    assert not missing, (
        "__init__.py imports these at load but dev-sync RUNTIME_PATHS does not "
        f"ship them: {missing}. Add them to RUNTIME_PATHS in "
        "scripts/sync_to_dev.py so a plain dev-sync can't 404 the loader."
    )


def test_dev_sync_ships_manager_visible_package_metadata():
    """The live dev install should report the same pack version as the source.

    ComfyUI Manager reads package metadata from ``pyproject.toml``. If dev-sync
    skips it, the code can be current while the UI still reports an older
    installed version.
    """
    assert "pyproject.toml" in sync_to_dev.RUNTIME_PATHS