aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBozhidar Batsov <bozhidar@toptal.com>2026-04-25 18:56:12 +0100
committerBozhidar Batsov <bozhidar@toptal.com>2026-04-25 18:56:12 +0100
commit925688ed5c0c7b7a9e97a47eaf2e2c63771aea35 (patch)
tree00fb3f8e827edbe59d977773074083b79595886c
parent930564202e350d5d6c73d6fc765b1cc29cb48f38 (diff)
Add tests for the dirconfig cache
The mtime-keyed cache around projectile-parse-dirconfig-file had no direct coverage. Cover the four invariants that matter: a cache hit on repeat calls, a re-parse when mtime advances, no caching of a nil result for a missing file, and entry removal via projectile-invalidate-cache (the path that previously crashed in #1854).
-rw-r--r--test/projectile-test.el59
1 files changed, 59 insertions, 0 deletions
diff --git a/test/projectile-test.el b/test/projectile-test.el
index 4a20e92..f874a83 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -574,6 +574,65 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'.
(expect (projectile-parse-dirconfig-file)
:to-equal '(nil ("keep-this") nil)))))
+(describe "dirconfig cache"
+ (before-each
+ (clrhash projectile--dirconfig-cache))
+ (it "memoizes the parsed result while the file is unchanged"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files
+ ("project/.projectile")
+ (let ((root (file-truename (expand-file-name "project/"))))
+ (with-temp-file (expand-file-name ".projectile" root)
+ (insert "-foo\n"))
+ (spy-on 'projectile-project-root :and-return-value root)
+ (spy-on 'projectile--parse-dirconfig-file-uncached
+ :and-call-through)
+ (projectile-parse-dirconfig-file)
+ (projectile-parse-dirconfig-file)
+ (projectile-parse-dirconfig-file)
+ (expect 'projectile--parse-dirconfig-file-uncached
+ :to-have-been-called-times 1)))))
+ (it "re-parses when the dirconfig file's mtime changes"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files
+ ("project/.projectile")
+ (let* ((root (file-truename (expand-file-name "project/")))
+ (dirconfig (expand-file-name ".projectile" root)))
+ (with-temp-file dirconfig (insert "-foo\n"))
+ (spy-on 'projectile-project-root :and-return-value root)
+ (expect (cadr (projectile-parse-dirconfig-file))
+ :to-equal '("foo"))
+ ;; Force a distinct mtime — file-attribute-modification-time has
+ ;; second-level resolution on some filesystems.
+ (set-file-times dirconfig (time-add (current-time) 5))
+ (with-temp-file dirconfig (insert "-bar\n"))
+ (set-file-times dirconfig (time-add (current-time) 5))
+ (expect (cadr (projectile-parse-dirconfig-file))
+ :to-equal '("bar"))))))
+ (it "returns nil and does not cache when the dirconfig file is absent"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files
+ ("project/")
+ (let ((root (file-truename (expand-file-name "project/"))))
+ (spy-on 'projectile-project-root :and-return-value root)
+ (expect (projectile-parse-dirconfig-file) :to-be nil)
+ (expect (gethash root projectile--dirconfig-cache) :to-be nil)))))
+ (it "is cleared for the project by projectile-invalidate-cache"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files
+ ("project/.projectile")
+ (let ((root (file-truename (expand-file-name "project/"))))
+ (with-temp-file (expand-file-name ".projectile" root)
+ (insert "-foo\n"))
+ (spy-on 'projectile-project-root :and-return-value root)
+ ;; Avoid touching the on-disk cache file or recentf during the test.
+ (spy-on 'projectile-persistent-cache-p :and-return-value nil)
+ (spy-on 'recentf-cleanup)
+ (projectile-parse-dirconfig-file)
+ (expect (gethash root projectile--dirconfig-cache) :not :to-be nil)
+ (projectile-invalidate-cache nil)
+ (expect (gethash root projectile--dirconfig-cache) :to-be nil))))))
+
(describe "projectile-get-project-directories"
(it "gets the list of project directories"
(spy-on 'projectile-project-root :and-return-value "/my/root/")