qwen-image-editor / tests /test_models.py
techfreakworm's picture
feat: build Qwen Image Editor app (Edit/Compose tabs, Fast/Quality, ZeroGPU + local CUDA) with tests
d713f9b
Raw
History Blame Contribute Delete
7.63 kB
"""Tests for models.py — constants, env helpers, fit_dimensions, scheduler config."""
from __future__ import annotations
import importlib.util
import math
import pytest
from PIL import Image
import models
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
def test_model_id():
assert models.MODEL_ID == "Qwen/Qwen-Image-Edit-2511"
def test_lora_repo():
assert models.LORA_REPO == "lightx2v/Qwen-Image-Edit-2511-Lightning"
def test_lora_file():
assert models.LORA_FILE == "Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors"
def test_lora_adapter_name():
assert models.LORA_ADAPTER_NAME == "lightning"
# ---------------------------------------------------------------------------
# LIGHTNING_SCHEDULER_CONFIG
# ---------------------------------------------------------------------------
def test_scheduler_config_time_shift_type():
assert models.LIGHTNING_SCHEDULER_CONFIG["time_shift_type"] == "exponential"
def test_scheduler_config_base_shift():
assert models.LIGHTNING_SCHEDULER_CONFIG["base_shift"] == math.log(3)
def test_scheduler_config_max_shift():
assert models.LIGHTNING_SCHEDULER_CONFIG["max_shift"] == math.log(3)
def test_scheduler_config_use_dynamic_shifting():
assert models.LIGHTNING_SCHEDULER_CONFIG["use_dynamic_shifting"] is True
def test_scheduler_config_has_required_keys():
required = {
"base_image_seq_len",
"base_shift",
"invert_sigmas",
"max_image_seq_len",
"max_shift",
"num_train_timesteps",
"shift",
"shift_terminal",
"stochastic_sampling",
"time_shift_type",
"use_beta_sigmas",
"use_dynamic_shifting",
"use_exponential_sigmas",
"use_karras_sigmas",
}
assert required.issubset(models.LIGHTNING_SCHEDULER_CONFIG.keys())
# ---------------------------------------------------------------------------
# on_spaces
# ---------------------------------------------------------------------------
def test_on_spaces_false_when_env_absent(monkeypatch):
monkeypatch.delenv("SPACES_ZERO_GPU", raising=False)
assert models.on_spaces() is False
def test_on_spaces_true_when_env_set(monkeypatch):
monkeypatch.setenv("SPACES_ZERO_GPU", "1")
assert models.on_spaces() is True
def test_on_spaces_true_for_nonempty_string(monkeypatch):
monkeypatch.setenv("SPACES_ZERO_GPU", "true")
assert models.on_spaces() is True
def test_on_spaces_false_for_empty_string(monkeypatch):
monkeypatch.setenv("SPACES_ZERO_GPU", "")
assert models.on_spaces() is False
# ---------------------------------------------------------------------------
# fit_dimensions helpers
# ---------------------------------------------------------------------------
def _img(w: int, h: int) -> Image.Image:
return Image.new("RGB", (w, h))
# --- multiples of 16 ---
def test_fit_dimensions_landscape_multiple_of_16():
w, h = models.fit_dimensions(_img(1920, 1080))
assert w % 16 == 0
assert h % 16 == 0
def test_fit_dimensions_portrait_multiple_of_16():
w, h = models.fit_dimensions(_img(1080, 1920))
assert w % 16 == 0
assert h % 16 == 0
def test_fit_dimensions_square_multiple_of_16():
w, h = models.fit_dimensions(_img(2000, 2000))
assert w % 16 == 0
assert h % 16 == 0
# --- area cap ---
def test_fit_dimensions_area_cap_landscape():
w, h = models.fit_dimensions(_img(1920, 1080))
assert w * h <= 1024 * 1024
def test_fit_dimensions_area_cap_portrait():
w, h = models.fit_dimensions(_img(1080, 1920))
assert w * h <= 1024 * 1024
def test_fit_dimensions_area_cap_large_square():
w, h = models.fit_dimensions(_img(2048, 2048))
assert w * h <= 1024 * 1024
def test_fit_dimensions_custom_max_pixels():
w, h = models.fit_dimensions(_img(1024, 1024), max_pixels=512 * 512)
assert w * h <= 512 * 512
# --- minimum 256 ---
def test_fit_dimensions_min_256_tiny_image():
w, h = models.fit_dimensions(_img(32, 32))
assert w >= 256
assert h >= 256
def test_fit_dimensions_min_256_small_image():
w, h = models.fit_dimensions(_img(100, 100))
assert w >= 256
assert h >= 256
def test_fit_dimensions_min_256_thin_portrait():
# Thin portrait: short side would be tiny without min clamp
w, h = models.fit_dimensions(_img(64, 512))
assert w >= 256
assert h >= 256
# --- aspect ratio within tolerance ---
def test_fit_dimensions_aspect_landscape_within_5pct():
original_ratio = 1920 / 1080
w, h = models.fit_dimensions(_img(1920, 1080))
new_ratio = w / h
assert abs(new_ratio - original_ratio) / original_ratio < 0.05
def test_fit_dimensions_aspect_portrait_within_5pct():
original_ratio = 1080 / 1920
w, h = models.fit_dimensions(_img(1080, 1920))
new_ratio = w / h
assert abs(new_ratio - original_ratio) / original_ratio < 0.05
def test_fit_dimensions_aspect_wide_within_5pct():
# 16:9 wide variant
original_ratio = 1280 / 720
w, h = models.fit_dimensions(_img(1280, 720))
new_ratio = w / h
assert abs(new_ratio - original_ratio) / original_ratio < 0.05
# --- no-op when image already fits ---
def test_fit_dimensions_no_change_when_already_fits():
# 768x1024 = 786432 < 1048576, already multiples of 16, both >= 256
w, h = models.fit_dimensions(_img(768, 1024))
assert w == 768
assert h == 1024
def test_fit_dimensions_exact_max_pixels():
# Exactly 1024x1024 = max_pixels — no scaling, no rounding needed
w, h = models.fit_dimensions(_img(1024, 1024))
assert w == 1024
assert h == 1024
# --- custom multiple ---
def test_fit_dimensions_custom_multiple():
w, h = models.fit_dimensions(_img(1920, 1080), multiple=32)
assert w % 32 == 0
assert h % 32 == 0
# ---------------------------------------------------------------------------
# auto_device — requires torch; skip gracefully when absent
# ---------------------------------------------------------------------------
@pytest.mark.skipif(importlib.util.find_spec("torch") is None, reason="torch not installed")
def test_auto_device_returns_valid_device():
device = models.auto_device()
assert device in ("cuda", "mps", "cpu")
# ---------------------------------------------------------------------------
# should_cpu_offload — basic logic tests (no real GPU needed)
# ---------------------------------------------------------------------------
def test_should_cpu_offload_false_on_spaces(monkeypatch):
monkeypatch.setenv("SPACES_ZERO_GPU", "1")
assert models.should_cpu_offload("cuda") is False
def test_should_cpu_offload_false_for_mps(monkeypatch):
monkeypatch.delenv("SPACES_ZERO_GPU", raising=False)
assert models.should_cpu_offload("mps") is False
def test_should_cpu_offload_false_for_cpu(monkeypatch):
monkeypatch.delenv("SPACES_ZERO_GPU", raising=False)
assert models.should_cpu_offload("cpu") is False
def test_should_cpu_offload_false_when_torch_unavailable(monkeypatch):
monkeypatch.delenv("SPACES_ZERO_GPU", raising=False)
# Simulate torch raising on cuda check by patching the function's import
import sys
original = sys.modules.get("torch")
sys.modules["torch"] = None # type: ignore[assignment]
try:
result = models.should_cpu_offload("cuda")
finally:
if original is None:
sys.modules.pop("torch", None)
else:
sys.modules["torch"] = original
assert result is False