diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-wttrin-favorite-override.el | 13 | ||||
| -rw-r--r-- | tests/test-wttrin-make-default.el | 9 | ||||
| -rw-r--r-- | tests/test-wttrin-saved-locations.el | 9 | ||||
| -rw-r--r-- | tests/test-wttrin-state-file.el | 258 | ||||
| -rw-r--r-- | tests/testutil-wttrin.el | 19 |
5 files changed, 293 insertions, 15 deletions
diff --git a/tests/test-wttrin-favorite-override.el b/tests/test-wttrin-favorite-override.el index 0236901..95916c7 100644 --- a/tests/test-wttrin-favorite-override.el +++ b/tests/test-wttrin-favorite-override.el @@ -64,15 +64,18 @@ with `d' reverted to the init value on restart." (should (equal "New Orleans, LA" wttrin-favorite-location)) (should (equal "Paris, FR" (wttrin--favorite-location))))) -;;; savehist registration targets the runtime vars, not the config defcustoms +;;; savehist registration covers only the scrub-tolerant search history; +;;; the runtime vars persist in `wttrin-state-file' (see test-wttrin-state-file.el) -(ert-deftest test-wttrin-favorite-override-normal-savehist-registers-override () - "Normal: savehist persists the override, not the config defcustom." +(ert-deftest test-wttrin-favorite-override-normal-savehist-excludes-runtime-vars () + "Normal: savehist persists neither the runtime vars nor the config defcustoms. +The runtime favorite and directory live in `wttrin-state-file' — a savehist +entry would be scrubbed by any session that saves savehist without wttrin." (require 'savehist) (let ((savehist-additional-variables '(kill-ring))) (wttrin--savehist-register) - (should (memq 'wttrin--favorite-override savehist-additional-variables)) - (should (memq 'wttrin--saved-locations-runtime savehist-additional-variables)) + (should-not (memq 'wttrin--favorite-override savehist-additional-variables)) + (should-not (memq 'wttrin--saved-locations-runtime savehist-additional-variables)) (should-not (memq 'wttrin-favorite-location savehist-additional-variables)) (should-not (memq 'wttrin-saved-locations savehist-additional-variables)))) diff --git a/tests/test-wttrin-make-default.el b/tests/test-wttrin-make-default.el index e824194..c7954c8 100644 --- a/tests/test-wttrin-make-default.el +++ b/tests/test-wttrin-make-default.el @@ -60,13 +60,14 @@ unbound); persistence is left to `wttrin--savehist-register'." (wttrin--set-favorite-location "Berkeley, CA") (should (equal wttrin--location-history '("Oslo, NO"))))) -(ert-deftest test-wttrin-favorite-savehist-register-includes-favorite () - "Normal: `wttrin--savehist-register' registers the runtime override, not the -`wttrin-favorite-location' defcustom." +(ert-deftest test-wttrin-favorite-savehist-register-excludes-favorite () + "Normal: `wttrin--savehist-register' does not register the runtime override. +The favorite persists in `wttrin-state-file' instead — a savehist entry would +be scrubbed by any Emacs session that saves savehist without wttrin loaded." (require 'savehist) (let ((savehist-additional-variables '(kill-ring))) (wttrin--savehist-register) - (should (memq 'wttrin--favorite-override savehist-additional-variables)) + (should-not (memq 'wttrin--favorite-override savehist-additional-variables)) (should-not (memq 'wttrin-favorite-location savehist-additional-variables)))) ;;; -------------------------------------------------------------------------- diff --git a/tests/test-wttrin-saved-locations.el b/tests/test-wttrin-saved-locations.el index 1a08aaf..b41d7ee 100644 --- a/tests/test-wttrin-saved-locations.el +++ b/tests/test-wttrin-saved-locations.el @@ -159,13 +159,14 @@ Two names with the same query share a key; the name never leaks into the key." ;;; savehist -(ert-deftest test-wttrin-saved-locations-integration-savehist-registers () - "Integration: the runtime directory is registered for savehist persistence, -not the `wttrin-saved-locations' defcustom (which the user sets in init)." +(ert-deftest test-wttrin-saved-locations-integration-savehist-excludes-runtime () + "Integration: the runtime directory is not registered with savehist. +It persists in `wttrin-state-file' instead — a savehist entry would be +scrubbed by any Emacs session that saves savehist without wttrin loaded." (require 'savehist) (let ((savehist-additional-variables '(kill-ring))) (wttrin--savehist-register) - (should (memq 'wttrin--saved-locations-runtime savehist-additional-variables)) + (should-not (memq 'wttrin--saved-locations-runtime savehist-additional-variables)) (should-not (memq 'wttrin-saved-locations savehist-additional-variables)))) ;;; wttrin--coordinates-p diff --git a/tests/test-wttrin-state-file.el b/tests/test-wttrin-state-file.el new file mode 100644 index 0000000..5648a13 --- /dev/null +++ b/tests/test-wttrin-state-file.el @@ -0,0 +1,258 @@ +;;; test-wttrin-state-file.el --- Tests for state-file persistence -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;;; Commentary: + +;; Unit tests for the wttrin state file: `wttrin--state-save', +;; `wttrin--state-read', and `wttrin--state-load'. The state file persists +;; the runtime favorite (`wttrin--favorite-override') and runtime directory +;; (`wttrin--saved-locations-runtime') in a file only wttrin writes, so a +;; foreign Emacs process saving savehist without wttrin loaded can no longer +;; scrub them. Covers round-trips, authority over in-memory values, the +;; savehist-legacy adoption path, setter integration, and failure isolation. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'wttrin) + +(defmacro test-wttrin-state-file--with-sandbox (&rest body) + "Run BODY with `wttrin-state-file' bound to a fresh temp path. +The runtime vars start nil. The file is removed afterward." + (declare (indent 0)) + `(let ((wttrin-state-file (expand-file-name + (format "wttrin-test-state-%s.el" (random 1000000)) + temporary-file-directory)) + (wttrin--favorite-override nil) + (wttrin--saved-locations-runtime nil)) + (unwind-protect + (progn ,@body) + (when (file-exists-p wttrin-state-file) + (delete-file wttrin-state-file))))) + +;;; -------------------------------------------------------------------------- +;;; Normal Cases +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-state-file-normal-save-load-round-trip () + "Normal: save writes both vars; load restores them after they are cleared." + (test-wttrin-state-file--with-sandbox + (setq wttrin--favorite-override "Hyatt Place Warwick, RI") + (setq wttrin--saved-locations-runtime + '(("Hyatt Place Warwick, RI" . "41.7266678,-71.443097"))) + (wttrin--state-save) + (setq wttrin--favorite-override nil) + (setq wttrin--saved-locations-runtime nil) + (wttrin--state-load) + (should (equal wttrin--favorite-override "Hyatt Place Warwick, RI")) + (should (equal wttrin--saved-locations-runtime + '(("Hyatt Place Warwick, RI" . "41.7266678,-71.443097")))))) + +(ert-deftest test-wttrin-state-file-normal-file-wins-over-memory () + "Normal: an existing state file is authoritative over in-memory values. +This is the guard against a later savehist restore of stale legacy lines." + (test-wttrin-state-file--with-sandbox + (setq wttrin--favorite-override "Newer, ME") + (wttrin--state-save) + (setq wttrin--favorite-override "Stale Legacy, LA") + (setq wttrin--saved-locations-runtime '(("Stale" . "stale"))) + (wttrin--state-load) + (should (equal wttrin--favorite-override "Newer, ME")) + (should (null wttrin--saved-locations-runtime)))) + +(ert-deftest test-wttrin-state-file-normal-set-favorite-writes-file () + "Normal: `wttrin--set-favorite-location' persists to the state file." + (test-wttrin-state-file--with-sandbox + (let ((wttrin-favorite-location nil) + (wttrin--location-history nil)) + (wttrin--set-favorite-location "Paris, FR") + (should (file-exists-p wttrin-state-file)) + (should (equal (plist-get (wttrin--state-read) :favorite-override) + "Paris, FR"))))) + +(ert-deftest test-wttrin-state-file-normal-put-saved-location-writes-file () + "Normal: `wttrin--put-saved-location' persists to the state file." + (test-wttrin-state-file--with-sandbox + (wttrin--put-saved-location "Home" "New Orleans, LA") + (should (equal (plist-get (wttrin--state-read) :saved-locations) + '(("Home" . "New Orleans, LA")))))) + +(ert-deftest test-wttrin-state-file-normal-remove-saved-location-writes-file () + "Normal: `wttrin--remove-saved-location' persists the removal." + (test-wttrin-state-file--with-sandbox + (wttrin--put-saved-location "Home" "New Orleans, LA") + (wttrin--remove-saved-location "Home") + (should (null (plist-get (wttrin--state-read) :saved-locations))))) + +(ert-deftest test-wttrin-state-file-normal-rename-location-writes-file () + "Normal: `wttrin-rename-location' persists the rename and favorite update." + (test-wttrin-state-file--with-sandbox + (let ((wttrin-saved-locations nil) + (wttrin-favorite-location nil) + (wttrin--location-history nil)) + (wttrin--put-saved-location "Hotel" "41.72,-71.44") + (wttrin--set-favorite-location "Hotel") + (wttrin-rename-location "Hotel" "Hyatt") + (let ((data (wttrin--state-read))) + (should (equal (plist-get data :saved-locations) + '(("Hyatt" . "41.72,-71.44")))) + (should (equal (plist-get data :favorite-override) "Hyatt")))))) + +(ert-deftest test-wttrin-state-file-normal-adopts-savehist-legacy-values () + "Normal: with no state file, non-nil vars (savehist legacy) are adopted." + (test-wttrin-state-file--with-sandbox + (setq wttrin--favorite-override "Legacy, TX") + (wttrin--state-load) + (should (file-exists-p wttrin-state-file)) + (should (equal (plist-get (wttrin--state-read) :favorite-override) + "Legacy, TX")))) + +(ert-deftest test-wttrin-state-file-normal-savehist-mode-hook-wired () + "Normal: `wttrin--state-load' is on `savehist-mode-hook' so a savehist +restore after wttrin loads cannot clobber state-file values." + (should (memq #'wttrin--state-load savehist-mode-hook))) + +;;; -------------------------------------------------------------------------- +;;; Boundary Cases +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-state-file-boundary-tri-state-t-round-trips () + "Boundary: the favorite's auto-detect value t survives a round-trip." + (test-wttrin-state-file--with-sandbox + (setq wttrin--favorite-override t) + (wttrin--state-save) + (setq wttrin--favorite-override nil) + (wttrin--state-load) + (should (eq wttrin--favorite-override t)))) + +(ert-deftest test-wttrin-state-file-boundary-nils-round-trip () + "Boundary: an explicitly saved all-nil state loads as nils." + (test-wttrin-state-file--with-sandbox + (wttrin--state-save) + (setq wttrin--favorite-override "Ghost, AZ") + (setq wttrin--saved-locations-runtime '(("Ghost" . "ghost"))) + (wttrin--state-load) + (should (null wttrin--favorite-override)) + (should (null wttrin--saved-locations-runtime)))) + +(ert-deftest test-wttrin-state-file-boundary-long-directory-round-trips () + "Boundary: a long saved-locations alist survives intact. +Guards the `print-length' / `print-level' bindings in the writer." + (test-wttrin-state-file--with-sandbox + (let ((entries (cl-loop for i from 1 to 60 + collect (cons (format "Place %02d" i) + (format "%d.0,-%d.0" i i))))) + (setq wttrin--saved-locations-runtime entries) + (wttrin--state-save) + (setq wttrin--saved-locations-runtime nil) + (wttrin--state-load) + (should (equal wttrin--saved-locations-runtime entries)) + (should (= (length wttrin--saved-locations-runtime) 60))))) + +(ert-deftest test-wttrin-state-file-boundary-unicode-round-trips () + "Boundary: unicode names and queries survive a round-trip." + (test-wttrin-state-file--with-sandbox + (setq wttrin--favorite-override "Zürich 🌦️") + (setq wttrin--saved-locations-runtime '(("北京" . "Beijing, CN"))) + (wttrin--state-save) + (setq wttrin--favorite-override nil) + (setq wttrin--saved-locations-runtime nil) + (wttrin--state-load) + (should (equal wttrin--favorite-override "Zürich 🌦️")) + (should (equal wttrin--saved-locations-runtime '(("北京" . "Beijing, CN")))))) + +(ert-deftest test-wttrin-state-file-boundary-read-absent-file-returns-nil () + "Boundary: reading a nonexistent state file returns nil." + (test-wttrin-state-file--with-sandbox + (should-not (wttrin--state-read)))) + +(ert-deftest test-wttrin-state-file-boundary-absent-file-nil-vars-noop () + "Boundary: no file and nil vars is a silent no-op — no file created." + (test-wttrin-state-file--with-sandbox + (wttrin--state-load) + (should-not (file-exists-p wttrin-state-file)) + (should (null wttrin--favorite-override)) + (should (null wttrin--saved-locations-runtime)))) + +;;; -------------------------------------------------------------------------- +;;; Error Cases +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-state-file-error-corrupt-file-does-not-signal () + "Error: unreadable lisp in the state file is ignored, vars untouched." + (test-wttrin-state-file--with-sandbox + (with-temp-file wttrin-state-file (insert "(((( not lisp")) + (setq wttrin--favorite-override "Kept, OK") + (wttrin--state-load) + (should (equal wttrin--favorite-override "Kept, OK")))) + +(ert-deftest test-wttrin-state-file-error-empty-file-does-not-signal () + "Error: an empty state file is ignored, vars untouched." + (test-wttrin-state-file--with-sandbox + (with-temp-file wttrin-state-file) + (setq wttrin--favorite-override "Kept, OK") + (wttrin--state-load) + (should (equal wttrin--favorite-override "Kept, OK")))) + +(ert-deftest test-wttrin-state-file-error-wrong-shape-data-ignored () + "Error: readable lisp that is not a versioned plist is treated as corrupt." + (test-wttrin-state-file--with-sandbox + (with-temp-file wttrin-state-file (insert "[1 2 3]")) + (setq wttrin--favorite-override "Kept, OK") + (wttrin--state-load) + (should (equal wttrin--favorite-override "Kept, OK")))) + +(ert-deftest test-wttrin-state-file-error-failed-write-preserves-file () + "Error: a failed write leaves the previous state file intact. +The writer goes through a temp file + rename, so an error before the +rename cannot truncate or clobber the existing file." + (test-wttrin-state-file--with-sandbox + (setq wttrin--favorite-override "Good, OK") + (wttrin--state-save) + (setq wttrin--favorite-override "Never Written, NV") + (cl-letf (((symbol-function 'write-region) + (lambda (&rest _) (error "Disk full")))) + (wttrin--state-save)) + (should (equal (plist-get (wttrin--state-read) :favorite-override) + "Good, OK")))) + +(ert-deftest test-wttrin-state-file-error-corrupt-file-survives-adoption () + "Error: adoption never writes over an existing corrupt state file. +The file is left byte-for-byte in place for inspection; non-nil vars do +not trigger the missing-file adoption write because the file exists." + (test-wttrin-state-file--with-sandbox + (with-temp-file wttrin-state-file (insert "(((( not lisp")) + (setq wttrin--favorite-override "Legacy, TX") + (wttrin--state-load) + (should (equal (with-temp-buffer + (insert-file-contents wttrin-state-file) + (buffer-string)) + "(((( not lisp")))) + +(ert-deftest test-wttrin-state-file-error-unwritable-path-does-not-signal () + "Error: a save to an unwritable path messages instead of signaling." + (let ((wttrin-state-file "/nonexistent-root-dir/wttrin/state.el") + (wttrin--favorite-override "Anywhere, US") + (wttrin--saved-locations-runtime nil)) + (should-not + (condition-case nil (progn (wttrin--state-save) nil) (error t))))) + +;;; -------------------------------------------------------------------------- +;;; savehist registration (post state-file) +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin-state-file-normal-savehist-excludes-state-vars () + "Normal: `wttrin--savehist-register' no longer registers the state-file +vars; only the scrub-tolerant search history stays with savehist." + (require 'savehist) + (let ((savehist-additional-variables '(kill-ring))) + (wttrin--savehist-register) + (should (memq 'wttrin--location-history savehist-additional-variables)) + (should-not (memq 'wttrin--favorite-override savehist-additional-variables)) + (should-not (memq 'wttrin--saved-locations-runtime + savehist-additional-variables)))) + +(provide 'test-wttrin-state-file) +;;; test-wttrin-state-file.el ends here diff --git a/tests/testutil-wttrin.el b/tests/testutil-wttrin.el index e4e2e4e..1373813 100644 --- a/tests/testutil-wttrin.el +++ b/tests/testutil-wttrin.el @@ -136,17 +136,32 @@ Other mocks BODY needs can be set in a nested `cl-letf'." ;;; Test Setup and Teardown +;; Redirect state-file writes for the whole batch: any test that exercises a +;; location setter triggers `wttrin--state-save', which must never touch the +;; developer's real `wttrin-state-file'. Process-wide, set once at load. +(setq wttrin-state-file + (expand-file-name (format "wttrin-test-state-%d.el" (emacs-pid)) + temporary-file-directory)) + (defun testutil-wttrin-setup () "Common setup for wttrin tests. Call this at the beginning of each test." (testutil-wttrin-clear-cache) - (setq wttrin--force-refresh nil)) + (setq wttrin--force-refresh nil) + (setq wttrin--favorite-override nil) + (setq wttrin--saved-locations-runtime nil) + (when (file-exists-p wttrin-state-file) + (delete-file wttrin-state-file))) (defun testutil-wttrin-teardown () "Common teardown for wttrin tests. Call this at the end of each test." (testutil-wttrin-clear-cache) - (setq wttrin--force-refresh nil)) + (setq wttrin--force-refresh nil) + (setq wttrin--favorite-override nil) + (setq wttrin--saved-locations-runtime nil) + (when (file-exists-p wttrin-state-file) + (delete-file wttrin-state-file))) (provide 'testutil-wttrin) ;;; testutil-wttrin.el ends here |
