mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 11:35:55 +00:00
c41845e6d7
From the review: git patch-id --stable ignores whitespace, so a conflict resolution that only respaced the change (inside a string, or Python indentation) counted as no change and was carried; --verbatim compares every byte. And a passing run is now taken only from this pull request (by number, or for a fork, where GitHub lists none on the run, by head repository and branch), not from another pull request with the same head commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
148 lines
6.5 KiB
Python
148 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Self-test for main_merge.py. Run: python3 -I scripts/ci/test_main_merge.py (from the repo root).
|
|
|
|
Builds small throwaway git repositories; needs only git.
|
|
"""
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import main_merge # noqa: E402
|
|
|
|
|
|
class Repo:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.git("init", "-q", "-b", "main")
|
|
self.git("config", "user.email", "t@example.com")
|
|
self.git("config", "user.name", "t")
|
|
self.git("config", "commit.gpgsign", "false")
|
|
|
|
def git(self, *args):
|
|
return subprocess.run(["git", *args], cwd=self.root, capture_output=True, text=True, check=True).stdout.strip()
|
|
|
|
def write(self, path, text):
|
|
with open(os.path.join(self.root, path), "w") as f:
|
|
f.write(text)
|
|
|
|
def commit(self, msg, **files):
|
|
for path, text in files.items():
|
|
self.write(path, text)
|
|
self.git("add", "-A")
|
|
self.git("commit", "-q", "-m", msg)
|
|
return self.git("rev-parse", "HEAD")
|
|
|
|
|
|
LINES = "".join(f"line {i}\n" for i in range(40))
|
|
|
|
|
|
class SamePullRequest(unittest.TestCase):
|
|
def test_listed_pull_request_must_match(self):
|
|
self.assertTrue(main_merge.same_pull_request({"pull_requests": [{"number": 7}]}, 7, "x/y", "b"))
|
|
self.assertFalse(main_merge.same_pull_request({"pull_requests": [{"number": 8}]}, 7, "x/y", "b"))
|
|
|
|
def test_fork_run_matches_by_head_repository_and_branch(self):
|
|
run = {"pull_requests": [], "head_repository": {"full_name": "fork/solidcraft"}, "head_branch": "fix"}
|
|
self.assertTrue(main_merge.same_pull_request(run, 7, "fork/solidcraft", "fix"))
|
|
self.assertFalse(main_merge.same_pull_request(run, 7, "fork/solidcraft", "other"))
|
|
self.assertFalse(main_merge.same_pull_request(run, 7, "someone/solidcraft", "fix"))
|
|
|
|
|
|
class MergeOfMain(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.r = Repo(self.tmp.name)
|
|
self.r.commit("base", **{"a.txt": LINES, "b.txt": LINES})
|
|
self.r.git("checkout", "-q", "-b", "pr")
|
|
self.change = self.r.commit("pr change", **{"a.txt": LINES.replace("line 5\n", "line five\n")})
|
|
self.cwd = os.getcwd()
|
|
os.chdir(self.tmp.name)
|
|
|
|
def tearDown(self):
|
|
os.chdir(self.cwd)
|
|
self.tmp.cleanup()
|
|
|
|
def main_commit(self, **files):
|
|
self.r.git("checkout", "-q", "main")
|
|
sha = self.r.commit("main moves", **files)
|
|
self.r.git("checkout", "-q", "pr")
|
|
return sha
|
|
|
|
def merge_main(self):
|
|
self.r.git("merge", "-q", "--no-edit", "main")
|
|
return self.r.git("rev-parse", "HEAD")
|
|
|
|
def test_clean_merge_of_unrelated_main_change_is_carried(self):
|
|
base = self.main_commit(**{"b.txt": LINES.replace("line 30\n", "line thirty\n")})
|
|
head = self.merge_main()
|
|
self.assertEqual(main_merge.merge_of_main(base, head), self.change)
|
|
|
|
def test_consecutive_merges_of_main_chain_back_to_the_change(self):
|
|
self.main_commit(**{"b.txt": LINES.replace("line 30\n", "line thirty\n")})
|
|
first = self.merge_main()
|
|
base = self.main_commit(**{"b.txt": LINES.replace("line 30\n", "line thirty\n").replace("line 35\n", "line 35b\n")})
|
|
head = self.merge_main()
|
|
self.assertEqual(main_merge.merge_chain(base, head), [first, self.change])
|
|
|
|
def test_chain_stops_at_an_ordinary_commit(self):
|
|
self.main_commit(**{"b.txt": LINES.replace("line 30\n", "line thirty\n")})
|
|
self.merge_main()
|
|
later = self.r.commit("more work", **{"a.txt": LINES.replace("line 5\n", "line five\n").replace("line 20\n", "line twenty\n")})
|
|
base = self.main_commit(**{"b.txt": LINES.replace("line 30\n", "line thirty\n").replace("line 35\n", "line 35b\n")})
|
|
head = self.merge_main()
|
|
self.assertEqual(main_merge.merge_chain(base, head), [later])
|
|
|
|
def test_conflict_resolution_is_not_carried(self):
|
|
base = self.main_commit(**{"a.txt": LINES.replace("line 5\n", "line 5 on main\n")})
|
|
subprocess.run(["git", "merge", "-q", "main"], cwd=self.tmp.name, capture_output=True)
|
|
self.r.write("a.txt", LINES.replace("line 5\n", "line five on both\n"))
|
|
self.r.git("add", "-A")
|
|
self.r.git("commit", "-q", "--no-edit")
|
|
head = self.r.git("rev-parse", "HEAD")
|
|
self.assertIsNone(main_merge.merge_of_main(base, head))
|
|
|
|
def test_main_change_next_to_the_change_is_not_carried(self):
|
|
# Main edits a context line of the pull request's hunk: the change's patch differs.
|
|
base = self.main_commit(**{"a.txt": LINES.replace("line 7\n", "line seven\n")})
|
|
head = self.merge_main()
|
|
self.assertIsNone(main_merge.merge_of_main(base, head))
|
|
|
|
def test_whitespace_change_in_the_change_is_not_carried(self):
|
|
# A conflict resolution that only respaces the change's own line still changes the change.
|
|
base = self.main_commit(**{"a.txt": LINES.replace("line 5\n", "line 5 on main\n")})
|
|
subprocess.run(["git", "merge", "-q", "main"], cwd=self.tmp.name, capture_output=True)
|
|
self.r.write("a.txt", LINES.replace("line 5\n", "line five\n"))
|
|
self.r.git("add", "-A")
|
|
self.r.git("commit", "-q", "--no-edit")
|
|
head = self.r.git("rev-parse", "HEAD")
|
|
self.assertIsNone(main_merge.merge_of_main(base, head))
|
|
|
|
def test_ordinary_commit_is_not_carried(self):
|
|
head = self.r.commit("more", **{"a.txt": LINES.replace("line 5\n", "line 5b\n")})
|
|
base = self.r.git("rev-parse", "main")
|
|
self.assertIsNone(main_merge.merge_of_main(base, head))
|
|
|
|
def test_merge_of_another_branch_is_not_carried(self):
|
|
base = self.r.git("rev-parse", "main")
|
|
self.r.git("checkout", "-q", "-b", "other", "main")
|
|
self.r.commit("other work", **{"c.txt": "x\n"})
|
|
self.r.git("checkout", "-q", "pr")
|
|
self.r.git("merge", "-q", "--no-edit", "other")
|
|
head = self.r.git("rev-parse", "HEAD")
|
|
self.assertIsNone(main_merge.merge_of_main(base, head))
|
|
|
|
def test_change_with_empty_diff_is_not_carried(self):
|
|
self.r.git("checkout", "-q", "-b", "empty", "main")
|
|
base = self.main_commit(**{"b.txt": LINES.replace("line 30\n", "line thirty\n")})
|
|
self.r.git("checkout", "-q", "empty")
|
|
self.r.git("merge", "-q", "--no-ff", "--no-edit", "main")
|
|
head = self.r.git("rev-parse", "HEAD")
|
|
self.assertIsNone(main_merge.merge_of_main(base, head))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|