添加qiming-rcoder模块
This commit is contained in:
126
qiming-rcoder/crates/agent_runner/src/testing/blocking.rs
Normal file
126
qiming-rcoder/crates/agent_runner/src/testing/blocking.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
//! 测试用阻塞注入
|
||||
//!
|
||||
//! 用于模拟极端场景 (Agent 阻塞、超时等)
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// 阻塞配置
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockingConfig {
|
||||
/// 是否阻塞 new_session
|
||||
pub block_new_session: bool,
|
||||
/// 是否阻塞 prompt
|
||||
pub block_prompt: bool,
|
||||
/// 阻塞持续时间
|
||||
pub block_duration: Duration,
|
||||
}
|
||||
|
||||
impl Default for BlockingConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
block_new_session: false,
|
||||
block_prompt: false,
|
||||
block_duration: Duration::from_secs(30),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 全局阻塞配置 (线程安全)
|
||||
pub static BLOCKING_CONFIG: std::sync::LazyLock<Arc<std::sync::RwLock<BlockingConfig>>> =
|
||||
std::sync::LazyLock::new(|| Arc::new(std::sync::RwLock::new(BlockingConfig::default())));
|
||||
|
||||
/// 注入阻塞 (用于测试)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use agent_runner::testing::blocking::{BlockingConfig, inject_blocking};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // 注入 30 秒阻塞
|
||||
/// inject_blocking(BlockingConfig {
|
||||
/// block_prompt: true,
|
||||
/// block_duration: Duration::from_secs(30),
|
||||
/// ..Default::default()
|
||||
/// });
|
||||
/// ```
|
||||
pub fn inject_blocking(config: BlockingConfig) {
|
||||
tracing::warn!("🧪 [TEST] Blocking config updated: {:?}", config);
|
||||
let mut global = BLOCKING_CONFIG.write().unwrap();
|
||||
*global = config;
|
||||
}
|
||||
|
||||
/// 检查并执行阻塞 (在 agent_worker_with_heartbeat 中调用)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `blocking_type` - 阻塞类型: "new_session" 或 "prompt"
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use agent_runner::testing::blocking::maybe_block;
|
||||
/// # async fn test() {
|
||||
/// // 在 agent_worker_with_heartbeat 中调用
|
||||
/// maybe_block("prompt").await;
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn maybe_block(blocking_type: &str) {
|
||||
let config = BLOCKING_CONFIG.read().unwrap();
|
||||
|
||||
let should_block = match blocking_type {
|
||||
"new_session" => config.block_new_session,
|
||||
"prompt" => config.block_prompt,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if should_block {
|
||||
tracing::warn!(
|
||||
"🧪 [TEST] Injected blocking: {}, duration: {:?}",
|
||||
blocking_type,
|
||||
config.block_duration
|
||||
);
|
||||
tokio::time::sleep(config.block_duration).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// 重置阻塞配置 (用于测试清理)
|
||||
pub fn reset_blocking() {
|
||||
inject_blocking(BlockingConfig::default());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_blocking_config_default() {
|
||||
let config = BlockingConfig::default();
|
||||
assert!(!config.block_new_session);
|
||||
assert!(!config.block_prompt);
|
||||
assert_eq!(config.block_duration, Duration::from_secs(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inject_and_reset_blocking() {
|
||||
// 注入配置
|
||||
inject_blocking(BlockingConfig {
|
||||
block_prompt: true,
|
||||
block_duration: Duration::from_secs(10),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let config = BLOCKING_CONFIG.read().unwrap();
|
||||
assert!(config.block_prompt);
|
||||
assert_eq!(config.block_duration, Duration::from_secs(10));
|
||||
|
||||
// 重置配置
|
||||
drop(config);
|
||||
reset_blocking();
|
||||
|
||||
let config = BLOCKING_CONFIG.read().unwrap();
|
||||
assert!(!config.block_prompt);
|
||||
assert_eq!(config.block_duration, Duration::from_secs(30));
|
||||
}
|
||||
}
|
||||
226
qiming-rcoder/crates/agent_runner/src/testing/fixtures.rs
Normal file
226
qiming-rcoder/crates/agent_runner/src/testing/fixtures.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
//! 测试 Fixtures
|
||||
//!
|
||||
//! 提供测试用的构造器和辅助工具
|
||||
|
||||
use crate::proxy_agent::AgentRequest;
|
||||
use agent_abstraction::PromptMessage;
|
||||
use shared_types::{Attachment, ServiceType};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// 测试请求构造器
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use agent_runner::testing::fixtures::TestRequestBuilder;
|
||||
///
|
||||
/// // 创建基础测试请求
|
||||
/// let (req, resp_rx) = TestRequestBuilder::new()
|
||||
/// .project_id("test-project")
|
||||
/// .content("Hello, Agent!")
|
||||
/// .build();
|
||||
/// ```
|
||||
pub struct TestRequestBuilder {
|
||||
project_id: String,
|
||||
content: String,
|
||||
request_id: String,
|
||||
attachments: Vec<Attachment>,
|
||||
session_id: Option<String>,
|
||||
project_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for TestRequestBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRequestBuilder {
|
||||
/// 创建新的测试请求构造器
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
project_id: "test-project".to_string(),
|
||||
content: "Hello, Agent!".to_string(),
|
||||
request_id: uuid::Uuid::new_v4().to_string().replace("-", ""),
|
||||
attachments: vec![],
|
||||
session_id: None,
|
||||
project_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置 project_id
|
||||
pub fn project_id(mut self, id: &str) -> Self {
|
||||
self.project_id = id.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置请求内容
|
||||
pub fn content(mut self, content: &str) -> Self {
|
||||
self.content = content.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置 request_id
|
||||
pub fn request_id(mut self, request_id: &str) -> Self {
|
||||
self.request_id = request_id.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置 session_id
|
||||
pub fn session_id(mut self, session_id: &str) -> Self {
|
||||
self.session_id = Some(session_id.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置项目路径
|
||||
pub fn project_path(mut self, path: &str) -> Self {
|
||||
self.project_path = Some(PathBuf::from(path));
|
||||
self
|
||||
}
|
||||
|
||||
/// 添加附件
|
||||
pub fn add_attachment(mut self, attachment: Attachment) -> Self {
|
||||
self.attachments.push(attachment);
|
||||
self
|
||||
}
|
||||
|
||||
/// 构建 PromptMessage(用于测试验证)
|
||||
///
|
||||
/// 返回构建的 PromptMessage,可以直接访问字段进行断言验证
|
||||
pub fn build_prompt_message(&self) -> PromptMessage {
|
||||
PromptMessage {
|
||||
content: self.content.clone(),
|
||||
project_id: self.project_id.clone(),
|
||||
project_path: self
|
||||
.project_path
|
||||
.clone()
|
||||
.unwrap_or_else(|| PathBuf::from("/tmp/test")),
|
||||
session_id: self.session_id.clone(),
|
||||
request_id: self.request_id.clone(),
|
||||
attachments: self.attachments.clone(),
|
||||
data_source_attachments: vec![],
|
||||
service_type: ServiceType::RCoder,
|
||||
system_prompt_override: None,
|
||||
user_prompt_template_override: None,
|
||||
agent_config_override: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建测试请求
|
||||
pub fn build(
|
||||
self,
|
||||
) -> (
|
||||
AgentRequest,
|
||||
tokio::sync::oneshot::Receiver<crate::model::ChatPromptResponse>,
|
||||
) {
|
||||
let prompt_message = self.build_prompt_message();
|
||||
AgentRequest::new(prompt_message, None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_default() {
|
||||
let builder = TestRequestBuilder::new();
|
||||
let prompt = builder.build_prompt_message();
|
||||
|
||||
// 验证默认值
|
||||
assert_eq!(prompt.project_id, "test-project");
|
||||
assert_eq!(prompt.content, "Hello, Agent!");
|
||||
assert!(!prompt.request_id.is_empty(), "request_id 应该自动生成");
|
||||
assert_eq!(prompt.project_path, PathBuf::from("/tmp/test"));
|
||||
assert!(prompt.session_id.is_none());
|
||||
assert!(prompt.attachments.is_empty());
|
||||
assert_eq!(prompt.service_type, ServiceType::RCoder);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_with_project_id() {
|
||||
let prompt = TestRequestBuilder::new()
|
||||
.project_id("custom-project")
|
||||
.build_prompt_message();
|
||||
|
||||
assert_eq!(prompt.project_id, "custom-project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_with_content() {
|
||||
let prompt = TestRequestBuilder::new()
|
||||
.content("Custom content")
|
||||
.build_prompt_message();
|
||||
|
||||
assert_eq!(prompt.content, "Custom content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_with_session_id() {
|
||||
let prompt = TestRequestBuilder::new()
|
||||
.session_id("test-session-123")
|
||||
.build_prompt_message();
|
||||
|
||||
assert_eq!(prompt.session_id, Some("test-session-123".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_with_request_id() {
|
||||
let prompt = TestRequestBuilder::new()
|
||||
.request_id("custom-request-id")
|
||||
.build_prompt_message();
|
||||
|
||||
assert_eq!(prompt.request_id, "custom-request-id");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_with_project_path() {
|
||||
let prompt = TestRequestBuilder::new()
|
||||
.project_path("/home/user/project")
|
||||
.build_prompt_message();
|
||||
|
||||
assert_eq!(prompt.project_path, PathBuf::from("/home/user/project"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_chain() {
|
||||
// 验证链式调用和所有字段设置
|
||||
let prompt = TestRequestBuilder::new()
|
||||
.project_id("my-project")
|
||||
.content("Test content")
|
||||
.request_id("custom-request-id")
|
||||
.session_id("session-456")
|
||||
.project_path("/home/user/project")
|
||||
.build_prompt_message();
|
||||
|
||||
assert_eq!(prompt.project_id, "my-project");
|
||||
assert_eq!(prompt.content, "Test content");
|
||||
assert_eq!(prompt.request_id, "custom-request-id");
|
||||
assert_eq!(prompt.session_id, Some("session-456".to_string()));
|
||||
assert_eq!(prompt.project_path, PathBuf::from("/home/user/project"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_build_returns_receiver() {
|
||||
// 验证 build() 返回的接收器可用
|
||||
let (_req, mut rx) = TestRequestBuilder::new().build();
|
||||
|
||||
// 验证接收器尚未收到消息
|
||||
assert!(rx.try_recv().is_err(), "接收器应该为空");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_request_builder_request_id_auto_generated() {
|
||||
// 验证每次构建生成不同的 request_id
|
||||
let builder1 = TestRequestBuilder::new();
|
||||
let builder2 = TestRequestBuilder::new();
|
||||
|
||||
let prompt1 = builder1.build_prompt_message();
|
||||
let prompt2 = builder2.build_prompt_message();
|
||||
|
||||
assert_ne!(
|
||||
prompt1.request_id, prompt2.request_id,
|
||||
"每次构建应该生成不同的 request_id"
|
||||
);
|
||||
}
|
||||
}
|
||||
14
qiming-rcoder/crates/agent_runner/src/testing/mod.rs
Normal file
14
qiming-rcoder/crates/agent_runner/src/testing/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! 测试辅助模块
|
||||
//!
|
||||
//! 仅在 `testing` feature 启用时编译
|
||||
|
||||
// 阻塞注入模块 (用于极端场景测试)
|
||||
#[cfg(feature = "test-blocking")]
|
||||
pub mod blocking;
|
||||
|
||||
#[cfg(feature = "test-blocking")]
|
||||
pub use blocking::{BlockingConfig, inject_blocking};
|
||||
|
||||
// 测试 Fixtures (通用测试辅助工具)
|
||||
pub mod fixtures;
|
||||
pub use fixtures::TestRequestBuilder;
|
||||
Reference in New Issue
Block a user