Files
totk-pc-recomp/tests/worktree_test.py
T
2026-10-23 23:08:22 +03:00

131 lines
5.4 KiB
Python

"""tools/worktree.py against a scratch repo whose "main checkout" holds dummy ignored data. No game files needed.
Proves that marker files in the linked directories and in the linked file survive:
1. tools/worktree.py remove,
2. git worktree remove --force with the junctions still in place,
3. git worktree prune after the worktree directory is deleted with its junctions in place.
Also checks that git status in a worktree shows no untracked or typechanged entry for the links.
Run with the repo's Python: tests/worktree_test.py
"""
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / 'tools'))
import worktree # noqa: E402
IGNORED = '\n'.join('/' + d + '/' for d in worktree.LINK_DIRS) + '\n/' + worktree.LINK_FILES[0] + '\n/.worktrees/\n'
def git(cwd, *a):
r = subprocess.run(['git', '-C', str(cwd), *a], capture_output=True, text=True)
assert r.returncode == 0, f'git {a}: {r.stderr}'
return r.stdout
def tool(scratch, *a):
r = subprocess.run([sys.executable, str(scratch / 'tools' / 'worktree.py'), *a], capture_output=True, text=True)
assert r.returncode == 0, f'worktree.py {a}: {r.stdout}{r.stderr}'
return r.stdout
def markers(main):
return [main / d / 'deep' / 'marker.txt' for d in worktree.LINK_DIRS if (main / d).is_dir()] + \
[main / f for f in worktree.LINK_FILES]
def check(main, what):
for m in markers(main):
assert m.is_file() and m.read_text() == 'keep', f'{what}: {m} lost'
print(f'ok: {what}: {len(markers(main))} markers intact')
def make_repo(base):
main = base / 'main'
(main / 'tools').mkdir(parents=True)
shutil.copy(ROOT / 'tools' / 'worktree.py', main / 'tools' / 'worktree.py')
(main / '.gitignore').write_text(IGNORED)
(main / 'README.md').write_text('x\n')
git(main, 'init', '-b', 'main')
git(main, 'config', 'user.email', 't@t')
git(main, 'config', 'user.name', 't')
git(main, 'add', '.')
git(main, 'commit', '-m', 'init')
for d in worktree.LINK_DIRS:
if d == 'game':
continue # absent in a real checkout too: link only what exists
(main / d / 'deep').mkdir(parents=True)
(main / d / 'deep' / 'marker.txt').write_text('keep')
for f in worktree.LINK_FILES:
(main / f).write_text('keep')
return main
def main():
base = Path(tempfile.mkdtemp(prefix='wt-test-')).resolve()
try:
main_dir = make_repo(base)
check(main_dir, 'setup')
# tools/worktree.py new / status / remove
wt = Path(tool(main_dir, 'new', 'a').strip())
assert wt.resolve() == (main_dir / '.worktrees' / 'a').resolve(), wt
for d in worktree.LINK_DIRS:
if (main_dir / d).is_dir():
assert os.path.isjunction(wt / d), d
assert (wt / 'build' / 'deep' / 'marker.txt').read_text() == 'keep'
assert os.path.samefile(wt / 'config.local.toml', main_dir / 'config.local.toml')
status = git(wt, 'status', '--porcelain', '--ignored=no')
assert status == '', f'links show in git status: {status!r}'
assert git(wt, 'ls-files', '--others', '--exclude-standard') == ''
print('ok: git status in the worktree is clean, links are ignored')
assert '.worktrees/a' in tool(main_dir, 'list').replace(os.sep, '/')
(wt / 'dirty.txt').write_text('x')
r = subprocess.run([sys.executable, str(main_dir / 'tools' / 'worktree.py'), 'remove', 'a'],
capture_output=True, text=True)
assert r.returncode != 0 and (wt / 'build').is_dir(), 'remove must refuse a dirty worktree'
check(main_dir, 'dirty remove refused')
(wt / 'dirty.txt').unlink()
tool(main_dir, 'remove', 'a')
assert not wt.exists()
check(main_dir, 'tools/worktree.py remove')
# unmerged branch: --delete-branch refuses, --force deletes
wt = Path(tool(main_dir, 'new', 'b').strip())
(wt / 'f.txt').write_text('x')
git(wt, 'add', 'f.txt')
git(wt, 'commit', '-m', 'work')
r = subprocess.run([sys.executable, str(main_dir / 'tools' / 'worktree.py'), 'remove', 'b', '--delete-branch'],
capture_output=True, text=True)
assert r.returncode != 0 and wt.is_dir(), 'unmerged branch must block --delete-branch'
git(main_dir, 'merge', '--ff-only', 'task/b')
tool(main_dir, 'remove', 'b', '--delete-branch')
assert 'task/b' not in git(main_dir, 'branch', '--list')
check(main_dir, 'remove --delete-branch after merge')
# raw git: worktree remove --force with the links in place
wt = Path(tool(main_dir, 'new', 'c').strip())
git(main_dir, 'worktree', 'remove', '--force', str(wt))
check(main_dir, 'raw git worktree remove --force')
# raw: delete the worktree directory with the junctions in place, then prune
wt = Path(tool(main_dir, 'new', 'e').strip())
shutil.rmtree(wt)
git(main_dir, 'worktree', 'prune')
check(main_dir, 'rmtree of a linked worktree, then git worktree prune')
print('PASS')
finally:
# never follow a junction while cleaning up
for w in (base / 'main' / '.worktrees').glob('*'):
for d in worktree.LINK_DIRS:
worktree.drop_link(w / d)
shutil.rmtree(base, ignore_errors=True)
if __name__ == '__main__':
main()