diff --git a/Docs/APILLAMA_OpenRouter集成说明.md b/Docs/前后端调试说明/APILLAMA_OpenRouter集成说明.md similarity index 100% rename from Docs/APILLAMA_OpenRouter集成说明.md rename to Docs/前后端调试说明/APILLAMA_OpenRouter集成说明.md diff --git a/Docs/API接口文档.md b/Docs/前后端调试说明/API接口文档.md similarity index 100% rename from Docs/API接口文档.md rename to Docs/前后端调试说明/API接口文档.md diff --git a/Docs/环境变量配置说明.md b/Docs/前后端调试说明/环境变量配置说明.md similarity index 100% rename from Docs/环境变量配置说明.md rename to Docs/前后端调试说明/环境变量配置说明.md diff --git a/Docs/MCP函数工具调用测试报告.md b/Docs/测试文档/MCP函数工具调用测试报告.md similarity index 100% rename from Docs/MCP函数工具调用测试报告.md rename to Docs/测试文档/MCP函数工具调用测试报告.md diff --git a/Docs/测试报告.md b/Docs/测试文档/测试报告.md similarity index 100% rename from Docs/测试报告.md rename to Docs/测试文档/测试报告.md diff --git a/Docs/jpg (1).jpg b/Docs/项目文档/jpg (1).jpg similarity index 100% rename from Docs/jpg (1).jpg rename to Docs/项目文档/jpg (1).jpg diff --git a/Docs/任务拆分与分工.md b/Docs/项目文档/任务拆分与分工.md similarity index 100% rename from Docs/任务拆分与分工.md rename to Docs/项目文档/任务拆分与分工.md diff --git a/Docs/工程排期计划.md b/Docs/项目文档/工程排期计划.md similarity index 100% rename from Docs/工程排期计划.md rename to Docs/项目文档/工程排期计划.md diff --git a/Docs/系统运作流程图.md b/Docs/项目文档/系统运作流程图.md similarity index 100% rename from Docs/系统运作流程图.md rename to Docs/项目文档/系统运作流程图.md diff --git a/Docs/项目说明.txt b/Docs/项目文档/项目说明.txt similarity index 100% rename from Docs/项目说明.txt rename to Docs/项目文档/项目说明.txt diff --git a/services/mcp-server/pytest.ini b/services/mcp-server/pytest.ini new file mode 100644 index 0000000..047b911 --- /dev/null +++ b/services/mcp-server/pytest.ini @@ -0,0 +1,12 @@ +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +asyncio_mode = auto +addopts = + -v + --tb=short + --strict-markers + --disable-warnings + diff --git a/services/mcp-server/run_tests.sh b/services/mcp-server/run_tests.sh new file mode 100755 index 0000000..ff365c4 --- /dev/null +++ b/services/mcp-server/run_tests.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# 运行单元测试脚本 + +set -e + +echo "🧪 运行 MCP Server 单元测试" +echo "" + +# 检查是否在 Docker 容器中 +if [ -f /.dockerenv ]; then + echo "✅ 在 Docker 容器中运行测试" + cd /app +else + echo "⚠️ 在本地环境运行测试,请确保已安装依赖" + cd "$(dirname "$0")" +fi + +# 运行测试 +echo "📋 运行 function_registry 测试..." +python3 -m pytest tests/test_function_registry.py -v --tb=short + +echo "" +echo "📋 运行 sandbox_executor 测试..." +python3 -m pytest tests/test_sandbox_executor.py -v --tb=short + +echo "" +echo "📋 运行 mcp_function_tool 测试..." +python3 -m pytest tests/test_mcp_function_tool.py -v --tb=short + +echo "" +echo "✅ 所有测试完成!" + diff --git a/services/mcp-server/tests/README.md b/services/mcp-server/tests/README.md new file mode 100644 index 0000000..3df9544 --- /dev/null +++ b/services/mcp-server/tests/README.md @@ -0,0 +1,148 @@ +# MCP Server 单元测试 + +## 📋 测试概述 + +本目录包含 MCP Server 的单元测试,覆盖以下模块: + +1. **function_registry** - 函数注册表测试 +2. **sandbox_executor** - 沙箱执行器测试 +3. **mcp_function_tool** - MCP 函数工具调用测试 + +## 🚀 运行测试 + +### 方法 1: 使用测试脚本 + +```bash +./run_tests.sh +``` + +### 方法 2: 使用 pytest 直接运行 + +```bash +# 运行所有测试 +pytest tests/ -v + +# 运行特定测试文件 +pytest tests/test_function_registry.py -v + +# 运行特定测试类 +pytest tests/test_function_registry.py::TestFunctionRegistry -v + +# 运行特定测试方法 +pytest tests/test_function_registry.py::TestFunctionRegistry::test_math_add -v +``` + +### 方法 3: 在 Docker 容器中运行 + +```bash +docker-compose exec mcp-server pytest tests/ -v +``` + +## 📊 测试覆盖 + +### function_registry 测试 + +- ✅ 注册表初始化 +- ✅ 函数注册和查询 +- ✅ 内置函数测试(16个函数) + - 数学函数(5个) + - 字符串函数(4个) + - JSON 函数(2个) + - 哈希函数(2个) + - Base64 函数(2个) + - 日期时间函数(1个) + +### sandbox_executor 测试 + +- ✅ 简单函数执行 +- ✅ 参数验证 +- ✅ 超时控制 +- ✅ 异常处理 +- ✅ 并发执行 + +### mcp_function_tool 测试 + +- ✅ 函数工具调用 +- ✅ 参数验证 +- ✅ 错误处理 +- ✅ 所有内置函数集成测试 + +## 📝 测试要求 + +- Python 3.8+ +- pytest 7.4.3+ +- pytest-asyncio 0.21.1+ + +## 🔧 配置 + +测试配置在 `pytest.ini` 文件中: + +```ini +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +asyncio_mode = auto +``` + +## 📈 测试报告 + +运行测试后,可以使用以下命令生成覆盖率报告: + +```bash +# 安装 coverage +pip install pytest-cov + +# 运行测试并生成覆盖率报告 +pytest tests/ --cov=. --cov-report=html + +# 查看 HTML 报告 +open htmlcov/index.html +``` + +## 🐛 调试测试 + +如果测试失败,可以使用以下选项获取更详细的输出: + +```bash +# 显示详细输出 +pytest tests/ -v -s + +# 显示失败测试的完整堆栈跟踪 +pytest tests/ --tb=long + +# 在第一个失败时停止 +pytest tests/ -x +``` + +## 📚 添加新测试 + +添加新测试时,请遵循以下规范: + +1. 测试文件命名:`test_.py` +2. 测试类命名:`Test` +3. 测试方法命名:`test_` +4. 使用 `@pytest.mark.asyncio` 标记异步测试 +5. 使用 fixtures 共享测试数据 + +示例: + +```python +import pytest +from module import Class + +class TestClass: + @pytest.fixture + def instance(self): + return Class() + + def test_method(self, instance): + assert instance.method() == expected + + @pytest.mark.asyncio + async def test_async_method(self, instance): + result = await instance.async_method() + assert result == expected +``` + diff --git a/services/mcp-server/tests/__init__.py b/services/mcp-server/tests/__init__.py new file mode 100644 index 0000000..eb696b1 --- /dev/null +++ b/services/mcp-server/tests/__init__.py @@ -0,0 +1,4 @@ +""" +MCP Server 单元测试 +""" + diff --git a/services/mcp-server/tests/conftest.py b/services/mcp-server/tests/conftest.py new file mode 100644 index 0000000..3efa8d8 --- /dev/null +++ b/services/mcp-server/tests/conftest.py @@ -0,0 +1,20 @@ +""" +Pytest 配置和共享 fixtures +""" + +import pytest +import sys +import os + +# 添加项目根目录到 Python 路径 +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +@pytest.fixture(scope="session") +def event_loop(): + """创建事件循环""" + import asyncio + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + diff --git a/services/mcp-server/tests/test_function_registry.py b/services/mcp-server/tests/test_function_registry.py new file mode 100644 index 0000000..2a509b5 --- /dev/null +++ b/services/mcp-server/tests/test_function_registry.py @@ -0,0 +1,242 @@ +""" +函数注册表单元测试 +""" + +import pytest +from function_registry import FunctionRegistry, get_function_registry + + +class TestFunctionRegistry: + """函数注册表测试类""" + + def test_registry_initialization(self): + """测试注册表初始化""" + registry = FunctionRegistry() + assert registry is not None + assert len(registry.list_functions()) > 0 + + def test_list_functions(self): + """测试列出所有函数""" + registry = get_function_registry() + functions = registry.list_all() + assert isinstance(functions, list) + assert len(functions) > 0 + + # 检查函数结构 + for func_info in functions: + assert "name" in func_info + assert "description" in func_info + assert "parameters" in func_info + assert "returns" in func_info + assert "func" not in func_info # 不应该包含实际函数对象 + + def test_get_function(self): + """测试获取函数信息""" + registry = get_function_registry() + # 测试存在的函数 + func_info = registry.get("math_add") + assert func_info is not None + assert func_info["name"] == "math_add" + assert "func" in func_info + assert "description" in func_info + assert "parameters" in func_info + + # 测试不存在的函数 + func_info = registry.get("non_existent_function") + assert func_info is None + + def test_register_function(self): + """测试注册新函数""" + registry = FunctionRegistry() + + def test_func(x: int, y: int) -> int: + """测试函数""" + return x + y + + registry.register( + name="test_add", + func=test_func, + description="测试加法函数", + parameters=[ + {"name": "x", "type": "integer", "description": "第一个数"}, + {"name": "y", "type": "integer", "description": "第二个数"} + ], + returns={"type": "integer", "description": "两数之和"} + ) + + func_info = registry.get_function("test_add") + assert func_info is not None + assert func_info["name"] == "test_add" + assert func_info["description"] == "测试加法函数" + + # 测试函数可以执行 + result = func_info["func"](10, 20) + assert result == 30 + + def test_register_duplicate_function(self): + """测试注册重复函数(应该覆盖)""" + registry = FunctionRegistry() + + def func1(): + return 1 + + def func2(): + return 2 + + registry.register( + name="duplicate_test", + func=func1, + description="第一个函数", + parameters=[], + returns={"type": "integer"} + ) + + registry.register( + name="duplicate_test", + func=func2, + description="第二个函数", + parameters=[], + returns={"type": "integer"} + ) + + func_info = registry.get_function("duplicate_test") + assert func_info["func"]() == 2 # 应该是第二个函数 + + def test_builtin_math_functions(self): + """测试内置数学函数""" + registry = get_function_registry() + # math_add + func_info = registry.get("math_add") + assert func_info is not None + result = func_info["func"](10, 20) + assert result == 30.0 + + # math_subtract + func_info = registry.get("math_subtract") + assert func_info is not None + result = func_info["func"](20, 10) + assert result == 10.0 + + # math_multiply + func_info = registry.get("math_multiply") + assert func_info is not None + result = func_info["func"](5, 6) + assert result == 30.0 + + # math_divide + func_info = registry.get("math_divide") + assert func_info is not None + result = func_info["func"](20, 4) + assert result == 5.0 + + # math_power + func_info = registry.get("math_power") + assert func_info is not None + result = func_info["func"](2, 3) + assert result == 8.0 + + def test_builtin_string_functions(self): + """测试内置字符串函数""" + registry = get_function_registry() + # string_upper + func_info = registry.get("string_upper") + assert func_info is not None + result = func_info["func"]("hello") + assert result == "HELLO" + + # string_lower + func_info = registry.get("string_lower") + assert func_info is not None + result = func_info["func"]("WORLD") + assert result == "world" + + # string_length + func_info = registry.get("string_length") + assert func_info is not None + result = func_info["func"]("test") + assert result == 4 + + # string_replace + func_info = registry.get("string_replace") + assert func_info is not None + result = func_info["func"]("hello world", "world", "python") + assert result == "hello python" + + def test_builtin_json_functions(self): + """测试内置JSON函数""" + registry = get_function_registry() + # json_stringify + func_info = registry.get("json_stringify") + assert func_info is not None + result = func_info["func"]({"key": "value"}) + assert result == '{"key": "value"}' + + # json_parse + func_info = registry.get("json_parse") + assert func_info is not None + result = func_info["func"]('{"key": "value"}') + assert result == {"key": "value"} + + def test_builtin_hash_functions(self): + """测试内置哈希函数""" + registry = get_function_registry() + # hash_md5 + func_info = registry.get("hash_md5") + assert func_info is not None + result = func_info["func"]("test") + assert len(result) == 32 # MD5 哈希长度为 32 + assert isinstance(result, str) + + # hash_sha256 + func_info = registry.get("hash_sha256") + assert func_info is not None + result = func_info["func"]("test") + assert len(result) == 64 # SHA256 哈希长度为 64 + assert isinstance(result, str) + + def test_builtin_base64_functions(self): + """测试内置Base64函数""" + registry = get_function_registry() + # base64_encode + func_info = registry.get("base64_encode") + assert func_info is not None + result = func_info["func"]("hello") + assert isinstance(result, str) + + # base64_decode + func_info = registry.get("base64_decode") + assert func_info is not None + encoded = registry.get("base64_encode")["func"]("hello") + result = func_info["func"](encoded) + assert result == "hello" + + def test_builtin_datetime_function(self): + """测试内置日期时间函数""" + registry = get_function_registry() + func_info = registry.get("datetime_now") + assert func_info is not None + result = func_info["func"]() + assert isinstance(result, str) + assert "T" in result or "-" in result # ISO格式 + + def test_function_count(self): + """测试函数数量""" + registry = get_function_registry() + functions = registry.list_all() + # 应该至少有16个内置函数 + assert len(functions) >= 16 + + # 检查所有预期的函数都存在 + expected_functions = [ + "math_add", "math_subtract", "math_multiply", "math_divide", "math_power", + "string_upper", "string_lower", "string_length", "string_replace", + "datetime_now", + "json_parse", "json_stringify", + "hash_md5", "hash_sha256", + "base64_encode", "base64_decode" + ] + + function_names = [f["name"] for f in functions] + for expected_func in expected_functions: + assert expected_func in function_names, f"函数 {expected_func} 未找到" + diff --git a/services/mcp-server/tests/test_mcp_function_tool.py b/services/mcp-server/tests/test_mcp_function_tool.py new file mode 100644 index 0000000..981c3e8 --- /dev/null +++ b/services/mcp-server/tests/test_mcp_function_tool.py @@ -0,0 +1,216 @@ +""" +MCP 协议函数工具调用单元测试 +""" + +import pytest +from unittest.mock import Mock, AsyncMock, patch +from mcp_protocol import MCPProtocolHandler +from function_registry import get_function_registry +from sandbox_executor import get_sandbox_executor + + +class TestMCPFunctionTool: + """MCP 函数工具调用测试类""" + + @pytest.fixture + def mcp_server(self): + """创建 MCP Protocol Handler 实例""" + from unittest.mock import MagicMock + redis_mock = MagicMock() + nats_mock = MagicMock() + handler = MCPProtocolHandler(redis_client=redis_mock, nats_client=nats_mock) + return handler + + @pytest.mark.asyncio + async def test_execute_function_tool_math_add(self, mcp_server): + """测试执行数学加法函数""" + tool_info = { + "name": "math_add", + "timeout": 5 + } + arguments = {"a": 10, "b": 20} + + result = await mcp_server._execute_function_tool(tool_info, arguments) + assert result == 30.0 + + @pytest.mark.asyncio + async def test_execute_function_tool_string_upper(self, mcp_server): + """测试执行字符串大写函数""" + tool_info = { + "name": "string_upper", + "timeout": 5 + } + arguments = {"s": "hello world"} + + result = await mcp_server._execute_function_tool(tool_info, arguments) + assert result == "HELLO WORLD" + + @pytest.mark.asyncio + async def test_execute_function_tool_missing_function_name(self, mcp_server): + """测试缺少函数名称的情况""" + tool_info = {} + arguments = {"a": 10, "b": 20} + + with pytest.raises(ValueError, match="函数工具必须指定"): + await mcp_server._execute_function_tool(tool_info, arguments) + + @pytest.mark.asyncio + async def test_execute_function_tool_unregistered_function(self, mcp_server): + """测试未注册的函数""" + tool_info = { + "name": "non_existent_function", + "timeout": 5 + } + arguments = {"a": 10} + + with pytest.raises(ValueError, match="未在注册表中"): + await mcp_server._execute_function_tool(tool_info, arguments) + + @pytest.mark.asyncio + async def test_execute_function_tool_missing_required_parameter(self, mcp_server): + """测试缺少必需参数""" + tool_info = { + "name": "math_add", + "timeout": 5 + } + arguments = {"a": 10} # 缺少 b 参数 + + # 注意:由于参数验证可能允许部分参数,这里可能需要调整 + # 如果函数定义中 b 是必需的,应该会抛出异常 + try: + result = await mcp_server._execute_function_tool(tool_info, arguments) + # 如果没有抛出异常,说明参数验证允许部分参数 + # 这种情况下函数可能会使用默认值或报错 + except (ValueError, TypeError) as e: + # 参数验证失败是预期的 + assert "参数" in str(e) or "missing" in str(e).lower() + + @pytest.mark.asyncio + async def test_execute_function_tool_timeout(self, mcp_server): + """测试函数执行超时""" + # 注册一个会超时的函数 + def slow_function(): + import time + time.sleep(10) + return "done" + + registry = get_function_registry() + registry.register( + name="slow_test", + func=slow_function, + description="慢速测试函数", + parameters=[], + returns={"type": "string"} + ) + + tool_info = { + "name": "slow_test", + "timeout": 1 # 1秒超时 + } + arguments = {} + + with pytest.raises(Exception): # 可能是 TimeoutError 或其他异常 + await mcp_server._execute_function_tool(tool_info, arguments) + + @pytest.mark.asyncio + async def test_execute_function_tool_with_all_builtin_functions(self, mcp_server): + """测试所有内置函数""" + test_cases = [ + ("math_add", {"a": 5, "b": 3}, 8.0), + ("math_subtract", {"a": 10, "b": 4}, 6.0), + ("math_multiply", {"a": 6, "b": 7}, 42.0), + ("math_divide", {"a": 20, "b": 4}, 5.0), + ("math_power", {"a": 2, "b": 3}, 8.0), + ("string_upper", {"s": "hello"}, "HELLO"), + ("string_lower", {"s": "WORLD"}, "world"), + ("string_length", {"s": "test"}, 4), + ("string_replace", {"s": "hello", "old": "l", "new": "L"}, "heLLo"), + ] + + for func_name, args, expected in test_cases: + tool_info = { + "name": func_name, + "timeout": 5 + } + result = await mcp_server._execute_function_tool(tool_info, args) + assert result == expected, f"函数 {func_name} 执行结果不正确" + + @pytest.mark.asyncio + async def test_execute_function_tool_json_functions(self, mcp_server): + """测试JSON函数""" + # json_stringify + tool_info = { + "name": "json_stringify", + "timeout": 5 + } + arguments = {"obj": {"key": "value", "number": 123}} + result = await mcp_server._execute_function_tool(tool_info, arguments) + assert isinstance(result, str) + assert "key" in result + assert "value" in result + + # json_parse + tool_info = { + "name": "json_parse", + "timeout": 5 + } + arguments = {"s": '{"key": "value"}'} + result = await mcp_server._execute_function_tool(tool_info, arguments) + assert isinstance(result, dict) + assert result["key"] == "value" + + @pytest.mark.asyncio + async def test_execute_function_tool_hash_functions(self, mcp_server): + """测试哈希函数""" + # hash_md5 + tool_info = { + "name": "hash_md5", + "timeout": 5 + } + arguments = {"s": "test"} + result = await mcp_server._execute_function_tool(tool_info, arguments) + assert isinstance(result, str) + assert len(result) == 32 + + # hash_sha256 + tool_info = { + "name": "hash_sha256", + "timeout": 5 + } + result = await mcp_server._execute_function_tool(tool_info, arguments) + assert isinstance(result, str) + assert len(result) == 64 + + @pytest.mark.asyncio + async def test_execute_function_tool_base64_functions(self, mcp_server): + """测试Base64函数""" + # base64_encode + tool_info = { + "name": "base64_encode", + "timeout": 5 + } + arguments = {"s": "hello"} + encoded = await mcp_server._execute_function_tool(tool_info, arguments) + assert isinstance(encoded, str) + + # base64_decode + tool_info = { + "name": "base64_decode", + "timeout": 5 + } + arguments = {"s": encoded} + decoded = await mcp_server._execute_function_tool(tool_info, arguments) + assert decoded == "hello" + + @pytest.mark.asyncio + async def test_execute_function_tool_datetime_function(self, mcp_server): + """测试日期时间函数""" + tool_info = { + "name": "datetime_now", + "timeout": 5 + } + arguments = {} + result = await mcp_server._execute_function_tool(tool_info, arguments) + assert isinstance(result, str) + assert "T" in result or "-" in result # ISO格式 + diff --git a/services/mcp-server/tests/test_sandbox_executor.py b/services/mcp-server/tests/test_sandbox_executor.py new file mode 100644 index 0000000..ee5437d --- /dev/null +++ b/services/mcp-server/tests/test_sandbox_executor.py @@ -0,0 +1,173 @@ +""" +沙箱执行器单元测试 +""" + +import pytest +import asyncio +from sandbox_executor import SandboxExecutor + + +class TestSandboxExecutor: + """沙箱执行器测试类""" + + @pytest.fixture + def executor(self): + """创建沙箱执行器实例""" + return SandboxExecutor(timeout=2.0, max_memory_mb=100) + + @pytest.mark.asyncio + async def test_execute_simple_function(self, executor): + """测试执行简单函数""" + def add(a, b): + return a + b + + result = await executor.execute(add, {"a": 10, "b": 20}, "add") + assert result == 30 + + @pytest.mark.asyncio + async def test_execute_with_correct_parameters(self, executor): + """测试使用正确参数执行函数""" + def multiply(x, y): + return x * y + + result = await executor.execute(multiply, {"x": 5, "y": 6}, "multiply") + assert result == 30 + + @pytest.mark.asyncio + async def test_execute_with_missing_parameters(self, executor): + """测试缺少参数的情况""" + def func(a, b, c): + return a + b + c + + with pytest.raises(TypeError): + await executor.execute(func, {"a": 1, "b": 2}, "func") + + @pytest.mark.asyncio + async def test_execute_with_extra_parameters(self, executor): + """测试多余参数的情况(应该被忽略)""" + def func(a, b): + return a + b + + # 传递额外参数应该不影响执行 + result = await executor.execute(func, {"a": 1, "b": 2, "c": 3}, "func") + assert result == 3 + + @pytest.mark.asyncio + async def test_execute_timeout(self, executor): + """测试执行超时""" + def slow_function(): + import time + time.sleep(5) # 休眠5秒 + return "done" + + with pytest.raises(asyncio.TimeoutError): + await executor.execute(slow_function, {}, "slow_function") + + @pytest.mark.asyncio + async def test_execute_with_exception(self, executor): + """测试函数抛出异常的情况""" + def error_function(): + raise ValueError("测试错误") + + with pytest.raises(ValueError, match="测试错误"): + await executor.execute(error_function, {}, "error_function") + + @pytest.mark.asyncio + async def test_execute_string_function(self, executor): + """测试字符串处理函数""" + def upper_case(s): + return s.upper() + + result = await executor.execute(upper_case, {"s": "hello"}, "upper_case") + assert result == "HELLO" + + @pytest.mark.asyncio + async def test_execute_list_function(self, executor): + """测试列表处理函数""" + def sum_list(numbers): + return sum(numbers) + + result = await executor.execute(sum_list, {"numbers": [1, 2, 3, 4, 5]}, "sum_list") + assert result == 15 + + @pytest.mark.asyncio + async def test_execute_dict_function(self, executor): + """测试字典处理函数""" + def get_value(data, key): + return data.get(key) + + result = await executor.execute( + get_value, + {"data": {"name": "test", "value": 123}, "key": "value"}, + "get_value" + ) + assert result == 123 + + @pytest.mark.asyncio + async def test_execute_nested_function(self, executor): + """测试嵌套函数调用""" + def outer(x): + def inner(y): + return y * 2 + return inner(x) + 10 + + result = await executor.execute(outer, {"x": 5}, "outer") + assert result == 20 + + @pytest.mark.asyncio + async def test_execute_with_type_conversion(self, executor): + """测试类型转换""" + def add_strings(a, b): + return str(a) + str(b) + + result = await executor.execute(add_strings, {"a": 123, "b": 456}, "add_strings") + assert result == "123456" + + @pytest.mark.asyncio + async def test_execute_with_none(self, executor): + """测试处理None值""" + def return_none(): + return None + + result = await executor.execute(return_none, {}, "return_none") + assert result is None + + @pytest.mark.asyncio + async def test_execute_with_empty_dict(self, executor): + """测试空参数字典""" + def no_params(): + return "success" + + result = await executor.execute(no_params, {}, "no_params") + assert result == "success" + + @pytest.mark.asyncio + async def test_custom_timeout(self): + """测试自定义超时时间""" + executor = SandboxExecutor(timeout=0.5) + + def slow_function(): + import time + time.sleep(1) + return "done" + + with pytest.raises(asyncio.TimeoutError): + await executor.execute(slow_function, {}, "slow_function") + + @pytest.mark.asyncio + async def test_concurrent_executions(self, executor): + """测试并发执行""" + def add(a, b): + return a + b + + # 并发执行多个函数 + tasks = [ + executor.execute(add, {"a": i, "b": i+1}, f"add_{i}") + for i in range(10) + ] + + results = await asyncio.gather(*tasks) + assert len(results) == 10 + assert results[0] == 1 # 0 + 1 + assert results[9] == 19 # 9 + 10 +