You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cbmc/codedetect/tests/conftest.py

580 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
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
}