"添加前端模板和运行代码模块"

This commit is contained in:
Codex
2026-06-01 13:43:09 +08:00
parent 24045274ad
commit 8092c4b1f8
587 changed files with 99761 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Failed to create temporary file: {0}")]
IoError(#[from] std::io::Error),
#[error("Failed to parse JSON: {0}")]
JsonError(#[from] serde_json::Error),
}

View File

@@ -0,0 +1,131 @@
use anyhow::{Context, Result};
use log::info;
use std::path::{Path, PathBuf};
use tokio::fs::{self, File, create_dir_all};
use tokio::io::AsyncWriteExt;
use crate::model::LanguageScript;
///针对用的代码,进行检测和缓存
pub struct CodeFileCache;
impl CodeFileCache {
/// 检查代码文件缓存
pub fn obtain_code_hash(code: &str) -> String {
// 使用BLAKE3计算哈希更快速且安全
let hash = blake3::hash(code.as_bytes());
let hash_str = hash.to_hex().to_string();
info!("计算代码hash值: {}", &hash_str);
hash_str
}
/// 根据代码的hash检查是否存在缓存
pub async fn check_code_file_cache_exisht(hash: &str, language: &LanguageScript) -> bool {
fs::try_exists(Self::get_cache_file_path(hash, language))
.await
.unwrap_or(false)
}
/// 获取代码文件缓存
pub async fn get_code_file_cache(
hash: &str,
language: &LanguageScript,
) -> Result<(File, PathBuf)> {
let file_path = Self::get_cache_file_path(hash, language);
let file = File::open(&file_path)
.await
.with_context(|| format!("无法打开缓存文件: {}", file_path.display()))?;
Ok((file, file_path))
}
/// 保存代码文件缓存
pub async fn save_code_file_cache(
hash: &str,
code: &str,
language: &LanguageScript,
) -> Result<(File, PathBuf)> {
// 确保缓存目录存在
let cache_dir = Self::get_cache_dir();
//检查目录是否存在
if !fs::try_exists(&cache_dir).await.unwrap_or(false) {
create_dir_all(&cache_dir)
.await
.with_context(|| format!("无法创建缓存目录: {}", cache_dir.display()))?;
}
// 构建文件路径并保存代码
let file_path = Self::get_cache_file_path(hash, language);
let mut file = File::create(&file_path)
.await
.with_context(|| format!("无法创建缓存文件: {}", file_path.display()))?;
file.write_all(code.as_bytes())
.await
.with_context(|| "写入代码到缓存文件失败")?;
// 设置文件权限
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&file_path).await?.permissions();
perms.set_mode(0o755); // rwxr-xr-x
fs::set_permissions(&file_path, perms).await?;
}
let file_path = Self::get_cache_file_path(hash, language);
Ok((file, file_path))
}
/// 清除指定语言的缓存文件
pub async fn clear_cache_by_language(language: &LanguageScript) -> Result<()> {
let cache_dir = Self::get_cache_dir();
if !fs::try_exists(&cache_dir).await.unwrap_or(false) {
return Ok(());
}
let suffix = language.get_file_suffix();
let mut entries = fs::read_dir(cache_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if let Some(file_name) = path.file_name() {
if let Some(name_str) = file_name.to_str() {
if name_str.ends_with(suffix) {
fs::remove_file(&path)
.await
.with_context(|| format!("无法删除缓存文件: {}", path.display()))?;
}
}
}
}
Ok(())
}
/// 清除所有缓存文件
pub async fn clear_all_cache() -> Result<()> {
let cache_dir = Self::get_cache_dir();
if fs::try_exists(&cache_dir).await.unwrap_or(false) {
fs::remove_dir_all(&cache_dir)
.await
.with_context(|| format!("无法删除缓存目录: {}", cache_dir.display()))?;
}
Ok(())
}
/// 获取缓存目录路径
fn get_cache_dir() -> PathBuf {
// 在容器环境中使用固定路径
Path::new("/tmp/code_cache").to_path_buf()
}
/// 获取缓存文件路径
fn get_cache_file_path(hash: &str, language: &LanguageScript) -> PathBuf {
let mut path = Self::get_cache_dir();
// 根据语言类型添加对应的文件扩展名
let file_name = format!("{}{}", hash, language.get_file_suffix());
path.push(file_name);
path
}
}

3
qiming-run_code_rmcp/src/cache/mod.rs vendored Normal file
View File

@@ -0,0 +1,3 @@
mod code_file_cache;
pub use code_file_cache::CodeFileCache;

View File

@@ -0,0 +1,117 @@
use crate::cache::CodeFileCache;
use crate::model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript};
use anyhow::Result;
use log::{debug, error, info};
use serde_json::Value;
use std::io::Write;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
/// 通用的 Deno 脚本执行逻辑,供 JS/TS Runner 复用
pub async fn run_deno_script_with_params<F>(
code: &str,
params: Option<Value>,
timeout_seconds: Option<u64>,
lang: LanguageScript,
prepare_code_fn: F,
) -> Result<CodeScriptExecutionResult>
where
F: Fn(&str, bool) -> String,
{
debug!("开始执行{lang:?}脚本...,执行参数: {params:?}");
let hash = CodeFileCache::obtain_code_hash(code);
let cache_exist = CodeFileCache::check_code_file_cache_exisht(&hash, &lang).await;
let run_code_script_file_tuple = if cache_exist {
let cache_code = CodeFileCache::get_code_file_cache(&hash, &lang).await;
debug!("从缓存中读取代码:hash值 {:?}", &hash);
cache_code?
} else {
let wrapped_code = prepare_code_fn(code, true);
CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &lang).await?;
let code_script_file_tuple = CodeFileCache::get_code_file_cache(&hash, &lang).await?;
debug!("创建脚本缓存:hash值 {:?}", &hash);
code_script_file_tuple
};
let temp_path = run_code_script_file_tuple.1;
let mut execute_command = Command::new("deno");
execute_command
.arg("run")
.arg("--allow-net")
.arg("--allow-env")
.arg("--allow-read")
.arg("--no-check")
.arg("--v8-flags=--max-heap-size=512")
.arg(&temp_path)
.kill_on_drop(true);
// 处理参数:统一使用临时文件传递
let temp_input_path = if let Some(params) = params {
let params_json = serde_json::to_string(&params)?;
// 创建临时文件写入参数
let temp_dir = tempfile::TempDir::new()?;
let temp_file_path = temp_dir.path().join("input_params.json");
// 写入参数到临时文件
std::fs::write(&temp_file_path, params_json.as_bytes())?;
// 保持TempDir存在这样文件就不会被删除
let temp_dir_path = temp_dir.path().to_path_buf();
std::mem::forget(temp_dir);
// 设置环境变量指向临时文件
execute_command.env("INPUT_JSON_FILE", &temp_file_path);
debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path);
Some(temp_file_path)
} else {
// 没有参数时设置空对象
execute_command.env("INPUT_JSON", "{}");
None
};
debug!("Deno命令[{:?}]: {:?}", lang, &execute_command);
let executor = match timeout_seconds {
Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout),
None => CommandExecutor::default(execute_command.output()),
};
info!("执行命令: {:?}", &execute_command);
let executor_result = executor.await;
let output = match executor_result {
Ok(cmd_result) => match cmd_result {
Ok(output) => output,
Err(e) => {
error!("Deno命令执行失败 [{lang:?}]: {e:?}");
return Err(e.into());
}
},
Err(e) => {
error!("Deno任务执行异常 [{lang:?}]: {e:?}");
return Err(e.into());
}
};
debug!("标准输出:\n{}", String::from_utf8_lossy(&output.stdout));
debug!("错误输出:\n{}", String::from_utf8_lossy(&output.stderr));
// 执行完成后删除临时文件和目录
if let Some(temp_file_path) = temp_input_path {
// 删除文件
let _ = fs::remove_file(&temp_file_path).await;
// 尝试删除父目录(如果为空)
if let Some(parent) = temp_file_path.parent() {
let _ = fs::remove_dir(parent).await;
}
debug!("已删除临时文件: {:?}", temp_file_path);
}
CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await
}

View File

@@ -0,0 +1,61 @@
// deno 运行js脚本
use crate::deno_runner::common_runner::run_deno_script_with_params;
use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode};
use anyhow::Result;
#[derive(Default)]
pub struct JsRunner;
impl RunCode for JsRunner {
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
run_deno_script_with_params(
code,
params,
timeout_seconds,
LanguageScript::Js,
|c, show_logs| self.prepare_js_code(c, show_logs),
)
.await
}
}
impl JsRunner {
/// 准备JavaScript代码添加日志捕获和handler函数执行逻辑
fn prepare_js_code(&self, code: &str, show_logs: bool) -> String {
// 检查代码中是否包含ES模块特征
let is_esm = {
// 检查正式的 import/export 语句
let has_import_export = code.contains("import ")
|| code.contains("export ")
|| code.contains("import{")
|| code.contains("export{");
// 检查动态 import
let has_dynamic_import = code.contains("import(");
// 检查是否有 require - CommonJS 的标志
let has_require = code.contains("require(");
// 如果有 import/export 特征,或者动态 import但没有 require则判定为 ESM
(has_import_export || has_dynamic_import) && !has_require
};
// 根据代码特征选择合适的模板
let template = if is_esm {
include_str!("../templates/js_template_es.js")
} else {
include_str!("../templates/js_template_normal.js")
};
// 替换模板中的占位符
template
.replace("{{USER_CODE}}", code)
.replace("{{SHOW_LOGS}}", &show_logs.to_string())
}
}

View File

@@ -0,0 +1,6 @@
mod common_runner;
mod js_runner;
mod ts_runner;
pub use js_runner::JsRunner;
pub use ts_runner::TsRunner;

View File

@@ -0,0 +1,37 @@
// deno 运行ts脚本
use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode};
use anyhow::Result;
use crate::deno_runner::common_runner::run_deno_script_with_params;
#[derive(Default)]
pub struct TsRunner;
impl RunCode for TsRunner {
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
run_deno_script_with_params(
code,
params,
timeout_seconds,
LanguageScript::Ts,
|c, show_logs| self.prepare_ts_code(c, show_logs),
).await
}
}
impl TsRunner {
/// 准备TypeScript代码添加日志捕获和handler函数执行逻辑
fn prepare_ts_code(&self, code: &str, show_logs: bool) -> String {
let template = include_str!("../templates/ts_template.ts");
template
.replace("{{USER_CODE}}", code)
.replace("{{SHOW_LOGS}}", &show_logs.to_string())
}
}

View File

@@ -0,0 +1,19 @@
mod app_error;
mod cache;
mod deno_runner;
#[cfg(feature = "mcp")]
mod mcp;
mod model;
mod python_runner;
#[cfg(test)]
mod tests;
mod warm_up;
pub use cache::*;
pub use deno_runner::*;
#[cfg(feature = "mcp")]
pub use mcp::CodeRunRequest;
pub use model::RunCodeHttpResult;
pub use model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript};
pub use python_runner::*;
pub use warm_up::warm_up_all_envs;

View File

@@ -0,0 +1,194 @@
use anyhow::{Context, Result};
use clap::{Args, Parser, Subcommand};
use log::{error, info};
use serde_json::Value;
use std::{fs, path::PathBuf};
mod app_error;
mod cache;
mod deno_runner;
#[cfg(feature = "mcp")]
mod mcp;
mod model;
mod python_runner;
use crate::cache::CodeFileCache;
use crate::model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript};
#[derive(Parser)]
#[command(name = "run_code_rmcp")]
#[command(about = "Execute JavaScript and Python code using MCP SDK", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
/// Show execution logs
#[arg(short, long)]
show_logs: bool,
/// Use MCP SDK integration
#[cfg(feature = "mcp")]
#[arg(short, long)]
use_mcp: bool,
/// Clear cache before execution
#[arg(short = 'c', long)]
clear_cache: bool,
}
#[derive(Subcommand)]
enum Commands {
/// Execute JavaScript code
Js(CodeArgs),
/// Execute TypeScript code
Ts(CodeArgs),
/// Execute Python code
Python(CodeArgs),
/// Clear cache files
ClearCache {
/// Language to clear cache for (js, ts, python, or all)
#[arg(short, long)]
language: String,
},
}
#[derive(Args)]
struct CodeArgs {
/// Path to the code file
#[arg(short, long)]
file: Option<PathBuf>,
/// Code content as string
#[arg(short, long)]
code: Option<String>,
/// Parameters to pass to the script (JSON format)
#[arg(short, long)]
params: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
// 解析命令行参数
let cli = Cli::parse();
// 初始化日志
let mut builder = env_logger::Builder::from_default_env();
if cli.show_logs {
// 如果--show-logs参数为true则至少显示info级别的日志
builder.filter_level(log::LevelFilter::Info);
}
builder.init();
if let Commands::ClearCache { language } = &cli.command {
match language.to_lowercase().as_str() {
"js" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Js).await?;
info!("已清除 JavaScript 缓存");
}
"ts" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Ts).await?;
info!("已清除 TypeScript 缓存");
}
"python" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Python).await?;
info!("已清除 Python 缓存");
}
"all" => {
CodeFileCache::clear_all_cache().await?;
info!("已清除所有缓存");
}
_ => {
info!("无效的语言类型,可选项: js, ts, python, all");
return Ok(());
}
}
return Ok(());
}
// 解析传递给脚本的参数
let params = match &cli.command {
Commands::Js(args) => parse_params(&args.params)?,
Commands::Ts(args) => parse_params(&args.params)?,
Commands::Python(args) => parse_params(&args.params)?,
_ => None,
};
// 从参数获取代码
let (code, language) = match &cli.command {
Commands::Js(args) => (get_code(args)?, LanguageScript::Js),
Commands::Ts(args) => (get_code(args)?, LanguageScript::Ts),
Commands::Python(args) => (get_code(args)?, LanguageScript::Python),
_ => unreachable!(),
};
// 如果指定了清除缓存选项,则清除对应语言的缓存
if cli.clear_cache {
CodeFileCache::clear_cache_by_language(&language).await?;
info!("已清除 {language:?} 缓存");
}
// 执行代码
#[cfg(feature = "mcp")]
let result = if cli.use_mcp {
// 使用MCP SDK集成
CodeExecutor::execute_with_params_compat(&code, language, params).await?
} else {
// 直接执行
CodeExecutor::execute_with_params_compat(&code, language, params).await?
};
#[cfg(not(feature = "mcp"))]
let result = CodeExecutor::execute_with_params_compat(&code, language, params).await?;
// 打印结果
print_result(result);
Ok(())
}
fn get_code(args: &CodeArgs) -> Result<String> {
if let Some(file) = &args.file {
fs::read_to_string(file).context("Failed to read code file")
} else if let Some(code) = &args.code {
Ok(code.clone())
} else {
anyhow::bail!("Either file or code must be provided")
}
}
fn parse_params(params_str: &Option<String>) -> Result<Option<Value>> {
match params_str {
Some(s) if !s.is_empty() => {
let parsed: Value =
serde_json::from_str(s).context("Failed to parse parameters as JSON")?;
Ok(Some(parsed))
}
_ => Ok(None),
}
}
fn print_result(result: CodeScriptExecutionResult) {
if !result.logs.is_empty() {
info!("--- Logs ---");
for log in result.logs {
info!("{log}");
}
info!("------------");
}
if let Some(result_val) = result.result {
// 使用 serde_json 序列化结果,确保所有类型都能正确显示
match serde_json::to_string_pretty(&result_val) {
Ok(json_str) => info!("Result: {json_str}"),
Err(_) => info!("Result: {result_val}"),
}
}
if let Some(error) = result.error {
error!("Error: {error}");
}
}

View File

@@ -0,0 +1,164 @@
use anyhow::Result;
use rmcp::{
ErrorData as McpError, ServerHandler,
handler::server::wrapper::Parameters,
model::{
CallToolResult, Content, Implementation, ProtocolVersion, ServerCapabilities, ServerInfo,
},
tool, tool_handler, tool_router,
};
use serde::Deserialize;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
/// 代码执行请求参数
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CodeRunRequest {
#[schemars(description = "要执行的代码")]
pub code: String,
#[schemars(description = "可选的执行参数")]
pub params: Option<serde_json::Value>,
}
/// 代码执行工具服务
#[derive(Debug, Clone, Default)]
pub struct CodeRunnerService;
#[tool_router]
impl CodeRunnerService {
#[tool(description = "执行JavaScript代码并返回结果")]
async fn run_javascript(
&self,
request: Parameters<CodeRunRequest>,
) -> Result<CallToolResult, McpError> {
let request = request.0;
match CodeExecutor::execute_with_params_compat(
&request.code,
LanguageScript::Js,
request.params,
)
.await
{
Ok(result) => {
if result.success {
let content = Content::json(json!({
"result": result.result,
"logs": result.logs,
"success": true
}))?;
Ok(CallToolResult::success(vec![content]))
} else {
let content = Content::json(json!({
"success": false,
"error": result.error,
"logs": result.logs
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
Err(err) => {
let content = Content::json(json!({
"success": false,
"error": err.to_string(),
"logs": []
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
}
#[tool(description = "执行TypeScript代码并返回结果")]
async fn run_typescript(
&self,
request: Parameters<CodeRunRequest>,
) -> Result<CallToolResult, McpError> {
let request = request.0;
match CodeExecutor::execute_with_params_compat(
&request.code,
LanguageScript::Ts,
request.params,
)
.await
{
Ok(result) => {
if result.success {
let content = Content::json(json!({
"result": result.result,
"logs": result.logs,
"success": true
}))?;
Ok(CallToolResult::success(vec![content]))
} else {
let content = Content::json(json!({
"success": false,
"error": result.error,
"logs": result.logs
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
Err(err) => {
let content = Content::json(json!({
"success": false,
"error": err.to_string(),
"logs": []
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
}
#[rmcp::tool(description = "执行Python代码并返回结果")]
async fn run_python(
&self,
request: Parameters<CodeRunRequest>,
) -> Result<CallToolResult, McpError> {
let request = request.0;
match CodeExecutor::execute_with_params_compat(
&request.code,
LanguageScript::Python,
request.params,
)
.await
{
Ok(result) => {
if result.success {
let content = Content::json(json!({
"result": result.result,
"logs": result.logs,
"success": true
}))?;
Ok(CallToolResult::success(vec![content]))
} else {
let content = Content::json(json!({
"success": false,
"error": result.error,
"logs": result.logs
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
Err(err) => {
let content = Content::json(json!({
"success": false,
"error": err.to_string(),
"logs": []
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
}
}
impl ServerHandler for CodeRunnerService {
fn get_info(&self) -> ServerInfo {
ServerInfo {
protocol_version: ProtocolVersion::V_2024_11_05,
capabilities: ServerCapabilities::builder().enable_tools().build(),
server_info: Implementation::from_build_env(),
instructions: Some("一个支持执行JavaScript、TypeScript和Python代码的服务".to_string()),
}
}
}

View File

@@ -0,0 +1,3 @@
mod mcp_server;
pub use mcp_server::{CodeRunnerService,CodeRunRequest};

View File

@@ -0,0 +1,268 @@
use std::{
pin::Pin,
task::{Context, Poll},
};
use anyhow::{Context as AnyHowContext, Result};
use log::{info, warn};
use pin_project::pin_project;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::future::Future;
use tokio::{
io,
process::Command,
time::{Duration, Sleep, sleep},
};
use crate::{deno_runner::JsRunner, deno_runner::TsRunner, python_runner::PythonRunner};
///语言脚本,选择对应的语言脚本运行期
#[derive(Debug, Clone)]
pub enum LanguageScript {
Js,
Ts,
Python,
}
impl LanguageScript {
/// 获取文件后缀
pub fn get_file_suffix(&self) -> &str {
match self {
LanguageScript::Js => ".js",
LanguageScript::Ts => ".ts",
LanguageScript::Python => ".py",
}
}
}
///执行结果,包含js/python 执行结果,和打印的log日志
#[derive(Debug, Serialize, Deserialize)]
pub struct CodeScriptExecutionResult {
//js/python 执行结果
pub result: Option<Value>,
//js/python 打印的log日志
pub logs: Vec<String>,
// 是否执行成功,ture:默认值,执行成功
#[serde(skip_serializing)]
pub success: bool,
//如果执行错误的话,错误信息
#[serde(skip_serializing)]
pub error: Option<String>,
}
///运行代码的抽象
#[allow(async_fn_in_trait)]
pub trait RunCode {
///运行代码并传递参数,可选设置超时时间
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult>;
}
/// 代码执行器
pub struct CodeExecutor;
impl CodeExecutor {
/// 执行代码并传递参数,可选设置超时时间
pub async fn execute_with_params(
code: &str,
language: LanguageScript,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
info!(
"开始执行代码... 语言[{language:?}],执行参数: {params:?}"
);
match language {
LanguageScript::Js => {
JsRunner
.run_with_params(code, params, timeout_seconds)
.await
}
LanguageScript::Ts => {
TsRunner
.run_with_params(code, params, timeout_seconds)
.await
}
LanguageScript::Python => {
PythonRunner
.run_with_params(code, params, timeout_seconds)
.await
}
}
}
/// 兼容旧代码的方法,不指定超时时间
pub async fn execute_with_params_compat(
code: &str,
language: LanguageScript,
params: Option<serde_json::Value>,
) -> Result<CodeScriptExecutionResult> {
Self::execute_with_params(code, language, params, None).await
}
/// 解析执行输出
pub async fn parse_execution_output(
stdout: &[u8],
stderr: &[u8],
) -> Result<CodeScriptExecutionResult> {
let stdout_str = String::from_utf8_lossy(stdout).to_string();
let stderr_str = String::from_utf8_lossy(stderr).to_string();
// 尝试从stdout中查找JSON输出
let json_pattern = r#"\{"logs":\s*\[.*\],\s*"result":.*,\s*"error":.*\}"#;
let re = Regex::new(json_pattern)?;
if let Some(captures) = re.find(&stdout_str) {
let json_str = captures.as_str();
let parsed: serde_json::Value =
serde_json::from_str(json_str).context("Failed to parse JSON output")?;
// 从JSON中提取logs、result和error
let logs = parsed["logs"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
// 处理结果尝试解析JSON字符串
let result = if parsed["result"].is_null() {
None
} else if let Some(result_str) = parsed["result"].as_str() {
// 如果是字符串尝试解析为JSON对象
match serde_json::from_str::<Value>(result_str) {
Ok(json_value) => Some(json_value),
Err(_) => Some(Value::String(result_str.to_string())),
}
} else {
// 其他类型(数字、布尔值等)直接使用
Some(parsed["result"].clone())
};
let error = parsed["error"].as_str().map(String::from);
return Ok(CodeScriptExecutionResult {
logs,
result,
success: error.is_none(),
error,
});
}
// 如果没有找到结构化输出,返回原始输出
Ok(CodeScriptExecutionResult {
logs: if !stdout_str.is_empty() {
vec![stdout_str]
} else {
vec![]
},
result: None,
success: false,
error: Some(format!(
"Failed to extract structured output: {stderr_str}"
)),
})
}
}
/// 封装 tokio::command 的执行,并设置堆大小限制
#[allow(dead_code)]
pub struct TokioHeapSize {
//堆大小限制
pub heap_size: u64,
}
impl Default for TokioHeapSize {
fn default() -> Self {
// 堆大小限制为 2GB
Self::new(2048 * 1024 * 1024)
}
}
impl TokioHeapSize {
pub fn new(heap_size: u64) -> Self {
Self { heap_size }
}
/// 启动带有堆内存限制的子进程,返回 tokio::process::Child
#[allow(dead_code)]
pub async fn with_heap_limit(&self, command: &mut Command) {
#[cfg(target_os = "linux")]
{
use libc::{RLIMIT_AS, rlimit, setrlimit};
let heap_size = self.heap_size.clone();
unsafe {
command.pre_exec(move || {
let rlim = rlimit {
rlim_cur: heap_size,
rlim_max: heap_size,
};
if setrlimit(RLIMIT_AS, &rlim) != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
}
// macOS/Windows 下不做限制
}
}
///使用 pin-project 实现一个代码执行器,参数:timeout 超时时间; command 执行命令; 以及内置限制 command命令执行的堆大小限制
#[pin_project]
pub struct CommandExecutor<F> {
#[pin]
timeout: Sleep,
#[pin]
future: F,
}
impl<F> CommandExecutor<F> {
pub fn default(future: F) -> Self {
let timeout = sleep(Duration::from_secs(180));
Self { timeout, future }
}
pub fn with_timeout(future: F, timeout_seconds: u64) -> Self {
let timeout = sleep(Duration::from_secs(timeout_seconds));
Self { timeout, future }
}
#[allow(dead_code)]
pub fn change_timeout(&mut self, timeout: Duration) {
self.timeout = sleep(timeout);
}
}
impl<F: Future> Future for CommandExecutor<F> {
type Output = io::Result<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
//根据 heap_size ,通过setrlimit设置执行 command 的堆大小限制
match this.future.poll(cx) {
Poll::Ready(result) => Poll::Ready(Ok(result)),
Poll::Pending => match this.timeout.poll(cx) {
Poll::Ready(()) => {
warn!("执行命令超时");
Poll::Ready(Err(io::Error::new(
io::ErrorKind::TimedOut,
"future timed out",
)))
}
Poll::Pending => Poll::Pending,
},
}
}
}

View File

@@ -0,0 +1,8 @@
mod code_run_model;
mod tool_params;
pub use code_run_model::{
CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript, RunCode,
};
#[allow(unused_imports)]
pub use tool_params::RunCodeHttpResult;

View File

@@ -0,0 +1,50 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
///代码运行请求,mcp调用tool工具时传入的参数
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunCodeMessageRequest {
//js运行参数
pub(crate) json_param: HashMap<String, Value>,
//运行的代码
pub code: String,
//前端生成的随机uid,用于查找websocket连接,发送执行过程中的log日志
pub uid: String,
pub engine_type: String,
}
//http返回的结构,data的结构一般是: CodeScriptExecutionResult,就是代码脚本的执行结果
#[derive(Debug, Serialize, Deserialize)]
pub struct RunCodeHttpResult {
//js 结果,json_value
pub data: Value,
// 是否执行成功,ture:默认值,执行成功
pub success: bool,
//如果执行错误的话,错误日志
pub error: Option<String>,
}
/// 代码执行参数
#[derive(Debug, Serialize, Deserialize)]
pub struct CodeExecutionParams {
/// 要执行的代码内容
pub code: String,
/// 代码语言
pub language: String,
/// 是否显示日志
pub show_logs: bool,
}
/// 代码执行结果
#[derive(Debug, Serialize, Deserialize)]
pub struct ToolExecutionResult {
/// 执行日志
pub logs: Vec<String>,
/// 执行结果
pub result: Option<Value>,
/// 执行是否成功
pub success: bool,
/// 错误信息
pub error: Option<String>,
}

View File

@@ -0,0 +1,25 @@
WHITESPACE = _{ " " | "\t" }
alpha = { 'a'..'z' | 'A'..'Z' }
digit = { '0'..'9' }
module_name = @{ alpha ~ (alpha | digit | "_")* }
module_name_alias = @{ alpha ~ (alpha | digit | "_")* }
import_statement = ${ "import" ~ WHITESPACE+ ~ module_name}
from_import_statement = ${ "from" ~ WHITESPACE+ ~ module_name ~ WHITESPACE+ ~ ("import" ~ WHITESPACE+ ~ module_name_alias)? }
quote = {( "'" | "\"")}
importlib_statement = { module_name_alias? ~ "="? ~ "importlib.import_module" ~ "(" ~ WHITESPACE* ~ quote ~ module_name ~ quote ~ WHITESPACE* ~ ")" }
empty_line = _{ WHITESPACE* ~ ("\r\n" | "\n") }
line = _{ (!("\r\n" | "\n") ~ ANY)* ~ ("\r\n" | "\n") }
record = { from_import_statement| import_statement | importlib_statement | empty_line | line }
file = { SOI ~ record* ~ EOI }

View File

@@ -0,0 +1,3 @@
mod python_dependencies;
pub use python_dependencies::*;

View File

@@ -0,0 +1,406 @@
use anyhow::Result;
use log::{debug, info};
use pest::Parser;
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "python_imports.pest"] // 这里是你的pest语法文件
struct ImportParser;
// Python标准库模块列表使用静态变量定义
static PYTHON_STDLIB_MODULES: &[&str] = &[
"abc",
"argparse",
"array",
"ast",
"asyncio",
"atexit",
"base64",
"bdb",
"binascii",
"bisect",
"builtins",
"bz2",
"calendar",
"cmath",
"cmd",
"code",
"codecs",
"codeop",
"collections",
"colorsys",
"compileall",
"concurrent",
"configparser",
"contextlib",
"copy",
"copyreg",
"cProfile",
"csv",
"ctypes",
"curses",
"dataclasses",
"datetime",
"dbm",
"decimal",
"difflib",
"dis",
"doctest",
"email",
"encodings",
"ensurepip",
"enum",
"errno",
"faulthandler",
"fcntl",
"filecmp",
"fileinput",
"fnmatch",
"fractions",
"ftplib",
"functools",
"gc",
"getopt",
"getpass",
"gettext",
"glob",
"graphlib",
"grp",
"gzip",
"hashlib",
"heapq",
"hmac",
"html",
"http",
"idlelib",
"imaplib",
"importlib",
"inspect",
"io",
"ipaddress",
"itertools",
"json",
"keyword",
"linecache",
"locale",
"logging",
"lzma",
"mailbox",
"marshal",
"math",
"mimetypes",
"mmap",
"modulefinder",
"msvcrt",
"multiprocessing",
"netrc",
"numbers",
"operator",
"optparse",
"os",
"pathlib",
"pdb",
"pickle",
"pickletools",
"pkgutil",
"platform",
"plistlib",
"poplib",
"posix",
"pprint",
"profile",
"pstats",
"pty",
"pwd",
"py_compile",
"pyclbr",
"pydoc",
"queue",
"quopri",
"random",
"re",
"readline",
"reprlib",
"resource",
"rlcompleter",
"runpy",
"sched",
"secrets",
"select",
"selectors",
"shelve",
"shlex",
"shutil",
"signal",
"site",
"smtplib",
"socket",
"socketserver",
"sqlite3",
"ssl",
"stat",
"statistics",
"string",
"stringprep",
"struct",
"subprocess",
"sys",
"sysconfig",
"syslog",
"tabnanny",
"tarfile",
"tempfile",
"termios",
"test",
"textwrap",
"threading",
"time",
"timeit",
"tkinter",
"token",
"tokenize",
"tomllib",
"trace",
"traceback",
"tracemalloc",
"tty",
"turtle",
"turtledemo",
"types",
"typing",
"unicodedata",
"unittest",
"urllib",
"uuid",
"venv",
"warnings",
"wave",
"weakref",
"webbrowser",
"winreg",
"winsound",
"wsgiref",
"xml",
"xmlrpc",
"zipapp",
"zipfile",
"zipimport",
"zlib",
"zoneinfo",
];
pub fn parse_import(code: &str) -> Result<Vec<String>> {
let modules = parse_python_imports(code)?;
// 在非测试环境中,过滤标准库模块
let mut imports = Vec::new();
for module in modules {
if !is_standard_library(&module) {
imports.push(module);
}
}
Ok(imports)
}
// 解析Python代码中的import语句
fn parse_python_imports(python_code: &str) -> Result<Vec<String>> {
let input = if python_code.ends_with('\n') {
python_code.to_string()
} else {
format!("{python_code}\n")
};
debug!("Processing input: {input:?}");
let pairs = ImportParser::parse(Rule::file, &input)
.map_err(|e| anyhow::anyhow!("Failed to parse input: {}", e))?;
debug!("Initial parse successful");
let mut imported_modules = Vec::new();
for pair in pairs {
debug!("Top level rule: {:?}", pair.as_rule());
for record in pair.into_inner() {
debug!(
"Record rule: {:?}, text: {:?}",
record.as_rule(),
record.as_str()
);
// 遍历 record 的内部规则
for inner in record.into_inner() {
debug!(
"Inner rule: {:?}, text: {:?}",
inner.as_rule(),
inner.as_str()
);
match inner.as_rule() {
Rule::from_import_statement => {
for module in inner.into_inner() {
if let Rule::module_name = module.as_rule() {
imported_modules.push(module.as_str().to_string());
}
}
}
Rule::import_statement => {
// 遍历 import_statement 的内部规则找到 module_name
for module in inner.into_inner() {
if let Rule::module_name = module.as_rule() {
imported_modules.push(module.as_str().to_string());
}
}
}
Rule::importlib_statement => {
// 遍历 importlib_statement 的内部规则找到 module_name
for module in inner.into_inner() {
if let Rule::module_name = module.as_rule() {
imported_modules.push(module.as_str().to_string());
}
}
}
_ => {
info!("Unknown rule: {:?}", inner.as_rule());
}
}
}
}
}
debug!("Final result: {imported_modules:?}");
Ok(imported_modules)
}
// 判断是否为Python标准库模块
fn is_standard_library(module: &str) -> bool {
// 检查模块名是否在标准库列表中
PYTHON_STDLIB_MODULES.contains(&module)
}
#[cfg(test)]
mod tests {
use super::parse_import;
use anyhow::Result;
use log::{LevelFilter, info};
use std::sync::Once;
// 使用 Once 确保 env_logger 只初始化一次
static INIT: Once = Once::new();
fn setup() {
INIT.call_once(|| {
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
});
}
#[test]
fn test_line() -> Result<()> {
setup();
let python_code = "
import pandas as pd\n";
info!("Testing with input: {python_code:?}");
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["pandas"]);
Ok(())
}
#[test]
fn test_line2() -> Result<()> {
setup();
let python_code = "
numpy_alias = importlib.import_module('numpy')";
info!("Testing with input: {python_code:?}");
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["numpy"]);
Ok(())
}
#[test]
fn test_parse_import_bs() -> Result<()> {
setup();
let python_code = r#"
import requests
from bs4 import BeautifulSoup
# 发送请求到百度
url = 'https://www.baidu.com'
response = requests.get(url)
# 检查请求是否成功
if response.status_code == 200:
# 解析网页内容
soup = BeautifulSoup(response.text, 'html.parser')
# 提取你想要的信息,例如标题
title = soup.title.string
print(f"网页标题: {title}")
else:
print(f"请求失败,状态码: {response.status_code}")
"#;
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["requests", "bs4"]);
Ok(())
}
#[test]
fn test_parse_import() -> Result<()> {
setup();
let python_code = r#"
import pandas as pd
import logging
# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
import importlib
# 使用 importlib 动态导入 numpy
numpy = importlib.import_module('numpy')
# 创建一个简单的 numpy 数组
arr = numpy.array([1, 2, 3, 4, 5])
# 记录数组信息
logging.info(f"Created numpy array: {arr}")
# 进行一些简单的操作
sum_arr = numpy.sum(arr)
logging.info(f"Sum of array: {sum_arr}")
# 创建一个简单的 DataFrame
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35]
}
df = pd.DataFrame(data)
# 记录 DataFrame 信息
logging.info(f"Created DataFrame:\n{df}")
def main(args: dict) -> dict:
logging.info(f"input args: {args}")
params = args.get("params")
# 构建输出对象
ret = {
"key0": params['input'], # 拼接两次入参 input 的值
"key1": ["hello", "world"], # 输出一个数组
"key2": { # 输出一个Object
"key21": "hi"
},
}
return ret
"#;
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["pandas", "numpy"]);
Ok(())
}
}

View File

@@ -0,0 +1,5 @@
mod dependencies;
mod python_runner;
pub use dependencies::parse_import;
pub use python_runner::PythonRunner;

View File

@@ -0,0 +1,200 @@
//通过 uv 命令,来运行 python脚本
use crate::{
cache::CodeFileCache,
model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript, RunCode},
python_runner::parse_import,
};
use anyhow::{Context, Result};
use log::{debug, error, info, warn};
use std::io::Write;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
#[derive(Default)]
pub struct PythonRunner;
//定义国内python加速地址: https://mirrors.aliyun.com/pypi/simple
const PYTHON_ACCELERATION_ADDRESS: &str = "https://mirrors.aliyun.com/pypi/simple";
impl RunCode for PythonRunner {
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
debug!("开始执行Python脚本...,执行参数: {params:?}");
// 根据 code ,获取对应的hash, 对用户脚本代码,使用胶水代码处理后,缓存到文件系统里,下次使用如果hash相同,直接使用
let hash = CodeFileCache::obtain_code_hash(code);
let cache_exist =
CodeFileCache::check_code_file_cache_exisht(&hash, &LanguageScript::Python).await;
let run_code_script_file_tuple = if cache_exist {
// 从缓存中读取代码
let cache_code =
CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await;
debug!("从缓存中读取代码:hash值 {:?}", &hash);
cache_code?
} else {
// 分析用户python代码依赖
let dependencies = parse_import(code)?;
// 准备Python代码添加日志捕获和handler函数执行逻辑
let wrapped_code = self.prepare_python_code(code, true);
// 缓存代码
CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &LanguageScript::Python)
.await?;
//保存后,获取文件
let code_script_file_tuple =
CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await?;
let run_code_script_file_path = code_script_file_tuple.1.clone();
// 按照 uv命令的规范,添加用户脚本所需的依赖
// uv add --script example.py 'requests<3' 'rich' 这是添加依赖的参考命令,dependencies 是解析出来的依赖列表
// 执行添加依赖命令
if !dependencies.is_empty() {
info!("正在添加依赖: {dependencies:?}");
let mut cmd = Command::new("uv");
cmd.arg("add")
.arg("--script")
.arg(&run_code_script_file_path);
//添加 python加速地址
cmd.arg("--default-index").arg(PYTHON_ACCELERATION_ADDRESS);
// 为每个依赖添加一个参数
for dep in &dependencies {
cmd.arg(dep);
}
// 打印 cmd 命令,可以直接复制执行的命令字符串
let cmd_str = format!("{:?}", &cmd);
info!("uv命令字符串: {cmd_str}");
let cmd_output = match cmd.kill_on_drop(true).output().await {
Ok(output) => output,
Err(e) => {
error!("安装Python依赖失败: {e:?}");
error!("失败的命令: {cmd:?}");
return Err(e).context("Failed to add dependencies with uv");
}
};
let stdout = String::from_utf8_lossy(&cmd_output.stdout).to_string();
let stderr = String::from_utf8_lossy(&cmd_output.stderr).to_string();
info!("添加依赖结果 - stdout: {stdout}");
info!("添加依赖结果 - stderr: {stderr}");
if !cmd_output.status.success() {
warn!("添加依赖失败,状态码: {}", cmd_output.status);
}
}
debug!("创建脚本缓存:hash值 {:?}", &hash);
code_script_file_tuple
};
let temp_path = run_code_script_file_tuple.1;
// 使用uv run命令执行Python脚本提供隔离环境
let mut execute_command = Command::new("uv");
//还需要指定国内镜像地址,参考示例: uv run -s -p 3.13 d5ebe48b7d9da8cb835af6ef77b212921f9a44881fb232837b4dcc6ebecf9401.py --default-index https://mirrors.aliyun.com/pypi/simple
execute_command
.arg("run")
.arg("-s") // 明确指定作为脚本运行
// .arg("-p")
// .arg("3.13") // 指定Python解释器版本3.13
.arg("--default-index")
.arg(PYTHON_ACCELERATION_ADDRESS)
.arg(&temp_path)
.kill_on_drop(true);
// 处理参数:统一使用临时文件传递
let temp_input_path = if let Some(params) = params {
let params_json = serde_json::to_string(&params)?;
// 创建临时文件写入参数
let temp_dir = tempfile::TempDir::new()?;
let temp_file_path = temp_dir.path().join("input_params.json");
// 写入参数到临时文件
std::fs::write(&temp_file_path, params_json.as_bytes())?;
// 保持TempDir存在这样文件就不会被删除
let temp_dir_path = temp_dir.path().to_path_buf();
std::mem::forget(temp_dir);
// 设置环境变量指向临时文件
execute_command.env("INPUT_JSON_FILE", &temp_file_path);
debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path);
Some(temp_file_path)
} else {
// 没有参数时设置空对象
execute_command.env("INPUT_JSON", "{}");
None
};
info!("执行命令: {:?}", &execute_command);
// let tokio_child_command = TokioHeapSize::default();
// // 设置堆大小限制
// tokio_child_command
// .with_heap_limit(&mut execute_command)
// .await;
//限制command 的执行超时时间
let executor = match timeout_seconds {
Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout),
None => CommandExecutor::default(execute_command.output()),
};
let executor_result = executor.await;
let output = match executor_result {
Ok(cmd_result) => match cmd_result {
Ok(output) => output,
Err(e) => {
error!("Python命令执行失败: {e:?}");
return Err(e.into());
}
},
Err(e) => {
error!("Python任务执行异常: {e:?}");
return Err(e.into());
}
};
// 调试输出
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
debug!("Python stdout: {stdout}");
debug!("Python stderr: {stderr}");
// 执行完成后删除临时文件和目录
if let Some(temp_file_path) = temp_input_path {
// 删除文件
let _ = fs::remove_file(&temp_file_path).await;
// 尝试删除父目录(如果为空)
if let Some(parent) = temp_file_path.parent() {
let _ = fs::remove_dir(parent).await;
}
debug!("已删除临时文件: {:?}", temp_file_path);
}
// 解析输出
CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await
}
}
impl PythonRunner {
/// 准备Python代码添加日志捕获和handler函数执行逻辑
fn prepare_python_code(&self, code: &str, show_logs: bool) -> String {
let show_logs_value = if show_logs { "True" } else { "False" };
let template = include_str!("../templates/python_template.py");
template
.replace("{{USER_CODE}}", code)
.replace("{{SHOW_LOGS}}", show_logs_value)
}
}

View File

@@ -0,0 +1,166 @@
use anyhow::{Context, Result};
use clap::Parser;
use log::{error, info};
use rmcp::ServiceExt;
use tokio::io::{stdin, stdout};
mod app_error;
mod cache;
mod deno_runner;
mod mcp;
mod model;
mod python_runner;
use mcp::CodeRunnerService;
/// MCP脚本运行器 - 通过MCP协议执行JavaScript、TypeScript和Python代码
#[derive(Parser)]
#[command(name = "script_runner")]
#[command(author = "MCP Script Runner")]
#[command(version = "0.1.0")]
#[command(about = "通过MCP协议执行JavaScript、TypeScript和Python代码", long_about = None)]
struct Cli {
/// 启用详细日志输出
#[arg(short, long)]
verbose: bool,
}
/// 启动MCP服务器
async fn start_mcp_server(verbose: bool) -> Result<()> {
if verbose {
info!("初始化 MCP 服务...");
}
// 创建服务实例
let service = CodeRunnerService::default();
// 使用标准输入输出作为传输方式
let transport = (stdin(), stdout());
if verbose {
info!("MCP 服务已启动,等待连接...");
}
// 启动服务
let server = service.serve(transport).await.context("启动MCP服务失败")?;
// 等待服务结束
let result = server.waiting().await;
if verbose {
match &result {
Ok(reason) => error!("MCP 服务已停止: {:?}", reason),
Err(err) => error!("MCP 服务出错: {}", err),
}
}
result.map(|_| ()).map_err(Into::into)
}
#[tokio::main]
async fn main() -> Result<()> {
// 解析命令行参数
let cli = Cli::parse();
// 启动MCP服务
start_mcp_server(cli.verbose).await
}
#[cfg(test)]
mod tests {
use super::*;
use rmcp::{model::CallToolRequestParam, ServiceExt};
use tokio::sync::oneshot;
use tokio::time::timeout;
use std::time::Duration;
#[tokio::test]
#[ignore = "MCP client test requires proper service implementation - TODO: fix later"]
async fn test_start_mcp_server() -> Result<()> {
// 创建管道,模拟标准输入输出
let (client_stream, server_stream) = tokio::io::duplex(8192);
// 分割成读写部分
let (server_read, server_write) = tokio::io::split(server_stream);
let (client_read, client_write) = tokio::io::split(client_stream);
// 使用通道控制服务器生命周期
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
// 在单独的任务中启动服务器
let server_task = tokio::spawn(async move {
// 创建服务实例
let service = CodeRunnerService::default();
// 创建服务Future并pin
let service_fut = service.serve((server_read, server_write));
tokio::pin!(service_fut);
// 等待关闭信号或服务器完成
tokio::select! {
res = &mut service_fut => {
match res {
Ok(server) => {
match server.waiting().await {
Ok(reason) => println!("服务正常结束: {:?}", reason),
Err(e) => println!("服务错误: {}", e),
}
}
Err(e) => println!("启动服务失败: {}", e),
}
}
_ = shutdown_rx => {
println!("收到关闭信号");
}
}
});
// 给服务器一点时间启动
tokio::time::sleep(Duration::from_millis(200)).await;
// 使用RMCP客户端SDK连接到服务器
let client = ().serve((client_read, client_write)).await?;
// 获取服务器信息
let server_info = client.peer_info();
println!("服务器信息: {:?}", server_info);
// 验证服务器信息
assert!(!server_info.unwrap().instructions.is_none(), "服务器应该提供说明");
// 测试执行JavaScript代码
let js_code = "function handler(input) { return {success: true, message: 'JavaScript测试成功'}; }";
let result = client.call_tool(CallToolRequestParam {
name: "run_javascript".into(),
arguments: serde_json::json!({
"code": js_code,
}).as_object().cloned(),
}).await?;
// 验证JavaScript结果这里只简单验证调用成功
println!("JavaScript执行结果: {:?}", result);
// 测试执行Python代码
let py_code = "def handler(input):\n return {'success': True, 'message': 'Python测试成功'}";
let result = client.call_tool(CallToolRequestParam {
name: "run_python".into(),
arguments: serde_json::json!({
"code": py_code,
}).as_object().cloned(),
}).await?;
// 验证Python结果这里只简单验证调用成功
println!("Python执行结果: {:?}", result);
// 关闭客户端
client.cancel().await?;
// 发送服务器关闭信号
let _ = shutdown_tx.send(());
// 等待服务器任务结束
let _ = timeout(Duration::from_secs(5), server_task).await;
Ok(())
}
}

View File

@@ -0,0 +1,92 @@
// @ts-nocheck
// ES模块格式支持import/export语句
// 保存原始console.log
const originalConsoleLog = console.log;
let logs = [];
// 替换console.log以捕获日志
console.log = function() {
// 将参数转换为字符串并连接它们
const message = Array.from(arguments).map(arg =>
typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
).join(' ');
// 存储日志
logs.push(message);
// 如果显示日志,也输出到原始控制台
if ({{SHOW_LOGS}}) {
originalConsoleLog.apply(console, arguments);
}
};
// 异步读取输入参数的函数
async function readInputParams() {
let input = {};
try {
const inputFile = Deno.env.get("INPUT_JSON_FILE");
if (inputFile) {
const inputJson = await Deno.readTextFile(inputFile);
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
} else {
// 兼容旧的环境变量方式
const inputJson = Deno.env.get("INPUT_JSON");
if (inputJson) {
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
}
}
} catch (error) {
console.error("解析输入参数失败:", error);
}
return input;
}
// 用户代码
{{USER_CODE}}
// 执行handler函数并获取结果
let result = null;
// 异步立即执行函数
(async () => {
// 读取输入参数
const input = await readInputParams();
try {
// 优先检查main函数
if (typeof main === 'function') {
// 检查main是否是异步函数
if (main.constructor.name === 'AsyncFunction') {
result = await main(input);
} else {
result = main(input);
}
} else if (typeof handler === 'function') {
// 如果没有main函数检查handler
if (handler.constructor.name === 'AsyncFunction') {
result = await handler(input);
} else {
result = handler(input);
}
}else{
throw new Error("没有找到main或handler函数");
}
// 打印最终输出为JSON
originalConsoleLog(JSON.stringify({
logs: logs,
result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null,
error: null
}));
} catch (error) {
// 处理错误
originalConsoleLog(JSON.stringify({
logs: logs,
result: null,
error: error.toString()
}));
}
})();

View File

@@ -0,0 +1,93 @@
// @ts-nocheck
// 普通脚本格式
// 保存原始console.log
const originalConsoleLog = console.log;
let logs = [];
// 替换console.log以捕获日志
console.log = function() {
// 将参数转换为字符串并连接它们
const message = Array.from(arguments).map(arg =>
typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
).join(' ');
// 存储日志
logs.push(message);
// 如果显示日志,也输出到原始控制台
if ({{SHOW_LOGS}}) {
originalConsoleLog.apply(console, arguments);
}
};
// 异步读取输入参数的函数
async function readInputParams() {
let input = {};
try {
const inputFile = Deno.env.get("INPUT_JSON_FILE");
if (inputFile) {
const inputJson = await Deno.readTextFile(inputFile);
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
} else {
// 兼容旧的环境变量方式
const inputJson = Deno.env.get("INPUT_JSON");
if (inputJson) {
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
}
}
} catch (error) {
console.error("解析输入参数失败:", error);
}
return input;
}
// 异步立即执行函数
(async () => {
// 读取输入参数
const input = await readInputParams();
try {
// 用户代码开始
{{USER_CODE}}
// 用户代码结束
// 执行函数并获取结果
let result = null;
// 优先检查main函数
if (typeof main === 'function') {
// 检查main是否是异步函数
if (main.constructor.name === 'AsyncFunction') {
result = await main(input);
} else {
result = main(input);
}
} else if (typeof handler === 'function') {
// 如果没有main函数检查handler
if (handler.constructor.name === 'AsyncFunction') {
result = await handler(input);
} else {
result = handler(input);
}
} else {
throw new Error("没有找到main或handler函数");
}
// 打印最终输出为JSON
originalConsoleLog(JSON.stringify({
logs: logs,
result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null,
error: null
}));
} catch (error) {
// 处理错误
originalConsoleLog(JSON.stringify({
logs: logs,
result: null,
error: error.toString()
}));
}
})();

View File

@@ -0,0 +1,134 @@
import sys
import json
import os
import logging
from io import StringIO
# 保存原始的stdout
original_stdout = sys.stdout
logs = []
# 创建一个StringIO对象来捕获输出
class LogCapture:
def __init__(self, show_logs=False):
self.buffer = StringIO()
self.show_logs = show_logs
def write(self, text):
if text.strip(): # 忽略空行
logs.append(text.rstrip())
if self.show_logs:
original_stdout.write(text)
def flush(self):
self.buffer.flush()
if self.show_logs:
original_stdout.flush()
# 替换sys.stdout为我们的捕获器
sys.stdout = LogCapture(show_logs={{SHOW_LOGS}})
# 配置logging将日志输出重定向到我们的捕获器
class LoggingHandler(logging.Handler):
def emit(self, record):
msg = self.format(record)
print(f"[{record.levelname}] {msg}")
# 配置根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# 移除所有现有的处理程序
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# 添加我们自定义的处理程序
handler = LoggingHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
root_logger.addHandler(handler)
# 从环境变量获取输入参数
args = {}
has_input = False
try:
# 优先从临时文件读取参数
input_file = os.environ.get('INPUT_JSON_FILE')
if input_file:
with open(input_file, 'r', encoding='utf-8') as f:
input_json = f.read()
args = json.loads(input_json)
has_input = True
print(f"接收到的参数: {args}")
else:
# 兼容旧的环境变量方式
input_json = os.environ.get('INPUT_JSON')
if input_json:
args = json.loads(input_json)
has_input = True
print(f"接收到的参数: {args}")
# 确保参数同时可以通过args直接访问也可以通过args.get("params")访问
# 如果args中没有params键但有其他键则将整个args作为params的值
if has_input and "params" not in args and len(args) > 0:
args["params"] = args.copy()
# 如果params存在但为None则初始化为空字典
elif has_input and args.get("params") is None:
args["params"] = {}
except Exception as e:
print(f"解析输入参数失败: {e}")
# 用户代码开始
{{USER_CODE}}
try:
# 执行handler函数或main函数并获取结果
result = None
if 'handler' in globals() and callable(globals()['handler']):
# 优先使用 handler 函数
try:
if has_input:
result = handler(args)
else:
# handler 函数不接受参数,无参数调用
result = handler()
# 确保结果不为 None
if result is None:
print("警告: handler 函数返回了 None")
except Exception as e:
print(f"执行 handler 函数时出错: {e}")
elif 'main' in globals() and callable(globals()['main']):
# 如果没有 handler 函数,尝试使用 main 函数
try:
result = main(args)
except Exception as e:
print(f"执行 main 函数时出错: {e}")
# 打印最终输出为JSON
sys.stdout = original_stdout
# 根据结果类型选择合适的处理方式
result_json = None
if result is not None:
if isinstance(result, (dict, list)):
# 如果是字典或列表,直接使用 json.dumps 序列化
result_json = json.dumps(result)
elif isinstance(result, (int, float, bool)) or result is None:
# 如果是基本类型,直接传递给外层 JSON
result_json = result
else:
# 其他类型(如字符串)转换为字符串
result_json = str(result)
print(json.dumps({
'logs': logs,
'result': result_json,
'error': None
}))
except Exception as e:
# 处理错误
import traceback
error_msg = f"{str(e)}\n{traceback.format_exc()}"
# 处理错误
sys.stdout = original_stdout
print(json.dumps({
'logs': logs,
'result': None,
'error': error_msg
}))

View File

@@ -0,0 +1,96 @@
// TypeScript类型声明
// @ts-nocheck
type LogFunction = (...args: any[]) => void;
type Handler = (input: any) => any;
// Save original console.log
const originalConsoleLog: LogFunction = console.log;
let logs: string[] = [];
// Replace console.log to capture logs
console.log = function(...args: any[]): void {
// Convert arguments to string and join them
const message = args.map(arg =>
typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
).join(' ');
// Store log
logs.push(message);
// Also log to original console if showing logs
if ({{SHOW_LOGS}}) {
originalConsoleLog.apply(console, args);
}
};
// 异步读取输入参数的函数
async function readInputParams(): Promise<any> {
let input: any = {};
try {
const inputFile = Deno.env.get("INPUT_JSON_FILE");
if (inputFile) {
const inputJson = await Deno.readTextFile(inputFile);
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
} else {
// 兼容旧的环境变量方式
const inputJson = Deno.env.get("INPUT_JSON");
if (inputJson) {
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
}
}
} catch (error) {
console.error("解析输入参数失败:", error);
}
return input;
}
async function executeHandler() {
try {
// Add the original code
{{USER_CODE}}
// 读取输入参数
const input = await readInputParams();
// Execute handler function and get result
let result: any = null;
// 优先检查main函数
if (typeof main === 'function') {
// 检查main是否是异步函数
if (main.constructor.name === 'AsyncFunction') {
result = await (main as (input: any) => Promise<any>)(input);
} else {
result = (main as Handler)(input);
}
} else if (typeof handler === 'function') {
// 如果没有main函数检查handler
if (handler.constructor.name === 'AsyncFunction') {
result = await (handler as (input: any) => Promise<any>)(input);
} else {
result = (handler as Handler)(input);
}
} else {
throw new Error("没有找到main或handler函数");
}
// Print final output as JSON
originalConsoleLog(JSON.stringify({
logs: logs,
result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null,
error: null
}));
} catch (error) {
// Handle errors
originalConsoleLog(JSON.stringify({
logs: logs,
result: null,
error: String(error)
}));
}
}
// 执行并等待结果
executeHandler();

View File

@@ -0,0 +1,740 @@
#[cfg(test)]
mod js_tests {
use anyhow::Result;
use log::info;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
use crate::tests::test_utils::setup;
#[tokio::test]
async fn test_js_basic_execution() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js.js")?;
info!("读取测试脚本: test_js.js");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, None).await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
Ok(())
}
#[tokio::test]
async fn test_js_with_params() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js_params.js")?;
info!("读取测试脚本: test_js_params.js");
// 准备参数
let params = json!({
"a": 10,
"b": 20,
"name": "测试用户"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("sum"), "结果应包含 sum 字段");
assert!(json_str.contains("greeting"), "结果应包含 greeting 字段");
assert!(json_str.contains("message"), "结果应包含 message 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_object_result() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_test1.js")?;
info!("读取测试脚本: rfunction_test1.js");
// 准备参数
let params = json!({
"a": 1,
"b": 2
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("3"), "message 字段应为 3");
}
Ok(())
}
#[tokio::test]
async fn test_js_with_timeout() -> Result<()> {
// 初始化日志
setup();
// 创建一个会超时的JavaScript脚本
let code = r#"
// 创建一个长时间运行的脚本
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function handler(input) {
console.log("开始执行耗时操作");
// 这个操作会运行10秒钟
for (let i = 0; i < 10; i++) {
console.log(`已经执行了 ${i+1} 秒`);
await sleep(1000); // 等待1秒
}
console.log("操作完成");
return { result: "完成" };
}
"#;
info!("创建了一个会运行10秒的测试脚本");
// 准备参数
let params = json!({
"test": "超时测试"
});
info!("准备测试参数: {params:?}");
// 设置2秒超时并执行脚本
info!("开始执行JavaScript脚本设置2秒超时...");
let start_time = std::time::Instant::now();
let result =
CodeExecutor::execute_with_params(code, LanguageScript::Js, Some(params), Some(2))
.await;
let elapsed = start_time.elapsed();
// 检查是否在2-3秒内超时给一点缓冲时间
info!("脚本执行耗时: {elapsed:?}");
assert!(elapsed.as_secs() >= 2, "脚本应该至少运行2秒");
assert!(elapsed.as_secs() < 4, "脚本应该在4秒内超时");
// 检查是否返回超时错误
match result {
Ok(exec_result) => {
if let Some(error) = exec_result.error {
info!("正确捕获到超时错误: {error}");
assert!(
error.contains("timed out") ||
error.contains("TimedOut") ||
error.contains("executor await error"),
"错误信息应该包含超时相关信息"
);
} else {
panic!("应该捕获到超时错误,但脚本执行成功了");
}
}
Err(e) => {
// 使用特殊格式化获取完整错误链
let full_error = format!("{e:#}");
info!("捕获到错误: {full_error}");
assert!(
full_error.contains("timed out"),
"完整错误链中应该包含'timed out'超时信息"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_js_cow_say_hello() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/cow_say_hello.js")?;
info!("读取测试脚本: cow_say_hello.js");
// 准备参数
let params = json!({
"a": 5,
"b": 7
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params(&code, LanguageScript::Js, Some(params), None)
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
// 检查计算结果是否正确 (5 + 7 = 12)
if let Some(obj) = result_val.as_object() {
if let Some(message) = obj.get("message") {
assert_eq!(message, 12, "message 字段的值应为 12");
}
}
}
Ok(())
}
#[tokio::test]
async fn test_js_import_deno_std() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_deno_std_example.js")?;
info!("读取测试脚本: import_deno_std_example.js");
// 准备参数
let params = json!({
"path": "test.txt"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("pathInfo"), "结果应包含 pathInfo 字段");
assert!(
json_str.contains("fileContent"),
"结果应包含 fileContent 字段"
);
}
Ok(())
}
#[tokio::test]
async fn test_js_import_jsr() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_jsr_example.js")?;
info!("读取测试脚本: import_jsr_example.js");
// 准备参数
let params = json!({
"a": 10,
"b": 5,
"expected": 15
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("results"), "结果应包含 results 字段");
assert!(json_str.contains("summary"), "结果应包含 summary 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_local_module() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_local_module_example.js")?;
info!("读取测试脚本: import_local_module_example.js");
// 准备参数
let params = json!({
"order": {
"items": [
{ "name": "测试产品", "price": 200, "quantity": 2 }
],
"taxRate": 0.05
}
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("order"), "结果应包含 order 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_axios() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_axios_example.js")?;
info!("读取测试脚本: import_axios_example.js");
// 准备参数
let params = json!({
"url": "https://jsonplaceholder.typicode.com/todos/1"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("data"), "结果应包含 data 字段");
assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_esm() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_esm_module_example.js")?;
info!("读取测试脚本: import_esm_module_example.js");
// 准备参数
let params = json!({
"data": "测试数据",
"salt": "test-salt-123"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("result"), "结果应包含 result 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_lodash() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_lodash_example.js")?;
info!("读取测试脚本: import_lodash_example.js");
// 准备参数
let params = json!({
"data": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"chunkSize": 3
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(
json_str.contains("originalData"),
"结果应包含 originalData 字段"
);
assert!(
json_str.contains("processedData"),
"结果应包含 processedData 字段"
);
assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_main_function() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_js_test3.js")?;
info!("读取测试脚本: rfunction_js_test3.js");
// 准备参数
let params = json!({
"testKey": "测试值"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("key"), "结果应包含 key 字段");
assert!(json_str.contains("value"), "key 字段的值应为 value");
// 检查JSON结构
if let Some(obj) = result_val.as_object() {
if let Some(key_value) = obj.get("key") {
assert_eq!(key_value, "value", "key 字段的值应为 value");
}
}
}
Ok(())
}
#[tokio::test]
async fn test_js_large_parameters() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js_large_params.js")?;
info!("读取测试脚本: test_js_large_params.js");
// 生成大于2MB的文本参数
fn generate_large_text(size_in_mb: usize) -> String {
let chunk = "This is a test chunk of text designed to generate large parameters for testing the Argument list too long fix. ".repeat(100);
let iterations = (size_in_mb * 1024 * 1024) / chunk.len();
let mut result = String::new();
for i in 0..iterations {
result.push_str(&chunk);
result.push_str(&format!("Iteration {}\n", i));
}
result
}
let large_text = generate_large_text(3); // 3MB
info!("生成了3MB大小的文本参数实际大小: {:.2} MB", large_text.len() as f64 / 1024.0 / 1024.0);
// 准备参数
let params = json!({
"largeText": large_text
});
info!("准备测试参数JSON大小: {:.2} MB", serde_json::to_string(&params).unwrap().len() as f64 / 1024.0 / 1024.0);
// 执行脚本
info!("开始执行JavaScript脚本测试大参数处理...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误,这表明大参数处理成功");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值包含成功处理大参数的信息
if let Some(result_val) = result.result {
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("success"), "结果应包含 success 字段");
assert!(json_str.contains("true"), "success 应为 true");
assert!(json_str.contains("Successfully processed large text parameter"), "结果应包含成功消息");
assert!(json_str.contains("sizeMB"), "结果应包含 sizeMB 字段");
// 验证返回值是对象格式
if let Some(obj) = result_val.as_object() {
if let Some(success) = obj.get("success") {
assert_eq!(success, true, "success 字段的值应为 true");
}
if let Some(text_size) = obj.get("textSize") {
assert!(text_size.is_number(), "textSize 应为数字");
let size = text_size.as_u64().unwrap();
assert!(size > 2 * 1024 * 1024, "文本大小应大于2MB");
}
}
}
info!("大参数测试通过!临时文件解决方案有效。");
Ok(())
}
}

View File

@@ -0,0 +1,90 @@
#[cfg(test)]
pub mod test_utils {
use log::LevelFilter;
use log::info;
use std::fs;
use std::path::Path;
use std::sync::Once;
// 使用 Once 确保 env_logger 只初始化一次
static INIT: Once = Once::new();
// 控制是否清理缓存的环境变量名
const CLEAN_CACHE_ENV: &str = "CLEAN_TEST_CACHE";
pub fn setup() {
INIT.call_once(|| {
// 尝试初始化日志,如果失败则忽略
let _ = std::panic::catch_unwind(|| {
env_logger::builder().filter_level(LevelFilter::Info).init();
});
});
// 只有当环境变量设置为"1"时才清理缓存
if let Ok(clean_cache) = std::env::var(CLEAN_CACHE_ENV) {
if clean_cache == "1" {
clean_cache_dir();
}
}
}
// 清理缓存目录函数
fn clean_cache_dir() {
let cache_dir = Path::new("/tmp/code_cache");
// 如果缓存目录存在,清理其中的文件
if cache_dir.exists() {
info!("正在清理缓存目录: {cache_dir:?}");
match fs::read_dir(cache_dir) {
Ok(entries) => {
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.is_file() {
if let Err(e) = fs::remove_file(&path) {
info!("删除文件失败 {path:?}: {e}");
} else {
info!("已删除缓存文件: {path:?}");
}
}
}
}
info!("缓存目录清理完成");
}
Err(e) => {
info!("读取缓存目录失败: {e}");
}
}
} else {
// 如果缓存目录不存在,创建它
info!("缓存目录不存在,创建目录: {cache_dir:?}");
if let Err(e) = fs::create_dir_all(cache_dir) {
info!("创建缓存目录失败: {e}");
} else {
info!("缓存目录创建成功");
}
}
// 确保缓存目录存在且可写
if !cache_dir.exists() {
if let Err(e) = fs::create_dir_all(cache_dir) {
info!("创建缓存目录失败: {e}");
}
}
// 设置目录权限为777所有用户可读写执行
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Err(e) = fs::set_permissions(cache_dir, fs::Permissions::from_mode(0o775)) {
info!("设置缓存目录权限失败: {e}");
} else {
info!("设置缓存目录权限成功");
}
}
}
}
pub mod js_tests;
pub mod python_tests;
pub mod ts_tests;

View File

@@ -0,0 +1,591 @@
#[cfg(test)]
mod python_tests {
use anyhow::Result;
use log::info;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
use crate::tests::test_utils::setup;
#[tokio::test]
async fn test_python_basic_execution() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python.py")?;
info!("读取测试脚本: test_python.py");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, None).await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
// 检查日志中是否包含预期的输出
let logs_str = result.logs.join(" ");
assert!(
logs_str.contains("Handler function called"),
"日志应包含 'Handler function called'"
);
assert!(
logs_str.contains("Final calculation completed"),
"日志应包含 'Final calculation completed'"
);
assert!(
logs_str.contains("The product of [1, 2, 3, 4, 5] is 120"),
"日志应包含计算结果"
);
Ok(())
}
#[tokio::test]
async fn test_python_with_params() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_params.py")?;
info!("读取测试脚本: test_python_params.py");
// 准备参数
let params = json!({
"a": 10,
"b": 20
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证返回值包含预期的字段
if let Some(result_val) = result.result {
// 结果应该是 JSON 对象Python 字典被正确解析)
assert!(result_val.get("sum").is_some(), "结果应包含 sum 字段");
assert!(result_val.get("numbers").is_some(), "结果应包含 numbers 字段");
assert!(result_val.get("message").is_some(), "结果应包含 message 字段");
}
Ok(())
}
#[tokio::test]
async fn test_python_different_return_types() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_types.py")?;
info!("读取测试脚本: test_python_types.py");
// 测试字符串类型
let params = json!({"type": "string"});
info!("测试字符串类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
assert!(result_val.is_string(), "结果应为字符串类型");
}
// 测试数字类型
let params = json!({"type": "number"});
info!("测试数字类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
assert!(result_val.is_number(), "结果应为数字类型");
assert_eq!(result_val.as_i64().unwrap(), 12345, "结果应为 12345");
}
// 测试布尔类型
let params = json!({"type": "boolean"});
info!("测试布尔类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
assert!(result_val.is_boolean(), "结果应为布尔类型");
assert!(result_val.as_bool().unwrap(), "结果应为 true");
}
// 测试列表类型
let params = json!({"type": "list"});
info!("测试列表类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
// 结果应该是数组类型Python列表被正确解析
assert!(result_val.is_array(), "结果应为数组类型");
assert_eq!(result_val.as_array().unwrap().len(), 6, "数组长度应为 6");
}
// 测试字典类型
let params = json!({"type": "dict"});
info!("测试字典类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
// 结果应该是对象类型Python字典被正确解析
assert!(result_val.is_object(), "结果应为对象类型");
assert!(result_val.get("name").is_some(), "结果应包含 name 字段");
assert!(result_val.get("age").is_some(), "结果应包含 age 字段");
assert!(result_val.get("tags").is_some(), "结果应包含 tags 字段");
}
// 测试 None 类型
let params = json!({"type": "null"});
info!("测试 None 类型, 参数: {params:?}");
let _result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
Ok(())
}
// 此测试使用标准库替代pandas不再需要忽略
#[tokio::test]
async fn test_python_with_pandas() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_test2.py")?;
info!("读取测试脚本: rfunction_test2.py");
// 检查依赖识别
let dependencies = crate::python_runner::parse_import(&code)?;
info!("识别到的依赖: {dependencies:?}");
// 验证依赖识别结果 - 应该包含pandas依赖
assert!(!dependencies.is_empty(), "依赖列表不应为空");
assert!(
dependencies.contains(&"pandas".to_string()),
"依赖列表应包含pandas"
);
// 准备参数
let params = json!({
"params": {
"input": "测试数据"
}
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
// 如果有执行错误,我们只验证依赖识别是否正确,不再检查执行结果
// 这是因为pandas可能未安装或环境问题导致执行失败
return Ok(());
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 如果脚本执行成功,才验证结果
// 验证日志和结果
if result.logs.is_empty() {
info!("警告: 日志为空,但脚本执行成功");
} else {
info!("捕获到的日志数量: {}", result.logs.len());
// 逐条打印日志
for (i, log) in result.logs.iter().enumerate() {
info!("日志[{i}]: {log}");
}
// 检查日志中是否包含logging.info的输出
let logs_str = result.logs.join(" ");
info!("合并后的日志字符串: {logs_str}");
assert!(
logs_str.contains("Created data structure"),
"日志应包含'Created data structure'"
);
assert!(logs_str.contains("input args"), "日志应包含'input args'");
}
// 如果有结果,验证返回值包含预期的字段
if let Some(result_val) = result.result {
// 结果应该是JSON对象Python字典被正确解析
assert!(result_val.get("key0").is_some(), "结果应包含 key0 字段");
assert_eq!(
result_val["key0"].as_str().unwrap(),
"测试数据",
"key0 应等于输入参数"
);
assert!(result_val.get("key1").is_some(), "结果应包含 key1 字段");
assert!(result_val["key1"].is_array(), "key1 应为数组");
assert!(result_val.get("key2").is_some(), "结果应包含 key2 字段");
assert!(result_val["key2"].is_object(), "key2 应为对象");
assert!(result_val["key2"].get("key21").is_some(), "key2.key21 应存在");
}
Ok(())
}
#[tokio::test]
async fn test_python_params_access() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_simple.py")?;
info!("读取测试脚本: test_python_simple.py");
// 准备参数 - 直接提供input参数
let params = json!({
"input": "直接提供的参数"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result = CodeExecutor::execute_with_params_compat(
&code,
LanguageScript::Python,
Some(params.clone()),
)
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
panic!("执行出错: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
if let Some(result_str) = result_val.as_str() {
let json_val = serde_json::from_str::<serde_json::Value>(result_str)?;
// 验证两种访问方式都能获取到参数
assert_eq!(
json_val["direct_access"], "直接提供的参数",
"直接访问应该能获取到参数"
);
assert_eq!(
json_val["nested_access"], "直接提供的参数",
"嵌套访问也应该能获取到参数"
);
// 验证args结构中同时包含直接参数和params嵌套参数
assert!(
json_val["args_structure"].get("input").is_some(),
"args结构应包含直接参数"
);
assert!(
json_val["args_structure"].get("params").is_some(),
"args结构应包含params参数"
);
}
}
// 准备参数 - 通过params嵌套提供
let nested_params = json!({
"params": {
"input": "嵌套提供的参数"
}
});
info!("准备嵌套测试参数: {nested_params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result = CodeExecutor::execute_with_params_compat(
&code,
LanguageScript::Python,
Some(nested_params),
)
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
panic!("执行出错: {error}");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
if let Some(result_str) = result_val.as_str() {
let json_val = serde_json::from_str::<serde_json::Value>(result_str)?;
// 验证嵌套参数可以被访问
assert_eq!(
json_val["nested_access"], "嵌套提供的参数",
"应该能通过params.input访问到参数"
);
// 验证args结构
assert!(
json_val["args_structure"].get("params").is_some(),
"args结构应包含params参数"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_python_logging() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_logging.py")?;
info!("读取测试脚本: test_python_logging.py");
// 准备参数
let params = json!({
"test": "日志测试"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
panic!("执行出错: {error}");
} else {
info!("脚本执行成功");
}
// 验证日志捕获
assert!(!result.logs.is_empty(), "日志不应为空");
info!("捕获到的日志数量: {}", result.logs.len());
// 逐条打印日志
for (i, log) in result.logs.iter().enumerate() {
info!("日志[{i}]: {log}");
}
// 检查是否捕获了所有级别的日志
let logs_str = result.logs.join(" ");
// DEBUG级别的日志可能不会被捕获因为Python胶水代码中可能设置了最低级别为INFO
// assert!(logs_str.contains("DEBUG级别"), "应捕获DEBUG级别的日志");
assert!(logs_str.contains("INFO级别"), "应捕获INFO级别的日志");
assert!(logs_str.contains("WARNING级别"), "应捕获WARNING级别的日志");
assert!(logs_str.contains("ERROR级别"), "应捕获ERROR级别的日志");
assert!(
logs_str.contains("CRITICAL级别"),
"应捕获CRITICAL级别的日志"
);
assert!(
logs_str.contains("格式化的JSON数据"),
"应捕获格式化的JSON数据"
);
// 验证返回值
if let Some(result_val) = result.result {
// 结果应该是JSON对象Python字典被正确解析
assert_eq!(
result_val["message"], "日志测试完成",
"返回的message字段不正确"
);
assert_eq!(result_val["log_count"], 6, "返回的log_count字段不正确");
} else {
panic!("应有返回结果");
}
Ok(())
}
// 这个测试使用execute_with_params并指定超时时间
#[tokio::test]
async fn test_python_with_timeout() -> Result<()> {
// 初始化日志
setup();
// 创建一个会超时的Python脚本
let code = r#"
import time
import logging
def main(args: dict) -> dict:
logging.info("开始执行耗时操作")
# 这个操作会运行10秒钟
for i in range(10):
logging.info(f"已经执行了 {i+1} 秒")
time.sleep(1)
logging.info("操作完成")
return {"result": "完成"}
"#;
info!("创建了一个会运行10秒的测试脚本");
// 准备参数
let params = json!({
"test": "超时测试"
});
info!("准备测试参数: {params:?}");
// 设置3秒超时并执行脚本
info!("开始执行Python脚本设置3秒超时...");
let start_time = std::time::Instant::now();
let result =
CodeExecutor::execute_with_params(code, LanguageScript::Python, Some(params), Some(3))
.await;
let elapsed = start_time.elapsed();
// 检查是否在3-4秒内超时给一点缓冲时间
info!("脚本执行耗时: {elapsed:?}");
assert!(elapsed.as_secs() >= 3, "脚本应该至少运行3秒");
assert!(elapsed.as_secs() < 5, "脚本应该在5秒内超时");
// 检查是否返回超时错误
match result {
Ok(exec_result) => {
if let Some(error) = exec_result.error {
info!("正确捕获到超时错误: {error}");
assert!(
error.contains("timed out") ||
error.contains("TimedOut") ||
error.contains("executor await error"),
"错误信息应该包含超时相关信息"
);
} else {
panic!("应该捕获到超时错误,但脚本执行成功了");
}
}
Err(e) => {
// 使用特殊格式化获取完整错误链
let full_error = format!("{e:#}");
info!("捕获到错误: {full_error}");
assert!(
full_error.contains("timed out"),
"完整错误链中应该包含'timed out'超时信息"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_python_large_parameters() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_large_params.py")?;
info!("读取测试脚本: test_python_large_params.py");
// 生成大于2MB的文本参数
fn generate_large_text(size_in_mb: usize) -> String {
let chunk = "This is a test chunk of text designed to generate large parameters for Python testing. ".repeat(100);
let iterations = (size_in_mb * 1024 * 1024) / chunk.len();
let mut result = String::new();
for i in 0..iterations {
result.push_str(&chunk);
result.push_str(&format!("Iteration {}\n", i));
}
result
}
let large_text = generate_large_text(3); // 3MB
info!("生成了3MB大小的文本参数实际大小: {:.2} MB", large_text.len() as f64 / 1024.0 / 1024.0);
// 准备参数
let params = json!({
"largeText": large_text
});
info!("准备测试参数JSON大小: {:.2} MB", serde_json::to_string(&params).unwrap().len() as f64 / 1024.0 / 1024.0);
// 执行脚本
info!("开始执行Python脚本测试大参数处理...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误,这表明大参数处理成功");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值包含成功处理大参数的信息
if let Some(result_val) = result.result {
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("success"), "结果应包含 success 字段");
assert!(json_str.contains("true"), "success 应为 true");
assert!(json_str.contains("Successfully processed large text parameter"), "结果应包含成功消息");
assert!(json_str.contains("sizeMB"), "结果应包含 sizeMB 字段");
// 验证返回值是对象格式
if let Some(obj) = result_val.as_object() {
if let Some(success) = obj.get("success") {
assert_eq!(success, true, "success 字段的值应为 true");
}
if let Some(text_size) = obj.get("textSize") {
assert!(text_size.is_number(), "textSize 应为数字");
let size = text_size.as_u64().unwrap();
assert!(size > 2 * 1024 * 1024, "文本大小应大于2MB");
}
}
}
info!("Python大参数测试通过临时文件解决方案有效。");
Ok(())
}
}

View File

@@ -0,0 +1,202 @@
#[cfg(test)]
mod ts_tests {
use anyhow::Result;
use log::info;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
use crate::tests::test_utils::setup;
#[tokio::test]
async fn test_ts_basic_execution() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_ts.ts")?;
info!("读取测试脚本: test_ts.ts");
// 执行脚本
info!("开始执行TypeScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, None).await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
Ok(())
}
#[tokio::test]
async fn test_ts_with_params() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_ts_params.ts")?;
info!("读取测试脚本: test_ts_params.ts");
// 准备参数
let params = json!({
"a": 10,
"b": 20,
"name": "测试用户"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行TypeScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值包含预期的字段
if let Some(result_val) = result.result {
// 结果应该是JSON对象TypeScript对象被正确解析
assert!(result_val.get("sum").is_some(), "结果应包含 sum 字段");
assert!(
result_val.get("greeting").is_some(),
"结果应包含 greeting 字段"
);
assert!(result_val.get("message").is_some(), "结果应包含 message 字段");
assert!(result_val.get("numbers").is_some(), "结果应包含 numbers 字段");
}
Ok(())
}
#[tokio::test]
async fn test_ts_type_checking() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_ts.ts")?;
info!("读取测试脚本: test_ts.ts");
// 执行脚本
info!("开始执行TypeScript类型检查...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, None).await?;
info!("类型检查完成");
if let Some(error) = &result.error {
info!("类型检查错误: {error}");
} else {
info!("类型检查通过");
}
// 验证结果
assert!(result.error.is_none(), "TypeScript 类型检查应通过");
Ok(())
}
#[tokio::test]
async fn test_ts_with_timeout() -> Result<()> {
// 初始化日志
setup();
// 创建一个会超时的TypeScript脚本
let code = r#"
// 创建一个长时间运行的脚本
function sleep(ms: number): Promise<void> {
return new Promise<void>(resolve => setTimeout(resolve, ms));
}
async function handler(input: any): Promise<{result: string}> {
console.log("开始执行耗时操作");
// 这个操作会运行10秒钟
for (let i = 0; i < 10; i++) {
console.log(`已经执行了 ${i+1} 秒`);
await sleep(1000); // 等待1秒
}
console.log("操作完成");
return { result: "完成" };
}
"#;
info!("创建了一个会运行10秒的测试脚本");
// 准备参数
let params = json!({
"test": "超时测试"
});
info!("准备测试参数: {params:?}");
// 设置2秒超时并执行脚本
info!("开始执行TypeScript脚本设置2秒超时...");
let start_time = std::time::Instant::now();
let result =
CodeExecutor::execute_with_params(code, LanguageScript::Ts, Some(params), Some(2))
.await;
let elapsed = start_time.elapsed();
// 检查是否在2-3秒内超时给一点缓冲时间
info!("脚本执行耗时: {elapsed:?}");
assert!(elapsed.as_secs() >= 2, "脚本应该至少运行2秒");
assert!(elapsed.as_secs() < 4, "脚本应该在4秒内超时");
// 检查是否返回超时错误
match result {
Ok(exec_result) => {
if let Some(error) = exec_result.error {
info!("正确捕获到超时错误: {error}");
assert!(
error.contains("timed out") ||
error.contains("TimedOut") ||
error.contains("executor await error"),
"错误信息应该包含超时相关信息"
);
} else {
panic!("应该捕获到超时错误,但脚本执行成功了");
}
}
Err(e) => {
// 使用特殊格式化获取完整错误链
let full_error = format!("{e:#}");
info!("捕获到错误: {full_error}");
assert!(
full_error.contains("timed out"),
"完整错误链中应该包含'timed out'超时信息"
);
}
}
Ok(())
}
}

View File

@@ -0,0 +1,366 @@
use crate::model::CommandExecutor;
use anyhow::Result;
use log::{info, warn};
use std::path::Path;
use tokio::process::Command;
//定义国内python加速地址: https://mirrors.aliyun.com/pypi/simple
const PYTHON_ACCELERATION_ADDRESS: &str = "https://mirrors.aliyun.com/pypi/simple";
//使用 uv安装 python 3.13,比如: uv python install 3.13
async fn install_python_3_13() -> Result<()> {
let mut cmd = Command::new("uv");
cmd.arg("python")
.arg("install")
.arg("3.13")
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("安装Python 3.13失败");
return Err(anyhow::anyhow!("安装Python 3.13失败"));
}
info!("安装Python 3.13成功");
Ok(())
}
Ok(Err(e)) => {
warn!("命令执行失败: {e}");
Err(anyhow::anyhow!("命令执行失败: {}", e))
}
Err(e) => {
warn!("执行超时或系统错误: {e}");
Err(anyhow::anyhow!("执行超时或系统错误: {}", e))
}
}
}
// 检查 uv 虚拟环境是否存在
async fn check_and_create_uv_venv() -> Result<()> {
info!("检查 uv 虚拟环境...");
// 检查 .venv 目录是否存在
if Path::new(".venv").exists() {
info!("uv 虚拟环境已存在");
return Ok(());
}
info!("未检测到 uv 虚拟环境,开始创建...");
let mut cmd = Command::new("uv");
cmd.arg("venv").kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("创建 uv 虚拟环境失败");
return Err(anyhow::anyhow!("创建 uv 虚拟环境失败"));
}
info!("创建 uv 虚拟环境成功");
Ok(())
}
Ok(Err(e)) => {
warn!("命令执行失败: {e}");
Err(anyhow::anyhow!("命令执行失败: {}", e))
}
Err(e) => {
warn!("执行超时或系统错误: {e}");
Err(anyhow::anyhow!("执行超时或系统错误: {}", e))
}
}
}
/// 预热Python环境安装常用依赖
async fn warm_up_python_env(custom_deps: Option<Vec<String>>) -> Result<()> {
info!("开始预热Python环境...");
// 默认Python依赖列表
let default_deps = [
"requests",
"pandas",
"numpy",
"matplotlib",
"scikit-learn",
"pytest",
"pydantic",
"fastapi",
"uvicorn",
"sqlalchemy",
"opencv-python",
"python-docx",
];
// 使用自定义依赖或默认依赖
let deps_to_install = if let Some(deps) = custom_deps {
info!("使用自定义Python依赖列表");
deps
} else {
info!("使用默认Python依赖列表");
default_deps.iter().map(|&s| s.to_string()).collect()
};
let total_deps = deps_to_install.len();
info!("总共需要预热 {total_deps} 个Python依赖");
// 使用uv安装依赖
for (index, dep) in deps_to_install.iter().enumerate() {
let progress = ((index + 1) as f32 / total_deps as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在安装Python依赖: {dep}");
let mut cmd = Command::new("uv");
cmd.arg("pip")
.arg("install")
.arg("--default-index")
.arg(PYTHON_ACCELERATION_ADDRESS)
.arg(dep)
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("安装依赖 {dep} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 依赖: {dep}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 依赖: {dep}");
continue;
}
}
}
info!("Python环境预热完成 (100%)");
Ok(())
}
/// 预热JavaScript/TypeScript环境缓存常用模块
async fn warm_up_js_env(
custom_npm_packages: Option<Vec<String>>,
custom_jsr_packages: Option<Vec<String>>,
custom_node_modules: Option<Vec<String>>,
) -> Result<()> {
info!("开始预热JavaScript/TypeScript环境...");
// 默认npm包列表
let default_npm_packages = [
"lodash",
"axios",
"moment",
"uuid",
"express",
"react",
"react-dom",
"typescript",
"jest",
"webpack",
];
// 默认JSR包列表
let default_jsr_packages = [
"@std/testing",
"@std/http",
"@std/path",
"@std/fs",
"@std/encoding/json",
];
// 默认Node.js内置模块列表
let default_node_modules = [
"crypto", "buffer", "fs", "path", "http", "https", "url", "util", "stream", "events",
];
// 使用自定义npm包或默认包
let npm_packages = if let Some(packages) = custom_npm_packages {
info!("使用自定义npm包列表");
packages
} else {
info!("使用默认npm包列表");
default_npm_packages
.iter()
.map(|&s| s.to_string())
.collect()
};
// 使用自定义JSR包或默认包
let jsr_packages = if let Some(packages) = custom_jsr_packages {
info!("使用自定义JSR包列表");
packages
} else {
info!("使用默认JSR包列表");
default_jsr_packages
.iter()
.map(|&s| s.to_string())
.collect()
};
// 使用自定义Node.js模块或默认模块
let node_modules = if let Some(modules) = custom_node_modules {
info!("使用自定义Node.js模块列表");
modules
} else {
info!("使用默认Node.js模块列表");
default_node_modules
.iter()
.map(|&s| s.to_string())
.collect()
};
// 计算总任务数
let total_tasks = npm_packages.len() + jsr_packages.len() + node_modules.len();
info!("总共需要预热 {total_tasks} 个JavaScript/TypeScript模块");
let mut completed_tasks = 0;
// 预热npm包
for pkg in npm_packages.iter() {
completed_tasks += 1;
let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在缓存npm包: {pkg}");
let mut cmd = Command::new("deno");
cmd.args(["cache", "--reload", &format!("npm:{pkg}")])
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("缓存npm包 {pkg} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 包: {pkg}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 包: {pkg}");
continue;
}
}
}
// 预热JSR包
for pkg in jsr_packages.iter() {
completed_tasks += 1;
let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在缓存JSR包: {pkg}");
let mut cmd = Command::new("deno");
cmd.args(["cache", "--reload", &format!("jsr:{pkg}")])
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("缓存JSR包 {pkg} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 包: {pkg}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 包: {pkg}");
continue;
}
}
}
// 预热Node.js内置模块
for module in node_modules.iter() {
completed_tasks += 1;
let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在缓存Node.js模块: {module}");
let mut cmd = Command::new("deno");
cmd.args(["cache", "--reload", &format!("node:{module}")])
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("缓存Node.js模块 {module} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 模块: {module}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 包: {module}");
continue;
}
}
}
info!("JavaScript/TypeScript环境预热完成 (100%)");
Ok(())
}
/// 预热所有脚本执行环境
pub async fn warm_up_all_envs(
custom_python_deps: Option<Vec<String>>,
custom_npm_packages: Option<Vec<String>>,
custom_jsr_packages: Option<Vec<String>>,
custom_node_modules: Option<Vec<String>>,
) -> Result<()> {
info!("开始预热所有脚本执行环境...");
// 检查并创建 uv 虚拟环境
if let Err(e) = check_and_create_uv_venv().await {
warn!("检查或创建 uv 虚拟环境失败: {e}");
}
// 安装Python 3.13
if let Err(e) = install_python_3_13().await {
warn!("安装Python 3.13失败: {e}");
}
// 预热Python环境
if let Err(e) = warm_up_python_env(custom_python_deps).await {
warn!("预热Python环境失败: {e}");
}
// 预热JavaScript/TypeScript环境
if let Err(e) = warm_up_js_env(
custom_npm_packages,
custom_jsr_packages,
custom_node_modules,
)
.await
{
warn!("预热JavaScript/TypeScript环境失败: {e}");
}
info!("所有脚本执行环境预热完成");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_warm_up_python_env() {
// 测试默认依赖
warm_up_python_env(None).await.unwrap();
// 测试自定义依赖
let custom_deps = vec!["requests".to_string(), "pandas".to_string()];
warm_up_python_env(Some(custom_deps)).await.unwrap();
}
#[tokio::test]
async fn test_warm_up_js_env() {
// 测试默认依赖
warm_up_js_env(None, None, None).await.unwrap();
// 测试自定义依赖
let custom_npm = vec!["lodash".to_string(), "axios".to_string()];
let custom_jsr = vec!["@std/testing".to_string()];
let custom_node = vec!["crypto".to_string(), "buffer".to_string()];
warm_up_js_env(Some(custom_npm), Some(custom_jsr), Some(custom_node))
.await
.unwrap();
}
}