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
//! End-to-end smoke test for `BundleRepo` against a real HuggingFace
//! URL. Proves the downloader + integrity check + cache-hit codepath
//! actually work against the upstream CDN, on a real GGUF.
//!
//! Two assertions:
//! 1. A fresh `BundleRepo` resolves an HF URL, downloads the file, and
//! the resulting path loads into a `CeraEngine` without error —
//! this exercises SHA-256-on-the-fly hashing + `X-Linked-Etag`
//! verification against the live HF CDN.
//! 2. A second `resolve_url` call for the same URL returns the same
//! path without re-downloading (proved by comparing mtimes).
//!
//! Gating: `#[ignore]` + `CERA_TEST_DOWNLOAD=1` env var so `cargo test`
//! never hits the network. To opt in:
//!
//! ```sh
//! CERA_TEST_DOWNLOAD=1 cargo test -p cera --features remote \
//! --test bundle_download -- --ignored
//! ```
//!
//! On CI, the download is cached under `target/tmp/cera-test-models/`
//! so repeat runs pay only an HTTP HEAD probe.
#![cfg(feature = "remote")]
mod common;
use std::time::SystemTime;
use cera::bundle::BundleRepo;
use cera::engine::{BackendPreference, CeraEngine, EngineConfig, ModelFiles};
/// LFM2-350M-Extract Q4_0 GGUF — the same file `from_bundle_id` pulls
/// in `bundle_from_id.rs` (its manifest's `model` URL points here), so
/// CI's `target/tmp/cera-test-models` cache is shared across all three
/// integration tests and pays only a HEAD probe on subsequent runs.
/// Mutable `resolve/main/` ref is intentional — these tests assert
/// load-and-shift sanity, not bit-exact weight equality. If upstream
/// re-uploads, `BundleRepo`'s validation policy detects the changed
/// artifact via `X-Linked-Etag` (or per-file SHA-256 when supplied)
/// and falls back to `Content-Length` only when no hash-like
/// identifier is available — either way a re-download fires on the
/// next run. Phase 1.6's `BundleRepo` is where pinning to a revision
/// hash belongs.
const MODEL_URL: &str = "https://huggingface.co/LiquidAI/LFM2-350M-Extract-GGUF/resolve/main/LFM2-350M-Extract-Q4_0.gguf";
const MODEL_FILE: &str = "LFM2-350M-Extract-Q4_0.gguf";
#[test]
#[ignore = "downloads ~210 MB; set CERA_TEST_DOWNLOAD=1 and pass --ignored"]
fn bundle_repo_resolves_and_loads_from_hf() {
if std::env::var("CERA_TEST_DOWNLOAD").is_err() {
eprintln!("skipping: CERA_TEST_DOWNLOAD not set");
return;
}
// Use the shared test cache via the `common` helper — exercises the
// same BundleRepo codepath but rooted in the CI-cached location so
// repeat runs don't redownload.
let path = common::download::ensure_cached(MODEL_URL, MODEL_FILE);
assert!(
path.exists(),
"BundleRepo.resolve_url did not produce an existing file at {}",
path.display()
);
// Sanity: the file is large enough to be a real GGUF (>150 MB —
// LFM2-350M-Extract Q4_0 is ~209 MB).
let size = std::fs::metadata(&path)
.expect("stat downloaded file")
.len();
assert!(
size > 150 * 1024 * 1024,
"downloaded file only {size} bytes — HF returned an error page?"
);
// End-to-end: feed the resolved path into CeraEngine. A bogus
// download (truncated / wrong content-type page) would fail GGUF
// header parse here, so this doubles as an integrity smoke test.
let engine = CeraEngine::from_files(
ModelFiles::text(&path),
EngineConfig {
context_size: 128,
backend: BackendPreference::Cpu,
..Default::default()
},
)
.expect("load engine from resolved bundle file");
let meta = engine.metadata();
assert!(
meta.max_seq_len > 0,
"engine metadata missing max_seq_len — model parse failed silently"
);
}
#[test]
#[ignore = "downloads ~210 MB; set CERA_TEST_DOWNLOAD=1 and pass --ignored"]
fn bundle_repo_cache_hit_does_not_redownload() {
if std::env::var("CERA_TEST_DOWNLOAD").is_err() {
eprintln!("skipping: CERA_TEST_DOWNLOAD not set");
return;
}
// Pre-populate via the shared helper so we're measuring a cache hit
// on the *second* call regardless of whether the first test ran.
let first = common::download::ensure_cached(MODEL_URL, MODEL_FILE);
let mtime_before = first
.metadata()
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
// A fresh BundleRepo pointed at the same store_dir must see the
// cached file via the HEAD-probe policy — no rewrite.
let repo = BundleRepo::new(common::download::cache_dir());
let second = repo
.resolve_url(MODEL_URL, None)
.expect("resolve on cache hit");
assert_eq!(first, second, "cache-hit path should match the first call");
let mtime_after = second
.metadata()
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
assert_eq!(
mtime_before, mtime_after,
"cache hit should not rewrite the file (mtime changed)"
);
}