From 28833eace00a2845f95076f388da18504fe21587 Mon Sep 17 00:00:00 2001 From: Saagar Date: Thu, 9 Jul 2026 21:58:18 -0700 Subject: [PATCH 1/2] fix(deps): share llama backend in tests --- src-tauri/Cargo.lock | 54 +++++++++++++++++++--------------- src-tauri/src/kb/embeddings.rs | 2 +- src-tauri/src/lib.rs | 9 ++++++ src-tauri/src/llm.rs | 4 +-- 4 files changed, 42 insertions(+), 27 deletions(-) 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()); From 90086f9d1b4e9da58bd5d200bfbcffaf20a305fa Mon Sep 17 00:00:00 2001 From: Saagar Date: Thu, 9 Jul 2026 22:13:04 -0700 Subject: [PATCH 2/2] test(llm): cover singleton backend policy --- docs/adr/0015-test-llama-backend-singleton.md | 26 ++++++ scripts/ci/require-tests-and-docs.mjs | 86 +------------------ .../tests/llama_backend_singleton_policy.rs | 16 ++++ 3 files changed, 45 insertions(+), 83 deletions(-) create mode 100644 docs/adr/0015-test-llama-backend-singleton.md create mode 100644 src-tauri/tests/llama_backend_singleton_policy.rs 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 160e1b7..0e0e8f7 100644 --- a/scripts/ci/require-tests-and-docs.mjs +++ b/scripts/ci/require-tests-and-docs.mjs @@ -1,5 +1,4 @@ import { execSync } from 'node:child_process'; -import { readFileSync } from 'node:fs'; const defaultBaseRef = (() => { try { @@ -20,86 +19,8 @@ const diff = execSync(`git diff --name-only ${baseRef}...HEAD`, { encoding: 'utf .map((line) => line.trim()) .filter(Boolean); -const testOnlyRanges = (source) => { - const lines = source.split('\n'); - const ranges = []; - let depth = 0; - let cfgStart = null; - let itemStart = null; - let itemDepth = null; - - for (let index = 0; index < lines.length; index += 1) { - const line = lines[index]; - const trimmed = line.trim(); - - if (itemStart === null && trimmed === '#[cfg(test)]') cfgStart = index + 1; - - const opens = (line.match(/{/g) || []).length; - const closes = (line.match(/}/g) || []).length; - - if ( - itemStart === null && - cfgStart !== null && - trimmed && - !trimmed.startsWith('#') && - opens > closes - ) { - itemStart = cfgStart; - itemDepth = depth; - } - - depth += opens - closes; - - if (itemStart !== null && depth === itemDepth) { - ranges.push([itemStart, index + 1]); - cfgStart = null; - itemStart = null; - itemDepth = null; - } - } - - return ranges; -}; - -const changedLineRanges = (file) => { - const patch = execSync(`git diff --unified=0 --no-color ${baseRef}...HEAD -- ${file}`, { - encoding: 'utf8', - }); - - return [...patch.matchAll(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/gm)].map( - (match) => ({ - oldStart: Number(match[1]), - oldCount: Number(match[2] ?? 1), - newStart: Number(match[3]), - newCount: Number(match[4] ?? 1), - }), - ); -}; - -const rangeIsTestOnly = (start, count, ranges) => - count === 0 || - Array.from({ length: count }, (_, offset) => start + offset).every((line) => - ranges.some(([rangeStart, rangeEnd]) => line >= rangeStart && line <= rangeEnd), - ); - -const isTestOnlyRustChange = (file) => { - const currentRanges = testOnlyRanges(readFileSync(file, 'utf8')); - const baseSource = execSync(`git show ${baseRef}:${file}`, { encoding: 'utf8' }); - const baseRanges = testOnlyRanges(baseSource); - - return changedLineRanges(file).every( - ({ oldStart, oldCount, newStart, newCount }) => - rangeIsTestOnly(oldStart, oldCount, baseRanges) && - rangeIsTestOnly(newStart, newCount, currentRanges), - ); -}; - -const testOnlyRustFiles = new Set( - diff.filter((file) => /^src-tauri\/src\/.*\.rs$/.test(file) && isTestOnlyRustChange(file)), -); - const isJsTest = (file) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(file); -const isRustTest = (file) => /^src-tauri\/tests\//.test(file) || testOnlyRustFiles.has(file); +const isRustTest = (file) => /^src-tauri\/tests\//.test(file); const isPythonTest = (file) => /^search-api\/tests\//.test(file) || /^search-api\/test_.*\.py$/.test(file); const dependencyManifestFiles = new Set([ @@ -115,7 +36,7 @@ const isDependencyManifest = (file) => dependencyManifestFiles.has(file); const isProdCode = (file) => ((/^(src|app|server|api)\//.test(file) && !isJsTest(file)) || - (/^src-tauri\/src\//.test(file) && !testOnlyRustFiles.has(file)) || + /^src-tauri\/src\//.test(file) || (/^search-api\//.test(file) && !/^search-api\/tests\//.test(file) && !/^search-api\/README\.md$/.test(file))); @@ -136,8 +57,7 @@ const isApiSurface = (file) => const isArchChange = (file) => /^src\/(auth|db|infra|queue|events|architecture)\//.test(file) || - (/^src-tauri\/src\/(db|kb|commands|backup|migration)\//.test(file) && - !testOnlyRustFiles.has(file)) || + /^src-tauri\/src\/(db|kb|commands|backup|migration)\//.test(file) || /^search-api\/(db_config|hybrid_search|search_api|wsgi)\.py$/.test(file) || /^infra\//.test(file); const isAdr = (file) => /^docs\/adr\/\d{4}-.*\.md$/.test(file); 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\")")); +}