"""Tests de pixeloe_downscale — tolerantes al entorno. El venv del registry NO trae `pixeloe`, asi que estas pruebas ejercitan el "bridge" de interprete (subprocess al python de ComfyUI, que si la tiene). Si tampoco hay ningun interprete con pixeloe disponible, la funcion debe degradar limpiamente: ok=False con error no vacio y SIN lanzar excepcion. Por eso cada test PASA en los dos escenarios: - pixeloe disponible (inproc o via bridge): assert sobre el resultado real. - pixeloe ausente en todos lados: assert sobre la degradacion no-throw. Asi la suite es verde tanto en este PC (ComfyUI presente) como en uno sin ComfyUI, y el contrato "no-throw" queda cubierto en ambos. """ import os import sys import numpy as np from PIL import Image sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from ml.pixeloe_downscale import pixeloe_downscale # noqa: E402 def _shapes_png(path, w=256, h=256): """PNG 256x256 RGB con un gradiente + formas (contraste con silueta clara).""" yy, xx = np.mgrid[0:h, 0:w] arr = np.zeros((h, w, 3), dtype=np.uint8) arr[..., 0] = (xx * 255 // max(1, w - 1)).astype(np.uint8) # gradiente rojo arr[..., 1] = (yy * 255 // max(1, h - 1)).astype(np.uint8) # gradiente verde # Bloque azul central: borde duro para que el modo "contrast" tenga silueta. arr[h // 4:3 * h // 4, w // 4:3 * w // 4, 2] = 255 Image.fromarray(arr, "RGB").save(path) return path def test_golden_downscale_64_or_clean_degrade(tmp_path): """Golden: 256x256 -> grid 64x64 (no_upscale). Si pixeloe no esta -> ok=False limpio.""" src = _shapes_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "grid64.png") res = pixeloe_downscale(src, dst, target_size=64, no_upscale=True) assert isinstance(res, dict) if res["ok"]: assert os.path.isfile(dst) assert res["size"] == [64, 64] # no_upscale=True -> grid real assert res["error"] == "" assert res["via"] in ("inproc", "bridge") assert res["mode"] == "contrast" assert res["target_size"] == 64 else: # Degradacion limpia: sin pixeloe en ningun interprete. assert res["error"] != "" assert res["via"] in ("", "bridge", "inproc") def test_edge_target_size_32(tmp_path): """Edge: grid de 32 (iconos). size==[32,32] cuando pixeloe esta presente.""" src = _shapes_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "grid32.png") res = pixeloe_downscale(src, dst, target_size=32, no_upscale=True) if res["ok"]: assert res["size"] == [32, 32] assert res["target_size"] == 32 assert os.path.isfile(dst) else: assert res["error"] != "" def test_edge_mode_nearest_no_color_matching(tmp_path): """Edge: otro modo + color_matching off; debe seguir produciendo el grid o degradar.""" src = _shapes_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "near.png") res = pixeloe_downscale( src, dst, mode="nearest", target_size=64, color_matching=False, no_upscale=True, ) assert isinstance(res, dict) if res["ok"]: assert res["mode"] == "nearest" assert res["size"] == [64, 64] else: assert res["error"] != "" def test_error_missing_src_no_throw(tmp_path): """Error path: src inexistente -> ok=False, error explica, sin excepcion.""" res = pixeloe_downscale( str(tmp_path / "nope.png"), str(tmp_path / "o.png"), target_size=64, ) assert res["ok"] is False assert "no existe" in res["error"] assert res["size"] == [0, 0] def test_error_no_interpreter_with_pixeloe(tmp_path): """Error path: forzar comfy_python invalido cuando el actual no tiene pixeloe. Si el interprete que corre el test YA tiene pixeloe (inproc), el comfy_python invalido se ignora y la llamada puede salir ok=True; el test sigue siendo valido (no-throw). Si NO lo tiene, no hay ningun interprete con pixeloe y debe devolver ok=False con error, nunca lanzar. """ src = _shapes_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "o.png") res = pixeloe_downscale( src, dst, target_size=64, comfy_python="/no/such/python-interpreter", ) assert isinstance(res, dict) try: import pixeloe # noqa: F401 has_local = True except Exception: # noqa: BLE001 has_local = False if has_local: # pixeloe en el interprete del test -> ruta inproc, comfy_python ignorado. assert res["ok"] is True else: # comfy_python invalido + env vacio: si ~/ComfyUI/.venv existe, puede # bridgear y salir ok; si no, ok=False con error. Ambos no-throw. assert res["ok"] in (True, False) if not res["ok"]: assert res["error"] != ""