summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-03 23:43:42 -0500
committerCraig Jennings <c@cjennings.net>2026-05-03 23:43:42 -0500
commitf674e607cc4e3520b0da3281d36d344a6b24b0a2 (patch)
tree2c4a53a9e4cf06c781c87fda0a5bbc7040d04e5e /tests
parent9c7654e0e0f4777176ad5a9ea30075431e931c02 (diff)
downloaddotemacs-f674e607cc4e3520b0da3281d36d344a6b24b0a2.tar.gz
dotemacs-f674e607cc4e3520b0da3281d36d344a6b24b0a2.zip
fix: scope test-runner state by project
`test-runner.el` stored `cj/test-focused-files` and `cj/test-mode` in single global variables. ERT tests loaded by `cj/test-load-all` accumulated in the same global registry across projects. Switching projects inherited the previous project's focused files and mode. `cj/test-run-all` then ran every loaded ERT test from every project visited this session. I introduced a per-project state hash, `cj/test-project-states`, keyed by Projectile project root (or `default-directory` when not in a project). New helpers `cj/test--state-get` and `cj/test--state-put` route each read and write through that hash, so the focused-files list and the all/focused mode now live per project. The legacy public variables `cj/test-focused-files` and `cj/test-mode` are kept. They mirror the active project's state via `cj/test--sync-legacy-state` so existing modeline indicators and external code keep working. I also tracked which project roots had loaded tests (`cj/test-loaded-project-roots`) and added two ERT-isolation helpers. `cj/test--current-project-test-names` filters ERT's full registry to tests whose source file lives under the current project root. `cj/ert-clear-tests` deletes ERT tests loaded from other known project roots, so a fresh project starts with only its own tests. `cj/test-run-all` now uses the filtered name list, and a `projectile-after-switch-project-hook` clears foreign tests automatically when you switch projects. I added four regression tests to `tests/test-test-runner.el`: focus state isolated per project, mode isolated per project, `cj/ert-clear-tests` keeps the current project's tests and removes others, and `cj/test--current-project-test-names` returns only the current project's tests. Each test creates throwaway projects under the test temp dir and stubs `projectile-project-root` to switch contexts. 33 test-runner tests pass together.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-test-runner.el123
1 files changed, 123 insertions, 0 deletions
diff --git a/tests/test-test-runner.el b/tests/test-test-runner.el
index 0edc0d65..0ff66f7f 100644
--- a/tests/test-test-runner.el
+++ b/tests/test-test-runner.el
@@ -56,6 +56,18 @@
(insert content))
filepath))
+(defun test-testrunner-create-project (name files)
+ "Create temp project NAME with test FILES.
+FILES is an alist of relative test filenames to file contents."
+ (let* ((root (expand-file-name name test-testrunner--temp-dir))
+ (tests-dir (expand-file-name "tests" root)))
+ (make-directory tests-dir t)
+ (dolist (file files)
+ (let ((path (expand-file-name (car file) tests-dir)))
+ (with-temp-file path
+ (insert (cdr file)))))
+ root))
+
;;; Normal Cases - Load Files
(ert-deftest test-testrunner-load-files-success ()
@@ -355,5 +367,116 @@
(should (member "test-real" names)))
(test-testrunner-teardown))
+;;; Project-Scoped State
+
+(ert-deftest test-testrunner-focus-state-is-project-scoped ()
+ "Focused test files should not bleed between projects."
+ (test-testrunner-setup)
+ (let ((project-a (test-testrunner-create-project
+ "project-a"
+ '(("test-a.el" . "(ert-deftest test-project-a () t)"))))
+ (project-b (test-testrunner-create-project
+ "project-b"
+ '(("test-b.el" . "(ert-deftest test-project-b () t)"))))
+ (cj/test-project-states (make-hash-table :test #'equal)))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a))
+ ((symbol-function 'completing-read)
+ (lambda (&rest _args) "test-a.el")))
+ (cj/test-focus-add)
+ (should (equal (cj/test--current-focused-files) '("test-a.el"))))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-b)))
+ (should (null (cj/test--current-focused-files))))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-b))
+ ((symbol-function 'completing-read)
+ (lambda (&rest _args) "test-b.el")))
+ (cj/test-focus-add)
+ (should (equal (cj/test--current-focused-files) '("test-b.el"))))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a)))
+ (should (equal (cj/test--current-focused-files) '("test-a.el")))))
+ (test-testrunner-teardown))
+
+(ert-deftest test-testrunner-mode-is-project-scoped ()
+ "Focused/all mode should be tracked independently per project."
+ (test-testrunner-setup)
+ (let ((project-a (test-testrunner-create-project "mode-a" nil))
+ (project-b (test-testrunner-create-project "mode-b" nil))
+ (cj/test-project-states (make-hash-table :test #'equal)))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a)))
+ (should (eq (cj/test--current-mode) 'all))
+ (cj/test-toggle-mode)
+ (should (eq (cj/test--current-mode) 'focused)))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-b)))
+ (should (eq (cj/test--current-mode) 'all)))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a)))
+ (should (eq (cj/test--current-mode) 'focused))))
+ (test-testrunner-teardown))
+
+(ert-deftest test-testrunner-ert-clear-tests-keeps-current-project-tests ()
+ "Clearing ERT tests for a project switch should remove other project tests."
+ (test-testrunner-setup)
+ (let* ((project-a (test-testrunner-create-project
+ "ert-a"
+ '(("test-a.el" . "(ert-deftest test-testrunner-project-a-sentinel () t)"))))
+ (project-b (test-testrunner-create-project
+ "ert-b"
+ '(("test-b.el" . "(ert-deftest test-testrunner-project-b-sentinel () t)"))))
+ (file-a (expand-file-name "tests/test-a.el" project-a))
+ (file-b (expand-file-name "tests/test-b.el" project-b)))
+ (unwind-protect
+ (progn
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a)))
+ (cj/test-load-all))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-b)))
+ (cj/test-load-all))
+ (should (ert-test-boundp 'test-testrunner-project-a-sentinel))
+ (should (ert-test-boundp 'test-testrunner-project-b-sentinel))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a)))
+ (should (= (cj/ert-clear-tests) 1)))
+ (should (ert-test-boundp 'test-testrunner-project-a-sentinel))
+ (should-not (ert-test-boundp 'test-testrunner-project-b-sentinel)))
+ (when (ert-test-boundp 'test-testrunner-project-a-sentinel)
+ (ert-delete-test 'test-testrunner-project-a-sentinel))
+ (when (ert-test-boundp 'test-testrunner-project-b-sentinel)
+ (ert-delete-test 'test-testrunner-project-b-sentinel))))
+ (test-testrunner-teardown))
+
+(ert-deftest test-testrunner-current-project-test-names-ignore-other-projects ()
+ "Current project ERT selection should ignore loaded tests from other projects."
+ (test-testrunner-setup)
+ (let* ((project-a (test-testrunner-create-project
+ "names-a"
+ '(("test-a.el" . "(ert-deftest test-testrunner-project-names-a () t)"))))
+ (project-b (test-testrunner-create-project
+ "names-b"
+ '(("test-b.el" . "(ert-deftest test-testrunner-project-names-b () t)")))))
+ (unwind-protect
+ (progn
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a)))
+ (cj/test-load-all))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-b)))
+ (cj/test-load-all))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda () project-a)))
+ (let ((names (cj/test--current-project-test-names)))
+ (should (member 'test-testrunner-project-names-a names))
+ (should-not (member 'test-testrunner-project-names-b names)))))
+ (when (ert-test-boundp 'test-testrunner-project-names-a)
+ (ert-delete-test 'test-testrunner-project-names-a))
+ (when (ert-test-boundp 'test-testrunner-project-names-b)
+ (ert-delete-test 'test-testrunner-project-names-b))))
+ (test-testrunner-teardown))
+
(provide 'test-test-runner)
;;; test-test-runner.el ends here