mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Add tests for check_conventions.py's pull request checks: a filled-in copy of the real template passes, the untouched template reports every gap, unchecked items are named for any list marker, AI-assisted and Depends on accept only their documented answers, template comments do not count as answers, sections need content, and titles must follow Conventional Commits.
130 lines
6.4 KiB
Python
130 lines
6.4 KiB
Python
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
CHECKER = ROOT / "tools" / "check_conventions.py"
|
|
TEMPLATE = ROOT / ".github" / "pull_request_template.md"
|
|
PREFIX = "(pull request): error: [pr-template] Fill in the pull request template: "
|
|
|
|
|
|
def body(checklist, what="Fix x.", tested="Not tested."):
|
|
return f"### What\n{what}\n\n### Tested\n{tested}\n\n### Checklist\n{checklist}"
|
|
|
|
|
|
def filled_template():
|
|
answers = {"Depends on": "none", "Unblocks": "none", "GPUs tested": "n/a", "AI-assisted": "yes"}
|
|
text = TEMPLATE.read_text(encoding="utf-8").replace("- [ ]", "- [x]")
|
|
for label, answer in answers.items():
|
|
text, count = re.subn(rf"({re.escape(label)}:) <!--.*?-->", rf"\1 {answer}", text)
|
|
if count != 1:
|
|
raise AssertionError(f"the template has no single '{label}:' answer placeholder")
|
|
text = text.replace("### What\n", "### What\nFix x.\n", 1)
|
|
return re.sub(r"(### Tested\n<!--.*?-->)", r"\1\nNot tested.", text, count=1, flags=re.S)
|
|
|
|
|
|
class PullRequestTemplateTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.directory = tempfile.TemporaryDirectory()
|
|
cls.root = Path(cls.directory.name)
|
|
for args in (["init", "-q"], ["add", "."], ["commit", "-qm", "docs: add readme"]):
|
|
if args[0] == "add":
|
|
(cls.root / "README.md").write_text("# Example\n", encoding="utf-8")
|
|
subprocess.run(["git", "-c", "user.name=Conventions Test", "-c", "user.email=test@example.com",
|
|
"-c", "commit.gpgsign=false", *args], cwd=cls.root, capture_output=True, check=True)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.directory.cleanup()
|
|
|
|
def run_checker(self, text, title="fix: x"):
|
|
(self.root / "body.md").write_text(text, encoding="utf-8")
|
|
result = subprocess.run(
|
|
[sys.executable, "-I", str(CHECKER), "--base", "HEAD", "--head", "HEAD", "--title", title, "--body", "body.md"],
|
|
cwd=self.root, capture_output=True, text=True, encoding="utf-8",
|
|
env={key: value for key, value in os.environ.items()
|
|
if key not in ("GITHUB_EVENT_PATH", "GITHUB_STEP_SUMMARY", "GITHUB_ACTIONS")},
|
|
)
|
|
findings = [line[len(PREFIX):] for line in result.stdout.splitlines() if line.startswith(PREFIX)]
|
|
return result, findings
|
|
|
|
def assert_passes(self, text, title="fix: x"):
|
|
result, _ = self.run_checker(text, title)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
|
|
def assert_findings(self, text, expected):
|
|
result, findings = self.run_checker(text)
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertEqual(findings, expected, result.stdout)
|
|
|
|
def test_filled_template_passes(self):
|
|
self.assert_passes(filled_template())
|
|
|
|
def test_untouched_template_reports_every_gap(self):
|
|
template = TEMPLATE.read_text(encoding="utf-8")
|
|
items = re.findall(r"^- \[ \] (.+)$", re.sub(r"<!--.*?-->", "", template, flags=re.S), re.M)
|
|
self.assertTrue(items)
|
|
result, findings = self.run_checker(template)
|
|
self.assertEqual(result.returncode, 1, result.stdout)
|
|
self.assertIn("section 'What' is missing or empty", findings)
|
|
self.assertIn("section 'Tested' is missing or empty", findings)
|
|
for item in items:
|
|
self.assertIn(f"unchecked: {item.strip()}", findings)
|
|
self.assertIn("answer AI-assisted with yes or no", findings)
|
|
self.assertIn("fill Depends on with #N or none", findings)
|
|
|
|
def test_one_unchecked_item_is_named(self):
|
|
self.assert_findings(body("- [x] Depends on: none\n- [ ] One topic per PR\n- [x] AI-assisted: yes\n"),
|
|
["unchecked: One topic per PR"])
|
|
|
|
def test_unchecked_items_with_other_list_markers(self):
|
|
self.assert_findings(body("* [ ] First\n+ [ ] Second\n- [x] Depends on: none\n- [x] AI-assisted: no\n"),
|
|
["unchecked: First", "unchecked: Second"])
|
|
|
|
def test_unchecked_box_outside_the_checklist_is_ignored(self):
|
|
self.assert_passes(body("- [x] Depends on: none\n- [x] AI-assisted: yes\n", tested="- [ ] Windows\n"))
|
|
|
|
def test_ai_assisted_answers(self):
|
|
for answer in ("yes", "no", "Yes", "NO"):
|
|
with self.subTest(answer=answer):
|
|
self.assert_passes(body(f"- [x] Depends on: none\n- [x] AI-assisted: {answer}\n"))
|
|
for answer in ("", "maybe", "<!-- yes / no -->", "yesterday"):
|
|
with self.subTest(answer=answer):
|
|
self.assert_findings(body(f"- [x] Depends on: none\n- [x] AI-assisted: {answer}\n"),
|
|
["answer AI-assisted with yes or no"])
|
|
|
|
def test_depends_on_answers(self):
|
|
for answer in ("none", "#123", "None"):
|
|
with self.subTest(answer=answer):
|
|
self.assert_passes(body(f"- [x] Depends on: {answer}\n- [x] AI-assisted: yes\n"))
|
|
for answer in ("", "later", "123", "<!-- #PR, or none -->"):
|
|
with self.subTest(answer=answer):
|
|
self.assert_findings(body(f"- [x] Depends on: {answer}\n- [x] AI-assisted: yes\n"),
|
|
["fill Depends on with #N or none"])
|
|
|
|
def test_sections_must_have_content(self):
|
|
checklist = "- [x] Depends on: none\n- [x] AI-assisted: yes\n"
|
|
self.assert_findings(body(checklist, what="<!-- nothing -->"), ["section 'What' is missing or empty"])
|
|
self.assert_findings(body(checklist, tested=" \n"), ["section 'Tested' is missing or empty"])
|
|
self.assert_findings(body(checklist).replace("### What", "#### What"), ["section 'What' is missing or empty"])
|
|
|
|
def test_title_must_be_conventional(self):
|
|
text = body("- [x] Depends on: none\n- [x] AI-assisted: yes\n")
|
|
for title in ("fix: x", "test(cli): cover options", "feat(relinker)!: drop flag"):
|
|
with self.subTest(title=title):
|
|
self.assert_passes(text, title)
|
|
for title in ("Fix x", "fix:x", "update(cli): x", ""):
|
|
with self.subTest(title=title):
|
|
result, _ = self.run_checker(text, title)
|
|
self.assertEqual(result.returncode, 1, result.stdout)
|
|
self.assertIn("[pr-title] Pull request title is not Conventional Commits", result.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|