|
|
"""
|
|
|
Pytest配置文件
|
|
|
|
|
|
本模块提供全局的pytest fixtures、hooks和配置,用于支持测试的执行和管理。
|
|
|
包括测试环境设置、模拟配置、测试数据管理等。
|
|
|
"""
|
|
|
|
|
|
import os
|
|
|
import sys
|
|
|
import pytest
|
|
|
import tempfile
|
|
|
import shutil
|
|
|
from pathlib import Path
|
|
|
from unittest.mock import Mock, AsyncMock
|
|
|
from typing import Dict, List, Any, Optional, Generator
|
|
|
import json
|
|
|
import time
|
|
|
|
|
|
# 添加项目根目录到Python路径
|
|
|
project_root = Path(__file__).parent.parent
|
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
|
|
# 导入项目模块
|
|
|
from src.utils.config import get_config, ConfigManager
|
|
|
from src.verify.verification_types import (
|
|
|
VerificationStatus, VerificationType, CBMCConfiguration,
|
|
|
VerificationResult, VerificationIssue
|
|
|
)
|
|
|
from src.parser.metadata import FunctionInfo, ParameterInfo, SourceLocation
|
|
|
from src.mutate.mutation_types import MutationType, MutationResult
|
|
|
|
|
|
# =============================================================================
|
|
|
# 全局配置和设置
|
|
|
# =============================================================================
|
|
|
|
|
|
def pytest_configure(config):
|
|
|
"""pytest配置hook"""
|
|
|
# 注册自定义标记
|
|
|
config.addinivalue_line(
|
|
|
"markers", "performance: Tests that measure performance metrics"
|
|
|
)
|
|
|
config.addinivalue_line(
|
|
|
"markers", "slow: Tests that take a long time to run"
|
|
|
)
|
|
|
config.addinivalue_line(
|
|
|
"markers", "requires_cbmc: Tests that require CBMC installation"
|
|
|
)
|
|
|
config.addinivalue_line(
|
|
|
"markers", "requires_freertos: Tests that require FreeRTOS availability"
|
|
|
)
|
|
|
config.addinivalue_line(
|
|
|
"markers", "external: Tests that require external services"
|
|
|
)
|
|
|
|
|
|
|
|
|
def pytest_collection_modifyitems(config, items):
|
|
|
"""修改测试收集的hook"""
|
|
|
# 自动为慢速测试添加slow标记
|
|
|
for item in items:
|
|
|
# 根据测试名称或模块自动添加标记
|
|
|
if "performance" in item.nodeid:
|
|
|
item.add_marker(pytest.mark.performance)
|
|
|
item.add_marker(pytest.mark.slow)
|
|
|
|
|
|
if "regression" in item.nodeid:
|
|
|
item.add_marker(pytest.mark.regression)
|
|
|
|
|
|
if "freertos" in item.nodeid:
|
|
|
item.add_marker(pytest.mark.freertos)
|
|
|
item.add_marker(pytest.mark.requires_freertos)
|
|
|
|
|
|
if "integration" in item.nodeid:
|
|
|
item.add_marker(pytest.mark.integration)
|
|
|
|
|
|
if "unit" in item.nodeid:
|
|
|
item.add_marker(pytest.mark.unit)
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 跳过条件hook
|
|
|
# =============================================================================
|
|
|
|
|
|
def pytest_runtest_setup(item):
|
|
|
"""测试设置前hook"""
|
|
|
# 检查CBMC可用性
|
|
|
if item.get_closest_marker("requires_cbmc"):
|
|
|
if not shutil.which("cbmc"):
|
|
|
pytest.skip("CBMC not installed")
|
|
|
|
|
|
# 检查FreeRTOS可用性
|
|
|
if item.get_closest_marker("requires_freertos"):
|
|
|
if not os.environ.get("FREERTOS_PATH"):
|
|
|
pytest.skip("FreeRTOS headers not available")
|
|
|
|
|
|
# 检查网络连接
|
|
|
if item.get_closest_marker("network"):
|
|
|
try:
|
|
|
import socket
|
|
|
socket.create_connection(("www.google.com", 80), timeout=5)
|
|
|
except OSError:
|
|
|
pytest.skip("Network connection not available")
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 基础配置fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def project_root() -> Path:
|
|
|
"""项目根目录路径"""
|
|
|
return Path(__file__).parent.parent
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def test_config():
|
|
|
"""测试配置"""
|
|
|
return get_config()
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def temp_dir() -> Generator[Path, None, None]:
|
|
|
"""临时目录fixture"""
|
|
|
temp_path = Path(tempfile.mkdtemp())
|
|
|
try:
|
|
|
yield temp_path
|
|
|
finally:
|
|
|
shutil.rmtree(temp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def config_manager():
|
|
|
"""配置管理器fixture"""
|
|
|
return ConfigManager()
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# CBMC相关fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def cbmc_config() -> CBMCConfiguration:
|
|
|
"""CBMC配置fixture"""
|
|
|
return CBMCConfiguration(
|
|
|
path="cbmc",
|
|
|
timeout=60,
|
|
|
unwind_depth=20,
|
|
|
memory_model="simple",
|
|
|
output_format="text",
|
|
|
check_assertions=True,
|
|
|
check_overflow=True,
|
|
|
check_bounds=True,
|
|
|
max_concurrent=2
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def cbmc_available() -> bool:
|
|
|
"""检查CBMC是否可用"""
|
|
|
return shutil.which("cbmc") is not None
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def cbmc_version(cbmc_available) -> Optional[str]:
|
|
|
"""获取CBMC版本"""
|
|
|
if not cbmc_available:
|
|
|
return None
|
|
|
|
|
|
try:
|
|
|
import subprocess
|
|
|
result = subprocess.run(
|
|
|
["cbmc", "--version"],
|
|
|
capture_output=True,
|
|
|
text=True,
|
|
|
timeout=10
|
|
|
)
|
|
|
if result.returncode == 0:
|
|
|
return result.stdout.strip().split()[1] if " " in result.stdout.strip() else "unknown"
|
|
|
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
|
|
pass
|
|
|
|
|
|
return "unknown"
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# FreeRTOS相关fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def freertos_available() -> bool:
|
|
|
"""检查FreeRTOS是否可用"""
|
|
|
freertos_path = os.environ.get("FREERTOS_PATH")
|
|
|
if not freertos_path:
|
|
|
return False
|
|
|
|
|
|
freertos_h = Path(freertos_path) / "FreeRTOS" / "Source" / "include" / "FreeRTOS.h"
|
|
|
return freertos_h.exists()
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def freertos_config() -> Dict[str, Any]:
|
|
|
"""FreeRTOS配置fixture"""
|
|
|
return {
|
|
|
"freertos_mode": True,
|
|
|
"freertos_include_path": "/opt/freertos/FreeRTOS/Source/include",
|
|
|
"define_macros": [
|
|
|
"FREERTOS", "CBMC", "configUSE_PREEMPTION=1",
|
|
|
"configMAX_PRIORITIES=5", "configMINIMAL_STACK_SIZE=128"
|
|
|
],
|
|
|
"include_paths": [
|
|
|
"/opt/freertos/FreeRTOS/Source/include",
|
|
|
"/opt/freertos/FreeRTOS/Source/portable/GCC/Linux"
|
|
|
]
|
|
|
}
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 函数元数据fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def simple_function() -> FunctionInfo:
|
|
|
"""简单函数元数据fixture"""
|
|
|
return FunctionInfo(
|
|
|
name="simple_function",
|
|
|
return_type="int",
|
|
|
parameters=[
|
|
|
ParameterInfo(name="a", type_name="int", is_pointer=False),
|
|
|
ParameterInfo(name="b", type_name="int", is_pointer=False)
|
|
|
],
|
|
|
source_location=SourceLocation(file="simple.c", line=1, column=1),
|
|
|
complexity_score=0.3
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def pointer_function() -> FunctionInfo:
|
|
|
"""指针函数元数据fixture"""
|
|
|
return FunctionInfo(
|
|
|
name="pointer_function",
|
|
|
return_type="void",
|
|
|
parameters=[
|
|
|
ParameterInfo(name="buffer", type_name="char*", is_pointer=True),
|
|
|
ParameterInfo(name="size", type_name="size_t", is_pointer=False)
|
|
|
],
|
|
|
source_location=SourceLocation(file="pointer.c", line=5, column=1),
|
|
|
complexity_score=0.6
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def complex_function() -> FunctionInfo:
|
|
|
"""复杂函数元数据fixture"""
|
|
|
return FunctionInfo(
|
|
|
name="complex_function",
|
|
|
return_type="int*",
|
|
|
parameters=[
|
|
|
ParameterInfo(name="array", type_name="int*", is_pointer=True),
|
|
|
ParameterInfo(name="length", type_name="int", is_pointer=False),
|
|
|
ParameterInfo(name="callback", type_name="int(*)(int)", is_pointer=False)
|
|
|
],
|
|
|
source_location=SourceLocation(file="complex.c", line=10, column=1),
|
|
|
complexity_score=0.8
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def function_fixtures() -> Dict[str, FunctionInfo]:
|
|
|
"""函数元数据fixture集合"""
|
|
|
return {
|
|
|
"simple": FunctionInfo(
|
|
|
name="add",
|
|
|
return_type="int",
|
|
|
parameters=[
|
|
|
ParameterInfo(name="a", type_name="int", is_pointer=False),
|
|
|
ParameterInfo(name="b", type_name="int", is_pointer=False)
|
|
|
],
|
|
|
complexity_score=0.3
|
|
|
),
|
|
|
"pointer": FunctionInfo(
|
|
|
name="copy_string",
|
|
|
return_type="void",
|
|
|
parameters=[
|
|
|
ParameterInfo(name="dest", type_name="char*", is_pointer=True),
|
|
|
ParameterInfo(name="src", type_name="const char*", is_pointer=True),
|
|
|
ParameterInfo(name="size", type_name="size_t", is_pointer=False)
|
|
|
],
|
|
|
complexity_score=0.6
|
|
|
),
|
|
|
"complex": FunctionInfo(
|
|
|
name="process_data",
|
|
|
return_type="int*",
|
|
|
parameters=[
|
|
|
ParameterInfo(name="data", type_name="int*", is_pointer=True),
|
|
|
ParameterInfo(name="count", type_name="int", is_pointer=False),
|
|
|
ParameterInfo(name="processor", type_name="int(*)(int)", is_pointer=False)
|
|
|
],
|
|
|
complexity_score=0.9
|
|
|
)
|
|
|
}
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 验证结果fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def verification_success_result() -> VerificationResult:
|
|
|
"""成功验证结果fixture"""
|
|
|
return VerificationResult(
|
|
|
status=VerificationStatus.SUCCESSFUL,
|
|
|
function_name="test_function",
|
|
|
harness_file="test_harness.c",
|
|
|
start_time=time.time() - 1.0,
|
|
|
end_time=time.time(),
|
|
|
duration=1.0,
|
|
|
issues=[],
|
|
|
cbmc_output="VERIFICATION SUCCESSFUL",
|
|
|
cbmc_command="cbmc test_harness.c"
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def verification_failure_result() -> VerificationResult:
|
|
|
"""失败验证结果fixture"""
|
|
|
return VerificationResult(
|
|
|
status=VerificationStatus.FAILED,
|
|
|
function_name="test_function",
|
|
|
harness_file="test_harness.c",
|
|
|
start_time=time.time() - 1.0,
|
|
|
end_time=time.time(),
|
|
|
duration=1.0,
|
|
|
issues=[
|
|
|
VerificationIssue(
|
|
|
issue_type="assertion_failure",
|
|
|
description="Assertion failed: buffer size check",
|
|
|
severity="error",
|
|
|
source_location=SourceLocation(file="test.c", line=15, column=5)
|
|
|
)
|
|
|
],
|
|
|
cbmc_output="VERIFICATION FAILED",
|
|
|
cbmc_command="cbmc test_harness.c"
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def verification_timeout_result() -> VerificationResult:
|
|
|
"""超时验证结果fixture"""
|
|
|
return VerificationResult(
|
|
|
status=VerificationStatus.TIMEOUT,
|
|
|
function_name="test_function",
|
|
|
harness_file="test_harness.c",
|
|
|
start_time=time.time() - 60.0,
|
|
|
end_time=time.time(),
|
|
|
duration=60.0,
|
|
|
issues=[
|
|
|
VerificationIssue(
|
|
|
issue_type="timeout",
|
|
|
description="CBMC execution timed out",
|
|
|
severity="error"
|
|
|
)
|
|
|
],
|
|
|
cbmc_output="TIMEOUT",
|
|
|
cbmc_command="cbmc test_harness.c"
|
|
|
)
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 突变结果fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def mutation_success_result() -> MutationResult:
|
|
|
"""成功突变结果fixture"""
|
|
|
return MutationResult(
|
|
|
specification="void test_harness() { __CPROVER_assert(1 == 1); }",
|
|
|
mutation_type=MutationType.PREDICATE_MUTATION,
|
|
|
confidence=0.85,
|
|
|
description="Basic assertion mutation"
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def mutation_results_fixtures() -> List[MutationResult]:
|
|
|
"""突变结果fixture集合"""
|
|
|
return [
|
|
|
MutationResult(
|
|
|
specification="void test_harness() { __CPROVER_assert(x > 0); }",
|
|
|
mutation_type=MutationType.PREDICATE_MUTATION,
|
|
|
confidence=0.8,
|
|
|
description="Positive value check"
|
|
|
),
|
|
|
MutationResult(
|
|
|
specification="void test_harness() { __CPROVER_assert(y != NULL); }",
|
|
|
mutation_type=MutationType.NULL_POINTER_MUTATION,
|
|
|
confidence=0.9,
|
|
|
description="Null pointer check"
|
|
|
),
|
|
|
MutationResult(
|
|
|
specification="void test_harness() { __CPROVER_assert(size > 0 && size < 1000); }",
|
|
|
mutation_type=MutationType.BOUNDS_MUTATION,
|
|
|
confidence=0.75,
|
|
|
description="Bounds check"
|
|
|
)
|
|
|
]
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 性能测试fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def performance_config():
|
|
|
"""性能测试配置fixture"""
|
|
|
return {
|
|
|
"iterations": 100,
|
|
|
"timeout": 300,
|
|
|
"warmup_iterations": 10,
|
|
|
"memory_threshold_mb": 100,
|
|
|
"time_threshold_seconds": 30
|
|
|
}
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def benchmark_functions():
|
|
|
"""基准测试函数fixture"""
|
|
|
return [
|
|
|
{"name": "simple_add", "code": "int add(int a, int b) { return a + b; }"},
|
|
|
{"name": "loop_sum", "code": "int sum(int n) { int s = 0; for(int i=0; i<n; i++) s+=i; return s; }"},
|
|
|
{"name": "recursive_fib", "code": "int fib(int n) { if(n<=1) return n; return fib(n-1)+fib(n-2); }"}
|
|
|
]
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# API测试fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def api_test_client():
|
|
|
"""API测试客户端fixture"""
|
|
|
from src.ui.web_app import create_app
|
|
|
from src.utils.config import get_config
|
|
|
|
|
|
config = get_config()
|
|
|
app = create_app(config)
|
|
|
app.config['TESTING'] = True
|
|
|
app.config['SECRET_KEY'] = 'test-secret'
|
|
|
|
|
|
with app.test_client() as client:
|
|
|
with app.app_context():
|
|
|
yield client
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def api_headers():
|
|
|
"""API请求头fixture"""
|
|
|
return {
|
|
|
'Content-Type': 'application/json',
|
|
|
'Accept': 'application/json'
|
|
|
}
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 模拟fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def mock_cbmc_runner():
|
|
|
"""模拟CBMC运行器fixture"""
|
|
|
runner = Mock()
|
|
|
runner.run_verification = AsyncMock()
|
|
|
runner._execute_cbmc_verification = AsyncMock()
|
|
|
return runner
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def mock_llm_generator():
|
|
|
"""模拟LLM生成器fixture"""
|
|
|
generator = Mock()
|
|
|
generator.generate_specification = AsyncMock()
|
|
|
return generator
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def mock_mutation_engine():
|
|
|
"""模拟突变引擎fixture"""
|
|
|
engine = Mock()
|
|
|
engine.generate_mutations = AsyncMock()
|
|
|
return engine
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def mock_code_parser():
|
|
|
"""模拟代码解析器fixture"""
|
|
|
parser = Mock()
|
|
|
parser.parse_file = AsyncMock()
|
|
|
return parser
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 测试数据fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def test_code_samples():
|
|
|
"""测试代码样本fixture"""
|
|
|
return {
|
|
|
"arithmetic": """
|
|
|
int add(int a, int b) {
|
|
|
return a + b;
|
|
|
}
|
|
|
|
|
|
int multiply(int x, int y) {
|
|
|
return x * y;
|
|
|
}""",
|
|
|
"pointers": """
|
|
|
void safe_copy(char* dest, const char* src, size_t size) {
|
|
|
if (dest == NULL || src == NULL) return;
|
|
|
for (size_t i = 0; i < size && src[i] != '\\0'; i++) {
|
|
|
dest[i] = src[i];
|
|
|
}
|
|
|
}""",
|
|
|
"arrays": """
|
|
|
int sum_array(int* arr, int size) {
|
|
|
if (arr == NULL || size <= 0) return 0;
|
|
|
int sum = 0;
|
|
|
for (int i = 0; i < size; i++) {
|
|
|
sum += arr[i];
|
|
|
}
|
|
|
return sum;
|
|
|
}"""
|
|
|
}
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 环境变量fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
|
def mock_env_vars():
|
|
|
"""模拟环境变量fixture"""
|
|
|
original_env = os.environ.copy()
|
|
|
|
|
|
# 设置测试环境变量
|
|
|
os.environ.update({
|
|
|
'CODEDETECT_TEST': '1',
|
|
|
'CODEDETECT_CONFIG': 'tests/config/test_config.yaml',
|
|
|
'PYTHONPATH': str(project_root)
|
|
|
})
|
|
|
|
|
|
try:
|
|
|
yield os.environ
|
|
|
finally:
|
|
|
# 恢复原始环境变量
|
|
|
os.environ.clear()
|
|
|
os.environ.update(original_env)
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
# 测试会话fixtures
|
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
|
def test_session_id():
|
|
|
"""测试会话ID fixture"""
|
|
|
return f"test_session_{int(time.time())}"
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def test_report_data():
|
|
|
"""测试报告数据fixture"""
|
|
|
return {
|
|
|
"session_id": "test_session",
|
|
|
"start_time": time.time(),
|
|
|
"tests_total": 0,
|
|
|
"tests_passed": 0,
|
|
|
"tests_failed": 0,
|
|
|
"tests_skipped": 0,
|
|
|
"execution_time": 0.0,
|
|
|
"coverage_percentage": 0.0
|
|
|
} |