diff options
| author | Bozhidar Batsov <bozhidar@toptal.com> | 2026-04-26 07:14:41 +0100 |
|---|---|---|
| committer | Bozhidar Batsov <bozhidar@toptal.com> | 2026-04-26 07:14:41 +0100 |
| commit | 69adda8f55aaeedcee77948318c3e389665019db (patch) | |
| tree | 90b33a5edeea12c5ca76f381e40234a2293493d8 /test | |
| parent | 67981d6d93b957edf3359d94f6216837427834bc (diff) | |
Memoize per-function nil results in the root cache (#1836)
Previously a root function returning nil stored nil in the cache,
which is indistinguishable from a missing entry, so on the next call
the function was re-run. When a project's root is found by the third
function in projectile-project-root-functions, the first two
re-walked the tree on every call - and projectile-project-root is hot
in mode-line / company / spaceline updates.
Store the symbol 'none for unsuccessful entries and treat it as a
cache hit on lookup. The overall-failure marker already used 'none
in a separate slot, so this just extends the same convention to the
per-function entries.
Diffstat (limited to 'test')
| -rw-r--r-- | test/projectile-test.el | 26 |
1 files changed, 25 insertions, 1 deletions
diff --git a/test/projectile-test.el b/test/projectile-test.el index 52f24b1..88d1da7 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -1283,7 +1283,31 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. ;; Second "buffer": same default-directory, different override. ;; The cache must not return the previous buffer's answer. (let ((projectile-project-root beta-root)) - (expect (projectile-project-root) :to-equal beta-root))))))) + (expect (projectile-project-root) :to-equal beta-root)))))) + + (it "caches per-function nil results so earlier misses aren't re-walked (#1836)" + (projectile-test-with-sandbox + (projectile-test-with-files + ("projectA/.git/" + "projectA/src/") + (let* ((calls 0) + (always-nil (lambda (_dir) (cl-incf calls) nil)) + (projectile-project-root-functions + (list always-nil 'projectile-root-bottom-up)) + (projectile-project-root-files-bottom-up '(".git")) + (dir (expand-file-name "projectA/src/")) + (projectile-project-root-cache (make-hash-table :test 'equal))) + ;; first call: always-nil is invoked, then bottom-up wins + (expect (file-truename (projectile-project-root dir)) + :to-equal (file-truename (expand-file-name "projectA/"))) + (expect calls :to-equal 1) + ;; second call: the cached 'none for always-nil should be honoured + (expect (file-truename (projectile-project-root dir)) + :to-equal (file-truename (expand-file-name "projectA/"))) + (expect calls :to-equal 1) + ;; and the cache holds the 'none sentinel for the failing function + (expect (gethash (cons always-nil dir) projectile-project-root-cache) + :to-be 'none)))))) (describe "projectile-file-exists-p" (it "returns t if file exists" |
