aboutsummaryrefslogtreecommitdiff
path: root/tests/testutil-general.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-03 19:59:42 -0500
committerCraig Jennings <c@cjennings.net>2026-05-03 19:59:42 -0500
commitdeff663d480afb23e47f0d0fd834d2c0959d0b00 (patch)
tree0d6ca6f4c429e3623ae6b69d19ff1716852856f2 /tests/testutil-general.el
parent3e9499f62c4fb621ec234a0cf5cee51eb2cf32c0 (diff)
downloaddotemacs-deff663d480afb23e47f0d0fd834d2c0959d0b00.tar.gz
dotemacs-deff663d480afb23e47f0d0fd834d2c0959d0b00.zip
fix: make test scratch paths sandbox-friendly
`tests/testutil-general.el` hard-coded `~/.temp-emacs-tests/` as the test root. That worked locally but blew up under sandboxed `make` runs and CI environments that can't write outside the repo or `/tmp`. A clean sandbox `make test` run reported 32 failing test files purely from the home-directory write attempt, even though the same suite passed when run with normal write permission. I rewrote `cj/test-base-dir` to honor `CJ_EMACS_TEST_DIR` if set, otherwise create a unique directory under `temporary-file-directory` via `make-temp-file`. So sandbox and CI paths just work, and a stable local debug root is still one env-var away. I also tightened the path-containment checks. The old `(string-prefix-p base fullpath)` was a textual hack. Relative paths and weird trailing slashes could fool it. I extracted `cj/test--assert-inside-base` using `file-in-directory-p`, which is the proper API. While I was there, I added `cj/test--safe-base-dir-p` so `cj/delete-test-base-dir` refuses to recursively wipe `/`, `~/`, `temporary-file-directory`, `user-emacs-directory`, `default-directory`, or any path of length under six characters. That guards against an env-var typo or a misaligned `let` binding accidentally deleting something important. I updated the Makefile's `clean-tests` target to nuke the new `$TMPDIR/cj-emacs-tests-*` pattern plus an explicit `CJ_EMACS_TEST_DIR` (if set) and the legacy `~/.temp-emacs-tests` directory. I added `tests/test-testutil-general.el` with five tests: default base lives under `temporary-file-directory`, env override resolves correctly, parent-escape paths are rejected, broad roots are refused for deletion, and a specific selected root is cleaned cleanly.
Diffstat (limited to 'tests/testutil-general.el')
-rw-r--r--tests/testutil-general.el44
1 files changed, 32 insertions, 12 deletions
diff --git a/tests/testutil-general.el b/tests/testutil-general.el
index b7222d1a..52b8a8ea 100644
--- a/tests/testutil-general.el
+++ b/tests/testutil-general.el
@@ -6,9 +6,9 @@
;; This library provides general helper functions and constants for managing
;; test directories and files across test suites.
;;
-;; It establishes a user-local hidden directory as the root for all test assets,
-;; provides utilities to create this directory safely, create temporary files
-;; and subdirectories within it, and clean up after tests.
+;; It establishes a sandbox- and CI-friendly directory as the root for all test
+;; assets, provides utilities to create this directory safely, create temporary
+;; files and subdirectories within it, and clean up after tests.
;;
;; This library should be required by test suites to ensure consistent,
;; reliable, and isolated file-system resources.
@@ -16,12 +16,32 @@
;;; Code:
(defconst cj/test-base-dir
- (expand-file-name "~/.temp-emacs-tests/")
+ (file-name-as-directory
+ (expand-file-name
+ (or (getenv "CJ_EMACS_TEST_DIR")
+ (make-temp-file "cj-emacs-tests-" t))))
"Base directory for all Emacs test files and directories.
-All test file-system artifacts should be created under this hidden
-directory in the user's home. This avoids relying on ephemeral system
-directories like /tmp and reduces flaky test failures caused by external
-cleanup.")
+All test file-system artifacts should be created under this directory.
+Set CJ_EMACS_TEST_DIR to use a stable local directory while debugging.
+The default is a unique directory under `temporary-file-directory' so
+tests work in sandboxes, CI, and parallel Emacs test processes without
+home-directory write permission.")
+
+(defun cj/test--safe-base-dir-p (dir)
+ "Return non-nil when DIR is specific enough to delete recursively."
+ (let* ((expanded (file-name-as-directory (expand-file-name dir)))
+ (forbidden (mapcar
+ (lambda (path)
+ (file-name-as-directory (expand-file-name path)))
+ (list "/" "~/" temporary-file-directory
+ user-emacs-directory default-directory))))
+ (and (not (member expanded forbidden))
+ (> (length (directory-file-name expanded)) 5))))
+
+(defun cj/test--assert-inside-base (path base)
+ "Signal an error unless PATH is inside BASE."
+ (unless (file-in-directory-p path base)
+ (error "Path %s is outside base test directory %s" path base)))
(defun cj/create-test-base-dir ()
"Create the test base directory `cj/test-base-dir' if it does not exist.
@@ -39,8 +59,7 @@ Error if DIRPATH exists already.
Ensure DIRPATH is within `cj/test-base-dir`."
(let* ((base (file-name-as-directory cj/test-base-dir))
(fullpath (expand-file-name dirpath base)))
- (unless (string-prefix-p base fullpath)
- (error "Directory path %s is outside base test directory %s" fullpath base))
+ (cj/test--assert-inside-base fullpath base)
(when (file-exists-p fullpath)
(error "Directory path already exists: %s" fullpath))
(make-directory fullpath t)
@@ -55,8 +74,7 @@ Ensure FILEPATH is within `cj/test-base-dir`."
(let* ((base (file-name-as-directory cj/test-base-dir))
(fullpath (expand-file-name filepath base))
(parent-dir (file-name-directory fullpath)))
- (unless (string-prefix-p base fullpath)
- (error "File path %s is outside base test directory %s" fullpath base))
+ (cj/test--assert-inside-base fullpath base)
(when (file-exists-p fullpath)
(error "File already exists: %s" fullpath))
(unless (file-directory-p parent-dir)
@@ -114,6 +132,8 @@ so deletion is not blocked by permissions.
After deletion, verifies that the directory no longer exists.
Signals an error if the directory still exists after deletion attempt."
(let ((dir (file-name-as-directory cj/test-base-dir)))
+ (unless (cj/test--safe-base-dir-p dir)
+ (error "Refusing to delete unsafe test base directory %s" dir))
(when (file-directory-p dir)
(cj/fix-permissions-recursively dir)
(delete-directory dir t))