#!/usr/bin/env python3 """ Woodpecker CI 场景测试 模拟不同分支场景下的 CI/CD 流程执行 """ import yaml from pathlib import Path from typing import Dict, List, Set class ScenarioTester: """场景测试器""" def __init__(self, config_path: str): self.config_path = Path(config_path) with open(self.config_path, 'r', encoding='utf-8') as f: self.config = yaml.safe_load(f) self.scenarios = { "场景1: feature分支开发": { "branch": "feature/new-feature", "event": "push", "expected_steps": [ "lint", "type-check", "security-scan", "unit-tests", "e2e-smoke" ], "unexpected_steps": [ "e2e-standard", "e2e-deep", "build-image", "deploy-production", "archive-to-main" ] }, "场景2: feature分支PR": { "branch": "feature/another-feature", "event": "pull_request", "expected_steps": [ "lint", "type-check", "security-scan", "unit-tests", "e2e-smoke" ], "unexpected_steps": [ "e2e-standard", "e2e-deep", "build-image", "deploy-production", "archive-to-main" ] }, "场景3: dev分支集成": { "branch": "dev", "event": "push", "expected_steps": [ "lint", "type-check", "security-scan", "unit-tests", "e2e-standard" ], "unexpected_steps": [ "e2e-smoke", "e2e-deep", "build-image", "deploy-production", "archive-to-main" ] }, "场景4: release分支部署": { "branch": "release/v1.0.0", "event": "push", "expected_steps": [ "lint", "type-check", "security-scan", "unit-tests", "e2e-standard", "e2e-deep", "e2e-performance", "e2e-accessibility", "e2e-visual", "build-image", "deploy-production", "archive-to-main" ], "unexpected_steps": ["e2e-smoke"] }, "场景5: release主分支部署": { "branch": "release", "event": "push", "expected_steps": [ "lint", "type-check", "security-scan", "unit-tests", "e2e-standard", "e2e-deep", "e2e-performance", "e2e-accessibility", "e2e-visual", "build-image", "deploy-production", "archive-to-main" ], "unexpected_steps": ["e2e-smoke"] }, "场景6: main分支只读": { "branch": "main", "event": "push", "expected_steps": [], "unexpected_steps": [ "lint", "type-check", "security-scan", "unit-tests", "build-image", "deploy-production" ] } } def match_branch(self, pattern: str, branch: str) -> bool: """匹配分支模式""" if pattern == branch: return True if pattern.endswith("/**"): prefix = pattern[:-3] return branch.startswith(prefix + "/") return False def get_triggered_steps(self, branch: str, event: str) -> Set[str]: """获取触发的步骤""" triggered = set() for step_name, step_config in self.config.get('steps', {}).items(): if not isinstance(step_config, dict): continue when_config = step_config.get('when', {}) if not when_config: triggered.add(step_name) continue event_match = False branch_match = False if isinstance(when_config, list): for condition in when_config: if isinstance(condition, dict): if 'event' in condition: if event in condition['event']: event_match = True if 'branch' in condition: for pattern in condition['branch']: if self.match_branch(pattern, branch): branch_match = True break elif isinstance(when_config, dict): if 'event' in when_config: if event in when_config['event']: event_match = True if 'branch' in when_config: for pattern in when_config['branch']: if self.match_branch(pattern, branch): branch_match = True break if event_match and branch_match: triggered.add(step_name) return triggered def run_scenario(self, scenario_name: str, scenario: Dict) -> Dict: """运行单个场景""" branch = scenario['branch'] event = scenario['event'] expected = set(scenario['expected_steps']) unexpected = set(scenario['unexpected_steps']) triggered = self.get_triggered_steps(branch, event) missing = expected - triggered extra = triggered & unexpected passed = len(missing) == 0 and len(extra) == 0 return { "scenario": scenario_name, "branch": branch, "event": event, "passed": passed, "triggered": triggered, "expected": expected, "missing": missing, "extra": extra } def run_all_scenarios(self): """运行所有场景""" print("\n" + "="*70) print("Woodpecker CI 场景测试") print("="*70) results = [] for scenario_name, scenario in self.scenarios.items(): result = self.run_scenario(scenario_name, scenario) results.append(result) print(f"\n📋 {scenario_name}") print(f" 分支: {result['branch']}") print(f" 事件: {result['event']}") if result['passed']: print(f" ✅ 测试通过") else: print(f" ❌ 测试失败") print(f" 触发步骤 ({len(result['triggered'])}): {', '.join(sorted(result['triggered']))}") if result['missing']: print(f" ⚠️ 缺少步骤: {', '.join(sorted(result['missing']))}") if result['extra']: print(f" ⚠️ 多余步骤: {', '.join(sorted(result['extra']))}") print("\n" + "="*70) print("测试总结") print("="*70) passed_count = sum(1 for r in results if r['passed']) total_count = len(results) print(f"\n✅ 通过: {passed_count}/{total_count}") print(f"❌ 失败: {total_count - passed_count}/{total_count}") if passed_count == total_count: print("\n✅ 所有场景测试通过!") else: print("\n❌ 部分场景测试失败,请检查配置") print("\n" + "="*70) return results def main(): tester = ScenarioTester(".woodpecker.yml") results = tester.run_all_scenarios() failed = [r for r in results if not r['passed']] if failed: print("\n失败的场景:") for result in failed: print(f" - {result['scenario']}") exit(1) else: exit(0) if __name__ == "__main__": main()