blob: 37a5d0a36013bc456f7c4aad146f9d6c0fcc1605 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
;;; test-reconcile--check-for-open-work.el --- Tests for cj/check-for-open-work -*- lexical-binding: t; -*-
;;; Commentary:
;; Tests for the top-level entry point that iterates `projects-dir',
;; `code-dir', `org-dir', and `user-emacs-directory'.
;;
;; Regression guard: a prior version used `(boundp 'base-dir)' under
;; lexical-binding, which always returned nil, causing every repo under
;; `projects-dir' and `code-dir' to be silently skipped.
;;; Code:
(require 'ert)
(require 'cl-lib)
(require 'testutil-reconcile-open-repos)
(require 'reconcile-open-repos)
;; Declare as special so `let' creates dynamic bindings the SUT can observe.
(defvar projects-dir)
(defvar code-dir)
(defvar org-dir)
(defvar test-reconcile-calls nil
"Directories passed to `cj/reconcile-git-directory' during a test.")
(defmacro test-reconcile-with-mocked-reconcile (&rest body)
"Run BODY with `cj/reconcile-git-directory' recording its argument.
Uses `setq' so recorded calls remain readable after BODY returns — a
`let' binding would be gone by the time the outer `should' runs."
(declare (indent 0))
`(progn
(setq test-reconcile-calls nil)
(cl-letf (((symbol-function 'cj/reconcile-git-directory)
(lambda (dir) (push dir test-reconcile-calls)))
((symbol-function 'message) (lambda (_fmt &rest _args))))
,@body)))
(defun test-reconcile-called-with-p (path)
"Return non-nil if `cj/reconcile-git-directory' was called with PATH."
(cl-some (lambda (d)
(string= (file-name-as-directory d)
(file-name-as-directory path)))
test-reconcile-calls))
;;; Normal Cases
(ert-deftest test-reconcile-check-for-open-work-normal-reconciles-projects-dir-repo ()
"A repo under `projects-dir' is passed to `cj/reconcile-git-directory'.
Regression: lexical-binding + `(boundp 'base-dir)' used to silently skip this."
(reconcile-test-with-temp-dirs
("projects/repo-a/.git/")
(let ((projects-dir (expand-file-name "projects" test-root))
(code-dir nil)
(org-dir nil))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (test-reconcile-called-with-p
(expand-file-name "projects/repo-a" test-root))))))
(ert-deftest test-reconcile-check-for-open-work-normal-reconciles-code-dir-repo ()
"A repo under `code-dir' is passed to `cj/reconcile-git-directory'.
Regression: lexical-binding + `(boundp 'base-dir)' used to silently skip this."
(reconcile-test-with-temp-dirs
("code/archsetup/.git/")
(let ((projects-dir nil)
(code-dir (expand-file-name "code" test-root))
(org-dir nil))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (test-reconcile-called-with-p
(expand-file-name "code/archsetup" test-root))))))
(ert-deftest test-reconcile-check-for-open-work-normal-reconciles-both-dirs ()
"Repos under both `projects-dir' and `code-dir' are reconciled in one run."
(reconcile-test-with-temp-dirs
("projects/proj-a/.git/" "code/code-a/.git/")
(let ((projects-dir (expand-file-name "projects" test-root))
(code-dir (expand-file-name "code" test-root))
(org-dir nil))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (test-reconcile-called-with-p
(expand-file-name "projects/proj-a" test-root)))
(should (test-reconcile-called-with-p
(expand-file-name "code/code-a" test-root))))))
(ert-deftest test-reconcile-check-for-open-work-normal-reconciles-every-repo ()
"Every repo under `projects-dir' is reconciled, not just the first."
(reconcile-test-with-temp-dirs
("projects/a/.git/" "projects/b/.git/" "projects/c/.git/")
(let ((projects-dir (expand-file-name "projects" test-root))
(code-dir nil)
(org-dir nil))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(dolist (repo '("projects/a" "projects/b" "projects/c"))
(should (test-reconcile-called-with-p
(expand-file-name repo test-root)))))))
(ert-deftest test-reconcile-check-for-open-work-normal-reconciles-org-dir ()
"`org-dir' is reconciled individually (the dir itself, not its children)."
(reconcile-test-with-temp-dirs
("orgdir/.git/")
(let ((projects-dir nil)
(code-dir nil)
(org-dir (expand-file-name "orgdir" test-root)))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (test-reconcile-called-with-p
(expand-file-name "orgdir" test-root))))))
(ert-deftest test-reconcile-check-for-open-work-normal-reconciles-user-emacs-directory ()
"`user-emacs-directory' is always reconciled individually."
(reconcile-test-with-temp-dirs
("emacsdir/.git/")
(let ((projects-dir nil)
(code-dir nil)
(org-dir nil)
(user-emacs-directory (expand-file-name "emacsdir" test-root)))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (test-reconcile-called-with-p
(expand-file-name "emacsdir" test-root))))))
;;; Boundary Cases
(ert-deftest test-reconcile-check-for-open-work-boundary-nil-projects-dir ()
"Nil `projects-dir' doesn't crash; other dirs still process."
(reconcile-test-with-temp-dirs
("code/repo/.git/")
(let ((projects-dir nil)
(code-dir (expand-file-name "code" test-root))
(org-dir nil))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (test-reconcile-called-with-p
(expand-file-name "code/repo" test-root))))))
(ert-deftest test-reconcile-check-for-open-work-boundary-nonexistent-projects-dir ()
"Non-existent `projects-dir' is skipped without error; `code-dir' processes."
(reconcile-test-with-temp-dirs
("code/repo/.git/")
(let ((projects-dir (expand-file-name "does-not-exist" test-root))
(code-dir (expand-file-name "code" test-root))
(org-dir nil))
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (test-reconcile-called-with-p
(expand-file-name "code/repo" test-root))))))
(ert-deftest test-reconcile-check-for-open-work-boundary-empty-dirs-produce-no-calls ()
"Empty `projects-dir' and `code-dir' produce no repo-level reconcile calls."
(reconcile-test-with-temp-dirs
("projects/" "code/")
(let ((projects-dir (expand-file-name "projects" test-root))
(code-dir (expand-file-name "code" test-root))
(org-dir nil)
(user-emacs-directory (expand-file-name "emacs-d" test-root))) ;; non-existent
(test-reconcile-with-mocked-reconcile
(cj/check-for-open-work))
(should (null test-reconcile-calls)))))
;;; Error / Edge Cases
(ert-deftest test-reconcile-check-for-open-work-error-emits-complete-message ()
"Emits the terminal `Complete.' message after iteration."
(reconcile-test-with-temp-dirs
("projects/" "code/")
(let ((projects-dir (expand-file-name "projects" test-root))
(code-dir (expand-file-name "code" test-root))
(org-dir nil)
(messages nil))
(cl-letf (((symbol-function 'cj/reconcile-git-directory) (lambda (_dir)))
((symbol-function 'message)
(lambda (fmt &rest args)
(push (apply #'format fmt args) messages))))
(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
|