branch: elpa/projectile
commit 6a55a186ecd4c62e3cc2cf7e2389ff18e9ce7816
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    Extract shared sandbox-project test helpers
    
    Collapse the copy-paste that had accumulated across the search/replace
    and project specs into projectile-test-helpers:
    
    - projectile-test-project-root - the (file-truename (expand-file-name
      "project/")) incantation appeared ~48 times.
    - projectile-test-kill-project-buffers - three near-identical copies
      (the superset version kills the project buffers and both result buffers).
    - projectile-test-use-plain-grep - three identical copies.
    - projectile-test-disk / projectile-test-disk-raw - the on-disk content
      readers, now available to every spec.
    
    No behavior change; 889 specs green.
---
 test/projectile-async-test.el          |   2 +-
 test/projectile-consult-test.el        |   2 +-
 test/projectile-file-kinds-test.el     |   6 +-
 test/projectile-ignore-test.el         |  30 +++----
 test/projectile-indexing-test.el       |  26 +++---
 test/projectile-project-type-test.el   |  22 ++---
 test/projectile-replace-review-test.el | 142 +++++++++++++--------------------
 test/projectile-replace-test.el        |  41 +++-------
 test/projectile-scan-async-test.el     |   2 +-
 test/projectile-search-review-test.el  |  47 +++--------
 test/projectile-test-helpers.el        |  50 ++++++++++++
 11 files changed, 173 insertions(+), 197 deletions(-)

diff --git a/test/projectile-async-test.el b/test/projectile-async-test.el
index dad5ed02e6..9025f34277 100644
--- a/test/projectile-async-test.el
+++ b/test/projectile-async-test.el
@@ -305,7 +305,7 @@ that stores into it as the async callback."
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/" "project/src/" "project/a.el" "project/src/b.el")
-      (let ((default-directory (file-truename (expand-file-name "project/")))
+      (let ((default-directory (projectile-test-project-root))
             (projectile-git-use-fd nil))
         (call-process "git" nil nil nil "init")
         (call-process "git" nil nil nil "add" "-A")
diff --git a/test/projectile-consult-test.el b/test/projectile-consult-test.el
index 4da9a1225a..f883207084 100644
--- a/test/projectile-consult-test.el
+++ b/test/projectile-consult-test.el
@@ -67,7 +67,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/" "project/src/" "project/a.el" "project/src/b.el")
-      (let ((default-directory (file-truename (expand-file-name "project/")))
+      (let ((default-directory (projectile-test-project-root))
             (projectile-git-use-fd nil))
         (call-process "git" nil nil nil "init")
         (call-process "git" nil nil nil "add" "-A")
diff --git a/test/projectile-file-kinds-test.el 
b/test/projectile-file-kinds-test.el
index eb3160ef4f..f77b7f6128 100644
--- a/test/projectile-file-kinds-test.el
+++ b/test/projectile-file-kinds-test.el
@@ -264,7 +264,7 @@
           ("app/controllers/users_controller.rb"
            "app/models/user.rb")
           (:file-kinds projectile--rails-file-kinds)
-        (let ((root (file-truename (expand-file-name "project/"))))
+        (let ((root (projectile-test-project-root)))
           (spy-on 'find-file)
           (spy-on 'buffer-file-name :and-return-value
                   (expand-file-name "app/controllers/users_controller.rb" 
root))
@@ -278,7 +278,7 @@
           ("app/controllers/users_controller.rb"
            "app/models/user.rb")
           (:file-kinds projectile--rails-file-kinds)
-        (let* ((root (file-truename (expand-file-name "project/")))
+        (let* ((root (projectile-test-project-root))
                ;; the current file reached through a symlinked project root, so
                ;; `buffer-file-name' is un-resolved while the root is resolved
                (link-file (expand-file-name
@@ -299,7 +299,7 @@
            "app/models/user.rb"
            "app/views/users/index.html.erb")
           (:file-kinds projectile--rails-file-kinds)
-        (let* ((root (file-truename (expand-file-name "project/")))
+        (let* ((root (projectile-test-project-root))
                (current "app/controllers/users_controller.rb"))
           (spy-on 'find-file :and-call-fake
                   (lambda (path)
diff --git a/test/projectile-ignore-test.el b/test/projectile-ignore-test.el
index bea06af668..3e22091f7e 100644
--- a/test/projectile-ignore-test.el
+++ b/test/projectile-ignore-test.el
@@ -316,7 +316,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "+/src\n"
                   "-/build\n"
@@ -333,7 +333,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/")))
+      (let ((root (projectile-test-project-root))
             (coding-system-for-write 'utf-8-unix))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-héllo/wörld\n"
@@ -347,7 +347,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n-bar"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -361,7 +361,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -376,7 +376,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let* ((root (file-truename (expand-file-name "project/")))
+      (let* ((root (projectile-test-project-root))
              (dirconfig (expand-file-name ".projectile" root)))
         (with-temp-file dirconfig (insert "-foo\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -393,7 +393,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (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)))))
@@ -401,7 +401,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -416,7 +416,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n"))
         (with-temp-file (expand-file-name ".projectile-alt" root)
@@ -437,7 +437,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\nstale-pattern\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -450,7 +450,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n+/src\n!/build/keep\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -462,7 +462,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "stale-pattern\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -478,7 +478,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -494,7 +494,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (spy-on 'projectile-project-root :and-return-value root)
         (spy-on 'projectile-dir-files-alien :and-return-value '("a"))
         (spy-on 'display-warning)
@@ -507,7 +507,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -522,7 +522,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-foo\n"))
         (spy-on 'projectile-project-root :and-return-value root)
diff --git a/test/projectile-indexing-test.el b/test/projectile-indexing-test.el
index 5bf1e55348..92ddf1278d 100644
--- a/test/projectile-indexing-test.el
+++ b/test/projectile-indexing-test.el
@@ -63,7 +63,7 @@
      (projectile-test-with-files
       ("project/"
        "project/existing.txt")
-      (let ((default-directory (file-truename (expand-file-name "project/")))
+      (let ((default-directory (projectile-test-project-root))
             (projectile-git-use-fd nil)
             (projectile-fd-executable nil))
         ;; Initialize a real git repo, commit a file, then delete it without 
staging
@@ -137,7 +137,7 @@
        "project/keep.txt"
        "project/drop.txt"
        "project/.projectile")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-/drop.txt\n"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -614,7 +614,7 @@
      (projectile-test-with-files
       ("project/.git/"
        "project/.gitmodules")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (spy-on 'projectile--git-submodule-paths
                 :and-return-value '("vendor/sub"))
         (expect (projectile--git-submodules root) :to-equal '("vendor/sub"))
@@ -625,7 +625,7 @@
      (projectile-test-with-files
       ("project/.git/"
        "project/.gitmodules")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (spy-on 'projectile--git-submodule-paths
                 :and-return-value '("vendor/sub"))
         (projectile--git-submodules root)
@@ -640,7 +640,7 @@
      (projectile-test-with-files
       ("project/.git/"
        "project/.gitmodules")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (spy-on 'projectile--git-submodule-paths
                 :and-return-value '("vendor/sub"))
         (spy-on 'projectile-project-root :and-return-value root)
@@ -657,7 +657,7 @@
     (projectile-test-with-sandbox
      (projectile-test-with-files
       ("project/.git/")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (spy-on 'projectile--git-submodule-paths)
         (spy-on 'projectile-files-via-ext-command)
         (expect (projectile--git-submodules root) :to-be nil)
@@ -669,7 +669,7 @@
      (projectile-test-with-files
       ("project/.git/"
        "project/vendor/sub/.git")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".gitmodules" root)
           (insert "[submodule \"sub\"]\n"
                   "\tpath = vendor/sub\n"
@@ -684,7 +684,7 @@
        "project/web-ui/"
        "project/web-ui/vendor/sub/.git"
        "project/server/vendor/other/.git")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".gitmodules" root)
           (insert "[submodule \"sub\"]\n"
                   "\tpath = web-ui/vendor/sub\n"
@@ -699,7 +699,7 @@
      (projectile-test-with-files
       ("project/.git/"
        "project/.gitmodules")
-      (let ((root (file-truename (expand-file-name "project/")))
+      (let ((root (projectile-test-project-root))
             (projectile-git-submodule-command "git my-submodule-lister"))
         (spy-on 'projectile-files-via-ext-command
                 :and-return-value '("vendor/sub"))
@@ -713,7 +713,7 @@
      (projectile-test-with-files
       ("project/.git/"
        "project/.gitmodules")
-      (let ((root (file-truename (expand-file-name "project/")))
+      (let ((root (projectile-test-project-root))
             (projectile-git-submodule-command nil))
         (spy-on 'projectile--git-submodule-paths)
         (spy-on 'projectile-files-via-ext-command)
@@ -850,7 +850,7 @@
        "project/dir with spaces/.git"
        ;; Registered but never initialized: no .git inside.
        "project/vendor/uninitialized/")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".gitmodules" root)
           (insert "# top-level comment\n"
                   "[submodule \"first\"]\n"
@@ -869,7 +869,7 @@
      (projectile-test-with-files
       ("project/.git/"
        "project/.gitmodules")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (expect (projectile--git-submodule-paths root) :to-be nil))))))
 
 (describe "projectile-get-all-sub-projects-files"
@@ -964,7 +964,7 @@
        "project/docs/a.text"
        "project/keep.text"
        "project/README.md")
-      (let ((root (file-truename (expand-file-name "project/"))))
+      (let ((root (projectile-test-project-root)))
         (with-temp-file (expand-file-name ".projectile" root)
           (insert "-*.text\n!keep.text\n-vendor/\n-src/gen/\n"))
         (spy-on 'projectile-project-root :and-return-value root)
diff --git a/test/projectile-project-type-test.el 
b/test/projectile-project-type-test.el
index 4e94368273..a84187739d 100644
--- a/test/projectile-project-type-test.el
+++ b/test/projectile-project-type-test.el
@@ -258,7 +258,7 @@
        "project/spec/"
        "project/package.json")
       (let ((projectile-indexing-method 'native))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'rails-rspec)))))
   (it "detects project-type for elisp eldev projects"
     (projectile-test-with-sandbox
@@ -267,7 +267,7 @@
        "project/Eldev"
        "project/project.el")
       (let ((projectile-indexing-method 'native))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'emacs-eldev)))))
   (it "detects project-type for dotnet sln projects"
     (projectile-test-with-sandbox
@@ -275,7 +275,7 @@
       ("project/"
        "project/Project.sln")
       (let ((projectile-indexing-method 'native))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'dotnet-sln)))))
   (it "detects project-type for dotnet slnx projects"
     (projectile-test-with-sandbox
@@ -283,7 +283,7 @@
       ("project/"
        "project/Project.slnx")
       (let ((projectile-indexing-method 'native))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'dotnet-sln)))))
   (it "detects project-type for Julia PkgTemplates.jl projects"
     (projectile-test-with-sandbox
@@ -292,7 +292,7 @@
        "project/src/"
        "project/Project.toml")
       (let ((projectile-indexing-method 'native))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'julia)))))
   (it "detects project-type for Zig projects"
     (projectile-test-with-sandbox
@@ -301,7 +301,7 @@
        "project/src/"
        "project/build.zig.zon")
       (let ((projectile-indexing-method 'native))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'zig)))))
   (it "does not match a project type whose marker-files are empty"
     (projectile-test-with-sandbox
@@ -311,7 +311,7 @@
       (let ((projectile-project-types '((empty marker-files nil)
                                         (real marker-files ("foo"))))
             (projectile-project-type-cache (make-hash-table :test 'equal)))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'real)))))
   (it "falls back to generic when the only type has empty marker-files"
     (projectile-test-with-sandbox
@@ -320,7 +320,7 @@
        "project/foo")
       (let ((projectile-project-types '((empty marker-files nil)))
             (projectile-project-type-cache (make-hash-table :test 'equal)))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'generic)))))
   (it "detects a marker that sits in a subdirectory of the root"
     ;; `debian/control' carries a path separator, so it can't be answered
@@ -332,7 +332,7 @@
        "project/debian/"
        "project/debian/control")
       (let ((projectile-project-type-cache (make-hash-table :test 'equal)))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'debian)))))
   (it "detects project-type for lowercase makefile projects"
     (projectile-test-with-sandbox
@@ -340,7 +340,7 @@
       ("project/"
        "project/makefile")
       (let ((projectile-project-type-cache (make-hash-table :test 'equal)))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'make)))))
   (it "detects project-type for GNUmakefile projects"
     (projectile-test-with-sandbox
@@ -348,7 +348,7 @@
       ("project/"
        "project/GNUmakefile")
       (let ((projectile-project-type-cache (make-hash-table :test 'equal)))
-        (spy-on 'projectile-project-root :and-return-value (file-truename 
(expand-file-name "project/")))
+        (spy-on 'projectile-project-root :and-return-value 
(projectile-test-project-root))
         (expect (projectile-detect-project-type) :to-equal 'gnumake)))))
   (it "passes the project root to a function marker (#1909)"
     (let ((projectile-project-types
diff --git a/test/projectile-replace-review-test.el 
b/test/projectile-replace-review-test.el
index c2ea9629c7..fe46fb16d2 100644
--- a/test/projectile-replace-review-test.el
+++ b/test/projectile-replace-review-test.el
@@ -30,18 +30,6 @@
 
 (require 'projectile-test-helpers)
 
-(defun projectile-replace-review-test--kill-project-buffers (root)
-  "Kill all buffers visiting files under ROOT, discarding modifications."
-  (dolist (buffer (buffer-list))
-    (when-let* ((file (buffer-file-name buffer)))
-      (when (string-prefix-p root (file-truename file))
-        (with-current-buffer buffer
-          (set-buffer-modified-p nil))
-        (let (kill-buffer-query-functions)
-          (kill-buffer buffer)))))
-  (when-let* ((buf (get-buffer projectile-replace-buffer-name)))
-    (kill-buffer buf)))
-
 (defmacro projectile-replace-review-test--with-project (files &rest body)
   "Evaluate BODY in a sandbox project containing FILES.
 
@@ -63,7 +51,7 @@ afterwards."
                       (insert ,(cdr spec)))))
                files)
      (let ((default-directory (file-name-as-directory
-                               (file-truename (expand-file-name "project/"))))
+                               (projectile-test-project-root)))
            (projectile-indexing-method 'native)
            (projectile-projects-cache (make-hash-table :test 'equal))
            (projectile-projects-cache-time (make-hash-table :test 'equal))
@@ -72,14 +60,7 @@ afterwards."
        (spy-on 'projectile-project-root :and-return-value default-directory)
        (unwind-protect
            (progn ,@body)
-         (projectile-replace-review-test--kill-project-buffers 
default-directory)))))
-
-(defun projectile-replace-review-test--use-plain-grep ()
-  "Force `projectile-files-with-string' to shell out to plain grep."
-  (assume (projectile-unixy-system-p) "needs unixy text utilities")
-  (spy-on 'executable-find :and-call-fake
-          (lambda (command &rest _)
-            (member command '("grep" "cut" "uniq")))))
+         (projectile-test-kill-project-buffers default-directory)))))
 
 (defun projectile-replace-review-test--run (term replacement &optional 
regexp-p)
   "Drive the review command for TERM/REPLACEMENT and return the results buffer.
@@ -94,19 +75,6 @@ REGEXP-P selects `projectile-replace-regexp-review'."
                             #'projectile-replace-review)))
     (get-buffer projectile-replace-buffer-name)))
 
-(defun projectile-replace-review-test--disk (file)
-  "Return the on-disk contents of project FILE."
-  (with-temp-buffer
-    (insert-file-contents (expand-file-name file))
-    (buffer-string)))
-
-(defun projectile-replace-review-test--disk-raw (file)
-  "Return the raw (unconverted) on-disk bytes of project FILE as a string."
-  (with-temp-buffer
-    (let ((coding-system-for-read 'no-conversion))
-      (insert-file-contents (expand-file-name file)))
-    (buffer-string)))
-
 (defun projectile-replace-review-test--apply (buf)
   "Apply the enabled matches in results buffer BUF."
   (with-current-buffer buf
@@ -125,33 +93,33 @@ REGEXP-P selects `projectile-replace-regexp-review'."
     (projectile-replace-review-test--with-project
         (("a.txt" . "foo one foo\n")
          ("lib/b.txt" . "start foo end\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           (expect (length projectile-replace--matches) :to-equal 3))
         (projectile-replace-review-test--apply buf)
         ;; the files were never opened, so this proves the disk write-back
         (expect (get-file-buffer (expand-file-name "a.txt")) :to-be nil)
-        (expect (projectile-replace-review-test--disk "a.txt")
+        (expect (projectile-test-disk "a.txt")
                 :to-equal "bar one bar\n")
-        (expect (projectile-replace-review-test--disk "lib/b.txt")
+        (expect (projectile-test-disk "lib/b.txt")
                 :to-equal "start bar end\n"))))
 
   (it "applies multiple matches on one line in descending order"
     (projectile-replace-review-test--with-project
         (("m.txt" . "xx xx xx\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "xx" "yyy")))
         (with-current-buffer buf
           (expect (length projectile-replace--matches) :to-equal 3))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "m.txt")
+        (expect (projectile-test-disk "m.txt")
                 :to-equal "yyy yyy yyy\n"))))
 
   (it "edits an already-open buffer in place rather than the file on disk"
     (projectile-replace-review-test--with-project
         (("open.txt" . "first foo\nlast foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (find-file-noselect (expand-file-name "open.txt"))
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
@@ -162,13 +130,13 @@ REGEXP-P selects `projectile-replace-regexp-review'."
         (projectile-replace-review-test--apply buf)
         (with-current-buffer (get-file-buffer (expand-file-name "open.txt"))
           (expect (buffer-string) :to-equal "first bar\nlast bar\n"))
-        (expect (projectile-replace-review-test--disk "open.txt")
+        (expect (projectile-test-disk "open.txt")
                 :to-equal "first bar\nlast bar\n"))))
 
   (it "leaves a disabled match untouched and applies only the enabled ones"
     (projectile-replace-review-test--with-project
         (("t.txt" . "foo foo foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           ;; disable the first of the three matches
@@ -176,7 +144,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
                  (car projectile-replace--matches))
                 nil))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "t.txt")
+        (expect (projectile-test-disk "t.txt")
                 :to-equal "foo bar bar\n")))))
 
 (describe "projectile-replace-regexp-review"
@@ -189,7 +157,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
           ;; only the two standalone `foo' symbols match
           (expect (length projectile-replace--matches) :to-equal 2))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "s.txt")
+        (expect (projectile-test-disk "s.txt")
                 :to-equal "X foobar barfoo X\n"))))
 
   (it "substitutes capture groups in the replacement"
@@ -198,7 +166,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
       (let ((buf (projectile-replace-review-test--run
                   "\\([a-z]+\\)_\\([a-z]+\\)" "\\2-\\1" 'regexp)))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "c.txt")
+        (expect (projectile-test-disk "c.txt")
                 :to-equal "bar-foo and qux-baz\n"))))
 
   (it "excludes files ignored via .projectile"
@@ -216,9 +184,9 @@ REGEXP-P selects `projectile-replace-regexp-review'."
                            projectile-replace--matches)
                   :to-be nil))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "keep.txt")
+        (expect (projectile-test-disk "keep.txt")
                 :to-equal "bar\n")
-        (expect (projectile-replace-review-test--disk "secret/hide.txt")
+        (expect (projectile-test-disk "secret/hide.txt")
                 :to-equal "foo\n")))))
 
 (describe "projectile-replace--expand"
@@ -237,14 +205,14 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "skips a closed file that changed on disk since the scan"
     (projectile-replace-review-test--with-project
         (("a.txt" . "hello foo world\nsecond foo line\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         ;; the file changes on disk between scan and apply; stale positions
         ;; must NOT be applied to the shifted text
         (with-temp-file (expand-file-name "a.txt")
           (insert "PREPENDED LINE\nhello foo world\nsecond foo line\n"))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "a.txt")
+        (expect (projectile-test-disk "a.txt")
                 :to-equal
                 "PREPENDED LINE\nhello foo world\nsecond foo line\n"))))
 
@@ -260,20 +228,20 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "applies via disk when the scanned buffer was killed before apply"
     (projectile-replace-review-test--with-project
         (("k.txt" . "foo here\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (find-file-noselect (expand-file-name "k.txt"))
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         ;; the scan tagged matches with the live buffer; kill it before apply
         (let (kill-buffer-query-functions)
           (kill-buffer (get-file-buffer (expand-file-name "k.txt"))))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "k.txt")
+        (expect (projectile-test-disk "k.txt")
                 :to-equal "bar here\n"))))
 
   (it "edits the buffer, not disk, for a file opened and modified after scan"
     (projectile-replace-review-test--with-project
         (("o.txt" . "foo tail\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         ;; closed at scan; now open it and make an unsaved edit AFTER the
         ;; match, so the recorded position still spans `foo'
@@ -285,13 +253,13 @@ REGEXP-P selects `projectile-replace-regexp-review'."
           ;; edit lands in the buffer; disk is not clobbered behind it
           (with-current-buffer fb
             (expect (buffer-string) :to-equal "bar tail\nUNSAVED\n"))
-          (expect (projectile-replace-review-test--disk "o.txt")
+          (expect (projectile-test-disk "o.txt")
                   :to-equal "foo tail\n")))))
 
   (it "skips a live buffer's match when text is inserted before it after the 
scan"
     (projectile-replace-review-test--with-project
         (("p.txt" . "keep\nfoo tail\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       ;; open before the scan so the match is tagged against the live buffer
       (find-file-noselect (expand-file-name "p.txt"))
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
@@ -307,18 +275,18 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "preserves CRLF line endings on the disk write-back path"
     (projectile-replace-review-test--with-project
         (("crlf.txt" . "foo\r\nbar\r\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "baz")))
         (projectile-replace-review-test--apply buf)
         ;; raw bytes: CRLF must survive, not be normalized to LF
-        (expect (projectile-replace-review-test--disk-raw "crlf.txt")
+        (expect (projectile-test-disk-raw "crlf.txt")
                 :to-equal "baz\r\nbar\r\n"))))
 
   (it "keeps applying the remaining files when one file's write fails"
     (projectile-replace-review-test--with-project
         (("g.txt" . "foo\n")
          ("h.txt" . "foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         ;; make applying g.txt error; h.txt must still be replaced
         (let ((orig (symbol-function 'projectile-replace--apply-file)))
@@ -328,13 +296,13 @@ REGEXP-P selects `projectile-replace-regexp-review'."
                            (error "boom")
                          (funcall orig file matches replacement literal)))))
             (projectile-replace-review-test--apply buf)))
-        (expect (projectile-replace-review-test--disk "h.txt")
+        (expect (projectile-test-disk "h.txt")
                 :to-equal "bar\n"))))
 
   (it "does not force-save a buffer that had unsaved edits"
     (projectile-replace-review-test--with-project
         (("u.txt" . "foo mid\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (find-file-noselect (expand-file-name "u.txt"))
       (with-current-buffer (get-file-buffer (expand-file-name "u.txt"))
         (goto-char (point-max))
@@ -346,14 +314,14 @@ REGEXP-P selects `projectile-replace-regexp-review'."
         (with-current-buffer (get-file-buffer (expand-file-name "u.txt"))
           (expect (buffer-string) :to-equal "bar mid\nEXTRA\n")
           (expect (buffer-modified-p) :to-be-truthy))
-        (expect (projectile-replace-review-test--disk "u.txt")
+        (expect (projectile-test-disk "u.txt")
                 :to-equal "foo mid\n")))))
 
 (describe "projectile-replace-review case-sensitivity toggle"
   (it "flips which matches are found and re-renders"
     (projectile-replace-review-test--with-project
         (("case.txt" . "Foo foo FOO\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           ;; `case-fold-search' is t in the sandbox, so all three case
@@ -373,13 +341,13 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "applies only the case-sensitive survivors after a toggle"
     (projectile-replace-review-test--with-project
         (("s.txt" . "Foo foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           (projectile-replace--toggle-case))
         (projectile-replace-review-test--apply buf)
         ;; only the lowercase `foo' is replaced; `Foo' is left alone
-        (expect (projectile-replace-review-test--disk "s.txt")
+        (expect (projectile-test-disk "s.txt")
                 :to-equal "Foo bar\n"))))
 
   (it "still excludes ignored files on the case-insensitive fallback path"
@@ -389,7 +357,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
         ((".projectile" . "-secret\n")
          ("keep.txt" . "FOO\n")
          ("secret/hide.txt" . "FOO\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       ;; case-insensitive (sandbox default) literal search for `foo' finds
       ;; `FOO' in keep.txt but never in the ignored secret/ tree
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
@@ -402,15 +370,15 @@ REGEXP-P selects `projectile-replace-regexp-review'."
                            projectile-replace--matches)
                   :to-be nil))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "keep.txt")
+        (expect (projectile-test-disk "keep.txt")
                 :to-equal "bar\n")
-        (expect (projectile-replace-review-test--disk "secret/hide.txt")
+        (expect (projectile-test-disk "secret/hide.txt")
                 :to-equal "FOO\n"))))
 
   (it "re-enables all matches when re-scanning (documented reset)"
     (projectile-replace-review-test--with-project
         (("e.txt" . "foo foo foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           ;; disable one match, then toggle case: re-scan rebuilds the list,
@@ -428,7 +396,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "re-scans and changes the count for a term with metacharacters"
     (projectile-replace-review-test--with-project
         (("meta.txt" . "a.b axb a.b\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "a.b" "Z")))
         (with-current-buffer buf
           (expect projectile-replace--literal :to-be-truthy)
@@ -443,7 +411,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "refuses an invalid regexp and stays literal without erroring"
     (projectile-replace-review-test--with-project
         (("br.txt" . "foo[bar\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo[" "X")))
         (with-current-buffer buf
           (expect projectile-replace--literal :to-be-truthy)
@@ -457,7 +425,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "keeps only matches whose line matches and applies just those"
     (projectile-replace-review-test--with-project
         (("f.txt" . "keep foo here\ndrop foo there\nkeep foo again\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           (expect (length projectile-replace--matches) :to-equal 3)
@@ -465,25 +433,25 @@ REGEXP-P selects `projectile-replace-regexp-review'."
           (expect (length projectile-replace--matches) :to-equal 2)
           (expect projectile-replace--filtered :to-be-truthy))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "f.txt")
+        (expect (projectile-test-disk "f.txt")
                 :to-equal "keep bar here\ndrop foo there\nkeep bar again\n"))))
 
   (it "flushes matches whose line matches and applies the survivors"
     (projectile-replace-review-test--with-project
         (("f.txt" . "keep foo here\ndrop foo there\nkeep foo again\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           (projectile-replace--flush-matches "drop")
           (expect (length projectile-replace--matches) :to-equal 2))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "f.txt")
+        (expect (projectile-test-disk "f.txt")
                 :to-equal "keep bar here\ndrop foo there\nkeep bar again\n"))))
 
   (it "restores a filtered-away match on re-search"
     (projectile-replace-review-test--with-project
         (("f.txt" . "keep foo here\ndrop foo there\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           (expect (length projectile-replace--matches) :to-equal 2)
@@ -500,7 +468,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
     (projectile-replace-review-test--with-project
         (("keep.txt" . "foo\n")
          ("lib/skip.txt" . "foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           (expect (length projectile-replace--matches) :to-equal 2)
@@ -511,16 +479,16 @@ REGEXP-P selects `projectile-replace-regexp-review'."
                     (car projectile-replace--matches)))
                   :to-equal "keep.txt"))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "keep.txt")
+        (expect (projectile-test-disk "keep.txt")
                 :to-equal "bar\n")
-        (expect (projectile-replace-review-test--disk "lib/skip.txt")
+        (expect (projectile-test-disk "lib/skip.txt")
                 :to-equal "foo\n"))))
 
   (it "flushes matches whose project-relative path matches the regexp"
     (projectile-replace-review-test--with-project
         (("keep.txt" . "foo\n")
          ("lib/skip.txt" . "foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           ;; matches the directory component of the relative path
@@ -531,14 +499,14 @@ REGEXP-P selects `projectile-replace-regexp-review'."
                     (car projectile-replace--matches)))
                   :to-equal "keep.txt"))
         (projectile-replace-review-test--apply buf)
-        (expect (projectile-replace-review-test--disk "lib/skip.txt")
+        (expect (projectile-test-disk "lib/skip.txt")
                 :to-equal "foo\n")))))
 
 (describe "projectile-replace-review status header"
   (it "reflects the mode flags and updates when they toggle"
     (projectile-replace-review-test--with-project
         (("h.txt" . "foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (let ((header (projectile-replace-review-test--header buf)))
           (expect header :to-match "Replace \"foo\" with \"bar\"")
@@ -554,7 +522,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "shows (none) when there is no replacement yet"
     (projectile-replace-review-test--with-project
         (("h.txt" . "foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "")))
         (expect (projectile-replace-review-test--header buf)
                 :to-match "with (none)"))))
@@ -562,7 +530,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "notes when the list has been filtered"
     (projectile-replace-review-test--with-project
         (("h.txt" . "keep foo\ndrop foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (expect (projectile-replace-review-test--header buf)
                 :not :to-match "filtered")
@@ -581,7 +549,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
     (projectile-replace-review-test--with-project
         (("a.txt" . "foo one foo\n")
          ("lib/b.txt" . "start foo end\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let* ((buf (projectile-replace-review-test--run "foo" "bar"))
              (gbuf (projectile-replace-review-test--export buf)))
         (unwind-protect
@@ -610,7 +578,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
     (projectile-replace-review-test--with-project
         (("keep.txt" . "keep foo\n")
          ("drop.txt" . "drop foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf (projectile-replace--flush-files "drop"))
         (let ((gbuf (projectile-replace-review-test--export buf)))
@@ -624,7 +592,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "errors when there are no matches to export"
     (projectile-replace-review-test--with-project
         (("z.txt" . "foo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           (setq projectile-replace--matches nil)
@@ -633,7 +601,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "excludes matches toggled off, matching what apply would do"
     (projectile-replace-review-test--with-project
         (("e.txt" . "foo\nfoo\nfoo\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "foo" "bar")))
         (with-current-buffer buf
           ;; disable the first (line 1) match
@@ -653,7 +621,7 @@ REGEXP-P selects `projectile-replace-regexp-review'."
   (it "does not create a phantom grep hit for a numeric term like 10:30"
     (projectile-replace-review-test--with-project
         (("t.txt" . "before 10:30 after\n"))
-      (projectile-replace-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-replace-review-test--run "10:30" "NOON")))
         (let ((gbuf (projectile-replace-review-test--export buf)))
           (unwind-protect
diff --git a/test/projectile-replace-test.el b/test/projectile-replace-test.el
index 87c19a56f4..0d792e4635 100644
--- a/test/projectile-replace-test.el
+++ b/test/projectile-replace-test.el
@@ -27,16 +27,6 @@
 
 (require 'projectile-test-helpers)
 
-(defun projectile-replace-test--kill-project-buffers (root)
-  "Kill all buffers visiting files under ROOT, discarding modifications."
-  (dolist (buffer (buffer-list))
-    (when-let* ((file (buffer-file-name buffer)))
-      (when (string-prefix-p root (file-truename file))
-        (with-current-buffer buffer
-          (set-buffer-modified-p nil))
-        (let (kill-buffer-query-functions)
-          (kill-buffer buffer))))))
-
 (defmacro projectile-replace-test--with-project (files &rest body)
   "Evaluate BODY in a sandbox project containing FILES.
 
@@ -59,7 +49,7 @@ next spec."
                       (insert ,(cdr spec)))))
                files)
      (let ((default-directory (file-name-as-directory
-                               (file-truename (expand-file-name "project/"))))
+                               (projectile-test-project-root)))
            (projectile-indexing-method 'native)
            (projectile-projects-cache (make-hash-table :test 'equal))
            (projectile-projects-cache-time (make-hash-table :test 'equal))
@@ -72,7 +62,7 @@ next spec."
        (spy-on 'projectile-project-root :and-return-value default-directory)
        (unwind-protect
            (progn ,@body)
-         (projectile-replace-test--kill-project-buffers default-directory)))))
+         (projectile-test-kill-project-buffers default-directory)))))
 
 (defun projectile-replace-test--replace (old new &optional regexp-p)
   "Replace OLD with NEW in the current project, auto-confirming everything.
@@ -112,23 +102,12 @@ modify buffers without saving them."
         (insert-file-contents file)
         (buffer-string)))))
 
-(defun projectile-replace-test--use-plain-grep ()
-  "Force `projectile-files-with-string' to shell out to plain grep.
-Skips the calling spec when grep isn't available.  Pinning the tool
-makes the specs exercise the external listing pipeline deterministically
-no matter which of rg/ag/ack happens to be installed."
-  (assume (projectile-unixy-system-p) "needs unixy text utilities")
-  (spy-on 'executable-find :and-call-fake
-          (lambda (command &rest _)
-            ;; keep `projectile-unixy-system-p' truthy, hide rg/ag/ack/git
-            (member command '("grep" "cut" "uniq")))))
-
 (describe "projectile-replace"
   (it "replaces a literal string across multiple project files"
     (projectile-replace-test--with-project
         (("a.txt" . "foo one foo\n")
          ("lib/b.txt" . "start foo end\n"))
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (projectile-replace-test--replace "foo" "bar")
       (expect (projectile-replace-test--contents "a.txt")
               :to-equal "bar one bar\n")
@@ -139,7 +118,7 @@ no matter which of rg/ag/ack happens to be installed."
     (projectile-replace-test--with-project
         (("code.txt" . "call f(x) + 1\n")
          ("decoy.txt" . "fx is not a match\n"))
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (projectile-replace-test--replace "f(x)" "g(y)")
       (expect (projectile-replace-test--contents "code.txt")
               :to-equal "call g(y) + 1\n")
@@ -151,7 +130,7 @@ no matter which of rg/ag/ack happens to be installed."
     (projectile-replace-test--with-project
         (("match.txt" . "some foo here\n")
          ("nomatch.txt" . "nothing to see\n"))
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (projectile-replace-test--replace "foo" "bar")
       (expect (projectile-replace-test--contents "match.txt")
               :to-equal "some bar here\n")
@@ -164,7 +143,7 @@ no matter which of rg/ag/ack happens to be installed."
     (projectile-replace-test--with-project
         (("open.txt" . "first foo\nmiddle\nlast foo\n")
          ("closed.txt" . "foo here\n"))
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (with-current-buffer (find-file-noselect (expand-file-name "open.txt"))
         (goto-char (point-max)))
       (projectile-replace-test--replace "foo" "bar")
@@ -178,7 +157,7 @@ no matter which of rg/ag/ack happens to be installed."
         (("case.txt" . "Foo bar\n"))
       ;; the listing must go through a real (case-sensitive by default)
       ;; external tool for this to prove anything
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (projectile-replace-test--replace "foo" "qux")
       ;; standard `query-replace' semantics: the match is folded and the
       ;; replacement preserves its case
@@ -189,7 +168,7 @@ no matter which of rg/ag/ack happens to be installed."
     (projectile-replace-test--with-project
         (("case.txt" . "Foo bar foo\n")
          ("lower.txt" . "foo only\n"))
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (projectile-replace-test--replace "Foo" "Qux")
       (expect (projectile-replace-test--contents "case.txt")
               :to-equal "Qux bar foo\n")
@@ -201,7 +180,7 @@ no matter which of rg/ag/ack happens to be installed."
   (it "works when the project root is in abbreviated form (#1115)"
     (projectile-replace-test--with-project
         (("a.txt" . "hello foo world\n"))
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       ;; roots commonly come back as "~/..." because `find-file' sets an
       ;; abbreviated `default-directory'; the external listing must still
       ;; line up with the expanded project file list
@@ -279,7 +258,7 @@ no matter which of rg/ag/ack happens to be installed."
         (("exact.txt" . "foo\n")
          ("cased.txt" . "FOO\n")
          ("none.txt" . "bar\n"))
-      (projectile-replace-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((files (projectile-files-with-string "foo" default-directory)))
         (expect (sort files #'string<)
                 :to-equal (list (expand-file-name "cased.txt")
diff --git a/test/projectile-scan-async-test.el 
b/test/projectile-scan-async-test.el
index 07596886c9..a4cd6f0f1c 100644
--- a/test/projectile-scan-async-test.el
+++ b/test/projectile-scan-async-test.el
@@ -52,7 +52,7 @@ project root.  BODY runs with `default-directory' at the root 
and
                       (insert ,(cdr spec)))))
                files)
      (let ((default-directory (file-name-as-directory
-                               (file-truename (expand-file-name "project/"))))
+                               (projectile-test-project-root)))
            (projectile-indexing-method 'native)
            (projectile-projects-cache (make-hash-table :test 'equal))
            (projectile-projects-cache-time (make-hash-table :test 'equal))
diff --git a/test/projectile-search-review-test.el 
b/test/projectile-search-review-test.el
index c1fb5c86de..f4f5346b35 100644
--- a/test/projectile-search-review-test.el
+++ b/test/projectile-search-review-test.el
@@ -30,20 +30,6 @@
 
 (require 'projectile-test-helpers)
 
-(defun projectile-search-review-test--kill-project-buffers (root)
-  "Kill all buffers visiting files under ROOT, discarding modifications."
-  (dolist (buffer (buffer-list))
-    (when-let* ((file (buffer-file-name buffer)))
-      (when (string-prefix-p root (file-truename file))
-        (with-current-buffer buffer
-          (set-buffer-modified-p nil))
-        (let (kill-buffer-query-functions)
-          (kill-buffer buffer)))))
-  (dolist (name (list projectile-search-buffer-name
-                      projectile-replace-buffer-name))
-    (when-let* ((buf (get-buffer name)))
-      (kill-buffer buf))))
-
 (defmacro projectile-search-review-test--with-project (files &rest body)
   "Evaluate BODY in a sandbox project containing FILES.
 
@@ -65,7 +51,7 @@ afterwards."
                       (insert ,(cdr spec)))))
                files)
      (let ((default-directory (file-name-as-directory
-                               (file-truename (expand-file-name "project/"))))
+                               (projectile-test-project-root)))
            (projectile-indexing-method 'native)
            (projectile-projects-cache (make-hash-table :test 'equal))
            (projectile-projects-cache-time (make-hash-table :test 'equal))
@@ -74,14 +60,7 @@ afterwards."
        (spy-on 'projectile-project-root :and-return-value default-directory)
        (unwind-protect
            (progn ,@body)
-         (projectile-search-review-test--kill-project-buffers 
default-directory)))))
-
-(defun projectile-search-review-test--use-plain-grep ()
-  "Force `projectile-files-with-string' to shell out to plain grep."
-  (assume (projectile-unixy-system-p) "needs unixy text utilities")
-  (spy-on 'executable-find :and-call-fake
-          (lambda (command &rest _)
-            (member command '("grep" "cut" "uniq")))))
+         (projectile-test-kill-project-buffers default-directory)))))
 
 (defun projectile-search-review-test--run (term &optional regexp-p)
   "Drive the search review command for TERM and return the results buffer.
@@ -109,7 +88,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
     (projectile-search-review-test--with-project
         (("a.txt" . "foo one foo\n")
          ("lib/b.txt" . "start foo end\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           (expect major-mode :to-equal 'projectile-search-mode)
@@ -129,7 +108,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
   (it "renders each match as LINE:COL: with the matched span present"
     (projectile-search-review-test--with-project
         (("a.txt" . "alpha foo beta\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           (expect (buffer-string) :to-match "1:7: alpha foo beta")))))
@@ -137,7 +116,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
   (it "reports no matches without popping a buffer"
     (projectile-search-review-test--with-project
         (("a.txt" . "nothing here\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (spy-on 'message)
       (let ((buf (projectile-search-review-test--run "absent")))
         (expect buf :to-be nil)))))
@@ -157,7 +136,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
     (projectile-search-review-test--with-project
         (("a.txt" . "line one\nsecond foo here\n")
          ("lib/b.txt" . "foo at top\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           ;; jump to the match in lib/b.txt
@@ -186,7 +165,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
   (it "prunes matches by line and marks the list filtered"
     (projectile-search-review-test--with-project
         (("f.txt" . "keep foo here\ndrop foo there\nkeep foo again\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           (expect (length projectile-replace--matches) :to-equal 3)
@@ -202,7 +181,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
     (projectile-search-review-test--with-project
         (("keep.txt" . "foo\n")
          ("lib/skip.txt" . "foo\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           (expect (length projectile-replace--matches) :to-equal 2)
@@ -217,7 +196,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
   (it "re-scans when case sensitivity flips"
     (projectile-search-review-test--with-project
         (("case.txt" . "Foo foo FOO\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           (expect projectile-replace--case-fold :to-be-truthy)
@@ -232,7 +211,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
   (it "re-scans when literal/regexp flips"
     (projectile-search-review-test--with-project
         (("meta.txt" . "a.b axb a.b\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "a.b")))
         (with-current-buffer buf
           (expect projectile-replace--literal :to-be-truthy)
@@ -246,7 +225,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
   (it "shows the term, counts and mode flags"
     (projectile-search-review-test--with-project
         (("h.txt" . "foo\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           (let ((header (buffer-substring-no-properties
@@ -270,7 +249,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
     (projectile-search-review-test--with-project
         (("a.txt" . "foo one foo\n")
          ("lib/b.txt" . "start foo end\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let* ((buf (projectile-search-review-test--run "foo"))
              (gbuf (projectile-search-review-test--export buf)))
         (unwind-protect
@@ -288,7 +267,7 @@ REGEXP-P selects `projectile-search-regexp-review'."
   (it "opens the replace reviewer preloaded with the same term"
     (projectile-search-review-test--with-project
         (("a.txt" . "foo one foo\n"))
-      (projectile-search-review-test--use-plain-grep)
+      (projectile-test-use-plain-grep)
       (let ((buf (projectile-search-review-test--run "foo")))
         (with-current-buffer buf
           ;; the bridge prompts only for the replacement
diff --git a/test/projectile-test-helpers.el b/test/projectile-test-helpers.el
index e6165b91b6..60ec0ba448 100644
--- a/test/projectile-test-helpers.el
+++ b/test/projectile-test-helpers.el
@@ -282,6 +282,56 @@ regardless of the order they were collected in."
                 matches)
         (lambda (a b) (string< (format "%S" a) (format "%S" b)))))
 
+;;; Sandbox project helpers
+
+(defun projectile-test-project-root ()
+  "Return the truename'd root of the sandbox `project/' subdirectory.
+The `projectile-test-with-files-using-custom-project' macro and the
+search/replace `--with-project' macros all build the project under a
+`project/' directory and stub `projectile-project-root' to this value."
+  (file-truename (expand-file-name "project/")))
+
+(defun projectile-test-kill-project-buffers (root)
+  "Kill buffers visiting files under ROOT, plus the results buffers.
+Discards modifications first (the replace commands leave buffers dirty),
+and also kills the `*projectile-search*'/`*projectile-replace*' results
+buffers so they don't leak into the next spec."
+  (dolist (buffer (buffer-list))
+    (when-let* ((file (buffer-file-name buffer)))
+      (when (string-prefix-p root (file-truename file))
+        (with-current-buffer buffer
+          (set-buffer-modified-p nil))
+        (let (kill-buffer-query-functions)
+          (kill-buffer buffer)))))
+  (dolist (name (list projectile-search-buffer-name
+                      projectile-replace-buffer-name))
+    (when-let* ((buf (get-buffer name)))
+      (kill-buffer buf))))
+
+(defun projectile-test-use-plain-grep ()
+  "Force `projectile-files-with-string' to shell out to plain grep.
+Skips the calling spec when grep isn't available.  Pinning the tool makes
+the specs exercise the external listing pipeline deterministically no
+matter which of rg/ag/ack happens to be installed."
+  (assume (projectile-unixy-system-p) "needs unixy text utilities")
+  (spy-on 'executable-find :and-call-fake
+          (lambda (command &rest _)
+            ;; keep `projectile-unixy-system-p' truthy, hide rg/ag/ack/git
+            (member command '("grep" "cut" "uniq")))))
+
+(defun projectile-test-disk (file)
+  "Return the on-disk contents of project FILE."
+  (with-temp-buffer
+    (insert-file-contents (expand-file-name file))
+    (buffer-string)))
+
+(defun projectile-test-disk-raw (file)
+  "Return the raw (unconverted) on-disk bytes of project FILE as a string."
+  (with-temp-buffer
+    (let ((coding-system-for-read 'no-conversion))
+      (insert-file-contents (expand-file-name file)))
+    (buffer-string)))
+
 (defun file-handler-for-tests (operation &rest args)
   "Handler for # files.
 Just delegates OPERATION and ARGS for all operations except for

Reply via email to