ohler / ert
- Source
- Commits
- Network (2)
- Issues (0)
- Downloads (0)
- Wiki (1)
- Graphs
-
Tree:
22b6ebe
Christian Ohler (author)
Sun Aug 10 14:18:45 -0700 2008
ert / ert.el
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1 | ;;; ert.el --- Emacs Lisp Regression Testing | |
| 2 | |||||
| 3 | ;; Copyright (C) 2007, 2008 Christian M. Ohler | ||||
| 4 | |||||
| 5 | ;; Author: Christian M. Ohler | ||||
| 6 | ;; Version: 0.2 | ||||
| 7 | ;; Keywords: lisp, tools | ||||
| 8 | |||||
| 9 | ;; This file is NOT part of GNU Emacs. | ||||
| 10 | |||||
| 11 | ;; This program is free software: you can redistribute it and/or | ||||
| 12 | ;; modify it under the terms of the GNU General Public License as | ||||
| 13 | ;; published by the Free Software Foundation, either version 3 of the | ||||
| 14 | ;; License, or (at your option) any later version. | ||||
| 15 | ;; | ||||
| 16 | ;; This program is distributed in the hope that it will be useful, but | ||||
| 17 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
| 19 | ;; General Public License for more details. | ||||
| 20 | ;; | ||||
| 21 | ;; You should have received a copy of the GNU General Public License | ||||
| 22 | ;; along with this program. If not, see `http://www.gnu.org/licenses/'. | ||||
| 23 | |||||
| 24 | ;;; Commentary: | ||||
| 25 | |||||
| 26 | ;; ERT is a tool for automated testing in Emacs Lisp. Its main | ||||
| 27 | ;; features are facilities for defining and running test cases and | ||||
| 28 | ;; reporting the results as well as for debugging test failures | ||||
| 29 | ;; interactively. | ||||
| 30 | ;; | ||||
| 31 | ;; The main entry points are `ert-deftest', which is similar to | ||||
| 32 | ;; `defun' but defines a test, and `ert-run-tests-interactively', | ||||
| 33 | ;; which runs tests and offers an interactive interface for inspecting | ||||
| 34 | ;; results and debugging. There is also `ert-run-tests-batch' for | ||||
| 35 | ;; non-interactive use. | ||||
| 36 | ;; | ||||
| 37 | ;; The body of `ert-deftest' forms resembles a function body, but the | ||||
| 38 | ;; additional operators `should', `should-not' and `should-error' are | ||||
| 39 | ;; available. `should' is similar to cl's `assert', but signals a | ||||
| 40 | ;; different error when its condition is violated that is caught and | ||||
| 41 | ;; processed by ERT. In addition, it analyzes its argument form and | ||||
| 42 | ;; records information that helps debugging (`assert' tries to do | ||||
| 43 | ;; something similar when its second argument SHOW-ARGS is true, but | ||||
| 44 | ;; `should' is more sophisticated). For information on `should-not' | ||||
| 45 | ;; and `should-error', see their docstrings. | ||||
| 46 | ;; | ||||
| 47 | ;; For example, | ||||
| 48 | ;; | ||||
| 49 | ;; ;; Define a test named `foo'. | ||||
| 50 | ;; (ert-deftest foo () | ||||
| 51 | ;; (should (= (+ 1 2) 4))) | ||||
| 52 | ;; | ||||
| 53 | ;; ;; Run it. | ||||
| 54 | ;; (ert-run-tests-interactively 'foo) | ||||
| 55 | ;; | ||||
| 56 | ;; generates the following output (in addition to some statistics) in | ||||
| 57 | ;; the *ert* results buffer: | ||||
| 58 | ;; | ||||
| 59 | ;; F foo | ||||
| 60 | ;; (ert-test-failed | ||||
| 61 | ;; ((should | ||||
| 62 | ;; (= | ||||
| 63 | ;; (+ 1 2) | ||||
| 64 | ;; 4)) | ||||
| 65 | ;; :form | ||||
| 66 | ;; (= 3 4) | ||||
| 67 | ;; :value nil)) | ||||
| 68 | ;; | ||||
| 69 | ;; This indicates that the test failed. The `should' form that failed | ||||
| 70 | ;; was (should (= (+ 1 2) 4)), because its inner form, after | ||||
| 71 | ;; evaluation of its arguments, was the function call (= 3 4), which | ||||
| 72 | ;; returned nil. | ||||
| 73 | ;; | ||||
| 74 | ;; Obviously, this is a bug in the test case, not in the functions `+' | ||||
| 75 | ;; or `='. In the results buffer, with point on the test result, the | ||||
| 76 | ;; key "." can be used to jump to the definition of the test to modify | ||||
| 77 | ;; it to correct the bug. After evaluating the modified definition | ||||
| 78 | ;; and switching back to the results buffer, the key "r" will re-run | ||||
| 79 | ;; the test and show the new result. | ||||
| 80 | |||||
| 81 | |||||
| 82 | ;; Test selectors | ||||
| 83 | ;; | ||||
| 84 | ;; Functions like `ert-run-tests-interactively' accept a test | ||||
| 85 | ;; selector, which is a Lisp expression specifying a set of tests. | ||||
| 86 | ;; Each test name is a selector that refers to that test, the selector | ||||
| 87 | ;; `t' refers to all tests, and the selector `:failed' refers to all | ||||
| 88 | ;; tests that failed; but more complex selectors are available. Test | ||||
| 89 | ;; selector syntax is similar to cl's type specifier syntax. See the | ||||
| 90 | ;; docstring of `ert-select-tests' for details. | ||||
| 91 | |||||
| 92 | |||||
| 93 | ;; Comparison with other testing tools | ||||
| 94 | ;; | ||||
| 95 | ;; ERT allows test-driven development similar to *Unit frameworks for | ||||
| 96 | ;; other languages. However, two common *Unit features are notably | ||||
| 97 | ;; absent from ERT: fixtures and test suites. | ||||
| 98 | ;; | ||||
| 99 | ;; Fixtures, as used e.g. in SUnit or JUnit, have two main purposes: | ||||
| 100 | ;; Setting up (and tearing down) an environment for a set of test | ||||
| 101 | ;; cases, and making that environment accessible through object | ||||
| 102 | ;; attributes that can be used like local variables. | ||||
| 103 | ;; | ||||
| 104 | ;; While fixtures are a great syntactic simplification in other | ||||
| 105 | ;; languages, they are not very useful in Lisp, where higher-order | ||||
| 106 | ;; functions and `unwind-protect' are available. One way to implement | ||||
| 107 | ;; and use a fixture in ERT is | ||||
| 108 | ;; | ||||
| 109 | ;; (defun my-fixture (body) | ||||
| 110 | ;; (unwind-protect | ||||
| 111 | ;; (progn ...set up... | ||||
| 112 | ;; (funcall body)) | ||||
| 113 | ;; ...tear down...)) | ||||
| 114 | ;; | ||||
| 115 | ;; (ert-deftest my-test () | ||||
| 116 | ;; (my-fixture | ||||
| 117 | ;; (lambda () | ||||
| 118 | ;; ...test code...))) | ||||
| 119 | ;; | ||||
| 120 | ;; (Another way would be a `with-my-fixture' macro.) This solves the | ||||
| 121 | ;; set-up and tear-down part, and additionally allows any test case to | ||||
| 122 | ;; use any combination of fixtures, so it is more general than what | ||||
| 123 | ;; other tools typically allow. | ||||
| 124 | ;; | ||||
| 125 | ;; If the test case needs access to the environment the fixture sets | ||||
| 126 | ;; up, the fixture can be modified to pass arguments to the body. | ||||
| 127 | ;; | ||||
| 128 | ;; These are standard Lisp idioms. Special syntax for them could be | ||||
| 129 | ;; added easily enough, but would provide only a minor simplification. | ||||
| 130 | ;; | ||||
| 131 | ;; (Note that splitting set-up and tear-down into separate functions, | ||||
| 132 | ;; like *Unit tools usually do, makes it impossible to establish | ||||
| 133 | ;; dynamic `let' bindings as part of the fixture. So, blindly | ||||
| 134 | ;; imitating the way fixtures are implemented in other languages would | ||||
| 135 | ;; be counter-productive in Lisp.) | ||||
| 136 | ;; | ||||
| 137 | ;; | ||||
| 138 | ;; The purpose of test suites is to group related test cases together. | ||||
| 139 | ;; The most common use of this is to run just the tests for one | ||||
| 140 | ;; particular module. Since symbol prefixes are the usual way of | ||||
| 141 | ;; separating module namespaces in Emacs Lisp, test selectors already | ||||
| 142 | ;; solve this by allowing regexp matching on test names; e.g., the | ||||
| 143 | ;; selector "^ert-" selects ERT's self-tests. | ||||
| 144 | ;; | ||||
| 145 | ;; If test suites containing arbitrary sets of tests are found to be | ||||
| 146 | ;; desirable, it would be easy to add a `define-test-selector' | ||||
| 147 | ;; mechanism that introduces a new selector, defined in terms of | ||||
| 148 | ;; existing ones; e.g. | ||||
| 149 | ;; | ||||
| 150 | ;; ;; Note that `define-test-selector' does not exist yet. | ||||
| 151 | ;; (define-test-selector my-test-suite () `(member foo-test bar-test)) | ||||
| 152 | ;; | ||||
| 153 | ;; would define a test suite named `my-test-suite' consisting of | ||||
| 154 | ;; `foo-test' and `bar-test'. See also `deftype' in Common Lisp. | ||||
| 155 | |||||
| 156 | |||||
| 157 | ;; TODO: Add `skip' feature for tests that can't run in current environment. | ||||
| 158 | |||||
| 159 | |||||
| 160 | ;;; Code: | ||||
| 161 | |||||
| 162 | (require 'cl) | ||||
| 163 | (require 'ewoc) | ||||
| 164 | (require 'find-func) | ||||
| 165 | (require 'debug) | ||||
| 166 | |||||
| 167 | (defvar ert-debug-on-error nil | ||||
| 168 | "Non-nil means enter debugger when a test fails or terminates with an error.") | ||||
| 169 | |||||
| 170 | |||||
| 171 | ;;; Defining and locating tests. | ||||
| 172 | |||||
| 173 | ;; The data structure that represents a test case. | ||||
| 174 | (defstruct ert-test | ||||
| 175 | (name nil) | ||||
| 176 | (documentation nil) | ||||
| 177 | (body (assert nil)) | ||||
| 178 | (most-recent-result nil) | ||||
| 179 | (expected-result-type 'ert-test-passed)) | ||||
| 180 | |||||
| 181 | (defun ert-test-boundp (symbol) | ||||
| 182 | "Return non-nil if SYMBOL names a test." | ||||
| 183 | (and (get symbol 'ert-test) t)) | ||||
| 184 | |||||
| 185 | (defun ert-get-test (symbol) | ||||
| 186 | "If SYMBOL names a test, return that. Signal an error otherwise." | ||||
| 187 | (assert (ert-test-boundp symbol) t) | ||||
| 188 | (get symbol 'ert-test)) | ||||
| 189 | |||||
| 190 | (defun ert-set-test (symbol definition) | ||||
| 191 | "Make SYMBOL name the test DEFINITION, and return DEFINITION." | ||||
| 192 | (put symbol 'ert-test definition) | ||||
| 193 | definition) | ||||
| 194 | |||||
| 195 | (defun ert-make-test-unbound (symbol) | ||||
| 196 | "Make SYMBOL name no test. Return SYMBOL." | ||||
| 197 | (remprop symbol 'ert-test) | ||||
| 198 | symbol) | ||||
| 199 | |||||
| 200 | (defun ert-test-result-expected-p (test result) | ||||
| 201 | "Return non-nil if RESULT matches the expected result type for TEST." | ||||
| 202 | (typep result (ert-test-expected-result-type test))) | ||||
| 203 | |||||
| 204 | (defvar ert-find-test-regexp | ||||
| 205 | (concat "^\\s-*(ert-deftest" | ||||
| 206 | find-function-space-re | ||||
| 207 | "%s\\(\\s-\\|$\\)") | ||||
| 208 | "The regexp the `find-function' mechanisms use for locating test definitions.") | ||||
| 209 | |||||
| 210 | (eval-and-compile | ||||
| 211 | (defun ert-parse-keys-and-body (keys-and-body) | ||||
| 212 | "Split KEYS-AND-BODY into keyword-and-value pairs and the remaining body. | ||||
| 213 | |||||
| 214 | KEYS-AND-BODY should have the form of a property list, with the | ||||
| 215 | exception that only keywords are permitted as keys and that the | ||||
| 216 | tail -- the body -- is a list of forms that does not start with a | ||||
| 217 | keyword. | ||||
| 218 | |||||
| 219 | Returns a two-element list containing the keys-and-values plist | ||||
| 220 | and the body." | ||||
| 221 | (let ((extracted-key-accu '()) | ||||
| 222 | (remaining keys-and-body)) | ||||
| 223 | (while (and (consp remaining) (keywordp (first remaining))) | ||||
| 224 | (let ((keyword (pop remaining))) | ||||
| 225 | (unless (consp remaining) | ||||
| 226 | (error "Value expected after keyword %S in %S" | ||||
| 227 | keyword keys-and-body)) | ||||
| 228 | (when (assoc keyword extracted-key-accu) | ||||
| 229 | (warn "Keyword %S appears more than once in %S" keyword | ||||
| 230 | keys-and-body)) | ||||
| 231 | (push (cons keyword (pop remaining)) extracted-key-accu))) | ||||
| 232 | (setq extracted-key-accu (nreverse extracted-key-accu)) | ||||
| 233 | (list (loop for (key . value) in extracted-key-accu | ||||
| 234 | collect key | ||||
| 235 | collect value) | ||||
| 236 | remaining)))) | ||||
| 237 | |||||
| 238 | ;;;###autoload | ||||
| 239 | (defmacro* ert-deftest (name () &body keys-and-body) | ||||
| 240 | "Define NAME (a symbol) as a test. | ||||
| 241 | |||||
| 242 | \(fn NAME () [:documentation DOCSTRING] [:expected-result TYPE] BODY...)" | ||||
| 243 | (declare (debug (&define :name test name sexp | ||||
| 244 | [&optional [":documentation" stringp]] | ||||
| 245 | [&optional [":expected-result" sexp]] | ||||
| 246 | def-body))) | ||||
| 247 | (destructuring-bind ((&key (expected-result nil expected-result-supplied-p) | ||||
| 248 | (documentation nil documentation-supplied-p)) | ||||
| 249 | body) | ||||
| 250 | (ert-parse-keys-and-body keys-and-body) | ||||
| 251 | `(progn | ||||
| 252 | (ert-set-test ',name | ||||
| 253 | (make-ert-test | ||||
| 254 | :name ',name | ||||
| 255 | :body (lambda () ,@body) | ||||
| 256 | ,@(when expected-result-supplied-p | ||||
| 257 | `(:expected-result-type ,expected-result)) | ||||
| 258 | ,@(when documentation-supplied-p | ||||
| 259 | `(:documentation ,documentation)))) | ||||
| 260 | ;; This hack allows `symbol-file' to associate `ert-deftest' | ||||
| 261 | ;; forms with files, and therefore enables `find-function' to | ||||
| 262 | ;; work with tests. However, it leads to warnings in | ||||
| 263 | ;; `unload-feature', which doesn't know how to undefine tests | ||||
| 264 | ;; and has no mechanism for extension. | ||||
| 265 | (push '(ert-deftest . ,name) current-load-list) | ||||
| 266 | ',name))) | ||||
| 267 | |||||
| 268 | (defun ert-read-test-name (prompt &optional default-value history) | ||||
| 269 | "Read the name of a test and return it as a symbol. | ||||
| 270 | Prompt with PROMPT. By default, return DEFAULT-VALUE." | ||||
| 271 | (when (symbolp default-value) (setq default-value (symbol-name default-value))) | ||||
| 272 | (intern (completing-read prompt obarray #'ert-test-boundp | ||||
| 273 | t nil history default-value nil))) | ||||
| 274 | |||||
| 275 | (defun ert-find-test-other-window (test-name) | ||||
| 276 | "Find, in another window, the definition of TEST-NAME." | ||||
| 277 | (interactive (list (ert-read-test-name "Find test definition: "))) | ||||
| 278 | (find-function-do-it test-name 'ert-deftest 'switch-to-buffer-other-window)) | ||||
| 279 | |||||
| 280 | (defun ert-delete-test (test-name) | ||||
| 281 | "An interactive interface to `ert-make-test-unbound'." | ||||
| 282 | (interactive (list (let ((default (thing-at-point 'symbol))) | ||||
| 283 | (when default | ||||
| 284 | (set-text-properties 0 (length default) nil default) | ||||
| 285 | (when (or (string= default "nil") (intern-soft default)) | ||||
| 286 | (setq default (intern default))) | ||||
| 287 | (unless (ert-test-boundp default) | ||||
| 288 | (setq default nil))) | ||||
| 289 | (completing-read (if (null default) | ||||
| 290 | "Delete test: " | ||||
| 291 | (format "Delete test (default %s): " | ||||
| 292 | default)) | ||||
| 293 | obarray #'ert-test-boundp | ||||
| 294 | 'really-require-match | ||||
| 295 | nil nil default nil)))) | ||||
| 296 | (ert-make-test-unbound test-name)) | ||||
| 297 | |||||
| 298 | (defun ert-delete-all-tests () | ||||
| 299 | "Make all symbols in `obarray' name no test." | ||||
| 300 | (interactive) | ||||
| 301 | (when (interactive-p) | ||||
| 302 | (unless (y-or-n-p "Delete all tests? ") | ||||
| 303 | (error "Aborted"))) | ||||
| 304 | (mapc #'ert-delete-test (mapcar #'ert-test-name (ert-select-tests t t))) | ||||
| 305 | t) | ||||
| 306 | |||||
| 307 | |||||
| 308 | ;;; Test selectors. | ||||
| 309 | |||||
| 310 | (defun ert-select-tests (selector universe) | ||||
| 311 | "Select, from UNIVERSE, a set of tests according to SELECTOR. | ||||
| 312 | |||||
| 313 | UNIVERSE should be a list of tests, or t, which refers to all | ||||
| 314 | tests named by symbols in `obarray'. | ||||
| 315 | |||||
| 316 | Returns the set of tests as a list. | ||||
| 317 | |||||
| 318 | Valid selectors: | ||||
| 319 | |||||
| 320 | nil -- Selects the empty set. | ||||
| 321 | t -- Selects UNIVERSE. | ||||
| 322 | :new -- Selects all tests that have not been run yet. | ||||
| 323 | :failed, :passed, :error -- Select tests according to their most recent result. | ||||
| 324 | :expected, :unexpected -- Select tests according to their most recent result. | ||||
| 325 | a string -- Selects all tests that have a name that matches the string, a regexp. | ||||
| 326 | a test -- Selects that test. | ||||
| 327 | a symbol -- Selects the test that the symbol names, errors if none. | ||||
| 328 | \(member TESTS...\) -- Selects TESTS, a list of tests or symbols naming tests. | ||||
| 329 | \(eql TEST\) -- Selects TEST, a test or a symbol naming a test. | ||||
| 330 | \(and SELECTORS...\) -- Selects the tests that match all SELECTORS. | ||||
| 331 | \(or SELECTORS...\) -- Selects the tests that match any SELECTOR. | ||||
| 332 | \(not SELECTOR\) -- Selects all tests that do not match SELECTOR. | ||||
| 333 | \(satisfies PREDICATE\) -- Selects all tests that satisfy PREDICATE. | ||||
| 334 | |||||
| 335 | Only selectors that require a superset of tests, such | ||||
| 336 | as (satisfies ...), strings, :new, etc. make use of UNIVERSE. | ||||
| 337 | Selectors that do not, such as \(member ...\), just return the | ||||
| 338 | set implied by them without checking whether it is really | ||||
| 339 | contained in UNIVERSE." | ||||
| 340 | ;; This code needs to match the etypecase in | ||||
| 341 | ;; `ert-insert-human-readable-selector'. | ||||
| 342 | (etypecase selector | ||||
| 343 | ((member nil) nil) | ||||
| 344 | ((member t) (etypecase universe | ||||
| 345 | (list universe) | ||||
| 346 | ((member t) (ert-select-tests "" universe)))) | ||||
| 347 | ((member :new) (ert-select-tests | ||||
| 348 | `(satisfies ,(lambda (test) | ||||
| 349 | (typep (ert-test-most-recent-result test) | ||||
| 350 | 'null))) | ||||
| 351 | universe)) | ||||
| 352 | ((member :failed) (ert-select-tests | ||||
| 353 | `(satisfies ,(lambda (test) | ||||
| 354 | (typep (ert-test-most-recent-result test) | ||||
| 355 | 'ert-test-failed))) | ||||
| 356 | universe)) | ||||
| 357 | ((member :passed) (ert-select-tests | ||||
| 358 | `(satisfies ,(lambda (test) | ||||
| 359 | (typep (ert-test-most-recent-result test) | ||||
| 360 | 'ert-test-passed))) | ||||
| 361 | universe)) | ||||
| 362 | ((member :error) (ert-select-tests | ||||
| 363 | `(satisfies ,(lambda (test) | ||||
| 364 | (typep (ert-test-most-recent-result test) | ||||
| 365 | 'ert-test-error))) | ||||
| 366 | universe)) | ||||
| 367 | ((member :expected) (ert-select-tests | ||||
| 368 | `(satisfies | ||||
| 369 | ,(lambda (test) | ||||
| 370 | (ert-test-result-expected-p | ||||
| 371 | test | ||||
| 372 | (ert-test-most-recent-result test)))) | ||||
| 373 | universe)) | ||||
| 374 | ((member :unexpected) (ert-select-tests `(not :expected) universe)) | ||||
| 375 | (string | ||||
| 376 | (etypecase universe | ||||
| 377 | ((member t) (mapcar #'ert-get-test | ||||
| 378 | (apropos-internal selector #'ert-test-boundp))) | ||||
| 379 | (list (remove-if-not (lambda (test) | ||||
| 380 | (and (ert-test-name test) | ||||
| 381 | (string-match selector (ert-test-name test)))) | ||||
| 382 | universe)))) | ||||
| 383 | (ert-test (list selector)) | ||||
| 384 | (symbol | ||||
| 385 | (assert (ert-test-boundp selector)) | ||||
| 386 | (list (ert-get-test selector))) | ||||
| 387 | (cons | ||||
| 388 | (destructuring-bind (operator &rest operands) selector | ||||
| 389 | (ecase operator | ||||
| 390 | (member | ||||
| 391 | (mapcar (lambda (purported-test) | ||||
| 392 | (etypecase purported-test | ||||
| 393 | (symbol (assert (ert-test-boundp purported-test)) | ||||
| 394 | (ert-get-test purported-test)) | ||||
| 395 | (ert-test purported-test))) | ||||
| 396 | operands)) | ||||
| 397 | (eql | ||||
| 398 | (assert (eql (length operands) 1)) | ||||
| 399 | (ert-select-tests `(member ,@operands) universe)) | ||||
| 400 | (and | ||||
| 401 | ;; Do these definitions of AND, NOT and OR satisfy de | ||||
| 402 | ;; Morgan's rules? Should they? | ||||
| 403 | (case (length operands) | ||||
| 404 | (0 (ert-select-tests 't universe)) | ||||
| 405 | (t (ert-select-tests `(and ,@(rest operands)) | ||||
| 406 | (ert-select-tests (first operands) universe))))) | ||||
| 407 | (not | ||||
| 408 | (assert (eql (length operands) 1)) | ||||
| 409 | (set-difference (ert-select-tests 't universe) | ||||
| 410 | (ert-select-tests (first operands) universe))) | ||||
| 411 | (or | ||||
| 412 | (case (length operands) | ||||
| 413 | (0 (ert-select-tests 'nil universe)) | ||||
| 414 | (t (union (ert-select-tests (first operands) universe) | ||||
| 415 | (ert-select-tests `(or ,@(rest operands)) universe))))) | ||||
| 416 | (satisfies | ||||
| 417 | (assert (eql (length operands) 1)) | ||||
| 418 | (remove-if-not (first operands) (ert-select-tests 't universe)))))))) | ||||
| 419 | |||||
| 420 | (defun ert-insert-human-readable-selector (selector) | ||||
| 421 | "Insert a human-readable presentation of SELECTOR into the current buffer." | ||||
| 422 | ;; This is needed to avoid printing the (huge) contents of the | ||||
| 423 | ;; `backtrace' slot of the result objects in the | ||||
| 424 | ;; `most-recent-result' slots of test case objects in (eql ...) or | ||||
| 425 | ;; (member ...) selectors. | ||||
| 426 | (labels ((rec (selector) | ||||
| 427 | ;; This code needs to match the etypecase in `ert-select-tests'. | ||||
| 428 | (etypecase selector | ||||
| 429 | ((or (member nil t | ||||
| 430 | :new :failed :passed :error | ||||
| 431 | :expected :unexpected) | ||||
| 432 | string | ||||
| 433 | symbol) | ||||
| 434 | selector) | ||||
| 435 | (ert-test | ||||
| 436 | (if (ert-test-name selector) | ||||
| 437 | (make-symbol (format "<%S>" (ert-test-name selector))) | ||||
| 438 | (make-symbol "<unnamed test>"))) | ||||
| 439 | (cons | ||||
| 440 | (destructuring-bind (operator &rest operands) selector | ||||
| 441 | (ecase operator | ||||
| 442 | ((member eql and not or) | ||||
| 443 | `(,operator ,@(mapcar #'rec operands))) | ||||
| 444 | (satisfies | ||||
| 445 | selector))))))) | ||||
| 446 | (insert (format "%S" (rec selector))))) | ||||
| 447 | |||||
| 448 | |||||
| 449 | ;;; Running tests. | ||||
| 450 | |||||
| 451 | (put 'ert-test-failed 'error-conditions '(error ert-test-failed)) | ||||
| 452 | (put 'ert-test-failed 'error-message "Test failed") | ||||
| 453 | |||||
| 454 | (defun ert-pass () | ||||
| 455 | "Terminate the current test and mark it passed. Does not return." | ||||
| 456 | (throw 'ert-pass nil)) | ||||
| 457 | |||||
| 458 | (defun ert-fail (data) | ||||
| 459 | "Terminate the current test and mark it failed. Does not return. | ||||
| 460 | DATA is displayed to the user and should state the reason of the failure." | ||||
| 461 | (signal 'ert-test-failed (list data))) | ||||
| 462 | |||||
| 463 | ;; The data structures that represent the result of running a test. | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 464 | (defstruct ert-test-result | |
| 22b6ebee » | Christian Ohler | 2008-08-10 | 465 | (messages nil) | |
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 466 | ) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 467 | (defstruct (ert-test-passed (:include ert-test-result))) | |
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 468 | (defstruct (ert-test-result-with-condition (:include ert-test-result)) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 469 | (condition (assert nil)) | |
| 470 | (backtrace (assert nil))) | ||||
| 471 | (defstruct (ert-test-error (:include ert-test-result-with-condition))) | ||||
| 472 | (defstruct (ert-test-quit (:include ert-test-result-with-condition))) | ||||
| 473 | (defstruct (ert-test-failed (:include ert-test-result-with-condition))) | ||||
| 474 | (defstruct (ert-test-aborted-with-non-local-exit (:include ert-test-result))) | ||||
| 475 | |||||
| 476 | |||||
| 477 | (defun ert-record-backtrace () | ||||
| 478 | "Record the current backtrace (as a list) and return it." | ||||
| 479 | ;; Since the backtrace is stored in the result object, result | ||||
| 480 | ;; objects must only be printed with appropriate limits | ||||
| 481 | ;; (`print-level' and `print-length') in place. For interactive | ||||
| 482 | ;; use, the cost of ensuring this possibly outweighs the advantage | ||||
| 483 | ;; of storing the backtrace for | ||||
| 484 | ;; `ert-results-pop-to-backtrace-for-test-at-point' given that we | ||||
| 485 | ;; already have `ert-results-rerun-test-debugging-errors-at-point'. | ||||
| 486 | ;; For batch use, however, printing the backtrace may be useful. | ||||
| 487 | (loop | ||||
| 488 | ;; 6 is the number of frames our own debugger adds (when | ||||
| 489 | ;; compiled; more when interpreted). FIXME: Need to describe a | ||||
| 490 | ;; procedure for determining this constant. | ||||
| 491 | for i from 6 | ||||
| 492 | for frame = (backtrace-frame i) | ||||
| 493 | while frame | ||||
| 494 | collect frame)) | ||||
| 495 | |||||
| 496 | ;; A container for the state of the execution of a single test and | ||||
| 497 | ;; environment data needed during its execution. | ||||
| 498 | (defstruct ert-test-execution-info | ||||
| 499 | (test (assert nil)) | ||||
| 500 | (result (assert nil)) | ||||
| 501 | ;; A thunk that may be called when RESULT has been set to its final | ||||
| 502 | ;; value and test execution should be terminated. Should not | ||||
| 503 | ;; return. | ||||
| 504 | (exit-continuation (assert nil)) | ||||
| 505 | ;; The binding of `debugger' outside of the execution of the test. | ||||
| 506 | next-debugger | ||||
| 507 | ;; The binding of `ert-debug-on-error' that is in effect for the | ||||
| 508 | ;; execution of the current test. We store it to avoid being | ||||
| 509 | ;; affected by any new bindings the test itself may establish. (I | ||||
| 510 | ;; don't remember whether this feature is important.) | ||||
| 511 | ert-debug-on-error) | ||||
| 512 | |||||
| 513 | (defun ert-run-test-debugger (info debugger-args) | ||||
| 514 | "The function that `debugger' is bound to during the execution of tests. | ||||
| 515 | |||||
| 516 | Records failures and errors and either terminates the test | ||||
| 517 | silently or calls the interactive debugger, as appropriate." | ||||
| 518 | (destructuring-bind (first-debugger-arg &rest more-debugger-args) debugger-args | ||||
| 519 | (ecase first-debugger-arg | ||||
| 520 | ((lambda debug t exit nil) | ||||
| 521 | (apply (ert-test-execution-info-next-debugger info) debugger-args)) | ||||
| 522 | (error | ||||
| 523 | (let* ((condition (first more-debugger-args)) | ||||
| 524 | (type (case (car condition) | ||||
| 525 | ((quit) 'quit) | ||||
| 526 | ((ert-test-failed) 'failed) | ||||
| 527 | (otherwise 'error))) | ||||
| 528 | (backtrace (ert-record-backtrace))) | ||||
| 529 | (setf (ert-test-execution-info-result info) | ||||
| 530 | (ecase type | ||||
| 531 | (quit | ||||
| 532 | (make-ert-test-quit :condition condition | ||||
| 533 | :backtrace backtrace)) | ||||
| 534 | (failed | ||||
| 535 | (make-ert-test-failed :condition condition | ||||
| 536 | :backtrace backtrace)) | ||||
| 537 | (error | ||||
| 538 | (make-ert-test-error :condition condition | ||||
| 539 | :backtrace backtrace)))) | ||||
| 540 | ;; Work around Emacs' heuristic (in eval.c) for detecting | ||||
| 541 | ;; errors in the debugger. | ||||
| 542 | (incf num-nonmacro-input-events) | ||||
| 543 | ;; FIXME: We should probably implement more fine-grained | ||||
| 544 | ;; control a la non-t `debug-on-error' here. | ||||
| 545 | (cond | ||||
| 546 | ((ert-test-execution-info-ert-debug-on-error info) | ||||
| 547 | (apply (ert-test-execution-info-next-debugger info) debugger-args)) | ||||
| 548 | (t)) | ||||
| 549 | (funcall (ert-test-execution-info-exit-continuation info))))))) | ||||
| 550 | |||||
| 551 | (defun ert-run-test-internal (ert-test-execution-info) | ||||
| 552 | (lexical-let ((info ert-test-execution-info)) | ||||
| 553 | (setf (ert-test-execution-info-next-debugger info) debugger | ||||
| 554 | (ert-test-execution-info-ert-debug-on-error info) ert-debug-on-error) | ||||
| 555 | (catch 'ert-pass | ||||
| 556 | ;; For now, each test gets its own temp buffer and its own | ||||
| 557 | ;; window excursion, just to be safe. If this turns out to be | ||||
| 558 | ;; too expensive, we can remove it. | ||||
| 559 | (with-temp-buffer | ||||
| 560 | (save-window-excursion | ||||
| 561 | (let ((debugger (lambda (&rest debugger-args) | ||||
| 562 | (ert-run-test-debugger info debugger-args))) | ||||
| 563 | (debug-on-error t) | ||||
| 564 | (debug-on-quit t) | ||||
| 565 | ;; FIXME: Do we need to store the old binding of this | ||||
| 566 | ;; and consider it in `ert-run-test-debugger'? | ||||
| 567 | (debug-ignored-errors nil)) | ||||
| 568 | (funcall (ert-test-body (ert-test-execution-info-test info)))))) | ||||
| 569 | (ert-pass)) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 570 | (setf (ert-test-execution-info-result info) (make-ert-test-passed))) | |
| 571 | nil) | ||||
| 572 | |||||
| 573 | (defun ert-make-marker-in-messages-buffer () | ||||
| 574 | (with-current-buffer (get-buffer-create "*Messages*") | ||||
| 575 | (set-marker (make-marker) (point-max)))) | ||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 576 | ||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 577 | (defun ert-force-message-log-buffer-truncation () | |
| 578 | (with-current-buffer (get-buffer-create "*Messages*") | ||||
| 579 | ;; This is a reimplementation of this part of message_dolog() in xdisp.c: | ||||
| 580 | ;; if (NATNUMP (Vmessage_log_max)) | ||||
| 581 | ;; { | ||||
| 582 | ;; scan_newline (Z, Z_BYTE, BEG, BEG_BYTE, | ||||
| 583 | ;; -XFASTINT (Vmessage_log_max) - 1, 0); | ||||
| 584 | ;; del_range_both (BEG, BEG_BYTE, PT, PT_BYTE, 0); | ||||
| 585 | ;; } | ||||
| 586 | (when (and (integerp message-log-max) (>= message-log-max 0)) | ||||
| 587 | (let ((begin (point-min)) | ||||
| 588 | (end (save-excursion | ||||
| 589 | (goto-char (point-max)) | ||||
| 590 | (forward-line (- message-log-max)) | ||||
| 591 | (point)))) | ||||
| 592 | (delete-region begin end))))) | ||||
| 593 | |||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 594 | (defun ert-run-test (test) | |
| 595 | "Run TEST. Return the result and store it in TEST's `most-recent-result' slot." | ||||
| 596 | (setf (ert-test-most-recent-result test) nil) | ||||
| 597 | (block error | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 598 | (lexical-let* ((begin-marker (ert-make-marker-in-messages-buffer)) | |
| 599 | (info (make-ert-test-execution-info | ||||
| 600 | :test test | ||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 601 | :result (make-ert-test-aborted-with-non-local-exit) | |
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 602 | :exit-continuation (lambda () | |
| 603 | (return-from error nil))))) | ||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 604 | (unwind-protect | |
| 22b6ebee » | Christian Ohler | 2008-08-10 | 605 | (let ((message-log-max t)) | |
| 606 | (ert-run-test-internal info)) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 607 | (let ((result (ert-test-execution-info-result info))) | |
| 22b6ebee » | Christian Ohler | 2008-08-10 | 608 | (setf (ert-test-result-messages result) | |
| 609 | (with-current-buffer (get-buffer-create "*Messages*") | ||||
| 610 | (buffer-substring begin-marker (point-max)))) | ||||
| 611 | (ert-force-message-log-buffer-truncation) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 612 | (setf (ert-test-most-recent-result test) result))))) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 613 | (ert-test-most-recent-result test)) | |
| 614 | |||||
| 615 | |||||
| 616 | ;;; The `should' macros. | ||||
| 617 | |||||
| 618 | (eval-and-compile | ||||
| 619 | (defun ert-special-operator-p (thing) | ||||
| 620 | "Return non-nil if THING is a symbol naming a special operator." | ||||
| 621 | (and (symbolp thing) | ||||
| 622 | (let ((definition (indirect-function thing t))) | ||||
| 623 | (and (subrp definition) | ||||
| 624 | (eql (cdr (subr-arity definition)) 'unevalled))))) | ||||
| 625 | (defun ert-expand-should (whole form env inner-expander) | ||||
| 626 | "Helper function for the `should' macro and its variants. | ||||
| 627 | |||||
| 628 | Analyzes FORM and produces an expression that has the same | ||||
| 629 | semantics under evaluation but records additional debugging | ||||
| 630 | information. INNER-EXPANDER adds the actual checks specific to | ||||
| 631 | the particular variant of `should'." | ||||
| 632 | (let ((form (macroexpand form env))) | ||||
| 633 | ;; It's sort of a wart that `inner-expander' can't influence the | ||||
| 634 | ;; value the expansion returns. | ||||
| 635 | (cond | ||||
| 636 | ((atom form) | ||||
| 637 | (funcall inner-expander form `(list ',whole :form ',form :value ,form))) | ||||
| 638 | ((ert-special-operator-p (car form)) | ||||
| 639 | (let ((value (gensym "value-"))) | ||||
| 640 | `(let ((,value (make-symbol "ert-form-evaluation-aborted"))) | ||||
| 641 | ,(funcall inner-expander | ||||
| 642 | `(setq ,value ,form) | ||||
| 643 | `(list ',whole :form ',form :value ,value)) | ||||
| 644 | ,value))) | ||||
| 645 | (t | ||||
| 646 | (let ((fn-name (car form)) | ||||
| 647 | (arg-forms (cdr form))) | ||||
| 648 | (assert (or (symbolp fn-name) | ||||
| 649 | (and (consp fn-name) | ||||
| 650 | (eql (car fn-name) 'lambda) | ||||
| 651 | (listp (cdr fn-name))))) | ||||
| 652 | (let ((fn (gensym "fn-")) | ||||
| 653 | (args (gensym "args-")) | ||||
| 654 | (value (gensym "value-")) | ||||
| 655 | (default-value (gensym "ert-form-evaluation-aborted-"))) | ||||
| 656 | `(let ((,fn (function ,fn-name)) | ||||
| 657 | (,args (list ,@arg-forms))) | ||||
| 658 | (let ((,value ',default-value)) | ||||
| 659 | ,(funcall inner-expander | ||||
| 660 | `(setq ,value (apply ,fn ,args)) | ||||
| 661 | `(nconc (list ',whole) | ||||
| 662 | (list :form `(,,fn ,@,args)) | ||||
| 663 | (unless (eql ,value ',default-value) | ||||
| 664 | (list :value ,value)) | ||||
| 665 | (let ((-explainer- | ||||
| 666 | (and (symbolp ',fn-name) | ||||
| 667 | (get ',fn-name | ||||
| 668 | 'ert-explainer)))) | ||||
| 669 | (when -explainer- | ||||
| 670 | (list :explanation | ||||
| 671 | (apply -explainer- ,args)))))) | ||||
| 672 | ,value))))))))) | ||||
| 673 | |||||
| 674 | (defmacro* should (form &environment env) | ||||
| 675 | "Evaluate FORM. If it returns nil, abort the current test as failed. | ||||
| 676 | |||||
| 677 | Returns the value of FORM." | ||||
| 678 | (ert-expand-should `(should ,form) form env | ||||
| 679 | (lambda (inner-form form-description-form) | ||||
| 680 | `(unless ,inner-form | ||||
| 681 | (ert-fail ,form-description-form))))) | ||||
| 682 | |||||
| 683 | (defmacro* should-not (form &environment env) | ||||
| 684 | "Evaluate FORM. If it returns non-nil, abort the current test as failed. | ||||
| 685 | |||||
| 686 | Returns nil." | ||||
| 687 | (ert-expand-should `(should-not ,form) form env | ||||
| 688 | (lambda (inner-form form-description-form) | ||||
| 689 | `(unless (not ,inner-form) | ||||
| 690 | (ert-fail ,form-description-form))))) | ||||
| 691 | |||||
| 692 | (defun ert-should-error-handle-error (form-description-fn | ||||
| 693 | condition type exclude-subtypes test) | ||||
| 694 | "Helper function for `should-error'. | ||||
| 695 | |||||
| 696 | Determines whether CONDITION matches TYPE, EXCLUDE-SUBTYPES and | ||||
| 697 | TEST, and aborts the current test as failed if it doesn't." | ||||
| 698 | (let ((signalled-conditions (get (car condition) 'error-conditions)) | ||||
| 699 | (handled-conditions (etypecase type | ||||
| 700 | (list type) | ||||
| 701 | (symbol (list type))))) | ||||
| 702 | (assert signalled-conditions) | ||||
| 703 | (unless (intersection signalled-conditions handled-conditions) | ||||
| 704 | (ert-fail (append | ||||
| 705 | (funcall form-description-fn) | ||||
| 706 | (list | ||||
| 707 | :condition condition | ||||
| 708 | :fail-reason (concat "the error signalled did not" | ||||
| 709 | " have the expected type"))))) | ||||
| 710 | (when exclude-subtypes | ||||
| 711 | (unless (member (car condition) handled-conditions) | ||||
| 712 | (ert-fail (append | ||||
| 713 | (funcall form-description-fn) | ||||
| 714 | (list | ||||
| 715 | :condition condition | ||||
| 716 | :fail-reason (concat "the error signalled was a subtype" | ||||
| 717 | " of the expected type")))))) | ||||
| 718 | (unless (funcall test condition) | ||||
| 719 | (ert-fail (append | ||||
| 720 | (funcall form-description-fn) | ||||
| 721 | (list | ||||
| 722 | :condition condition | ||||
| 723 | :fail-reason "the error signalled did not pass the test")))))) | ||||
| 724 | |||||
| 725 | ;; FIXME: The expansion will evaluate the keyword args (if any) in | ||||
| 726 | ;; nonstandard order. | ||||
| 727 | (defmacro* should-error (form &rest keys &key type exclude-subtypes test | ||||
| 728 | &environment env) | ||||
| 729 | "Evaluate FORM. Unless it signals an error, abort the current test as failed. | ||||
| 730 | |||||
| 731 | The error signalled additionally needs to match TYPE and satisfy | ||||
| 732 | TEST. TYPE should be a condition name or a list of condition | ||||
| 733 | names. If EXCLUDE-SUBTYPES is nil, the error matches TYPE if one | ||||
| 734 | of its condition names is an element of TYPE. If | ||||
| 735 | EXCLUDE-SUBTYPES is non-nil, the error matches TYPE if it is an | ||||
| 736 | element of TYPE. TEST should be a predicate." | ||||
| 737 | ;; Returns a gensym named `ert-form-evaluation-aborted-XXX', but | ||||
| 738 | ;; that's a wart, so let's not document it. | ||||
| 739 | (unless type (setq type ''error)) | ||||
| 740 | (unless test (setq test '(lambda (condition) t))) | ||||
| 741 | (ert-expand-should | ||||
| 742 | `(should-error ,form ,@keys) | ||||
| 743 | form env | ||||
| 744 | (lambda (inner-form form-description-form) | ||||
| 745 | (let ((errorp (gensym "errorp")) | ||||
| 746 | (form-description-fn (gensym "form-description-fn-"))) | ||||
| 747 | `(let ((,errorp nil) | ||||
| 748 | (,form-description-fn (lambda () ,form-description-form))) | ||||
| 749 | (condition-case -condition- | ||||
| 750 | ,inner-form | ||||
| 751 | ;; We can't use ,type here because we want to evaluate it. | ||||
| 752 | (error | ||||
| 753 | (setq ,errorp t) | ||||
| 754 | (ert-should-error-handle-error ,form-description-fn | ||||
| 755 | -condition- | ||||
| 756 | ,type ,exclude-subtypes ,test) | ||||
| 757 | ;; It would make sense to have the `should-error' form | ||||
| 758 | ;; return the error in this case, but `ert-expand-should' | ||||
| 759 | ;; doesn't allow that at the moment. | ||||
| 760 | )) | ||||
| 761 | (unless ,errorp | ||||
| 762 | (ert-fail (append | ||||
| 763 | (funcall ,form-description-fn) | ||||
| 764 | (list | ||||
| 765 | :fail-reason "did not signal an error"))))))))) | ||||
| 766 | |||||
| 767 | |||||
| 768 | ;;; Explanation of `should' failures. | ||||
| 769 | |||||
| 770 | (defun ert-proper-list-p (x) | ||||
| 771 | "Return non-nil if X is a proper list, nil otherwise." | ||||
| 772 | (loop | ||||
| 773 | for firstp = t then nil | ||||
| 774 | for fast = x then (cddr fast) | ||||
| 775 | for slow = x then (cdr slow) do | ||||
| 776 | (when (null fast) (return t)) | ||||
| 777 | (when (not (consp fast)) (return nil)) | ||||
| 778 | (when (null (cdr fast)) (return t)) | ||||
| 779 | (when (not (consp (cdr fast))) (return nil)) | ||||
| 780 | (when (and (not firstp) (eq fast slow)) (return nil)))) | ||||
| 781 | |||||
| 782 | (defun ert-explain-not-equal (a b) | ||||
| 783 | "Return a programmer-readable explanation of why A and B are not `equal'. | ||||
| 784 | |||||
| 785 | Returns nil if they are equal." | ||||
| 786 | (if (not (equal (type-of a) (type-of b))) | ||||
| 787 | `(different-types ,a ,b) | ||||
| 788 | (etypecase a | ||||
| 789 | (cons | ||||
| 790 | (let ((a-proper-p (ert-proper-list-p a)) | ||||
| 791 | (b-proper-p (ert-proper-list-p b))) | ||||
| 792 | (if (not (eql (not a-proper-p) (not b-proper-p))) | ||||
| 793 | `(one-list-proper-one-improper ,a ,b) | ||||
| 794 | (if a-proper-p | ||||
| 795 | (if (not (equal (length a) (length b))) | ||||
| 796 | ;; This would be even more helpful if it showed | ||||
| 797 | ;; something like what `set-difference' would | ||||
| 798 | ;; return. | ||||
| 799 | `(proper-lists-of-different-length ,a ,b) | ||||
| 800 | (loop for i from 0 | ||||
| 801 | for ai in a | ||||
| 802 | for bi in b | ||||
| 803 | for xi = (ert-explain-not-equal ai bi) | ||||
| 804 | do (when xi (return `(list-elt ,i ,xi))))) | ||||
| 805 | (let ((car-x (ert-explain-not-equal (car a) (car b)))) | ||||
| 806 | (if car-x | ||||
| 807 | `(car ,car-x) | ||||
| 808 | (let ((cdr-x (ert-explain-not-equal (cdr a) (cdr b)))) | ||||
| 809 | (if cdr-x | ||||
| 810 | `(cdr ,cdr-x)) | ||||
| 811 | nil))))))) | ||||
| 812 | (array (if (not (equal (length a) (length b))) | ||||
| 813 | `(arrays-of-different-length ,a ,b) | ||||
| 814 | (loop for i from 0 | ||||
| 815 | for ai across a | ||||
| 816 | for bi across b | ||||
| 817 | for xi = (ert-explain-not-equal ai bi) | ||||
| 818 | do (when xi (return `(array-elt ,i ,xi)))))) | ||||
| 819 | (atom (if (not (equal a b)) | ||||
| 820 | `(different-atoms ,a ,b) | ||||
| 821 | nil))))) | ||||
| 822 | (put 'equal 'ert-explainer 'ert-explain-not-equal) | ||||
| 823 | |||||
| 824 | |||||
| 825 | ;;; Results display. | ||||
| 826 | |||||
| 827 | ;; The data structure that contains the set of tests being executed | ||||
| 828 | ;; during one particular test run, their results, the state of the | ||||
| 829 | ;; execution, and some statistics. | ||||
| 830 | ;; | ||||
| 831 | ;; The data about results and expected results of tests may seem | ||||
| 832 | ;; redundant here, since the test objects also carry such information. | ||||
| 833 | ;; However, the information in the test objects may be more recent, it | ||||
| 834 | ;; may correspond to a different test run. We need the information | ||||
| 835 | ;; that corresponds to this run in order to be able to update the | ||||
| 836 | ;; statistics correctly when a test is re-run interactively and has a | ||||
| 837 | ;; different result than before. | ||||
| 838 | (defstruct ert-stats | ||||
| 839 | (selector (assert nil)) | ||||
| 840 | ;; The tests, in order. | ||||
| 841 | (tests (assert nil) :type vector) | ||||
| 842 | ;; A map of test names (or the test objects themselves for unnamed | ||||
| 843 | ;; tests) to indices into the `tests' vector. | ||||
| 844 | (test-map (assert nil) :type hash-table) | ||||
| 845 | ;; The results of the tests during this run, in order. | ||||
| 846 | (test-results (assert nil) :type vector) | ||||
| 847 | ;; The expected result types of the tests, in order. | ||||
| 848 | (test-results-expected (assert nil) :type vector) | ||||
| 849 | (total (assert nil)) | ||||
| 850 | (passed-expected 0) | ||||
| 851 | (passed-unexpected 0) | ||||
| 852 | (failed-expected 0) | ||||
| 853 | (failed-unexpected 0) | ||||
| 854 | (error-expected 0) | ||||
| 855 | (error-unexpected 0) | ||||
| 856 | (start-time (assert nil)) | ||||
| 857 | (end-time nil) | ||||
| 858 | (aborted-p nil) | ||||
| 859 | (current-test nil)) | ||||
| 860 | |||||
| 861 | ;; An entry in the results buffer ewoc. There is one entry per test. | ||||
| 862 | (defstruct ert-ewoc-entry | ||||
| 863 | (test (assert nil)) | ||||
| 864 | (result nil) | ||||
| 865 | ;; If the result of this test was expected, its ewoc entry is hidden | ||||
| 866 | ;; initially. | ||||
| 867 | (hidden-p (assert nil)) | ||||
| 868 | ;; An ewoc entry may be collapsed to hide details such as the error | ||||
| 869 | ;; condition. | ||||
| 870 | ;; | ||||
| 871 | ;; I'm not sure the ability to expand and collapse entries is still | ||||
| 872 | ;; a useful feature. | ||||
| 873 | (expanded-p t) | ||||
| 874 | ;; By default, the ewoc entry presents the error condition with | ||||
| 875 | ;; certain limits on how much to print (`print-level', | ||||
| 876 | ;; `print-length'). The user can interactively switch to a set of | ||||
| 877 | ;; higher limits. | ||||
| 878 | (extended-printer-limits-p nil)) | ||||
| 879 | |||||
| 880 | ;; Variables local to the results buffer. | ||||
| 881 | |||||
| 882 | ;; The ewoc. | ||||
| 883 | (defvar ert-results-ewoc) | ||||
| 884 | ;; The stats object. | ||||
| 885 | (defvar ert-results-stats) | ||||
| 886 | ;; A string with one character per test. Each character represents | ||||
| 887 | ;; the result of the corresponding test. The string is displayed near | ||||
| 888 | ;; the top of the buffer and serves as a progress bar. | ||||
| 889 | (defvar ert-results-progress-bar-string) | ||||
| 890 | ;; The position where the progress bar button begins. | ||||
| 891 | (defvar ert-results-progress-bar-button-begin) | ||||
| 892 | ;; The test result listener that updates the buffer when tests are run. | ||||
| 893 | (defvar ert-results-listener) | ||||
| 894 | |||||
| 895 | ;; The same as `ert-results-stats', but dynamically bound. Used for | ||||
| 896 | ;; the mode line progress indicator. | ||||
| 897 | (defvar ert-current-run-stats nil) | ||||
| 898 | |||||
| 899 | (defun ert-format-time-iso8601 (time) | ||||
| 900 | "Format TIME in the particular variant of ISO 8601 used for timestamps in ERT." | ||||
| 901 | (format-time-string "%Y-%m-%d %T%z" time)) | ||||
| 902 | |||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 903 | (defun ert-insert-test-name-button (test-name) | |
| cf126a81 » | Christian Ohler | 2008-08-10 | 904 | (insert-text-button (format "%S" test-name) | |
| 905 | :type 'ert-test-name-button | ||||
| 906 | 'ert-test-name test-name)) | ||||
| 907 | |||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 908 | (defun ert-results-update-ewoc-hf (ewoc stats) | |
| 909 | "Update the header and footer of EWOC to show certain information from STATS. | ||||
| 910 | |||||
| 911 | Also sets `ert-results-progress-bar-button-begin'." | ||||
| 912 | (let ((run-count (+ (ert-stats-passed-expected stats) | ||||
| 913 | (ert-stats-passed-unexpected stats) | ||||
| 914 | (ert-stats-failed-expected stats) | ||||
| 915 | (ert-stats-failed-unexpected stats) | ||||
| 916 | (ert-stats-error-expected stats) | ||||
| 917 | (ert-stats-error-unexpected stats))) | ||||
| 918 | (results-buffer (current-buffer))) | ||||
| 919 | (ewoc-set-hf | ||||
| 920 | ewoc | ||||
| 921 | ;; header | ||||
| 922 | (with-temp-buffer | ||||
| 923 | (insert "Selector: ") | ||||
| 924 | (ert-insert-human-readable-selector (ert-stats-selector stats)) | ||||
| 925 | (insert "\n") | ||||
| 926 | (insert | ||||
| 927 | (format (concat "Passed: %s (%s unexpected)\n" | ||||
| 928 | "Failed: %s (%s unexpected)\n" | ||||
| 929 | "Error: %s (%s unexpected)\n" | ||||
| 930 | "Total: %s/%s\n\n") | ||||
| 931 | (+ (ert-stats-passed-expected stats) | ||||
| 932 | (ert-stats-passed-unexpected stats)) | ||||
| 933 | (ert-stats-passed-unexpected stats) | ||||
| 934 | (+ (ert-stats-failed-expected stats) | ||||
| 935 | (ert-stats-failed-unexpected stats)) | ||||
| 936 | (ert-stats-failed-unexpected stats) | ||||
| 937 | (+ (ert-stats-error-expected stats) | ||||
| 938 | (ert-stats-error-unexpected stats)) | ||||
| 939 | (ert-stats-error-unexpected stats) | ||||
| 940 | run-count | ||||
| 941 | (ert-stats-total stats))) | ||||
| 942 | (insert | ||||
| 943 | (format "Started at: %s\n" | ||||
| 944 | (ert-format-time-iso8601 (ert-stats-start-time stats)))) | ||||
| 945 | ;; FIXME: This is ugly. Need to properly define invariants of | ||||
| 946 | ;; the `stats' data structure. | ||||
| 947 | (let ((state (cond ((ert-stats-aborted-p stats) | ||||
| 948 | 'aborted) | ||||
| 949 | ((ert-stats-current-test stats) | ||||
| 950 | 'running) | ||||
| 951 | ((ert-stats-end-time stats) | ||||
| 952 | 'finished) | ||||
| 953 | (t | ||||
| 954 | 'preparing)))) | ||||
| 955 | (ecase state | ||||
| 956 | (preparing | ||||
| 957 | (insert "")) | ||||
| 958 | (aborted | ||||
| 959 | (cond ((ert-stats-current-test stats) | ||||
| 960 | (insert "Aborted during test: ") | ||||
| cf126a81 » | Christian Ohler | 2008-08-10 | 961 | (ert-insert-test-name-button | |
| 962 | (ert-test-name (ert-stats-current-test stats)))) | ||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 963 | (t | |
| 964 | (insert "Aborted.")))) | ||||
| 965 | (running | ||||
| 966 | (assert (ert-stats-current-test stats)) | ||||
| 967 | (insert "Running test: ") | ||||
| cf126a81 » | Christian Ohler | 2008-08-10 | 968 | (ert-insert-test-name-button (ert-test-name | |
| 969 | (ert-stats-current-test stats)))) | ||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 970 | (finished | |
| 971 | (assert (not (ert-stats-current-test stats))) | ||||
| 972 | (insert "Finished."))) | ||||
| 973 | (insert "\n") | ||||
| 974 | (if (ert-stats-end-time stats) | ||||
| 975 | (insert | ||||
| 976 | (format "%s%s\n" | ||||
| 977 | (if (ert-stats-aborted-p stats) | ||||
| 978 | "Aborted at: " | ||||
| 979 | "Finished at: ") | ||||
| 980 | (ert-format-time-iso8601 (ert-stats-end-time stats)))) | ||||
| 981 | (insert "\n")) | ||||
| 982 | (insert "\n")) | ||||
| 983 | (let ((progress-bar-string (with-current-buffer results-buffer | ||||
| 984 | ert-results-progress-bar-string))) | ||||
| 985 | (let ((progress-bar-button-begin | ||||
| 986 | (insert-text-button (substring progress-bar-string 0 run-count) | ||||
| 987 | :type 'ert-results-progress-bar-button))) | ||||
| 988 | (with-current-buffer results-buffer | ||||
| 989 | (set (make-local-variable 'ert-results-progress-bar-button-begin) | ||||
| 990 | progress-bar-button-begin))) | ||||
| 991 | (insert (substring progress-bar-string run-count))) | ||||
| 992 | (insert "\n\n") | ||||
| 993 | (buffer-string)) | ||||
| 994 | ;; footer | ||||
| 995 | ;; | ||||
| 996 | ;; We actually want an empty footer, but that would trigger a bug | ||||
| 997 | ;; in ewoc, sometimes clearing the entire buffer. | ||||
| 998 | "\n"))) | ||||
| 999 | |||||
| 1000 | (defun ert-results-update-stats-display (ewoc stats) | ||||
| 1001 | "Update EWOC and the mode line to show data from STATS." | ||||
| 1002 | (ert-results-update-ewoc-hf ewoc stats) | ||||
| 1003 | (force-mode-line-update) | ||||
| 1004 | (redisplay t)) | ||||
| 1005 | |||||
| 1006 | (defun ert-char-for-test-result (result expectedp) | ||||
| 1007 | "Return a character that represents the test result RESULT." | ||||
| 1008 | (let ((char | ||||
| 1009 | (etypecase result | ||||
| 1010 | (ert-test-passed ?.) | ||||
| 1011 | (ert-test-failed ?f) | ||||
| 1012 | (ert-test-error ?e) | ||||
| 1013 | (null ?-) | ||||
| 1014 | (ert-test-aborted-with-non-local-exit ?a)))) | ||||
| 1015 | (if expectedp | ||||
| 1016 | char | ||||
| 1017 | (upcase char)))) | ||||
| 1018 | |||||
| 1019 | (defun ert-string-for-test-result (result expectedp) | ||||
| 1020 | "Return a string that represents the test result RESULT." | ||||
| 1021 | (etypecase result | ||||
| 1022 | (ert-test-passed "passed") | ||||
| 1023 | (ert-test-failed "failed") | ||||
| 1024 | (ert-test-error "error") | ||||
| 1025 | (null "unknown") | ||||
| 1026 | (ert-test-aborted-with-non-local-exit "aborted"))) | ||||
| 1027 | |||||
| 1028 | (defun ert-tests-running-mode-line-indicator () | ||||
| 1029 | (let* ((stats ert-current-run-stats) | ||||
| 1030 | (tests-total (ert-stats-total stats)) | ||||
| 1031 | (tests-completed (+ (ert-stats-passed-expected stats) | ||||
| 1032 | (ert-stats-passed-unexpected stats) | ||||
| 1033 | (ert-stats-failed-expected stats) | ||||
| 1034 | (ert-stats-failed-unexpected stats) | ||||
| 1035 | (ert-stats-error-expected stats) | ||||
| 1036 | (ert-stats-error-unexpected stats)))) | ||||
| 1037 | (if (>= tests-completed tests-total) | ||||
| 1038 | (format " ERT(%s/%s,finished)" tests-completed tests-total) | ||||
| 1039 | (format " ERT(%s/%s):%s" | ||||
| 1040 | (1+ tests-completed) | ||||
| 1041 | tests-total | ||||
| 1042 | (if (null (ert-stats-current-test stats)) | ||||
| 1043 | "?" | ||||
| 1044 | (format "%S" | ||||
| 1045 | (ert-test-name (ert-stats-current-test stats)))))))) | ||||
| 1046 | |||||
| 1047 | (defun ert-pp-with-indentation-and-newline (object) | ||||
| 1048 | "Pretty-print OBJECT, indenting it to the current column of point. | ||||
| 1049 | Ensures a final newline is inserted." | ||||
| 1050 | (let ((begin (point))) | ||||
| 1051 | (pp object (current-buffer)) | ||||
| 1052 | (unless (bolp) (insert "\n")) | ||||
| 1053 | (save-excursion | ||||
| 1054 | (goto-char begin) | ||||
| 1055 | (indent-sexp)))) | ||||
| 1056 | |||||
| 1057 | (defun ert-print-test-for-ewoc (entry) | ||||
| 1058 | "The ewoc print function for ewoc test entries." | ||||
| 1059 | (let* ((test (ert-ewoc-entry-test entry)) | ||||
| 1060 | (result (ert-ewoc-entry-result entry)) | ||||
| 1061 | (hiddenp (ert-ewoc-entry-hidden-p entry)) | ||||
| 1062 | (expandedp (ert-ewoc-entry-expanded-p entry)) | ||||
| 1063 | (extended-printer-limits-p (ert-ewoc-entry-extended-printer-limits-p | ||||
| 1064 | entry))) | ||||
| 1065 | (cond (hiddenp) | ||||
| 1066 | (t | ||||
| 1067 | (insert-text-button (format "%c" | ||||
| 1068 | (ert-char-for-test-result | ||||
| 1069 | result | ||||
| 1070 | (ert-test-result-expected-p test | ||||
| 1071 | result))) | ||||
| 1072 | :type 'ert-results-expand-collapse-button) | ||||
| 1073 | (insert " ") | ||||
| cf126a81 » | Christian Ohler | 2008-08-10 | 1074 | (ert-insert-test-name-button (ert-test-name test)) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1075 | (insert "\n") | |
| 1076 | (when (and expandedp (not (eql result 'nil))) | ||||
| 1077 | (etypecase result | ||||
| 1078 | (ert-test-passed | ||||
| 1079 | (insert " passed\n") | ||||
| 1080 | (insert "")) | ||||
| 1081 | (ert-test-result-with-condition | ||||
| 1082 | (insert " ") | ||||
| 1083 | (let ((print-escape-newlines t) | ||||
| 1084 | (print-level (if extended-printer-limits-p 10 5)) | ||||
| 1085 | (print-length (if extended-printer-limits-p 100 10))) | ||||
| 1086 | (let ((begin (point))) | ||||
| 1087 | (ert-pp-with-indentation-and-newline | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1088 | (ert-test-result-with-condition-condition result)) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1089 | (save-restriction | |
| 1090 | (narrow-to-region begin (point)) | ||||
| 1091 | ;; Inhibit optimization in `debugger-make-xrefs' | ||||
| 1092 | ;; that sometimes inserts unrelated backtrace | ||||
| 1093 | ;; info into our buffer. | ||||
| 1094 | (let ((debugger-previous-backtrace nil)) | ||||
| 1095 | (debugger-make-xrefs)))))) | ||||
| 1096 | (ert-test-aborted-with-non-local-exit | ||||
| 1097 | (insert " aborted\n"))) | ||||
| 1098 | (insert "\n"))))) | ||||
| 1099 | nil) | ||||
| 1100 | |||||
| 1101 | (defun ert-setup-results-buffer (stats listener buffer-name) | ||||
| 1102 | "Set up a test results buffer." | ||||
| 1103 | (unless buffer-name (setq buffer-name "*ert*")) | ||||
| 1104 | (let ((buffer (let ((default-major-mode 'fundamental-mode)) | ||||
| 1105 | (get-buffer-create buffer-name)))) | ||||
| 1106 | (with-current-buffer buffer | ||||
| 1107 | (setq buffer-read-only t) | ||||
| 1108 | (let ((inhibit-read-only t)) | ||||
| 1109 | (buffer-disable-undo) | ||||
| 1110 | (erase-buffer) | ||||
| 1111 | (ert-results-mode) | ||||
| 1112 | (set (make-local-variable 'ert-results-ewoc) | ||||
| 1113 | (ewoc-create 'ert-print-test-for-ewoc nil nil t)) | ||||
| 1114 | (set (make-local-variable 'ert-results-stats) stats) | ||||
| 1115 | (set (make-local-variable 'ert-results-progress-bar-string) | ||||
| 1116 | (make-string (ert-stats-total stats) | ||||
| 1117 | (ert-char-for-test-result nil t))) | ||||
| 1118 | (set (make-local-variable 'ert-results-listener) listener) | ||||
| 1119 | (ert-results-update-ewoc-hf ert-results-ewoc ert-results-stats) | ||||
| 1120 | (goto-char (1- (point-max))) | ||||
| 1121 | buffer)))) | ||||
| 1122 | |||||
| 1123 | (defun ert-run-or-rerun-test (stats test listener) | ||||
| 1124 | "Run the single test TEST and record the result using STATS and LISTENER." | ||||
| 1125 | (let ((ert-current-run-stats stats) | ||||
| 1126 | (pos (ert-stats-test-index stats test)) | ||||
| 1127 | (results (ert-stats-test-results stats)) | ||||
| 1128 | (expected (ert-stats-test-results-expected stats))) | ||||
| 1129 | ;; Adjust stats to remove previous result. | ||||
| 1130 | (if (aref expected pos) | ||||
| 1131 | (etypecase (aref results pos) | ||||
| 1132 | (ert-test-passed (decf (ert-stats-passed-expected stats))) | ||||
| 1133 | (ert-test-failed (decf (ert-stats-failed-expected stats))) | ||||
| 1134 | (ert-test-error (decf (ert-stats-error-expected stats))) | ||||
| 1135 | (null) | ||||
| 1136 | (ert-test-aborted-with-non-local-exit)) | ||||
| 1137 | (etypecase (aref results pos) | ||||
| 1138 | (ert-test-passed (decf (ert-stats-passed-unexpected stats))) | ||||
| 1139 | (ert-test-failed (decf (ert-stats-failed-unexpected stats))) | ||||
| 1140 | (ert-test-error (decf (ert-stats-error-unexpected stats))) | ||||
| 1141 | (null) | ||||
| 1142 | (ert-test-aborted-with-non-local-exit))) | ||||
| 1143 | (setf (aref results pos) nil) | ||||
| 1144 | ;; Call listener after setting/before resetting | ||||
| 1145 | ;; (ert-stats-current-test stats); the listener might refresh the | ||||
| 1146 | ;; mode line display, and if the value is not set yet/any more | ||||
| 1147 | ;; during this refresh, the mode line will flicker unnecessarily. | ||||
| 1148 | (setf (ert-stats-current-test stats) test) | ||||
| 1149 | (funcall listener 'test-started stats test) | ||||
| 1150 | (setf (ert-test-most-recent-result test) nil) | ||||
| 1151 | (unwind-protect | ||||
| 1152 | (ert-run-test test) | ||||
| 1153 | (let* ((result (ert-test-most-recent-result test)) | ||||
| 1154 | (expectedp (typep result (ert-test-expected-result-type test)))) | ||||
| 1155 | ;; Adjust stats to add new result. | ||||
| 1156 | (if expectedp | ||||
| 1157 | (etypecase result | ||||
| 1158 | (ert-test-passed (incf (ert-stats-passed-expected stats))) | ||||
| 1159 | (ert-test-failed (incf (ert-stats-failed-expected stats))) | ||||
| 1160 | (ert-test-error (incf (ert-stats-error-expected stats))) | ||||
| 1161 | (null) | ||||
| 1162 | (ert-test-aborted-with-non-local-exit)) | ||||
| 1163 | (etypecase result | ||||
| 1164 | (ert-test-passed (incf (ert-stats-passed-unexpected stats))) | ||||
| 1165 | (ert-test-failed (incf (ert-stats-failed-unexpected stats))) | ||||
| 1166 | (ert-test-error (incf (ert-stats-error-unexpected stats))) | ||||
| 1167 | (null) | ||||
| 1168 | (ert-test-aborted-with-non-local-exit))) | ||||
| 1169 | (setf (aref results pos) result | ||||
| 1170 | (aref expected pos) expectedp) | ||||
| 1171 | (funcall listener 'test-ended stats test result)) | ||||
| 1172 | (setf (ert-stats-current-test stats) nil)))) | ||||
| 1173 | |||||
| 1174 | (defun ert-run-tests (selector listener) | ||||
| 1175 | "Run the tests specified by SELECTOR, sending progress updates to LISTENER." | ||||
| 1176 | (let* ((tests (coerce (ert-select-tests selector t) 'vector)) | ||||
| 1177 | (map (let ((map (make-hash-table :size (length tests)))) | ||||
| 1178 | (loop for i from 0 | ||||
| 1179 | for test across tests | ||||
| 1180 | for key = (or (ert-test-name test) test) do | ||||
| 1181 | (assert (not (gethash key map))) | ||||
| 1182 | (setf (gethash key map) i)) | ||||
| 1183 | map)) | ||||
| 1184 | (stats (make-ert-stats :selector selector | ||||
| 1185 | :tests tests | ||||
| 1186 | :test-map map | ||||
| 1187 | :test-results (make-vector (length tests) nil) | ||||
| 1188 | :test-results-expected (make-vector | ||||
| 1189 | (length tests) nil) | ||||
| 1190 | :total (length tests) | ||||
| 1191 | :start-time (current-time)))) | ||||
| 1192 | (funcall listener 'run-started stats) | ||||
| 1193 | (let ((abortedp t)) | ||||
| 1194 | (let ((ert-current-run-stats stats)) | ||||
| 1195 | (force-mode-line-update) | ||||
| 1196 | (unwind-protect | ||||
| 1197 | (progn | ||||
| 1198 | (loop for test across tests do | ||||
| 1199 | (ert-run-or-rerun-test stats test listener)) | ||||
| 1200 | (setq abortedp nil)) | ||||
| 1201 | (setf (ert-stats-aborted-p stats) abortedp) | ||||
| 1202 | (setf (ert-stats-end-time stats) (current-time)) | ||||
| 1203 | (funcall listener 'run-ended stats abortedp))) | ||||
| 1204 | stats))) | ||||
| 1205 | |||||
| 1206 | (defun ert-stats-test-index (stats test) | ||||
| 1207 | "Return the index of TEST in the run represented by STATS." | ||||
| 1208 | (gethash (or (ert-test-name test) test) (ert-stats-test-map stats))) | ||||
| 1209 | |||||
| 1210 | (defvar ert-selector-history nil | ||||
| 1211 | "List of recent test selectors read from terminal.") | ||||
| 1212 | |||||
| 1213 | ;; Should OUTPUT-BUFFER-NAME and MESSAGE-FN really be arguments here? | ||||
| 1214 | ;; They are needed only for our automated self-tests at the moment. | ||||
| 1215 | ;; Or should there be some other mechanism? | ||||
| 1216 | ;;;###autoload | ||||
| 1217 | (defun ert-run-tests-interactively (selector | ||||
| 1218 | &optional output-buffer-name message-fn) | ||||
| 1219 | "Run the tests specified by SELECTOR and display the results in a buffer." | ||||
| 1220 | (interactive | ||||
| 1221 | (list (let ((default (if ert-selector-history | ||||
| 1222 | (first ert-selector-history) | ||||
| 1223 | "t"))) | ||||
| 1224 | (read-from-minibuffer (if (null default) | ||||
| 1225 | "Run tests: " | ||||
| 1226 | (format "Run tests (default %s): " default)) | ||||
| 1227 | nil nil t 'ert-selector-history | ||||
| 1228 | default nil)) | ||||
| 1229 | nil)) | ||||
| 1230 | (unless message-fn (setq message-fn 'message)) | ||||
| 1231 | (lexical-let ((output-buffer-name output-buffer-name) | ||||
| 1232 | buffer | ||||
| 1233 | listener | ||||
| 1234 | (message-fn message-fn)) | ||||
| 1235 | (setq listener | ||||
| 1236 | (lambda (event-type &rest event-args) | ||||
| 1237 | (ecase event-type | ||||
| 1238 | (run-started | ||||
| 1239 | (destructuring-bind (stats) event-args | ||||
| 1240 | (setq buffer (ert-setup-results-buffer stats | ||||
| 1241 | listener | ||||
| 1242 | output-buffer-name)) | ||||
| 1243 | (pop-to-buffer buffer))) | ||||
| 1244 | (run-ended | ||||
| 1245 | (destructuring-bind (stats abortedp) event-args | ||||
| 1246 | (funcall message-fn | ||||
| 1247 | "%sRan %s tests, %s results were as expected%s" | ||||
| 1248 | (if (not abortedp) | ||||
| 1249 | "" | ||||
| 1250 | "Aborted: ") | ||||
| 1251 | (ert-stats-total stats) | ||||
| 1252 | (+ (ert-stats-passed-expected stats) | ||||
| 1253 | (ert-stats-failed-expected stats) | ||||
| 1254 | (ert-stats-error-expected stats)) | ||||
| 1255 | (let ((unexpected | ||||
| 1256 | (+ (ert-stats-passed-unexpected stats) | ||||
| 1257 | (ert-stats-failed-unexpected stats) | ||||
| 1258 | (ert-stats-error-unexpected stats)))) | ||||
| 1259 | (if (zerop unexpected) | ||||
| 1260 | "" | ||||
| 1261 | (format ", %s unexpected" unexpected)))) | ||||
| 1262 | (ert-results-update-stats-display (with-current-buffer buffer | ||||
| 1263 | ert-results-ewoc) | ||||
| 1264 | stats))) | ||||
| 1265 | (test-started | ||||
| 1266 | (destructuring-bind (stats test) event-args | ||||
| 1267 | (with-current-buffer buffer | ||||
| 1268 | (let* ((ewoc ert-results-ewoc) | ||||
| 1269 | (pos (ert-stats-test-index stats test)) | ||||
| 1270 | (node (ewoc-nth ewoc pos))) | ||||
| 1271 | (unless node | ||||
| 1272 | ;; FIXME: How expensive is this assertion? | ||||
| 1273 | (assert (or (zerop pos) (ewoc-nth ewoc (1- pos))) | ||||
| 1274 | t) | ||||
| 1275 | (setq node (ewoc-enter-last | ||||
| 1276 | ewoc | ||||
| 1277 | (make-ert-ewoc-entry :test test | ||||
| 1278 | :hidden-p t)))) | ||||
| 1279 | (setf (ert-ewoc-entry-test (ewoc-data node)) test) | ||||
| 1280 | (setf (ert-ewoc-entry-result (ewoc-data node)) nil) | ||||
| 1281 | (aset ert-results-progress-bar-string pos | ||||
| 1282 | (ert-char-for-test-result nil t)) | ||||
| 1283 | (ert-results-update-stats-display ewoc stats) | ||||
| 1284 | (ewoc-invalidate ewoc node))))) | ||||
| 1285 | (test-ended | ||||
| 1286 | (destructuring-bind (stats test result) event-args | ||||
| 1287 | (with-current-buffer buffer | ||||
| 1288 | (let* ((ewoc ert-results-ewoc) | ||||
| 1289 | (pos (ert-stats-test-index stats test)) | ||||
| 1290 | (node (ewoc-nth ewoc pos))) | ||||
| 1291 | (setf (ert-ewoc-entry-result (ewoc-data node)) result) | ||||
| 1292 | (when (ert-ewoc-entry-hidden-p (ewoc-data node)) | ||||
| 1293 | (setf (ert-ewoc-entry-hidden-p (ewoc-data node)) | ||||
| 1294 | (ert-test-result-expected-p test result))) | ||||
| 1295 | (aset ert-results-progress-bar-string pos | ||||
| 1296 | (ert-char-for-test-result result | ||||
| 1297 | (ert-test-result-expected-p | ||||
| 1298 | test result))) | ||||
| 1299 | (ert-results-update-stats-display ewoc stats) | ||||
| 1300 | (ewoc-invalidate ewoc node)))))))) | ||||
| 1301 | (ert-run-tests | ||||
| 1302 | selector | ||||
| 1303 | listener))) | ||||
| 1304 | |||||
| 1305 | (defvar ert-batch-backtrace-right-margin 70 | ||||
| 1306 | "*The maximum line length for printing backtraces in `ert-run-tests-batch'.") | ||||
| 1307 | |||||
| 1308 | (defun ert-run-tests-batch (selector) | ||||
| 1309 | "Run the tests specified by SELECTOR, printing results to the terminal. | ||||
| 1310 | |||||
| 1311 | Returns the stats object." | ||||
| 1312 | (ert-run-tests | ||||
| 1313 | selector | ||||
| 1314 | (lambda (event-type &rest event-args) | ||||
| 1315 | (ecase event-type | ||||
| 1316 | (run-started | ||||
| 1317 | (destructuring-bind (stats) event-args | ||||
| 1318 | (message "Running %s tests (%s)" | ||||
| 1319 | (length (ert-stats-tests stats)) | ||||
| 1320 | (ert-format-time-iso8601 (ert-stats-start-time stats))))) | ||||
| 1321 | (run-ended | ||||
| 1322 | (destructuring-bind (stats abortedp) event-args | ||||
| 1323 | (let ((unexpected (+ (ert-stats-passed-unexpected stats) | ||||
| 1324 | (ert-stats-failed-unexpected stats) | ||||
| 1325 | (ert-stats-error-unexpected stats)))) | ||||
| 1326 | (message "\n%sRan %s tests, %s results were as expected%s (%s)\n" | ||||
| 1327 | (if (not abortedp) | ||||
| 1328 | "" | ||||
| 1329 | "Aborted: ") | ||||
| 1330 | (ert-stats-total stats) | ||||
| 1331 | (+ (ert-stats-passed-expected stats) | ||||
| 1332 | (ert-stats-failed-expected stats) | ||||
| 1333 | (ert-stats-error-expected stats)) | ||||
| 1334 | (if (zerop unexpected) | ||||
| 1335 | "" | ||||
| 1336 | (format ", %s unexpected" unexpected)) | ||||
| 1337 | (ert-format-time-iso8601 (ert-stats-end-time stats))) | ||||
| 1338 | (unless (zerop unexpected) | ||||
| 1339 | (message "%s unexpected results:" unexpected) | ||||
| 1340 | (loop for test across (ert-stats-tests stats) | ||||
| 1341 | for result = (ert-test-most-recent-result test) do | ||||
| 1342 | (when (not (ert-test-result-expected-p test result)) | ||||
| 1343 | (message "%9s %S" | ||||
| 1344 | (ert-string-for-test-result result nil) | ||||
| 1345 | (ert-test-name test)))) | ||||
| 1346 | (message "%s" ""))))) | ||||
| 1347 | (test-started | ||||
| 1348 | ) | ||||
| 1349 | (test-ended | ||||
| 1350 | (destructuring-bind (stats test result) event-args | ||||
| 1351 | (etypecase result | ||||
| 1352 | (ert-test-passed) | ||||
| 1353 | (ert-test-result-with-condition | ||||
| 1354 | (message "Test %S backtrace:" (ert-test-name test)) | ||||
| 1355 | (with-temp-buffer | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1356 | (ert-print-backtrace (ert-test-result-with-condition-backtrace result)) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1357 | (goto-char (point-min)) | |
| 1358 | (while (not (eobp)) | ||||
| 1359 | (let ((start (point)) | ||||
| 1360 | (end (progn (end-of-line) (point)))) | ||||
| 1361 | (setq end (min end | ||||
| 1362 | (+ start ert-batch-backtrace-right-margin))) | ||||
| 1363 | (message "%s" (buffer-substring-no-properties | ||||
| 1364 | start end))) | ||||
| 1365 | (forward-line 1))) | ||||
| 1366 | (with-temp-buffer | ||||
| 1367 | (insert " ") | ||||
| 1368 | (let ((print-escape-newlines t) | ||||
| 1369 | (print-level 5) | ||||
| 1370 | (print-length 10)) | ||||
| 1371 | (let ((begin (point))) | ||||
| 1372 | (ert-pp-with-indentation-and-newline | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1373 | (ert-test-result-with-condition-condition result)))) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1374 | (goto-char (1- (point-max))) | |
| 1375 | (assert (looking-at "\n")) | ||||
| 1376 | (delete-char 1) | ||||
| 1377 | (message "Test %S condition:" (ert-test-name test)) | ||||
| 1378 | (message "%s" (buffer-string)))) | ||||
| 1379 | (ert-test-aborted-with-non-local-exit)) | ||||
| 1380 | (let* ((max (prin1-to-string (length (ert-stats-tests stats)))) | ||||
| 1381 | (format-string (concat "%9s %" | ||||
| 1382 | (prin1-to-string (length max)) | ||||
| 1383 | "s/" max " %S"))) | ||||
| 1384 | (message format-string | ||||
| 1385 | (ert-string-for-test-result result | ||||
| 1386 | (ert-test-result-expected-p | ||||
| 1387 | test result)) | ||||
| 1388 | (1+ (ert-stats-test-index stats test)) | ||||
| 1389 | (ert-test-name test))))))))) | ||||
| 1390 | |||||
| 1391 | |||||
| 1392 | ;;; Commands and button actions for the results buffer. | ||||
| 1393 | |||||
| 1394 | (define-derived-mode ert-results-mode fundamental-mode "ERT-Results" | ||||
| 1395 | "Major mode for viewing results of ERT test runs.") | ||||
| 1396 | |||||
| 1397 | (loop for (key binding) in | ||||
| 1398 | '(("j" ert-results-jump-between-summary-and-result) | ||||
| 1399 | ("." ert-results-find-test-at-point-other-window) | ||||
| 1400 | ("r" ert-results-rerun-test-at-point) | ||||
| 1401 | ("d" ert-results-rerun-test-at-point-debugging-errors) | ||||
| 1402 | ("b" ert-results-pop-to-backtrace-for-test-at-point) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1403 | ("m" ert-results-pop-to-messages-for-test-at-point) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1404 | ("p" ert-results-toggle-printer-limits-for-test-at-point) | |
| 1405 | ("D" ert-delete-test) | ||||
| 1406 | ([tab] forward-button) | ||||
| 1407 | ([backtab] backward-button) | ||||
| 1408 | ) | ||||
| 1409 | do | ||||
| 1410 | (define-key ert-results-mode-map key binding)) | ||||
| 1411 | |||||
| 1412 | (define-button-type 'ert-results-progress-bar-button | ||||
| 1413 | 'action #'ert-results-progress-bar-button-action | ||||
| 1414 | 'help-echo "mouse-2, RET: Reveal test result") | ||||
| 1415 | |||||
| cf126a81 » | Christian Ohler | 2008-08-10 | 1416 | (define-button-type 'ert-test-name-button | |
| 1417 | 'action #'ert-test-name-button-action | ||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1418 | 'help-echo "mouse-2, RET: Find test definition") | |
| 1419 | |||||
| 1420 | (define-button-type 'ert-results-expand-collapse-button | ||||
| 1421 | 'action #'ert-results-expand-collapse-button-action | ||||
| 1422 | 'help-echo "mouse-2, RET: Expand/collapse test result") | ||||
| 1423 | |||||
| 1424 | (defun ert-results-test-node-or-null-at-point () | ||||
| 1425 | "If point is on a valid ewoc node, return it; return nil otherwise. | ||||
| 1426 | |||||
| 1427 | To be used in the ERT results buffer." | ||||
| 1428 | (let* ((ewoc ert-results-ewoc) | ||||
| 1429 | (node (ewoc-locate ewoc))) | ||||
| 1430 | ;; `ewoc-locate' will return an arbitrary node when point is on | ||||
| 1431 | ;; header or footer, or when all nodes are invisible. So we need | ||||
| 1432 | ;; to validate its return value here. | ||||
| 1433 | (if (and (>= (point) (ewoc-location node)) | ||||
| 1434 | (not (ert-ewoc-entry-hidden-p (ewoc-data node)))) | ||||
| 1435 | node | ||||
| 1436 | nil))) | ||||
| 1437 | |||||
| 1438 | (defun ert-results-test-node-at-point () | ||||
| 1439 | "If point is on a valid ewoc node, return it; signal an error otherwise. | ||||
| 1440 | |||||
| 1441 | To be used in the ERT results buffer." | ||||
| 1442 | (or (ert-results-test-node-or-null-at-point) | ||||
| 1443 | (error "No test at point"))) | ||||
| 1444 | |||||
| 1445 | (defun ert-results-expand-collapse-button-action (button) | ||||
| 1446 | "Expand or collapse the test node BUTTON belongs to." | ||||
| 1447 | (let* ((ewoc ert-results-ewoc) | ||||
| 1448 | (node (save-excursion | ||||
| 1449 | (goto-char (ert-button-action-position)) | ||||
| 1450 | (ert-results-test-node-at-point))) | ||||
| 1451 | (entry (ewoc-data node))) | ||||
| 1452 | (setf (ert-ewoc-entry-expanded-p entry) | ||||
| 1453 | (not (ert-ewoc-entry-expanded-p entry))) | ||||
| 1454 | (ewoc-invalidate ewoc node))) | ||||
| 1455 | |||||
| 1456 | (defun ert-results-find-test-at-point-other-window () | ||||
| 1457 | "Find the definition of the test at point in another window. | ||||
| 1458 | |||||
| 1459 | To be used in the ERT results buffer." | ||||
| 1460 | (interactive) | ||||
| 1461 | (let* ((node (ert-results-test-node-at-point)) | ||||
| 1462 | (entry (ewoc-data node)) | ||||
| 1463 | (test (ert-ewoc-entry-test entry)) | ||||
| 1464 | (name (ert-test-name test))) | ||||
| 1465 | (ert-find-test-other-window name))) | ||||
| 1466 | |||||
| cf126a81 » | Christian Ohler | 2008-08-10 | 1467 | (defun ert-test-name-button-action (button) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1468 | "Find the definition of the test BUTTON belongs to, in another window." | |
| 1469 | (let ((name (button-get button 'ert-test-name))) | ||||
| 1470 | (ert-find-test-other-window name))) | ||||
| 1471 | |||||
| 1472 | (defun ert-ewoc-position (ewoc node) | ||||
| 1473 | "Return the position of NODE in EWOC, or nil if NODE is not in EWOC." | ||||
| 1474 | (loop for i from 0 | ||||
| 1475 | for node-here = (ewoc-nth ewoc 0) then (ewoc-next ewoc node-here) | ||||
| 1476 | do (when (eql node node-here) | ||||
| 1477 | (return i)) | ||||
| 1478 | finally (return nil))) | ||||
| 1479 | |||||
| 1480 | (defun ert-results-jump-between-summary-and-result () | ||||
| 1481 | "Jump back and forth between the test run summary and individual test results. | ||||
| 1482 | |||||
| 1483 | From an ewoc node, jumps to the character that represents the | ||||
| 1484 | same test in the progress bar, and vice versa. | ||||
| 1485 | |||||
| 1486 | To be used in the ERT results buffer." | ||||
| 1487 | ;; Maybe this command isn't actually needed much, but if it is, it | ||||
| 1488 | ;; seems like an indication that the UI design is not optimal. If | ||||
| 1489 | ;; jumping back and forth between a summary at the top of the buffer | ||||
| 1490 | ;; and the error log in the remainder of the buffer is useful, then | ||||
| 1491 | ;; the summary apparently needs to be easily accessible from the | ||||
| 1492 | ;; error log, and perhaps it would be better to have it in a | ||||
| 1493 | ;; separate buffer to keep it visible. | ||||
| 1494 | (interactive) | ||||
| 1495 | (let ((ewoc ert-results-ewoc) | ||||
| 1496 | (progress-bar-begin ert-results-progress-bar-button-begin)) | ||||
| 1497 | (cond ((ert-results-test-node-or-null-at-point) | ||||
| 1498 | (let* ((node (ert-results-test-node-at-point)) | ||||
| 1499 | (pos (ert-ewoc-position ewoc node))) | ||||
| 1500 | (goto-char (+ progress-bar-begin pos)))) | ||||
| 1501 | ((and (<= progress-bar-begin (point)) | ||||
| 1502 | (< (point) (button-end (button-at progress-bar-begin)))) | ||||
| 1503 | (let* ((node (ewoc-nth ewoc (- (point) progress-bar-begin))) | ||||
| 1504 | (entry (ewoc-data node))) | ||||
| 1505 | (when (ert-ewoc-entry-hidden-p entry) | ||||
| 1506 | (setf (ert-ewoc-entry-hidden-p entry) nil) | ||||
| 1507 | (ewoc-invalidate ewoc node)) | ||||
| 1508 | (ewoc-goto-node ewoc node))) | ||||
| 1509 | (t | ||||
| 1510 | (goto-char progress-bar-begin))))) | ||||
| 1511 | |||||
| 1512 | (defun ert-button-action-position () | ||||
| 1513 | "The buffer position where the last button action was triggered." | ||||
| 1514 | (cond ((integerp last-command-event) | ||||
| 1515 | (point)) | ||||
| 1516 | ((eventp last-command-event) | ||||
| 1517 | (posn-point (event-start last-command-event))) | ||||
| 1518 | (t (assert nil)))) | ||||
| 1519 | |||||
| 1520 | (defun ert-results-progress-bar-button-action (button) | ||||
| 1521 | "Find the ewoc node that represents the same test as the character clicked on." | ||||
| 1522 | (goto-char (ert-button-action-position)) | ||||
| 1523 | (ert-results-jump-between-summary-and-result)) | ||||
| 1524 | |||||
| 1525 | (defun ert-results-rerun-test-at-point () | ||||
| 1526 | "Re-run the test at point. | ||||
| 1527 | |||||
| 1528 | To be used in the ERT results buffer." | ||||
| 1529 | (interactive) | ||||
| 1530 | (let* ((ewoc ert-results-ewoc) | ||||
| 1531 | (node (ert-results-test-node-at-point)) | ||||
| 1532 | (entry (ewoc-data node)) | ||||
| 1533 | (old-test (ert-ewoc-entry-test entry)) | ||||
| 1534 | (test-name (ert-test-name old-test)) | ||||
| 1535 | ;; FIXME: Write a test for this lookup. | ||||
| 1536 | (test (if test-name | ||||
| 1537 | (if (ert-test-boundp test-name) | ||||
| 1538 | (ert-get-test test-name) | ||||
| 1539 | (error "No such test: %S" test-name)) | ||||
| 1540 | old-test)) | ||||
| 1541 | (stats ert-results-stats) | ||||
| 1542 | (pos (gethash test (ert-stats-test-map stats))) | ||||
| 1543 | (progress-message (format "Running test %S" (ert-test-name test)))) | ||||
| 1544 | ;; Need to save and restore point manually here: When point is on | ||||
| 1545 | ;; the first visible ewoc entry while the header is updated, point | ||||
| 1546 | ;; moves to the top of the buffer. This is undesirable, and a | ||||
| 1547 | ;; simple `save-excursion' doesn't prevent it. | ||||
| 1548 | (let ((point (point))) | ||||
| 1549 | (unwind-protect | ||||
| 1550 | (unwind-protect | ||||
| 1551 | (progn | ||||
| 1552 | (message "%s..." progress-message) | ||||
| 1553 | (ert-run-or-rerun-test stats test | ||||
| 1554 | ert-results-listener)) | ||||
| 1555 | (ert-results-update-stats-display ewoc stats) | ||||
| 1556 | (message "%s...%s" | ||||
| 1557 | progress-message | ||||
| 1558 | (let ((result (ert-test-most-recent-result test))) | ||||
| 1559 | (ert-string-for-test-result | ||||
| 1560 | result (ert-test-result-expected-p test result))))) | ||||
| 1561 | (goto-char point))))) | ||||
| 1562 | |||||
| 1563 | (defun ert-results-rerun-test-at-point-debugging-errors () | ||||
| 1564 | "Re-run the test at point with `ert-debug-on-error' bound to t. | ||||
| 1565 | |||||
| 1566 | To be used in the ERT results buffer." | ||||
| 1567 | (interactive) | ||||
| 1568 | (let ((ert-debug-on-error t)) | ||||
| 1569 | (ert-results-rerun-test-at-point))) | ||||
| 1570 | |||||
| 1571 | (defun ert-print-backtrace (backtrace) | ||||
| 1572 | "Format the backtrace BACKTRACE to the current buffer." | ||||
| 1573 | ;; This is essentially a reimplementation of Fbacktrace | ||||
| 1574 | ;; (src/eval.c), but for a saved backtrace, not the current one. | ||||
| 1575 | (let ((print-escape-newlines t) | ||||
| 1576 | (print-level 8) | ||||
| 1577 | (print-length 50)) | ||||
| 1578 | (dolist (frame backtrace) | ||||
| 1579 | (ecase (first frame) | ||||
| 1580 | ((nil) | ||||
| 1581 | ;; Special operator. | ||||
| 1582 | (destructuring-bind (special-operator &rest arg-forms) | ||||
| 1583 | (cdr frame) | ||||
| 1584 | (insert | ||||
| 1585 | (format " %S\n" (list* special-operator arg-forms))))) | ||||
| 1586 | ((t) | ||||
| 1587 | ;; Function call. | ||||
| 1588 | (destructuring-bind (fn &rest args) (cdr frame) | ||||
| 1589 | (insert (format " %S(" fn)) | ||||
| 1590 | (loop for firstp = t then nil | ||||
| 1591 | for arg in args do | ||||
| 1592 | (unless firstp | ||||
| 1593 | (insert " ")) | ||||
| 1594 | (insert (format "%S" arg))) | ||||
| 1595 | (insert ")\n"))))))) | ||||
| 1596 | |||||
| 1597 | (defun ert-results-pop-to-backtrace-for-test-at-point () | ||||
| 1598 | "Display the backtrace for the test at point. | ||||
| 1599 | |||||
| 1600 | To be used in the ERT results buffer." | ||||
| 1601 | (interactive) | ||||
| 1602 | (let* ((node (ert-results-test-node-at-point)) | ||||
| 1603 | (entry (ewoc-data node)) | ||||
| 1604 | (test (ert-ewoc-entry-test entry)) | ||||
| 1605 | (result (ert-ewoc-entry-result entry))) | ||||
| 1606 | (etypecase result | ||||
| 1607 | (ert-test-passed (error "Test passed, no backtrace available")) | ||||
| 1608 | (ert-test-result-with-condition | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1609 | (let ((backtrace (ert-test-result-with-condition-backtrace result)) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1610 | (buffer | |
| 1611 | (let ((default-major-mode 'fundamental-mode)) | ||||
| 1612 | (get-buffer-create "*ERT Backtrace*")))) | ||||
| 1613 | (pop-to-buffer buffer) | ||||
| 1614 | (setq buffer-read-only t) | ||||
| 1615 | (let ((inhibit-read-only t)) | ||||
| 1616 | (erase-buffer) | ||||
| cf126a81 » | Christian Ohler | 2008-08-10 | 1617 | ;; Use unibyte because `debugger-setup-buffer' also does so. | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1618 | (set-buffer-multibyte nil) | |
| 1619 | (setq truncate-lines t) | ||||
| 1620 | (ert-print-backtrace backtrace) | ||||
| 1621 | (debugger-make-xrefs) | ||||
| cf126a81 » | Christian Ohler | 2008-08-10 | 1622 | (goto-char (point-min)) | |
| 1623 | (insert "Backtrace for test `") | ||||
| 1624 | (ert-insert-test-name-button (ert-test-name test)) | ||||
| 1625 | (insert "':\n"))))))) | ||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1626 | ||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1627 | (defun ert-results-pop-to-messages-for-test-at-point () | |
| 1628 | "Display the part of the *Messages* buffer generated during the test at point. | ||||
| 1629 | |||||
| 1630 | To be used in the ERT results buffer." | ||||
| 1631 | (interactive) | ||||
| 1632 | (let* ((node (ert-results-test-node-at-point)) | ||||
| 1633 | (entry (ewoc-data node)) | ||||
| 1634 | (test (ert-ewoc-entry-test entry)) | ||||
| 1635 | (result (ert-ewoc-entry-result entry))) | ||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 1636 | (let ((buffer | |
| 1637 | (let ((default-major-mode 'fundamental-mode)) | ||||
| 1638 | (get-buffer-create "*ERT Messages*")))) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1639 | (pop-to-buffer buffer) | |
| 1640 | (setq buffer-read-only t) | ||||
| 1641 | (let ((inhibit-read-only t)) | ||||
| 1642 | (erase-buffer) | ||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 1643 | (insert (ert-test-result-messages result)) | |
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1644 | (goto-char (point-min)) | |
| 1645 | (insert "Messages for test `") | ||||
| 1646 | (ert-insert-test-name-button (ert-test-name test)) | ||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 1647 | (insert "':\n"))))) | |
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1648 | ||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1649 | (defun ert-results-toggle-printer-limits-for-test-at-point () | |
| 1650 | "Toggle how much of the condition to print for the test at point. | ||||
| 1651 | |||||
| 1652 | To be used in the ERT results buffer." | ||||
| 1653 | (interactive) | ||||
| 1654 | (let* ((ewoc ert-results-ewoc) | ||||
| 1655 | (node (ert-results-test-node-at-point)) | ||||
| 1656 | (entry (ewoc-data node))) | ||||
| 1657 | (setf (ert-ewoc-entry-extended-printer-limits-p entry) | ||||
| 1658 | (not (ert-ewoc-entry-extended-printer-limits-p entry))) | ||||
| 1659 | (ewoc-invalidate ewoc node))) | ||||
| 1660 | |||||
| 1661 | (defun ert-activate-font-lock-keywords () | ||||
| 1662 | (font-lock-add-keywords | ||||
| 1663 | nil | ||||
| 1664 | '(("(\\(\\<ert-deftest\\)\\>\\s *\\(\\sw+\\)?" | ||||
| 1665 | (1 font-lock-keyword-face nil t) | ||||
| 1666 | (2 font-lock-function-name-face nil t))))) | ||||
| 1667 | |||||
| 1668 | (defun* ert-remove-from-list (list-var element &key key test) | ||||
| 1669 | "Remove ELEMENT from the value of LIST-VAR if present. | ||||
| 1670 | |||||
| 1671 | This is an inverse of `add-to-list'." | ||||
| 1672 | (unless key (setq key #'identity)) | ||||
| 1673 | (unless test (setq test #'equal)) | ||||
| 1674 | (setf (symbol-value list-var) | ||||
| 1675 | (remove* element | ||||
| 1676 | (symbol-value list-var) | ||||
| 1677 | :key key | ||||
| 1678 | :test test))) | ||||
| 1679 | |||||
| 1680 | |||||
| 1681 | ;;; Actions on load/unload. | ||||
| 1682 | |||||
| 1683 | (add-to-list 'find-function-regexp-alist '(ert-deftest . ert-find-test-regexp)) | ||||
| 1684 | (add-to-list 'minor-mode-alist '(ert-current-run-stats | ||||
| 1685 | (:eval | ||||
| 1686 | (ert-tests-running-mode-line-indicator)))) | ||||
| 1687 | (add-to-list 'emacs-lisp-mode-hook 'ert-activate-font-lock-keywords) | ||||
| 1688 | |||||
| 1689 | (defun ert-unload-function () | ||||
| 1690 | (ert-remove-from-list 'find-function-regexp-alist 'ert-deftest :key #'car) | ||||
| 1691 | (ert-remove-from-list 'minor-mode-alist 'ert-current-run-stats :key #'car) | ||||
| 1692 | (ert-remove-from-list 'emacs-lisp-mode-hook 'ert-activate-font-lock-keywords) | ||||
| 1693 | nil) | ||||
| 1694 | |||||
| 1695 | (defvar ert-unload-hook '()) | ||||
| 1696 | (add-hook 'ert-unload-hook 'ert-unload-function) | ||||
| 1697 | |||||
| 1698 | |||||
| 1699 | ;;; Self-tests. | ||||
| 1700 | |||||
| 1701 | ;; Test that test bodies are actually run. | ||||
| 1702 | (defvar ert-test-body-was-run) | ||||
| 1703 | (ert-deftest ert-test-body-runs () | ||||
| 1704 | (setq ert-test-body-was-run t)) | ||||
| 1705 | |||||
| 1706 | |||||
| 1707 | ;; Test that nested test bodies run. | ||||
| 1708 | (ert-deftest ert-nested-test-body-runs () | ||||
| 1709 | (lexical-let ((was-run nil)) | ||||
| 1710 | (let ((test (make-ert-test :body (lambda () | ||||
| 1711 | (setq was-run t))))) | ||||
| 1712 | (assert (not was-run)) | ||||
| 1713 | (ert-run-test test) | ||||
| 1714 | (assert was-run)))) | ||||
| 1715 | |||||
| 1716 | |||||
| 1717 | ;; Test that pass/fail works. | ||||
| 1718 | (ert-deftest ert-test-pass () | ||||
| 1719 | (let ((test (make-ert-test :body (lambda ())))) | ||||
| 1720 | (let ((result (ert-run-test test))) | ||||
| 1721 | (assert (typep result 'ert-test-passed))))) | ||||
| 1722 | |||||
| 1723 | (ert-deftest ert-test-fail () | ||||
| 1724 | (let ((test (make-ert-test :body (lambda () (ert-fail "failure message"))))) | ||||
| 1725 | (let ((result (let ((ert-debug-on-error nil)) | ||||
| 1726 | (ert-run-test test)))) | ||||
| 1727 | (assert (typep result 'ert-test-failed) t) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1728 | (assert (equal (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1729 | '(ert-test-failed "failure message")) | |
| 1730 | t)))) | ||||
| 1731 | |||||
| 1732 | (ert-deftest ert-test-fail-debug-with-condition-case () | ||||
| 1733 | (let ((test (make-ert-test :body (lambda () (ert-fail "failure message"))))) | ||||
| 1734 | (condition-case condition | ||||
| 1735 | (progn | ||||
| 1736 | (let ((ert-debug-on-error t)) | ||||
| 1737 | (ert-run-test test)) | ||||
| 1738 | (assert nil)) | ||||
| 1739 | ((error) | ||||
| 1740 | (assert (equal condition '(ert-test-failed "failure message")) t))))) | ||||
| 1741 | |||||
| 1742 | (ert-deftest ert-test-fail-debug-with-debugger-1 () | ||||
| 1743 | (let ((test (make-ert-test :body (lambda () (ert-fail "failure message"))))) | ||||
| 1744 | (let ((debugger (lambda (&rest debugger-args) | ||||
| 1745 | (assert nil)))) | ||||
| 1746 | (let ((ert-debug-on-error nil)) | ||||
| 1747 | (ert-run-test test))))) | ||||
| 1748 | |||||
| 1749 | (ert-deftest ert-test-fail-debug-with-debugger-2 () | ||||
| 1750 | (let ((test (make-ert-test :body (lambda () (ert-fail "failure message"))))) | ||||
| 1751 | (block nil | ||||
| 1752 | (let ((debugger (lambda (&rest debugger-args) | ||||
| 1753 | (return-from nil nil)))) | ||||
| 1754 | (let ((ert-debug-on-error t)) | ||||
| 1755 | (ert-run-test test)) | ||||
| 1756 | (assert nil))))) | ||||
| 1757 | |||||
| 1758 | (ert-deftest ert-test-fail-debug-nested-with-debugger () | ||||
| 1759 | (let ((test (make-ert-test :body (lambda () | ||||
| 1760 | (let ((ert-debug-on-error t)) | ||||
| 1761 | (ert-fail "failure message")))))) | ||||
| 1762 | (let ((debugger (lambda (&rest debugger-args) | ||||
| 1763 | (assert nil nil "Assertion a")))) | ||||
| 1764 | (let ((ert-debug-on-error nil)) | ||||
| 1765 | (ert-run-test test)))) | ||||
| 1766 | (let ((test (make-ert-test :body (lambda () | ||||
| 1767 | (let ((ert-debug-on-error nil)) | ||||
| 1768 | (ert-fail "failure message")))))) | ||||
| 1769 | (block nil | ||||
| 1770 | (let ((debugger (lambda (&rest debugger-args) | ||||
| 1771 | (return-from nil nil)))) | ||||
| 1772 | (let ((ert-debug-on-error t)) | ||||
| 1773 | (ert-run-test test)) | ||||
| 1774 | (assert nil nil "Assertion b"))))) | ||||
| 1775 | |||||
| 1776 | (ert-deftest ert-test-error () | ||||
| 1777 | (let ((test (make-ert-test :body (lambda () (error "error message"))))) | ||||
| 1778 | (let ((result (let ((ert-debug-on-error nil)) | ||||
| 1779 | (ert-run-test test)))) | ||||
| 1780 | (assert (typep result 'ert-test-error) t) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1781 | (assert (equal (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1782 | '(error "error message")) | |
| 1783 | t)))) | ||||
| 1784 | |||||
| 1785 | (ert-deftest ert-test-error-debug () | ||||
| 1786 | (let ((test (make-ert-test :body (lambda () (error "error message"))))) | ||||
| 1787 | (condition-case condition | ||||
| 1788 | (progn | ||||
| 1789 | (let ((ert-debug-on-error t)) | ||||
| 1790 | (ert-run-test test)) | ||||
| 1791 | (assert nil)) | ||||
| 1792 | ((error) | ||||
| 1793 | (assert (equal condition '(error "error message")) t))))) | ||||
| 1794 | |||||
| 1795 | |||||
| 1796 | ;; Test that `should' works. | ||||
| 1797 | (ert-deftest ert-test-should () | ||||
| 1798 | (let ((test (make-ert-test :body (lambda () (should nil))))) | ||||
| 1799 | (let ((result (let ((ert-debug-on-error nil)) | ||||
| 1800 | (ert-run-test test)))) | ||||
| 1801 | (assert (typep result 'ert-test-failed) t) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1802 | (assert (equal (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1803 | '(ert-test-failed ((should nil) :form nil :value nil))) | |
| 1804 | t))) | ||||
| 1805 | (let ((test (make-ert-test :body (lambda () (should t))))) | ||||
| 1806 | (let ((result (ert-run-test test))) | ||||
| 1807 | (assert (typep result 'ert-test-passed) t)))) | ||||
| 1808 | |||||
| 1809 | (ert-deftest ert-test-should-value () | ||||
| 1810 | (should (eql (should 'foo) 'foo)) | ||||
| 1811 | (should (eql (should 'bar) 'bar))) | ||||
| 1812 | |||||
| 1813 | (ert-deftest ert-test-should-not () | ||||
| 1814 | (let ((test (make-ert-test :body (lambda () (should-not t))))) | ||||
| 1815 | (let ((result (let ((ert-debug-on-error nil)) | ||||
| 1816 | (ert-run-test test)))) | ||||
| 1817 | (assert (typep result 'ert-test-failed) t) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1818 | (assert (equal (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1819 | '(ert-test-failed ((should-not t) :form t :value t))) | |
| 1820 | t))) | ||||
| 1821 | (let ((test (make-ert-test :body (lambda () (should-not nil))))) | ||||
| 1822 | (let ((result (ert-run-test test))) | ||||
| 1823 | (assert (typep result 'ert-test-passed))))) | ||||
| 1824 | |||||
| 1825 | |||||
| 1826 | (ert-deftest ert-test-should-error () | ||||
| 1827 | ;; No error. | ||||
| 1828 | (let ((test (make-ert-test :body (lambda () (should-error (progn)))))) | ||||
| 1829 | (let ((result (let ((ert-debug-on-error nil)) | ||||
| 1830 | (ert-run-test test)))) | ||||
| 1831 | (should (typep result 'ert-test-failed)) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1832 | (should (equal (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1833 | '(ert-test-failed | |
| 1834 | ((should-error (progn)) | ||||
| 1835 | :form (progn) | ||||
| 1836 | :value nil | ||||
| 1837 | :fail-reason "did not signal an error")))))) | ||||
| 1838 | ;; A simple error. | ||||
| 1839 | (let ((test (make-ert-test :body (lambda () (should-error (error "foo")))))) | ||||
| 1840 | (let ((result (ert-run-test test))) | ||||
| 1841 | (should (typep result 'ert-test-passed)))) | ||||
| 1842 | ;; Error of unexpected type, no test. | ||||
| 1843 | (let ((test (make-ert-test :body (lambda () | ||||
| 1844 | (should-error (error "foo") | ||||
| 1845 | :type 'singularity-error))))) | ||||
| 1846 | (let ((result (ert-run-test test))) | ||||
| 1847 | (should (typep result 'ert-test-failed)) | ||||
| 1848 | (should (equal | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1849 | (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1850 | '(ert-test-failed | |
| 1851 | ((should-error (error "foo") :type 'singularity-error) | ||||
| 1852 | :form (error "foo") | ||||
| 1853 | :condition (error "foo") | ||||
| 1854 | :fail-reason | ||||
| 1855 | "the error signalled did not have the expected type")))))) | ||||
| 1856 | ;; Error of the expected type, no test. | ||||
| 1857 | (let ((test (make-ert-test :body (lambda () | ||||
| 1858 | (should-error (signal 'singularity-error | ||||
| 1859 | nil) | ||||
| 1860 | :type 'singularity-error))))) | ||||
| 1861 | (let ((result (ert-run-test test))) | ||||
| 1862 | (should (typep result 'ert-test-passed)))) | ||||
| 1863 | ;; Error that fails the test, no type. | ||||
| 1864 | (let ((test (make-ert-test :body (lambda () | ||||
| 1865 | (should-error | ||||
| 1866 | (error "foo") | ||||
| 1867 | :test (lambda (error) nil)))))) | ||||
| 1868 | (let ((result (ert-run-test test))) | ||||
| 1869 | (should (typep result 'ert-test-failed)) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1870 | (should (equal (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1871 | '(ert-test-failed | |
| 1872 | ((should-error (error "foo") :test (lambda (error) nil)) | ||||
| 1873 | :form (error "foo") | ||||
| 1874 | :condition (error "foo") | ||||
| 1875 | :fail-reason | ||||
| 1876 | "the error signalled did not pass the test")))))) | ||||
| 1877 | ;; Error that passes the test, no type. | ||||
| 1878 | (let ((test (make-ert-test :body (lambda () | ||||
| 1879 | (should-error (error "foo") | ||||
| 1880 | :test (lambda (error) t)))))) | ||||
| 1881 | (let ((result (ert-run-test test))) | ||||
| 1882 | (should (typep result 'ert-test-passed)))) | ||||
| 1883 | ;; Error that has the expected type but fails the test. | ||||
| 1884 | (let ((test (make-ert-test :body (lambda () | ||||
| 1885 | (should-error | ||||
| 1886 | (signal 'singularity-error nil) | ||||
| 1887 | :type 'singularity-error | ||||
| 1888 | :test (lambda (error) nil)))))) | ||||
| 1889 | (let ((result (ert-run-test test))) | ||||
| 1890 | (should (typep result 'ert-test-failed)) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1891 | (should (equal (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1892 | '(ert-test-failed | |
| 1893 | ((should-error (signal 'singularity-error nil) | ||||
| 1894 | :type 'singularity-error | ||||
| 1895 | :test (lambda (error) nil)) | ||||
| 1896 | :form (signal singularity-error nil) | ||||
| 1897 | :condition (singularity-error) | ||||
| 1898 | :fail-reason | ||||
| 1899 | "the error signalled did not pass the test")))))) | ||||
| 1900 | ;; Error that has the expected type and passes the test. | ||||
| 1901 | (let ((test (make-ert-test :body (lambda () | ||||
| 1902 | (should-error | ||||
| 1903 | (signal 'singularity-error nil) | ||||
| 1904 | :type 'singularity-error | ||||
| 1905 | :test (lambda (error) t)))))) | ||||
| 1906 | (let ((result (ert-run-test test))) | ||||
| 1907 | (should (typep result 'ert-test-passed)))) | ||||
| 1908 | ) | ||||
| 1909 | |||||
| 1910 | (ert-deftest ert-test-should-error-subtypes () | ||||
| 1911 | (let ((test (make-ert-test | ||||
| 1912 | :body (lambda () | ||||
| 1913 | (should-error (signal 'singularity-error nil) | ||||
| 1914 | :type 'singularity-error | ||||
| 1915 | :exclude-subtypes t))))) | ||||
| 1916 | (let ((result (ert-run-test test))) | ||||
| 1917 | (should (typep result 'ert-test-passed)))) | ||||
| 1918 | (let ((test (make-ert-test | ||||
| 1919 | :body (lambda () | ||||
| 1920 | (should-error (signal 'arith-error nil) | ||||
| 1921 | :type 'singularity-error))))) | ||||
| 1922 | (let ((result (ert-run-test test))) | ||||
| 1923 | (should (typep result 'ert-test-failed)) | ||||
| 1924 | (should (equal | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1925 | (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1926 | '(ert-test-failed | |
| 1927 | ((should-error (signal 'arith-error nil) | ||||
| 1928 | :type 'singularity-error) | ||||
| 1929 | :form (signal arith-error nil) | ||||
| 1930 | :condition (arith-error) | ||||
| 1931 | :fail-reason | ||||
| 1932 | "the error signalled did not have the expected type")))))) | ||||
| 1933 | (let ((test (make-ert-test | ||||
| 1934 | :body (lambda () | ||||
| 1935 | (should-error (signal 'arith-error nil) | ||||
| 1936 | :type 'singularity-error | ||||
| 1937 | :exclude-subtypes t))))) | ||||
| 1938 | (let ((result (ert-run-test test))) | ||||
| 1939 | (should (typep result 'ert-test-failed)) | ||||
| 1940 | (should (equal | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1941 | (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1942 | '(ert-test-failed | |
| 1943 | ((should-error (signal 'arith-error nil) | ||||
| 1944 | :type 'singularity-error | ||||
| 1945 | :exclude-subtypes t) | ||||
| 1946 | :form (signal arith-error nil) | ||||
| 1947 | :condition (arith-error) | ||||
| 1948 | :fail-reason | ||||
| 1949 | "the error signalled did not have the expected type")))))) | ||||
| 1950 | (let ((test (make-ert-test | ||||
| 1951 | :body (lambda () | ||||
| 1952 | (should-error (signal 'singularity-error nil) | ||||
| 1953 | :type 'arith-error | ||||
| 1954 | :exclude-subtypes t))))) | ||||
| 1955 | (let ((result (ert-run-test test))) | ||||
| 1956 | (should (typep result 'ert-test-failed)) | ||||
| 1957 | (should (equal | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 1958 | (ert-test-result-with-condition-condition result) | |
| fb8b8021 » | Christian Ohler | 2008-08-10 | 1959 | '(ert-test-failed | |
| 1960 | ((should-error (signal 'singularity-error nil) | ||||
| 1961 | :type 'arith-error | ||||
| 1962 | :exclude-subtypes t) | ||||
| 1963 | :form (signal singularity-error nil) | ||||
| 1964 | :condition (singularity-error) | ||||
| 1965 | :fail-reason | ||||
| 1966 | "the error signalled was a subtype of the expected type")))))) | ||||
| 1967 | ) | ||||
| 1968 | |||||
| 1969 | ;; Test that `should' errors contain the information we expect them to. | ||||
| 1970 | (defmacro ert-test-my-list (&rest args) | ||||
| 1971 | `(list ,@args)) | ||||
| 1972 | |||||
| 1973 | (ert-deftest ert-test-should-failure-debugging () | ||||
| 1974 | (loop for (body expected-condition) in | ||||
| 1975 | `((,(lambda () (let ((x nil)) (should x))) | ||||
| 1976 | (ert-test-failed ((should x) :form x :value nil))) | ||||
| 1977 | (,(lambda () (let ((x t)) (should-not x))) | ||||
| 1978 | (ert-test-failed ((should-not x) :form x :value t))) | ||||
| 1979 | (,(lambda () (let ((x t)) (should (not x)))) | ||||
| 1980 | (ert-test-failed ((should (not x)) :form (not t) :value nil))) | ||||
| 1981 | (,(lambda () (let ((x nil)) (should-not (not x)))) | ||||
| 1982 | (ert-test-failed ((should-not (not x)) :form (not nil) :value t))) | ||||
| 1983 | (,(lambda () (let ((x t) (y nil)) (should-not (ert-test-my-list x y)))) | ||||
| 1984 | (ert-test-failed | ||||
| 1985 | ((should-not (ert-test-my-list x y)) | ||||
| 1986 | :form (list t nil) | ||||
| 1987 | :value (t nil)))) | ||||
| 1988 | (,(lambda () (let ((x t)) (should (error "foo")))) | ||||
| 1989 | (error "foo"))) | ||||
| 1990 | do | ||||
| 1991 | (let ((test (make-ert-test :body body))) | ||||
| 1992 | (condition-case actual-condition | ||||
| 1993 | (progn | ||||
| 1994 | (let ((ert-debug-on-error t)) | ||||
| 1995 | (ert-run-test test)) | ||||
| 1996 | (assert nil)) | ||||
| 1997 | ((error) | ||||
| 1998 | (should (equal actual-condition expected-condition))))))) | ||||
| 1999 | |||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 2000 | (ert-deftest ert-test-messages () | |
| 2001 | (let* ((message-string "Test message") | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 2002 | (messages-buffer (get-buffer-create "*Messages*")) | |
| 2003 | (test (make-ert-test :body (lambda () (message "%s" message-string))))) | ||||
| 2004 | (with-current-buffer messages-buffer | ||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 2005 | (let ((result (ert-run-test test))) | |
| 2006 | (should (equal (concat message-string "\n") | ||||
| 2007 | (ert-test-result-messages result))))))) | ||||
| 2008 | |||||
| 2009 | (defun ert-call-with-temporary-messages-buffer (thunk) | ||||
| 2010 | (lexical-let ((new-buffer-name (generate-new-buffer-name | ||||
| 2011 | "*Messages* orig buffer"))) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 2012 | (unwind-protect | |
| 2013 | (progn | ||||
| 2014 | (with-current-buffer (get-buffer-create "*Messages*") | ||||
| 2015 | (rename-buffer new-buffer-name)) | ||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 2016 | (get-buffer-create "*Messages*") | |
| 2017 | (funcall thunk)) | ||||
| 8de23ff1 » | Christian Ohler | 2008-08-10 | 2018 | (kill-buffer "*Messages*") | |
| 2019 | (with-current-buffer new-buffer-name | ||||
| 2020 | (rename-buffer "*Messages*"))))) | ||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 2021 | ||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 2022 | (ert-deftest ert-test-messages-on-log-truncation () | |
| 2023 | (let ((test (make-ert-test | ||||
| 2024 | :body (lambda () | ||||
| 2025 | ;; Emacs would combine messages if we | ||||
| 2026 | ;; generate the same message multiple | ||||
| 2027 | ;; times. | ||||
| 2028 | (message "a") | ||||
| 2029 | (message "b") | ||||
| 2030 | (message "c") | ||||
| 2031 | (message "d"))))) | ||||
| 2032 | (let (result) | ||||
| 2033 | (ert-call-with-temporary-messages-buffer | ||||
| 2034 | (lambda () | ||||
| 2035 | (let ((message-log-max 2)) | ||||
| 2036 | (setq result (ert-run-test test))) | ||||
| 2037 | (should (equal (with-current-buffer "*Messages*" | ||||
| 2038 | (buffer-string)) | ||||
| 2039 | "c\nd\n")))) | ||||
| 2040 | (should (equal (ert-test-result-messages result) "a\nb\nc\nd\n"))))) | ||||
| 2041 | |||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 2042 | ;; Test `ert-select-tests'. | |
| 2043 | (ert-deftest ert-test-select-regexp () | ||||
| 2044 | (should (equal (ert-select-tests "^ert-test-select-regexp$" t) | ||||
| 2045 | (list (ert-get-test 'ert-test-select-regexp))))) | ||||
| 2046 | |||||
| 2047 | (ert-deftest ert-test-test-boundp () | ||||
| 2048 | (should (ert-test-boundp 'ert-test-test-boundp)) | ||||
| 2049 | (should-not (ert-test-boundp (make-symbol "ert-not-a-test")))) | ||||
| 2050 | |||||
| 2051 | (ert-deftest ert-test-select-member () | ||||
| 2052 | (should (equal (ert-select-tests '(member ert-test-select-member) t) | ||||
| 2053 | (list (ert-get-test 'ert-test-select-member))))) | ||||
| 2054 | |||||
| 2055 | (ert-deftest ert-test-select-test () | ||||
| 2056 | (should (equal (ert-select-tests (ert-get-test 'ert-test-select-test) t) | ||||
| 2057 | (list (ert-get-test 'ert-test-select-test))))) | ||||
| 2058 | |||||
| 2059 | (ert-deftest ert-test-select-symbol () | ||||
| 2060 | (should (equal (ert-select-tests 'ert-test-select-symbol t) | ||||
| 2061 | (list (ert-get-test 'ert-test-select-symbol))))) | ||||
| 2062 | |||||
| 2063 | (ert-deftest ert-test-select-and () | ||||
| 2064 | (let ((test (make-ert-test | ||||
| 2065 | :name nil | ||||
| 2066 | :body nil | ||||
| 2067 | :most-recent-result (make-ert-test-failed | ||||
| 2068 | :condition nil | ||||
| 2069 | :backtrace nil)))) | ||||
| 2070 | (should (equal (ert-select-tests `(and (member ,test) :failed) t) | ||||
| 2071 | (list test))))) | ||||
| 2072 | |||||
| 2073 | |||||
| 2074 | ;; Test utility functions. | ||||
| 2075 | (ert-deftest ert-proper-list-p () | ||||
| 2076 | (should (ert-proper-list-p '())) | ||||
| 2077 | (should (ert-proper-list-p '(1))) | ||||
| 2078 | (should (ert-proper-list-p '(1 2))) | ||||
| 2079 | (should (ert-proper-list-p '(1 2 3))) | ||||
| 2080 | (should (ert-proper-list-p '(1 2 3 4))) | ||||
| 2081 | (should (not (ert-proper-list-p 'a))) | ||||
| 2082 | (should (not (ert-proper-list-p '(1 . a)))) | ||||
| 2083 | (should (not (ert-proper-list-p '(1 2 . a)))) | ||||
| 2084 | (should (not (ert-proper-list-p '(1 2 3 . a)))) | ||||
| 2085 | (should (not (ert-proper-list-p '(1 2 3 4 . a)))) | ||||
| 2086 | (let ((a (list 1))) | ||||
| 2087 | (setf (cdr (last a)) a) | ||||
| 2088 | (should (not (ert-proper-list-p a)))) | ||||
| 2089 | (let ((a (list 1 2))) | ||||
| 2090 | (setf (cdr (last a)) a) | ||||
| 2091 | (should (not (ert-proper-list-p a)))) | ||||
| 2092 | (let ((a (list 1 2 3))) | ||||
| 2093 | (setf (cdr (last a)) a) | ||||
| 2094 | (should (not (ert-proper-list-p a)))) | ||||
| 2095 | (let ((a (list 1 2 3 4))) | ||||
| 2096 | (setf (cdr (last a)) a) | ||||
| 2097 | (should (not (ert-proper-list-p a)))) | ||||
| 2098 | (let ((a (list 1 2))) | ||||
| 2099 | (setf (cdr (last a)) (cdr a)) | ||||
| 2100 | (should (not (ert-proper-list-p a)))) | ||||
| 2101 | (let ((a (list 1 2 3))) | ||||
| 2102 | (setf (cdr (last a)) (cdr a)) | ||||
| 2103 | (should (not (ert-proper-list-p a)))) | ||||
| 2104 | (let ((a (list 1 2 3 4))) | ||||
| 2105 | (setf (cdr (last a)) (cdr a)) | ||||
| 2106 | (should (not (ert-proper-list-p a)))) | ||||
| 2107 | (let ((a (list 1 2 3))) | ||||
| 2108 | (setf (cdr (last a)) (cddr a)) | ||||
| 2109 | (should (not (ert-proper-list-p a)))) | ||||
| 2110 | (let ((a (list 1 2 3 4))) | ||||
| 2111 | (setf (cdr (last a)) (cddr a)) | ||||
| 2112 | (should (not (ert-proper-list-p a)))) | ||||
| 2113 | (let ((a (list 1 2 3 4))) | ||||
| 2114 | (setf (cdr (last a)) (cdddr a)) | ||||
| 2115 | (should (not (ert-proper-list-p a))))) | ||||
| 2116 | |||||
| 2117 | (ert-deftest ert-parse-keys-and-body () | ||||
| 2118 | (should (equal (ert-parse-keys-and-body '(foo)) '(nil (foo)))) | ||||
| 2119 | (should (equal (ert-parse-keys-and-body '(:bar foo)) '((:bar foo) nil))) | ||||
| 2120 | (should (equal (ert-parse-keys-and-body '(:bar foo a (b))) '((:bar foo) (a (b))))) | ||||
| 2121 | (should (equal (ert-parse-keys-and-body '(:bar foo :a (b))) '((:bar foo :a (b)) nil))) | ||||
| 2122 | (should (equal (ert-parse-keys-and-body '(bar foo :a (b))) '(nil (bar foo :a (b))))) | ||||
| 2123 | (should-error (ert-parse-keys-and-body '(:bar foo :a)))) | ||||
| 2124 | |||||
| 2125 | |||||
| 2126 | |||||
| 2127 | ;; Test `ert-run-tests'. | ||||
| 2128 | (ert-deftest ert-test-run-tests () | ||||
| 2129 | (let ((passing-test (make-ert-test :name 'passing-test | ||||
| 2130 | :body (lambda () (ert-pass)))) | ||||
| 2131 | (failing-test (make-ert-test :name 'failing-test | ||||
| 2132 | :body (lambda () (ert-fail | ||||
| 2133 | "failure message")))) | ||||
| 2134 | ) | ||||
| 2135 | (let ((ert-debug-on-error nil)) | ||||
| 2136 | (let* ((buffer-name (generate-new-buffer-name " *ert-test-run-tests*")) | ||||
| 2137 | (messages nil) | ||||
| 2138 | (mock-message-fn | ||||
| 2139 | (lambda (format-string &rest args) | ||||
| 2140 | (push (apply #'format format-string args) messages)))) | ||||
| 2141 | (save-window-excursion | ||||
| 2142 | (unwind-protect | ||||
| 2143 | (let ((case-fold-search nil)) | ||||
| 2144 | (ert-run-tests-interactively | ||||
| 2145 | `(member ,passing-test ,failing-test) buffer-name | ||||
| 2146 | mock-message-fn) | ||||
| 2147 | (should (equal messages `(,(concat | ||||
| 2148 | "Ran 2 tests, 1 results were " | ||||
| 2149 | "as expected, 1 unexpected")))) | ||||
| 2150 | (with-current-buffer buffer-name | ||||
| 2151 | (goto-char (point-min)) | ||||
| 2152 | (should (equal | ||||
| 2153 | (buffer-substring (point-min) | ||||
| 2154 | (save-excursion | ||||
| 2155 | (forward-line 5) | ||||
| 2156 | (point))) | ||||
| 2157 | (concat | ||||
| 2158 | "Selector: (member <passing-test> <failing-test>)\n" | ||||
| 2159 | "Passed: 1 (0 unexpected)\n" | ||||
| 2160 | "Failed: 1 (1 unexpected)\n" | ||||
| 2161 | "Error: 0 (0 unexpected)\n" | ||||
| 2162 | "Total: 2/2\n"))))) | ||||
| 2163 | (when (get-buffer buffer-name) | ||||
| 2164 | (kill-buffer buffer-name)))))))) | ||||
| 2165 | |||||
| 2166 | (ert-deftest ert-test-special-operator-p () | ||||
| 2167 | (should (ert-special-operator-p 'if)) | ||||
| 2168 | (should-not (ert-special-operator-p 'car)) | ||||
| 2169 | (should-not (ert-special-operator-p 'ert-special-operator-p)) | ||||
| 2170 | (let ((b (gensym))) | ||||
| 2171 | (should-not (ert-special-operator-p b)) | ||||
| 2172 | (fset b 'if) | ||||
| 2173 | (should (ert-special-operator-p b)))) | ||||
| 2174 | |||||
| 22b6ebee » | Christian Ohler | 2008-08-10 | 2175 | ;; This test attempts to demonstrate that there is no way to force | |
| 2176 | ;; immediate truncation of the *Messages* buffer from Lisp (and hence | ||||
| 2177 | ;; justifies the existence of | ||||
| 2178 | ;; `ert-force-message-log-buffer-truncation'): The only way that came | ||||
| 2179 | ;; to my mind was (message ""), which doesn't have the desired effect. | ||||
| 2180 | (ert-deftest ert-test-builtin-message-log-flushing () | ||||
| 2181 | (ert-call-with-temporary-messages-buffer | ||||
| 2182 | (lambda () | ||||
| 2183 | (with-current-buffer "*Messages*" | ||||
| 2184 | (let ((message-log-max 2)) | ||||
| 2185 | (let ((message-log-max t)) | ||||
| 2186 | (loop for i below 4 do | ||||
| 2187 | (message "%s" i)) | ||||
| 2188 | (should (eql (count-lines (point-min) (point-max)) 4))) | ||||
| 2189 | (should (eql (count-lines (point-min) (point-max)) 4)) | ||||
| 2190 | (message "") | ||||
| 2191 | (should (eql (count-lines (point-min) (point-max)) 4)) | ||||
| 2192 | (message "Test message") | ||||
| 2193 | (should (eql (count-lines (point-min) (point-max)) 2))))))) | ||||
| 2194 | |||||
| 2195 | (ert-deftest ert-test-force-message-log-buffer-truncation () | ||||
| 2196 | (labels ((body () | ||||
| 2197 | (loop for i below 5 do | ||||
| 2198 | (message "%s" i))) | ||||
| 2199 | (c (x) | ||||
| 2200 | (ert-call-with-temporary-messages-buffer | ||||
| 2201 | (lambda () | ||||
| 2202 | (let ((message-log-max x)) | ||||
| 2203 | (body)) | ||||
| 2204 | (with-current-buffer "*Messages*" | ||||
| 2205 | (buffer-string))))) | ||||
| 2206 | (lisp (x) | ||||
| 2207 | (ert-call-with-temporary-messages-buffer | ||||
| 2208 | (lambda () | ||||
| 2209 | (let ((message-log-max t)) | ||||
| 2210 | (body)) | ||||
| 2211 | (let ((message-log-max x)) | ||||
| 2212 | (ert-force-message-log-buffer-truncation)) | ||||
| 2213 | (with-current-buffer "*Messages*" | ||||
| 2214 | (buffer-string)))))) | ||||
| 2215 | (loop for x in '(0 1 2 3 4 5 6 t) do | ||||
| 2216 | (should (equal (c x) (lisp x)))))) | ||||
| 2217 | |||||
| fb8b8021 » | Christian Ohler | 2008-08-10 | 2218 | ;; Run tests and make sure they actually ran. | |
| 2219 | (let ((window-configuration (current-window-configuration))) | ||||
| 2220 | (let ((ert-test-body-was-run nil)) | ||||
| 2221 | ;; The buffer name chosen here should not compete with the default | ||||
| 2222 | ;; results buffer name for completion in `switch-to-buffer'. | ||||
| 2223 | (let ((stats (ert-run-tests-interactively "^ert-" " *ert self-tests*"))) | ||||
| 2224 | (assert ert-test-body-was-run) | ||||
| 2225 | (when (zerop (+ (ert-stats-passed-unexpected stats) | ||||
| 2226 | (ert-stats-failed-unexpected stats) | ||||
| 2227 | (ert-stats-error-unexpected stats))) | ||||
| 2228 | ;; Hide results window only when everything went well. | ||||
| 2229 | (set-window-configuration window-configuration))))) | ||||
| 2230 | |||||
| 2231 | (provide 'ert) | ||||
| 2232 | |||||
| 2233 | ;;; ert.el ends here | ||||
