summaryrefslogtreecommitdiff
path: root/tests/test-testutil-filesystem-directory-entries.el
blob: 7ddbf426a6549abc799b0d621121681e99280a67 (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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
;;; test-testutil-filesystem-directory-entries.el ---  -*- coding: utf-8; lexical-binding: t; -*-
;;
;; Author: Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;; ERT tests for testutil-filesystem.el
;; Tests  cj/list-directory-recursive and it's helper function cj/get--directory-entries.
;;
;;; Code:

(require 'ert)
(require 'f)

;; load test directory
(add-to-list 'load-path (concat user-emacs-directory "tests/"))
(require 'testutil-general)    ;; helper functions
(require 'testutil-filesystem) ;; file under test

(defun cj/test--setup ()
  "Create the test base directory using `cj/create-test-base-dir'."
  (cj/create-test-base-dir))

(defun cj/test--teardown ()
  "Remove the test base directory using `cj/delete-test-base-dir'."
  (cj/delete-test-base-dir))

;;; ---------------------- CJ/GET--DIRECTORY-ENTRIES TESTS ----------------------
;;;; Normal Case Tests

(ert-deftest test-normal-one-file ()
  "Test a single file at the base directory."
  (cj/test--setup)
  (unwind-protect
	  (progn
		(cj/create-directory-or-file-ensuring-parents "file.txt" "Test file")
		(let
			;; get paths to all files
			((entries (cj/get--directory-entries cj/test-base-dir)))
		  ;; check for files of different types and in subdirectories
		  (should (cl-some (lambda (e) (string= (f-filename e) "file.txt")) entries))))
	(cj/test--teardown)))

(ert-deftest test-normal-includes-subdirectories-but-no-contents ()
  "Test that we do include subdirectories themselves."
  (cj/test--setup)
  (unwind-protect
	  (progn
		;; create yoru test assets
		(cj/create-directory-or-file-ensuring-parents "file1.org" "Test file 1" t)
		(cj/create-directory-or-file-ensuring-parents "subdir/file2.org" "Nested file")
		;; get paths to all files
		(let ((entries (cj/get--directory-entries cj/test-base-dir)))
		  (should (cl-some (lambda (e) (and (file-directory-p e)
									   (string= (f-filename e) "subdir"))) entries))
		  (should-not (cl-some (lambda (e) (string= (f-filename e) "file2.org")) entries))))
	(cj/test--teardown)))

(ert-deftest test-normal-excludes-hidden-by-default ()
  "Test that hidden files (i.e.,begin with a dot) are excluded by default.
Asserts no subdirectories or hidden files or visible files in hidden subdirectories are returned."
  (cj/test--setup)
  (unwind-protect
	  (progn
		;; create your test assets
		(cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
		;; get paths to all files
		(let ((entries (cj/get--directory-entries cj/test-base-dir)))
		  ;; should not see hidden file
		  (should-not (cl-some (lambda (e) (string= (f-filename e) ".hiddenfile")) entries))))
	(cj/test--teardown)))

(ert-deftest test-normal-includes-hidden-with-flag ()
  "Non-nil means hidden files are included."
  (cj/test--setup)
  (unwind-protect
	  (progn
		;; create your test assets
		(cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
		;; get paths to all files passing in t to reveal hidden files
		(let ((entries (cj/get--directory-entries cj/test-base-dir t)))
		  ;; should not see hidden file
		  (should (cl-some (lambda (e) (string= (f-filename e) ".hiddenfile")) entries))))
	(cj/test--teardown)))

;;
;;;; Boundary Cases

(ert-deftest test-boundary-empty-directory ()
  "Test an empty directory returns empty list."
  (cj/test--setup)
  (unwind-protect
	  (let ((entries (cj/get--directory-entries cj/test-base-dir)))
		(should (equal entries nil)))
	(cj/test--teardown)))

(ert-deftest test-boundary-files-with-unusual-names ()
  "Test files with unusual names."
  (cj/test--setup)
  (unwind-protect
	  (progn
		(cj/create-directory-or-file-ensuring-parents "file with spaces.org" "content")
		(cj/create-directory-or-file-ensuring-parents "unicode-ß₄©.org" "content")  ;; Direct Unicode chars
		;; Or use proper escape sequences:
		;; (cj/create-directory-or-file-ensuring-parents "unicode-\u00DF\u2074\u00A9.org" "content")
		(let ((entries (cj/get--directory-entries cj/test-base-dir)))
		  (should (cl-some (lambda (e) (string= (f-filename e) "file with spaces.org")) entries))
		  (should (cl-some (lambda (e) (string= (f-filename e) "unicode-ß₄©.org")) entries))))
	(cj/test--teardown)))

;;;; Error Cases

(ert-deftest test-error-nonexistent-directory ()
  "Test calling on nonexistent directory returns nil or error handled."
  (should-error (cj/get--directory-entries "/path/does/not/exist")))
										;
(ert-deftest test-error-not-a-directory-path ()
  "Test calling on a file path signals error."
  (cj/test--setup)
  (unwind-protect
	  (let ((filepath (cj/create-directory-or-file-ensuring-parents "file.txt" "data")))
		(should-error (cj/get--directory-entries filepath)))
	(cj/test--teardown)))

(ert-deftest test-error-permission-denied ()
  "Test directory with no permission signals error or returns nil."
  (cj/test--setup)
  (unwind-protect
	  (let ((dir (expand-file-name "noperm" cj/test-base-dir)))
		(cj/create-directory-or-file-ensuring-parents "noperm/file2.org" "Nested file")
		(let ((original-mode (file-modes dir))) ; Save original permissions
		  (set-file-modes dir #o000) ; Remove all permissions
		  (unwind-protect
			  (should-error (cj/get--directory-entries dir))
			(set-file-modes dir original-mode)))) ; Restore permissions - extra paren here
	(cj/test--teardown)))

;;; --------------------- CJ/LIST-DIRECTORY-RECURSIVE TESTS ---------------------
;;;; Normal Cases

(ert-deftest test-normal-single-file-at-root ()
  "Test the normal base case: one single file at the root."
  (cj/test--setup)
  (unwind-protect
	  (progn
		(cj/create-directory-or-file-ensuring-parents "file.txt" "Content")
		(let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
		  (should  (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file.txt")) file-infos))))
	(cj/test--teardown)))

(ert-deftest test-normal-multiple-files-at-root ()
  "Test finding multiple files at the root directory."
  (cj/test--setup)
  (unwind-protect
	  (cj/create-directory-or-file-ensuring-parents "file1.txt" "Content in File 1")
	(cj/create-directory-or-file-ensuring-parents "file2.org" "Content in File 2")
	(cj/create-directory-or-file-ensuring-parents "file3.md" "Content in File 3")
	(let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
	  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.txt")) file-infos))
	  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file2.org")) file-infos))
	  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file3.md")) file-infos)))
	(cj/test--teardown)))

(ert-deftest test-normal-multiple-files-in-subdirectories ()
  "Test finding multiple files at the root directory."
  (cj/test--setup)
  (unwind-protect
	  (cj/create-directory-or-file-ensuring-parents "one/file1.txt" "Content in File 1")
	(cj/create-directory-or-file-ensuring-parents "two/file2.org" "Content in File 2")
	(cj/create-directory-or-file-ensuring-parents "three/file3.md" "Content in File 3")
	(let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
	  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.txt")) file-infos))
	  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file2.org")) file-infos))
	  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file3.md")) file-infos)))
	(cj/test--teardown)))

(ert-deftest test-recursive-excludes-hidden-by-default ()
  "Test that hidden files are excluded by default in recursive listing.
Verify that files beginning with a dot, hidden directories, and files
within hidden directories are all excluded when include-hidden is nil."
  (cj/test--setup)
  (unwind-protect
	  (progn
		;; Create test assets including hidden files at various levels
		(cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
		(cj/create-directory-or-file-ensuring-parents ".hiddendir/visible-in-hidden.txt" "File in hidden dir")
		(cj/create-directory-or-file-ensuring-parents "visible/normal.txt" "Normal file")
		(cj/create-directory-or-file-ensuring-parents "visible/.hidden-in-visible.txt" "Hidden in visible dir")

		;; Get all files recursively (default excludes hidden)
		(let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
		  ;; Should not see .hiddenfile at root
		  (should-not (cl-some (lambda (fi)
								 (string= (f-filename (plist-get fi :path)) ".hiddenfile"))
							   file-infos))
		  ;; Should not see .hiddendir directory
		  (should-not (cl-some (lambda (fi)
								 (string= (f-filename (plist-get fi :path)) ".hiddendir"))
							   file-infos))
		  ;; Should not see files inside hidden directory
		  (should-not (cl-some (lambda (fi)
								 (string= (f-filename (plist-get fi :path)) "visible-in-hidden.txt"))
							   file-infos))
		  ;; Should not see hidden file in visible directory
		  (should-not (cl-some (lambda (fi)
								 (string= (f-filename (plist-get fi :path)) ".hidden-in-visible.txt"))
							   file-infos))
		  ;; Should see normal visible file
		  (should (cl-some (lambda (fi)
							 (string= (f-filename (plist-get fi :path)) "normal.txt"))
						   file-infos))))
	(cj/test--teardown)))

(ert-deftest test-recursive-includes-hidden-with-flag ()
  "Non-nil means hidden files are included.
Verifies that files beginning with a dot, hidden directories, and files
within hidden directories are all included when include-hidden is t."
  (cj/test--setup)
  (unwind-protect
	  (progn
		;; Create test assets including hidden files at various levels
		(cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
		(cj/create-directory-or-file-ensuring-parents ".hiddendir/visible-in-hidden.txt" "File in hidden dir")
		(cj/create-directory-or-file-ensuring-parents "visible/normal.txt" "Normal file")
		(cj/create-directory-or-file-ensuring-parents "visible/.hidden-in-visible.txt" "Hidden in visible dir")

		;; Get all files recursively with include-hidden = t
		(let ((file-infos (cj/list-directory-recursive cj/test-base-dir t)))
		  ;; Should see .hiddenfile at root
		  (should (cl-some (lambda (fi)
							 (string= (f-filename (plist-get fi :path)) ".hiddenfile")) file-infos))
		  ;; Should see .hiddendir directory
		  (should (cl-some (lambda (fi) (and (plist-get fi :directory)
										(string= (f-filename (plist-get fi :path)) ".hiddendir"))) file-infos))
		  ;; Should see files inside hidden directory
		  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "visible-in-hidden.txt")) file-infos))
		  ;; Should see hidden file in visible directory
		  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) ".hidden-in-visible.txt")) file-infos))
		  ;; Should still see normal visible file
		  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "normal.txt")) file-infos))))
	(cj/test--teardown)))

(ert-deftest test-normal-deeply-nested-structure ()
  "Tests with deeply nested directory trees."
  (cj/test--setup)
  (unwind-protect
	  (progn
		(cj/create-directory-or-file-ensuring-parents
		 "one/two/three/four/five/six/seven/eight/nine/ten/eleven/twelve/13.txt" "thirteen")
		(cj/create-directory-or-file-ensuring-parents
		 "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/thirty.txt" "30")
		(let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
		  ;; validate the files
		  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "13.txt")) file-infos))
		  (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "thirty.txt")) file-infos))))
	(cj/test--teardown)))

(ert-deftest test-normal-only-directory-entries ()
  "Tests with deeply nested directory trees without files."
  (cj/test--setup)
  (unwind-protect
	  (progn
		(cj/create-directory-or-file-ensuring-parents
		 "one/two/three/four/five/six/seven/eight/nine/ten/eleven/twelve/thirteen/")
		(cj/create-directory-or-file-ensuring-parents
		 "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/")
		(let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
		  ;; validate the directories
		  (should (cl-some (lambda (fi)
							 (and (string= (f-filename (plist-get fi :path)) "thirteen")
								  (plist-get fi :directory)
								  (file-directory-p (plist-get fi :path))))
						   file-infos))

		  (should (cl-some (lambda (fi)
							 (and (string= (f-filename (plist-get fi :path)) "30")
								  (plist-get fi :directory)
								  (file-directory-p (plist-get fi :path))))
						   file-infos))))
	(cj/test--teardown)))

;; 5. =test-normal-filter-by-extension= - Filter predicate correctly filters .org files


;; 6. =test-normal-filter-by-size= - Filter predicate filters files > 1KB
;; 7. =test-normal-filter-excludes-directories= - Filter can exclude directories themselves
;; 8. =test-normal-max-depth-one= - Respects max-depth=1 (only immediate children)
;; 9. =test-normal-max-depth-three= - Respects max-depth=3 limit
;; 11. =test-normal-executable-files= - Correctly identifies executable files
;; 12. =test-normal-file-info-plist-structure= - Verifies correct plist keys/values returned

;;;; Boundary Cases
;; 1. =test-boundary-empty-directory= - Empty directory returns empty list
;; 2. =test-boundary-single-empty-subdirectory= - Directory with only empty subdirectory
;; 3. =test-boundary-unicode-filenames= - Files with unicode characters (emoji, Chinese, etc.)
;; 4. =test-boundary-spaces-in-names= - Files/dirs with spaces in names
;; 5. =test-boundary-special-characters= - Files with special chars (@#$%^&*()_+)
;; 6. =test-boundary-very-long-filename= - File with 255 character name
;; 8. =test-boundary-many-files= - Directory with 1000+ files
;; 9. =test-boundary-max-depth-zero= - max-depth=0 (unlimited) works correctly
;; 10. =test-boundary-symlinks= - How it handles symbolic links
;; 11. =test-boundary-filter-returns-all-nil= - Filter that rejects everything
;; 12. =test-boundary-filter-returns-all-true= - Filter that accepts everything

;;;; Error Cases
;; 1. =test-error-nonexistent-path= - Path that doesn't exist
;; 2. =test-error-file-not-directory= - PATH is a file, not directory
;; 3. =test-error-permission-denied= - Directory without read permissions
;; 4. =test-error-permission-denied-subdirectory= - Subdirectory without permissions
;; 5. =test-error-invalid-max-depth= - Negative max-depth value
;; 6. =test-error-filter-predicate-errors= - Filter function that throws error
;; 7. =test-error-circular-symlinks= - Circular symbolic link reference
;; 8. =test-error-path-outside-home= - Attempt to access system directories (if restricted)
;; 9. =test-error-nil-path= - PATH is nil
;; 10. =test-error-empty-string-path= - PATH is empty string

(provide 'test-testutil-filesystem-directory-entries)
;;; test-testutil-filesystem-directory-entries.el ends here.