diff --git a/docs/adr/0015-test-llama-backend-singleton.md b/docs/adr/0015-test-llama-backend-singleton.md new file mode 100644 index 0000000..5a98a8b --- /dev/null +++ b/docs/adr/0015-test-llama-backend-singleton.md @@ -0,0 +1,26 @@ +# ADR 0015: Share The Llama Backend In Rust Tests + +## Status + +Accepted + +## Context + +`llama-cpp-2` 0.1.151 permits backend initialization only once per process. +AssistSupport had independent embedding and LLM engine tests that each initialized +the backend. When the full Rust suite ran in one process, later tests failed with +`BackendAlreadyInitialized` even though production initialization remained valid. + +## Decision + +Keep production backend ownership unchanged. Under `cfg(test)`, initialize one +`LlamaBackend` in a `OnceLock>` and clone the `Arc` into engine +tests. Add a source-policy regression test so future tests do not reintroduce +independent backend initialization. + +## Consequences + +- Rust engine tests can run together against llama-cpp 0.1.151. +- The singleton helper is absent from production builds. +- Tests share process-wide backend state, matching the upstream library contract. +- Model-loading tests remain ignored unless their model fixture is available. diff --git a/scripts/ci/require-tests-and-docs.mjs b/scripts/ci/require-tests-and-docs.mjs index 72ba882..0e0e8f7 100644 --- a/scripts/ci/require-tests-and-docs.mjs +++ b/scripts/ci/require-tests-and-docs.mjs @@ -2,7 +2,12 @@ import { execSync } from 'node:child_process'; const defaultBaseRef = (() => { try { - return execSync('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' }).trim().replace('refs/remotes/', ''); + return execSync('git symbolic-ref refs/remotes/origin/HEAD', { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }) + .trim() + .replace('refs/remotes/', ''); } catch { return 'origin/master'; } @@ -76,4 +81,4 @@ if (failures.length > 0) { process.exit(1); } -console.log('Policy checks passed.'); \ No newline at end of file +console.log('Policy checks passed.'); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 41fd753..f11a2ce 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -708,7 +708,7 @@ dependencies = [ "quote", "regex", "rustc-hash 1.1.0", - "shlex", + "shlex 1.3.0", "syn 1.0.109", "which", ] @@ -722,14 +722,14 @@ dependencies = [ "bitflags 2.11.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "log", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 2.1.1", - "shlex", + "shlex 1.3.0", "syn 2.0.117", ] @@ -1032,14 +1032,14 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -2457,7 +2457,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2489,7 +2489,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.9", + "libloading 0.7.4", ] [[package]] @@ -2717,7 +2717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3147,7 +3147,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.2.1", + "windows-link 0.1.3", "windows-result", ] @@ -4090,7 +4090,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5149,9 +5149,9 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "llama-cpp-2" -version = "0.1.135" +version = "0.1.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b660b832461ef32da66c7acedf35251cdc9e13c0e816bc5904b717cd1e5b25" +checksum = "36ead0925a19d754d5d13761ceb0f0fe49d36f8103c3596588ac1fb8911e5223" dependencies = [ "encoding_rs", "enumflags2", @@ -5163,9 +5163,9 @@ dependencies = [ [[package]] name = "llama-cpp-sys-2" -version = "0.1.135" +version = "0.1.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95cf8be1ff000b531ac0523c66a9ffce3cef6d09a5d95b21cad745bedb115468" +checksum = "23c676b02f8f2bdd9d7df7bcca0bd176b27964440998e7aa31c6fe32552964d0" dependencies = [ "bindgen 0.72.1", "cc", @@ -5442,7 +5442,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5593,7 +5593,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6509,7 +6509,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "itertools 0.14.0", "log", "multimap", @@ -7228,7 +7228,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7674,6 +7674,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -7757,7 +7763,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.117", @@ -7769,7 +7775,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.117", @@ -8395,7 +8401,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix 1.1.3", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8903,7 +8909,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9566,7 +9572,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/src-tauri/src/kb/embeddings.rs b/src-tauri/src/kb/embeddings.rs index fbfdd8f..80a89f7 100644 --- a/src-tauri/src/kb/embeddings.rs +++ b/src-tauri/src/kb/embeddings.rs @@ -282,7 +282,7 @@ mod tests { #[test] fn test_engine_creation() { - let backend = Arc::new(LlamaBackend::init().expect("backend init")); + let backend = crate::test_llama_backend(); let engine = EmbeddingEngine::new(backend); assert!(engine.is_ok()); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 007817c..7e068a1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -114,6 +114,15 @@ impl Default for AppState { } } +#[cfg(test)] +pub(crate) fn test_llama_backend() -> Arc { + static BACKEND: std::sync::OnceLock> = std::sync::OnceLock::new(); + + Arc::clone(BACKEND.get_or_init(|| { + Arc::new(LlamaBackend::init().expect("test llama backend should initialize")) + })) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { #[cfg(target_os = "macos")] diff --git a/src-tauri/src/llm.rs b/src-tauri/src/llm.rs index c9ea0fc..41258c8 100644 --- a/src-tauri/src/llm.rs +++ b/src-tauri/src/llm.rs @@ -514,7 +514,7 @@ mod tests { #[test] fn test_engine_creation() { - let backend = Arc::new(LlamaBackend::init().expect("backend init")); + let backend = crate::test_llama_backend(); let engine = LlmEngine::new(backend); assert!(engine.is_ok(), "Engine should initialize"); @@ -545,7 +545,7 @@ mod tests { } // Create engine - let backend = Arc::new(LlamaBackend::init().expect("backend init")); + let backend = crate::test_llama_backend(); let engine = LlmEngine::new(backend).expect("Failed to create engine"); assert!(!engine.is_model_loaded()); diff --git a/src-tauri/tests/llama_backend_singleton_policy.rs b/src-tauri/tests/llama_backend_singleton_policy.rs new file mode 100644 index 0000000..b9e7db8 --- /dev/null +++ b/src-tauri/tests/llama_backend_singleton_policy.rs @@ -0,0 +1,16 @@ +#[test] +fn engine_tests_share_the_process_wide_llama_backend() { + let lib = include_str!("../src/lib.rs"); + let embeddings = include_str!("../src/kb/embeddings.rs"); + let llm = include_str!("../src/llm.rs"); + + assert!(lib.contains("fn test_llama_backend()")); + assert!(embeddings.contains("let backend = crate::test_llama_backend();")); + assert_eq!( + llm.matches("let backend = crate::test_llama_backend();") + .count(), + 2 + ); + assert!(!embeddings.contains("LlamaBackend::init().expect(\"backend init\")")); + assert!(!llm.contains("LlamaBackend::init().expect(\"backend init\")")); +}