2017-11-05 01:23:36 +01:00
|
|
|
;;; core/autoload/test.el -*- lexical-binding: t; no-byte-compile: t; -*-
|
2017-04-04 22:18:38 -04:00
|
|
|
|
2017-11-05 01:23:36 +01:00
|
|
|
(defun doom//run-tests (&optional modules)
|
2017-06-24 17:15:22 +02:00
|
|
|
"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-08 22:52:49 +01:00
|
|
|
(interactive)
|
2018-05-20 12:21:13 +02:00
|
|
|
(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.
|
|
|
|
(doom-initialize))
|
|
|
|
(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))))
|
2017-06-24 17:15:22 +02:00
|
|
|
|
2018-05-20 12:21:13 +02:00
|
|
|
(modules ; cons-cells given to MODULES
|
|
|
|
(cl-loop for (module . submodule) in modules
|
|
|
|
if (doom-module-locate-path module submodule)
|
|
|
|
collect it))
|
2017-06-24 17:15:22 +02:00
|
|
|
|
2018-05-20 12:21:13 +02:00
|
|
|
((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)
|
|
|
|
(dolist (test-file (reverse (doom-files-under test-path :match "\\.el$")))
|
|
|
|
(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 01:22:57 +02:00
|
|
|
|
|
|
|
|
|
|
|
;; --- Test helpers -----------------------
|
|
|
|
|
|
|
|
(defmacro def-test! (name &rest body)
|
|
|
|
"Define a namespaced ERT test."
|
|
|
|
(declare (indent defun) (doc-string 2))
|
2017-12-31 11:45:02 -05:00
|
|
|
(let (plist)
|
|
|
|
(while (keywordp (car body))
|
2018-01-03 19:59:15 -05:00
|
|
|
(push (pop body) plist)
|
2017-12-31 11:45:02 -05:00
|
|
|
(push (pop body) plist))
|
|
|
|
(setq plist (reverse plist))
|
|
|
|
(when-let* ((modes (doom-enlist (plist-get plist :minor-mode))))
|
|
|
|
(dolist (mode modes)
|
2018-03-27 02:52:30 -04:00
|
|
|
(setq body `((with-minor-mode! ,mode ,@body)))))
|
2017-12-31 11:45:02 -05:00
|
|
|
(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 01:22:57 +02:00
|
|
|
`(ert-deftest
|
2018-02-16 02:02:58 -05:00
|
|
|
,(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))
|
2017-12-31 11:45:02 -05:00
|
|
|
()
|
2018-01-03 19:59:15 -05: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 01:22:57 +02:00
|
|
|
|
2018-03-27 02:52:30 -04:00
|
|
|
(defmacro should-buffer! (initial expected &rest body)
|
2017-10-05 01:22:57 +02: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")
|
2018-03-27 02:52:30 -04:00
|
|
|
(goto-char! 0)))
|
2017-10-05 01:22:57 +02:00
|
|
|
,@body
|
2018-05-08 15:20:06 +02:00
|
|
|
(let ((result-text (buffer-string))
|
2017-10-05 01:22:57 +02:00
|
|
|
(point (point))
|
|
|
|
expected-text)
|
|
|
|
(with-temp-buffer
|
|
|
|
(cl-loop for line in ',expected
|
|
|
|
do (insert line "\n"))
|
|
|
|
(save-excursion
|
2018-05-08 15:20:06 +02:00
|
|
|
(goto-char (point-min))
|
2017-10-05 01:22:57 +02:00
|
|
|
(when (re-search-forward "{|}" nil t)
|
2018-05-08 15:20:06 +02:00
|
|
|
(replace-match "" t t)
|
|
|
|
(should (equal (point) point))))
|
|
|
|
(setq expected-text (buffer-string))
|
|
|
|
(should (equal expected-text result-text))))))))
|
2017-10-05 01:22:57 +02:00
|
|
|
|
2018-03-27 02:52:30 -04: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 01:22:57 +02:00
|
|
|
|
2018-03-27 02:52:30 -04: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 01:22:57 +02:00
|
|
|
`(cdr (assoc ,(cond ((numberp index) (number-to-string index))
|
|
|
|
((symbolp index) (symbol-name index))
|
|
|
|
((stringp index) index))
|
|
|
|
marker-list)))
|
2017-12-31 11:45:02 -05:00
|
|
|
|
2018-03-27 02:52:30 -04:00
|
|
|
(defmacro with-minor-mode! (mode &rest body)
|
2018-01-03 14:10:31 -05:00
|
|
|
"Activate a minor mode while in BODY, deactivating it after."
|
2017-12-31 11:45:02 -05:00
|
|
|
(declare (indent defun))
|
|
|
|
`(progn (,mode +1)
|
|
|
|
,@body
|
|
|
|
(,mode -1)))
|
2018-01-03 14:10:31 -05:00
|
|
|
|
2018-03-27 02:52:30 -04:00
|
|
|
(defmacro let-advice! (binds &rest body)
|
2018-01-03 14:10:31 -05: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))))
|