Phase 3 of the modernization plan: move the framework-free math out of the comfy-coupled modules so Tier-0 tests can run on plain Linux without ComfyUI, coremltools, or python_coreml_stable_diffusion. New pure-core package (no comfy / coreml / mps imports): - coreml_suite.core.latents: chunk_batch, merge_chunks - coreml_suite.core.controlnet: expand_inputs, no_control, extract_residual_kwargs, chunk_control - coreml_suite.core.inputs: CoreMLInputs (chunks + coreml_kwargs) - coreml_suite.core.sdxl: is_sdxl / is_sdxl_base / is_sdxl_refiner, build_sdxl_time_ids (base len 6, refiner len 5), build_sdxl_text_embeds, sdxl_model_function_wrapper - coreml_suite.core.naming: compose_out_name, lora_names_from_params Thin adapters keep the public import paths: - coreml_suite.latents / coreml_suite.controlnet: re-export from core - coreml_suite.models: CoreMLModelWrapper, CoreMLModelWrapperLCM, add_sdxl_model_options (now uses the pure builders from core.sdxl), get_latent_image, get_model_patcher remain framework-coupled - coreml_suite.nodes: CoreMLConverter.convert now delegates the out_name composition to core.naming.compose_out_name Test infra: - tests/unit/* re-pointed at coreml_suite.core.* - test_chunks.py dropped `from comfy.model_management import ...` and the dead `model_config` fixture (Phase 1 left it broken; Phase 3 removes it entirely) - test_characterization_sdxl_options now targets the pure builders directly via inspect.getclosurevars on the wrapper closure - test_characterization_out_name now calls compose_out_name without the heavy CoreMLConverter monkey-patching that Phase 2 needed - tests/unit/test_tier0_purity.py: new gate that fails if comfy / coremltools / etc leak into sys.modules during a pure `-m unit` run (skipped in mixed runs where m2 / integration legitimately import them) - tests/__init__.py + top-level conftest.py + pyproject addopts `--import-mode=importlib --confcutdir=tests` together stop pytest from importing the repo-root `__init__.py` (the ComfyUI custom-node entry pulls in comfy) - tests/conftest.py adds tier-aware collect_ignore so `-m unit` skips tests/m2 + tests/integration at collection time Verification: - `pytest -m unit tests/` → 88 passed in ~2s; deterministic across runs - Tier-0 purity gate confirms no comfy/coreml/etc in sys.modules - m2 golden image (Phase 2 anchor) still hashes identical → refactor produced bit-for-bit unchanged output - `git diff main -- __init__.py coreml_suite/nodes.py` shows zero churn to NODE_CLASS_MAPPINGS keys or INPUT_TYPES field names (public workflow contract intact)
146 lines
4.3 KiB
Python
146 lines
4.3 KiB
Python
"""Phase 2 characterization tests, Phase 3 re-pointed.
|
|
|
|
After Phase 3 the .mlpackage filename composition is the pure
|
|
coreml_suite.core.naming.compose_out_name function. CoreMLConverter.convert
|
|
calls it; the previous Phase 2 test had to monkey-patch heavy converter
|
|
internals just to capture the string, which made the test framework-coupled.
|
|
"""
|
|
import pytest
|
|
|
|
from coreml_suite.core.naming import compose_out_name, lora_names_from_params
|
|
|
|
|
|
# ---------- attention suffixes ----------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attn_name,suffix",
|
|
[
|
|
("SPLIT_EINSUM", "se"),
|
|
("SPLIT_EINSUM_V2", "se2"),
|
|
("ORIGINAL", "orig"),
|
|
],
|
|
)
|
|
def test_attention_suffix(attn_name, suffix):
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation=attn_name,
|
|
)
|
|
assert out == f"dreamshaper_8_1x512x512_{suffix}"
|
|
|
|
|
|
# ---------- batch / size ----------------------------------------------------
|
|
|
|
|
|
def test_includes_batch_and_size():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=4, width=768, height=1024,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "dreamshaper_8_4x768x1024_se"
|
|
|
|
|
|
# ---------- ControlNet ------------------------------------------------------
|
|
|
|
|
|
def test_appends_cn_suffix_when_controlnet_support_true():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=True,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "dreamshaper_8_1x512x512_cn_se"
|
|
|
|
|
|
# ---------- ckpt name massage -----------------------------------------------
|
|
|
|
|
|
def test_drops_extension_at_first_period():
|
|
out = compose_out_name(
|
|
ckpt_name="my.checkpoint.v2.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "my_1x512x512_se"
|
|
|
|
|
|
def test_replaces_spaces_with_underscores():
|
|
out = compose_out_name(
|
|
ckpt_name="dream shaper 8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "dream_shaper_8_1x512x512_se"
|
|
|
|
|
|
# ---------- LoRA suffixes ---------------------------------------------------
|
|
|
|
|
|
def test_single_lora():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
lora_names=["epi_noiseoffset.safetensors"],
|
|
)
|
|
assert out == "dreamshaper_8_epi_noiseoffset_1x512x512_se"
|
|
|
|
|
|
def test_multiple_loras_sorted():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
lora_names=["zoom.safetensors", "alpha.safetensors", "moody.safetensors"],
|
|
)
|
|
assert out == "dreamshaper_8_alpha_moody_zoom_1x512x512_se"
|
|
|
|
|
|
def test_lora_plus_controlnet():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=True,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
lora_names=["a.safetensors"],
|
|
)
|
|
assert out == "dreamshaper_8_a_1x512x512_cn_se"
|
|
|
|
|
|
# ---------- sdxl combinations -----------------------------------------------
|
|
|
|
|
|
def test_sdxl_1024_original_gpu():
|
|
out = compose_out_name(
|
|
ckpt_name="sd_xl_base_1.0.safetensors",
|
|
batch_size=1, width=1024, height=1024,
|
|
controlnet_support=False,
|
|
attention_implementation="ORIGINAL",
|
|
)
|
|
assert out == "sd_xl_base_1_1x1024x1024_orig"
|
|
|
|
|
|
# ---------- lora_names_from_params helper ----------------------------------
|
|
|
|
|
|
def test_lora_names_from_params_sorts_by_name():
|
|
names = lora_names_from_params([
|
|
("zebra.safetensors", 1.0),
|
|
("apple.safetensors", 0.5),
|
|
("mango.safetensors", 0.7),
|
|
])
|
|
assert names == ["apple.safetensors", "mango.safetensors", "zebra.safetensors"]
|
|
|
|
|
|
def test_lora_names_from_params_empty_list():
|
|
assert lora_names_from_params([]) == []
|