aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-reconcile--check-for-open-work.el14
-rw-r--r--tests/test-reconcile--find-git-repos.el22
-rw-r--r--tests/test-reconcile--git-directory.el63
-rw-r--r--tests/test-reconcile--pull-clean.el32
-rw-r--r--tests/test-reconcile--pull-dirty.el105
-rw-r--r--tests/test-reconcile--should-skip-p.el110
-rw-r--r--tests/testutil-reconcile-open-repos.el32
7 files changed, 214 insertions, 164 deletions
diff --git a/tests/test-reconcile--check-for-open-work.el b/tests/test-reconcile--check-for-open-work.el
index e4615dab..37a5d0a3 100644
--- a/tests/test-reconcile--check-for-open-work.el
+++ b/tests/test-reconcile--check-for-open-work.el
@@ -177,5 +177,19 @@ Regression: lexical-binding + `(boundp 'base-dir)' used to silently skip this."
(cj/check-for-open-work))
(should (cl-some (lambda (m) (string-match-p "Complete\\." m)) messages)))))
+(ert-deftest test-reconcile-check-for-open-work-normal-summary-counts-statuses ()
+ "Summary reports pulled, review, skipped, and failed counts."
+ (let ((summary (cj/reconcile--summary-message
+ '((:status pulled)
+ (:status needs-review)
+ (:status skipped :reason skipped-remote)
+ (:status pull-failed)
+ (:status status-failed)))))
+ (should (string-match-p "Repositories checked: 5" summary))
+ (should (string-match-p "pulled: 1" summary))
+ (should (string-match-p "needs review: 1" summary))
+ (should (string-match-p "skipped: 1" summary))
+ (should (string-match-p "failed: 2" summary))))
+
(provide 'test-reconcile--check-for-open-work)
;;; test-reconcile--check-for-open-work.el ends here
diff --git a/tests/test-reconcile--find-git-repos.el b/tests/test-reconcile--find-git-repos.el
index 25987818..e065fca9 100644
--- a/tests/test-reconcile--find-git-repos.el
+++ b/tests/test-reconcile--find-git-repos.el
@@ -27,11 +27,19 @@
(should (= (length repos) 1))
(should (string-suffix-p "child" (car repos))))))
-(ert-deftest test-find-git-repos-normal-repo-with-nested-subrepo ()
- "Finds both a parent repo and a sub-repo inside it."
+(ert-deftest test-find-git-repos-normal-stops-at-repo-root-by-default ()
+ "Finds a parent repo and does not descend into nested repos by default."
(reconcile-test-with-temp-dirs
("deepsat/.git/" "deepsat/frontend/.git/" "deepsat/backend/.git/")
(let ((repos (cj/find-git-repos test-root)))
+ (should (= (length repos) 1))
+ (should (string-suffix-p "deepsat" (car repos))))))
+
+(ert-deftest test-find-git-repos-normal-can-include-nested-subrepos ()
+ "Finds nested repos when INCLUDE-NESTED is non-nil."
+ (reconcile-test-with-temp-dirs
+ ("deepsat/.git/" "deepsat/frontend/.git/" "deepsat/backend/.git/")
+ (let ((repos (cj/find-git-repos test-root t)))
(should (= (length repos) 3)))))
(ert-deftest test-find-git-repos-normal-mixed-repos-and-dirs ()
@@ -73,5 +81,15 @@
(should (= (length repos) 1))
(should (string-suffix-p "visible-repo" (car repos))))))
+(ert-deftest test-find-git-repos-boundary-prunes-heavy-directories ()
+ "Skips generated/heavy directories while discovering repos."
+ (reconcile-test-with-temp-dirs
+ ("project/node_modules/dependency/.git/"
+ "project/.venv/tool/.git/"
+ "project/src/repo/.git/")
+ (let ((repos (cj/find-git-repos test-root)))
+ (should (= (length repos) 1))
+ (should (string-suffix-p "repo" (car repos))))))
+
(provide 'test-reconcile--find-git-repos)
;;; test-reconcile--find-git-repos.el ends here
diff --git a/tests/test-reconcile--git-directory.el b/tests/test-reconcile--git-directory.el
index ab4a6323..1999f706 100644
--- a/tests/test-reconcile--git-directory.el
+++ b/tests/test-reconcile--git-directory.el
@@ -18,13 +18,15 @@
(let ((dir (expand-file-name "repo" test-root))
(clean-called nil)
(dirty-called nil))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (cond ((string-match-p "remote.origin.url" cmd) "git@host:repo.git")
- ((string-match-p "status --porcelain" cmd) "")
- (t "")))
- (cl-letf (((symbol-function 'cj/reconcile--pull-clean)
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (cond
+ ((equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "git@host:repo.git\n"))
+ ((equal args '("status" "--porcelain"))
+ '(:exit 0 :output ""))
+ (t '(:exit 0 :output ""))))
+ (cl-letf (((symbol-function 'cj/reconcile--pull-clean)
(lambda (_dir) (setq clean-called t)))
((symbol-function 'cj/reconcile--pull-dirty)
(lambda (_dir) (setq dirty-called t)))
@@ -33,20 +35,22 @@
(should clean-called)
(should-not dirty-called))))
-(ert-deftest test-reconcile-git-directory-normal-dirty-repo-stashes ()
- "Dirty SSH repo calls pull-dirty, not pull-clean."
+(ert-deftest test-reconcile-git-directory-normal-dirty-repo-opens-review ()
+ "Dirty SSH repo calls review-first handler, not pull-clean."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root))
(clean-called nil)
(dirty-called nil))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (cond ((string-match-p "remote.origin.url" cmd) "git@host:repo.git")
- ((string-match-p "status --porcelain" cmd) " M file.el\n")
- (t "")))
- (cl-letf (((symbol-function 'cj/reconcile--pull-clean)
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (cond
+ ((equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "git@host:repo.git\n"))
+ ((equal args '("status" "--porcelain"))
+ '(:exit 0 :output " M file.el\n"))
+ (t '(:exit 0 :output ""))))
+ (cl-letf (((symbol-function 'cj/reconcile--pull-clean)
(lambda (_dir) (setq clean-called t)))
((symbol-function 'cj/reconcile--pull-dirty)
(lambda (_dir) (setq dirty-called t)))
@@ -62,13 +66,12 @@
(let ((dir (expand-file-name "repo" test-root))
(clean-called nil)
(dirty-called nil))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (if (string-match-p "remote.origin.url" cmd)
- "https://github.com/user/repo.git"
- ""))
- (cl-letf (((symbol-function 'cj/reconcile--pull-clean)
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "https://github.com/user/repo.git\n")
+ '(:exit 0 :output "")))
+ (cl-letf (((symbol-function 'cj/reconcile--pull-clean)
(lambda (_dir) (setq clean-called t)))
((symbol-function 'cj/reconcile--pull-dirty)
(lambda (_dir) (setq dirty-called t)))
@@ -77,6 +80,20 @@
(should-not clean-called)
(should-not dirty-called))))
+(ert-deftest test-reconcile-git-directory-normal-skipped-result-includes-reason ()
+ "Skipped repos return a structured reason."
+ (reconcile-test-with-temp-dirs
+ ("repo/.git/")
+ (let ((dir (expand-file-name "repo" test-root)))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "https://github.com/user/repo.git\n")
+ '(:exit 0 :output "")))
+ (let ((result (cj/reconcile-git-directory dir)))
+ (should (eq (plist-get result :status) 'skipped))
+ (should (eq (plist-get result :reason) 'skipped-remote)))))))
+
;;; Boundary Cases
(ert-deftest test-reconcile-git-directory-boundary-emits-checking-message ()
diff --git a/tests/test-reconcile--pull-clean.el b/tests/test-reconcile--pull-clean.el
index a10c6f1e..89739987 100644
--- a/tests/test-reconcile--pull-clean.el
+++ b/tests/test-reconcile--pull-clean.el
@@ -17,12 +17,14 @@
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root))
(messages nil))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message)
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (should (equal args '("pull" "--rebase" "--quiet")))
+ '(:exit 0 :output ""))
+ (cl-letf (((symbol-function 'message)
(lambda (fmt &rest args) (push (apply #'format fmt args) messages))))
- (cj/reconcile--pull-clean dir)))
+ (let ((result (cj/reconcile--pull-clean dir)))
+ (should (eq (plist-get result :status) 'pulled)))))
(should-not (cl-some (lambda (m) (string-match-p "Warning" m)) messages)))))
(ert-deftest test-pull-clean-normal-failure-warns ()
@@ -31,12 +33,13 @@
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root))
(messages nil))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 1)
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message)
+ (reconcile-test-with-git-mock
+ (lambda (_args) '(:exit 1 :output "boom\n"))
+ (cl-letf (((symbol-function 'message)
(lambda (fmt &rest args) (push (apply #'format fmt args) messages))))
- (cj/reconcile--pull-clean dir)))
+ (let ((result (cj/reconcile--pull-clean dir)))
+ (should (eq (plist-get result :status) 'pull-failed))
+ (should (equal (plist-get result :output) "boom\n")))))
(should (cl-some (lambda (m) (string-match-p "Warning.*git pull failed" m)) messages))
(should (cl-some (lambda (m) (string-match-p "exit code: 1" m)) messages)))))
@@ -48,12 +51,11 @@
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root))
(messages nil))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 128)
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message)
+ (reconcile-test-with-git-mock
+ (lambda (_args) '(:exit 128 :output "fatal\n"))
+ (cl-letf (((symbol-function 'message)
(lambda (fmt &rest args) (push (apply #'format fmt args) messages))))
- (cj/reconcile--pull-clean dir)))
+ (cj/reconcile--pull-clean dir)))
(should (cl-some (lambda (m) (string-match-p "exit code: 128" m)) messages)))))
(provide 'test-reconcile--pull-clean)
diff --git a/tests/test-reconcile--pull-dirty.el b/tests/test-reconcile--pull-dirty.el
index 2ba1f5d1..c26c8548 100644
--- a/tests/test-reconcile--pull-dirty.el
+++ b/tests/test-reconcile--pull-dirty.el
@@ -1,7 +1,7 @@
-;;; test-reconcile--pull-dirty.el --- Tests for cj/reconcile--pull-dirty -*- lexical-binding: t; -*-
+;;; test-reconcile--pull-dirty.el --- Tests for dirty repo review handling -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the dirty-repo reconciliation: stash, pull, pop, magit.
+;; Dirty repositories should be review-first: no stash, pull, or stash-pop.
;;; Code:
@@ -9,104 +9,43 @@
(require 'testutil-reconcile-open-repos)
(require 'reconcile-open-repos)
-;;; Normal Cases
-
-(ert-deftest test-pull-dirty-normal-stash-pull-pop-success ()
- "When stash, pull, and pop all succeed, magit is still opened."
+(ert-deftest test-pull-dirty-normal-opens-magit-for-review ()
+ "Dirty repo handling opens Magit and returns a needs-review result."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
(reconcile-test-with-magit-mock
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (_cmd) "")
- (cj/reconcile--pull-dirty dir))
- (should (member dir reconcile-test-magit-calls))))))
-
-(ert-deftest test-pull-dirty-normal-stash-fails-opens-magit ()
- "When stash fails, magit is opened and warning emitted."
- (reconcile-test-with-temp-dirs
- ("repo/.git/")
- (let ((dir (expand-file-name "repo" test-root))
- (messages nil))
- (reconcile-test-with-magit-mock
- (reconcile-test-with-shell-mocks
- (lambda (cmd)
- (if (string-match-p "stash --quiet\\'" cmd) 1 0))
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (push (apply #'format fmt args) messages))))
- (cj/reconcile--pull-dirty dir)))
- (should (member dir reconcile-test-magit-calls))
- (should (cl-some (lambda (m) (string-match-p "stash failed" m)) messages))))))
+ (let ((result (cj/reconcile--pull-dirty dir)))
+ (should (member dir reconcile-test-magit-calls))
+ (should (eq (plist-get result :status) 'needs-review))
+ (should (equal (plist-get result :directory) dir)))))))
-(ert-deftest test-pull-dirty-normal-pull-fails-warns ()
- "When stash succeeds but pull fails, warning mentions pull failure."
- (reconcile-test-with-temp-dirs
- ("repo/.git/")
- (let ((dir (expand-file-name "repo" test-root))
- (messages nil))
- (reconcile-test-with-magit-mock
- (reconcile-test-with-shell-mocks
- (lambda (cmd)
- (cond ((string-match-p "stash --quiet\\'" cmd) 0)
- ((string-match-p "pull" cmd) 1)
- (t 0)))
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (push (apply #'format fmt args) messages))))
- (cj/reconcile--pull-dirty dir)))
- (should (cl-some (lambda (m) (string-match-p "git pull failed" m)) messages))))))
-
-(ert-deftest test-pull-dirty-normal-stash-pop-fails-warns ()
- "When stash and pull succeed but pop fails, warning mentions stash pop."
- (reconcile-test-with-temp-dirs
- ("repo/.git/")
- (let ((dir (expand-file-name "repo" test-root))
- (messages nil))
- (reconcile-test-with-magit-mock
- (reconcile-test-with-shell-mocks
- (lambda (cmd)
- (cond ((string-match-p "stash pop" cmd) 1)
- ((string-match-p "stash" cmd) 0)
- (t 0)))
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (push (apply #'format fmt args) messages))))
- (cj/reconcile--pull-dirty dir)))
- (should (cl-some (lambda (m) (string-match-p "stash pop failed" m)) messages))))))
-
-;;; Boundary Cases
-
-(ert-deftest test-pull-dirty-boundary-always-opens-magit ()
- "Magit is opened regardless of whether pull succeeds or fails."
+(ert-deftest test-pull-dirty-normal-does-not-run-git-commands ()
+ "Dirty repo handling must not mutate the worktree with git commands."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
- ;; Test with pull failure
(reconcile-test-with-magit-mock
- (reconcile-test-with-shell-mocks
- (lambda (cmd)
- (if (string-match-p "pull" cmd) 1 0))
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message) (lambda (_fmt &rest _args))))
- (cj/reconcile--pull-dirty dir)))
+ (reconcile-test-with-git-mock
+ (lambda (_args)
+ (ert-fail "Dirty repo handler should not run git commands"))
+ (cj/reconcile--pull-dirty dir))
(should (member dir reconcile-test-magit-calls))))))
(ert-deftest test-pull-dirty-boundary-uncommitted-work-message ()
- "Always emits 'contains uncommitted work' message."
+ "Dirty repo handling announces review instead of auto-reconciling."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root))
(messages nil))
(reconcile-test-with-magit-mock
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (_cmd) "")
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (push (apply #'format fmt args) messages))))
- (cj/reconcile--pull-dirty dir)))
- (should (cl-some (lambda (m) (string-match-p "uncommitted work" m)) messages))))))
+ (cl-letf (((symbol-function 'message)
+ (lambda (fmt &rest args)
+ (push (apply #'format fmt args) messages))))
+ (cj/reconcile--pull-dirty dir)))
+ (should (cl-some (lambda (m)
+ (string-match-p "opening Magit for review" m))
+ messages)))))
(provide 'test-reconcile--pull-dirty)
;;; test-reconcile--pull-dirty.el ends here
diff --git a/tests/test-reconcile--should-skip-p.el b/tests/test-reconcile--should-skip-p.el
index 3e9c0177..8964fd3b 100644
--- a/tests/test-reconcile--should-skip-p.el
+++ b/tests/test-reconcile--should-skip-p.el
@@ -17,50 +17,58 @@
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (if (string-match-p "remote.origin.url" cmd)
- "git@github.com:user/repo.git"
- ""))
- (should-not (cj/reconcile--should-skip-p dir))))))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "git@github.com:user/repo.git\n")
+ '(:exit 0 :output "")))
+ (should-not (cj/reconcile--should-skip-p dir))))))
(ert-deftest test-should-skip-p-normal-http-remote-skipped ()
"HTTP remote repo should be skipped (reference clone)."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (if (string-match-p "remote.origin.url" cmd)
- "http://github.com/user/repo.git"
- ""))
- (should (cj/reconcile--should-skip-p dir))))))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "http://github.com/user/repo.git\n")
+ '(:exit 0 :output "")))
+ (should (cj/reconcile--should-skip-p dir))))))
(ert-deftest test-should-skip-p-normal-https-remote-skipped ()
"HTTPS remote repo should be skipped (reference clone)."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (if (string-match-p "remote.origin.url" cmd)
- "https://github.com/user/repo.git"
- ""))
- (should (cj/reconcile--should-skip-p dir))))))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "https://github.com/user/repo.git\n")
+ '(:exit 0 :output "")))
+ (should (cj/reconcile--should-skip-p dir))))))
+
+(ert-deftest test-should-skip-p-normal-https-remote-not-skipped-when-policy-disabled ()
+ "HTTPS remote repos can be included by disabling the skip regexp."
+ (reconcile-test-with-temp-dirs
+ ("repo/.git/")
+ (let ((dir (expand-file-name "repo" test-root))
+ (cj/reconcile-skipped-remote-regexp nil))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "https://github.com/user/repo.git\n")
+ '(:exit 0 :output "")))
+ (should-not (cj/reconcile--should-skip-p dir))))))
(ert-deftest test-should-skip-p-normal-no-remote-skipped ()
"Local-only repo (no remote) should be skipped."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (if (string-match-p "remote.origin.url" cmd) "" ""))
- (should (cj/reconcile--should-skip-p dir))))))
+ (reconcile-test-with-git-mock
+ (lambda (_args) '(:exit 1 :output ""))
+ (should (cj/reconcile--should-skip-p dir))))))
;;; Boundary Cases
@@ -76,26 +84,48 @@
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (if (string-match-p "remote.origin.url" cmd)
- "user@myserver.com:repos/project.git"
- ""))
- (should-not (cj/reconcile--should-skip-p dir))))))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "user@myserver.com:repos/project.git\n")
+ '(:exit 0 :output "")))
+ (should-not (cj/reconcile--should-skip-p dir))))))
(ert-deftest test-should-skip-p-boundary-ssh-protocol-url-not-skipped ()
"ssh:// protocol URL should NOT be skipped."
(reconcile-test-with-temp-dirs
("repo/.git/")
(let ((dir (expand-file-name "repo" test-root)))
- (reconcile-test-with-shell-mocks
- (lambda (_cmd) 0)
- (lambda (cmd)
- (if (string-match-p "remote.origin.url" cmd)
- "ssh://git@github.com/user/repo.git"
- ""))
- (should-not (cj/reconcile--should-skip-p dir))))))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "ssh://git@github.com/user/repo.git\n")
+ '(:exit 0 :output "")))
+ (should-not (cj/reconcile--should-skip-p dir))))))
+
+(ert-deftest test-should-skip-p-boundary-file-protocol-remote-not-skipped ()
+ "file:// remote repo should NOT be skipped by the default policy."
+ (reconcile-test-with-temp-dirs
+ ("repo/.git/")
+ (let ((dir (expand-file-name "repo" test-root)))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "file:///srv/git/repo.git\n")
+ '(:exit 0 :output "")))
+ (should-not (cj/reconcile--should-skip-p dir))))))
+
+(ert-deftest test-should-skip-p-boundary-local-path-remote-not-skipped ()
+ "Plain local path remote should NOT be skipped by the default policy."
+ (reconcile-test-with-temp-dirs
+ ("repo/.git/")
+ (let ((dir (expand-file-name "repo" test-root)))
+ (reconcile-test-with-git-mock
+ (lambda (args)
+ (if (equal args '("config" "--get" "remote.origin.url"))
+ '(:exit 0 :output "/srv/git/repo.git\n")
+ '(:exit 0 :output "")))
+ (should-not (cj/reconcile--should-skip-p dir))))))
(provide 'test-reconcile--should-skip-p)
;;; test-reconcile--should-skip-p.el ends here
diff --git a/tests/testutil-reconcile-open-repos.el b/tests/testutil-reconcile-open-repos.el
index 2d8614eb..b81e1b48 100644
--- a/tests/testutil-reconcile-open-repos.el
+++ b/tests/testutil-reconcile-open-repos.el
@@ -2,7 +2,7 @@
;;; Commentary:
;; Provides helper macros and functions for testing reconcile-open-repos.
-;; Creates temporary directory trees with fake .git dirs and mocks shell commands.
+;; Creates temporary directory trees with fake .git dirs and mocks git commands.
;;; Code:
@@ -42,6 +42,36 @@ SHELL-CMD-TO-STR-FN receives (command) and returns a string."
(lambda (cmd) (funcall ,shell-cmd-to-str-fn cmd))))
,@body))
+(defvar reconcile-test-git-calls nil
+ "List of git argv lists observed during reconcile tests.")
+
+(defmacro reconcile-test-with-git-mock (handler &rest body)
+ "Run BODY with `process-file' mocked for git.
+HANDLER receives the argv list and returns either an exit code integer or a
+plist with :exit and :output."
+ (declare (indent 1))
+ `(let ((reconcile-test-git-calls nil))
+ (cl-letf (((symbol-function 'process-file)
+ (lambda (program _infile destination _display &rest args)
+ (unless (string= program "git")
+ (error "Unexpected program: %s" program))
+ (push args reconcile-test-git-calls)
+ (let* ((result (funcall ,handler args))
+ (plist-result (and (consp result) (keywordp (car result))))
+ (exit (if plist-result (plist-get result :exit) result))
+ (output (if plist-result (plist-get result :output) "")))
+ (when (and destination output)
+ (let ((stdout-dest (if (consp destination)
+ (car destination)
+ destination)))
+ (cond
+ ((bufferp stdout-dest)
+ (with-current-buffer stdout-dest (insert output)))
+ ((eq stdout-dest t)
+ (insert output)))))
+ exit))))
+ ,@body)))
+
(defvar reconcile-test-magit-calls nil
"List of directories passed to magit-status during tests.")