aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-25 21:51:13 -0500
committerCraig Jennings <c@cjennings.net>2026-05-25 21:51:13 -0500
commit33c5da1bc33b59f2969f176f434ca66050deab23 (patch)
tree3e342d597eb69b2818127301c84a789f42a3e7b1 /tests
parentdc2ca47ea5ab053722bf59ae1fde92e3786f6f41 (diff)
downloadpearl-33c5da1bc33b59f2969f176f434ca66050deab23.tar.gz
pearl-33c5da1bc33b59f2969f176f434ca66050deab23.zip
fix(save): guard atomic-saver commit callbacks against a killed buffer
The per-field atomic savers advance the baseline and rewrite the display through the marker (org-entry-put marker / org-with-point-at marker) inside the async push callback. If the buffer is killed before the push lands, those touch a dead marker and signal. The deleted pearl--push-issue-field carried this guard, but it went away with the immediate-push setters and the atomic engine never picked it up. pearl--run-atomic-field-save now skips the commit when the marker's buffer is dead (a new pearl--marker-live-p, which also passes a nil marker for synthetic callers) and still reports the remote outcome -- the push already landed, so only the local buffer update is moot. All three commit sites are guarded: the clean push, the converged-remote case, and the use-remote conflict resolution.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-pearl-save.el31
1 files changed, 31 insertions, 0 deletions
diff --git a/tests/test-pearl-save.el b/tests/test-pearl-save.el
index 65f742a..8050e6d 100644
--- a/tests/test-pearl-save.el
+++ b/tests/test-pearl-save.el
@@ -375,6 +375,37 @@ the conflict choice (stubbed). Returns (:outcome :fetched :pushed :applied
(should (eq 'failed (plist-get r :status)))
(should (eq 'fetch-failed (plist-get r :reason)))))
+(ert-deftest test-pearl-atomic-killed-buffer-no-signal ()
+ "A push whose buffer is killed before the callback reports without touching it.
+The commit advances the baseline via the marker; on a dead buffer it must be
+skipped (the remote push already landed), not signal."
+ (let ((buf (generate-new-buffer "pearl-atomic-kill")) outcome committed)
+ (unwind-protect
+ (let (marker)
+ (with-current-buffer buf
+ (insert "* h\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\n")
+ (org-mode)
+ (goto-char (point-min))
+ (setq marker (point-marker)))
+ (pearl--run-atomic-field-save
+ (list :issue-id "u" :identifier "ENG-1" :field 'assignee :marker marker
+ :label "ENG-1 assignee" :live "b" :baseline "a" :equal #'string=
+ :live-display "b" :remote-display (lambda (_) "a")
+ ;; remote = baseline -> clean push; the fetch kills the buffer first
+ :fetch-remote (lambda (cb) (kill-buffer buf) (funcall cb "a"))
+ :push (lambda (cb) (funcall cb '(:success t)))
+ ;; touching the dead marker would signal without the guard
+ :commit-local (lambda ()
+ (setq committed t)
+ (org-entry-put marker "X" "y"))
+ :commit-remote (lambda (_) nil))
+ (lambda (o) (setq outcome o)))
+ (should-not (buffer-live-p buf))
+ ;; remote success is still reported; the local commit is skipped
+ (should (eq 'pushed (plist-get outcome :status)))
+ (should-not committed))
+ (when (buffer-live-p buf) (kill-buffer buf)))))
+
;;; --save-assignee-field (the relation saver, end to end)
(ert-deftest test-pearl-save-assignee-clean-push-advances-baseline ()