From 62f7760e61900b6ac0fb513294129130446b17e7 Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Sun, 3 Sep 2023 18:45:16 +0200 Subject: [PATCH] Add `skip-when` macro to `ert-deftest` This can help avoid some awkward test skip conditions. For example, this triple negation: (skip-unless (not noninteractive)) Can be written as the simpler: (skip-when noninteractive) * lisp/emacs-lisp/ert.el (ert-deftest): Add new 'skip-when' macro. (ert--skip-when): New internal function. * doc/misc/ert.texi (Tests and Their Environment): Document above new macro. * test/lisp/emacs-lisp/ert-tests.el (ert-test-skip-when): New test. --- doc/misc/ert.texi | 23 ++++++++++++++++-- etc/NEWS | 7 ++++++ lisp/emacs-lisp/ert.el | 39 ++++++++++++++++++++----------- test/lisp/emacs-lisp/ert-tests.el | 14 +++++++++++ 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/doc/misc/ert.texi b/doc/misc/ert.texi index a6b62a058f2..f4a072cf2bc 100644 --- a/doc/misc/ert.texi +++ b/doc/misc/ert.texi @@ -658,11 +658,30 @@ versions, specific architectures, etc.: @cindex skipping tests @cindex test preconditions @cindex preconditions of a test +@findex skip-when +@findex skip-unless Sometimes, it doesn't make sense to run a test due to missing preconditions. A required Emacs feature might not be compiled in, the function to be tested could call an external binary which might not be -available on the test machine, you name it. In this case, the macro -@code{skip-unless} could be used to skip the test: +available on the test machine, you name it. In this case, the macros +@code{skip-when} or @code{skip-unless} could be used to skip the +test.@footnote{The @code{skip-when} macro was added in Emacs 30.1. If +you need your tests to be compatible with older versions of Emacs, use +@code{skip-unless} instead.} + +@noindent +For example, this test is skipped on MS-Windows and macOS: + +@lisp +(ert-deftest test-gnu-linux () + "A test that is not relevant on MS-Windows and macOS." + (skip-when (memq system-type '(windows-nt ns)) + ...)) +@end lisp + +@noindent +This test is skipped if the feature @samp{dbusbind} is not present in +the running Emacs: @lisp (ert-deftest test-dbus () diff --git a/etc/NEWS b/etc/NEWS index 3afb0e3008e..936bb62d750 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -724,6 +724,13 @@ without specifying a file, like this: *** New user option 'image-dired-thumb-naming'. You can now configure how a thumbnail is named using this option. +** ERT + +*** New macro `skip-when' to skip 'ert-deftest' tests. +This can help avoid some awkward skip conditions. For example +'(skip-unless (not noninteractive))' can be changed to the easier +to read '(skip-when noninteractive)'. + ** checkdoc --- diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el index 4ea894f4ede..d727bc94ec5 100644 --- a/lisp/emacs-lisp/ert.el +++ b/lisp/emacs-lisp/ert.el @@ -34,17 +34,18 @@ ;; `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', `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 -;; (`cl-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). +;; additional operators `should', `should-not', `should-error', +;; `skip-when' 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 (`cl-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. The `skip-when' and +;; `skip-unless' forms skip the test immediately, which is useful for +;; checking the test environment (like availability of features, +;; external binaries, etc). ;; ;; See ERT's Info manual `(ert) Top' as well as the docstrings for ;; more details. To see some examples of tests written in ERT, see @@ -194,8 +195,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', `should-error' and `skip-unless' are -useful for assertions in BODY. +`should', `should-not', `should-error', `skip-when', and +`skip-unless' are useful for assertions in BODY. Use `ert' to run tests interactively. @@ -227,7 +228,8 @@ in batch mode, an error is signaled. (tags nil tags-supplied-p)) body) (ert--parse-keys-and-body docstring-keys-and-body) - `(cl-macrolet ((skip-unless (form) `(ert--skip-unless ,form))) + `(cl-macrolet ((skip-when (form) `(ert--skip-when ,form)) + (skip-unless (form) `(ert--skip-unless ,form))) (ert-set-test ',name (make-ert-test :name ',name @@ -464,6 +466,15 @@ failed." (list :fail-reason "did not signal an error"))))))))) +(cl-defmacro ert--skip-when (form) + "Evaluate FORM. If it returns t, skip the current test. +Errors during evaluation are caught and handled like t." + (declare (debug t)) + (ert--expand-should `(skip-when ,form) form + (lambda (inner-form form-description-form _value-var) + `(when (condition-case nil ,inner-form (t t)) + (ert-skip ,form-description-form))))) + (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." diff --git a/test/lisp/emacs-lisp/ert-tests.el b/test/lisp/emacs-lisp/ert-tests.el index 7713a0f6e38..bb3de111e3e 100644 --- a/test/lisp/emacs-lisp/ert-tests.el +++ b/test/lisp/emacs-lisp/ert-tests.el @@ -304,6 +304,20 @@ failed or if there was a problem." (cl-macrolet ((test () (error "Foo"))) (should-error (test)))) +(ert-deftest ert-test-skip-when () + ;; Don't skip. + (let ((test (make-ert-test :body (lambda () (skip-when nil))))) + (let ((result (ert-run-test test))) + (should (ert-test-passed-p result)))) + ;; Skip. + (let ((test (make-ert-test :body (lambda () (skip-when t))))) + (let ((result (ert-run-test test))) + (should (ert-test-skipped-p result)))) + ;; Skip in case of error. + (let ((test (make-ert-test :body (lambda () (skip-when (error "Foo")))))) + (let ((result (ert-run-test test))) + (should (ert-test-skipped-p result))))) + (ert-deftest ert-test-skip-unless () ;; Don't skip. (let ((test (make-ert-test :body (lambda () (skip-unless t))))) -- 2.39.2