AnyPS5/tools/tests/test_debt_split.py

163 lines
8.3 KiB
Python

import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
TOOL = Path(__file__).resolve().parents[1] / "debt_split.py"
INDEX = "docs/dev/TechnicalDebt.md"
A_STUB = "- [libSceA](../../core/libs/prx/libSceA/Export.cpp) - `sceAOne` returns 0"
A_FUNCTIONAL = "- `sceATwo` in [libSceA](../../core/libs/prx/libSceA/src/Two.cpp) ignores its flags\n - flag 1 is unknown"
B_STUB = "- [libSceB](../../core/libs/prx/libSceB/Export.cpp) - `sceBOne` returns 0"
C_STUB = "- [libSceC](../../core/libs/prx/libSceC/Export.cpp) - `sceCOne` returns 0"
SHADER = "- [Recompiler](../../core/shader/recompiler/Lower.cpp) rounds once, pinned in [a test](../../core/libs/prx/libSceA/tests/Round.cpp)"
SHARED = "- A limitation of every library, see [architecture](ARCHITECTURE.md)"
def index(stubs, functional):
return ("# Project technical debt\n\n### Build\n\n- A toolchain limitation\n\n### Silent stubs\n\n"
"Everything throws, except:\n\n" + "\n".join(stubs) + "\n\n### Functional\n\n" + "\n".join(functional) + "\n")
ORIGINAL = index([A_STUB, B_STUB, C_STUB], [A_FUNCTIONAL, SHADER, SHARED])
class DebtSplitTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory()
self.root = Path(self.directory.name)
self.git("init", "-q", "-b", "main")
self.write(INDEX, ORIGINAL)
self.git("add", ".")
self.git("commit", "-qm", "docs: record technical debt")
def tearDown(self):
self.directory.cleanup()
def git(self, *args, check=True):
return subprocess.run(
["git", "-c", "user.name=Debt Test", "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args],
cwd=self.root, capture_output=True, text=True, check=check,
env={**os.environ, "GIT_EDITOR": "true"},
)
def write(self, path, text):
(self.root / path).parent.mkdir(parents=True, exist_ok=True)
(self.root / path).write_text(text, encoding="utf-8")
def read(self, path):
return (self.root / path).read_text(encoding="utf-8")
def tool(self, command):
return subprocess.run([sys.executable, str(TOOL), command], cwd=self.root, capture_output=True, text=True, timeout=20)
def split(self):
result = self.tool("split")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.git("add", "-A")
self.git("commit", "-qm", "docs: split technical debt")
def test_split_moves_entries_by_their_first_core_link(self):
self.split()
self.assertEqual(self.read("docs/dev/debt/libSceA.md"),
"# libSceA technical debt\n\n### Silent stubs\n\n" + A_STUB.replace("../../", "../../../") +
"\n\n### Functional\n\n" + A_FUNCTIONAL.replace("../../", "../../../") + "\n")
self.assertIn(SHADER.replace("../../", "../../../"), self.read("docs/dev/debt/shader.md"))
text = self.read(INDEX)
for entry in (A_STUB, B_STUB, C_STUB, SHADER):
self.assertNotIn(entry.split(" - ")[0], text)
for kept in ("- A toolchain limitation", "Everything throws, except:", SHARED, "### Silent stubs", "[debt](debt/)"):
self.assertIn(kept, text)
def test_split_again_appends_to_existing_files(self):
self.split()
moved = self.read("docs/dev/debt/libSceA.md")
extra = "- [libSceA](../../core/libs/prx/libSceA/Three.cpp) - `sceAThree` returns 0"
self.write(INDEX, self.read(INDEX).replace("Everything throws, except:\n", "Everything throws, except:\n\n" + extra))
result = self.tool("split")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(result.stdout.split(), ["docs/dev/debt/libSceA.md"])
stub = A_STUB.replace("../../", "../../../")
self.assertEqual(self.read("docs/dev/debt/libSceA.md"), moved.replace(stub, stub + "\n" + extra.replace("../../", "../../../")))
self.assertEqual(self.read(INDEX).count("[debt](debt/)"), 1)
def rebase(self, stubs, functional):
self.git("checkout", "-qb", "change")
self.write(INDEX, index(stubs, functional))
self.git("commit", "-qam", "feat: change debt")
self.git("checkout", "-q", "main")
self.split()
self.git("checkout", "-q", "change")
rebase = self.git("rebase", "main", check=False)
self.assertNotEqual(rebase.returncode, 0, rebase.stdout + rebase.stderr)
return self.tool("resolve")
def test_resolve_moves_added_edited_and_removed_entries(self):
added = "- [libSceA](../../core/libs/prx/libSceA/Export.cpp) - `sceAFour` returns 0"
edited = B_STUB + " and logs once"
result = self.rebase([A_STUB, added, edited], [A_FUNCTIONAL, SHADER, SHARED])
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(sorted(result.stdout.split()),
["docs/dev/debt/libSceA.md", "docs/dev/debt/libSceB.md", "docs/dev/debt/libSceC.md"])
self.assertEqual(self.git("diff", "--name-only", "--diff-filter=U").stdout, "")
self.git("rebase", "--continue")
a = self.read("docs/dev/debt/libSceA.md")
self.assertLess(a.index("`sceAOne`"), a.index("`sceAFour`"))
self.assertLess(a.index("`sceAFour`"), a.index("### Functional"))
self.assertIn(edited.replace("../../", "../../../"), self.read("docs/dev/debt/libSceB.md"))
self.assertFalse((self.root / "docs/dev/debt/libSceC.md").exists())
self.assertEqual(self.read(INDEX), self.git("show", "main:" + INDEX).stdout)
def test_resolve_moves_unchanged_entry_to_functional(self):
result = self.rebase([A_STUB, B_STUB], [C_STUB, A_FUNCTIONAL, SHADER, SHARED])
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.git("diff", "--name-only", "--diff-filter=U").stdout, "")
self.git("rebase", "--continue")
self.assertEqual(self.read("docs/dev/debt/libSceC.md"),
"# libSceC technical debt\n\n### Functional\n\n" + C_STUB.replace("../../", "../../../") + "\n")
self.assertEqual(result.stdout.split(), ["docs/dev/debt/libSceC.md"])
self.assertEqual(self.read(INDEX), self.git("show", "main:" + INDEX).stdout)
def test_resolve_moves_unchanged_entry_to_silent_stubs(self):
result = self.rebase([A_STUB, B_STUB, C_STUB, A_FUNCTIONAL], [SHADER, SHARED])
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.git("diff", "--name-only", "--diff-filter=U").stdout, "")
self.git("rebase", "--continue")
self.assertEqual(self.read("docs/dev/debt/libSceA.md"),
"# libSceA technical debt\n\n### Silent stubs\n\n" + A_STUB.replace("../../", "../../../") +
"\n" + A_FUNCTIONAL.replace("../../", "../../../") + "\n")
self.assertEqual(result.stdout.split(), ["docs/dev/debt/libSceA.md"])
self.assertEqual(self.read(INDEX), self.git("show", "main:" + INDEX).stdout)
def test_resolve_refuses_changes_to_lines_that_stay_in_the_index(self):
result = self.rebase([A_STUB, B_STUB, C_STUB], [A_FUNCTIONAL, SHADER, SHARED + " too"])
self.assertNotEqual(result.returncode, 0)
self.assertIn("resolve those by hand", result.stderr)
self.assertIn("<<<<<<<", self.read(INDEX))
def test_resolve_refuses_an_entry_main_changed_too(self):
self.git("checkout", "-qb", "change")
self.write(INDEX, index([A_STUB, B_STUB + " here", C_STUB], [A_FUNCTIONAL, SHADER, SHARED]))
self.git("commit", "-qam", "feat: change b")
self.git("checkout", "-q", "main")
self.split()
path = "docs/dev/debt/libSceB.md"
self.write(path, self.read(path).replace("returns 0", "returns 1"))
self.git("commit", "-qam", "docs: change b")
self.git("checkout", "-q", "change")
self.assertNotEqual(self.git("rebase", "main", check=False).returncode, 0)
result = self.tool("resolve")
self.assertNotEqual(result.returncode, 0)
self.assertIn("entry not found", result.stderr)
def test_resolve_needs_a_conflict(self):
result = self.tool("resolve")
self.assertNotEqual(result.returncode, 0)
self.assertIn("not in a three-way conflict", result.stderr)
if __name__ == "__main__":
unittest.main()