doomemacs/core/autoload/test.el

169 lines
7.0 KiB
EmacsLisp
Raw Normal View History

;;; core/autoload/test.el -*- lexical-binding: t; no-byte-compile: t; -*-
(defun doom//run-tests (&optional modules)
"Run all loaded tests, specified by MODULES (a list of module cons cells) or
command line args following a double dash (each arg should be in the
'module/submodule' format).
If neither is available, run all tests in all enabled modules."
2017-11-09 05:52:49 +08:00
(interactive)
2018-05-25 03:15:44 +08:00
(doom//reload-autoloads)
(let (noninteractive)
;; Core libraries aren't fully loaded in a noninteractive session, so we
;; reload it with `noninteractive' set to nil to force them to.
2018-05-25 03:15:44 +08:00
(doom-initialize t)
(doom-initialize-modules t))
(condition-case-unless-debug ex
(let ((target-paths
;; Convert targets (either from MODULES or `argv') into a list of
;; string paths, pointing to the root directory of modules
(cond ((stringp (car modules)) ; command line
(save-match-data
(cl-loop for arg in modules
if (string= arg ":core") collect doom-core-dir
else if (string-match-p "/" arg)
nconc (cl-loop for dir in doom-modules-dirs
collect (expand-file-name arg dir))
else
nconc (cl-loop for dir in doom-modules-dirs
for path = (expand-file-name arg dir)
if (file-directory-p path)
nconc
(cl-remove-if-not
#'file-directory-p
(directory-files path t "^[^.]" t)))
finally do (setq argv nil))))
(modules ; cons-cells given to MODULES
(cl-loop for (module . submodule) in modules
if (doom-module-locate-path module submodule)
collect it))
((append (list doom-core-dir)
(doom-module-load-path))))))
;; Load all the unit test files...
(dolist (path target-paths)
(let ((test-path (expand-file-name "test/" path)))
(when (file-directory-p test-path)
2018-05-25 03:15:44 +08:00
(dolist (test-file (reverse (doom-files-in test-path :match "\\.el$" :full t)))
(load test-file nil :noerror)))))
;; ... then run them
(switch-to-buffer (get-buffer-create "*blank*"))
(if noninteractive
(ert-run-tests-batch-and-exit)
(call-interactively #'ert-run-tests-interactively)))
('error
(lwarn 'doom-test :error
"%s -> %s"
(car ex) (error-message-string ex)))))
2017-10-05 07:22:57 +08:00
;; --- Test helpers -----------------------
(defmacro def-test! (name &rest body)
"Define a namespaced ERT test."
(declare (indent defun) (doc-string 2))
(let (plist)
(while (keywordp (car body))
2018-01-04 08:59:15 +08:00
(push (pop body) plist)
(push (pop body) plist))
(setq plist (reverse plist))
(when-let* ((modes (doom-enlist (plist-get plist :minor-mode))))
(dolist (mode modes)
(setq body `((with-minor-mode! ,mode ,@body)))))
(when-let* ((before (plist-get plist :before)))
(setq body `(,@before ,@body)))
(when-let* ((after (plist-get plist :after)))
(setq body `(,@body @after)))
2017-10-05 07:22:57 +08:00
`(ert-deftest
,(intern (format "%s::%s"
(if (file-in-directory-p load-file-name doom-core-dir)
(format "core/%s" (file-name-base load-file-name))
(replace-regexp-in-string "^.*/modules/\\([^/]+\\)/\\([^/]+\\)/test/" "\\1/\\2:"
(file-name-sans-extension load-file-name)))
name))
()
2018-01-04 08:59:15 +08:00
,(if (plist-get plist :skip)
`(ert-skip ,(plist-get plist :skip))
`(with-temp-buffer
(save-mark-and-excursion
(save-window-excursion
,@body)))))))
2017-10-05 07:22:57 +08:00
(defmacro should-buffer! (initial expected &rest body)
2017-10-05 07:22:57 +08:00
"Test that a buffer with INITIAL text, run BODY, then test it against EXPECTED.
INITIAL will recognize cursor markers in the form {[0-9]}. A {0} marker marks
where the cursor should be after setup. Otherwise, the cursor will be placed at
`point-min'.
EXPECTED will recognize one (optional) cursor marker: {|}, this is the
'expected' location of the cursor after BODY is finished, and will be tested
against."
(declare (indent 2))
`(with-temp-buffer
(cl-loop for line in ',initial
do (insert line "\n"))
(goto-char (point-min))
(let (marker-list)
(save-excursion
(while (re-search-forward "{\\([0-9]\\)}" nil t)
(push (cons (match-string 1)
(set-marker (make-marker) (match-beginning 0)))
marker-list)
(replace-match "" t t))
(if (not marker-list)
(goto-char (point-min))
(sort marker-list
(lambda (m1 m2) (< (marker-position m1)
(marker-position m2))))
(when (equal (caar marker-list) "0")
(goto-char! 0)))
2017-10-05 07:22:57 +08:00
,@body
2018-05-08 21:20:06 +08:00
(let ((result-text (buffer-string))
2017-10-05 07:22:57 +08:00
(point (point))
expected-text)
(with-temp-buffer
(cl-loop for line in ',expected
do (insert line "\n"))
(save-excursion
2018-05-08 21:20:06 +08:00
(goto-char (point-min))
2017-10-05 07:22:57 +08:00
(when (re-search-forward "{|}" nil t)
2018-05-08 21:20:06 +08:00
(replace-match "" t t)
(should (equal (point) point))))
(setq expected-text (buffer-string))
(should (equal expected-text result-text))))))))
2017-10-05 07:22:57 +08:00
(defmacro goto-char! (index)
"Meant to be used with `should-buffer!'. Will move the cursor to one of the
cursor markers. e.g. Go to marker {2} with (goto-char! 2)."
`(goto-char (point! ,index)))
2017-10-05 07:22:57 +08:00
(defmacro point! (index)
"Meant to be used with `should-buffer!'. Returns the position of a cursor
marker. e.g. {2} can be retrieved with (point! 2)."
2017-10-05 07:22:57 +08:00
`(cdr (assoc ,(cond ((numberp index) (number-to-string index))
((symbolp index) (symbol-name index))
((stringp index) index))
marker-list)))
(defmacro with-minor-mode! (mode &rest body)
2018-01-04 03:10:31 +08:00
"Activate a minor mode while in BODY, deactivating it after."
(declare (indent defun))
`(progn (,mode +1)
,@body
(,mode -1)))
2018-01-04 03:10:31 +08:00
(defmacro let-advice! (binds &rest body)
2018-01-04 03:10:31 +08:00
"Temporarily bind advice in BINDS while in BODY.
e.g. (old-fn :before advice-fn)
(old-fn :around advice-fn)"
(declare (indent defun))
`(progn
,@(cl-loop for (target type advice) in binds
collect `(advice-add #',target ,type #',advice))
,@body
,@(cl-loop for (target type advice) in binds
collect `(advice-remove #',target #',advice))))