blackriver-gguf-tools / test_app.py
AI-Joe-git's picture
Add legacy mtmd pipeline: LLaVA, MiniCPM-V, GLM-Edge surgery+convert support
910246d verified
Raw
History Blame Contribute Delete
10.1 kB
"""
Tests for BlackRiver GGUF Tools.
Run: python -m pytest test_app.py -v
"""
import os
import sys
import json
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Mock Gradio before importing app
mock_gr = MagicMock()
mock_gr.OAuthToken = MagicMock()
mock_gr.Progress = MagicMock()
mock_gr.Error = Exception
mock_gr.Interface = MagicMock()
mock_gr.Blocks = MagicMock()
mock_gr.Markdown = MagicMock()
mock_gr.HTML = MagicMock()
mock_gr.Dropdown = MagicMock()
mock_gr.Checkbox = MagicMock()
mock_gr.File = MagicMock()
mock_gr.Number = MagicMock()
mock_gr.Textbox = MagicMock()
mock_gr.Image = MagicMock()
mock_gr.Row = MagicMock()
mock_gr.Column = MagicMock()
mock_gr.Group = MagicMock()
mock_gr.LoginButton = MagicMock()
mock_gr.Button = MagicMock()
mock_gr.update = MagicMock()
mock_gr.themes = MagicMock()
mock_gr.themes.Base = MagicMock(return_value=MagicMock())
sys.modules['gradio'] = mock_gr
sys.modules['gradio_huggingfacehub_search'] = MagicMock()
sys.modules['huggingface_hub'] = MagicMock()
sys.modules['apscheduler'] = MagicMock()
sys.modules['apscheduler.schedulers'] = MagicMock()
sys.modules['apscheduler.schedulers.background'] = MagicMock()
# Now import app
import app
class TestDetectMultimodal:
def test_text_only_model(self, tmp_path):
config = {"architectures": ["LlamaForCausalLM"], "model_type": "llama"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is False
assert "No multimodal indicators" in result["reason"]
def test_vision_model_vision_config(self, tmp_path):
config = {
"architectures": ["LlavaForConditionalGeneration"],
"vision_config": {"hidden_size": 768},
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
assert "vision_config present" in result["reason"]
def test_vision_model_image_token_ids(self, tmp_path):
config = {
"architectures": ["Gemma3ForConditionalGeneration"],
"image_token_id": 256000,
"vision_start_token_id": 255999,
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_vision_model_text_config(self, tmp_path):
config = {
"architectures": ["Qwen2VLForConditionalGeneration"],
"text_config": {"vision_config": {"hidden_size": 1024}},
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_vision_model_mm_projector(self, tmp_path):
config = {
"architectures": ["MiniCPMV"],
"mm_projector": "mlp2x_gelu",
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_vision_model_mm_vision_tower(self, tmp_path):
config = {
"architectures": ["BailingMoe2ForConditionalGeneration"],
"mm_vision_tower": "openai/clip-vit-large-patch14-336",
}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["mmproj_supported"] is True
def test_missing_config(self, tmp_path):
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is False
assert "No config.json" in result["reason"]
def test_invalid_config(self, tmp_path):
(tmp_path / "config.json").write_text("not json")
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is False
assert "Failed to parse" in result["reason"]
def test_preprocessor_detection(self, tmp_path):
config = {"architectures": ["LlavaForConditionalGeneration"]}
(tmp_path / "config.json").write_text(json.dumps(config))
pp = {"image_processor_type": "CLIPImageProcessor", "size": 336}
(tmp_path / "preprocessor_config.json").write_text(json.dumps(pp))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
def test_legacy_llava_detection(self, tmp_path):
config = {"architectures": ["LlavaLlamaForCausalLM"], "model_type": "llava"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["method"] == "legacy"
assert result["legacy_key"] == "llava"
def test_legacy_minicpmv_detection(self, tmp_path):
config = {"architectures": ["MiniCPMVForCausalLM"], "model_type": "minicpmv"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["method"] == "legacy"
assert result["legacy_key"] == "minicpmv"
def test_legacy_glmedge_detection(self, tmp_path):
config = {"architectures": ["GlmEdgeForCausalLM"], "model_type": "glm-edge"}
(tmp_path / "config.json").write_text(json.dumps(config))
result = app.detect_multimodal(str(tmp_path))
assert result["is_multimodal"] is True
assert result["method"] == "legacy"
assert result["legacy_key"] == "glmedge"
class TestSanitizeName:
def test_simple(self):
assert app._sanitize_name("hello-world") == "hello-world"
def test_spaces(self):
assert app._sanitize_name("hello world") == "hello_world"
def test_special_chars(self):
assert app._sanitize_name("model@v1.0!") == "model_v1.0_"
class TestEscape:
def test_html_chars(self):
result = app.escape("<script>alert('xss')</script>")
assert "<script>" not in result
assert "&lt;script&gt;" in result
def test_newlines(self):
result = app.escape("line1\nline2")
assert "<br/>" in result
class TestGenerateMmproj:
def test_non_vision_model_skips(self, tmp_path):
config = {"architectures": ["LlamaForCausalLM"]}
(tmp_path / "config.json").write_text(json.dumps(config))
path, ok, msg = app.generate_mmproj(str(tmp_path), str(tmp_path), "test-model")
assert path is None
assert ok is True
assert "Not a vision model" in msg
def test_missing_config(self, tmp_path):
path, ok, msg = app.generate_mmproj(str(tmp_path), str(tmp_path), "test-model")
assert path is None
assert ok is True
class TestThemeCss:
def test_css_contains_design_tokens(self):
assert "--br-bg" in app.BLACKRIVER_CSS
assert "--br-surface-1" in app.BLACKRIVER_CSS
assert "--br-surface-2" in app.BLACKRIVER_CSS
assert "--br-accent" in app.BLACKRIVER_CSS
assert "--br-text" in app.BLACKRIVER_CSS
assert "--br-text-secondary" in app.BLACKRIVER_CSS
assert "--br-text-muted" in app.BLACKRIVER_CSS
assert "--br-border" in app.BLACKRIVER_CSS
assert "--br-error" in app.BLACKRIVER_CSS
assert "--br-success" in app.BLACKRIVER_CSS
def test_css_has_dropdown_overlay_fix(self):
assert "body > .options" in app.BLACKRIVER_CSS
assert "body > [role=\"listbox\"]" in app.BLACKRIVER_CSS
def test_css_has_focus_rings(self):
assert "focus-visible" in app.BLACKRIVER_CSS
def test_css_has_disabled_states(self):
assert ":disabled" in app.BLACKRIVER_CSS
def test_css_has_reduced_motion(self):
assert "prefers-reduced-motion" in app.BLACKRIVER_CSS
def test_css_has_autocomplete_styles(self):
assert "gr-huggingfacehub-search" in app.BLACKRIVER_CSS
def test_css_no_white_backgrounds(self):
css = app.BLACKRIVER_CSS
assert "#fff" not in css.lower()
assert "#ffffff" not in css.lower()
assert "background: white" not in css.lower()
assert "background-color: white" not in css.lower()
class TestRunHelper:
def test_run_returns_completed_process(self):
import platform
if platform.system() == "Windows":
result = app._run(["cmd", "/c", "echo", "hello"], timeout=5)
else:
result = app._run(["echo", "hello"], timeout=5)
assert result.returncode == 0
assert "hello" in result.stdout
def test_run_timeout(self):
import platform
if platform.system() == "Windows":
result = app._run(["cmd", "/c", "timeout", "/t", "5", "/nobreak"], timeout=1)
assert result.returncode != 0
else:
try:
app._run(["sleep", "10"], timeout=1)
assert False, "Should have raised"
except Exception as e:
assert "timed out" in str(e).lower()
class TestUploadFileList:
def test_normal_upload_includes_model_and_readme(self):
files = ["model-q4_k_m.gguf", "README.md"]
assert "model-q4_k_m.gguf" in files
assert "README.md" in files
def test_split_upload_includes_shards(self):
files = ["model-q4_k_m-00001-of-00003.gguf",
"model-q4_k_m-00002-of-00003.gguf",
"model-q4_k_m-00003-of-00003.gguf",
"README.md"]
assert len([f for f in files if f.endswith(".gguf")]) == 3
def test_mmproj_included_when_present(self):
files = ["model-q4_k_m.gguf", "mmproj-model-f32.gguf", "README.md"]
assert "mmproj-model-f32.gguf" in files