Skip to content

Commit 8ebd0ca

Browse files
committed
github-actions: add validate-pr self-tests
Run focused black-box tests for exact and context-only cherry-picks, malformed Git output, wrong-location changes, object paths containing separators, and replay failures whenever validator files change. Pin third-party workflow actions to immutable commit SHAs.
1 parent f213596 commit 8ebd0ca

2 files changed

Lines changed: 109 additions & 1 deletion

File tree

.github/scripts/test_validate_pr.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ class ValidatePrPatchIdTest(unittest.TestCase):
2020
def setUp(self):
2121
self._tmp = tempfile.TemporaryDirectory()
2222
self.addCleanup(self._tmp.cleanup)
23-
self.repo = pathlib.Path(self._tmp.name)
23+
self.repo = pathlib.Path(self._tmp.name) / "repo"
24+
self.repo.mkdir()
2425
self.real_git = shutil.which("git")
2526
self.assertIsNotNone(self.real_git)
2627

@@ -40,6 +41,11 @@ def setUp(self):
4041
])
4142
self.base = self._commit("fixture: base", LOCAL_SOB)
4243

44+
def _relocate_repo(self, name):
45+
relocated = self.repo.parent / name
46+
self.repo.rename(relocated)
47+
self.repo = relocated
48+
4349
def _git(self, *args, input_text=None):
4450
result = subprocess.run(
4551
["git", *args],
@@ -65,12 +71,33 @@ def _write_fixture(self, lines):
6571
def _read_fixture(self):
6672
return (self.repo / "fixture.txt").read_text().splitlines()
6773

74+
def _use_identical_occurrence_fixture(self):
75+
block = [
76+
"same-before-4",
77+
"same-before-3",
78+
"same-before-2",
79+
"same-before-1",
80+
"anchor",
81+
"same-after-1",
82+
"same-after-2",
83+
"same-after-3",
84+
"same-after-4",
85+
]
86+
separator = ["separator-{}".format(i) for i in range(1, 8)]
87+
self._write_fixture(["prefix", *block, *separator, *block, "suffix"])
88+
self.base = self._commit("fixture: duplicate context", LOCAL_SOB)
89+
6890
def _commit(self, subject, body):
6991
self._git("add", "fixture.txt")
7092
self._git("commit", "-F", "-",
7193
input_text="{}\n\n{}\n".format(subject, body))
7294
return self._git("rev-parse", "HEAD")
7395

96+
def _patch_id(self, commit):
97+
patch = self._git("show", commit)
98+
output = self._git("patch-id", "--stable", input_text=patch)
99+
return output.split()[0]
100+
74101
def _build_case(self, change, context_parent=True):
75102
self._git("checkout", "-b", "upstream-topic", self.base)
76103
self._set_identity("Upstream Author", "upstream@example.com")
@@ -141,6 +168,15 @@ def _fake_git_env(self, mode):
141168
"a" * 40, "b" * 40, "c" * 40, "d" * 40))
142169
sys.exit(0)
143170
171+
if (mode == "rev-parse-invalid-utf8" and
172+
args[:3] == ["rev-parse", "--git-path", "objects"]):
173+
sys.stdout.buffer.write(b"\\xff\\n")
174+
sys.exit(0)
175+
176+
if mode == "merge-tree-failure" and args and args[0] == "merge-tree":
177+
sys.stderr.write("synthetic merge-tree failure\\n")
178+
sys.exit(2)
179+
144180
if mode == "merge-tree-extra-line" and args and args[0] == "merge-tree":
145181
result = subprocess.run([real_git, *args], capture_output=True)
146182
sys.stdout.buffer.write(result.stdout)
@@ -193,6 +229,15 @@ def test_accepts_context_only_replay(self):
193229
self.assertEqual(result.returncode, 0, self._output(result))
194230
self.assertEqual(self._patch_id_status(result, local), "context")
195231

232+
def test_accepts_context_only_replay_with_colon_in_object_path(self):
233+
self._relocate_repo("repo:colon")
234+
parent, local = self._build_case("context")
235+
236+
result = self._validate(parent, local)
237+
238+
self.assertEqual(result.returncode, 0, self._output(result))
239+
self.assertEqual(self._patch_id_status(result, local), "context")
240+
196241
def test_accepts_exact_cherry_pick(self):
197242
parent, local = self._build_case("exact", context_parent=False)
198243

@@ -225,6 +270,22 @@ def test_rejects_merge_tree_output_with_extra_line(self):
225270
self.assertEqual(result.returncode, 1, self._output(result))
226271
self.assertIn("patch-ID mismatch with upstream", result.stdout)
227272

273+
def test_reports_replay_environment_failure(self):
274+
parent, local = self._build_case("exact", context_parent=False)
275+
276+
result = self._validate(parent, local, "rev-parse-invalid-utf8")
277+
278+
self.assertEqual(result.returncode, 1, self._output(result))
279+
self.assertIn("unable to verify patch replay", result.stderr)
280+
281+
def test_reports_merge_tree_failure(self):
282+
parent, local = self._build_case("exact", context_parent=False)
283+
284+
result = self._validate(parent, local, "merge-tree-failure")
285+
286+
self.assertEqual(result.returncode, 1, self._output(result))
287+
self.assertIn("synthetic merge-tree failure", result.stderr)
288+
228289
def test_rejects_multiple_patch_id_output_lines(self):
229290
parent, local = self._build_case("exact", context_parent=False)
230291

@@ -249,6 +310,18 @@ def test_rejects_same_change_at_different_occurrence(self):
249310
self.assertEqual(result.returncode, 1, self._output(result))
250311
self.assertIn("patch-ID mismatch with upstream", result.stdout)
251312

313+
def test_rejects_equal_patch_id_at_different_occurrence(self):
314+
self._use_identical_occurrence_fixture()
315+
parent, local = self._build_case(
316+
"wrong-occurrence", context_parent=False)
317+
upstream = self._git("rev-parse", "refs/remotes/upstream/linux")
318+
319+
self.assertEqual(self._patch_id(local), self._patch_id(upstream))
320+
result = self._validate(parent, local)
321+
322+
self.assertEqual(result.returncode, 1, self._output(result))
323+
self.assertIn("patch-ID mismatch with upstream", result.stdout)
324+
252325

253326
if __name__ == "__main__":
254327
unittest.main()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Validate PR Tests
2+
3+
'on':
4+
pull_request:
5+
branches:
6+
- github-actions
7+
paths:
8+
- .github/scripts/validate-pr
9+
- .github/scripts/test_validate_pr.py
10+
- .github/scripts/requirements.txt
11+
- .github/workflows/validate-pr-tests.yml
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
test:
18+
name: Validate PR self-tests
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Check out PR merge commit
22+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
23+
with:
24+
persist-credentials: false
25+
26+
- name: Set up Python
27+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
28+
with:
29+
python-version: '3.12'
30+
31+
- name: Install dependencies
32+
run: python3 -m pip install -r .github/scripts/requirements.txt
33+
34+
- name: Run validate-pr tests
35+
run: python3 .github/scripts/test_validate_pr.py -v

0 commit comments

Comments
 (0)