Échec du linker de test natif Fastly Compute
Problème
Les binaires Rust Fastly Compute se lient contre libfastly, qui fournit des symboles FFI n'existant que lors du ciblage de wasm32-wasip1. Sur un hôte natif (macOS, Linux x86_64, Linux arm64), les symboles FFI sont absents, donc la liaison d'un binaire de test pour le crate Fastly Compute échoue avec des erreurs comme :
"_version_set", referenced from:
fastly::http::response::handle::ResponseHandle::set_version::h234bdb33f8878476 in libfastly-XXXX.rlib
"_body_new", referenced from:
fastly::http::body::Body::new::hYYYY in libfastly-XXXX.rlib
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1
error: could not compile `fastly-blossom` (bin "fastly-blossom" test)
L'erreur apparaît lors de cargo test même si aucun de vos tests n'appelle réellement le code du SDK Fastly — cargo essaie quand même de compiler et de lier le binaire entier comme cible de test.
Cela a une conséquence désagréable : tout bloc #[cfg(test)] mod tests { ... } à l'intérieur d'un fichier compilé dans le crate binaire est effectivement du code mort. Il ne s'exécute jamais via cargo test car cargo ne peut pas lier le binaire de test. Les développeurs ajoutent des tests, le compilateur les accepte, ils sont visibles dans l'arborescence source — mais ils ne s'exécutent jamais. Les tests censés prévenir les régressions deviennent silencieusement une documentation qui vieillit.
Contexte / Conditions déclencheurs
- Projet Fastly Compute Rust avec
fastly = "0.11.x"(ou similaire) comme dépendance - Le crate a à la fois
src/main.rsetsrc/lib.rsOU uniquementsrc/main.rs cargo testéchoue au moment de la liaison avec_version_set/_body_new/_req_sendnon définiscargo check --target wasm32-wasip1réussitcargo check(natif) réussit (le code de la bibliothèque compile, impossible de lier le binaire)- Vous avez ajouté des tests dans
src/admin.rs,src/blossom.rs,src/metadata.rs, ou tout fichier qui estmod-inclus depuissrc/main.rsmais PAS depuissrc/lib.rs
Cause racine
Le SDK Rust de Fastly (crate fastly) est conçu exclusivement pour wasm32-wasip1. Son API publique appelle des fonctions FFI non sûres (_version_set, _req_send, _body_new, etc.) que le runtime Fastly Compute fournit à l'exécution dans le POP. Sur un hôte natif, le compilateur rustc compile quand même le crate de bibliothèque, mais le linker ne peut pas résoudre ces symboles FFI car libfastly-native n'existe pas.
cargo test pour un crate lib+bin mixte lie deux artefacts : le binaire de test de la bibliothèque ET le binaire de test du binaire (l'exécutable main avec les tests activés). Le binaire de test de la bibliothèque peut éviter les symboles du SDK si lib.rs ne les importe pas transitivement. Le binaire de test du binaire ne le peut pas, car main.rs câble tout le runtime Fastly Compute.
Solution
Structurez votre crate comme lib + bin, et mettez toute la logique testable dans lib
- Créez
src/lib.rss'il n'existe pas :
// src/lib.rs — cible de bibliothèque, testable nativement
pub mod admin_sweep; // logique pure, pas de SDK Fastly
pub mod classifiers; // logique pure, pas de SDK Fastly
pub mod parsers; // logique pure, pas de SDK Fastly
- Mettez tout module que vous voulez soumettre à des tests unitaires dans un fichier séparé qui N'importe PAS les types du SDK Fastly, et exposez-le via
pub moddanslib.rs:
// src/admin_sweep.rs — logique pure, testable nativement
pub enum StuckAction { SkipNotStuck, SkipTooRecent, MarkComplete, ResetPending }
pub fn classify_stuck_record(
is_processing: bool,
uploaded_iso: &str,
threshold_iso: &str,
hls_present: bool,
) -> StuckAction { /* ... */ }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn skip_not_stuck_when_status_is_not_processing() { /* ... */ }
}
- Dans
src/main.rs, déclarez également le module pour que le binaire puisse l'utiliser :
mod admin_sweep; // même fichier, compilé deux fois — une fois pour lib, une fois pour bin
- Le handler dans un admin.rs (ou ailleurs) qui UTILISE les types du SDK Fastly extrait les primitives des types du SDK et appelle le classificateur pur :
// src/admin.rs (partie du bin, utilise les types du SDK Fastly comme Request/Response)
pub fn handle_sweep(req: Request) -> Result<Response> {
let is_processing = meta.transcode_status == Some(TranscodeStatus::Processing);
let action = crate::admin_sweep::classify_stuck_record(
is_processing, &meta.uploaded, &threshold_iso, hls_present,
);
// ... match sur action, appels au niveau du SDK ...
}
- Exécutez les tests avec :
cargo test --lib
Pas cargo test (qui essaie de lier le bin). --lib ne construit et n'exécute que le binaire de test de la bibliothèque, qui peut se lier car aucun de ses codes ne touche aux symboles du SDK Fastly.
Ne dépendez PAS des internals du crate depuis le module lib
Votre module exposé par lib ne doit PAS importer transitivement quoi que ce soit qui touche au SDK Fastly. Pièges courants :
use crate::blossom::BlobMetadata;— siblossom.rsest dans le crate binaire et utilisefastly::Request, importerBlobMetadatatraîne les symboles du SDK dans la construction lib. Solution : passez des primitives brutes (Option<TranscodeStatus>, &str, etc.) à travers la limite, pas des structs conscientes du SDK.use crate::storage::current_timestamp;— si storage.rs appellefastly::kv_store::*, même problème. Dupliquez l'helper dans admin_sweep.rs ou extrayez-le dans un troisième module sans SDK dans lib.rs.
Traitez lib.rs comme un espace blindé : uniquement du Rust pur, pas de types du SDK, pas d'I/O spécifique à la plateforme.
Vérifiez
cargo check --target wasm32-wasip1 # doit réussir (bin se compile pour le déploiement)
cargo test --lib # doit s'exécuter et réussir (tests exercent la logique pure)
Vous pouvez aussi ajouter à la CI :
- run: cargo test --lib
- run: cargo check --target wasm32-wasip1
cargo test (sans flags) échouera toujours sur ce projet ; c'est attendu et inévitable. Documentez-le dans README ou CONTRIBUTING pour que les contributeurs ne perdent pas de temps dessus.
Vérification
Après avoir appliqué ce motif :
cargo test --libexécute vos nouveaux tests et ils s'exécutent (réussissent ou échouent, mais ils S'EXÉCUTENT)cargo check --target wasm32-wasip1produit toujours un WASM déployable- Les blocs
#[cfg(test)] mod testsexistants dans les fichiers binaires uniquement (admin.rs, etc.) sont toujours morts ; envisagez de les migrer dans des modules exposés par lib ou de les supprimer si le comportement qu'ils testent peut être déplacé
Exemple
Dans le repo Divine Blossom, le crate Fastly Compute (src/main.rs) avait des modules #[cfg(test)] dans src/delete_policy.rs, src/error.rs, src/auth.rs, src/blossom.rs, et src/metadata.rs. Aucun ne s'exécutait via cargo test — lib.rs exposait uniquement resumable_complete. Tenter d'ajouter des tests à src/admin.rs échouait au moment de la liaison.
Le correctif était de créer src/admin_sweep.rs sans aucune dépendance du reste du crate (pas de use crate::blossom, pas de use crate::storage), d'ajouter pub mod admin_sweep; à lib.rs, et d'avoir les tests là-bas. Le handler dans src/admin.rs (binaire uniquement) extrait quatre primitives de BlobMetadata avant d'appeler le classificateur pur.
Résultat : cargo test --lib exécute maintenant 11 tests (7 nouveaux + 4 pré-existants de resumable_complete). La construction wasm32-wasip1 produit toujours un binaire Fastly Compute valide. Les modules #[cfg(test)] binaires uniquement sont toujours morts, mais la logique qui compte est maintenant couverte.
Notes
- C'est une contrainte structurelle imposée par la conception FFI du SDK Fastly, pas quelque chose que Fastly va « corriger » — le crate du SDK doit fournir ces symboles d'une manière ou d'une autre, et le runtime est wasm.
- Les propres exemples de Fastly ont tendance à éviter les tests entièrement, ce qui est pourquoi ce piège est généralisé.
- Le même motif s'applique à tout crate Rust qui cible une plateforme spécifique via des symboles FFI fournis par un runtime hôte : Cloudflare Workers (
worker-rs), Vercel Edge (@vercel/edge), certains cratesHAL embarqués, etc. Le motif « mettez la logique pure dans lib, gardez les appels du SDK dans bin » se généralise. - Si vous commencez de zéro, envisagez de faire de votre crate Fastly Compute un crate de bibliothèque uniquement avec un tiny
src/main.rsqui appelle justelib::run(). Ensuite, tout est dans la lib par défaut et il n'y a pas de division bin/lib à gérer. rust-analyzeret l'outillage IDE analyseront et vérifieront toujours heureux le typage des modules#[cfg(test)]dans les fichiers binaires uniquement, donc ils paraissent vivants. Seulcargo testrévèle la vérité.
Références
- Crate Fastly Compute Rust SDK — notez la restriction de cible dans la documentation
- Skill associé :
fastly-compute-rust-edition2024-fix— mode d'échec différent (incompatibilité de dépendance au moment de la construction) mais partage le thème « le crate Fastly Compute a des contraintes inhabituelles autour de l'outillage natif »