Spaces:
Sleeping
Sleeping
| """ | |
| 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 "<script>" 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 | |