26382a7ac6
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
JetBrains Plugin / Actionlint (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (rust) (push) Waiting to run
JetBrains Plugin / Validation (push) Waiting to run
JetBrains Plugin / Build (push) Waiting to run
JetBrains Plugin / Test (push) Blocked by required conditions
Security Check / Security Scan (push) Waiting to run
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
79 lines
2.8 KiB
Rust
79 lines
2.8 KiB
Rust
use serde_json::json;
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn context_engine_call_tool_text_reads_file() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let file_path = dir.path().join("a.txt");
|
|
std::fs::write(&file_path, "hello-engine\n").expect("write file");
|
|
|
|
let engine = lean_ctx::engine::ContextEngine::with_project_root(dir.path());
|
|
let out = engine
|
|
.call_tool_text(
|
|
"ctx_read",
|
|
Some(json!({
|
|
"path": file_path.to_string_lossy().to_string(),
|
|
"mode": "full"
|
|
})),
|
|
)
|
|
.await
|
|
.expect("call tool");
|
|
|
|
assert!(out.contains("hello-engine"));
|
|
}
|
|
|
|
/// #271 regression: many concurrent reads must all return through the
|
|
/// `spawn_blocking` dispatch path without starving the core workers or hanging.
|
|
/// Before the dispatch watchdog, a single hung handler could swallow its
|
|
/// response (client crash: "Cannot read properties of undefined (reading
|
|
/// 'invoke')"); this exercises the path under real concurrency.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
async fn context_engine_handles_concurrent_reads_without_hang() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
// Distinct files/contents per call so the session's auto-dedup (which
|
|
// returns a compact stub for a *re-read* of the same file) does not mask the
|
|
// body — here we assert each concurrent read returns real content.
|
|
const CALLS: usize = 16;
|
|
for i in 0..CALLS {
|
|
std::fs::write(
|
|
dir.path().join(format!("f{i}.txt")),
|
|
format!("content-{i}-unique\n"),
|
|
)
|
|
.expect("write file");
|
|
}
|
|
|
|
let engine = std::sync::Arc::new(lean_ctx::engine::ContextEngine::with_project_root(
|
|
dir.path(),
|
|
));
|
|
let mut handles = Vec::with_capacity(CALLS);
|
|
for i in 0..CALLS {
|
|
let engine = engine.clone();
|
|
let path = dir
|
|
.path()
|
|
.join(format!("f{i}.txt"))
|
|
.to_string_lossy()
|
|
.to_string();
|
|
handles.push(tokio::spawn(async move {
|
|
engine
|
|
.call_tool_text("ctx_read", Some(json!({ "path": path, "mode": "full" })))
|
|
.await
|
|
}));
|
|
}
|
|
|
|
// A generous bound: the reads are trivial, so completion well under this
|
|
// means no hang/starvation. A regression (dropped response) would time out.
|
|
let texts = tokio::time::timeout(std::time::Duration::from_secs(30), async {
|
|
let mut out = Vec::with_capacity(CALLS);
|
|
for h in handles {
|
|
out.push(h.await.expect("task joined").expect("tool call ok"));
|
|
}
|
|
out
|
|
})
|
|
.await
|
|
.expect("concurrent reads must not hang");
|
|
|
|
assert_eq!(texts.len(), CALLS);
|
|
for text in texts {
|
|
assert!(text.contains("content-"), "each read returns its file body");
|
|
}
|
|
}
|