chore(publication): close pre-integration release blockers

This commit is contained in:
Alexandre Teixeira
2026-10-05 01:37:49 +01:00
parent 3d3aee2093
commit dab660543b
77 changed files with 4074 additions and 83103 deletions
+158
View File
@@ -0,0 +1,158 @@
[
{
"name": "microsoft/Phi-mini-MoE-instruct",
"provider": "test",
"parameter_count": "2B",
"parameters_raw": 2000000000,
"quantization": "F16",
"context_length": 4096,
"release_date": "2026-01-01",
"gguf_sources": []
},
{
"name": "Qwen/Qwen2.5-3B-Instruct-AWQ",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "AWQ-4bit",
"context_length": 4096,
"release_date": "2026-01-02",
"gguf_sources": []
},
{
"name": "Qwen/Qwen2.5-3B-Instruct",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "F16",
"context_length": 4096,
"release_date": "2026-01-03",
"gguf_sources": [
{
"repo": "test/three-GGUF",
"file": "three-Q4_K_M.gguf"
}
]
},
{
"name": "Qwen/Qwen3.5-9B",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "F16",
"context_length": 4096,
"release_date": "2026-01-04",
"gguf_sources": [
{
"repo": "unsloth/Qwen3.5-9B-GGUF",
"file": "Qwen3.5-9B-Q4_K_M.gguf"
}
]
},
{
"name": "Qwen/Qwen3.6-27B",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "F16",
"context_length": 4096,
"release_date": "2026-01-05",
"gguf_sources": [
{
"repo": "unsloth/Qwen3.6-27B-GGUF",
"file": "Qwen3.6-27B-Q4_K_M.gguf"
}
]
},
{
"name": "Qwen/Qwen3.6-35B-A3B",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "F16",
"context_length": 4096,
"release_date": "2026-01-06",
"gguf_sources": [
{
"repo": "unsloth/Qwen3.6-35B-A3B-GGUF",
"file": "Qwen3.6-35B-A3B-UD-Q4_K_M.gguf"
}
]
},
{
"name": "google/gemma-4-12B-it",
"provider": "test",
"parameter_count": "12B",
"parameters_raw": 12000000000,
"quantization": "F16",
"context_length": 4096,
"release_date": "2026-01-07",
"gguf_sources": [
{
"repo": "unsloth/gemma-4-12B-it-GGUF",
"file": "test-Q4_K_M.gguf"
}
]
},
{
"name": "google/gemma-4-12B-it-qat-int4",
"provider": "test",
"parameter_count": "12B",
"parameters_raw": 12000000000,
"quantization": "QAT-INT4",
"context_length": 4096,
"release_date": "2026-01-08",
"gguf_sources": []
},
{
"name": "google/gemma-4-12B-it-qat-int8",
"provider": "test",
"parameter_count": "12B",
"parameters_raw": 12000000000,
"quantization": "QAT-INT8",
"context_length": 4096,
"release_date": "2026-01-09",
"gguf_sources": []
},
{
"name": "test/native-NVFP4",
"provider": "test",
"parameter_count": "12B",
"parameters_raw": 12000000000,
"quantization": "NVFP4",
"context_length": 4096,
"release_date": "2026-01-10",
"gguf_sources": []
},
{
"name": "test/native-FP8",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "FP8",
"context_length": 4096,
"release_date": "2026-01-11",
"gguf_sources": []
},
{
"name": "mlx-community/test-4bit",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "mlx-4bit",
"context_length": 4096,
"release_date": "2026-01-12",
"gguf_sources": []
},
{
"name": "test/standalone-GGUF",
"provider": "test",
"parameter_count": "3B",
"parameters_raw": 3000000000,
"quantization": "Q4_K_M",
"context_length": 4096,
"release_date": null,
"gguf_sources": [],
"is_gguf": true
}
]
+33
View File
@@ -0,0 +1,33 @@
"""Authored ranking inputs; factual identifiers from regression tests are selectors.
All sizes, dates and capabilities are synthetic, not statements about Hub models.
No copied production-catalog rows or descriptions are included.
The fixture is opt-in: a module imports ``publication_catalog`` and requests it
explicitly (``pytestmark = pytest.mark.usefixtures("publication_catalog")`` or
a test argument). It yields the authored input rows so tests can derive their
expectations from the input instead of restating it.
"""
import json
from pathlib import Path
import pytest
FIXTURE_PATH = Path(__file__).parent / "fixtures/hwfit_publication_models.json"
def authored_rows():
return json.loads(FIXTURE_PATH.read_text())
@pytest.fixture
def publication_catalog(monkeypatch):
from services.hwfit import models, hf_discovery
rows = authored_rows()
# Exercise the real merge/normalization path, independent of user caches.
monkeypatch.setattr(models, "_models_cache", None)
monkeypatch.setattr(models, "_load_model_file", lambda path: rows if str(path) == models.model_catalog_path() else [])
monkeypatch.setattr(hf_discovery, "load_cached_hf_collection_models", lambda: [])
monkeypatch.setattr(hf_discovery, "load_cached_mlx_community_models", lambda: [])
yield authored_rows()
+127
View File
@@ -0,0 +1,127 @@
// Behavioral browser checks for the accepted publication surface.
const { chromium } = require('playwright');
const { readFileSync } = require('fs');
const assert = require('node:assert/strict');
(async () => {
const origin = process.env.ODYSSEUS_TEST_STATIC_ORIGIN;
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
const mediaRequests = [];
const errors = [];
page.on('request', req => { if (/\.(webm|mp4)(?:$|\?)/.test(req.url())) mediaRequests.push(req.url()); });
page.on('pageerror', e => errors.push(e.message));
await page.goto(origin + '/website/index.html');
assert.equal(await page.locator('video, .preview-panel, .sec-bg-tint').count(), 0);
assert.equal(await page.locator('.feature-description').count(), 8);
for (const description of await page.locator('.feature-description .desc').allTextContents()) assert(description.trim());
for (const width of [1280, 390]) {
await page.setViewportSize({ width, height: 900 });
assert(await page.locator('.feature-description').first().isVisible());
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth));
}
assert.deepEqual(mediaRequests, []);
assert.deepEqual(errors, []);
const manifest = await (await page.request.get(origin + '/static/manifest.json')).json();
assert.equal(manifest.display, 'standalone');
assert(!manifest.icons);
for (const filename of ['index.html', 'login.html']) {
const html = await (await page.request.get(origin + '/static/' + filename)).text();
assert(!/<link[^>]+rel=["']apple-touch-icon/.test(html));
}
// A small host DOM runs the actual export function and markdown renderer.
await page.route('**/publication-harness', route => route.fulfill({ contentType: 'text/html', body: '<!DOCTYPE html><body><textarea id="doc-editor-textarea"></textarea><select id="doc-language-select"><option value="markdown">Markdown</option><option value="text">Text</option><option value="html">HTML</option></select><div id="theme-grid"></div><select id="theme-font-select"><option value="mono">Fira Code</option><option value="sans">Sans</option></select><select id="theme-density-select"><option value="comfortable">Comfortable</option></select></body>' }));
await page.route('**/api/**', route => route.fulfill({ contentType: 'application/json', body: JSON.stringify({ fonts: {}, value: null }) }));
await page.goto(origin + '/publication-harness');
const source = readFileSync('static/js/document.js', 'utf8');
const fn = source.match(/\n async function exportAsPdf\(\) \{(.*?)\n \}\n/s)[0];
const result = await page.evaluate(async ({ origin, fn }) => {
const module = await import(origin + '/static/js/markdown.js');
const markdownModule = module.default || module;
const _isRichTextLang = lang => lang === 'html';
const _isDocxLang = () => false;
const _richTextExportCss = () => 'p { color: black; }';
const _getExportBaseName = () => 'Print test';
const activeDocId = 'test';
const errors = [];
const uiModule = { showError: message => errors.push(message) };
const originalRender = markdownModule.renderMath;
const prints = [];
markdownModule.renderMath = async container => {
await originalRender(container);
const frame = document.getElementById('doc-browser-print-frame');
frame.contentWindow.print = () => {
prints.push({ body: frame.contentDocument.body.innerHTML, title: frame.contentDocument.title, links: [...frame.contentDocument.querySelectorAll('link')].map(l => l.href) });
frame.contentWindow.dispatchEvent(new Event('afterprint'));
};
};
const run = eval('(' + fn.trim() + ')');
const textarea = document.getElementById('doc-editor-textarea');
textarea.value = 'Formula $E = mc^2$ here.';
await run();
document.getElementById('doc-language-select').value = 'text';
textarea.value = '<script>parent.unsafe = true</script>& literal';
await run();
document.getElementById('doc-language-select').value = 'html';
textarea.value = '<p>Rich content</p><script>parent.unsafe = true</script><img src="data:,x" onerror="parent.unsafe = true">';
await run();
const oldPrint = window.print;
window.print = undefined;
await run();
window.print = oldPrint;
return { prints, errors, unsafe: !!window.unsafe, frameRemaining: !!document.getElementById('doc-browser-print-frame') };
}, { origin, fn });
assert.equal(result.prints.length, 3);
assert(result.prints[0].body.includes('class="katex"'));
assert(!result.prints[0].body.includes('ody-math-pending'));
assert(result.prints[0].links.some(url => url.includes('/katex/')));
assert.equal(result.prints[0].title, 'Print test');
assert(result.prints[1].body.includes('&lt;script&gt;'));
assert(result.prints[2].body.includes('Rich content'));
assert.equal(result.unsafe, false);
assert.equal(result.frameRemaining, false);
assert.deepEqual(result.errors, ['Browser printing is unavailable.']);
// Actual theme module: default, legacy local/server selections and custom theme preferences.
const fonts = await page.evaluate(async origin => {
localStorage.setItem('odysseus-theme', JSON.stringify({ name: 'dark', colors: {}, font: 'gohu' }));
localStorage.setItem('odysseus-custom-themes', JSON.stringify({ old: { font: 'GohuFont' } }));
const theme = await import(origin + '/static/js/theme.js');
theme.applyFontDensity(null, null);
const fallback = document.documentElement.style.getPropertyValue('--font-family');
theme.applyFontDensity('gohu', null);
const legacy = document.documentElement.style.getPropertyValue('--font-family');
theme.initThemeUI();
const selected = document.getElementById('theme-font-select').value;
const saved = theme.getSaved().font;
const custom = theme.getCustomThemes().old.font;
theme.applyFontDensity('sans', null);
const sans = document.documentElement.style.getPropertyValue('--font-family');
return { fallback, legacy, selected, saved, custom, sans };
}, origin);
assert.equal(fonts.fallback, "'Fira Code', monospace");
assert.equal(fonts.legacy, fonts.fallback);
assert.equal(fonts.selected, 'mono');
assert.equal(fonts.saved, 'mono');
assert.equal(fonts.custom, 'mono');
assert(fonts.sans.includes('system-ui'));
// Verify the real early bootstrap handles persisted legacy preferences.
const appHtml = readFileSync('static/index.html', 'utf8');
const bootstrap = [...appHtml.matchAll(/<script(?:\s[^>]*)?>([\s\S]*?)<\/script>/g)].map(m => m[1]).find(code => code.includes('Apply font early'));
assert(bootstrap);
const early = await page.evaluate(async code => {
const theme = await import('/static/js/theme.js');
localStorage.setItem('odysseus-theme', JSON.stringify({ name: 'dark', colors: theme.THEMES.dark, font: 'gohu' }));
eval(code);
return document.documentElement.style.getPropertyValue('--font-family');
}, bootstrap);
assert.equal(early, fonts.fallback);
console.log(JSON.stringify({ website: true, fonts: true, print: true, pwa: true }));
} finally {
await browser.close();
}
})().catch(e => { console.error(e); process.exit(1); });
+1 -1
View File
@@ -89,7 +89,7 @@ a status code. `scripts/odysseus-smoke --areas` prints the current list.
| Email | the fixture inbox lists, opens with its body, and the unread count drops on mark-read |
| Memory | a fact is listed, found by search, and gone after delete |
| Uploads | an attachment reads back byte for byte |
| Cookbook | hardware is detected and recommendations come back sized against it; state persists |
| Cookbook | hardware is detected; a fresh install's recommendation request returns the explicit empty-catalog Rescan guidance; state persists |
| Settings | a preference written on one session is still there after a new login |
## What is not covered, and why
+32 -8
View File
@@ -1,12 +1,22 @@
"""Cookbook: hardware is detected and the recommendations are sized against it.
"""Cookbook: hardware is detected and a fresh install says how to get a catalog.
What the README advertises here is hardware-aware recommendation, and
that is exactly the part that runs offline. Downloading and serving a
model is left to the gap list: it needs tmux, a GPU runtime and several
gigabytes over the network.
No model catalog ships with the app: the bundled lists are empty, and
rows only arrive when Rescan sends `refresh_catalog` while online. So a
fresh data dir that was never refreshed must answer a recommendation
request with an explicit empty-catalog message, not an empty table or a
server error. Ranking itself is covered by the unit tests against an
authored fixture catalog. Downloading and serving a model is left to the
gap list: it needs tmux, a GPU runtime and several gigabytes over the
network.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from src.constants import DATA_DIR
SYSTEM_PATH = "/api/hwfit/system"
MODELS_PATH = "/api/hwfit/models"
STATE_PATH = "/api/cookbook/state"
@@ -14,6 +24,10 @@ GPUS_PATH = "/api/cookbook/gpus"
STATE_MARKER = "odysseusSmokeMarker"
# Everything that can give the instance a catalog: the user catalog and the
# two caches a Rescan writes, all under the instance's data dir.
CATALOG_FILES = ("hf_models.json", "hf_collection_models.json", "mlx_community_models.json")
def test_hardware_is_detected(client):
response = client.get(SYSTEM_PATH)
@@ -28,14 +42,24 @@ def test_hardware_is_detected(client):
assert gpus.json().get("ok") is True, gpus.text
def test_recommendations_fit_the_detected_hardware(client):
def test_fresh_install_explains_how_to_populate_the_catalog(client):
catalog_dir = Path(DATA_DIR) / "hwfit"
present = [name for name in CATALOG_FILES if (catalog_dir / name).exists()]
if present:
pytest.skip(f"not a fresh install: {catalog_dir} already holds {present}")
response = client.get(MODELS_PATH)
assert response.status_code == 200, response.text
body = response.json()
system = body.get("system") or {}
assert system.get("cpu_name"), body
recommended = body.get("models") or body.get("recommendations") or []
assert recommended, f"no model recommendation for this hardware: {list(body)}"
assert body.get("models") == [], body
error = body.get("error") or ""
assert "Model catalog is empty" in error, body
assert "Rescan" in error, body
# A plain request never refreshes, so nothing was fetched behind our back.
assert "catalog_refresh" not in body, body
assert not any((catalog_dir / name).exists() for name in CATALOG_FILES)
def test_cookbook_state_persists(client):
-1
View File
@@ -60,7 +60,6 @@ def test_login_page_static_assets_resolve_under_mount_path():
mounted_login_url = "https://example.test/odysseus/login"
for relative_asset, mounted_path in (
("static/manifest.json", "/odysseus/static/manifest.json"),
("static/icons/icon-192.png", "/odysseus/static/icons/icon-192.png"),
(
"static/fonts/FiraCode-Regular.woff2",
"/odysseus/static/fonts/FiraCode-Regular.woff2",
+6 -8
View File
@@ -1,9 +1,10 @@
"""Repository asset ownership guards for issues #1335 and #6175.
Public Markdown and landing-page media belong in website/, while shared
README/packaging imagery belongs in assets/branding/. Images in either managed
root must be referenced by tracked text, and every tracked website video must
be referenced by the site's entry point.
optional README/packaging imagery belongs in assets/branding/. Retained images
in either managed root must be referenced by tracked text, and every retained
website video must be referenced by the site's entry point. Publication can
operate with no imagery or videos.
"""
import re
import subprocess
@@ -41,7 +42,8 @@ def _tracked(*paths_under):
return None
if out.returncode != 0:
return None
return [REPO / line for line in out.stdout.splitlines() if line.strip()]
return [REPO / line for line in out.stdout.splitlines()
if line.strip() and (REPO / line).is_file()]
def test_no_orphan_documentation_or_branding_images():
@@ -49,9 +51,6 @@ def test_no_orphan_documentation_or_branding_images():
if managed_files is None:
pytest.skip("not a git checkout")
managed_images = [p for p in managed_files if p.suffix.lower() in IMAGE_EXTS]
assert any("assets/branding" in p.as_posix() for p in managed_images), (
"expected assets/branding/ to contain the shared project imagery"
)
# All tracked text we might reference an image from.
all_tracked = _tracked(".") or []
@@ -94,7 +93,6 @@ def test_pages_site_owns_its_entrypoint_and_media():
assert text.startswith("---\nlayout: default\n---\n"), guide
website_videos = [p for p in website_files if p.suffix.lower() in VIDEO_EXTS]
assert website_videos, "expected website/ to contain the landing-page videos"
entrypoint = (REPO / "website/index.html").read_text(encoding="utf-8")
unreferenced = [
+8
View File
@@ -8,9 +8,15 @@ like Apple Silicon (GGUF-only recommendations) while datacenter CDNA and
unknown-family AMD are left untouched, and that CUDA is unchanged.
"""
import pytest
from services.hwfit import hardware
from services.hwfit.fit import rank_models
from services.hwfit.models import get_models
from tests.hwfit_publication_fixtures import publication_catalog # noqa: F401
# Rank authored inputs rather than publication catalog snapshots.
pytestmark = pytest.mark.usefixtures("publication_catalog")
def _rocm_system(family="rdna", ram_gb=32.0, vram_gb=16.0):
@@ -170,6 +176,8 @@ def test_sort_by_newest_orders_by_release_date():
"gpu_family": "rdna", "gpu_count": 1, "available_ram_gb": 22.0, "total_ram_gb": 31.0}
res = rank_models(sys, sort="newest", limit=50)
dated = [r.get("release_date") for r in res if r.get("release_date")]
assert len(set(dated)) > 1, "authored inputs must exercise date ordering"
assert any(not r.get("release_date") for r in res), "authored inputs must include an undated row"
# dates present must be in descending order
assert dated == sorted(dated, reverse=True), "release dates not descending"
# any undated entries must come after all dated ones
+54 -23
View File
@@ -1,5 +1,11 @@
import pytest
from services.hwfit.fit import rank_models
from services.hwfit.models import get_models, is_prequantized
from tests.hwfit_publication_fixtures import publication_catalog # noqa: F401
# Rank authored inputs rather than publication catalog snapshots.
pytestmark = pytest.mark.usefixtures("publication_catalog")
def _8gb_vram_system():
@@ -14,38 +20,63 @@ def _8gb_vram_system():
}
def test_gemma4_12b_in_catalog():
catalog = {m["name"]: m for m in get_models()}
assert "google/gemma-4-12B-it" in catalog, "gemma-4-12B-it missing from catalog"
GEMMA = "google/gemma-4-12B-it"
def test_gemma4_12b_has_gguf_source():
catalog = {m["name"]: m for m in get_models()}
entry = catalog["google/gemma-4-12B-it"]
assert entry.get("gguf_sources"), "gemma-4-12B-it has no gguf_sources"
repos = [s["repo"] for s in entry["gguf_sources"]]
assert "unsloth/gemma-4-12B-it-GGUF" in repos
def _input_row(rows, name):
return next(r for r in rows if r["name"] == name)
def _qat_rows(rows):
return [r for r in rows if r["name"].startswith(GEMMA + "-qat-")]
def test_gemma4_12b_user_catalog_row_wins_the_merge(publication_catalog, monkeypatch):
"""A dynamic cache listing the same repo must not replace or duplicate the
user-catalog row; rows only the cache knows are still merged in."""
from services.hwfit import hf_discovery
monkeypatch.setattr(hf_discovery, "load_cached_hf_collection_models", lambda: [
{"name": GEMMA, "quantization": "F16", "gguf_sources": [{"repo": "test/cache-GGUF", "file": "cache.gguf"}]},
{"name": "test/cache-only", "quantization": "F16", "gguf_sources": []},
])
merged = get_models()
names = [m["name"] for m in merged]
assert names.count(GEMMA) == 1
assert "test/cache-only" in names
entry = next(m for m in merged if m["name"] == GEMMA)
assert entry["gguf_sources"] == _input_row(publication_catalog, GEMMA)["gguf_sources"]
def test_gemma4_12b_ranked_row_carries_its_gguf_source(publication_catalog):
hit = next(r for r in rank_models(_8gb_vram_system(), search="gemma-4-12B-it", limit=20) if r["name"] == GEMMA)
assert hit["gguf_sources"] == _input_row(publication_catalog, GEMMA)["gguf_sources"]
# The F16 input does not fit 8 GB, so the fit falls back to a GGUF quant.
assert hit["quant"] == "Q4_K_M"
def test_gemma4_12b_rank_models_returns_it_for_8gb_vram():
results = rank_models(_8gb_vram_system(), search="gemma-4-12B-it", limit=20)
names = [r["name"] for r in results]
assert "google/gemma-4-12B-it" in names, "rank_models did not return gemma-4-12B-it for 8 GB VRAM"
assert GEMMA in names, "rank_models did not return gemma-4-12B-it for 8 GB VRAM"
def test_gemma4_12b_qat_entries_in_catalog():
def test_gemma4_12b_qat_entries_rank_with_their_native_quant(publication_catalog):
qat = _qat_rows(publication_catalog)
assert len(qat) == 2
ranked = {r["name"]: r for r in rank_models(_8gb_vram_system(), search="gemma-4-12B-it-qat", limit=20)}
for row in qat:
assert ranked[row["name"]]["quant"] == row["quantization"]
def test_gemma4_12b_qat_entries_are_prequantized(publication_catalog):
catalog = {m["name"]: m for m in get_models()}
assert "google/gemma-4-12B-it-qat-int4" in catalog
assert "google/gemma-4-12B-it-qat-int8" in catalog
for row in _qat_rows(publication_catalog):
assert is_prequantized(catalog[row["name"]])
def test_gemma4_12b_qat_entries_are_prequantized():
catalog = {m["name"]: m for m in get_models()}
assert is_prequantized(catalog["google/gemma-4-12B-it-qat-int4"])
assert is_prequantized(catalog["google/gemma-4-12B-it-qat-int8"])
def test_gemma4_12b_qat_entries_have_no_gguf():
catalog = {m["name"]: m for m in get_models()}
assert catalog["google/gemma-4-12B-it-qat-int4"]["gguf_sources"] == []
assert catalog["google/gemma-4-12B-it-qat-int8"]["gguf_sources"] == []
def test_gemma4_12b_qat_entries_have_no_gguf(publication_catalog):
ranked = {r["name"]: r for r in rank_models(_8gb_vram_system(), search="gemma-4-12B-it-qat", limit=20)}
for row in _qat_rows(publication_catalog):
assert ranked[row["name"]]["gguf_sources"] == []
+90 -9
View File
@@ -3,8 +3,33 @@
The handler did `n = int(gpu_count)` with no guard, so `?gpu_count=abc` (or any
non-integer) raised ValueError -> HTTP 500. A malformed count is now ignored,
matching how the neighbouring gpu_group param is already parsed.
The count parsing runs only after the empty-catalog early return, so these
tests rank the authored publication catalog against a fixed two-GPU system and
spy on rank_models to prove the parsing path actually ran.
"""
import functools
from copy import deepcopy
import pytest
from routes.hwfit_routes import setup_hwfit_routes
from tests.hwfit_publication_fixtures import publication_catalog # noqa: F401
SYSTEM = {
"has_gpu": True,
"backend": "cuda",
"gpu_name": "Synthetic GPU",
"gpu_vram_gb": 24.0,
"gpu_count": 2,
"gpus": [
{"index": 0, "name": "Synthetic GPU", "vram_gb": 12.0},
{"index": 1, "name": "Synthetic GPU", "vram_gb": 12.0},
],
"gpu_groups": [{"name": "Synthetic GPU", "vram_each": 12.0, "count": 2, "indices": [0, 1], "vram_total": 24.0}],
"available_ram_gb": 32.0,
"total_ram_gb": 32.0,
}
def _get_models():
@@ -15,24 +40,80 @@ def _get_models():
raise AssertionError("hwfit /models route not found")
def test_non_numeric_gpu_count_does_not_raise():
@pytest.fixture
def ranked_systems(publication_catalog, monkeypatch):
"""Fix detection and record every system the handler hands to rank_models."""
from services.hwfit import fit, hardware
monkeypatch.setattr(hardware, "detect_system", lambda **_: deepcopy(SYSTEM))
calls = []
real_rank = fit.rank_models
@functools.wraps(real_rank)
def spy(system, **kwargs):
calls.append(deepcopy(system))
return real_rank(system, **kwargs)
monkeypatch.setattr(fit, "rank_models", spy)
return calls
def _assert_ranked(result, ranked_systems):
assert isinstance(result, dict)
assert "error" not in result
assert isinstance(result["models"], list) and result["models"]
# Reaching rank_models means the empty-catalog return was not taken and the
# manual-hardware and gpu_count parsing ran first.
assert len(ranked_systems) == 1
assert ranked_systems[0] == result["system"]
return result["system"]
def test_non_numeric_gpu_count_does_not_raise(ranked_systems):
handler = _get_models()
# Previously raised ValueError (HTTP 500); now degrades to a normal ranking.
result = handler(gpu_count="abc")
assert isinstance(result, dict)
system = _assert_ranked(handler(gpu_count="abc"), ranked_systems)
# Ignored like an omitted count: the auto pool keeps both GPUs and no
# explicit-count GPU-only pin is applied.
assert system["detected_gpu_count"] == 2
assert system["active_group"]["use_count"] == 2
assert system["gpu_count"] == 2
assert "gpu_only" not in system
def test_numeric_gpu_count_still_accepted():
def test_numeric_gpu_count_still_accepted(ranked_systems):
handler = _get_models()
result = handler(gpu_count="0")
assert isinstance(result, dict)
system = _assert_ranked(handler(gpu_count="0"), ranked_systems)
# 0 switches to RAM-only ranking.
assert system["detected_gpu_count"] == 2
assert system["has_gpu"] is False
assert system["gpu_count"] == 0
assert system["gpu_only"] is False
assert "active_group" not in system
def test_non_numeric_manual_gpu_count_does_not_raise():
def test_non_numeric_manual_gpu_count_does_not_raise(ranked_systems):
# manual_gpu_count is the other count param on this endpoint (the hardware
# simulator in _apply_manual_hardware). A non-numeric value must also degrade
# (default to 1) rather than 500, so the endpoint's count parsing is fully
# covered.
handler = _get_models()
result = handler(manual_mode="gpu", manual_gpu_count="abc")
assert isinstance(result, dict)
system = _assert_ranked(handler(manual_mode="gpu", manual_gpu_count="abc"), ranked_systems)
assert system["manual_hardware"] is True
assert system["gpu_name"] == "Simulated CUDA GPU"
assert system["gpu_count"] == 1
assert system["gpu_groups"][0]["count"] == 1
assert system["detected_gpu_count"] == 1
def test_empty_catalog_returns_before_count_parsing(ranked_systems, monkeypatch):
# Control: without catalog rows the handler stops before the parsing above,
# which is why the tests in this module must rank the authored catalog.
from services.hwfit import models
monkeypatch.setattr(models, "get_models", lambda: [])
result = _get_models()(gpu_count="abc", manual_mode="gpu", manual_gpu_count="abc")
assert result["models"] == []
assert "Model catalog is empty" in result["error"]
assert ranked_systems == []
assert "detected_gpu_count" not in result["system"]
+17 -10
View File
@@ -6,9 +6,15 @@ guarantee that non-macOS (Linux/Windows) detection is unchanged.
import json
import pytest
from services.hwfit import hardware
from services.hwfit.fit import rank_models
from services.hwfit.models import get_models
from tests.hwfit_publication_fixtures import publication_catalog # noqa: F401
# Rank authored inputs rather than publication catalog snapshots.
pytestmark = pytest.mark.usefixtures("publication_catalog")
def _metal_system(ram_gb=16.0, vram_gb=10.7):
@@ -78,19 +84,20 @@ def test_only_gguf_or_mlx_models_recommended_on_metal():
assert unservable == [], f"{len(unservable)} non-servable models on Metal, e.g. {unservable[:3]}"
def test_qwen_catalog_entries_point_at_verified_gguf_repos():
"""Qwen GGUF-looking Cookbook rows must download GGUF repos, not the base
safetensors repositories."""
catalog = {m["name"]: m for m in get_models()}
def test_qwen_gguf_rows_rank_with_their_gguf_downloads_on_metal(publication_catalog):
"""Qwen GGUF-looking Cookbook rows must download the GGUF repo/file their
catalog row names, not the base safetensors repository."""
expected = {
"Qwen/Qwen3.5-9B": ("unsloth/Qwen3.5-9B-GGUF", "Qwen3.5-9B-Q4_K_M.gguf"),
"Qwen/Qwen3.6-27B": ("unsloth/Qwen3.6-27B-GGUF", "Qwen3.6-27B-Q4_K_M.gguf"),
"Qwen/Qwen3.6-35B-A3B": ("unsloth/Qwen3.6-35B-A3B-GGUF", "Qwen3.6-35B-A3B-UD-Q4_K_M.gguf"),
r["name"]: r["gguf_sources"]
for r in publication_catalog
if r["name"].startswith("Qwen/Qwen3.") and r["gguf_sources"]
}
assert len(expected) == 3
ranked = {r["name"]: r for r in rank_models(_metal_system(), search="Qwen/Qwen3.", limit=50)}
for model_name, (repo, filename) in expected.items():
sources = catalog[model_name].get("gguf_sources") or []
assert any(src.get("repo") == repo and src.get("file") == filename for src in sources)
for model_name, sources in expected.items():
assert ranked[model_name]["gguf_sources"] == sources
assert all(src["repo"] != model_name and src["file"].endswith(".gguf") for src in ranked[model_name]["gguf_sources"])
def test_safetensors_models_still_recommended_on_cuda():
+6
View File
@@ -7,9 +7,15 @@ notably that "metal" is honoured (Apple Silicon is GGUF-only via llama.cpp /
Ollama) instead of being silently coerced to CUDA.
"""
import pytest
from routes.hwfit_routes import _apply_manual_hardware, _MANUAL_BACKENDS
from services.hwfit.fit import rank_models
from services.hwfit.models import get_models
from tests.hwfit_publication_fixtures import publication_catalog # noqa: F401
# Rank authored inputs rather than publication catalog snapshots.
pytestmark = pytest.mark.usefixtures("publication_catalog")
def test_no_manual_mode_leaves_system_untouched():
+10 -4
View File
@@ -1,9 +1,15 @@
import pytest
from services.hwfit.fit import analyze_model, rank_models
from services.hwfit.models import (
get_models,
infer_quantization_from_name,
is_prequantized,
)
from tests.hwfit_publication_fixtures import publication_catalog # noqa: F401
# Rank authored inputs rather than publication catalog snapshots.
pytestmark = pytest.mark.usefixtures("publication_catalog")
def _dual_5060ti_system():
@@ -35,7 +41,7 @@ def test_infers_native_hf_quant_formats_from_repo_names():
def test_nvfp4_catalog_quant_is_preserved():
catalog = {m["name"]: m for m in get_models()}
model = catalog["txn545/Qwen3.5-122B-A10B-NVFP4"]
model = catalog["test/native-NVFP4"]
assert model["quantization"] == "NVFP4"
assert is_prequantized(model)
@@ -43,7 +49,7 @@ def test_nvfp4_catalog_quant_is_preserved():
def test_nvfp4_search_result_is_not_gguf_or_cpu_offload():
catalog = {m["name"]: m for m in get_models()}
model = catalog["txn545/Qwen3.5-122B-A10B-NVFP4"]
model = catalog["test/native-NVFP4"]
fit = analyze_model(model, _dual_5060ti_system())
assert fit["quant"] == "NVFP4"
@@ -51,10 +57,10 @@ def test_nvfp4_search_result_is_not_gguf_or_cpu_offload():
results = rank_models(
_dual_5060ti_system(),
search="Qwen3.5-122B-A10B-NVFP4",
search="native-NVFP4",
limit=10,
)
hit = next(r for r in results if r["name"] == "txn545/Qwen3.5-122B-A10B-NVFP4")
hit = next(r for r in results if r["name"] == "test/native-NVFP4")
assert hit["quant"] == "NVFP4"
assert hit["run_mode"] != "cpu_offload"
+6
View File
@@ -6,8 +6,14 @@ FP8 safetensors repos — must be filtered out on Windows so the Cookbook does
not recommend models the user cannot actually serve.
"""
import pytest
from services.hwfit.fit import rank_models
from services.hwfit.models import get_models
from tests.hwfit_publication_fixtures import publication_catalog # noqa: F401
# Rank authored inputs rather than publication catalog snapshots.
pytestmark = pytest.mark.usefixtures("publication_catalog")
def _windows_system(ram_gb=32.0, vram_gb=16.0):
+3 -3
View File
@@ -368,7 +368,7 @@ def test_detached_container_math_typesets_with_the_real_renderer(node_available)
mdToHtml defers math to a document-scoped flush, which cannot reach a
detached node, so the export has to typeset its own container before
handing it to html2pdf. This is that container: pending spans in, real
handing it to browser printing. This is that container: pending spans in, real
KaTeX markup out, no .katex-error and nothing left pending.
"""
out = _run_node(
@@ -400,7 +400,7 @@ def test_detached_container_math_typesets_with_the_real_renderer(node_available)
assert "ody-math-pending" not in out["written"]
def test_pdf_export_typesets_its_container_before_html2pdf():
def test_pdf_export_typesets_its_container_before_print():
"""Ordering in a call site, so pin the call site. No node needed."""
source = document_source()
match = re.search(r"\n async function exportAsPdf\(\) \{(.*?)\n \}\n", source, re.S)
@@ -410,7 +410,7 @@ def test_pdf_export_typesets_its_container_before_html2pdf():
render = "await markdownModule.renderMath(container);"
assert render in body, "the export never typesets its detached container"
assert body.index("container.innerHTML = html;") < body.index(render)
assert body.index(render) < body.index("window.html2pdf()")
assert body.index(render) < body.index("frame.contentWindow.print()")
def test_md_to_html_renders_inline_once_katex_is_loaded(node_available):
@@ -8,6 +8,6 @@ ROOT = Path(__file__).resolve().parents[1]
def test_pdf_backed_documents_do_not_offer_destructive_html_pdf_export():
source = document_source()
assert "if (!isForm) {" in source
assert "label: _isDocxLang(lang) ? 'Convert to PDF' : 'Print as PDF'" in source
assert "if (!isForm && (_isDocxLang(lang) || typeof window.print === 'function')) {" in source
assert "label: _isDocxLang(lang) ? 'Convert to PDF' : 'Print / save PDF'" in source
assert "destroy the original page layout, images, and form structure" in source
+150
View File
@@ -0,0 +1,150 @@
"""Publication behavior: cold catalogs, server QR, notices, and removed resources."""
import asyncio
import base64
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
ROOT = Path(__file__).resolve().parents[1]
def test_retained_artifacts_match_distributed_provenance_and_notices():
ledger = json.loads((ROOT / 'THIRD_PARTY_PROVENANCE.json').read_text())
assert len(ledger['retained']) == 6
for entry in ledger['retained']:
assert entry['source_records']
assert hashlib.sha256((ROOT / entry['notice']).read_bytes()).hexdigest() == entry['notice_sha256']
for artifact in entry['artifacts']:
data = (ROOT / artifact['path']).read_bytes()
assert len(data) == artifact['size_bytes']
assert hashlib.sha256(data).hexdigest() == artifact['sha256']
def test_removed_resource_ledger_has_no_runtime_references():
ledger = (ROOT / 'PUBLICATION_ASSET_DECISIONS.md').read_text()
removed = re.findall(r'^- SAN-\d+: `([^`]+)`$', ledger, re.M)
assert len(removed) == 21
# Inspect tracked text, including files omitted by parity-audit exclusions.
tracked = subprocess.check_output(['git', 'ls-files', '-z'], cwd=ROOT).decode().split('\0')
for path in removed:
assert not (ROOT / path).exists()
for name in tracked:
if name == "PUBLICATION_ASSET_DECISIONS.md":
continue # Historical ledger entries are intentionally non-resource references.
file = ROOT / name
if not file.is_file():
continue
try:
content = file.read_text()
except UnicodeError:
continue
assert Path(path).name not in content, (path, name)
def test_cold_catalog_and_refresh_use_mutable_data(monkeypatch, tmp_path):
from services.hwfit import models, hf_discovery as discovery
from src import constants
monkeypatch.setattr(constants, 'DATA_DIR', str(tmp_path))
monkeypatch.setattr(models, '_models_cache', None)
monkeypatch.setattr(discovery, 'MLX_COMMUNITY_CACHE', tmp_path / 'hwfit/mlx_community_models.json')
monkeypatch.setattr(discovery, 'HF_COLLECTION_MODELS_CACHE', tmp_path / 'hwfit/hf_collection_models.json')
for file in ['hf_models.json', 'mlx_community_models.json']:
assert (ROOT / 'services/hwfit/data' / file).read_text() == '[]\n'
assert models.get_models() == []
row = {'name': 'test/runtime', 'parameter_count': '3B', 'quantization': 'F16'}
monkeypatch.setattr(discovery, 'fetch_collection_models', lambda source: [dict(row, name='mlx-community/test' if source['mlx_only'] else row['name'])] if source.get('mlx_only') else [row])
assert models.refresh_dynamic_catalogs(force=True) == {'mlx_community': 1, 'hf_collections': 1}
assert {r['name'] for r in models.get_models()} == {'test/runtime', 'mlx-community/test'}
cached = json.loads(discovery.HF_COLLECTION_MODELS_CACHE.read_text())
assert cached['source'] and cached['fetched_at'] and cached['count'] == 1
# Offline cache loading must work without a fetch.
models.reset_model_cache()
monkeypatch.setattr(discovery, 'fetch_collection_models', Mock(side_effect=OSError('offline')))
assert len(models.get_models()) == 2
user_file = Path(models.model_catalog_path())
user_file.write_text(json.dumps([dict(row, name='test/user')]))
models.reset_model_cache()
assert len(models.get_models()) == 3
def test_empty_catalog_route_gives_real_refresh_guidance(monkeypatch):
from services.hwfit import models, hardware
from routes.hwfit_routes import setup_hwfit_routes
monkeypatch.setattr(hardware, 'detect_system', lambda **kwargs: {'backend': 'cpu_x86'})
monkeypatch.setattr(models, 'get_models', lambda: [])
monkeypatch.setattr(models, 'refresh_dynamic_catalogs', Mock(side_effect=OSError('offline')))
endpoint = next(r.endpoint for r in setup_hwfit_routes().routes if r.path.endswith('/models'))
result = endpoint(refresh_catalog=True)
assert result['models'] == []
assert 'Rescan' in result['error'] and 'offline' in result['error']
assert result['catalog_refresh'] == {'error': 'offline'}
def test_2fa_setup_still_generates_a_server_png():
from routes.auth_routes import setup_auth_routes
auth = Mock()
auth.get_username_for_token.return_value = 'alice'
auth.totp_enabled.return_value = False
auth.totp_generate_secret.return_value = 'JBSWY3DPEHPK3PXP'
auth.totp_get_provisioning_uri.return_value = 'otpauth://totp/test:alice?secret=JBSWY3DPEHPK3PXP&issuer=test'
endpoint = next(r.endpoint for r in setup_auth_routes(auth).routes if r.path.endswith('/2fa/setup'))
result = asyncio.run(endpoint(SimpleNamespace(cookies={})))
assert result['uri'].startswith('otpauth://')
prefix, data = result['qr_code'].split(',', 1)
assert prefix == 'data:image/png;base64'
assert base64.b64decode(data).startswith(b'\x89PNG\r\n\x1a\n')
def test_distribution_paths_include_all_notices():
for name in ['Odysseus.spec', 'build-windows-portable.ps1', 'build-macos-app.sh', 'Dockerfile']:
text = (ROOT / name).read_text()
assert 'licenses' in text and 'THIRD_PARTY_PROVENANCE.json' in text and 'ACKNOWLEDGMENTS.md' in text
ignore = (ROOT / '.dockerignore').read_text()
assert '!ACKNOWLEDGMENTS.md' in ignore
manifest = json.loads((ROOT / 'static/manifest.json').read_text())
assert not manifest.get('icons')
assert manifest['display'] == 'standalone'
def test_browser_publication_behaviors():
"""Real Chromium DOM: text-only website, saved fonts, escaped print/math."""
if not shutil.which('node'):
pytest.skip('Node is required')
probe = subprocess.run(['node', '-e', "require.resolve('playwright')"], cwd=ROOT, capture_output=True)
if probe.returncode:
pytest.skip('Playwright is required')
result = subprocess.run(['node', str(ROOT / 'tests/publication_plan_b_browser.cjs')], cwd=ROOT, capture_output=True, text=True, timeout=60)
assert result.returncode == 0, result.stdout + result.stderr
output = json.loads(result.stdout)
assert output == {'website': True, 'fonts': True, 'print': True, 'pwa': True}
def test_service_worker_activation_purges_previous_asset_cache():
result = subprocess.run(['node', '-e', r"""
const fs = require('fs');
const vm = require('vm');
const handlers = {};
const removed = [];
let pending;
const source = fs.readFileSync('static/sw.js', 'utf8');
const current = source.match(/const CACHE_NAME = '([^']+)'/)[1];
vm.runInNewContext(source, {
self: { addEventListener: (event, callback) => handlers[event] = callback, clients: { claim: () => Promise.resolve() } },
caches: { keys: async () => ['previous-cache', current], delete: async key => { removed.push(key); return true; } },
});
handlers.activate({ waitUntil: promise => pending = promise });
pending.then(() => process.stdout.write(JSON.stringify(removed))).catch(error => { console.error(error); process.exit(1); });
"""], cwd=ROOT, text=True, capture_output=True, timeout=10)
assert result.returncode == 0, result.stderr
assert json.loads(result.stdout) == ['previous-cache']
+5 -10
View File
@@ -1,11 +1,8 @@
"""Regression guard for the README title presentation.
Originally (#1390) the README opened with an ASCII-art banner that had to live
inside a ``` code fence, otherwise GitHub's markdown collapsed its leading
whitespace and box-drawing rules and rendered it misaligned. The README refresh
(#4306) dropped that banner in favour of a centered wordmark image, so the guard
now pins the wordmark identity instead, while still catching the original failure
mode if an un-fenced ASCII banner is ever reintroduced.
The original ASCII-art banner needed a code fence to preserve whitespace.
Publication now opens with a centered text heading after omitting the wordmark
artwork. Keep the title identity and the historical ASCII-fencing guard.
"""
from pathlib import Path
@@ -22,11 +19,9 @@ def _fenced_segments(text: str):
return parts[1::2]
def test_readme_opens_with_wordmark_title():
# The README must still open with a recognizable Odysseus title: now the
# centered wordmark image rather than an H1 / ASCII banner.
def test_readme_opens_with_text_title():
head = "\n".join(README.read_text(encoding="utf-8").splitlines()[:15])
assert 'alt="Odysseus"' in head, "README must open with the Odysseus wordmark image"
assert head.startswith('<h1 align="center">Odysseus</h1>'), "README must open with the Odysseus text title"
def test_reintroduced_ascii_banner_stays_fenced():