diff options
| author | Ken Raeburn <raeburn@raeburn.org> | 2015-11-01 01:42:21 -0400 |
|---|---|---|
| committer | Ken Raeburn <raeburn@raeburn.org> | 2015-11-01 01:42:21 -0400 |
| commit | 39372e1a1032521be74575bb06f95a3898fbae30 (patch) | |
| tree | 754bd242a23d2358ea116126fcb0a629947bd9ec /lisp/emacs-lisp/ert.el | |
| parent | 6a3121904d76e3b2f63007341d48c5c1af55de80 (diff) | |
| parent | e11aaee266da52937a3a031cb108fe13f68958c3 (diff) | |
| download | emacs-39372e1a1032521be74575bb06f95a3898fbae30.tar.gz | |
merge from trunk
Diffstat (limited to 'lisp/emacs-lisp/ert.el')
| -rw-r--r-- | lisp/emacs-lisp/ert.el | 240 |
1 files changed, 184 insertions, 56 deletions
diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el index 98576687f3d..21c1f1be394 100644 --- a/lisp/emacs-lisp/ert.el +++ b/lisp/emacs-lisp/ert.el @@ -1,6 +1,6 @@ ;;; ert.el --- Emacs Lisp Regression Testing -*- lexical-binding: t -*- -;; Copyright (C) 2007-2008, 2010-2013 Free Software Foundation, Inc. +;; Copyright (C) 2007-2008, 2010-2015 Free Software Foundation, Inc. ;; Author: Christian Ohler <ohler@gnu.org> ;; Keywords: lisp, tools @@ -34,14 +34,17 @@ ;; `ert-run-tests-batch-and-exit' for non-interactive use. ;; ;; The body of `ert-deftest' forms resembles a function body, but the -;; additional operators `should', `should-not' and `should-error' are -;; available. `should' is similar to cl's `assert', but signals a -;; different error when its condition is violated that is caught and -;; processed by ERT. In addition, it analyzes its argument form and -;; records information that helps debugging (`assert' tries to do -;; something similar when its second argument SHOW-ARGS is true, but -;; `should' is more sophisticated). For information on `should-not' -;; and `should-error', see their docstrings. +;; additional operators `should', `should-not', `should-error' and +;; `skip-unless' are available. `should' is similar to cl's `assert', +;; but signals a different error when its condition is violated that +;; is caught and processed by ERT. In addition, it analyzes its +;; argument form and records information that helps debugging +;; (`assert' tries to do something similar when its second argument +;; SHOW-ARGS is true, but `should' is more sophisticated). For +;; information on `should-not' and `should-error', see their +;; docstrings. `skip-unless' skips the test immediately without +;; processing further, this is useful for checking the test +;; environment (like availability of features, external binaries, etc). ;; ;; See ERT's info manual as well as the docstrings for more details. ;; To compile the manual, run `makeinfo ert.texinfo' in the ERT @@ -61,7 +64,7 @@ (require 'ewoc) (require 'find-func) (require 'help) - +(require 'pp) ;;; UI customization options. @@ -174,8 +177,8 @@ and the body." BODY is evaluated as a `progn' when the test is run. It should signal a condition on failure or just return if the test passes. -`should', `should-not' and `should-error' are useful for -assertions in BODY. +`should', `should-not', `should-error' and `skip-unless' are +useful for assertions in BODY. Use `ert' to run tests interactively. @@ -184,7 +187,7 @@ using :expected-result. See `ert-test-result-type-p' for a description of valid values for RESULT-TYPE. \(fn NAME () [DOCSTRING] [:expected-result RESULT-TYPE] \ -\[:tags '(TAG...)] BODY...)" +[:tags '(TAG...)] BODY...)" (declare (debug (&define :name test name sexp [&optional stringp] [&rest keywordp sexp] def-body)) @@ -200,7 +203,7 @@ description of valid values for RESULT-TYPE. (tags nil tags-supplied-p)) body) (ert--parse-keys-and-body docstring-keys-and-body) - `(progn + `(cl-macrolet ((skip-unless (form) `(ert--skip-unless ,form))) (ert-set-test ',name (make-ert-test :name ',name @@ -237,6 +240,7 @@ description of valid values for RESULT-TYPE. (define-error 'ert-test-failed "Test failed") +(define-error 'ert-test-skipped "Test skipped") (defun ert-pass () "Terminate the current test and mark it passed. Does not return." @@ -247,6 +251,11 @@ description of valid values for RESULT-TYPE. DATA is displayed to the user and should state the reason of the failure." (signal 'ert-test-failed (list data))) +(defun ert-skip (data) + "Terminate the current test and mark it skipped. Does not return. +DATA is displayed to the user and should state the reason for skipping." + (signal 'ert-test-skipped (list data))) + ;;; The `should' macros. @@ -260,7 +269,7 @@ DATA is displayed to the user and should state the reason of the failure." (defun ert--special-operator-p (thing) "Return non-nil if THING is a symbol naming a special operator." (and (symbolp thing) - (let ((definition (indirect-function thing t))) + (let ((definition (indirect-function thing))) (and (subrp definition) (eql (cdr (subr-arity definition)) 'unevalled))))) @@ -425,6 +434,15 @@ failed." (list :fail-reason "did not signal an error"))))))))) +(cl-defmacro ert--skip-unless (form) + "Evaluate FORM. If it returns nil, skip the current test. +Errors during evaluation are caught and handled like nil." + (declare (debug t)) + (ert--expand-should `(skip-unless ,form) form + (lambda (inner-form form-description-form _value-var) + `(unless (ignore-errors ,inner-form) + (ert-skip ,form-description-form))))) + ;;; Explanation of `should' failures. @@ -644,6 +662,7 @@ and is displayed in front of the value of MESSAGE-FORM." (infos (cl-assert nil))) (cl-defstruct (ert-test-quit (:include ert-test-result-with-condition))) (cl-defstruct (ert-test-failed (:include ert-test-result-with-condition))) +(cl-defstruct (ert-test-skipped (:include ert-test-result-with-condition))) (cl-defstruct (ert-test-aborted-with-non-local-exit (:include ert-test-result))) @@ -728,6 +747,7 @@ run. ARGS are the arguments to `debugger'." (let* ((condition (car more-debugger-args)) (type (cl-case (car condition) ((quit) 'quit) + ((ert-test-skipped) 'skipped) (otherwise 'failed))) (backtrace (ert--record-backtrace)) (infos (reverse ert--infos))) @@ -737,6 +757,10 @@ run. ARGS are the arguments to `debugger'." (make-ert-test-quit :condition condition :backtrace backtrace :infos infos)) + (skipped + (make-ert-test-skipped :condition condition + :backtrace backtrace + :infos infos)) (failed (make-ert-test-failed :condition condition :backtrace backtrace @@ -785,7 +809,7 @@ This mainly sets up debugger-related bindings." "Immediately truncate *Messages* buffer according to `message-log-max'. This can be useful after reducing the value of `message-log-max'." - (with-current-buffer (get-buffer-create "*Messages*") + (with-current-buffer (messages-buffer) ;; This is a reimplementation of this part of message_dolog() in xdisp.c: ;; if (NATNUMP (Vmessage_log_max)) ;; { @@ -798,7 +822,8 @@ This can be useful after reducing the value of `message-log-max'." (end (save-excursion (goto-char (point-max)) (forward-line (- message-log-max)) - (point)))) + (point))) + (inhibit-read-only t)) (delete-region begin end))))) (defvar ert--running-tests nil @@ -818,7 +843,7 @@ Returns the result and stores it in ERT-TEST's `most-recent-result' slot." (setf (ert-test-most-recent-result ert-test) nil) (cl-block error (let ((begin-marker - (with-current-buffer (get-buffer-create "*Messages*") + (with-current-buffer (messages-buffer) (point-max-marker)))) (unwind-protect (let ((info (make-ert--test-execution-info @@ -837,7 +862,7 @@ Returns the result and stores it in ERT-TEST's `most-recent-result' slot." (ert--run-test-internal info)) (let ((result (ert--test-execution-info-result info))) (setf (ert-test-result-messages result) - (with-current-buffer (get-buffer-create "*Messages*") + (with-current-buffer (messages-buffer) (buffer-substring begin-marker (point-max)))) (ert--force-message-log-buffer-truncation) (setq should-form-accu (nreverse should-form-accu)) @@ -861,11 +886,11 @@ Valid result types: nil -- Never matches. t -- Always matches. -:failed, :passed -- Matches corresponding results. -\(and TYPES...\) -- Matches if all TYPES match. -\(or TYPES...\) -- Matches if some TYPES match. -\(not TYPE\) -- Matches if TYPE does not match. -\(satisfies PREDICATE\) -- Matches if PREDICATE returns true when called with +:failed, :passed, :skipped -- Matches corresponding results. +\(and TYPES...) -- Matches if all TYPES match. +\(or TYPES...) -- Matches if some TYPES match. +\(not TYPE) -- Matches if TYPE does not match. +\(satisfies PREDICATE) -- Matches if PREDICATE returns true when called with RESULT." ;; It would be easy to add `member' and `eql' types etc., but I ;; haven't bothered yet. @@ -874,6 +899,7 @@ t -- Always matches. ((member t) t) ((member :failed) (ert-test-failed-p result)) ((member :passed) (ert-test-passed-p result)) + ((member :skipped) (ert-test-skipped-p result)) (cons (cl-destructuring-bind (operator &rest operands) result-type (cl-ecase operator @@ -898,7 +924,9 @@ t -- Always matches. (defun ert-test-result-expected-p (test result) "Return non-nil if TEST's expected result type matches RESULT." - (ert-test-result-type-p result (ert-test-expected-result-type test))) + (or + (ert-test-result-type-p result :skipped) + (ert-test-result-type-p result (ert-test-expected-result-type test)))) (defun ert-select-tests (selector universe) "Return a list of tests that match SELECTOR. @@ -918,7 +946,7 @@ a test -- (i.e., an object of the ert-test data-type) Selects that test. a symbol -- Selects the test that the symbol names, errors if none. \(member TESTS...) -- Selects the elements of TESTS, a list of tests or symbols naming tests. -\(eql TEST\) -- Selects TEST, a test or a symbol naming a test. +\(eql TEST) -- Selects TEST, a test or a symbol naming a test. \(and SELECTORS...) -- Selects the tests that match all SELECTORS. \(or SELECTORS...) -- Selects the tests that match any of the SELECTORS. \(not SELECTOR) -- Selects all tests that do not match SELECTOR. @@ -971,7 +999,8 @@ contained in UNIVERSE." (list (cl-remove-if-not (lambda (test) (and (ert-test-name test) (string-match selector - (ert-test-name test)))) + (symbol-name + (ert-test-name test))))) universe)))) (ert-test (list selector)) (symbol @@ -1084,6 +1113,7 @@ contained in UNIVERSE." (passed-unexpected 0) (failed-expected 0) (failed-unexpected 0) + (skipped 0) (start-time nil) (end-time nil) (aborted-p nil) @@ -1102,10 +1132,15 @@ contained in UNIVERSE." (+ (ert--stats-passed-unexpected stats) (ert--stats-failed-unexpected stats))) +(defun ert-stats-skipped (stats) + "Number of tests in STATS that have skipped." + (ert--stats-skipped stats)) + (defun ert-stats-completed (stats) "Number of tests in STATS that have run so far." (+ (ert-stats-completed-expected stats) - (ert-stats-completed-unexpected stats))) + (ert-stats-completed-unexpected stats) + (ert-stats-skipped stats))) (defun ert-stats-total (stats) "Number of tests in STATS, regardless of whether they have run yet." @@ -1137,6 +1172,8 @@ Also changes the counters in STATS to match." (cl-incf (ert--stats-passed-expected stats) d)) (ert-test-failed (cl-incf (ert--stats-failed-expected stats) d)) + (ert-test-skipped + (cl-incf (ert--stats-skipped stats) d)) (null) (ert-test-aborted-with-non-local-exit) (ert-test-quit)) @@ -1145,6 +1182,8 @@ Also changes the counters in STATS to match." (cl-incf (ert--stats-passed-unexpected stats) d)) (ert-test-failed (cl-incf (ert--stats-failed-unexpected stats) d)) + (ert-test-skipped + (cl-incf (ert--stats-skipped stats) d)) (null) (ert-test-aborted-with-non-local-exit) (ert-test-quit))))) @@ -1239,6 +1278,7 @@ EXPECTEDP specifies whether the result was expected." (let ((s (cl-etypecase result (ert-test-passed ".P") (ert-test-failed "fF") + (ert-test-skipped "sS") (null "--") (ert-test-aborted-with-non-local-exit "aA") (ert-test-quit "qQ")))) @@ -1251,6 +1291,7 @@ EXPECTEDP specifies whether the result was expected." (let ((s (cl-etypecase result (ert-test-passed '("passed" "PASSED")) (ert-test-failed '("failed" "FAILED")) + (ert-test-skipped '("skipped" "SKIPPED")) (null '("unknown" "UNKNOWN")) (ert-test-aborted-with-non-local-exit '("aborted" "ABORTED")) (ert-test-quit '("quit" "QUIT"))))) @@ -1259,7 +1300,8 @@ EXPECTEDP specifies whether the result was expected." (defun ert--pp-with-indentation-and-newline (object) "Pretty-print OBJECT, indenting it to the current column of point. Ensures a final newline is inserted." - (let ((begin (point))) + (let ((begin (point)) + (pp-escape-newlines nil)) (pp object (current-buffer)) (unless (bolp) (insert "\n")) (save-excursion @@ -1279,7 +1321,7 @@ RESULT must be an `ert-test-result-with-condition'." (unwind-protect (progn (insert message "\n") - (setq end (copy-marker (point))) + (setq end (point-marker)) (goto-char begin) (insert " " prefix) (forward-line 1) @@ -1317,8 +1359,9 @@ Returns the stats object." (run-ended (cl-destructuring-bind (stats abortedp) event-args (let ((unexpected (ert-stats-completed-unexpected stats)) - (expected-failures (ert--stats-failed-expected stats))) - (message "\n%sRan %s tests, %s results as expected%s (%s)%s\n" + (skipped (ert-stats-skipped stats)) + (expected-failures (ert--stats-failed-expected stats))) + (message "\n%sRan %s tests, %s results as expected%s%s (%s)%s\n" (if (not abortedp) "" "Aborted: ") @@ -1327,6 +1370,9 @@ Returns the stats object." (if (zerop unexpected) "" (format ", %s unexpected" unexpected)) + (if (zerop skipped) + "" + (format ", %s skipped" skipped)) (ert--format-time-iso8601 (ert--stats-end-time stats)) (if (zerop expected-failures) "" @@ -1339,6 +1385,15 @@ Returns the stats object." (message "%9s %S" (ert-string-for-test-result result nil) (ert-test-name test)))) + (message "%s" "")) + (unless (zerop skipped) + (message "%s skipped results:" skipped) + (cl-loop for test across (ert--stats-tests stats) + for result = (ert-test-most-recent-result test) do + (when (ert-test-result-type-p result :skipped) + (message "%9s %S" + (ert-string-for-test-result result nil) + (ert-test-name test)))) (message "%s" ""))))) (test-started ) @@ -1409,13 +1464,72 @@ the tests)." (kill-emacs 2)))) +(defun ert-summarize-tests-batch-and-exit () + "Summarize the results of testing. +Expects to be called in batch mode, with logfiles as command-line arguments. +The logfiles should have the `ert-run-tests-batch' format. When finished, +this exits Emacs, with status as per `ert-run-tests-batch-and-exit'." + (or noninteractive + (user-error "This function is only for use in batch mode")) + (let ((nlogs (length command-line-args-left)) + (ntests 0) (nrun 0) (nexpected 0) (nunexpected 0) (nskipped 0) + nnotrun logfile notests badtests unexpected) + (with-temp-buffer + (while (setq logfile (pop command-line-args-left)) + (erase-buffer) + (insert-file-contents logfile) + (if (not (re-search-forward "^Running \\([0-9]+\\) tests" nil t)) + (push logfile notests) + (setq ntests (+ ntests (string-to-number (match-string 1)))) + (if (not (re-search-forward "^\\(Aborted: \\)?\ +Ran \\([0-9]+\\) tests, \\([0-9]+\\) results as expected\ +\\(?:, \\([0-9]+\\) unexpected\\)?\ +\\(?:, \\([0-9]+\\) skipped\\)?" nil t)) + (push logfile badtests) + (if (match-string 1) (push logfile badtests)) + (setq nrun (+ nrun (string-to-number (match-string 2))) + nexpected (+ nexpected (string-to-number (match-string 3)))) + (when (match-string 4) + (push logfile unexpected) + (setq nunexpected (+ nunexpected + (string-to-number (match-string 4))))) + (if (match-string 5) + (setq nskipped (+ nskipped + (string-to-number (match-string 5))))))))) + (setq nnotrun (- ntests nrun)) + (message "\nSUMMARY OF TEST RESULTS") + (message "-----------------------") + (message "Files examined: %d" nlogs) + (message "Ran %d tests%s, %d results as expected%s%s" + nrun + (if (zerop nnotrun) "" (format ", %d failed to run" nnotrun)) + nexpected + (if (zerop nunexpected) + "" + (format ", %d unexpected" nunexpected)) + (if (zerop nskipped) + "" + (format ", %d skipped" nskipped))) + (when notests + (message "%d files did not contain any tests:" (length notests)) + (mapc (lambda (l) (message " %s" l)) notests)) + (when badtests + (message "%d files did not finish:" (length badtests)) + (mapc (lambda (l) (message " %s" l)) badtests)) + (when unexpected + (message "%d files contained unexpected results:" (length unexpected)) + (mapc (lambda (l) (message " %s" l)) unexpected)) + (kill-emacs (cond ((or notests badtests (not (zerop nnotrun))) 2) + (unexpected 1) + (t 0))))) + ;;; Utility functions for load/unload actions. (defun ert--activate-font-lock-keywords () "Activate font-lock keywords for some of ERT's symbols." (font-lock-add-keywords nil - '(("(\\(\\<ert-deftest\\)\\>\\s *\\(\\sw+\\)?" + '(("(\\(\\<ert-deftest\\)\\>\\s *\\(\\(?:\\sw\\|\\s_\\)+\\)?" (1 font-lock-keyword-face nil t) (2 font-lock-function-name-face nil t))))) @@ -1561,15 +1675,17 @@ Also sets `ert--results-progress-bar-button-begin'." (ert--insert-human-readable-selector (ert--stats-selector stats)) (insert "\n") (insert - (format (concat "Passed: %s\n" - "Failed: %s\n" - "Total: %s/%s\n\n") + (format (concat "Passed: %s\n" + "Failed: %s\n" + "Skipped: %s\n" + "Total: %s/%s\n\n") (ert--results-format-expected-unexpected (ert--stats-passed-expected stats) (ert--stats-passed-unexpected stats)) (ert--results-format-expected-unexpected (ert--stats-failed-expected stats) (ert--stats-failed-unexpected stats)) + (ert-stats-skipped stats) run-count (ert-stats-total stats))) (insert @@ -1734,7 +1850,9 @@ non-nil, returns the face for expected results.." (when (ert-test-documentation test) (insert " " (propertize - (ert--string-first-line (ert-test-documentation test)) + (ert--string-first-line + (substitute-command-keys + (ert-test-documentation test))) 'font-lock-face 'font-lock-doc-face) "\n")) (cl-etypecase result @@ -1826,11 +1944,12 @@ and how to display message." ;; defined without cl. (car ert--selector-history) "t"))) - (read-from-minibuffer (if (null default) - "Run tests: " - (format "Run tests (default %s): " default)) - nil nil t 'ert--selector-history - default nil)) + (read + (completing-read (if (null default) + "Run tests: " + (format "Run tests (default %s): " default)) + obarray #'ert-test-boundp nil nil + 'ert--selector-history default nil))) nil)) (unless message-fn (setq message-fn 'message)) (let ((output-buffer-name output-buffer-name) @@ -1849,7 +1968,7 @@ and how to display message." (run-ended (cl-destructuring-bind (stats abortedp) event-args (funcall message-fn - "%sRan %s tests, %s results were as expected%s" + "%sRan %s tests, %s results were as expected%s%s" (if (not abortedp) "" "Aborted: ") @@ -1859,7 +1978,12 @@ and how to display message." (ert-stats-completed-unexpected stats))) (if (zerop unexpected) "" - (format ", %s unexpected" unexpected)))) + (format ", %s unexpected" unexpected))) + (let ((skipped + (ert-stats-skipped stats))) + (if (zerop skipped) + "" + (format ", %s skipped" skipped)))) (ert--results-update-stats-display (with-current-buffer buffer ert--results-ewoc) stats))) @@ -2254,9 +2378,9 @@ To be used in the ERT results buffer." (ert--print-backtrace backtrace) (debugger-make-xrefs) (goto-char (point-min)) - (insert "Backtrace for test `") + (insert (substitute-command-keys "Backtrace for test `")) (ert-insert-test-name-button (ert-test-name test)) - (insert "':\n"))))))) + (insert (substitute-command-keys "':\n")))))))) (defun ert-results-pop-to-messages-for-test-at-point () "Display the part of the *Messages* buffer generated during the test at point. @@ -2275,9 +2399,9 @@ To be used in the ERT results buffer." (ert-simple-view-mode) (insert (ert-test-result-messages result)) (goto-char (point-min)) - (insert "Messages for test `") + (insert (substitute-command-keys "Messages for test `")) (ert-insert-test-name-button (ert-test-name test)) - (insert "':\n"))))) + (insert (substitute-command-keys "':\n")))))) (defun ert-results-pop-to-should-forms-for-test-at-point () "Display the list of `should' forms executed during the test at point. @@ -2305,9 +2429,10 @@ To be used in the ERT results buffer." (ert--pp-with-indentation-and-newline form-description) (ert--make-xrefs-region begin (point))))) (goto-char (point-min)) - (insert "`should' forms executed during test `") + (insert (substitute-command-keys + "`should' forms executed during test `")) (ert-insert-test-name-button (ert-test-name test)) - (insert "':\n") + (insert (substitute-command-keys "':\n")) (insert "\n") (insert (concat "(Values are shallow copies and may have " "looked different during the test if they\n" @@ -2384,9 +2509,11 @@ To be used in the ERT results buffer." (let ((file-name (and test-name (symbol-file test-name 'ert-deftest)))) (when file-name - (insert " defined in `" (file-name-nondirectory file-name) "'") + (insert (format-message " defined in `%s'" + (file-name-nondirectory file-name))) (save-excursion - (re-search-backward "`\\([^`']+\\)'" nil t) + (re-search-backward (substitute-command-keys "`\\([^`']+\\)'") + nil t) (help-xref-button 1 'help-function-def test-name file-name))) (insert ".") (fill-region-as-paragraph (point-min) (point)) @@ -2398,8 +2525,9 @@ To be used in the ERT results buffer." "this documentation refers to an old definition.") (fill-region-as-paragraph begin (point))) (insert "\n\n")) - (insert (or (ert-test-documentation test-definition) - "It is not documented.") + (insert (substitute-command-keys + (or (ert-test-documentation test-definition) + "It is not documented.")) "\n"))))))) (defun ert-results-describe-test-at-point () @@ -2416,7 +2544,7 @@ To be used in the ERT results buffer." (add-to-list 'minor-mode-alist '(ert--current-run-stats (:eval (ert--tests-running-mode-line-indicator)))) -(add-to-list 'emacs-lisp-mode-hook 'ert--activate-font-lock-keywords) +(add-hook 'emacs-lisp-mode-hook #'ert--activate-font-lock-keywords) (defun ert--unload-function () "Unload function to undo the side-effects of loading ert.el." @@ -2427,7 +2555,7 @@ To be used in the ERT results buffer." nil) (defvar ert-unload-hook '()) -(add-hook 'ert-unload-hook 'ert--unload-function) +(add-hook 'ert-unload-hook #'ert--unload-function) (provide 'ert) |
