branch: elpa/projectile
commit b33b988356b4007e22fedc89959f1394acab39c4
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Test the ripgrep fast-path against the elisp scan in CI
Install ripgrep in the CI test job so the search reviewer's rg fast-path
is actually exercised (the specs self-skip when rg is absent). Add a
parity spec asserting the rg fast-path and the portable elisp scan return
the same match set over a shared fixture - the core correctness claim of
the fast-path, previously only tested per-branch.
Enabling rg in CI also surfaced a flaky wait helper: the pre-existing rg
specs pumped `accept-process-output' on the scan *process*, which returns
immediately once that process has exited without dispatching its queued
sentinel - so `projectile-search--rg-finish' never ran and the scan flag
never cleared (hung on Emacs 28.2). Pump the general event loop instead.
---
.github/workflows/test.yml | 8 +++++++
test/projectile-search-review-test.el | 42 +++++++++++++++++++++++++++++++++--
2 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1e98f2aeef..43649458e9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,6 +27,14 @@ jobs:
- name: Check out the source code
uses: actions/checkout@v6
+ # ripgrep powers the search reviewer's fast-path; install it so the specs
+ # that exercise the real `rg' subprocess actually run in CI instead of
+ # being skipped (they self-skip when `rg' is absent).
+ - name: Install ripgrep
+ run: |
+ apt-get update
+ apt-get install -y ripgrep
+
- name: Test the project
run: |
eldev -p -dtT -C test --expect 680
diff --git a/test/projectile-search-review-test.el
b/test/projectile-search-review-test.el
index 0d9a1e4965..5ae732f8a8 100644
--- a/test/projectile-search-review-test.el
+++ b/test/projectile-search-review-test.el
@@ -104,6 +104,16 @@ 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
@@ -322,11 +332,15 @@ 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)."
+ "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 projectile-replace--scan-process 0.05)))))
+ (accept-process-output nil 0.05)))))
(describe "projectile-search--rg byte->char column conversion"
(it "counts a multibyte prefix as characters, not bytes"
@@ -633,4 +647,28 @@ REGEXP-P selects `projectile-search-regexp-review'."
projectile-replace--matches)
:to-be nil))))))
+(describe "projectile-search-review ripgrep vs elisp parity"
+ (it "the ripgrep fast-path finds the same matches as the elisp scan"
+ (assume (executable-find "rg") "needs a real ripgrep")
+ (projectile-search-review-test--with-project
+ (("a.txt" . "foo one\nno match here\nfoo two\n")
+ ("sub/b.txt" . "prefix foo suffix\n")
+ ("c.txt" . "nothing to see\n"))
+ (let* ((root default-directory)
+ (term "foo")
+ ;; elisp reference scan (the portable, deterministic path)
+ (candidates (projectile-replace--candidates term t t root))
+ (elisp (plist-get (projectile-replace--gather candidates term)
+ :matches))
+ ;; ripgrep fast-path
+ (buf (get-buffer-create projectile-search-buffer-name)))
+ (projectile-replace--seed buf #'projectile-search-mode
+ 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
+ (buffer-local-value 'projectile-replace--matches buf) root)
+ :to-equal
+ (projectile-search-review-test--sig elisp root))))))
+
;;; projectile-search-review-test.el ends here