opencode-skills-collection 3.1.23 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +24 -1
- package/bundled-skills/007/SKILL.md +2 -2
- package/bundled-skills/10-andruia-skill-smith/SKILL.md +1 -1
- package/bundled-skills/agent-squad/SKILL.md +5 -0
- package/bundled-skills/agent-squad/alex/SKILL.md +3 -0
- package/bundled-skills/agent-squad/aria/SKILL.md +3 -0
- package/bundled-skills/agent-squad/dep/SKILL.md +3 -0
- package/bundled-skills/agent-squad/luna/SKILL.md +4 -1
- package/bundled-skills/agent-squad/mason/SKILL.md +3 -0
- package/bundled-skills/agent-squad/max/SKILL.md +3 -0
- package/bundled-skills/agent-squad/quinn/SKILL.md +3 -0
- package/bundled-skills/agent-squad/rex/SKILL.md +3 -0
- package/bundled-skills/angular/metadata.json +1 -1
- package/bundled-skills/angular-best-practices/metadata.json +1 -1
- package/bundled-skills/angular-state-management/metadata.json +1 -1
- package/bundled-skills/angular-ui-patterns/metadata.json +1 -1
- package/bundled-skills/antigravity-skill-orchestrator/SKILL.md +2 -2
- package/bundled-skills/apify-actor-development/SKILL.md +2 -2
- package/bundled-skills/apple-container/SKILL.md +165 -0
- package/bundled-skills/apple-container/references/commands.md +1593 -0
- package/bundled-skills/apple-container/references/concepts.md +294 -0
- package/bundled-skills/apple-container/references/configuration.md +499 -0
- package/bundled-skills/apple-container/references/workflows.md +498 -0
- package/bundled-skills/ask-copilot/SKILL.md +112 -0
- package/bundled-skills/auto-research/SKILL.md +126 -0
- package/bundled-skills/brooks-lint/SKILL.md +1 -1
- package/bundled-skills/browser-harness/references/install.md +3 -1
- package/bundled-skills/chrome-extension-developer/SKILL.md +1 -1
- package/bundled-skills/clean-code-guard/SKILL.md +184 -0
- package/bundled-skills/clean-code-guard/references/ai-failure-modes.md +345 -0
- package/bundled-skills/clean-code-guard/references/comments-and-formatting.md +115 -0
- package/bundled-skills/clean-code-guard/references/dry-kiss-yagni.md +133 -0
- package/bundled-skills/clean-code-guard/references/naming-and-functions.md +187 -0
- package/bundled-skills/clean-code-guard/references/review-checklist.md +150 -0
- package/bundled-skills/clean-code-guard/references/solid.md +280 -0
- package/bundled-skills/clean-code-guard/references/sources.md +47 -0
- package/bundled-skills/cloudflare-security-audit/SKILL.md +137 -0
- package/bundled-skills/cloudflare-security-audit/references/AI-AND-LLM.md +67 -0
- package/bundled-skills/cloudflare-security-audit/references/ATTACK-CLASSES.md +116 -0
- package/bundled-skills/cloudflare-security-audit/references/CLIENT-SIDE.md +67 -0
- package/bundled-skills/cloudflare-security-audit/references/HUNTING.md +110 -0
- package/bundled-skills/cloudflare-security-audit/references/MEMORY-SAFETY-AND-BINARY.md +58 -0
- package/bundled-skills/cloudflare-security-audit/references/RECONNAISSANCE.md +46 -0
- package/bundled-skills/cloudflare-security-audit/references/VALIDATION-AND-REPORTING.md +109 -0
- package/bundled-skills/cloudflare-security-audit/references/WEB-PROTOCOL-AND-AUTH.md +85 -0
- package/bundled-skills/cloudflare-security-audit/resources/report-schema.json +210 -0
- package/bundled-skills/comfyui-gateway/references/troubleshooting.md +1 -1
- package/bundled-skills/commit/SKILL.md +2 -1
- package/bundled-skills/deepapi/SKILL.md +6 -6
- package/bundled-skills/docs/README.md +1 -1
- package/bundled-skills/docs/contributors/quality-bar.md +1 -1
- package/bundled-skills/docs/contributors/security-guardrails.md +1 -1
- package/bundled-skills/docs/integrations/jetski-cortex.md +5 -5
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +3 -3
- package/bundled-skills/docs/maintainers/ci-drift-fix.md +7 -5
- package/bundled-skills/docs/maintainers/merge-batch.md +24 -7
- package/bundled-skills/docs/maintainers/pr-autonomy.md +89 -0
- package/bundled-skills/docs/maintainers/release-notes-7.2.0.md +1 -1
- package/bundled-skills/docs/maintainers/release-process.md +9 -9
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +5 -5
- package/bundled-skills/docs/maintainers/skills-date-tracking.md +1 -1
- package/bundled-skills/docs/maintainers/skills-update-guide.md +2 -2
- package/bundled-skills/docs/users/{antigravity-awesome-skills-vs-awesome-claude-skills.md → agentic-awesome-skills-vs-awesome-claude-skills.md} +5 -5
- package/bundled-skills/docs/users/ai-agent-skills.md +4 -4
- package/bundled-skills/docs/users/best-claude-code-skills-github.md +3 -3
- package/bundled-skills/docs/users/best-cursor-skills-github.md +3 -3
- package/bundled-skills/docs/users/bundles.md +56 -57
- package/bundled-skills/docs/users/claude-code-skills.md +6 -6
- package/bundled-skills/docs/users/codex-cli-skills.md +5 -5
- package/bundled-skills/docs/users/cursor-skills.md +3 -3
- package/bundled-skills/docs/users/faq.md +27 -15
- package/bundled-skills/docs/users/gemini-cli-skills.md +4 -4
- package/bundled-skills/docs/users/getting-started.md +22 -12
- package/bundled-skills/docs/users/kiro-integration.md +7 -7
- package/bundled-skills/docs/users/plugins.md +8 -9
- package/bundled-skills/docs/users/security-skills.md +1 -1
- package/bundled-skills/docs/users/skills-vs-mcp-tools.md +1 -1
- package/bundled-skills/docs/users/specialized-plugin-roadmap.md +3 -4
- package/bundled-skills/docs/users/usage.md +9 -9
- package/bundled-skills/docs/users/visual-guide.md +7 -7
- package/bundled-skills/docs/users/windows-truncation-recovery.md +1 -1
- package/bundled-skills/docs/users/workflows.md +1 -1
- package/bundled-skills/docs/vietnamese/BUNDLES.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/FAQ.vi.md +15 -8
- package/bundled-skills/docs/vietnamese/GETTING_STARTED.vi.md +12 -5
- package/bundled-skills/docs/vietnamese/QUALITY_BAR.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/README.vi.md +21 -17
- package/bundled-skills/docs/vietnamese/SECURITY_GUARDRAILS.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/SKILLS_README.vi.md +2 -2
- package/bundled-skills/docs/vietnamese/TRANSLATION_PLAN.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/VISUAL_GUIDE.vi.md +8 -8
- package/bundled-skills/docs-guard/SKILL.md +109 -0
- package/bundled-skills/docs-guard/references/code-samples.md +46 -0
- package/bundled-skills/docs-guard/references/docstrings.md +60 -0
- package/bundled-skills/docs-guard/references/review-checklist.md +45 -0
- package/bundled-skills/docs-guard/references/sources.md +22 -0
- package/bundled-skills/docs-guard/references/verification.md +51 -0
- package/bundled-skills/efficient-web-research/SKILL.md +4 -0
- package/bundled-skills/electron-development/SKILL.md +1 -1
- package/bundled-skills/event-staffing-compliance/SKILL.md +3 -0
- package/bundled-skills/event-staffing-ordering/SKILL.md +3 -0
- package/bundled-skills/feature-tracking/SKILL.md +222 -0
- package/bundled-skills/flutter-expert/SKILL.md +6 -1
- package/bundled-skills/flutter-expert/resources/implementation-playbook.md +214 -0
- package/bundled-skills/gemini-deep-research/README.md +246 -0
- package/bundled-skills/gemini-deep-research/SKILL.md +136 -0
- package/bundled-skills/gemini-deep-research/requirements.txt +2 -0
- package/bundled-skills/gemini-deep-research/scripts/research.py +692 -0
- package/bundled-skills/git-pushing/SKILL.md +21 -4
- package/bundled-skills/git-pushing/scripts/smart_commit.sh +206 -11
- package/bundled-skills/go-in-depth/scripts/workflow-script.js +4 -1
- package/bundled-skills/grok-build/SKILL.md +167 -0
- package/bundled-skills/grok-build/references/cli.md +99 -0
- package/bundled-skills/huggingface-tool-builder/references/find_models_by_paper.sh +1 -1
- package/bundled-skills/idea-autopsy/SKILL.md +147 -0
- package/bundled-skills/it-manager-hospital/README.md +1 -1
- package/bundled-skills/it-manager-pro/README.md +1 -1
- package/bundled-skills/itil-expert/README.md +1 -1
- package/bundled-skills/langgraph/SKILL.md +1 -1
- package/bundled-skills/linkedin-content-generator/SKILL.md +1 -1
- package/bundled-skills/lint-and-validate/scripts/lint_runner.py +23 -23
- package/bundled-skills/lint-and-validate/scripts/type_coverage.py +30 -30
- package/bundled-skills/logic-lens/SKILL.md +1 -1
- package/bundled-skills/lore/LICENSE +21 -0
- package/bundled-skills/lore/README.md +386 -0
- package/bundled-skills/lore/README.zh-CN.md +386 -0
- package/bundled-skills/lore/SKILL.md +453 -0
- package/bundled-skills/lore/WORKFLOWS.md +216 -0
- package/bundled-skills/lore/WORKFLOWS.zh-CN.md +216 -0
- package/bundled-skills/lore/references/audit-template.md +60 -0
- package/bundled-skills/lore/references/compatibility.md +228 -0
- package/bundled-skills/lore/references/config.md +125 -0
- package/bundled-skills/lore/references/entry-format.md +70 -0
- package/bundled-skills/lore/references/history-command.md +106 -0
- package/bundled-skills/lore/references/monorepo-detection.md +78 -0
- package/bundled-skills/lore/references/platform-mirrors.md +377 -0
- package/bundled-skills/lore/references/stale-new-markers.md +62 -0
- package/bundled-skills/lore/references/summary-template.md +98 -0
- package/bundled-skills/lore/scripts/README.md +54 -0
- package/bundled-skills/lore/scripts/README.zh-CN.md +54 -0
- package/bundled-skills/lore/scripts/find_duplicates.py +211 -0
- package/bundled-skills/lore/scripts/find_stale.py +116 -0
- package/bundled-skills/lore/scripts/history.py +527 -0
- package/bundled-skills/lore/scripts/id_hash.py +32 -0
- package/bundled-skills/lore/scripts/list_entries.py +183 -0
- package/bundled-skills/lovable-cleanup/SKILL.md +3 -3
- package/bundled-skills/makepad-splash/SKILL.md +1 -1
- package/bundled-skills/mmx-cli/SKILL.md +14 -1
- package/bundled-skills/monopoly/SKILL.md +5 -0
- package/bundled-skills/monopoly/patterns/SKILL.md +4 -0
- package/bundled-skills/monopoly/scale-benchmarks/SKILL.md +4 -0
- package/bundled-skills/monopoly/security-checklist/SKILL.md +4 -0
- package/bundled-skills/monopoly/tech-matrix/SKILL.md +4 -0
- package/bundled-skills/mtls-configuration/SKILL.md +1 -1
- package/bundled-skills/network-101/SKILL.md +4 -5
- package/bundled-skills/nika/SKILL.md +270 -0
- package/bundled-skills/not-a-vibe-coder/SKILL.md +4 -0
- package/bundled-skills/ontoly-software-graph/SKILL.md +160 -0
- package/bundled-skills/openclaw-github-repo-commander/SKILL.md +1 -1
- package/bundled-skills/pilot-protocol/SKILL.md +6 -3
- package/bundled-skills/postgres-readonly-queries/README.md +77 -0
- package/bundled-skills/postgres-readonly-queries/SKILL.md +152 -0
- package/bundled-skills/postgres-readonly-queries/connections.example.json +34 -0
- package/bundled-skills/postgres-readonly-queries/requirements.txt +1 -0
- package/bundled-skills/postgres-readonly-queries/scripts/query.py +262 -0
- package/bundled-skills/pptx-deck-creation/SKILL.md +275 -0
- package/bundled-skills/pptx-deck-creation/references/audit-checklist.md +156 -0
- package/bundled-skills/pptx-deck-creation/references/design-profiles.md +96 -0
- package/bundled-skills/pptx-deck-creation/references/python-snippets.md +150 -0
- package/bundled-skills/pptx-deck-creation/references/reference-deck-analysis.md +91 -0
- package/bundled-skills/pptx-deck-creation/references/visual-asset-adapters.md +158 -0
- package/bundled-skills/privilege-escalation-methods/SKILL.md +1 -1
- package/bundled-skills/product-decision-agent/SKILL.md +94 -0
- package/bundled-skills/product-decision-agent/agents/openai.yaml +7 -0
- package/bundled-skills/product-decision-agent/references/methodology-basis.md +232 -0
- package/bundled-skills/product-decision-agent/references/product-playbooks.md +813 -0
- package/bundled-skills/product-decision-agent/references/reasoning-engine.md +289 -0
- package/bundled-skills/product-decision-agent/references/response-examples.md +279 -0
- package/bundled-skills/product-decision-agent/scripts/quality_gate.py +221 -0
- package/bundled-skills/product-decision-agent/scripts/test_quality_gate.py +94 -0
- package/bundled-skills/pytest-skill/reference/playbook.md +358 -0
- package/bundled-skills/quit-sponsor/SKILL.md +96 -0
- package/bundled-skills/rclone-cli/references/providers/s3.md +1 -1
- package/bundled-skills/security-and-hardening/SKILL.md +1 -1
- package/bundled-skills/semgrep-rule-creator/SKILL.md +6 -6
- package/bundled-skills/skill-creator/README.md +3 -3
- package/bundled-skills/skill-creator/SKILL.md +8 -4
- package/bundled-skills/skill-creator/scripts/quick_validate.py +1 -1
- package/bundled-skills/sql-injection-testing/SKILL.md +4 -4
- package/bundled-skills/subagent-orchestrator/README.md +1 -1
- package/bundled-skills/super-code/bash/SKILL.md +3 -0
- package/bundled-skills/super-code/c/SKILL.md +3 -0
- package/bundled-skills/super-code/cpp/SKILL.md +3 -0
- package/bundled-skills/super-code/csharp/SKILL.md +3 -0
- package/bundled-skills/super-code/dart/SKILL.md +3 -0
- package/bundled-skills/super-code/elixir/SKILL.md +3 -0
- package/bundled-skills/super-code/go/SKILL.md +3 -0
- package/bundled-skills/super-code/java/SKILL.md +3 -0
- package/bundled-skills/super-code/kotlin/SKILL.md +3 -0
- package/bundled-skills/super-code/php/SKILL.md +3 -0
- package/bundled-skills/super-code/python/SKILL.md +3 -0
- package/bundled-skills/super-code/ruby/SKILL.md +3 -0
- package/bundled-skills/super-code/rust/SKILL.md +3 -0
- package/bundled-skills/super-code/scala/SKILL.md +3 -0
- package/bundled-skills/super-code/swift/SKILL.md +3 -0
- package/bundled-skills/super-code/typescript/SKILL.md +3 -0
- package/bundled-skills/telegram-bot-messaging/README.md +185 -0
- package/bundled-skills/telegram-bot-messaging/SKILL.md +101 -0
- package/bundled-skills/telegram-bot-messaging/scripts/telegram.sh +420 -0
- package/bundled-skills/test-guard/SKILL.md +132 -0
- package/bundled-skills/test-guard/references/jest.md +50 -0
- package/bundled-skills/test-guard/references/llm-app-testing.md +40 -0
- package/bundled-skills/test-guard/references/phpunit.md +56 -0
- package/bundled-skills/test-guard/references/pytest.md +65 -0
- package/bundled-skills/trading-ledger/SKILL.md +11 -3
- package/bundled-skills/uizze-ui-research/SKILL.md +93 -0
- package/bundled-skills/vibe-code-auditor/SKILL.md +1 -1
- package/bundled-skills/vibecode-production-qa-validator/SKILL.md +1 -1
- package/bundled-skills/videodb/scripts/ws_listener.py +29 -15
- package/bundled-skills/voice-ai-engine-development/README.md +1 -1
- package/bundled-skills/vulnerability-scanner/SKILL.md +1 -1
- package/bundled-skills/warehouse/SKILL.md +154 -0
- package/bundled-skills/woo-guard/SKILL.md +113 -0
- package/bundled-skills/woo-guard/references/checkout-and-money.md +64 -0
- package/bundled-skills/woo-guard/references/hpos-and-crud.md +87 -0
- package/bundled-skills/woo-guard/references/review-checklist.md +44 -0
- package/bundled-skills/woo-guard/references/sources.md +17 -0
- package/bundled-skills/wp-guard/SKILL.md +119 -0
- package/bundled-skills/wp-guard/references/i18n.md +88 -0
- package/bundled-skills/wp-guard/references/performance.md +70 -0
- package/bundled-skills/wp-guard/references/review-checklist.md +57 -0
- package/bundled-skills/wp-guard/references/security.md +100 -0
- package/bundled-skills/wp-guard/references/sources.md +27 -0
- package/bundled-skills/writing-skills/gotchas.md +1 -1
- package/bundled-skills/writing-skills/references/standards/metadata-standard.md +1 -1
- package/bundled-skills/x-twitter-scraper/SKILL.md +14 -32
- package/bundled-skills/xss-html-injection/SKILL.md +3 -3
- package/bundled-skills/youtube-seo-optimizer/SKILL.md +1 -1
- package/bundled-skills/youtube-summarizer/CHANGELOG.md +2 -2
- package/package.json +1 -1
- package/skills_index.json +273 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Quality checks for Maoxuan Product Agent sample outputs.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
quality_gate.py output1.md output2.md ...
|
|
6
|
+
|
|
7
|
+
The script catches common regressions:
|
|
8
|
+
- source/theory leakage
|
|
9
|
+
- English-dominant answers in Chinese work scenes
|
|
10
|
+
- vague advice without concrete next actions
|
|
11
|
+
- excessive question dumping in the "需要确认" section
|
|
12
|
+
- missing judgment, risk, or decision signal
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
HARD_BANNED = [
|
|
23
|
+
r"毛泽东",
|
|
24
|
+
r"毛选",
|
|
25
|
+
r"矛盾论",
|
|
26
|
+
r"实践论",
|
|
27
|
+
r"教员",
|
|
28
|
+
r"毛主席",
|
|
29
|
+
r"同志",
|
|
30
|
+
r"阶级",
|
|
31
|
+
r"革命",
|
|
32
|
+
r"斗争",
|
|
33
|
+
r"主席指出",
|
|
34
|
+
r"《[^》]+》认为",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
SOFT_BANNED = [
|
|
38
|
+
r"辩证",
|
|
39
|
+
r"唯物",
|
|
40
|
+
r"经典",
|
|
41
|
+
r"原文",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
VAGUE_PATTERNS = [
|
|
45
|
+
r"提升用户体验",
|
|
46
|
+
r"加强沟通",
|
|
47
|
+
r"多看数据",
|
|
48
|
+
r"深入了解用户",
|
|
49
|
+
r"持续优化",
|
|
50
|
+
r"形成闭环",
|
|
51
|
+
r"赋能",
|
|
52
|
+
r"抓手",
|
|
53
|
+
r"打透认知",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
JUDGMENT_HINTS = [
|
|
57
|
+
"问题判断",
|
|
58
|
+
"核心是",
|
|
59
|
+
"关键是",
|
|
60
|
+
"不是",
|
|
61
|
+
"当前最",
|
|
62
|
+
"先不要",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
ACTION_HINTS = [
|
|
66
|
+
"行动建议",
|
|
67
|
+
"下一步",
|
|
68
|
+
"今天",
|
|
69
|
+
"本周",
|
|
70
|
+
"24 小时",
|
|
71
|
+
"48 小时",
|
|
72
|
+
"2 天",
|
|
73
|
+
"一周",
|
|
74
|
+
"两周",
|
|
75
|
+
"负责人",
|
|
76
|
+
"指标",
|
|
77
|
+
"验证",
|
|
78
|
+
"实验",
|
|
79
|
+
"灰度",
|
|
80
|
+
"拆",
|
|
81
|
+
"砍",
|
|
82
|
+
"暂停",
|
|
83
|
+
"停止",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
DECISION_HINTS = [
|
|
87
|
+
"成功指标",
|
|
88
|
+
"失败指标",
|
|
89
|
+
"主指标",
|
|
90
|
+
"影响指标",
|
|
91
|
+
"护栏",
|
|
92
|
+
"全量",
|
|
93
|
+
"停止",
|
|
94
|
+
"暂停",
|
|
95
|
+
"复盘",
|
|
96
|
+
"监控",
|
|
97
|
+
"达标",
|
|
98
|
+
"不达标",
|
|
99
|
+
"决策",
|
|
100
|
+
"上线",
|
|
101
|
+
"回滚",
|
|
102
|
+
"留存",
|
|
103
|
+
"转化",
|
|
104
|
+
"回复率",
|
|
105
|
+
"复访",
|
|
106
|
+
"D7",
|
|
107
|
+
"7 日",
|
|
108
|
+
"7日",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
RISK_HINTS = [
|
|
112
|
+
"风险提醒",
|
|
113
|
+
"不要",
|
|
114
|
+
"先不要",
|
|
115
|
+
"暂时不要",
|
|
116
|
+
"不建议",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def count_questions(text: str) -> int:
|
|
121
|
+
"""Count questions in the "需要确认" section only."""
|
|
122
|
+
match = re.search(
|
|
123
|
+
r"(?:^|\n)(?:#{1,3}\s*)?(?:\*\*)?需要确认(?:\*\*)?[^\n]*\n"
|
|
124
|
+
r"(?P<section>.*?)(?=\n(?:#{1,3}\s+|\*\*[^*\n]+?\*\*)|\Z)",
|
|
125
|
+
text,
|
|
126
|
+
re.DOTALL,
|
|
127
|
+
)
|
|
128
|
+
if not match:
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
section = match.group("section")
|
|
132
|
+
questions = 0
|
|
133
|
+
for line in section.splitlines():
|
|
134
|
+
stripped = line.strip()
|
|
135
|
+
if not stripped:
|
|
136
|
+
continue
|
|
137
|
+
# Count sentence-ending question marks. This ignores URL query strings
|
|
138
|
+
# such as https://example.com?a=1 while still catching two questions
|
|
139
|
+
# written on the same line.
|
|
140
|
+
questions += len(
|
|
141
|
+
re.findall(r"?|\?(?=[\s\u4e00-\u9fff]|$)", stripped)
|
|
142
|
+
)
|
|
143
|
+
return questions
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def chinese_ratio(text: str) -> float:
|
|
147
|
+
zh = len(re.findall(r"[\u4e00-\u9fff]", text))
|
|
148
|
+
en = len(re.findall(r"[A-Za-z]", text))
|
|
149
|
+
if zh + en == 0:
|
|
150
|
+
return 0.0
|
|
151
|
+
return zh / (zh + en)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def check_file(path: Path) -> tuple[list[str], list[str]]:
|
|
155
|
+
text = path.read_text(encoding="utf-8")
|
|
156
|
+
errors: list[str] = []
|
|
157
|
+
warnings: list[str] = []
|
|
158
|
+
|
|
159
|
+
for pattern in HARD_BANNED:
|
|
160
|
+
if re.search(pattern, text, re.IGNORECASE):
|
|
161
|
+
errors.append(f"exposes hard-banned source/style term: {pattern}")
|
|
162
|
+
|
|
163
|
+
for pattern in SOFT_BANNED:
|
|
164
|
+
if re.search(pattern, text, re.IGNORECASE):
|
|
165
|
+
warnings.append(f"contains review-needed term: {pattern}")
|
|
166
|
+
|
|
167
|
+
vague_hits = [p for p in VAGUE_PATTERNS if re.search(p, text)]
|
|
168
|
+
if vague_hits:
|
|
169
|
+
errors.append(f"contains vague phrase(s): {', '.join(vague_hits)}")
|
|
170
|
+
|
|
171
|
+
if chinese_ratio(text) < 0.72:
|
|
172
|
+
errors.append("answer is not Chinese-dominant enough")
|
|
173
|
+
|
|
174
|
+
if not any(hint in text for hint in JUDGMENT_HINTS):
|
|
175
|
+
errors.append("missing problem judgment")
|
|
176
|
+
|
|
177
|
+
if not any(hint in text for hint in ACTION_HINTS):
|
|
178
|
+
errors.append("missing concrete action hints")
|
|
179
|
+
|
|
180
|
+
if not any(hint in text for hint in DECISION_HINTS):
|
|
181
|
+
errors.append("missing metric/decision/review signal")
|
|
182
|
+
|
|
183
|
+
if not any(hint in text for hint in RISK_HINTS):
|
|
184
|
+
errors.append("missing risk or stop-doing guidance")
|
|
185
|
+
|
|
186
|
+
if count_questions(text) > 3:
|
|
187
|
+
errors.append("asks too many questions")
|
|
188
|
+
|
|
189
|
+
stripped_len = len(text.strip())
|
|
190
|
+
if stripped_len < 120:
|
|
191
|
+
errors.append("output is likely too thin")
|
|
192
|
+
if stripped_len > 2200:
|
|
193
|
+
errors.append("output is likely too verbose")
|
|
194
|
+
|
|
195
|
+
return errors, warnings
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def main(argv: list[str]) -> int:
|
|
199
|
+
if not argv:
|
|
200
|
+
print("Usage: quality_gate.py output1.md output2.md ...", file=sys.stderr)
|
|
201
|
+
return 2
|
|
202
|
+
|
|
203
|
+
failed = False
|
|
204
|
+
for item in argv:
|
|
205
|
+
path = Path(item)
|
|
206
|
+
errors, warnings = check_file(path)
|
|
207
|
+
if errors:
|
|
208
|
+
failed = True
|
|
209
|
+
print(f"FAIL {path}")
|
|
210
|
+
for err in errors:
|
|
211
|
+
print(f" - {err}")
|
|
212
|
+
else:
|
|
213
|
+
print(f"PASS {path}")
|
|
214
|
+
for warning in warnings:
|
|
215
|
+
print(f" WARN {warning}")
|
|
216
|
+
|
|
217
|
+
return 1 if failed else 0
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Regression tests for the sample-output quality gate."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import importlib.util
|
|
7
|
+
import tempfile
|
|
8
|
+
import unittest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
MODULE_PATH = Path(__file__).with_name("quality_gate.py")
|
|
13
|
+
SPEC = importlib.util.spec_from_file_location("quality_gate", MODULE_PATH)
|
|
14
|
+
if SPEC is None or SPEC.loader is None:
|
|
15
|
+
raise RuntimeError(f"Cannot load {MODULE_PATH}")
|
|
16
|
+
quality_gate = importlib.util.module_from_spec(SPEC)
|
|
17
|
+
SPEC.loader.exec_module(quality_gate)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
VALID_OUTPUT = """# 测试回答
|
|
21
|
+
|
|
22
|
+
**问题判断**
|
|
23
|
+
当前最关键的是先拆清楚留存下降来自哪个用户群体。
|
|
24
|
+
|
|
25
|
+
**原因分析**
|
|
26
|
+
- 总量变化可能掩盖渠道和版本差异。
|
|
27
|
+
|
|
28
|
+
**行动建议**
|
|
29
|
+
1. 今天按渠道和版本拆 D7 留存,并用结果决定是否回滚。
|
|
30
|
+
|
|
31
|
+
**风险提醒**
|
|
32
|
+
不要在口径不清时直接全量回滚。
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class QuestionCountingTests(unittest.TestCase):
|
|
37
|
+
def test_counts_only_confirmation_section(self) -> None:
|
|
38
|
+
text = VALID_OUTPUT + "\n前文的问题是什么?\n"
|
|
39
|
+
self.assertEqual(quality_gate.count_questions(text), 0)
|
|
40
|
+
|
|
41
|
+
def test_counts_multiple_questions_on_one_line(self) -> None:
|
|
42
|
+
text = VALID_OUTPUT + """
|
|
43
|
+
|
|
44
|
+
**需要确认**
|
|
45
|
+
1. 掉的是新用户吗?集中在哪个版本?
|
|
46
|
+
2. 埋点改过吗?是否有发布事故?
|
|
47
|
+
"""
|
|
48
|
+
self.assertEqual(quality_gate.count_questions(text), 4)
|
|
49
|
+
|
|
50
|
+
def test_ignores_url_query_string(self) -> None:
|
|
51
|
+
text = VALID_OUTPUT + """
|
|
52
|
+
|
|
53
|
+
**需要确认**
|
|
54
|
+
1. 请确认看板 https://example.com/report?cohort=new 是否采用同一口径。
|
|
55
|
+
"""
|
|
56
|
+
self.assertEqual(quality_gate.count_questions(text), 0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FileCheckingTests(unittest.TestCase):
|
|
60
|
+
def check(self, text: str) -> tuple[list[str], list[str]]:
|
|
61
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
62
|
+
path = Path(temp_dir) / "sample.md"
|
|
63
|
+
path.write_text(text, encoding="utf-8")
|
|
64
|
+
return quality_gate.check_file(path)
|
|
65
|
+
|
|
66
|
+
def test_valid_output_passes(self) -> None:
|
|
67
|
+
errors, warnings = self.check(VALID_OUTPUT)
|
|
68
|
+
self.assertEqual(errors, [])
|
|
69
|
+
self.assertEqual(warnings, [])
|
|
70
|
+
|
|
71
|
+
def test_hard_source_leak_fails(self) -> None:
|
|
72
|
+
errors, _ = self.check(VALID_OUTPUT + "\n毛泽东认为应该这样做。\n")
|
|
73
|
+
self.assertTrue(any("hard-banned" in error for error in errors))
|
|
74
|
+
|
|
75
|
+
def test_soft_term_warns_without_failing(self) -> None:
|
|
76
|
+
errors, warnings = self.check(VALID_OUTPUT + "\n这是一个经典案例。\n")
|
|
77
|
+
self.assertEqual(errors, [])
|
|
78
|
+
self.assertTrue(any("review-needed" in warning for warning in warnings))
|
|
79
|
+
|
|
80
|
+
def test_more_than_three_confirmation_questions_fails(self) -> None:
|
|
81
|
+
text = VALID_OUTPUT + """
|
|
82
|
+
|
|
83
|
+
**需要确认**
|
|
84
|
+
1. 指标是什么?
|
|
85
|
+
2. 持续多久?
|
|
86
|
+
3. 哪类用户?
|
|
87
|
+
4. 试过什么?
|
|
88
|
+
"""
|
|
89
|
+
errors, _ = self.check(text)
|
|
90
|
+
self.assertIn("asks too many questions", errors)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
unittest.main()
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# pytest — Advanced Implementation Playbook
|
|
2
|
+
|
|
3
|
+
## §1 — Production Configuration
|
|
4
|
+
|
|
5
|
+
```ini
|
|
6
|
+
# pytest.ini
|
|
7
|
+
[pytest]
|
|
8
|
+
testpaths = tests
|
|
9
|
+
python_files = test_*.py
|
|
10
|
+
python_classes = Test*
|
|
11
|
+
python_functions = test_*
|
|
12
|
+
addopts = -v --strict-markers --tb=short -q
|
|
13
|
+
markers =
|
|
14
|
+
slow: marks tests as slow (deselect with '-m "not slow"')
|
|
15
|
+
integration: marks integration tests
|
|
16
|
+
smoke: marks smoke tests
|
|
17
|
+
api: marks API tests
|
|
18
|
+
filterwarnings =
|
|
19
|
+
error
|
|
20
|
+
ignore::DeprecationWarning
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```toml
|
|
24
|
+
# pyproject.toml (alternative)
|
|
25
|
+
[tool.pytest.ini_options]
|
|
26
|
+
testpaths = ["tests"]
|
|
27
|
+
addopts = "-v --strict-markers --tb=short"
|
|
28
|
+
markers = [
|
|
29
|
+
"slow: marks tests as slow",
|
|
30
|
+
"integration: integration tests",
|
|
31
|
+
"smoke: smoke tests",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.coverage.run]
|
|
35
|
+
source = ["src"]
|
|
36
|
+
omit = ["tests/*", "*/migrations/*"]
|
|
37
|
+
|
|
38
|
+
[tool.coverage.report]
|
|
39
|
+
fail_under = 80
|
|
40
|
+
show_missing = true
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## §2 — Fixtures (Scoping, Factories, Teardown)
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# conftest.py — shared fixtures
|
|
47
|
+
import pytest
|
|
48
|
+
from sqlalchemy import create_engine
|
|
49
|
+
from sqlalchemy.orm import Session
|
|
50
|
+
|
|
51
|
+
# Session-scoped: created once per test session
|
|
52
|
+
@pytest.fixture(scope="session")
|
|
53
|
+
def engine():
|
|
54
|
+
engine = create_engine("sqlite:///test.db")
|
|
55
|
+
Base.metadata.create_all(engine)
|
|
56
|
+
yield engine
|
|
57
|
+
Base.metadata.drop_all(engine)
|
|
58
|
+
|
|
59
|
+
# Function-scoped: created per test (default), auto-cleanup
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def db_session(engine):
|
|
62
|
+
connection = engine.connect()
|
|
63
|
+
transaction = connection.begin()
|
|
64
|
+
session = Session(bind=connection)
|
|
65
|
+
yield session
|
|
66
|
+
session.close()
|
|
67
|
+
transaction.rollback()
|
|
68
|
+
connection.close()
|
|
69
|
+
|
|
70
|
+
# Factory fixture — create multiple instances
|
|
71
|
+
@pytest.fixture
|
|
72
|
+
def user_factory(db_session):
|
|
73
|
+
created = []
|
|
74
|
+
def _create_user(name="Test User", email=None, role="viewer"):
|
|
75
|
+
email = email or f"{name.lower().replace(' ', '.')}@test.com"
|
|
76
|
+
user = User(name=name, email=email, role=role)
|
|
77
|
+
db_session.add(user)
|
|
78
|
+
db_session.commit()
|
|
79
|
+
created.append(user)
|
|
80
|
+
return user
|
|
81
|
+
yield _create_user
|
|
82
|
+
for user in created:
|
|
83
|
+
db_session.delete(user)
|
|
84
|
+
db_session.commit()
|
|
85
|
+
|
|
86
|
+
# Autouse fixture — runs for every test in module
|
|
87
|
+
@pytest.fixture(autouse=True)
|
|
88
|
+
def reset_cache():
|
|
89
|
+
cache.clear()
|
|
90
|
+
yield
|
|
91
|
+
cache.clear()
|
|
92
|
+
|
|
93
|
+
# tmp_path for file operations (built-in)
|
|
94
|
+
def test_writes_output(tmp_path):
|
|
95
|
+
output_file = tmp_path / "result.json"
|
|
96
|
+
generate_report(output_file)
|
|
97
|
+
assert output_file.exists()
|
|
98
|
+
data = json.loads(output_file.read_text())
|
|
99
|
+
assert data["status"] == "complete"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## §3 — Parameterized Tests
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import pytest
|
|
106
|
+
|
|
107
|
+
# Basic parametrize
|
|
108
|
+
@pytest.mark.parametrize("input,expected", [
|
|
109
|
+
("hello", "HELLO"),
|
|
110
|
+
("world", "WORLD"),
|
|
111
|
+
("", ""),
|
|
112
|
+
("123", "123"),
|
|
113
|
+
])
|
|
114
|
+
def test_uppercase(input, expected):
|
|
115
|
+
assert input.upper() == expected
|
|
116
|
+
|
|
117
|
+
# Multiple parameters with IDs
|
|
118
|
+
@pytest.mark.parametrize("email,valid", [
|
|
119
|
+
("user@test.com", True),
|
|
120
|
+
("invalid", False),
|
|
121
|
+
("", False),
|
|
122
|
+
("user@.com", False),
|
|
123
|
+
], ids=["valid_email", "no_at_sign", "empty", "missing_domain"])
|
|
124
|
+
def test_validate_email(email, valid):
|
|
125
|
+
assert validate_email(email) == valid
|
|
126
|
+
|
|
127
|
+
# Combine parametrize (cartesian product)
|
|
128
|
+
@pytest.mark.parametrize("browser", ["chrome", "firefox", "edge"])
|
|
129
|
+
@pytest.mark.parametrize("resolution", ["1920x1080", "1366x768", "375x667"])
|
|
130
|
+
def test_responsive_layout(browser, resolution):
|
|
131
|
+
assert render_page(browser, resolution).is_valid()
|
|
132
|
+
|
|
133
|
+
# Indirect parametrize (pass to fixture)
|
|
134
|
+
@pytest.fixture
|
|
135
|
+
def user(request):
|
|
136
|
+
return create_user(role=request.param)
|
|
137
|
+
|
|
138
|
+
@pytest.mark.parametrize("user", ["admin", "editor", "viewer"], indirect=True)
|
|
139
|
+
def test_permissions(user):
|
|
140
|
+
assert user.can_view()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## §4 — Mocking with pytest-mock
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# pip install pytest-mock
|
|
147
|
+
|
|
148
|
+
def test_send_email(mocker):
|
|
149
|
+
mock_smtp = mocker.patch("myapp.email.smtplib.SMTP")
|
|
150
|
+
send_email("test@example.com", "Hello", "Body")
|
|
151
|
+
mock_smtp.return_value.sendmail.assert_called_once()
|
|
152
|
+
|
|
153
|
+
def test_api_call(mocker):
|
|
154
|
+
mock_get = mocker.patch("myapp.api.requests.get")
|
|
155
|
+
mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
|
|
156
|
+
mock_get.return_value.status_code = 200
|
|
157
|
+
user = get_user(1)
|
|
158
|
+
assert user["name"] == "Alice"
|
|
159
|
+
mock_get.assert_called_once_with("https://api.example.com/users/1")
|
|
160
|
+
|
|
161
|
+
def test_database_error(mocker):
|
|
162
|
+
mocker.patch("myapp.db.session.commit", side_effect=IntegrityError("duplicate"))
|
|
163
|
+
with pytest.raises(DuplicateError):
|
|
164
|
+
create_user("Alice", "alice@test.com")
|
|
165
|
+
|
|
166
|
+
# Spy — track calls without replacing
|
|
167
|
+
def test_logging(mocker):
|
|
168
|
+
spy = mocker.spy(logger, "info")
|
|
169
|
+
process_order(order)
|
|
170
|
+
spy.assert_called_with("Order processed: %s", order.id)
|
|
171
|
+
|
|
172
|
+
# Mock environment variables
|
|
173
|
+
def test_config(monkeypatch):
|
|
174
|
+
monkeypatch.setenv("API_KEY", "test-key-123")
|
|
175
|
+
monkeypatch.setenv("DEBUG", "true")
|
|
176
|
+
config = load_config()
|
|
177
|
+
assert config.api_key == "test-key-123"
|
|
178
|
+
assert config.debug is True
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## §5 — Async Testing
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# pip install pytest-asyncio
|
|
185
|
+
|
|
186
|
+
import pytest
|
|
187
|
+
|
|
188
|
+
@pytest.mark.asyncio
|
|
189
|
+
async def test_async_fetch():
|
|
190
|
+
result = await fetch_data("https://api.example.com/data")
|
|
191
|
+
assert result["status"] == "ok"
|
|
192
|
+
|
|
193
|
+
@pytest.mark.asyncio
|
|
194
|
+
async def test_async_exception():
|
|
195
|
+
with pytest.raises(ConnectionError):
|
|
196
|
+
await fetch_data("https://invalid.example.com")
|
|
197
|
+
|
|
198
|
+
# Async fixtures
|
|
199
|
+
@pytest.fixture
|
|
200
|
+
async def async_client():
|
|
201
|
+
async with AsyncClient(app=app, base_url="http://test") as client:
|
|
202
|
+
yield client
|
|
203
|
+
|
|
204
|
+
@pytest.mark.asyncio
|
|
205
|
+
async def test_api_endpoint(async_client):
|
|
206
|
+
response = await async_client.get("/api/users")
|
|
207
|
+
assert response.status_code == 200
|
|
208
|
+
assert len(response.json()) > 0
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## §6 — Testing Exceptions & Warnings
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
# Exception testing
|
|
215
|
+
def test_division_by_zero():
|
|
216
|
+
with pytest.raises(ZeroDivisionError):
|
|
217
|
+
1 / 0
|
|
218
|
+
|
|
219
|
+
def test_error_message():
|
|
220
|
+
with pytest.raises(ValueError, match=r".*invalid email.*"):
|
|
221
|
+
validate_email("not-an-email")
|
|
222
|
+
|
|
223
|
+
def test_raises_with_info():
|
|
224
|
+
with pytest.raises(PermissionError) as exc_info:
|
|
225
|
+
delete_file("/protected/file.txt")
|
|
226
|
+
assert "permission denied" in str(exc_info.value).lower()
|
|
227
|
+
assert exc_info.value.errno == 13
|
|
228
|
+
|
|
229
|
+
# Warning testing
|
|
230
|
+
def test_deprecation_warning():
|
|
231
|
+
with pytest.warns(DeprecationWarning, match="use new_func"):
|
|
232
|
+
old_func()
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## §7 — Markers & Custom Plugins
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
# Custom marker usage
|
|
239
|
+
@pytest.mark.slow
|
|
240
|
+
def test_full_data_processing():
|
|
241
|
+
result = process_large_dataset()
|
|
242
|
+
assert result.row_count > 1_000_000
|
|
243
|
+
|
|
244
|
+
@pytest.mark.integration
|
|
245
|
+
def test_database_connection():
|
|
246
|
+
assert db.is_connected()
|
|
247
|
+
|
|
248
|
+
# Run by marker: pytest -m "not slow"
|
|
249
|
+
# Run by marker: pytest -m "smoke and not integration"
|
|
250
|
+
|
|
251
|
+
# Custom plugin — conftest.py
|
|
252
|
+
def pytest_collection_modifyitems(config, items):
|
|
253
|
+
"""Auto-mark tests in integration/ directory"""
|
|
254
|
+
for item in items:
|
|
255
|
+
if "integration" in str(item.fspath):
|
|
256
|
+
item.add_marker(pytest.mark.integration)
|
|
257
|
+
|
|
258
|
+
# Custom report header
|
|
259
|
+
def pytest_report_header(config):
|
|
260
|
+
return f"Environment: {os.getenv('ENV', 'local')}"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## §8 — Class-Based Test Organization
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
class TestUserService:
|
|
267
|
+
@pytest.fixture(autouse=True)
|
|
268
|
+
def setup(self, db_session, user_factory):
|
|
269
|
+
self.db = db_session
|
|
270
|
+
self.create_user = user_factory
|
|
271
|
+
self.service = UserService(db_session)
|
|
272
|
+
|
|
273
|
+
def test_create_user(self):
|
|
274
|
+
user = self.service.create("Alice", "alice@test.com")
|
|
275
|
+
assert user.id is not None
|
|
276
|
+
assert user.name == "Alice"
|
|
277
|
+
|
|
278
|
+
def test_find_by_email(self):
|
|
279
|
+
self.create_user(name="Bob", email="bob@test.com")
|
|
280
|
+
user = self.service.find_by_email("bob@test.com")
|
|
281
|
+
assert user.name == "Bob"
|
|
282
|
+
|
|
283
|
+
def test_delete_nonexistent(self):
|
|
284
|
+
with pytest.raises(NotFoundError):
|
|
285
|
+
self.service.delete(999)
|
|
286
|
+
|
|
287
|
+
class TestPermissions:
|
|
288
|
+
"""Nested class for permission-related tests"""
|
|
289
|
+
def test_admin_can_delete(self, user_factory):
|
|
290
|
+
admin = user_factory(role="admin")
|
|
291
|
+
assert admin.can_delete()
|
|
292
|
+
|
|
293
|
+
def test_viewer_cannot_delete(self, user_factory):
|
|
294
|
+
viewer = user_factory(role="viewer")
|
|
295
|
+
assert not viewer.can_delete()
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## §9 — CI/CD Integration
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
# GitHub Actions
|
|
302
|
+
name: Python Tests
|
|
303
|
+
on: [push, pull_request]
|
|
304
|
+
jobs:
|
|
305
|
+
test:
|
|
306
|
+
runs-on: ubuntu-latest
|
|
307
|
+
strategy:
|
|
308
|
+
matrix:
|
|
309
|
+
python-version: ['3.10', '3.11', '3.12']
|
|
310
|
+
steps:
|
|
311
|
+
- uses: actions/checkout@v4
|
|
312
|
+
- uses: actions/setup-python@v5
|
|
313
|
+
with: { python-version: '${{ matrix.python-version }}' }
|
|
314
|
+
- name: Install deps
|
|
315
|
+
run: pip install -r requirements-test.txt
|
|
316
|
+
- name: Run tests
|
|
317
|
+
run: pytest --cov=src --cov-report=xml --junitxml=results.xml -v
|
|
318
|
+
- name: Upload coverage
|
|
319
|
+
uses: codecov/codecov-action@v4
|
|
320
|
+
with: { files: coverage.xml }
|
|
321
|
+
- name: Upload results
|
|
322
|
+
uses: actions/upload-artifact@v4
|
|
323
|
+
if: always()
|
|
324
|
+
with: { name: test-results-${{ matrix.python-version }}, path: results.xml }
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## §10 — Debugging Quick-Reference
|
|
328
|
+
|
|
329
|
+
| Problem | Cause | Fix |
|
|
330
|
+
|---------|-------|-----|
|
|
331
|
+
| Fixture not found | Wrong scope or missing conftest.py | Check conftest.py location, fixture name |
|
|
332
|
+
| `ScopeMismatch` | Function fixture depends on session fixture | Match scope: session → module → function |
|
|
333
|
+
| Tests interfere | Shared mutable state | Use function-scoped fixtures, `autouse` cleanup |
|
|
334
|
+
| Parametrize fails | Wrong number of params | Ensure tuple count matches parameter names |
|
|
335
|
+
| Slow collection | Too many test paths | Set `testpaths` in pytest.ini |
|
|
336
|
+
| Async test hangs | Missing `@pytest.mark.asyncio` | Add marker or set `asyncio_mode = "auto"` |
|
|
337
|
+
| Coverage wrong | Source path mismatch | Set `source` in `[tool.coverage.run]` |
|
|
338
|
+
| Import errors | Missing `__init__.py` or bad path | Add `__init__.py` or use `src` layout with `--import-mode=importlib` |
|
|
339
|
+
| Monkeypatch not reverting | Using at module scope | Only use in function-scoped fixtures |
|
|
340
|
+
| Marker warnings | Marker not registered | Add to `markers` in pytest.ini |
|
|
341
|
+
|
|
342
|
+
## §11 — Best Practices Checklist
|
|
343
|
+
|
|
344
|
+
- ✅ Use fixtures over setup/teardown methods
|
|
345
|
+
- ✅ Use `conftest.py` for shared fixtures (auto-discovered)
|
|
346
|
+
- ✅ Use `tmp_path` for file operations (built-in, auto-cleanup)
|
|
347
|
+
- ✅ Use `monkeypatch` for env vars and attribute patching
|
|
348
|
+
- ✅ Use `pytest-mock` (mocker fixture) over `unittest.mock`
|
|
349
|
+
- ✅ Use `@pytest.mark.parametrize` for data-driven tests
|
|
350
|
+
- ✅ Register all custom markers in `pytest.ini`
|
|
351
|
+
- ✅ Use `--strict-markers` to catch typos in marker names
|
|
352
|
+
- ✅ Use `pytest-cov` for coverage with `--cov-fail-under=80`
|
|
353
|
+
- ✅ Use `pytest-xdist` for parallel execution: `pytest -n auto`
|
|
354
|
+
- ✅ Use `--tb=short` for concise tracebacks in CI
|
|
355
|
+
- ✅ Structure: `tests/unit/`, `tests/integration/`, `conftest.py`
|
|
356
|
+
- ✅ Name files `test_*.py` and functions `test_*`
|
|
357
|
+
- ✅ Use factory fixtures for creating test objects
|
|
358
|
+
- ✅ Use `pytest.raises(match=...)` for precise error checking
|