add state constraint regression and compile-fail tests
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
#[test]
|
||||
fn compile_fail_state_constraints() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/cache_locked_register_tool.rs");
|
||||
t.compile_fail("tests/ui/tool_server_handle_register_tool.rs");
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use llm_worker::Worker;
|
||||
use llm_worker::llm_client::providers::ollama::OllamaClient;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
let client = OllamaClient::new("dummy-model");
|
||||
let worker = Worker::new(client);
|
||||
let mut locked = worker.lock();
|
||||
let def: llm_worker::tool::ToolDefinition = Arc::new(|| panic!("unused"));
|
||||
let _ = locked.register_tool(def);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
error[E0599]: no method named `register_tool` found for struct `Worker<OllamaClient, CacheLocked>` in the current scope
|
||||
--> tests/ui/cache_locked_register_tool.rs:10:20
|
||||
|
|
||||
10 | let _ = locked.register_tool(def);
|
||||
| ^^^^^^^^^^^^^ method not found in `Worker<OllamaClient, CacheLocked>`
|
||||
|
|
||||
= note: the method was found for
|
||||
- `Worker<C>`
|
||||
@@ -0,0 +1,11 @@
|
||||
use llm_worker::Worker;
|
||||
use llm_worker::llm_client::providers::ollama::OllamaClient;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
let client = OllamaClient::new("dummy-model");
|
||||
let worker = Worker::new(client);
|
||||
let handle = worker.tool_server_handle();
|
||||
let def: llm_worker::tool::ToolDefinition = Arc::new(|| panic!("unused"));
|
||||
let _ = handle.register_tool(def);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
error[E0624]: method `register_tool` is private
|
||||
--> tests/ui/tool_server_handle_register_tool.rs:10:20
|
||||
|
|
||||
10 | let _ = handle.register_tool(def);
|
||||
| ^^^^^^^^^^^^^ private method
|
||||
|
|
||||
::: src/tool_server.rs
|
||||
|
|
||||
| / pub(crate) fn register_tool(
|
||||
| | &self,
|
||||
| | factory: WorkerToolDefinition,
|
||||
| | ) -> Result<(), ToolServerError> {
|
||||
| |____________________________________- private method defined here
|
||||
@@ -5,9 +5,14 @@
|
||||
|
||||
mod common;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common::MockLlmClient;
|
||||
use llm_worker::Worker;
|
||||
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
|
||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta};
|
||||
use llm_worker::Item;
|
||||
|
||||
// =============================================================================
|
||||
@@ -91,6 +96,56 @@ fn test_mutable_extend_history() {
|
||||
assert_eq!(worker.history().len(), 4);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CountingTool {
|
||||
name: String,
|
||||
calls: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl CountingTool {
|
||||
fn new(name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
calls: Arc::new(AtomicUsize::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn definition(&self) -> ToolDefinition {
|
||||
let tool = self.clone();
|
||||
Arc::new(move || {
|
||||
(
|
||||
ToolMeta::new(&tool.name)
|
||||
.description("Counting tool")
|
||||
.input_schema(serde_json::json!({"type":"object","properties":{}})),
|
||||
Arc::new(tool.clone()) as Arc<dyn Tool>,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn call_count(&self) -> usize {
|
||||
self.calls.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for CountingTool {
|
||||
async fn execute(&self, _input_json: &str) -> Result<String, ToolError> {
|
||||
self.calls.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(format!("{}-ok", self.name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that tools can be registered in Mutable state.
|
||||
#[test]
|
||||
fn test_mutable_can_register_tool() {
|
||||
let client = MockLlmClient::new(vec![]);
|
||||
let mut worker = Worker::new(client);
|
||||
let tool = CountingTool::new("count_tool");
|
||||
|
||||
let result = worker.register_tool(tool.definition());
|
||||
assert!(result.is_ok(), "Mutable should allow tool registration");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// State Transition Tests
|
||||
// =============================================================================
|
||||
@@ -330,6 +385,67 @@ async fn test_unlock_edit_relock() {
|
||||
assert_eq!(relocked.locked_prefix_len(), 1);
|
||||
}
|
||||
|
||||
/// Verify that tools registered before lock and after unlock remain effective.
|
||||
#[tokio::test]
|
||||
async fn test_lock_unlock_relock_tools_remain_effective() {
|
||||
let client = MockLlmClient::with_responses(vec![
|
||||
vec![
|
||||
Event::tool_use_start(0, "call_1", "tool_a"),
|
||||
Event::tool_input_delta(0, r#"{}"#),
|
||||
Event::tool_use_stop(0),
|
||||
Event::Status(StatusEvent {
|
||||
status: ResponseStatus::Completed,
|
||||
}),
|
||||
],
|
||||
vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "done-a"),
|
||||
Event::text_block_stop(0, None),
|
||||
Event::Status(StatusEvent {
|
||||
status: ResponseStatus::Completed,
|
||||
}),
|
||||
],
|
||||
vec![
|
||||
Event::tool_use_start(0, "call_2", "tool_b"),
|
||||
Event::tool_input_delta(0, r#"{}"#),
|
||||
Event::tool_use_stop(0),
|
||||
Event::Status(StatusEvent {
|
||||
status: ResponseStatus::Completed,
|
||||
}),
|
||||
],
|
||||
vec![
|
||||
Event::text_block_start(0),
|
||||
Event::text_delta(0, "done-b"),
|
||||
Event::text_block_stop(0, None),
|
||||
Event::Status(StatusEvent {
|
||||
status: ResponseStatus::Completed,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
let mut worker = Worker::new(client);
|
||||
let tool_a = CountingTool::new("tool_a");
|
||||
worker
|
||||
.register_tool(tool_a.definition())
|
||||
.expect("register tool_a should succeed");
|
||||
|
||||
let mut locked = worker.lock();
|
||||
locked.run("first").await.expect("first run");
|
||||
assert_eq!(tool_a.call_count(), 1, "tool_a should be called once");
|
||||
|
||||
let mut unlocked = locked.unlock();
|
||||
let tool_b = CountingTool::new("tool_b");
|
||||
unlocked
|
||||
.register_tool(tool_b.definition())
|
||||
.expect("register tool_b after unlock should succeed");
|
||||
|
||||
let mut relocked = unlocked.lock();
|
||||
relocked.run("second").await.expect("second run");
|
||||
|
||||
assert_eq!(tool_a.call_count(), 1, "tool_a should not be called again");
|
||||
assert_eq!(tool_b.call_count(), 1, "tool_b should be called once");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// System Prompt Preservation Tests
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user