Skip to content

Commit 3a8c746

Browse files
committed
github: add PR template and body validation workflow
1 parent 0ed8d2f commit 3a8c746

4 files changed

Lines changed: 272 additions & 10 deletions

File tree

.github/pull_request_template.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
## Description
2+
3+
<!-- Describe your changes clearly -->
4+
5+
## Type of change
6+
7+
<!-- bug fix / feature / refactor / docs / cleanup -->
8+
9+
## Issue (optional)
10+
11+
<!-- e.g. Fixes #123 or Related to #456 -->
12+
13+
## Tests
14+
15+
<!-- Provide your GPU, driver, OS and test results in the format below -->
16+
17+
### GPU Model / Driver Version / OS
18+
19+
Total Tests:
20+
Passed:
21+
Crashed:
22+
Failed:
23+
Not Supported:
24+
Skipped:
25+
Success Rate:
26+
27+
## Additional Details (optional)
28+
29+
<!-- Any extra context: implementation notes, screenshots, logs, etc. -->
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: PR Validation
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited, synchronize]
6+
7+
jobs:
8+
check-pr:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout repository
12+
uses: actions/checkout@v4
13+
14+
- name: Write PR body to file
15+
env:
16+
PR_BODY: ${{ github.event.pull_request.body }}
17+
run: printf '%s' "$PR_BODY" > "$RUNNER_TEMP/pr_body.md"
18+
19+
- name: Validate PR title and body
20+
run: >
21+
python3 scripts/validate_pr.py
22+
--title "${{ github.event.pull_request.title }}"
23+
--body-file "$RUNNER_TEMP/pr_body.md"

CONTRIBUTING

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Contributing to Vulkan Video Samples
22

3+
Thank you for your interest in contributing! All contributions should be submitted through pull requests. If you're implementing a new feature, we encourage you to add unit tests using the [testing framework](tests/README.md).
4+
5+
---
6+
37
## Commit Message Guidelines
48

59
All commit messages must follow this format:
@@ -37,28 +41,83 @@ Commit messages are automatically validated on pull requests via commitlint. See
3741

3842
---
3943

40-
## Testing Hardware Information
44+
## Pull Request Guidelines
45+
46+
When opening a pull request, you'll be presented with a template. Please fill in all required sections.
4147

42-
Every PR must include a section describing the hardware configuration used to run the [testing framework](tests/README.md). This helps maintainers understand the test coverage and identify potential hardware-specific issues.
48+
### PR Title
4349

44-
Please include in your PR description:
50+
The PR title must follow the same format as commit messages:
4551

4652
```
47-
## Test Results
53+
scope: description
54+
```
55+
56+
- Same scope rules apply (lowercase for areas, PascalCase for components)
57+
- Maximum 100 characters
58+
- No draft indicators (WIP, fixup, etc.)
59+
- Must not end with a period
60+
61+
### Required Sections
62+
63+
#### Description
64+
65+
Describe your changes clearly. Explain the "why" behind the changes, not just the "what".
66+
67+
#### Type of Change
68+
69+
Specify one of: `bug fix` / `feature` / `refactor` / `docs` / `cleanup`
70+
71+
#### Tests
4872

73+
Every PR must include test results from the [testing framework](tests/README.md). This helps maintainers understand the test coverage and identify potential hardware-specific issues.
74+
75+
**Required format:**
76+
77+
```
4978
### [GPU Model] / [Driver Version] / [OS]
5079

51-
Total Tests: 70
52-
Passed: 48
53-
Crashed: 0
54-
Failed: 0
55-
Not Supported: 5
56-
Skipped: 17 (in skip list)
80+
Total Tests: 70
81+
Passed: 48
82+
Failed: 0
5783
Success Rate: 100.0%
5884
```
5985

86+
**Required fields** (validated by CI):
87+
- `Total Tests`
88+
- `Passed`
89+
- `Failed`
90+
- `Success Rate`
91+
- GPU / Driver / OS header line
92+
93+
**Optional fields** (not validated, but recommended):
94+
- `Crashed`
95+
- `Not Supported`
96+
- `Skipped`
97+
6098
This information is essential for tracking codec support across different hardware vendors and driver versions.
6199

100+
### Optional Sections
101+
102+
#### Issue
103+
104+
Link related issues using GitHub keywords:
105+
- `Fixes #123` - closes the issue when PR is merged
106+
- `Related to #456` - references without closing
107+
108+
#### Additional Details
109+
110+
Use this section for any extra context that doesn't fit elsewhere:
111+
- Implementation notes or trade-offs
112+
- Performance considerations
113+
- Migration or upgrade notes
114+
- Screenshots or logs
115+
- Links to related discussions or documentation
116+
117+
### Validation
118+
119+
PR titles and bodies are automatically validated via GitHub Actions. The check will fail if required sections are missing or incorrectly formatted.
120+
62121

63122
## Developer Certificate of Origin
64123

scripts/validate_pr.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Validate pull request title and body.
4+
5+
Usage:
6+
python3 validate_pr.py --title "scope: description" --body-file body.md
7+
python3 validate_pr.py --title "scope: description" --body "## Description ..."
8+
9+
Exits with code 1 and prints a human-readable error summary when
10+
validation fails.
11+
"""
12+
13+
import argparse
14+
import re
15+
import sys
16+
17+
TITLE_PATTERN = re.compile(
18+
r"^([a-z]+|[A-Z][a-zA-Z0-9]*(\{[^}]+\})?): .+"
19+
)
20+
FORBIDDEN_INDICATORS = re.compile(
21+
r"\b(wip|fixup|squash|tmp|todo|hack|xxx|do not merge|dnm|draft)\b",
22+
re.IGNORECASE,
23+
)
24+
HTML_COMMENT = re.compile(r"<!--[\s\S]*?-->")
25+
SECTION_RE = re.compile(r"## {}\s*\n([\s\S]*?)(?=\n## |$)")
26+
GPU_HEADER = re.compile(r"###\s+.+/.+/.+")
27+
28+
REQUIRED_TEST_FIELDS = [
29+
"Total Tests",
30+
"Passed",
31+
"Failed",
32+
"Success Rate",
33+
]
34+
35+
36+
def extract_section(body, name):
37+
"""Return the content of a markdown ## section, stripped of HTML comments."""
38+
match = re.search(SECTION_RE.pattern.format(re.escape(name)), body)
39+
if not match:
40+
return ""
41+
return HTML_COMMENT.sub("", match.group(1)).strip()
42+
43+
44+
def validate_title(title):
45+
"""Validate the PR title and return a list of error strings."""
46+
errors = []
47+
48+
if not TITLE_PATTERN.match(title):
49+
errors.append(
50+
"**Title** — must match pattern `scope: description`\n"
51+
" - scope: lowercase (cmake, ci, docs) OR "
52+
"PascalCase (VkVideoDecoder, FindShaderc)\n"
53+
" - Example: `cmake: fix build issue` or "
54+
"`VkVideoDecoder: add H265 support`\n"
55+
f" - Got: `{title}`"
56+
)
57+
58+
match = FORBIDDEN_INDICATORS.search(title)
59+
if match:
60+
errors.append(
61+
f"**Title** — must not contain draft indicators like `{match.group(0)}`\n"
62+
" - Forbidden: WIP, fixup, squash, tmp, todo, hack, "
63+
"xxx, do not merge, dnm, draft"
64+
)
65+
66+
if len(title) > 100:
67+
errors.append(
68+
f"**Title** — must not exceed 100 characters (currently {len(title)})."
69+
)
70+
71+
if title.endswith("."):
72+
errors.append("**Title** — must not end with a period.")
73+
74+
return errors
75+
76+
77+
def validate_body(body):
78+
"""Validate the PR body sections and return a list of error strings."""
79+
errors = []
80+
81+
# --- Description (required) ---
82+
if not extract_section(body, "Description"):
83+
errors.append("**Description** — please describe your changes.")
84+
85+
# --- Type of change (required) ---
86+
if not extract_section(body, "Type of change"):
87+
errors.append(
88+
"**Type of change** — please specify: "
89+
"bug fix / feature / refactor / docs / cleanup."
90+
)
91+
92+
# --- Tests (required) ---
93+
tests_content = extract_section(body, "Tests")
94+
if not tests_content:
95+
errors.append("**Tests** — please provide your test results.")
96+
else:
97+
missing = [
98+
field
99+
for field in REQUIRED_TEST_FIELDS
100+
if not re.search(rf"{field}\s*:\s*\S+", tests_content, re.IGNORECASE)
101+
]
102+
if missing:
103+
errors.append(
104+
f"**Tests** — missing or empty fields: {', '.join(missing)}.\n"
105+
" Expected format:\n"
106+
" ```\n"
107+
" Total Tests: 70\n"
108+
" Passed: 48\n"
109+
" Failed: 0\n"
110+
" Success Rate: 100.0%\n"
111+
" ```"
112+
)
113+
114+
if not GPU_HEADER.search(tests_content):
115+
errors.append(
116+
"**Tests** — please include a header with GPU / Driver / OS.\n"
117+
" Example: `### NVIDIA GeForce RTX 3050 Ti Laptop GPU "
118+
"/ NVIDIA 570.123.19 / Ubuntu 24.04.3 LTS`"
119+
)
120+
121+
return errors
122+
123+
124+
def main():
125+
parser = argparse.ArgumentParser(description="Validate PR title and body")
126+
parser.add_argument("--title", required=True, help="Pull request title")
127+
body_group = parser.add_mutually_exclusive_group(required=True)
128+
body_group.add_argument("--body", help="Pull request body as a string")
129+
body_group.add_argument("--body-file", help="Path to a file containing the PR body")
130+
args = parser.parse_args()
131+
132+
title = args.title
133+
if args.body_file:
134+
with open(args.body_file, encoding="utf-8") as f:
135+
body = f.read()
136+
else:
137+
body = args.body
138+
139+
errors = validate_title(title) + validate_body(body)
140+
141+
if errors:
142+
print("❌ PR validation failed. Please fix the following:\n")
143+
for error in errors:
144+
print(f"- {error}")
145+
sys.exit(1)
146+
147+
print("✅ PR title and body validation passed.")
148+
149+
150+
if __name__ == "__main__":
151+
main()

0 commit comments

Comments
 (0)