File size: 6,298 Bytes
66ee87e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21c5089
66ee87e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21c5089
66ee87e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43c3b3c
 
 
 
 
 
 
 
 
 
 
21c5089
 
 
 
 
 
 
 
 
 
66ee87e
 
 
 
 
 
abdbb98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""The Hugging Face Space packaging: front matter, requirements, entry point and LFS rules."""
import re
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent


def front_matter():
    text = (ROOT / "README.md").read_text(encoding="utf-8")
    m = re.match(r"---\n(.*?)\n---\n", text, re.S)
    if not m:
        raise AssertionError("README.md must start with YAML front matter for the Space")
    out = {}
    for line in m.group(1).splitlines():
        k, _, v = line.partition(":")
        out[k.strip()] = v.strip().strip('"')
    return out


def pins(path):
    out = {}
    for line in (ROOT / path).read_text().splitlines():
        line = line.split("#", 1)[0].strip()
        if line and not line.startswith("-"):
            name, _, version = line.partition("==")
            out[re.sub(r"\[.*\]", "", name).lower().replace("_", "-")] = version
    return out


@unittest.skipUnless((ROOT / "Dockerfile").is_file(), "packaging files are not in the image")
class SpacePackagingTest(unittest.TestCase):
    def test_front_matter_declares_a_gradio_space(self):
        fm = front_matter()
        self.assertEqual((fm["sdk"], fm["app_file"]), ("gradio", "app.py"))                 # Hugging Face's default name
        self.assertIn(fm["python_version"], ("3.10", "3.12"))                 # the Pythons ZeroGPU supports
        self.assertTrue((ROOT / fm["app_file"]).is_file())
        self.assertEqual(fm["title"], "DecisionLab")

    def test_sdk_version_matches_the_gradio_pin(self):
        self.assertEqual(front_matter()["sdk_version"], pins("requirements.txt")["gradio"])

    def test_model_stack_is_pinned_exactly(self):
        req = pins("requirements.txt")
        for name in ("torch", "transformers", "safetensors", "tokenizers", "huggingface-hub", "laya", "numpy", "gradio"):
            with self.subTest(package=name):
                self.assertRegex(req.get(name, ""), r"^\d+\.\d+")

    def test_torch_is_a_zerogpu_supported_cuda_build(self):
        """ZeroGPU's configuration check (2026-09-28): 2.13.0, 2.12.1, 2.11.0, 2.10.0, 2.9.1, 2.8.0."""
        self.assertIn(pins("requirements.txt")["torch"], ("2.13.0", "2.12.1", "2.11.0", "2.10.0", "2.9.1", "2.8.0"))
        self.assertNotIn("download.pytorch.org/whl/cpu", (ROOT / "requirements.txt").read_text())

    def test_spaces_package_is_required(self):
        self.assertIn("spaces", pins("requirements.txt"))

    def test_spaces_is_imported_before_anything_else(self):
        import ast
        tree = ast.parse((ROOT / "app.py").read_text(encoding="utf-8"))
        imports = [n for n in tree.body if isinstance(n, (ast.Import, ast.ImportFrom)) and
                   not (isinstance(n, ast.ImportFrom) and n.module == "__future__")]
        first = imports[0]
        self.assertEqual(first.names[0].name if isinstance(first, ast.Import) else first.module, "spaces")

    def test_web_stack_is_left_to_gradio(self):
        req = pins("requirements.txt")
        for name in ("fastapi", "starlette", "pydantic", "uvicorn"):
            with self.subTest(package=name):
                self.assertNotIn(name, req)

    def test_model_versions_match_the_container(self):
        space, container = pins("requirements.txt"), pins("constraints.txt")
        for name in ("transformers", "safetensors", "tokenizers", "huggingface-hub", "laya", "numpy"):
            with self.subTest(package=name):
                self.assertEqual(space[name], container[name])

    def test_weights_go_through_git_lfs(self):
        attrs = (ROOT / ".gitattributes").read_text()
        for pattern in ("*.safetensors", "tokenizer.json"):
            with self.subTest(pattern=pattern):
                self.assertRegex(attrs, rf"(?m)^{re.escape(pattern)} filter=lfs diff=lfs merge=lfs -text$")

    def test_the_space_starts_through_gradio_launch(self):
        """ZeroGPU's startup step runs inside Gradio's Blocks.launch(); uvicorn + mount_gradio_app skips it
        ("No @spaces.GPU function detected during startup", 2026-09-30)."""
        import ast
        tree = ast.parse((ROOT / "app.py").read_text(encoding="utf-8"))
        calls = {n.func.attr for n in ast.walk(tree) if isinstance(n, ast.Call) and isinstance(n.func, ast.Attribute)}
        imported = {a.name for n in ast.walk(tree) if isinstance(n, ast.Import) for a in n.names}
        self.assertIn("launch", calls)
        self.assertNotIn("mount_gradio_app", calls)
        self.assertNotIn("uvicorn", imported)

    def test_no_leftover_space_py(self):
        self.assertFalse((ROOT / "space.py").exists())

    def test_app_py_imports_the_app_package_not_itself(self):
        import subprocess, sys
        r = subprocess.run([sys.executable, "-c", "import sys; sys.path.insert(0, '.'); import app.validation; print(app.validation.__file__)"],
                           cwd=ROOT, capture_output=True, text=True)
        self.assertEqual(r.returncode, 0, r.stderr)
        self.assertIn("app", r.stdout)

    def test_models_folder_exists_for_local_models(self):
        self.assertTrue((ROOT / "models").is_dir())


if __name__ == "__main__":
    unittest.main()


@unittest.skipUnless((ROOT / "Dockerfile").is_file(), "packaging files are not in the image")
class ModelsFolderReachesTheSpaceTest(unittest.TestCase):
    """A Gradio Space is built with Docker from this repo, so .dockerignore applies to it. Excluding models/ there
    kept the Space's local models out of its image (2.2.0, 2026-09-30)."""

    def ignored(self):
        return [l.strip() for l in (ROOT / ".dockerignore").read_text().splitlines() if l.strip() and not l.startswith("#")]

    def test_dockerignore_does_not_exclude_the_models_folder(self):
        for pattern in self.ignored():
            with self.subTest(pattern=pattern):
                self.assertFalse(pattern.rstrip("/").lstrip("./") in ("models", "models/*", "models/**"))

    def test_the_container_image_still_leaves_models_out(self):
        copies = [l for l in (ROOT / "Dockerfile").read_text().splitlines() if l.startswith(("COPY", "ADD"))]
        for line in copies:
            with self.subTest(line=line):
                self.assertNotRegex(line, r"^(COPY|ADD)\s+(--\S+\s+)*\.\s")      # no "COPY . ..."
                self.assertNotIn("models", line)