Files
novalon-website/scripts/tools/test-scenarios.py
T

231 lines
7.9 KiB
Python

#!/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()