org-mac: Fix link to org-mac-mail-link from org-mac
[worg.git] / org-tests / tools / el-mock.el
blob4f5a356f6b723673deb21e12c828b47685e91d89
1 ;;; el-mock.el --- Tiny Mock and Stub framework in Emacs Lisp
2 ;; $Id: el-mock.el,v 1.17 2008/08/28 19:04:48 rubikitch Exp $
4 ;; Copyright (C) 2008 rubikitch
6 ;; Author: rubikitch <rubikitch@ruby-lang.org>
7 ;; Keywords: lisp, testing, unittest
8 ;; URL: http://www.emacswiki.org/cgi-bin/wiki/download/el-mock.el
10 ;; This file is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
15 ;; This file is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
22 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
25 ;;; Commentary:
27 ;; Emacs Lisp Mock is a library for mocking and stubbing using
28 ;; readable syntax. Most commonly Emacs Lisp Mock is used in
29 ;; conjunction with Emacs Lisp Expectations, but it can be used in
30 ;; other contexts.
32 ;; Emacs Lisp Mock provides two scope interface of mock and stub:
33 ;; `with-mock' and `mocklet'. `with-mock' only defines a
34 ;; scope. `mocklet' is more sophisticated interface than `with-mock':
35 ;; `mocklet' defines local mock and stub like `let', `flet', and
36 ;; `macrolet'.
38 ;; Within `with-mock' body (or argument function specified in
39 ;; `mock-protect'), you can create a mock and a stub. To create a
40 ;; stub, use `stub' macro. To create a mock, use `mock' macro.
42 ;; For further information: see docstrings.
43 ;; [EVAL IT] (describe-function 'with-mock)
44 ;; [EVAL IT] (describe-function 'mocklet)
45 ;; [EVAL IT] (describe-function 'stub)
46 ;; [EVAL IT] (describe-function 'mock)
48 ;;; History:
50 ;; $Log: el-mock.el,v $
51 ;; Revision 1.17 2008/08/28 19:04:48 rubikitch
52 ;; Implement `not-called' mock.
54 ;; Revision 1.16 2008/08/28 18:23:28 rubikitch
55 ;; unit test: use dont-compile
57 ;; Revision 1.15 2008/04/18 18:02:24 rubikitch
58 ;; bug fix about symbol
60 ;; Revision 1.14 2008/04/13 18:23:43 rubikitch
61 ;; removed `message' advice.
62 ;; mock-suppress-redefinition-message: suppress by empty message
64 ;; Revision 1.13 2008/04/12 17:36:11 rubikitch
65 ;; raise mock-syntax-error when invalid `mock' and `stub' spec.
67 ;; Revision 1.12 2008/04/12 17:30:33 rubikitch
68 ;; inhibit using `mock' and `stub' outside `mock-protect' function.
70 ;; Revision 1.11 2008/04/12 17:10:42 rubikitch
71 ;; * added docstrings.
72 ;; * `stublet' is an alias of `mocklet'.
74 ;; Revision 1.10 2008/04/12 16:14:16 rubikitch
75 ;; * allow omission of return value
76 ;; * (mock foo 2) and (stub foo 2) cause error now
77 ;; * arranged test
79 ;; Revision 1.9 2008/04/12 15:10:32 rubikitch
80 ;; changed mocklet syntax
82 ;; Revision 1.8 2008/04/12 14:54:16 rubikitch
83 ;; added Commentary
85 ;; Revision 1.7 2008/04/10 16:14:02 rubikitch
86 ;; fixed advice-related bug
88 ;; Revision 1.6 2008/04/10 14:08:32 rubikitch
89 ;; *** empty log message ***
91 ;; Revision 1.5 2008/04/10 14:01:48 rubikitch
92 ;; arranged code/test
94 ;; Revision 1.4 2008/04/10 12:57:00 rubikitch
95 ;; mock verify
97 ;; Revision 1.3 2008/04/10 07:50:10 rubikitch
98 ;; *** empty log message ***
100 ;; Revision 1.2 2008/04/10 07:48:04 rubikitch
101 ;; New functions:
102 ;; stub/setup
103 ;; stub/teardown
104 ;; stub/parse-spec
106 ;; refactored with-stub-function
108 ;; Revision 1.1 2008/04/10 07:37:54 rubikitch
109 ;; Initial revision
112 ;;; Code:
114 (eval-when-compile (require 'cl))
115 (require 'advice)
117 ;;;; stub setup/teardown
118 (defun stub/setup (funcsym value)
119 (mock-suppress-redefinition-message
120 (lambda ()
121 (when (fboundp funcsym)
122 (put 'mock-original-func funcsym (symbol-function funcsym)))
123 (ad-safe-fset funcsym `(lambda (&rest x) ,value)))))
125 (defun stub/teardown (funcsym)
126 (mock-suppress-redefinition-message
127 (lambda ()
128 (let ((func (get 'mock-original-func funcsym)))
129 (if (not func)
130 (fmakunbound funcsym)
131 (ad-safe-fset funcsym func)
132 ;; may be unadviced
133 )))))
135 ;;;; mock setup/teardown
136 (defun mock/setup (func-spec value)
137 (mock-suppress-redefinition-message
138 (lambda ()
139 (let ((funcsym (car func-spec)))
140 (when (fboundp funcsym)
141 (put 'mock-original-func funcsym (symbol-function funcsym)))
142 (put 'mock-not-yet-called funcsym t)
143 (ad-safe-fset funcsym
144 `(lambda (&rest actual-args)
145 (put 'mock-not-yet-called ',funcsym nil)
146 (add-to-list 'mock-verify-list
147 (list ',funcsym ',(cdr func-spec) actual-args))
148 ,value))))))
150 (defun not-called/setup (funcsym)
151 (mock-suppress-redefinition-message
152 (lambda ()
153 (let ()
154 (when (fboundp funcsym)
155 (put 'mock-original-func funcsym (symbol-function funcsym)))
156 (ad-safe-fset funcsym
157 `(lambda (&rest actual-args)
158 (signal 'mock-error '(called))))))))
160 (defalias 'mock/teardown 'stub/teardown)
162 ;;;; mock verify
163 (put 'mock-error 'error-conditions '(mock-error error))
164 (put 'mock-error 'error-message "Mock error")
165 (defun mock-verify ()
166 (when (loop for f in -mocked-functions
167 thereis (get 'mock-not-yet-called f))
168 (signal 'mock-error '(not-called)))
169 (loop for (funcsym expected-args actual-args) in mock-verify-list
171 (mock-verify-args funcsym expected-args actual-args)))
173 (defun mock-verify-args (funcsym expected-args actual-args)
174 (loop for e in expected-args
175 for a in actual-args
177 (unless (eq e '*) ; `*' is wildcard argument
178 (unless (equal (eval e) a)
179 (signal 'mock-error (list (cons funcsym expected-args)
180 (cons funcsym actual-args)))))))
181 ;;;; stub/mock provider
182 (defvar -stubbed-functions nil)
183 (defvar -mocked-functions nil)
184 (defvar mock-verify-list nil)
185 (defvar in-mocking nil)
186 (defun mock-protect (body-fn)
187 "The substance of `with-mock' macro.
188 Prepare for mock/stub, call BODY-FN, and teardown mock/stub.
190 For developer:
191 When you adapt Emacs Lisp Mock to a testing framework, wrap test method around this function."
192 (let (mock-verify-list
193 -stubbed-functions
194 -mocked-functions
195 (in-mocking t))
196 (setplist 'mock-original-func nil)
197 (setplist 'mock-not-yet-called nil)
198 (unwind-protect
199 (funcall body-fn)
200 (mapcar #'stub/teardown -stubbed-functions)
201 (unwind-protect
202 (mock-verify)
203 (mapcar #'mock/teardown -mocked-functions)))))
205 ;;;; message hack
206 (defun mock-suppress-redefinition-message (func)
207 "Erase \"ad-handle-definition: `%s' got redefined\" message."
208 (prog1
209 (funcall func)
210 (message "")))
211 (put 'mock-syntax-error 'error-conditions '(mock-syntax-error error))
212 (put 'mock-syntax-error 'error-message "Mock syntax error")
214 ;;;; User interface
215 (defmacro with-mock (&rest body)
216 "Execute the forms in BODY. You can use `mock' and `stub' in BODY.
217 The value returned is the value of the last form in BODY.
218 After executing BODY, mocks and stubs are guaranteed to be released.
220 Example:
221 (with-mock
222 (stub fooz => 2)
223 (fooz 9999)) ; => 2
225 `(mock-protect
226 (lambda () ,@body)))
227 (defalias 'with-stub 'with-mock)
229 (defmacro stub (function &rest rest)
230 "Create a stub for FUNCTION.
231 Stubs are temporary functions which accept any arguments and return constant value.
232 Stubs are removed outside `with-mock' (`with-stub' is an alias) and `mocklet'.
234 Synopsis:
235 * (stub FUNCTION)
236 Create a FUNCTION stub which returns nil.
237 * (stub FUNCTION => RETURN-VALUE)
238 Create a FUNCTION stub which returns RETURN-VALUE.
241 Example:
242 (with-mock
243 (stub foo)
244 (stub bar => 1)
245 (and (null (foo)) (= (bar 7) 1))) ; => t
247 (let ((value (cond ((eq '=> (car rest))
248 (cadr rest))
249 ((null rest) nil)
250 (t (signal 'mock-syntax-error '("Use `(stub FUNC)' or `(stub FUNC => RETURN-VALUE)'"))))))
251 `(if (not in-mocking)
252 (error "Do not use `stub' outside")
253 (stub/setup ',function ',value)
254 (push ',function -stubbed-functions))))
256 (defmacro mock (func-spec &rest rest)
257 "Create a mock for function described by FUNC-SPEC.
258 Mocks are temporary functions which accept specified arguments and return constant value.
259 If mocked functions are not called or called by different arguments, an `mock-error' occurs.
260 Mocks are removed outside `with-mock' and `mocklet'.
262 Synopsis:
263 * (mock (FUNCTION ARGS...))
264 Create a FUNCTION mock which returns nil.
265 * (mock (FUNCTION ARGS...) => RETURN-VALUE)
266 Create a FUNCTION mock which returns RETURN-VALUE.
268 Wildcard:
269 The `*' is a special symbol: it accepts any value for that argument position.
271 Example:
272 (with-mock
273 (mock (f * 2) => 3)
274 (mock (g 3))
275 (and (= (f 9 2) 3) (null (g 3)))) ; => t
276 (with-mock
277 (mock (g 3))
278 (g 7)) ; (mock-error (g 3) (g 7))
280 (let ((value (cond ((eq '=> (car rest))
281 (cadr rest))
282 ((null rest) nil)
283 (t (signal 'mock-syntax-error '("Use `(mock FUNC-SPEC)' or `(mock FUNC-SPEC => RETURN-VALUE)'"))))))
284 `(if (not in-mocking)
285 (error "Do not use `mock' outside")
286 (mock/setup ',func-spec ',value)
287 (push ',(car func-spec) -mocked-functions))))
289 (defmacro not-called (function)
290 "Create a not-called mock for FUNCTION.
291 Not-called mocks are temporary functions which raises an error when called.
292 If not-called functions are called, an `mock-error' occurs.
293 Not-called mocks are removed outside `with-mock' and `mocklet'.
295 Synopsis:
296 * (not-called FUNCTION)
297 Create a FUNCTION not-called mock.
299 Example:
300 (with-mock
301 (not-called f)
302 t) ; => t
303 (with-mock
304 (not-called g)
305 (g 7)) ; => (mock-error called)
307 (let ()
308 `(if (not in-mocking)
309 (error "Do not use `not-called' outside")
310 (not-called/setup ',function)
311 (push ',function -mocked-functions))))
314 (defun mock-parse-spec (spec)
315 (cons 'progn
316 (mapcar (lambda (args)
317 (if (eq (cadr args) 'not-called)
318 `(not-called ,(car args))
319 (cons (if (consp (car args)) 'mock 'stub)
320 args)))
321 spec)))
323 (defun mocklet-function (spec body-func)
324 (with-mock
325 (eval (mock-parse-spec spec))
326 (funcall body-func)))
328 (defmacro mocklet (speclist &rest body)
329 "`let'-like interface of `with-mock', `mock', `not-called' and `stub'.
331 Create mocks and stubs described by SPECLIST then execute the forms in BODY.
332 SPECLIST is a list of mock/not-called/stub spec.
333 The value returned is the value of the last form in BODY.
334 After executing BODY, mocks and stubs are guaranteed to be released.
336 Synopsis of spec:
337 Spec is arguments of `mock', `not-called' or `stub'.
338 * ((FUNCTION ARGS...)) : mock which returns nil
339 * ((FUNCTION ARGS...) => RETURN-VALUE) ; mock which returns RETURN-VALUE
340 * (FUNCTION) : stub which returns nil
341 * (FUNCTION => RETURN-VALUE) ; stub which returns RETURN-VALUE
342 * (FUNCTION not-called) ; not-called FUNCTION
344 Example:
345 (mocklet (((mock-nil 1))
346 ((mock-1 *) => 1)
347 (stub-nil)
348 (stub-2 => 2))
349 (and (null (mock-nil 1)) (= (mock-1 4) 1)
350 (null (stub-nil 'any)) (= (stub-2) 2))) ; => t
352 `(mocklet-function ',speclist (lambda () ,@body)))
354 (defalias 'stublet 'mocklet)
356 (put 'with-mock 'lisp-indent-function 0)
357 (put 'with-stub 'lisp-indent-function 0)
358 (put 'mocklet 'lisp-indent-function 1)
359 (put 'stublet 'lisp-indent-function 1)
361 ;;;; unit test
362 (dont-compile
363 (when (fboundp 'expectations)
364 (expectations
365 (desc "stub setup/teardown")
366 (expect 2
367 (stub/setup 'foo 2)
368 (prog1
369 (foo 1 2 3)
370 (stub/teardown 'foo)))
371 (expect nil
372 (stub/setup 'foox 2)
373 (foox 1 2 3)
374 (stub/teardown 'foox)
375 (fboundp 'foox))
376 (desc "with-mock interface")
377 (expect 9801
378 (with-mock
379 9801))
380 (desc "stub macro")
381 (expect nil
382 (with-mock
383 (stub hogehoges)
384 (hogehoges 75)))
385 (expect 2
386 (with-mock
387 (stub fooz => 2)
388 (fooz 9999)))
389 (expect nil
390 (with-mock
391 (stub fooz => 2)
392 (fooz 3))
393 (fboundp 'fooz))
394 (expect nil
395 (with-mock
396 (stub hoge) ;omission of return value
397 (hoge)))
398 (expect 'hoge
399 (with-mock
400 (stub me => 'hoge)
401 (me 1)))
402 (expect 34
403 (with-mock
404 (stub me => (+ 3 31))
405 (me 1)))
406 ;; TODO defie mock-syntax-error / detect mock-syntax-error in expectations
407 (desc "abused stub macro")
408 (expect (error mock-syntax-error '("Use `(stub FUNC)' or `(stub FUNC => RETURN-VALUE)'"))
409 (with-mock
410 (stub fooz 7)))
411 (expect (error-message "Do not use `stub' outside")
412 (let (in-mocking) ; while executing `expect', `in-mocking' is t.
413 (stub hahahaha)))
414 (desc "mock macro")
415 (expect 2
416 (with-mock
417 (mock (foom 5) => 2)
418 (foom 5)))
419 (expect 3
420 (with-mock
421 (mock (foo 5) => 2)
422 (mock (bar 7) => 1)
423 (+ (foo 5) (bar 7))))
424 (expect 3
425 (flet ((plus () (+ (foo 5) (bar 7))))
426 (with-mock
427 (mock (foo 5) => 2)
428 (mock (bar 7) => 1)
429 (plus))))
430 (expect 1
431 (with-mock
432 (mock (f * 2) => 1)
433 (f 1 2)))
434 (expect 1
435 (with-mock
436 (mock (f * (1+ 1)) => (+ 0 1)) ;evaluated
437 (f 1 2)))
438 (expect nil
439 (with-mock
440 (mock (f 2)) ;omission of return value
441 (f 2)))
442 (expect 'hoge
443 (with-mock
444 (mock (me 1) => 'hoge)
445 (me 1)))
446 (expect 34
447 (with-mock
448 (mock (me 1) => (+ 3 31))
449 (me 1)))
451 (desc "unfulfilled mock")
452 (expect (error mock-error '((foom 5) (foom 6)))
453 (with-mock
454 (mock (foom 5) => 2)
455 (foom 6)))
456 (expect (error mock-error '((bar 7) (bar 8)))
457 (with-mock
458 (mock (foo 5) => 2)
459 (mock (bar 7) => 1)
460 (+ (foo 5) (bar 8))))
461 (expect (error mock-error '(not-called))
462 (with-mock
463 (mock (foo 5) => 2)))
464 (expect (error mock-error '(not-called))
465 (with-mock
466 (mock (vi 5) => 2)
467 (mock (foo 5) => 2)
468 (vi 5)))
469 (expect (error mock-error '((f 2) (f 4)))
470 (with-mock
471 (mock (f 2)) ;omission of return value
472 (f 4)))
473 (desc "abused mock macro")
474 (expect (error mock-syntax-error '("Use `(mock FUNC-SPEC)' or `(mock FUNC-SPEC => RETURN-VALUE)'"))
475 (with-mock
476 (mock (fooz) 7)))
477 (expect (error-message "Do not use `mock' outside")
478 (let (in-mocking) ; while executing `expect', `in-mocking' is t.
479 (mock (hahahaha))))
481 (desc "mock with stub")
482 (expect 8
483 (with-mock
484 (mock (f 1 2) => 3)
485 (stub hoge => 5)
486 (+ (f 1 2) (hoge 'a))))
487 (expect (error mock-error '((f 1 2) (f 3 4)))
488 (with-mock
489 (mock (f 1 2) => 3)
490 (stub hoge => 5)
491 (+ (f 3 4) (hoge 'a))))
493 (desc "with-stub is an alias of with-mock")
494 (expect 'with-mock
495 (symbol-function 'with-stub))
497 (desc "stublet is an alias of mocklet")
498 (expect 'mocklet
499 (symbol-function 'stublet))
501 (desc "mock-parse-spec")
502 (expect '(progn
503 (mock (f 1 2) => 3)
504 (stub hoge => 5))
505 (mock-parse-spec
506 '(((f 1 2) => 3)
507 (hoge => 5))))
508 (expect '(progn
509 (not-called g))
510 (mock-parse-spec
511 '((g not-called))))
513 (desc "mocklet")
514 (expect 8
515 (mocklet (((f 1 2) => 3)
516 (hoge => 5))
517 (+ (f 1 2) (hoge 'a))))
518 (expect 2
519 (mocklet ((foo => 2))
520 (foo 1 2 3)))
521 (expect 3
522 (defun defined-func (x) 3)
523 (prog1
524 (mocklet ((defined-func => 3))
525 (defined-func 3))
526 (fmakunbound 'defined-func)))
527 (expect nil
528 (mocklet ((f)) ;omission of return value
529 (f 91)))
530 (expect nil
531 (mocklet (((f 76))) ;omission of return value
532 (f 76)))
533 (expect 5
534 (mocklet ((a => 3)
535 (b => 2))
536 1 ;multiple exprs
537 (+ (a 999) (b 7))))
539 (desc "stub for defined function")
540 (expect "xxx"
541 (defun blah (x) (* x 2))
542 (prog1
543 (let ((orig (symbol-function 'blah)))
544 (mocklet ((blah => "xxx"))
545 (blah "xx")))
546 (fmakunbound 'blah)))
547 (expect t
548 (defun blah (x) (* x 2))
549 (prog1
550 (let ((orig (symbol-function 'blah)))
551 (mocklet ((blah => "xx"))
552 (blah "xx"))
553 (equal orig (symbol-function 'blah)))
554 (fmakunbound 'blah)))
556 (desc "stub for adviced function")
557 (expect "xxx"
558 (mock-suppress-redefinition-message ;silence redefinition warning
559 (lambda ()
560 (defun fugaga (x) (* x 2))
561 (defadvice fugaga (around test activate)
562 (setq ad-return-value (concat "[" ad-return-value "]")))
563 (prog1
564 (let ((orig (symbol-function 'fugaga)))
565 (mocklet ((fugaga => "xxx"))
566 (fugaga "aaaaa")))
567 (fmakunbound 'fugaga)))))
568 (expect t
569 (mock-suppress-redefinition-message
570 (lambda ()
571 (defun fugaga (x) (* x 2))
572 (defadvice fugaga (around test activate)
573 (setq ad-return-value (concat "[" ad-return-value "]")))
574 (prog1
575 (let ((orig (symbol-function 'fugaga)))
576 (mocklet ((fugaga => "xx"))
577 (fugaga "aaaaa"))
578 (equal orig (symbol-function 'fugaga)))
579 (fmakunbound 'fugaga)))))
581 (desc "mock for adviced function")
582 (expect "xx"
583 (mock-suppress-redefinition-message
584 (lambda ()
585 (defun fugaga (x) (* x 2))
586 (defadvice fugaga (around test activate)
587 (setq ad-return-value (concat "[" ad-return-value "]")))
588 (prog1
589 (let ((orig (symbol-function 'fugaga)))
590 (mocklet (((fugaga "aaaaa") => "xx"))
591 (fugaga "aaaaa")))
592 (fmakunbound 'fugaga)))))
593 (expect t
594 (mock-suppress-redefinition-message
595 (lambda ()
596 (defun fugaga (x) (* x 2))
597 (defadvice fugaga (around test activate)
598 (setq ad-return-value (concat "[" ad-return-value "]")))
599 (prog1
600 (let ((orig (symbol-function 'fugaga)))
601 (mocklet (((fugaga "aaaaa") => "xx"))
602 (fugaga "aaaaa"))
603 (equal orig (symbol-function 'fugaga)))
604 (fmakunbound 'fugaga)))))
605 (desc "not-called macro")
606 (expect 'ok
607 (with-mock
608 (not-called foom)
609 'ok))
610 (desc "mocklet/notcalled")
611 (expect 'ok
612 (mocklet ((foom not-called))
613 'ok))
614 (desc "unfulfilled not-called")
615 (expect (error mock-error '(called))
616 (with-mock
617 (not-called hoge)
618 (hoge 1)))
619 (desc "abused not-called macro")
620 (expect (error-message "Do not use `not-called' outside")
621 (let (in-mocking) ; while executing `expect', `in-mocking' is t.
622 (not-called hahahaha)))
623 (desc "not-called for adviced function")
624 (expect "not-called"
625 (mock-suppress-redefinition-message ;silence redefinition warning
626 (lambda ()
627 (defun fugaga (x) (* x 2))
628 (defadvice fugaga (around test activate)
629 (setq ad-return-value (concat "[" ad-return-value "]")))
630 (prog1
631 (let ((orig (symbol-function 'fugaga)))
632 (mocklet ((fugaga not-called))
633 "not-called"))
634 (fmakunbound 'fugaga)))))
635 (expect t
636 (mock-suppress-redefinition-message
637 (lambda ()
638 (defun fugaga (x) (* x 2))
639 (defadvice fugaga (around test activate)
640 (setq ad-return-value (concat "[" ad-return-value "]")))
641 (prog1
642 (let ((orig (symbol-function 'fugaga)))
643 (mocklet ((fugaga not-called))
644 "not-called")
645 (equal orig (symbol-function 'fugaga)))
646 (fmakunbound 'fugaga)))))
651 (provide 'el-mock)
653 ;; How to save (DO NOT REMOVE!!)
654 ;; (emacswiki-post "el-mock.el")
655 ;;; el-mock.el ends here