Removed skip and just kept todo.
[ETest.git] / etest.el
blob39cb9043c762e4513e8d66e2b114cf6a7465b2c6
1 ;;; etest.el --- Run tests and get back a hierarchical set of results.
3 ;; Copyright (C) 2008 Philip Jackson
5 ;; Author: Philip Jackson <phil@shellarchive.co.uk>
7 ;; This file is not currently part of GNU Emacs.
9 ;; This program is free software; you can redistribute it and/or
10 ;; modify it under the terms of the GNU General Public License as
11 ;; published by the Free Software Foundation; either version 2, or (at
12 ;; your option) any later version.
14 ;; This program is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 ;; General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program ; see the file COPYING. If not, write to
21 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
24 ;;; Commentary:
26 ;; etest lets you define tests in a domain specific, hierarchical
27 ;; manner and gather results in a simple, structure of the same shape.
29 ;; To install you must put the location of etest into your
30 ;; `load-path', perhaps like this:
32 ;; (add-to-list 'load-path "~/.elisp/etest")
34 ;; Then actually load etest.el:
36 ;; (require 'etest)
37 ;;
38 ;; Valid examples of etest usage might be:
40 ;; Checking (+ 1 1) yeilds a non-nil result:
41 ;; (etest '(ok (+ 1 1)))
43 ;; You can add an extra argument to the end of any test and it will be
44 ;; used as the documentation string for the test:
45 ;;
46 ;; (etest '(ok (+ 1 1) "Check 1 + 1 yeilds non-nil"))
47 ;;
48 ;; If you omit this string then one will be generated for you.
49 ;;
50 ;; Checking (+ 1 1) yeilds 2:
51 ;; (etest '(eq (+ 1 1) 2))
53 ;; To combine these you might do this:
54 ;; (etest '("Check '+' function" (ok (+ 1 1)) (eq (+ 1 1) 2)))
56 ;; The string is just a header to split things up and hopefully make
57 ;; the output more readable. You can have header groups nest as deeply
58 ;; as you like and within each as many tests as you like.
60 ;; To define your own tests the `deftest' function should be used. For
61 ;; example the following can (and is) used to test etest itself:
62 ;;
63 ;; (deftest '(eres 1)
64 ;; (lambda (test)
65 ;; (etest-ok
66 ;; (plist-get (car (etest-run (list test))) :result))))
68 ;; Used like this:
69 ;;
70 ;; (etest '(eres (ok t)))
71 ;;
72 ;; I can see if etests 'built-ins' are working.
74 (require 'etest-result-mode)
75 (require 'etest-execute)
77 (defvar etest-results-function 'etest-rm-refresh-buffer
78 "Function used to display the results of a run.")
80 (defvar etest-candidates-plist
81 '(eq (etest-eq 2)
82 noerror (etest-noerror 1)
83 error (etest-error 1)
84 like (etest-like 2)
85 null (etest-null 1)
86 equal (etest-equal 2)
87 eql (etest-eql 2)
88 ok (etest-ok 1)
89 todo (etest-todo 1))
90 "Plist of test candidates where PROP is the name of the new
91 test . See `deftest' for details of how to modify this.")
93 (defun deftest (details func)
94 "Define a new test. DETAILS must be a list containing the name
95 of the test and the argcount. FUNC is the actual function that
96 will be run."
97 (destructuring-bind (name argcount) details
98 (plist-put etest-candidates-plist
99 name (list func argcount))))
101 (defun etest-todo (form)
102 "Return an etest result set with :result set to t. Set
103 :todo to t and comments to the result of FORM."
104 (let ((res (prin1-to-string
105 (condition-case err (car (etest-run (list form)))
106 (error
107 err)))))
108 (list :result t
109 :comments (concat "got: " (replace-regexp-in-string "\n" "" res))
110 :todo t)))
112 (defun etest-ok (test)
113 "Simply eval TEST and pass if the result is non-nil."
114 (let ((ret (eval test))
115 (result '()))
116 (setq result (plist-put result :result (not (null ret))))
117 (setq result (plist-put result :comments (format "got: '%S'" ret)))
118 result))
120 (defun etest-equality-test (func one two)
121 "Compare two items, ONE and TWO, using the function
122 FUNC. Returns a test result."
123 (let ((one (eval one))
124 (two (eval two))
125 (res (funcall func (eval one) (eval two)))
126 (result '()))
127 (setq result (plist-put result :result res))
128 (setq result (plist-put result :comments
129 (if res
130 (format "both: '%S'" one)
131 (format "one: '%S'\ntwo: '%S'" one two))))))
133 (defun etest-null (test)
134 "Allows the use of `null' in a test."
135 (let ((ret (eval test))
136 (result '()))
137 (setq result (plist-put result :result (null ret)))
138 (setq result (plist-put result :comments (format "got: '%S'" ret)))
139 result))
141 (defun etest-eq (one two)
142 "Allows the use of `eq' in a test."
143 (etest-equality-test 'eq one two))
145 (defun etest-equal (one two)
146 "Allows the use of `equal' in a test."
147 (etest-equality-test 'equal one two))
149 (defun etest-eql (one two)
150 (etest-equality-test 'eql one two))
152 (defun etest-noerror (form)
153 "Assert FORM evals without error."
154 (let ((result (etest-error form)))
155 (plist-put result :result (not (plist-get result :result)))))
157 (defun etest-error (form)
158 "Assert FORM evals with error."
159 (let* ((result '())
160 (val (condition-case err (eval form)
161 (error
162 (setq result (list :result t
163 :comments (format "got: '%S'" err)))))))
164 (if result
165 result
166 (list :result nil
167 :comments (format "got: '%S'" val)))))
169 (defun etest-resultp (result)
170 "Check that RESULT is a vaid test result."
171 (and (plist-member result :result)
172 (booleanp (plist-get result :result))
173 (plist-member result :comments)))
175 (defun etest-like (form re)
176 "Check string is like re"
177 (let* ((i 0)
178 (match nil)
179 (re (eval re))
180 (string (eval form))
181 (comments (format " needle: '%s'\n haystack: '%s'\n" re string))
182 (res (not (not (string-match re string))))
183 (result (list :result res)))
184 (while (setq match (match-string (setq i (1+ i)) string))
185 (setq comments (concat (or comments "")
186 (format "match %3d: '%s'\n" i match))))
187 (plist-put result :comments comments)
188 result))
190 ;;;###autoload
191 (defmacro etest (&rest form)
192 "Wrapper to `etest-run'. Will popup a window displaying the
193 results of the run."
194 `(let* ((meta-info (list :pass 0
195 :fail 0
196 :timestart (current-time)
197 :timefinish 0))
198 (results (etest-run ',form meta-info)))
199 (plist-put meta-info :timefinish (current-time))
200 (when (fboundp etest-results-function)
201 (funcall etest-results-function results meta-info))
202 results))
204 (defun etest-run (form &optional meta-info)
205 "This function does all of the work where actually running the
206 tests is concerned. Takes a valid etest form and will return a
207 similarly shaped set of results. "
208 (mapcar
209 '(lambda (test)
210 (let ((name (car test)))
211 (cond
212 ((stringp name)
213 (cons name (etest-run (cdr test) meta-info)))
214 ((symbolp name)
215 (let ((cand (car (plist-get etest-candidates-plist name)))
216 (args (cdr test))
217 (argcount (cadr (plist-get etest-candidates-plist name)))
218 (doc nil))
219 (unless cand
220 (error "'%s' is not a valid name type" name))
221 (if (< (length args) argcount)
222 (error "%s needs %d arguments" cand argcount)
223 (if (and (eq (length args) (1+ argcount))
224 (stringp (car (last args))))
225 (progn
226 (setq doc (car (last args)))
227 (setq args (delq doc args)))
228 (setq doc (prin1-to-string test))))
229 (let ((results (apply cand args)))
230 (plist-put results :doc doc)
231 (when meta-info
232 (etest-meta-info-update-pass-fail results meta-info))
233 results))))))
234 form))
236 (defun etest-meta-info-update-pass-fail (result meta-info)
237 "Update the pass/fail item in the meta-info plist based on the
238 resuls in RESULT."
239 (let ((type (if (plist-get result :result) :pass :fail)))
240 (plist-put meta-info type (1+ (plist-get meta-info type)))))
242 ;; This is defined so that etest can test itself
243 (defun etest-test-tests (test result)
244 "This test is used to test ETest itself. TEST is the test to be
245 run (in ETest syntax) and RESULT is a plist of items you would
246 like to compare. See the file etest.etest for example usage."
247 (let* ((testres (car (etest-run (list test))))
248 (my-res t)
249 (res-items '(:result :comments :doc :todo))
250 (my-comments (mapconcat
251 '(lambda (item)
252 (format "%9S %S" item (plist-get testres item)))
253 res-items
254 "\n")))
255 (dolist (sym res-items)
256 (let ((testval (plist-get testres sym))
257 (resultval (plist-get result sym)))
258 (when (and (plist-member result sym)
259 (not (equal testval resultval)))
260 (setq my-res nil))))
261 (list :result my-res :comments my-comments)))
263 ;; Make `etest-test-tests' available
264 (deftest '(eres 2) 'etest-test-tests)
266 (provide 'etest)