diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
| commit | 092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch) | |
| tree | ea81999b8442246c978b364dd90e8c752af50db5 /tests/test-testutil-filesystem-directory-entries.el | |
changing repositories
Diffstat (limited to 'tests/test-testutil-filesystem-directory-entries.el')
| -rw-r--r-- | tests/test-testutil-filesystem-directory-entries.el | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/tests/test-testutil-filesystem-directory-entries.el b/tests/test-testutil-filesystem-directory-entries.el new file mode 100644 index 00000000..7ddbf426 --- /dev/null +++ b/tests/test-testutil-filesystem-directory-entries.el @@ -0,0 +1,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. |
