aboutsummaryrefslogtreecommitdiff
path: root/tests/test-ai-term--shutdown-countdown.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-24 06:43:45 -0400
committerCraig Jennings <c@cjennings.net>2026-06-24 06:43:45 -0400
commit4e1401d1499be3d24df78edd97310d200e719e10 (patch)
tree9fe55839f6ca7741eaf45ac9f286f5b1bef49089 /tests/test-ai-term--shutdown-countdown.el
parent69931ef2923363e071cb125661ff2f0ed91e890e (diff)
downloaddotemacs-4e1401d1499be3d24df78edd97310d200e719e10.tar.gz
dotemacs-4e1401d1499be3d24df78edd97310d200e719e10.zip
feat(ai-term): wrap-teardown + shutdown entry points for rulesets
Add the three headless functions the rulesets wrap-it-up workflow calls via emacsclient -e, since this module owns the aiv- session naming, the agent buffer, and the geometry restore. cj/ai-term-quit kills a project's tmux session and agent buffer and restores the layout, idempotent and safe when already gone. cj/ai-term-live-count returns the integer count of live aiv- sessions for the shutdown safety gate. cj/ai-term-shutdown-countdown re-checks that gate, then runs an abort-able run-at-time countdown in the echo area and, uncancelled, runs the shutdown command (a defcustom so tests stub it). Reuses the existing kill/close helpers. 13 ERT tests cover the live-count parsing, the quit kill-and-idempotency, and the gate-abort/cancel/tick logic; the tmux and shutdown side effects are manual. Claude-Session: https://claude.ai/code/session_01BqrdWUo9GcznYX2pZr76gZ
Diffstat (limited to 'tests/test-ai-term--shutdown-countdown.el')
-rw-r--r--tests/test-ai-term--shutdown-countdown.el73
1 files changed, 73 insertions, 0 deletions
diff --git a/tests/test-ai-term--shutdown-countdown.el b/tests/test-ai-term--shutdown-countdown.el
new file mode 100644
index 000000000..6500e9634
--- /dev/null
+++ b/tests/test-ai-term--shutdown-countdown.el
@@ -0,0 +1,73 @@
+;;; test-ai-term--shutdown-countdown.el --- Tests for the shutdown countdown -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; The "wrap it up and shutdown" countdown. The testable logic is the safety
+;; gate (abort when more than one aiv-* session is live) and the cancel/timer
+;; bookkeeping; the tick rendering and the actual shutdown side effect are
+;; manual (see the spec). shell-command is stubbed throughout so no test can
+;; power the machine off, and timers are cancelled rather than allowed to fire.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'ai-term)
+
+(defmacro test-ai-term-shutdown--with (live-count shell-var &rest body)
+ "Run BODY with `cj/ai-term-live-count' mocked to LIVE-COUNT and `shell-command'
+recording its argument into SHELL-VAR; the timer is cleared before and after."
+ (declare (indent 2))
+ `(progn
+ (cj/--ai-term-shutdown-clear-timer)
+ (unwind-protect
+ (cl-letf (((symbol-function 'cj/ai-term-live-count) (lambda () ,live-count))
+ ((symbol-function 'shell-command)
+ (lambda (cmd &rest _) (setq ,shell-var cmd) 0)))
+ ,@body)
+ (cj/--ai-term-shutdown-clear-timer))))
+
+(ert-deftest test-ai-term-shutdown-aborts-when-other-sessions-live ()
+ "Normal: more than one live session aborts -- no timer, no shutdown."
+ (let ((shell nil))
+ (test-ai-term-shutdown--with 2 shell
+ (should-not (cj/ai-term-shutdown-countdown 3))
+ (should-not cj/--ai-term-shutdown-timer)
+ (should-not shell))))
+
+(ert-deftest test-ai-term-shutdown-schedules-timer-when-sole-session ()
+ "Normal: the sole live session schedules the countdown timer (does not fire here)."
+ (let ((shell nil))
+ (test-ai-term-shutdown--with 1 shell
+ (cj/ai-term-shutdown-countdown 3)
+ (should (timerp cj/--ai-term-shutdown-timer))
+ ;; The timer has not ticked (no event loop in batch), so no shutdown yet.
+ (should-not shell))))
+
+(ert-deftest test-ai-term-shutdown-cancel-clears-the-timer ()
+ "Normal: cancel stops an in-progress countdown."
+ (let ((shell nil))
+ (test-ai-term-shutdown--with 1 shell
+ (cj/ai-term-shutdown-countdown 5)
+ (should (timerp cj/--ai-term-shutdown-timer))
+ (cj/ai-term-shutdown-cancel)
+ (should-not cj/--ai-term-shutdown-timer)
+ (should-not shell))))
+
+(ert-deftest test-ai-term-shutdown-tick-fires-shutdown-at-zero ()
+ "Boundary: invoking the timer function at zero remaining runs the shutdown
+command and clears the timer. Drives the tick directly rather than waiting."
+ (let ((shell nil))
+ (test-ai-term-shutdown--with 1 shell
+ (cj/ai-term-shutdown-countdown 1)
+ (let ((fn (timer--function cj/--ai-term-shutdown-timer)))
+ ;; remaining starts at 1: first call renders, second call hits zero.
+ (funcall fn)
+ (should-not shell)
+ (funcall fn)
+ (should (equal shell cj/ai-term-shutdown-command))
+ (should-not cj/--ai-term-shutdown-timer)))))
+
+(provide 'test-ai-term--shutdown-countdown)
+;;; test-ai-term--shutdown-countdown.el ends here