From 13d3f30f34750be1d6d34c51c554dd07298be81e Mon Sep 17 00:00:00 2001 From: John Miller Date: Fri, 9 Sep 2016 10:10:36 -0500 Subject: [PATCH] Squashed 'test/mocker/' content from commit a1ddf87 git-subtree-dir: test/mocker git-subtree-split: a1ddf87150f67306ae8da927117063c75588be30 --- .bumpversion.cfg | 10 ++ .ert-runner | 1 + .travis.yml | 14 ++ Cask | 11 ++ Makefile | 25 +++ README.markdown | 206 +++++++++++++++++++++++++ mocker.el | 368 ++++++++++++++++++++++++++++++++++++++++++++ test/mocker-test.el | 279 +++++++++++++++++++++++++++++++++ 8 files changed, 914 insertions(+) create mode 100644 .bumpversion.cfg create mode 100644 .ert-runner create mode 100644 .travis.yml create mode 100644 Cask create mode 100644 Makefile create mode 100644 README.markdown create mode 100644 mocker.el create mode 100644 test/mocker-test.el diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..3daddf1 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,10 @@ +[bumpversion] +current_version = 0.3.1 +commit = True +tag = True +tag_name = v{new_version} + +[bumpversion:file:mocker.el] + +[bumpversion:file:Cask] + diff --git a/.ert-runner b/.ert-runner new file mode 100644 index 0000000..e35e9c9 --- /dev/null +++ b/.ert-runner @@ -0,0 +1 @@ +-L . diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ee54eed --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: emacs-lisp +sudo: no +env: + - EVM_EMACS=emacs-24.1-travis + - EVM_EMACS=emacs-24.2-travis + - EVM_EMACS=emacs-24.3-travis + - EVM_EMACS=emacs-24.4-travis + - EVM_EMACS=emacs-24.5-travis + - EVM_EMACS=emacs-25-pre-travis +before_install: + - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > travis.sh && source ./travis.sh + - evm install "$EVM_EMACS" --use --skip +script: + make travis-ci diff --git a/Cask b/Cask new file mode 100644 index 0000000..58ac238 --- /dev/null +++ b/Cask @@ -0,0 +1,11 @@ +(source gnu) +(source melpa-stable) + +(package "mocker" "0.3.1" + "mocking framework for emacs.") + +(depends-on "eieio" "1.4") + +(development + (depends-on "ert-runner") + (depends-on "shut-up")) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a71e3d7 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +EMACS ?= emacs +CASK ?= cask + +test: unit-tests + +unit-tests: elpa + ${CASK} exec ert-runner + +elpa: + mkdir -p elpa + ${CASK} install 2> elpa/install.log + +clean-elpa: + rm -rf elpa + +clean-elc: + rm -f *.elc test/*.elc + +clean: clean-elpa clean-elc + +print-deps: + ${EMACS} --version + @echo CASK=${CASK} + +travis-ci: print-deps test diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..16fe32e --- /dev/null +++ b/README.markdown @@ -0,0 +1,206 @@ +[![Build Status](https://travis-ci.org/sigma/mocker.el.png?branch=master)](https://travis-ci.org/sigma/mocker.el) + +Mocker.el is a mocking framework for Emacs lisp. + +Its single entry point, `mocker-let` provides an `let` like interface to +defining mock objects. Actually, `mocker-let` is a wrapper around `flet`, which +can be seen as a way to manually generate mocks. + +## Usage + +### Basic usage + +Let's start with a simple example: + +```lisp +(mocker-let ((foo (x y z) + ((:input '(1 2 3) :output 4) + (:input '(4 5 6) :output 10))) + (bar (x) + ((:input '(42) :output 4)))) + (+ (foo 1 2 3) + (foo 4 5 6) + (bar 42))) +``` + +Each mock is defined in a function-style, and is associated with a set of +"records" that map expected inputs to desired outputs. + +### Call order + +By default, the order of definition within a mock has to be respected by the +wrapped code, so that in this situation it would be an error to observe `(foo +4 5 6)` before `(foo 1 2 3)`. + +```lisp +(mocker-let ((foo (x y z) + ((:input '(1 2 3) :output 4) + (:input '(4 5 6) :output 10))) + (bar (x) + ((:input '(42) :output 4)))) + (+ (foo 4 5 6) + (foo 1 2 3) + (bar 42))) +``` + +In such a situation, you'll get a typed error with a message like +``` +(mocker-record-error "Violated record while mocking `foo'. Expected input like: `(1 2 3)', got: `(4 5 6)' instead") +... +``` + +If order is not important, you can obtain the same effect as before by +specifying it: + +```lisp +(mocker-let ((foo (x y z) + :ordered nil + ((:input '(1 2 3) :output 4) + (:input '(4 5 6) :output 10))) + (bar (x) + ((:input '(42) :output 4)))) + (+ (foo 4 5 6) + (foo 1 2 3) + (bar 42))) +``` + +### Counting calls + +In many situations it can be pretty repetitive to list all the expected calls +to a mock. In some, the count might even be a range rather than a fixed number. +The `:min-occur` and `:max-occur` options allow to tune that. By default, they +are both set to 1, so that exactly 1 call is expected. As a special case, +setting `:max-occur` to nil will accept any number of calls. +An `:occur` shorthand is also provided, to expect an exact number of calls. + +```lisp +(mocker-let ((foo (x) + ((:input '(1) :output 1 :min-occur 1 :max-occur 3)))) + (+ (foo 1) (foo 1))) +``` + +This example will accept between 1 and 3 calls to `(foo 1)`, and complain if +that constraint is not fulfilled. + +Note the applied algorithm is greedy, so that as many calls as possible will +count as part of the earliest constraints. + +### Flexible input/output + +The examples above are fine, but they suppose input and output are just +constant expressions. A useful addition is the ability to match arbitrary input +and generate arbitrary output. + +To this end, the `:input-matcher` and `:output-generator` options can be used +instead (actually think of `:input` and `:output` as convenience shortcuts for +constant matcher/generator). + +```lisp +(mocker-let ((foo (x) + :ordered nil + ((:input-matcher 'oddp :output-generator 'identity :max-occur 2) + (:input-matcher 'evenp :output 0)))) + (+ (foo 1) (foo 2) (foo 3))) +``` + +Both `:input-matcher` and `:output-generator` values need to be functions (or +function symbols) accepting the same arguments as the mocked function itself. + +## Extensibility + +Each record definition actually builds a `mocker-record` object, that's +responsible for checking the actual behavior. By providing alternative +implementations of those records, one can adapt the mocking to special needs. + +### Stubs + +As a quick proof of concept, an implementation of a stub is provided with the +class `mocker-stub-record` which casualy ignores any input and always emits the +same output: + +```lisp +(mocker-let ((foo (x) + ((:record-cls 'mocker-stub-record :output 42)))) + (foo 12345)) +``` + +### Passthrough + +In some occasions, you might want to mock only some calls for a function, and +let other calls invoke the real one. This can be achieved by using the +`mocker-passthrough-record`. In the following example, the first call to +`ignore` uses the real implementation, while the second one is mocked to return +`t`: + +```lisp +(mocker-let ((ignore (x) + :records ((:record-cls mocker-passthrough-record + :input '(42)) + (:input '(58) :output t)))) + (or (ignore 42) + (ignore 58))) +``` + +### Provide your own + +Customized classes can be provided, that can even introduce a mini-language for +describing the stub. This can be achieved by overloading +`mocker-read-record` correctly. + +In case the customized record class is meant to be used in many tests, it might +be more convenient to use a pattern like: + +```lisp +(let ((mocker-mock-default-record-cls 'mocker-stub-record)) + (mocker-let ((foo (x) + ((:output 42))) + (bar (x y) + ((:output 1)))) + (+ (foo 12345) + (bar 5 14)))) +``` + +Also note that `mocker-stub-record` set their `:min-occur` to 0 and +`:max-occur` to nil, if not specified otherwise. + +## Comparison to other mocking solutions + +* el-mock.el (http://www.emacswiki.org/emacs/EmacsLispMock) + + * el-mock.el uses a small DSL for recording behavior, which is great for + conciseness. mocker.el instead uses regular lisp as much as possible, which + is more flexible. + + * el-mock.el does not allow recording multiple behaviors (the same call will + always return the same value). This makes it difficult to use in real + situation, where different call sites for the same function might have to + behave differently. + +## Examples + +```lisp +;;; automatically answer some `y-or-n-p' questions +(mocker-let ((y-or-n-p (prompt) + ((:input '("Really?") :output t) + (:input '("I mean... for real?") :output nil)))) + ...) +``` + +```lisp +;;; blindly accept all `yes-or-no-p' questions +(mocker-let ((yes-or-no-p (prompt) + ((:record-cls mocker-stub-record :output t)))) + ...) +``` + +```lisp +;;; make `foo' generate the fibonacci suite, no matter how it's called +(mocker-let ((foo (x) + ((:input-matcher (lambda (x) t) + :output-generator (lexical-let ((x 0) (y 1)) + (lambda (any) + (let ((z (+ x y))) + (setq x y y z)))) + :max-occur nil)))) + ...) +``` diff --git a/mocker.el b/mocker.el new file mode 100644 index 0000000..2c02bd2 --- /dev/null +++ b/mocker.el @@ -0,0 +1,368 @@ +;;; mocker.el --- mocking framework for emacs + +;; Copyright (C) 2011 Yann Hodique. + +;; Author: Yann Hodique +;; Keywords: lisp, testing +;; Version: 0.3.1 +;; Package-Requires: ((eieio "1.3") (el-x "0.2.4")) + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; + +;;; Code: + +(eval-when-compile + (require 'cl)) + +(require 'eieio) + +(eval-and-compile + ;; use dflet from el-x if available + (if (require 'dflet nil t) + (defalias 'mocker-flet 'dflet) + ;; fallback to regular flet, hoping it's still there + (defalias 'mocker-flet 'flet))) + +(defvar mocker-mock-default-record-cls 'mocker-record) + +(put 'mocker-mock-error 'error-conditions '(mocker-mock-error error)) +(put 'mocker-mock-error 'error-message "Mocker mock error") + +(put 'mocker-record-error 'error-conditions '(mocker-record-error error)) +(put 'mocker-record-error 'error-message "Mocker record error") + +(defun mocker--plist-remove (plist key) + ;; courtesy of pjb + (if (eq (car plist) key) (cdr (cdr plist)) + (cons (car plist) + (cons (cadr plist) + (mocker--plist-remove (cddr plist) key))))) + +;;; Mock object +(defclass mocker-mock () + ((function :initarg :function :type symbol) + (orig-def :initarg :orig-def :initform nil) + (argspec :initarg :argspec :initform nil :type list) + (ordered :initarg :ordered :initform t) + (records :initarg :records :initform nil :type list))) + +(defmethod constructor :static ((mock mocker-mock) newname &rest args) + (let* ((obj (call-next-method)) + (recs (oref obj :records)) + (func (oref obj :function))) + (oset obj :orig-def (when (fboundp func) (symbol-function func))) + (oset obj :records nil) + (mapc #'(lambda (r) + (apply 'mocker-add-record obj r)) + recs) + obj)) + +(defmethod mocker-add-record ((mock mocker-mock) &rest args) + (object-add-to-list mock :records + (let ((cls mocker-mock-default-record-cls) + (tmp (plist-get args :record-cls))) + (when tmp + (setq cls tmp + args (mocker-read-record cls + (mocker--plist-remove + args :record-cls)))) + (apply 'make-instance cls :-mock mock + :-sym (make-symbol "unique") args)) + t)) + +(defmethod mocker-fail-mock ((mock mocker-mock) args) + (signal 'mocker-mock-error + (list (format (concat "Unexpected call to mock `%s'" + " with input `%s'") + (oref mock :function) args)))) + +(defvar mocker-inhibit nil) + +(defmethod mocker-run ((mock mocker-mock) &rest args) + (if (not mocker-inhibit) + (let* ((mocker-inhibit t) + (rec (mocker-find-active-record mock args)) + (ordered (oref mock :ordered))) + (cond ((null rec) + (mocker-fail-mock mock args)) + ((or (not ordered) (mocker-test-record rec args)) + (mocker-run-record rec args)) + (t + (mocker-fail-record rec args)))) + (apply (oref mock :orig-def) args))) + +(defmethod mocker-find-active-record ((mock mocker-mock) args) + (let ((first-match (lambda (pred seq) + (let ((x nil)) + (while (and seq + (not (setq x (funcall pred (pop seq)))))) + x)))) + (let* ((ordered (oref mock :ordered)) + rec) + (if ordered + (setq rec (funcall + first-match + #'(lambda (r) + (when (oref r :-active) + (if (mocker-test-record r args) + (progn + (mocker-use-record r) + r) + (mocker-skip-record r args)))) + (oref mock :records))) + (setq rec (funcall + first-match + #'(lambda (r) + (and + (oref r :-active) + (mocker-test-record r args) + (progn + (mocker-use-record r) + r))) + (oref mock :records)))) + rec))) + +(defmethod mocker-verify ((mock mocker-mock)) + (mapc #'(lambda (r) (when (and (oref r :-active) + (< (oref r :-occurrences) + (oref r :min-occur))) + (signal 'mocker-record-error + (list (format + (concat "Expected call to mock `%s'," + " with input like %s," + " was not run.") + (oref mock :function) + (mocker-get-record-expectations r)))))) + (oref mock :records))) + +;;; Mock record base object +(defclass mocker-record-base () + ((min-occur :initarg :min-occur :initform 1 :type number) + (max-occur :initarg :max-occur :initform nil :type (or null number)) + (-occur :initarg :occur :initform nil :type (or null number)) + (-occurrences :initarg :-occurrences :initform 0 :type number + :protection :protected) + (-mock :initarg :-mock) + (-active :initarg :-active :initform t :protection :protected) + (-sym :initarg :-sym))) + +(defmethod constructor :static ((rec mocker-record-base) newname &rest args) + (let* ((obj (call-next-method)) + (occur (oref obj :occur))) + (when occur + (oset obj :min-occur (max (oref obj :min-occur) + occur)) + (oset obj :max-occur (if (oref obj :max-occur) + (min (oref obj :max-occur) occur) + occur))) + obj)) + +(defmethod mocker-read-record :static ((rec mocker-record-base) spec) + spec) + +(defmethod mocker-use-record ((rec mocker-record-base)) + (let ((max (oref rec :max-occur)) + (n (1+ (oref rec :-occurrences)))) + (oset rec :-occurrences n) + (when (and (not (null max)) + (= n max)) + (oset rec :-active nil)))) + +(defmethod mocker-skip-record ((rec mocker-record-base) args) + (if (>= (oref rec :-occurrences) + (oref rec :min-occur)) + (oset rec :-active nil) + (mocker-fail-record rec args))) + +(defmethod mocker-test-record ((rec mocker-record-base) args) + (error "not implemented in base class")) + +(defmethod mocker-run-record ((rec mocker-record-base) args) + (error "not implemented in base class")) + +(defmethod mocker-get-record-expectations ((rec mocker-record-base))) + +(defmethod mocker-fail-record ((rec mocker-record-base) args) + (signal 'mocker-record-error + (list (format (concat "Violated record while mocking `%s'." + " Expected input like: %s, got: `%s' instead") + (oref (oref rec :-mock) :function) + (mocker-get-record-expectations rec) + args)))) + +;;; Mock input recognizer +(defclass mocker-input-record (mocker-record-base) + ((input :initarg :input :initform nil :type list) + (input-matcher :initarg :input-matcher :initform nil))) + +(defmethod constructor :static ((rec mocker-input-record) newname &rest args) + (let* ((obj (call-next-method))) + (when (or (not (slot-boundp obj :max-occur)) + (and (oref obj :max-occur) + (< (oref obj :max-occur) + (oref obj :min-occur)))) + (oset obj :max-occur (oref obj :min-occur))) + obj)) + +(defmethod mocker-test-record ((rec mocker-input-record) args) + (let ((matcher (oref rec :input-matcher)) + (input (oref rec :input))) + (cond (matcher + (apply matcher args)) + (t + (equal input args))))) + +(defmethod mocker-get-record-expectations ((rec mocker-input-record)) + (format "`%s'" (or (oref rec :input-matcher) (oref rec :input)))) + +;;; Mock record default object +(defclass mocker-record (mocker-input-record) + ((output :initarg :output :initform nil) + (output-generator :initarg :output-generator :initform nil))) + +(defmethod mocker-run-record ((rec mocker-record) args) + (let ((generator (oref rec :output-generator)) + (output (oref rec :output))) + (cond (generator + (apply generator args)) + (t + output)))) + +;;; Mock simple stub object +(defclass mocker-stub-record (mocker-record-base) + ((output :initarg :output :initform nil))) + +(defmethod constructor :static ((rec mocker-stub-record) newname &rest args) + (let* ((obj (call-next-method))) + (unless (slot-boundp obj :min-occur) + (oset obj :min-occur 0)) + (unless (slot-boundp obj :max-occur) + (oset obj :max-occur nil)) + obj)) + +(defmethod mocker-test-record ((rec mocker-stub-record) args) + t) + +(defmethod mocker-run-record ((rec mocker-stub-record) args) + (oref rec :output)) + +(defmethod mocker-get-record-expectations ((rec mocker-stub-record)) + "anything") + +;;; Mock passthrough record +(defclass mocker-passthrough-record (mocker-input-record) + ()) + +(defmethod mocker-run-record ((rec mocker-passthrough-record) args) + (let* ((mock (oref rec :-mock)) + (def (oref mock :orig-def))) + (when def + (apply def args)))) + +;;; Helpers +(defun mocker-gen-mocks (mockspecs) + "helper to generate mocks from the input of `mocker-let'" + (mapcar #'(lambda (m) + (let* ((func (car m)) + (argspec (cadr m)) + (rest (cddr m)) + (sym (make-symbol (concat (symbol-name func) "--mock")))) + (list sym + (apply 'make-instance 'mocker-mock + :function func + :argspec argspec + (let* ((order (if (plist-member rest :ordered) + (prog1 + (plist-get rest :ordered) + (setq rest + (mocker--plist-remove + rest :ordered))) + (oref-default 'mocker-mock + :ordered)))) + (list :ordered order))) + (if (plist-member rest :records) + (plist-get rest :records) + (car rest))))) + mockspecs)) + +;;;###autoload +(defmacro mocker-let (mockspecs &rest body) + "Generate temporary bindings according to MOCKSPECS then eval +BODY. The value of the last form in BODY is returned. +Each element of MOCKSPECS is a list (FUNC ARGS [OPTIONS] +RECORDS). + +FUNC is the name of the function to bind, whose original + definition must accept arguments compatible with ARGS. +OPTIONS can be :ordered nil if the records can be executed out of +order (by default, order is enforced). +RECORDS is a list ([:record-cls CLASS] ARG1 ARG2...). + +Each element of RECORDS will generate a record for the +corresponding mock. By default, records are objects of the +`mocker-record' class, but CLASS is used instead if specified. +The rest of the arguments are used to construct the record +object. They will be passed to method `mocker-read-record' for +the used CLASS. This method must return a valid list of +parameters for the CLASS constructor. This allows to implement +specialized mini-languages for specific record classes. +" + (declare (indent 1) (debug t)) + (let* ((mocks (mocker-gen-mocks mockspecs)) + (vars (mapcar #'(lambda (m) + `(,(car m) ,(cadr m))) + mocks)) + (specs (mapcar + #'(lambda (m) + (let* ((mock-sym (car m)) + (mock (cadr m)) + (func (oref mock :function)) + (spec (oref mock :argspec)) + (call (or (and (member '&rest spec) 'apply) + 'funcall)) + (args (loop for el in spec + if (or (not (symbolp el)) + (not (equal + (elt (symbol-name el) 0) + ?&))) + collect el))) + (list func + spec + `(,call #'mocker-run ,mock-sym ,@args)))) + mocks)) + (inits (mapcar #'(lambda (m) + (cons 'progn + (mapcar #'(lambda (rec) + `(mocker-add-record ,(car m) + ,@rec)) + (nth 2 m)))) + mocks)) + (verifs (mapcar #'(lambda (m) + `(mocker-verify ,(car m))) + mocks))) + `(let (,@vars) + ,@inits + (prog1 + ,(macroexpand `(mocker-flet (,@specs) + ,@body)) + ,@verifs)))) + +(provide 'mocker) +;;; mocker.el ends here diff --git a/test/mocker-test.el b/test/mocker-test.el new file mode 100644 index 0000000..ffc18e4 --- /dev/null +++ b/test/mocker-test.el @@ -0,0 +1,279 @@ +;;; mocker-tests.el --- tests for mocker.el + +;; Copyright (C) 2011 Free Software Foundation, Inc. + +;; Author: Yann Hodique +;; Keywords: lisp, testing + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; This file contains tests for mocker.el + +;;; Code: + +(eval-when-compile + (require 'cl)) + +(require 'mocker) + +(eval-and-compile + (when (null (ignore-errors (require 'ert))) + (defmacro* ert-deftest (name () &body docstring-keys-and-body) + (message "Skipping tests, ERT is not available")))) + +(ert-deftest mocker-let-basic () + (should + (eq t (mocker-let () t)))) + +(ert-deftest mocker-let-single () + (should + (eq t + (mocker-let ((foo () :records ((:output t)))) + (foo))))) + +(ert-deftest mocker-let-single-implicit-records () + (should + (eq t + (mocker-let ((foo () + ((:output t)))) + (foo))))) + +(ert-deftest mocker-let-no-record () + (should-error + (mocker-let ((foo () :records ())) + (foo)) + :type 'mocker-mock-error)) + +(ert-deftest mocker-let-multiple () + (should + (eq 42 + (mocker-let ((foo () :records ((:output 6))) + (bar () :records ((:output 7)))) + (* (foo) (bar)))))) + +(ert-deftest mocker-let-nested () + (should + (eq 42 + (mocker-let ((foo () :records ((:output 6)))) + (mocker-let ((bar () :records ((:output 7)))) + (* (foo) (bar))))))) + +(ert-deftest mocker-let-multiple-inputs () + (should + (eq 42 + (mocker-let ((foo (x) :records ((:input '(1) :output 6))) + (bar (x) :records ((:input '(2) :output 7)))) + (* (foo 1) (bar 2)))))) + +(ert-deftest mocker-let-multiple-inputs-invalid () + (should-error + (mocker-let ((foo (x) :records ((:input '(1) :output 6))) + (bar (x) :records ((:input '(2) :output 7)))) + (* (foo 2) (bar 2))) + :type 'mocker-record-error)) + +(ert-deftest mocker-let-single-input-matcher () + (should + (eq t + (mocker-let ((foo (x) + :records ((:input-matcher 'integerp :output t)))) + (foo 4))))) + +(ert-deftest mocker-let-single-input-matcher-invalid () + (should-error + (mocker-let ((foo (x) + :records ((:input-matcher 'integerp :output t)))) + (foo t)) + :type 'mocker-record-error)) + +(ert-deftest mocker-let-multiple-output-generator () + (should + (eq 2 + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity)) + (:input '(4) + :output-generator (lambda (x) 0))))) + (+ (foo 2) (foo 4)))))) + +(ert-deftest mocker-let-multiple-calls-min () + (should + (eq 4 + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity) + :min-occur 2)))) + (+ (foo 2) (foo 2)))))) + +(ert-deftest mocker-let-multiple-calls-illimited () + (should + (eq 8 + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity) + :max-occur nil)))) + (+ (foo 2) (foo 2) (foo 2) (foo 2)))))) + +(ert-deftest mocker-let-multiple-calls-exact () + (should + (eq 8 + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity) + :occur 4)))) + (+ (foo 2) (foo 2) (foo 2) (foo 2)))))) + +(ert-deftest mocker-let-multiple-calls-multiple-records () + (should + (eq 12 + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity) + :max-occur 2) + (:input '(2) + :output-generator (lambda (x) (* 2 x)) + :occur 2)))) + (+ (foo 2) (foo 2) (foo 2) (foo 2)))))) + +(ert-deftest mocker-let-multiple-calls-multiple-same-records () + (should + (eq 8 + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity) + :max-occur 2) + (:input '(2) + :output-generator (function identity) + :max-occur 2)))) + (+ (foo 2) (foo 2) (foo 2) (foo 2)))))) + +(ert-deftest mocker-let-multiple-calls-unexpected () + (should-error + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity) + :max-occur 2)))) + (+ (foo 2) (foo 2) (foo 2) (foo 2)))) + :type 'mocker-record-error) + +(ert-deftest mocker-let-multiple-calls-unexpected-exact () + (should-error + (mocker-let ((foo (x) + :records ((:input '(2) + :output-generator (function identity) + :occur 2)))) + (+ (foo 2) (foo 2) (foo 2) (foo 2)))) + :type 'mocker-record-error) + +(ert-deftest mocker-let-stub-simple () + (should + (let ((mocker-mock-default-record-cls 'mocker-stub-record)) + (eq t + (mocker-let ((foo (x) :records ((:output t)))) + (and (foo 1) (foo 42) (foo 666))))))) + +(ert-deftest mocker-let-stub-simple-explicit () + (should + (eq t + (mocker-let ((foo (x) :records ((:record-cls mocker-stub-record + :output t)))) + (and (foo 1) (foo 42) (foo 666)))))) + +(ert-deftest mocker-let-stub-limited () + (should-error + (let ((mocker-mock-default-record-cls 'mocker-stub-record)) + (mocker-let ((foo (x) :records ((:output t :max-occur 2)))) + (and (foo 1) (foo 42) (foo 666)))) + :type 'mocker-mock-error)) + +(ert-deftest mocker-let-multiple-calls-unordered () + (should + (eq 18 + (mocker-let ((foo (x y z) + :ordered nil + ((:input '(1 2 3) :output 4) + (:input '(4 5 6) :output 10))) + (bar (x) + ((:input '(42) :output 4)))) + (+ (foo 4 5 6) + (foo 1 2 3) + (bar 42)))))) + +(ert-deftest mocker-passthrough-basic () + (should + (not + (mocker-let ((ignore (x) + :records ((:record-cls mocker-passthrough-record + :input '(42))))) + (ignore 42))))) + +(ert-deftest mocker-passthrough-mixed () + (should + (mocker-let ((ignore (x) + :records ((:record-cls mocker-passthrough-record + :input '(42)) + (:input '(58) :output t)))) + (or (ignore 42) + (ignore 58))))) + +(ert-deftest mocker-passthrough-mixed-error () + (should-error + (mocker-let ((ignore (x) + :records ((:record-cls mocker-passthrough-record + :input '(42)) + (:input '(58) :output t)))) + (or (ignore 42) + (ignore 42))) + :type 'mocker-record-error)) + +(ert-deftest mocker-passthrough-multiple () + (should + (mocker-let ((ignore (x) + ((:input-matcher (lambda (x) t) + :output t :max-occur 2) + (:record-cls mocker-passthrough-record + :input '(42) :max-occur nil)))) + (and (ignore 1) + (ignore 2) + (not (or + (ignore 42) (ignore 42) (ignore 42) (ignore 42))))))) + +(ert-deftest mocker-inhibit-mock-not-consumed () + (should-error + (let ((mocker-inhibit t)) + (mocker-let ((ignore (x) + ((:input '(42) :output t)))) + (ignore 42))) + :type 'mocker-record-error)) + +(ert-deftest mocker-inhibit-mocking () + (should + (not + (mocker-let ((ignore (x) + ((:input '(42) :output t)))) + (and (ignore 42) + (let ((mocker-inhibit t)) + (ignore 42))))))) + +(ert-deftest mocker-rest-args () + (should + (mocker-let ((f (a b &rest args) ((:input '(1 2 3 4) :output t)))) + (f 1 2 3 4)))) + +(provide 'mocker-tests) +;;; mocker-tests.el ends here