""" Description : Diversity Constraint Injector (Layer 1) - Analyze existing test sequences - Detect overused patterns - Generate diversity constraints for Prompt - Recommend new test scenarios Author : CGA Enhancement Project Time : 2026/03/16 """ import logging import re from typing import List, Dict, Optional, Any, Tuple, Set from dataclasses import dataclass, field from enum import Enum # 支持两种导入方式:包导入和直接加载 try: from .test_history import ( TestHistoryManager, TestRecord, InputSequence, SequencePattern ) except ImportError: from test_history import ( TestHistoryManager, TestRecord, InputSequence, SequencePattern ) logger = logging.getLogger(__name__) # ============================================================================ # 配置常量 # ============================================================================ class DiversityConfig: """多样性约束配置""" # 编辑距离阈值 MIN_EDIT_DISTANCE = 3 # 模式过度使用阈值 OVERUSE_THRESHOLD = 3 # 新场景推荐数量 NEW_SCENARIO_COUNT = 3 # 序列长度限制(用于约束生成) MAX_SEQUENCE_LENGTH = 10 # 多样性得分权重 PATTERN_WEIGHT = 0.4 EDIT_DISTANCE_WEIGHT = 0.3 COVERAGE_WEIGHT = 0.3 # ============================================================================ # 约束类型定义 # ============================================================================ class ConstraintType(Enum): """约束类型枚举""" FORBID_SEQUENCE = "forbid_sequence" # 禁止特定序列 MIN_EDIT_DISTANCE = "min_edit_distance" # 最小编辑距离 AVOID_PATTERN = "avoid_pattern" # 革免模式 TRY_SCENARIO = "try_scenario" # 尝试新场景 EXPLORE_RANGE = "explore_range" # 探索范围 # ============================================================================ # 约束数据结构 # ============================================================================ @dataclass class DiversityConstraint: """ 多样性约束 Attributes: constraint_type: 约束类型 description: 约束描述 details: 详细信息 priority: 优先级 (1-5, 5最高) """ constraint_type: ConstraintType description: str details: Dict[str, Any] = field(default_factory=dict) priority: int = 3 def to_prompt_text(self) -> str: """转换为Prompt文本""" if self.constraint_type == ConstraintType.FORBID_SEQUENCE: return f"- AVOID using this sequence pattern: {self.details.get('pattern', 'unknown')}" elif self.constraint_type == ConstraintType.MIN_EDIT_DISTANCE: return f"- Your test sequence MUST differ from previous tests (edit distance >= {self.details.get('min_distance', 3)})" elif self.constraint_type == ConstraintType.AVOID_PATTERN: signal = self.details.get('signal', '') pattern = self.details.get('pattern', '') return f"- AVOID the pattern '{pattern}' for signal '{signal}' (already used {self.details.get('count', 0)} times)" elif self.constraint_type == ConstraintType.TRY_SCENARIO: return f"- TRY this new approach: {self.details.get('scenario', 'unknown')}" elif self.constraint_type == ConstraintType.EXPLORE_RANGE: return f"- EXPLORE values in range [{self.details.get('min', 0)}, {self.details.get('max', 255)}] for {self.details.get('signal', 'signal')}" return f"- {self.description}" # ============================================================================ # 序列分析器 # ============================================================================ class SequenceAnalyzer: """ 序列分析器 分析输入序列的特征和模式 """ @staticmethod def extract_value_range(values: List[Tuple[int, Any]]) -> Tuple[Any, Any]: """提取值范围""" if not values: return (0, 0) numeric_values = [] for _, v in values: # 尝试转换为数值 if isinstance(v, (int, float)): numeric_values.append(v) elif isinstance(v, str): # 处理 '0, '1, 'x 等 if v in ['0', '1', 'x', 'z']: numeric_values.append(int(v) if v.isdigit() else 0) # 处理带位宽的值 match = re.match(r"(\d+)'[bdh]([0-9a-fA-fA-FxXzZ_]+)", v) if match: try: numeric_values.append(int(match.group(2), 16)) except: pass if numeric_values: return (min(numeric_values), max(numeric_values)) return (0, 0) @staticmethod def detect_transition_pattern(values: List[Tuple[int, Any]]) -> str: """检测转换模式""" if len(values) < 2: return "single" # 提取值序列 val_seq = [v for _, v in values] # 检测递增 if all(str(val_seq[i]) <= str(val_seq[i+1]) for i in range(len(val_seq)-1)): return "incremental" # 检测递减 if all(str(val_seq[i]) >= str(val_seq[i+1]) for i in range(len(val_seq)-1)): return "decremental" # 检测交替 if len(val_seq) >= 4: if val_seq[0] == val_seq[2] and val_seq[1] == val_seq[3]: return "alternating" # 检测脉冲(单个变化后恢复) if len(val_seq) == 3 and val_seq[0] == val_seq[2] != val_seq[1]: return "pulse" return "random" @staticmethod def calculate_sequence_length(code: str) -> int: """计算代码中的操作序列长度""" # 统计赋值语句数量 assignments = len(re.findall(r'\w+\s*=\s*\S+\s*;', code)) # 统计repeat语句 repeats = re.findall(r'repeat\s*\(\s*(\d+)\s*\)', code) repeat_cycles = sum(int(r) for r in repeats) return assignments + repeat_cycles # ============================================================================ # 场景推荐器 # ============================================================================ class ScenarioRecommender: """ 场景推荐器 根据历史记录和未覆盖功能点推荐新测试场景 """ # 场景模板 SCENARIO_TEMPLATES = { 'fsm': [ "Test state transition from {state_a} to {state_b}", "Test illegal state transition handling", "Test state machine reset behavior", "Test state holding under stable inputs" ], 'counter': [ "Test counter overflow behavior (count to max value)", "Test counter underflow (if applicable)", "Test counter reset during counting", "Test counter enable/disable control" ], 'branch': [ "Test boundary condition: {condition} at threshold", "Test all branches of nested if-else", "Test case statement with all possible values" ], 'protocol': [ "Test handshake timeout scenario", "Test back-to-back transactions", "Test protocol violation handling" ], 'general': [ "Apply random input patterns for extended duration", "Test with boundary values (all 0s, all 1s)", "Test rapid signal transitions", "Test power-on/reset sequence variations" ] } def __init__(self, history_manager: TestHistoryManager): self.history = history_manager def recommend_scenarios(self, uncovered_functions: List[Dict], covered_patterns: Set[str] = None) -> List[str]: """ 推荐新的测试场景 Args: uncovered_functions: 未覆盖的功能点列表 covered_patterns: 已覆盖的模式集合 Returns: 推荐场景列表 """ recommendations = [] covered_patterns = covered_patterns or set() # 基于未覆盖功能点推荐 for func in uncovered_functions[:3]: func_type = func.get('type', 'general') func_name = func.get('name', '') templates = self.SCENARIO_TEMPLATES.get(func_type, self.SCENARIO_TEMPLATES['general']) for template in templates[:1]: # 每个功能点取一个模板 scenario = self._fill_template(template, func) if scenario not in covered_patterns: recommendations.append(scenario) # 基于历史分析推荐 if self.history.records: # 分析已使用的场景类型 used_patterns = set() for record in self.history.records: for seq in record.input_sequences: pattern = SequenceAnalyzer.detect_transition_pattern(seq.values) used_patterns.add(pattern) # 推荐未使用的场景类型 all_patterns = {'incremental', 'decremental', 'alternating', 'pulse', 'random'} unused_patterns = all_patterns - used_patterns if unused_patterns: recommendations.append(f"Try {list(unused_patterns)[0]} input pattern (different from your usual approach)") # 确保有足够的推荐 while len(recommendations) < DiversityConfig.NEW_SCENARIO_COUNT: recommendations.append("Explore a completely different input sequence than before") return recommendations[:DiversityConfig.NEW_SCENARIO_COUNT] def _fill_template(self, template: str, func: Dict) -> str: """填充场景模板""" result = template # 替换占位符 if '{state_a}' in template or '{state_b}' in template: states = func.get('states', ['STATE_A', 'STATE_B']) if len(states) >= 2: result = result.replace('{state_a}', states[0]) result = result.replace('{state_b}', states[1]) if '{condition}' in template: condition = func.get('condition', 'signal') result = result.replace('{condition}', condition) return result # ============================================================================ # 约束生成器 # ============================================================================ class ConstraintGenerator: """ 约束生成器 根据历史分析生成多样性约束 """ def __init__(self, history_manager: TestHistoryManager): self.history = history_manager self.analyzer = SequenceAnalyzer() def generate_constraints(self, target_function: str = None, uncovered_functions: List[Dict] = None) -> List[DiversityConstraint]: """ 生成多样性约束 Args: target_function: 当前目标功能点 uncovered_functions: 未覆盖功能点列表 Returns: 约束列表 """ constraints = [] if not self.history.records: return constraints # 1. 生成过度使用模式约束 overused = self.history.get_overused_patterns(DiversityConfig.OVERUSE_THRESHOLD) for pattern in overused[:3]: # 最多3个 constraints.append(DiversityConstraint( constraint_type=ConstraintType.AVOID_PATTERN, description=f"Avoid overused pattern for {pattern.signal_name}", details={ 'signal': pattern.signal_name, 'pattern': pattern.pattern, 'count': pattern.count }, priority=5 )) # 2. 生成编辑距离约束 recent_count = min(5, len(self.history.records)) if recent_count > 00: constraints.append(DiversityConstraint( constraint_type=ConstraintType.MIN_EDIT_DISTANCE, description="Maintain minimum edit distance from recent tests", details={ 'min_distance': DiversityConfig.MIN_EDIT_DISTANCE, 'reference_count': recent_count }, priority=4 )) # 3. 生成值范围探索约束 if uncovered_functions: for func in uncovered_functions[:2]: # 根据功能点类型生成范围约束 if func.get('type') == 'counter': max_val = func.get('max_value', 255) constraints.append(DiversityConstraint( constraint_type=ConstraintType.EXPLORE_RANGE, description=f"Explore counter boundary values", details={ 'signal': func.get('name', 'counter'), 'min': 0, 'max': max_val }, priority=3 )) # 按优先级排序 constraints.sort(key=lambda c: c.priority, reverse=True) return constraints def generate_forbidden_sequence_prompt(self) -> str: """生成禁止序列提示""" overused = self.history.get_overused_patterns(DiversityConfig.OVERUSE_THRESHOLD) if not overused: return "" lines = ["[DIVERSITY CONSTRAINTS - AVOID THESE OVERUSED PATTERNS]"] for i, pattern in enumerate(overused[:5], 1): lines.append(f"{i}. Signal '{pattern.signal_name}': {pattern.pattern[:50]}") lines.append(f" (This pattern has been used {pattern.count} times already)") lines.append("\nPlease create a DIFFERENT input sequence to improve test diversity.") return "\n".join(lines) # ============================================================================ # 多样性约束注入器(主入口) # ============================================================================ class DiversityInjector: """ 多样性约束注入器 - 第1层主入口 整合序列分析、模式检测、约束生成,提供统一的多样性约束接口 """ def __init__(self, history_manager: TestHistoryManager = None): """ Args: history_manager: 测试历史管理器 """ self.history = history_manager or TestHistoryManager() self.constraint_generator = ConstraintGenerator(self.history) self.scenario_recommender = ScenarioRecommender(self.history) def inject_diversity_constraints(self, prompt: str, target_function: str = None, uncovered_functions: List[Dict] = None) -> str: """ 将多样性约束注入到Prompt中 Args: prompt: 废始Prompt target_function: 当前目标功能点 uncovered_functions: 未覆盖功能点列表 Returns: 注入约束后的Prompt """ if not self.history.records: return prompt # 没有历史记录时不注入 # 生成约束 constraints = self.constraint_generator.generate_constraints( target_function=target_function, uncovered_functions=uncovered_functions ) # 生成推荐场景 recommendations = self.scenario_recommender.recommend_scenarios( uncovered_functions=uncovered_functions or [] ) # 构建约束文本 constraint_text = self._build_constraint_section(constraints, recommendations) # 找到插入点(在 [OUTPUT REQUIREMENTS] 之前插入) insert_marker = "[OUTPUT REQUIREMENTS" if insert_marker in prompt: parts = prompt.split(insert_marker, 1) enhanced_prompt = parts[0] + constraint_text + "\n\n" + insert_marker + parts[1] else: # 如果找不到标记,追加到末尾 enhanced_prompt = prompt + "\n\n" + constraint_text return enhanced_prompt def _build_constraint_section(self, constraints: List[DiversityConstraint], recommendations: List[str]) -> str: """构建约束章节""" lines = [] lines.append("[DIVERSITY CONSTRAINTS - CRITICAL]") lines.append("To improve test effectiveness, follow these diversity requirements:") lines.append("") # 添加约束 for constraint in constraints: lines.append(constraint.to_prompt_text()) lines.append("") # 添加推荐场景 if recommendations: lines.append("[RECOMMENDED NEW APPROACHES]") for i, rec in enumerate(recommendations, 1): lines.append(f"{i}. {rec}") lines.append("") lines.append("IMPORTANT: Repeated test patterns reduce coverage improvement efficiency.") lines.append("Generate a DISTINCTLY DIFFERENT test sequence from previous attempts.") return "\n".join(lines) def get_diversity_context(self) -> str: """获取多样性上下文信息(用于Prompt)""" if not self.history.records: return "" stats = self.history.get_statistics() overused = self.history.get_overused_patterns(DiversityConfig.OVERUSE_THRESHOLD) context_lines = [] context_lines.append(f"Test History: {stats['total_tests']} tests generated") context_lines.append(f"Unique Patterns: {stats['total_patterns']}") if overused: context_lines.append(f"Overused Patterns: {len(overused)} (avoid these)") return "\n".join(context_lines) def evaluate_diversity(self, new_code: str, known_signals: List[str] = None) -> Dict[str, float]: """ 评估新代码的多样性 Args: new_code: 新生成的测试代码 known_signals: 已知信号列表 Returns: 多样性评估结果 """ results = {} # 1. 序列多样性 if known_signals: self.history.sequence_extractor.set_known_signals(known_signals) new_sequences = self.history.sequence_extractor.extract(new_code) results['sequence_diversity'] = self.history.calculate_sequence_diversity(new_sequences) # 2. 编辑距离多样性 results['edit_distance_diversity'] = self.history.calculate_edit_distance_diversity(new_code) # 3. 综合得分 results['overall_diversity'] = ( DiversityConfig.PATTERN_WEIGHT * results['sequence_diversity'] + DiversityConfig.EDIT_DISTANCE_WEIGHT * results['edit_distance_diversity'] ) return results def record_test(self, code: str, target_function: str = "", coverage_score: float = 0.0, success: bool = False, iteration: int = 0, known_signals: List[str] = None) -> TestRecord: """ 记录新的测试用例 Args: code: 测试代码 target_function: 目标功能点 coverage_score: 覆盖率分数 success: 是否成功 iteration: 迭代次数 known_signals: 已知信号列表 Returns: 测试记录 """ return self.history.add_record( code=code, target_function=target_function, coverage_score=coverage_score, success=success, iteration=iteration, known_signals=known_signals ) def get_statistics(self) -> Dict[str, Any]: """获取统计信息""" return self.history.get_statistics() def generate_diversity_report(self) -> str: """生成多样性报告""" return self.history.get_diversity_report() # ============================================================================ # 便捷函数 # ============================================================================ def create_diversity_injector(history_file: str = None) -> DiversityInjector: """ 创建多样性约束注入器 Args: history_file: 屆史记录文件路径 Returns: 初始化完成的多样性约束注入器 """ history_manager = TestHistoryManager(history_file=history_file) return DiversityInjector(history_manager=history_manager)