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

    Lean up the tests with a shared matcher and helper consolidation
    
    - Add a :to-have-same-items-as custom matcher (order-independent list
      equality with a missing/extra failure message) and use it in place of
      the (sort X #'string<) :to-equal (sort ...) dance across the watch,
      indexing, async, consult and file-kinds specs.
    - Add a shared projectile-test-match-sig helper and drop the two nearly
      identical per-file --sig copies (scan-async, search-review).
    - Make the search-review --wait delegate to the shared
      projectile-test-wait-for pump instead of re-rolling the event loop.
    
    No behavior change; 889 specs green.
---
 test/projectile-async-test.el         |  8 +++-----
 test/projectile-consult-test.el       |  4 ++--
 test/projectile-file-kinds-test.el    |  6 +++---
 test/projectile-indexing-test.el      |  2 +-
 test/projectile-scan-async-test.el    | 27 +++++++++------------------
 test/projectile-search-review-test.el | 26 +++++---------------------
 test/projectile-test-helpers.el       | 30 ++++++++++++++++++++++++++++++
 test/projectile-watch-test.el         | 16 +++++++---------
 8 files changed, 60 insertions(+), 59 deletions(-)

diff --git a/test/projectile-async-test.el b/test/projectile-async-test.el
index d91a05686e..dad5ed02e6 100644
--- a/test/projectile-async-test.el
+++ b/test/projectile-async-test.el
@@ -309,11 +309,9 @@ that stores into it as the async callback."
             (projectile-git-use-fd nil))
         (call-process "git" nil nil nil "init")
         (call-process "git" nil nil nil "add" "-A")
-        (let ((sync (sort (projectile-dir-files-alien default-directory) 
#'string<))
-              (async (sort (copy-sequence
-                            (projectile--dir-files-alien-await 
default-directory))
-                           #'string<)))
-          (expect async :to-equal sync)
+        (let ((sync (projectile-dir-files-alien default-directory))
+              (async (projectile--dir-files-alien-await default-directory)))
+          (expect async :to-have-same-items-as sync)
           (expect async :to-contain "a.el")
           (expect async :to-contain "src/b.el"))))))
 
diff --git a/test/projectile-consult-test.el b/test/projectile-consult-test.el
index ea09632d82..4da9a1225a 100644
--- a/test/projectile-consult-test.el
+++ b/test/projectile-consult-test.el
@@ -77,9 +77,9 @@
                (output (with-temp-buffer
                          (apply #'process-file (car wrapped) nil t nil (cdr 
wrapped))
                          (buffer-string)))
-               (files (sort (split-string output "\n" t) #'string<)))
+               (files (split-string output "\n" t)))
           ;; Same set Projectile itself would index, one per line (no NULs).
-          (expect files :to-equal '("a.el" "src/b.el"))
+          (expect files :to-have-same-items-as '("a.el" "src/b.el"))
           (expect output :not :to-match "\0"))))))
 
   (it "errors when external-command indexing is disabled"
diff --git a/test/projectile-file-kinds-test.el 
b/test/projectile-file-kinds-test.el
index 9212038b4d..06d93a42bf 100644
--- a/test/projectile-file-kinds-test.el
+++ b/test/projectile-file-kinds-test.el
@@ -253,9 +253,9 @@
                                    (setq captured choices)
                                    nil))
           (projectile--find-file-of-kind (assq :controller 
projectile--rails-file-kinds))
-          (expect (sort (copy-sequence captured) #'string<)
-                  :to-equal '("app/controllers/products_controller.rb"
-                              "app/controllers/users_controller.rb")))))))
+          (expect captured
+                  :to-have-same-items-as 
'("app/controllers/products_controller.rb"
+                                           
"app/controllers/users_controller.rb")))))))
 
 (describe "projectile-toggle-related-file"
   (it "opens the single counterpart immediately"
diff --git a/test/projectile-indexing-test.el b/test/projectile-indexing-test.el
index d57359d701..5bf1e55348 100644
--- a/test/projectile-indexing-test.el
+++ b/test/projectile-indexing-test.el
@@ -987,7 +987,7 @@
                          (projectile-dir-files root)))
                (hybrid (let ((projectile-indexing-method 'hybrid))
                          (projectile-dir-files root))))
-          (expect (sort native #'string<) :to-equal (sort hybrid #'string<))
+          (expect native :to-have-same-items-as hybrid)
           (expect native :to-contain "src/main.c")
           (expect native :to-contain "README.md")
           (expect native :to-contain "keep.text")
diff --git a/test/projectile-scan-async-test.el 
b/test/projectile-scan-async-test.el
index c299c83362..07596886c9 100644
--- a/test/projectile-scan-async-test.el
+++ b/test/projectile-scan-async-test.el
@@ -74,15 +74,6 @@ project root.  BODY runs with `default-directory' at the 
root and
             projectile-replace--case-fold t))
     buf))
 
-(defun projectile-scan-async-test--sig (matches root)
-  "Return a comparable signature (relpath line col string) list for MATCHES."
-  (mapcar (lambda (m)
-            (list (file-relative-name (projectile-replace--match-file m) root)
-                  (projectile-replace--match-line m)
-                  (projectile-replace--match-column m)
-                  (projectile-replace--match-string m)))
-          matches))
-
 (defun projectile-scan-async-test--candidates (root)
   "Return the regexp-search candidate files under ROOT for term foo."
   (projectile-replace--candidates "foo" nil t root))
@@ -121,10 +112,10 @@ project root.  BODY runs with `default-directory' at the 
root and
               (with-current-buffer buf
                 (expect projectile-replace--scanning :to-be nil)
                 (expect projectile-replace--scan-timer :to-be nil)
-                (expect (projectile-scan-async-test--sig
+                (expect (projectile-test-match-sig
                          projectile-replace--matches root)
                         :to-equal
-                        (projectile-scan-async-test--sig sync root))))
+                        (projectile-test-match-sig sync root))))
           (kill-buffer buf)))))
 
   (it "streams matches in chunk by chunk and ends with the full set"
@@ -151,10 +142,10 @@ project root.  BODY runs with `default-directory' at the 
root and
               (with-current-buffer buf
                 (expect projectile-replace--scanning :to-be nil)
                 (expect projectile-replace--scan-timer :to-be nil)
-                (expect (projectile-scan-async-test--sig
+                (expect (projectile-test-match-sig
                          projectile-replace--matches root)
                         :to-equal
-                        (projectile-scan-async-test--sig sync root))))
+                        (projectile-test-match-sig sync root))))
           (kill-buffer buf)))))
 
   (it "keeps case sensitivity in a late chunk (parity with sync)"
@@ -179,11 +170,11 @@ project root.  BODY runs with `default-directory' at the 
root and
               (projectile-replace--gather-async candidates "foo" buf nil)
               (projectile-scan-async-test--pump buf)
               (with-current-buffer buf
-                (expect (projectile-scan-async-test--sig
+                (expect (projectile-test-match-sig
                          projectile-replace--matches root)
                         :to-equal
-                        (projectile-scan-async-test--sig sync root))
-                (expect (projectile-scan-async-test--sig
+                        (projectile-test-match-sig sync root))
+                (expect (projectile-test-match-sig
                          projectile-replace--matches root)
                         :not :to-contain '("e.txt" 1 0 "FOO"))))
           (kill-buffer buf)))))
@@ -204,10 +195,10 @@ project root.  BODY runs with `default-directory' at the 
root and
               (with-current-buffer buf
                 (expect (length projectile-replace--matches) :to-equal 3)
                 (expect projectile-replace--truncated :to-be-truthy)
-                (expect (projectile-scan-async-test--sig
+                (expect (projectile-test-match-sig
                          projectile-replace--matches root)
                         :to-equal
-                        (projectile-scan-async-test--sig sync root))))
+                        (projectile-test-match-sig sync root))))
           (kill-buffer buf)))))
 
   (it "runs ON-DONE in the buffer once scanning finishes"
diff --git a/test/projectile-search-review-test.el 
b/test/projectile-search-review-test.el
index 5ae732f8a8..edd733f33d 100644
--- a/test/projectile-search-review-test.el
+++ b/test/projectile-search-review-test.el
@@ -104,16 +104,6 @@ REGEXP-P selects `projectile-search-regexp-review'."
                     files :test #'equal))
       (sort files #'string<))))
 
-(defun projectile-search-review-test--sig (matches root)
-  "Return a comparable (relpath line col string) signature list for MATCHES."
-  (sort (mapcar (lambda (m)
-                  (list (file-relative-name (projectile-replace--match-file m) 
root)
-                        (projectile-replace--match-line m)
-                        (projectile-replace--match-column m)
-                        (projectile-replace--match-string m)))
-                matches)
-        (lambda (a b) (string< (format "%S" a) (format "%S" b)))))
-
 (describe "projectile-search-review (literal)"
   (it "finds every literal match grouped by file, read-only, no apply keys"
     (projectile-search-review-test--with-project
@@ -332,15 +322,9 @@ REGEXP-P selects `projectile-search-regexp-review'."
 ;;; Ripgrep fast-path
 
 (defun projectile-search-review-test--wait (buf)
-  "Pump events until BUF's ripgrep scan finishes (or a timeout elapses).
-Pumps the general event loop (not just the scan process): once the rg
-process has exited, `accept-process-output' on that dead process can
-return immediately without dispatching its queued sentinel - the sentinel
-is what runs `projectile-search--rg-finish' and clears the scanning flag."
-  (with-current-buffer buf
-    (let ((limit (+ (float-time) 10)))
-      (while (and projectile-replace--scanning (< (float-time) limit))
-        (accept-process-output nil 0.05)))))
+  "Pump events until BUF's ripgrep scan finishes (or a timeout elapses)."
+  (projectile-test-wait-for
+   (lambda () (not (buffer-local-value 'projectile-replace--scanning buf)))))
 
 (describe "projectile-search--rg byte->char column conversion"
   (it "counts a multibyte prefix as characters, not bytes"
@@ -666,9 +650,9 @@ is what runs `projectile-search--rg-finish' and clears the 
scanning flag."
                                   root term term nil t t)
         (projectile-search--gather-rg buf term nil)
         (projectile-search-review-test--wait buf)
-        (expect (projectile-search-review-test--sig
+        (expect (projectile-test-match-sig
                  (buffer-local-value 'projectile-replace--matches buf) root)
                 :to-equal
-                (projectile-search-review-test--sig elisp root))))))
+                (projectile-test-match-sig elisp root))))))
 
 ;;; projectile-search-review-test.el ends here
diff --git a/test/projectile-test-helpers.el b/test/projectile-test-helpers.el
index 3afd8a8cfe..ab0bfdc05a 100644
--- a/test/projectile-test-helpers.el
+++ b/test/projectile-test-helpers.el
@@ -190,6 +190,36 @@ process filters and sentinels get a chance to run.  
TIMEOUT defaults to
       (accept-process-output nil 0.05))
     result))
 
+
+;;; Custom matchers
+
+(buttercup-define-matcher :to-have-same-items-as (actual expected)
+  "Match when ACTUAL and EXPECTED hold the same items, ignoring order.
+A readable stand-in for `(sort ...) :to-equal (sort ...)' - the failure
+message reports exactly which items are missing and which are extra."
+  (let* ((actual (funcall actual))
+         (expected (funcall expected))
+         (missing (cl-set-difference expected actual :test #'equal))
+         (extra (cl-set-difference actual expected :test #'equal)))
+    (cons (and (null missing) (null extra))
+          (format "Expected %S to have the same items as %S (missing: %S, 
extra: %S)"
+                  actual expected missing extra))))
+
+;;; Match helpers (reviewable search/replace)
+
+(defun projectile-test-match-sig (matches root)
+  "Return a stable, comparable signature for MATCHES relative to ROOT.
+Each `projectile-replace--match' becomes (RELPATH LINE COLUMN STRING);
+the list is sorted so two gatherings of the same matches compare `equal'
+regardless of the order they were collected in."
+  (sort (mapcar (lambda (m)
+                  (list (file-relative-name (projectile-replace--match-file m) 
root)
+                        (projectile-replace--match-line m)
+                        (projectile-replace--match-column m)
+                        (projectile-replace--match-string m)))
+                matches)
+        (lambda (a b) (string< (format "%S" a) (format "%S" b)))))
+
 (defun file-handler-for-tests (operation &rest args)
   "Handler for # files.
 Just delegates OPERATION and ARGS for all operations except for
diff --git a/test/projectile-watch-test.el b/test/projectile-watch-test.el
index d528ca19bd..bcfaf042dc 100644
--- a/test/projectile-watch-test.el
+++ b/test/projectile-watch-test.el
@@ -66,10 +66,9 @@ watch bookkeeping into each other, caching is on (transient) 
and
 (describe "projectile--watch-directories"
   (it "derives the project root and the directory chain of every cached file"
     (let ((root "/tmp/watched-project/"))
-      (expect (sort (projectile--watch-directories
-                     root '("a.el" "src/b.el" "src/sub/c.el" "doc/d.adoc"))
-                    #'string<)
-              :to-equal
+      (expect (projectile--watch-directories
+               root '("a.el" "src/b.el" "src/sub/c.el" "doc/d.adoc"))
+              :to-have-same-items-as
               (list root
                     (concat root "doc/")
                     (concat root "src/")
@@ -83,9 +82,8 @@ watch bookkeeping into each other, caching is on (transient) 
and
         (let ((root (projectile-project-root)))
           (projectile-cache-project root '("a.el" "src/b.el" "src/sub/c.el"))
           (expect (spy-calls-count 'file-notify-add-watch) :to-equal 3)
-          (expect (sort (mapcar #'cdr (gethash root 
projectile--project-watches))
-                        #'string<)
-                  :to-equal
+          (expect (mapcar #'cdr (gethash root projectile--project-watches))
+                  :to-have-same-items-as
                   (list root (concat root "src/") (concat root 
"src/sub/")))))))
 
   (it "skips directories that no longer exist on disk"
@@ -323,8 +321,8 @@ watch bookkeeping into each other, caching is on 
(transient) and
             (projectile--handle-watch-event root (list descriptor 'created 
file))
             (projectile--handle-watch-event root (list descriptor 'created 
file)))
           (projectile--process-watch-events root)
-          (expect (sort (gethash root projectile-projects-cache) #'string<)
-                  :to-equal '("a.el" "b.el"))
+          (expect (gethash root projectile-projects-cache)
+                  :to-have-same-items-as '("a.el" "b.el"))
           (projectile--unwatch-project root)))))
 
   (it "invalidates the cache when the project root itself is deleted"

Reply via email to