Skip to content

Commit b03593d

Browse files
committed
tests(test_worktree) Add targeted tests for remaining coverage gaps
why: Fill 5 specific coverage gaps identified by analysis to improve worktree module coverage from 81%→88% and CLI from 78%→83%. what: - Add test_sync_worktree_executes_update for UPDATE execution path - Add test_sync_all_worktrees_counts_mixed for action counting - Add test_cli_worktree_sync_no_repos for empty repos path - Add test_cli_worktree_prune_no_repos for empty repos path - Add test_cli_worktree_prune_no_orphans for no-orphans path
1 parent 9901cde commit b03593d

1 file changed

Lines changed: 262 additions & 0 deletions

File tree

tests/test_worktree.py

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
list_existing_worktrees,
1717
plan_worktree_sync,
1818
prune_worktrees,
19+
sync_all_worktrees,
1920
sync_worktree,
2021
validate_worktree_config,
2122
)
@@ -947,6 +948,147 @@ def test_sync_worktree_branch_update(
947948
assert entries[0].exists is True
948949

949950

951+
def test_sync_worktree_executes_update(
952+
git_repo: GitSync,
953+
tmp_path: pathlib.Path,
954+
) -> None:
955+
"""Test sync_worktree UPDATE action attempts git pull.
956+
957+
Coverage: Lines 547-559 (UPDATE execution path in sync_worktree).
958+
959+
Note: Since git_repo is a local-only repo without a remote, git pull fails.
960+
This tests that the UPDATE path IS exercised and handles the error correctly.
961+
The error path (lines 554-559) converts it to ERROR action.
962+
"""
963+
workspace_root = git_repo.path.parent
964+
worktree_path = workspace_root / "update-exec-wt"
965+
966+
# Create a branch
967+
subprocess.run(
968+
["git", "branch", "update-exec-branch"],
969+
cwd=git_repo.path,
970+
check=True,
971+
capture_output=True,
972+
)
973+
974+
# Create the worktree
975+
subprocess.run(
976+
["git", "worktree", "add", str(worktree_path), "update-exec-branch"],
977+
cwd=git_repo.path,
978+
check=True,
979+
capture_output=True,
980+
)
981+
982+
wt_config: WorktreeConfigDict = {
983+
"dir": str(worktree_path),
984+
"branch": "update-exec-branch",
985+
}
986+
987+
# Sync without dry_run - attempts UPDATE but fails because no tracking info
988+
entry = sync_worktree(git_repo.path, wt_config, workspace_root, dry_run=False)
989+
990+
# The UPDATE path was executed (lines 547-549), but git pull failed (lines 554-559)
991+
assert entry.action == WorktreeAction.ERROR
992+
assert entry.exists is True
993+
assert "no tracking information" in (entry.error or "").lower()
994+
995+
996+
def test_sync_all_worktrees_counts_mixed(
997+
git_repo: GitSync,
998+
tmp_path: pathlib.Path,
999+
) -> None:
1000+
"""Test sync_all_worktrees correctly counts each action type.
1001+
1002+
Coverage: Lines 713-722 (action counting in sync_all_worktrees).
1003+
1004+
Note: This test uses dry_run=True to count planning actions without
1005+
executing, since git pull fails on local-only repos without remotes.
1006+
"""
1007+
workspace_root = git_repo.path.parent
1008+
1009+
# Create a valid tag
1010+
subprocess.run(
1011+
["git", "tag", "v-count-test"],
1012+
cwd=git_repo.path,
1013+
check=True,
1014+
capture_output=True,
1015+
)
1016+
1017+
# Create a branch and its worktree (for UPDATE)
1018+
subprocess.run(
1019+
["git", "branch", "count-branch"],
1020+
cwd=git_repo.path,
1021+
check=True,
1022+
capture_output=True,
1023+
)
1024+
branch_wt_path = workspace_root / "count-branch-wt"
1025+
subprocess.run(
1026+
["git", "worktree", "add", str(branch_wt_path), "count-branch"],
1027+
cwd=git_repo.path,
1028+
check=True,
1029+
capture_output=True,
1030+
)
1031+
1032+
# Create a tag worktree (for UNCHANGED)
1033+
tag_wt_path = workspace_root / "count-tag-wt"
1034+
subprocess.run(
1035+
["git", "worktree", "add", str(tag_wt_path), "v-count-test", "--detach"],
1036+
cwd=git_repo.path,
1037+
check=True,
1038+
capture_output=True,
1039+
)
1040+
1041+
# Create a dirty worktree (for BLOCKED)
1042+
dirty_wt_path = workspace_root / "count-dirty-wt"
1043+
subprocess.run(
1044+
["git", "worktree", "add", str(dirty_wt_path), "HEAD", "--detach"],
1045+
cwd=git_repo.path,
1046+
check=True,
1047+
capture_output=True,
1048+
)
1049+
# Make it dirty by adding an untracked file
1050+
(dirty_wt_path / "dirty.txt").write_text("dirty content")
1051+
1052+
# Get commit SHA for the dirty worktree config
1053+
git_result = subprocess.run(
1054+
["git", "rev-parse", "HEAD"],
1055+
cwd=git_repo.path,
1056+
capture_output=True,
1057+
text=True,
1058+
check=True,
1059+
)
1060+
commit_sha = git_result.stdout.strip()
1061+
1062+
worktrees_config: list[WorktreeConfigDict] = [
1063+
# CREATE: new worktree for existing tag
1064+
{"dir": str(workspace_root / "count-new-wt"), "tag": "v-count-test"},
1065+
# UPDATE: existing branch worktree
1066+
{"dir": str(branch_wt_path), "branch": "count-branch"},
1067+
# UNCHANGED: existing tag worktree
1068+
{"dir": str(tag_wt_path), "tag": "v-count-test"},
1069+
# BLOCKED: dirty worktree
1070+
{"dir": str(dirty_wt_path), "commit": commit_sha},
1071+
# ERROR: invalid ref
1072+
{"dir": str(workspace_root / "count-error-wt"), "tag": "v-nonexistent-tag"},
1073+
]
1074+
1075+
# Use dry_run to test the counting without git pull side effects
1076+
sync_result = sync_all_worktrees(
1077+
git_repo.path,
1078+
worktrees_config,
1079+
workspace_root,
1080+
dry_run=True,
1081+
)
1082+
1083+
# Verify counts (all branches through lines 713-722)
1084+
assert sync_result.created == 1
1085+
assert sync_result.updated == 1
1086+
assert sync_result.unchanged == 1
1087+
assert sync_result.blocked == 1
1088+
assert sync_result.errors == 1
1089+
assert len(sync_result.entries) == 5
1090+
1091+
9501092
def test_worktree_exists_with_git_dir(tmp_path: pathlib.Path) -> None:
9511093
"""Test _worktree_exists returns False for regular git directory."""
9521094
from vcspull._internal.worktree_sync import _worktree_exists
@@ -1025,3 +1167,123 @@ def test_get_worktree_head_with_actual_worktree(
10251167
head = _get_worktree_head(worktree_path)
10261168
assert head is not None
10271169
assert len(head) == 40 # Full SHA
1170+
1171+
1172+
# ---------------------------------------------------------------------------
1173+
# CLI Coverage Gap Tests
1174+
# ---------------------------------------------------------------------------
1175+
1176+
1177+
def test_cli_worktree_sync_no_repos(
1178+
tmp_path: pathlib.Path,
1179+
monkeypatch: pytest.MonkeyPatch,
1180+
capsys: pytest.CaptureFixture[str],
1181+
) -> None:
1182+
"""Test CLI sync shows message when no repos have worktrees.
1183+
1184+
Coverage: Lines 270-273 in cli/worktree.py.
1185+
"""
1186+
from vcspull.cli import cli
1187+
1188+
# Create a config without worktrees key
1189+
config_path = tmp_path / ".vcspull.yaml"
1190+
config_path.write_text(
1191+
"""\
1192+
~/repos/:
1193+
myproject:
1194+
repo: git+https://github.com/user/project.git
1195+
""",
1196+
encoding="utf-8",
1197+
)
1198+
1199+
monkeypatch.chdir(tmp_path)
1200+
monkeypatch.setenv("HOME", str(tmp_path))
1201+
1202+
cli(["worktree", "sync", "-f", str(config_path)])
1203+
1204+
captured = capsys.readouterr()
1205+
assert "No repositories with worktrees configured" in captured.out
1206+
1207+
1208+
def test_cli_worktree_prune_no_repos(
1209+
tmp_path: pathlib.Path,
1210+
monkeypatch: pytest.MonkeyPatch,
1211+
capsys: pytest.CaptureFixture[str],
1212+
) -> None:
1213+
"""Test CLI prune shows message when no repos have worktrees.
1214+
1215+
Coverage: Lines 338-341 in cli/worktree.py.
1216+
"""
1217+
from vcspull.cli import cli
1218+
1219+
# Create a config without worktrees key
1220+
config_path = tmp_path / ".vcspull.yaml"
1221+
config_path.write_text(
1222+
"""\
1223+
~/repos/:
1224+
myproject:
1225+
repo: git+https://github.com/user/project.git
1226+
""",
1227+
encoding="utf-8",
1228+
)
1229+
1230+
monkeypatch.chdir(tmp_path)
1231+
monkeypatch.setenv("HOME", str(tmp_path))
1232+
1233+
cli(["worktree", "prune", "-f", str(config_path)])
1234+
1235+
captured = capsys.readouterr()
1236+
assert "No repositories with worktrees configured" in captured.out
1237+
1238+
1239+
def test_cli_worktree_prune_no_orphans(
1240+
git_repo: GitSync,
1241+
tmp_path: pathlib.Path,
1242+
monkeypatch: pytest.MonkeyPatch,
1243+
capsys: pytest.CaptureFixture[str],
1244+
) -> None:
1245+
"""Test CLI prune shows 'No orphaned worktrees' when none exist.
1246+
1247+
Coverage: Line 385 in cli/worktree.py.
1248+
"""
1249+
from vcspull.cli import cli
1250+
1251+
# Get commit SHA for config
1252+
result = subprocess.run(
1253+
["git", "rev-parse", "HEAD"],
1254+
cwd=git_repo.path,
1255+
capture_output=True,
1256+
text=True,
1257+
check=True,
1258+
)
1259+
commit_sha = result.stdout.strip()
1260+
1261+
# Create a worktree that IS configured (not orphaned)
1262+
configured_wt = git_repo.path.parent / "configured-only-wt"
1263+
subprocess.run(
1264+
["git", "worktree", "add", str(configured_wt), "HEAD", "--detach"],
1265+
cwd=git_repo.path,
1266+
check=True,
1267+
capture_output=True,
1268+
)
1269+
1270+
# Create config where the existing worktree IS listed
1271+
config_path = tmp_path / ".vcspull.yaml"
1272+
config_path.write_text(
1273+
f"""\
1274+
{git_repo.path.parent}/:
1275+
{git_repo.path.name}:
1276+
repo: git+file://{git_repo.path}
1277+
worktrees:
1278+
- dir: {configured_wt}
1279+
commit: {commit_sha}
1280+
""",
1281+
encoding="utf-8",
1282+
)
1283+
1284+
monkeypatch.chdir(tmp_path)
1285+
1286+
cli(["worktree", "prune", "-f", str(config_path)])
1287+
1288+
captured = capsys.readouterr()
1289+
assert "No orphaned worktrees to prune" in captured.out

0 commit comments

Comments
 (0)