diff options
| -rw-r--r-- | modules/mail-config.el | 19 | ||||
| -rw-r--r-- | modules/system-lib.el | 19 | ||||
| -rw-r--r-- | tests/test-system-lib-executable-find-or-warn.el | 80 |
3 files changed, 104 insertions, 14 deletions
diff --git a/modules/mail-config.el b/modules/mail-config.el index f19adf80..7a41e942 100644 --- a/modules/mail-config.el +++ b/modules/mail-config.el @@ -25,6 +25,7 @@ ;;; Code: (require 'user-constants) +(require 'system-lib) ;; cj/custom-keymap's real binding is in keybindings.el, which init.el loads ;; first. The use-package org-msg :preface below wraps in eval-and-compile, so @@ -63,27 +64,17 @@ transport details in debug buffers." (interactive) (cj/set-smtpmail-debug (not smtpmail-debug-info))) -(defun cj/mail--executable-or-warn (program feature) - "Return PROGRAM's executable path, or warn that FEATURE is unavailable." - (or (executable-find program) - (progn - (display-warning - 'mail-config - (format "%s not found; %s unavailable" program feature) - :warning) - nil))) - (defun cj/mail--mbsync-command () "Return the mu4e mail sync command, or nil if mbsync is unavailable." - (when-let ((mbsync (cj/mail--executable-or-warn - "mbsync" "mu4e mail synchronization"))) + (when-let ((mbsync (cj/executable-find-or-warn + "mbsync" "mu4e mail synchronization" 'mail-config))) (concat (shell-quote-argument mbsync) " -a"))) (defun cj/mail-configure-smtpmail () "Configure SMTP mail transport when msmtp is available." (setq smtpmail-debug-info cj/smtpmail-debug-enabled) - (if-let ((msmtp (cj/mail--executable-or-warn - "msmtp" "SMTP mail sending"))) + (if-let ((msmtp (cj/executable-find-or-warn + "msmtp" "SMTP mail sending" 'mail-config))) (setq sendmail-program msmtp send-mail-function 'message-send-mail-with-sendmail message-send-mail-function 'message-send-mail-with-sendmail diff --git a/modules/system-lib.el b/modules/system-lib.el index 4c2f17ef..f932353f 100644 --- a/modules/system-lib.el +++ b/modules/system-lib.el @@ -17,6 +17,25 @@ PROGRAM should be a string naming an executable program." (not (string-empty-p program)) (executable-find program))) +(defun cj/executable-find-or-warn (program feature &optional group) + "Return PROGRAM's executable path, or warn that FEATURE is unavailable. + +When PROGRAM is in PATH, return its absolute path. When it isn't, +emit a `display-warning' naming PROGRAM and FEATURE so the user gets +a clear hint about what won't work, and return nil. + +GROUP is the symbol passed to `display-warning' for filtering and +defaults to `cj/system-lib'. Callers should pass their own module +symbol (for example `mail-config') so per-feature warning filters +keep working." + (or (executable-find program) + (progn + (display-warning + (or group 'cj/system-lib) + (format "%s not found; %s unavailable" program feature) + :warning) + nil))) + (defun cj/log-silently (format-string &rest args) "Append formatted message (FORMAT-STRING with ARGS) to *Messages* buffer. This does so without echoing in the minibuffer." diff --git a/tests/test-system-lib-executable-find-or-warn.el b/tests/test-system-lib-executable-find-or-warn.el new file mode 100644 index 00000000..e9a540bd --- /dev/null +++ b/tests/test-system-lib-executable-find-or-warn.el @@ -0,0 +1,80 @@ +;;; test-system-lib-executable-find-or-warn.el --- Tests for cj/executable-find-or-warn -*- lexical-binding: t; -*- + +;;; Commentary: +;; `cj/executable-find-or-warn' generalizes the mail-specific +;; `cj/mail--executable-or-warn' pattern. Returns the program's +;; executable path when found. When the program is missing, returns +;; nil AND emits a `display-warning' naming the feature that's +;; unavailable, so the user gets a clear hint about what won't work. +;; The optional GROUP argument controls `display-warning' grouping so +;; per-feature filters keep working. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'system-lib) + +(defmacro test-system-lib-find-or-warn--with-stubs (executable-fn warning-capture &rest body) + "Run BODY with `executable-find' and `display-warning' stubbed. + +EXECUTABLE-FN replaces `executable-find' (takes a string program name, +returns path or nil). WARNING-CAPTURE is a symbol bound in BODY's scope +to a list of (TYPE MESSAGE LEVEL) entries collected from +`display-warning' calls." + (declare (indent 2)) + `(let ((,warning-capture nil)) + (cl-letf (((symbol-function 'executable-find) ,executable-fn) + ((symbol-function 'display-warning) + (lambda (type message &optional level) + (push (list type message level) ,warning-capture)))) + ,@body))) + +(ert-deftest test-cj-executable-find-or-warn-program-found-returns-path () + "Normal: when the program exists, return its path and emit no warning." + (test-system-lib-find-or-warn--with-stubs + (lambda (prog) (when (string= prog "mbsync") "/usr/bin/mbsync")) + warnings + (let ((result (cj/executable-find-or-warn "mbsync" "mu4e mail sync"))) + (should (equal result "/usr/bin/mbsync")) + (should-not warnings)))) + +(ert-deftest test-cj-executable-find-or-warn-program-missing-returns-nil () + "Normal: when the program is missing, return nil." + (test-system-lib-find-or-warn--with-stubs + (lambda (_prog) nil) + _warnings + (should-not (cj/executable-find-or-warn "missing-prog" "fake feature")))) + +(ert-deftest test-cj-executable-find-or-warn-missing-emits-warning () + "Normal: a missing program produces a warning that names PROGRAM and FEATURE." + (test-system-lib-find-or-warn--with-stubs + (lambda (_prog) nil) + warnings + (cj/executable-find-or-warn "missing-prog" "fake feature") + (should (= 1 (length warnings))) + (let ((entry (car warnings))) + (should (string-match-p "missing-prog" (nth 1 entry))) + (should (string-match-p "fake feature" (nth 1 entry))) + (should (eq :warning (nth 2 entry)))))) + +(ert-deftest test-cj-executable-find-or-warn-default-group () + "Boundary: with no GROUP arg, the warning uses the default `cj/system-lib' symbol." + (test-system-lib-find-or-warn--with-stubs + (lambda (_prog) nil) + warnings + (cj/executable-find-or-warn "missing-prog" "feature") + (should (eq 'cj/system-lib (car (car warnings)))))) + +(ert-deftest test-cj-executable-find-or-warn-explicit-group () + "Normal: an explicit GROUP arg flows through to `display-warning'." + (test-system-lib-find-or-warn--with-stubs + (lambda (_prog) nil) + warnings + (cj/executable-find-or-warn "missing-prog" "feature" 'mail-config) + (should (eq 'mail-config (car (car warnings)))))) + +(provide 'test-system-lib-executable-find-or-warn) +;;; test-system-lib-executable-find-or-warn.el ends here |
