[PATCH b4 v2 38/44] tests: cover the tracking TUI restoring the branch on the way out

Christian Brauner <[email protected]> Fri, 31 Jul 2026 23:59:19 +0200
Newsgroups org.kernel.linux.tools
Message-ID <20260731-work-b4-editor-branch-guard-v2-38-243fd19d322d@kernel.org>
Three ways the restore was missed: a tracking app that moves HEAD and
quits, the same from a detached HEAD, and a status sync whose tracking
read exits rather than raising.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 src/tests/test_tui_tracking.py | 107 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/src/tests/test_tui_tracking.py b/src/tests/test_tui_tracking.py
index 7b2ffb9..ef79aeb 100644
--- a/src/tests/test_tui_tracking.py
+++ b/src/tests/test_tui_tracking.py
@@ -27,6 +27,7 @@ from textual.widgets import Input, ListView, Static
 import b4
 import b4.review
 import b4.review.tracking as tracking
+import b4.review_tui._entry as _entry
 from b4 import (
     _abort_worktree_op,
     _worktree_has_unmerged,
@@ -5363,3 +5364,109 @@ class TestTestApplySubmoduleRecurse:
         screen = TakeConfirmScreen('linear', 'master', branch)
         ok, detail = screen._test_take()
         assert ok, f'take test-apply recursed: {detail}'
+
+
+class TestTrackingEntryBranchRestore:
+    """run_tracking_tui() puts the worktree back on the way out."""
+
+    def test_quitting_after_an_upgrade_restores_the_branch(self, gitdir: str) -> None:
+        """The tracking list has actions of its own that move HEAD.
+
+        Upgrading a series to a newer revision checks out an upgrade branch
+        and renames it onto the review branch.  Quitting from the list then
+        went straight out of the loop, past a restore that only ran after a
+        review pass, and left the worktree on the review branch.
+        """
+        ecode, _out = b4.git_run_command(gitdir, ['checkout', '-q', '-b', 'work'])
+        assert ecode == 0
+        review_branch = 'b4/review/entry-restore-1'
+
+        class _MovesHeadThenQuits:
+            PATCHWORK_SENTINEL = TrackingApp.PATCHWORK_SENTINEL
+
+            def __init__(
+                self, identifier: str, original_branch: Optional[str], **kw: Any
+            ) -> None:
+                pass
+
+            def run(self, mouse: bool = True) -> Optional[str]:
+                b4.git_run_command(gitdir, ['checkout', '-q', '-b', review_branch])
+                return None
+
+        with patch.object(_entry, 'TrackingApp', _MovesHeadThenQuits):
+            _entry.run_tracking_tui('test-entry-restore')
+
+        assert b4.git_get_current_branch(gitdir) == 'work'
+
+    def test_quitting_after_an_upgrade_restores_a_detached_head(
+        self, gitdir: str
+    ) -> None:
+        """A session that started detached has a commit to go back to, and
+        putting the user on a branch instead is not restoring anything."""
+        ecode, out = b4.git_run_command(gitdir, ['rev-parse', 'HEAD'])
+        assert ecode == 0
+        start_sha = out.strip()
+        ecode, _out = b4.git_run_command(gitdir, ['checkout', '-q', '--detach'])
+        assert ecode == 0
+        review_branch = 'b4/review/entry-restore-detached'
+
+        class _MovesHeadThenQuits:
+            PATCHWORK_SENTINEL = TrackingApp.PATCHWORK_SENTINEL
+
+            def __init__(
+                self, identifier: str, original_branch: Optional[str], **kw: Any
+            ) -> None:
+                pass
+
+            def run(self, mouse: bool = True) -> Optional[str]:
+                b4.git_run_command(gitdir, ['checkout', '-q', '-b', review_branch])
+                return None
+
+        with patch.object(_entry, 'TrackingApp', _MovesHeadThenQuits):
+            _entry.run_tracking_tui('test-entry-restore-detached')
+
+        assert b4.git_get_current_branch(gitdir) is None
+        ecode, out = b4.git_run_command(gitdir, ['rev-parse', 'HEAD'])
+        assert ecode == 0
+        assert out.strip() == start_sha
+
+    def test_a_failed_tracking_load_still_restores_the_branch(
+        self, gitdir: str
+    ) -> None:
+        """load_tracking() exits rather than raising on a branch carrying no
+        tracking commit, and SystemExit is not an Exception, so it sailed past
+        the status sync's guard and out of run_tracking_tui() -- taking the
+        branch restore with it."""
+        ecode, _out = b4.git_run_command(gitdir, ['checkout', '-q', '-b', 'work'])
+        assert ecode == 0
+        review_branch = 'b4/review/entry-syncfail-1'
+        runs = {'n': 0}
+
+        class _ReviewsThenQuits:
+            PATCHWORK_SENTINEL = TrackingApp.PATCHWORK_SENTINEL
+
+            def __init__(
+                self, identifier: str, original_branch: Optional[str], **kw: Any
+            ) -> None:
+                pass
+
+            def run(self, mouse: bool = True) -> Optional[str]:
+                runs['n'] += 1
+                if runs['n'] > 1:
+                    return None
+                b4.git_run_command(gitdir, ['checkout', '-q', '-b', review_branch])
+                return review_branch
+
+        with (
+            patch.object(_entry, 'TrackingApp', _ReviewsThenQuits),
+            patch.object(_entry, 'ReviewApp'),
+            patch.object(
+                b4.review,
+                '_prepare_review_session',
+                return_value={'series': {'revision': 1}},
+            ),
+            patch.object(b4.review, 'load_tracking', side_effect=SystemExit(1)),
+        ):
+            _entry.run_tracking_tui('test-entry-syncfail')
+
+        assert b4.git_get_current_branch(gitdir) == 'work'

-- 
2.53.0