Mercurial > lasercutter
view src/clojure/contrib/mock.clj.rej @ 10:ef7dbbd6452c
added clojure source goodness
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 21 Aug 2010 06:25:44 -0400 |
parents | |
children |
line wrap: on
line source
1 diff a/src/main/clojure/clojure/contrib/mock.clj b/src/main/clojure/clojure/contrib/mock.clj (rejected hunks)2 @@ -1,285 +1,282 @@3 -;;; clojure.contrib.mock.clj: mocking/expectation framework for Clojure4 -5 -;; by Matt Clark6 -7 -;; Copyright (c) Matt Clark, 2009. All rights reserved. The use8 -;; and distribution terms for this software are covered by the Eclipse9 -;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php).10 -;; By using this software in any fashion, you are11 -;; agreeing to be bound by the terms of this license. You must not12 -;; remove this notice, or any other, from this software.13 -;;------------------------------------------------------------------------------14 -15 -(comment16 - ;; This is a simple function mocking library I accidentally wrote as a side17 - ;; effect of trying to write an opengl library in clojure. This is loosely18 - ;; based on various ruby and java mocking frameworks I have used in the past19 - ;; such as mockito, easymock, and whatever rspec uses.20 - ;;21 - ;; expect uses bindings to wrap the functions that are being tested and22 - ;; then validates the invocation count at the end. The expect macro is the23 - ;; main entry point and it is given a vector of binding pairs.24 - ;; The first of each pair names the dependent function you want to override,25 - ;; while the second is a hashmap containing the mock description, usually26 - ;; created via the simple helper methods described below.27 - ;;28 - ;; Usage:29 - ;;30 - ;; there are one or more dependent functions:31 -32 - (defn dep-fn1 [] "time consuming calculation in 3rd party library")33 - (defn dep-fn2 [x] "function with undesirable side effects while testing")34 -35 - ;; then we have the code under test that calls these other functions:36 -37 - (defn my-code-under-test [] (dep-fn1) (dep-fn2 "a") (+ 2 2))38 -39 - ;; to test this code, we simply surround it with an expect macro within40 - ;; the test:41 -42 - (expect [dep-fn1 (times 1)43 - dep-fn2 (times 1 (has-args [#(= "a" %)]))]44 - (my-code-under-test))45 -46 - ;; When an expectation fails during execution of the function under test,47 - ;; an error condition function is called with the name of the function48 - ;; being mocked, the expected form and the actual value. These49 - ;; error functions can be overridden to allow easy integration into50 - ;; test frameworks such as test-is by reporting errors in the function51 - ;; overrides.52 -53 - ) ;; end comment54 -55 -(ns clojure.contrib.mock56 - ^{:author "Matt Clark",57 - :doc "function mocking/expectations for Clojure" }58 - (:use [clojure.contrib.seq :only (positions)]59 - [clojure.contrib.def :only (defmacro-)]))60 -61 -62 -;;------------------------------------------------------------------------------63 -;; These are the error condition functions. Override them to integrate into64 -;; the test framework of your choice, or to simply customize error handling.65 -66 -(defn report-problem67 - {:dynamic true}68 - ([function expected actual]69 - (report-problem function expected actual "Expectation not met."))70 - ([function expected actual message]71 - (prn (str message " Function name: " function72 - " expected: " expected " actual: " actual))))73 -74 -(defn no-matching-function-signature75 - {:dynamic true}76 - [function expected actual]77 - (report-problem function expected actual78 - "No matching real function signature for given argument count."))79 -80 -(defn unexpected-args81 - {:dynamic true}82 - [function expected actual i]83 - (report-problem function expected actual84 - (str "Argument " i " has an unexpected value for function.")))85 -86 -(defn incorrect-invocation-count87 - {:dynamic true}88 - [function expected actual]89 - (report-problem function expected actual "Unexpected invocation count."))90 -91 -92 -;;------------------------------------------------------------------------------93 -;; Internal Functions - ignore these94 -95 -96 -(defn- has-arg-count-match?97 - "Given the sequence of accepted argument vectors for a function,98 -returns true if at least one matches the given-count value."99 - [arg-lists given-count]100 - (some #(let [[ind] (positions #{'&} %)]101 - (if ind102 - (>= given-count ind)103 - (= (count %) given-count)))104 - arg-lists))105 -106 -107 -(defn has-matching-signature?108 - "Calls no-matching-function-signature if no match is found for the given109 -function. If no argslist meta data is available for the function, it is110 -not called."111 - [fn-name args]112 - (let [arg-count (count args)113 - arg-lists (:arglists (meta (resolve fn-name)))]114 - (if (and arg-lists (not (has-arg-count-match? arg-lists arg-count)))115 - (no-matching-function-signature fn-name arg-lists args))))116 -117 -118 -(defn make-arg-checker119 - "Creates the argument verifying function for a replaced dependency within120 -the expectation bound scope. These functions take the additional argument121 -of the name of the replaced function, then the rest of their args. It is122 -designed to be called from the mock function generated in the first argument123 -of the mock info object created by make-mock."124 - [arg-preds arg-pred-forms]125 - (let [sanitized-preds (map (fn [v] (if (fn? v) v #(= v %))) arg-preds)]126 - (fn [fn-name & args]127 - (every? true?128 - (map (fn [pred arg pred-form i] (if (pred arg) true129 - (unexpected-args fn-name pred-form arg i)))130 - sanitized-preds args arg-pred-forms (iterate inc 0))))))131 -132 -133 -(defn make-count-checker134 - "creates the count checker that is invoked at the end of an expectation, after135 -the code under test has all been executed. The function returned takes the136 -name of the associated dependency and the invocation count as arguments."137 - [pred pred-form]138 - (let [pred-fn (if (integer? pred) #(= pred %) pred)]139 - (fn [fn-name v] (if (pred-fn v) true140 - (incorrect-invocation-count fn-name pred-form v)))))141 -142 -; Borrowed from clojure core. Remove if this ever becomes public there.143 -(defmacro- assert-args144 - [fnname & pairs]145 - `(do (when-not ~(first pairs)146 - (throw (IllegalArgumentException.147 - ~(str fnname " requires " (second pairs)))))148 - ~(let [more (nnext pairs)]149 - (when more150 - (list* `assert-args fnname more)))))151 -152 -(defn make-mock153 - "creates a vector containing the following information for the named function:154 -1. dependent function replacement - verifies signature, calls arg checker,155 -increases count, returns return value.156 -2. an atom containing the invocation count157 -3. the invocation count checker function158 -4. a symbol of the name of the function being replaced."159 - [fn-name expectation-hash]160 - (assert-args make-mock161 - (map? expectation-hash) "a map of expectations")162 - (let [arg-checker (or (expectation-hash :has-args) (fn [& args] true))163 - count-atom (atom 0)164 - ret-fn (or165 - (expectation-hash :calls)166 - (fn [& args] (expectation-hash :returns)))]167 - [(fn [& args]168 - (has-matching-signature? fn-name args)169 - (apply arg-checker fn-name args)170 - (swap! count-atom inc)171 - (apply ret-fn args))172 - count-atom173 - (or (expectation-hash :times) (fn [fn-name v] true))174 - fn-name]))175 -176 -177 -(defn validate-counts178 - "given the sequence of all mock data for the expectation, simply calls the179 -count checker for each dependency."180 - [mock-data] (doseq [[mfn i checker fn-name] mock-data] (checker fn-name @i)))181 -182 -(defn ^{:private true} make-bindings [expect-bindings mock-data-sym]183 - `[~@(interleave (map #(first %) (partition 2 expect-bindings))184 - (map (fn [i] `(nth (nth ~mock-data-sym ~i) 0))185 - (range (quot (count expect-bindings) 2))))])186 -187 -188 -;;------------------------------------------------------------------------------189 -;; These are convenience functions to improve the readability and use of this190 -;; library. Useful in expressions such as:191 -;; (expect [dep-fn1 (times (more-than 1) (returns 15)) etc)192 -193 -(defn once [x] (= 1 x))194 -195 -(defn never [x] (zero? x))196 -197 -(defn more-than [x] #(< x %))198 -199 -(defn less-than [x] #(> x %))200 -201 -(defn between [x y] #(and (< x %) (> y %)))202 -203 -204 -;;------------------------------------------------------------------------------205 -;; The following functions can be used to build up the expectation hash.206 -207 -(defn returns208 - "Creates or associates to an existing expectation hash the :returns key with209 -a value to be returned by the expectation after a successful invocation210 -matching its expected arguments (if applicable).211 -Usage:212 -(returns ret-value expectation-hash?)"213 -214 - ([val] (returns val {}))215 - ([val expectation-hash] (assoc expectation-hash :returns val)))216 -217 -218 -(defn calls219 - "Creates or associates to an existing expectation hash the :calls key with a220 -function that will be called with the given arguments. The return value from221 -this function will be returned returned by the expected function. If both this222 -and returns are specified, the return value of \"calls\" will have precedence.223 -Usage:224 -(calls some-fn expectation-hash?)"225 -226 - ([val] (calls val {}))227 - ([val expectation-hash] (assoc expectation-hash :calls val)))228 -229 -230 -(defmacro has-args231 - "Creates or associates to an existing expectation hash the :has-args key with232 -a value corresponding to a function that will either return true if its233 -argument expectations are met or throw an exception with the details of the234 -first failed argument it encounters.235 -Only specify as many predicates as you are interested in verifying. The rest236 -of the values are safely ignored.237 -Usage:238 -(has-args [arg-pred-1 arg-pred-2 ... arg-pred-n] expectation-hash?)"239 -240 - ([arg-pred-forms] `(has-args ~arg-pred-forms {}))241 - ([arg-pred-forms expect-hash-form]242 - (assert-args has-args243 - (vector? arg-pred-forms) "a vector of argument predicates")244 - `(assoc ~expect-hash-form :has-args245 - (make-arg-checker ~arg-pred-forms '~arg-pred-forms))))246 -247 -248 -(defmacro times249 - "Creates or associates to an existing expectation hash the :times key with a250 -value corresponding to a predicate function which expects an integer value.251 -This function can either be specified as the first argument to times or can be252 -the result of calling times with an integer argument, in which case the253 -predicate will default to being an exact match. This predicate is called at254 -the end of an expect expression to validate that an expected dependency255 -function was called the expected number of times.256 -Usage:257 -(times n)258 -(times #(> n %))259 -(times n expectation-hash)"260 - ([times-fn] `(times ~times-fn {}))261 - ([times-fn expectation-hash]262 - `(assoc ~expectation-hash :times (make-count-checker ~times-fn '~times-fn))))263 -264 -265 -;-------------------------------------------------------------------------------266 -; The main expect macro.267 -(defmacro expect268 - "Use expect to redirect calls to dependent functions that are made within the269 -code under test. Instead of calling the functions that would normally be used,270 -temporary stubs are used, which can verify function parameters and call counts.271 -Return values can also be specified as needed.272 -Usage:273 -(expect [dep-fn (has-args [arg-pred1] (times n (returns x)))]274 - (function-under-test a b c))"275 -276 - [expect-bindings & body]277 - (assert-args expect278 - (vector? expect-bindings) "a vector of expectation bindings"279 - (even? (count expect-bindings))280 - "an even number of forms in expectation bindings")281 - (let [mock-data (gensym "mock-data_")]282 - `(let [~mock-data (map (fn [args#]283 - (apply clojure.contrib.mock/make-mock args#))284 - ~(cons 'list (map (fn [[n m]] (vector (list 'quote n) m))285 - (partition 2 expect-bindings))))]286 - (binding ~(make-bindings expect-bindings mock-data) ~@body)287 - (clojure.contrib.mock/validate-counts ~mock-data) true)))288 +;;; clojure.contrib.mock.clj: mocking/expectation framework for Clojure289 +290 +;; by Matt Clark291 +292 +;; Copyright (c) Matt Clark, 2009. All rights reserved. The use and293 +;; distribution terms for this software are covered by the Eclipse Public294 +;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can295 +;; be found in the file epl-v10.html at the root of this distribution. By296 +;; using this software in any fashion, you are agreeing to be bound by the297 +;; terms of this license. You must not remove this notice, or any other298 +;; from this software.299 +;;------------------------------------------------------------------------------300 +301 +(comment302 + ;; Mock is a function mocking utility loosely based on various ruby and java303 + ;; mocking frameworks such as mockito, easymock, and rspec yet adapted to304 + ;; fit the functional style of clojure.305 + ;;306 + ;; Mock uses bindings to wrap the functions that are being tested and307 + ;; then validates the invocation count at the end. The expect macro is the308 + ;; main entry point and it is given a vector of binding pairs.309 + ;; The first of each pair names the dependent function you want to override310 + ;; while the second is a hashmap containing the mock description, usually311 + ;; created via the simple helper methods described below.312 + ;;313 + ;; Usage:314 + ;;315 + ;; there are one or more dependent functions:316 +317 + (defn dep-fn1 [] "time consuming calculation in 3rd party library")318 + (defn dep-fn2 [x] "function with undesirable side effects while testing")319 +320 + ;; then we have the code under test that calls these other functions:321 +322 + (defn my-code-under-test [] (dep-fn1) (dep-fn2 "a") (+ 2 2))323 +324 + ;; to test this code, we simply surround it with an expect macro within325 + ;; the test:326 +327 + (expect [dep-fn1 (times 1)328 + dep-fn2 (times 1 (has-args [#(= "a" %)]))]329 + (my-code-under-test))330 +331 + ;; When an expectation fails during execution of the function under test332 + ;; an error condition function is called with the name of the function333 + ;; being mocked, the expected form and the actual value. These334 + ;; error functions can be overridden to allow easy integration into335 + ;; test frameworks such as test-is by reporting errors in the function336 + ;; overrides.337 +338 + ) ;; end comment339 +340 +(ns clojure.contrib.mock341 + ^{:author "Matt Clark"342 + :doc "function mocking/expectations for Clojure" }343 + (:use [clojure.contrib.seq :only (positions)]344 + [clojure.contrib.def :only (defmacro-)]))345 +346 +347 +;;------------------------------------------------------------------------------348 +;; These are the error condition functions. Override them to integrate into349 +;; the test framework of your choice, or to simply customize error handling.350 +351 +(defn report-problem352 + {:dynamic true}353 + ([function expected actual]354 + (report-problem function expected actual "Expectation not met."))355 + ([function expected actual message]356 + (prn (str message " Function name: " function357 + " expected: " expected " actual: " actual))))358 +359 +(defn no-matching-function-signature360 + {:dynamic true}361 + [function expected actual]362 + (report-problem function expected actual363 + "No matching real function signature for given argument count."))364 +365 +(defn unexpected-args366 + {:dynamic true}367 + [function expected actual i]368 + (report-problem function expected actual369 + (str "Argument " i " has an unexpected value for function.")))370 +371 +(defn incorrect-invocation-count372 + {:dynamic true}373 + [function expected actual]374 + (report-problem function expected actual "Unexpected invocation count."))375 +376 +377 +;;------------------------------------------------------------------------------378 +;; Internal Functions - ignore these379 +380 +381 +(defn- has-arg-count-match?382 + "Given the sequence of accepted argument vectors for a function383 +returns true if at least one matches the given-count value."384 + [arg-lists given-count]385 + (some #(let [[ind] (positions #{'&} %)]386 + (if ind387 + (>= given-count ind)388 + (= (count %) given-count)))389 + arg-lists))390 +391 +392 +(defn has-matching-signature?393 + "Calls no-matching-function-signature if no match is found for the given394 +function. If no argslist meta data is available for the function, it is395 +not called."396 + [fn-name args]397 + (let [arg-count (count args)398 + arg-lists (:arglists (meta (resolve fn-name)))]399 + (if (and arg-lists (not (has-arg-count-match? arg-lists arg-count)))400 + (no-matching-function-signature fn-name arg-lists args))))401 +402 +403 +(defn make-arg-checker404 + "Creates the argument verifying function for a replaced dependency within405 +the expectation bound scope. These functions take the additional argument406 +of the name of the replaced function, then the rest of their args. It is407 +designed to be called from the mock function generated in the first argument408 +of the mock info object created by make-mock."409 + [arg-preds arg-pred-forms]410 + (let [sanitized-preds (map (fn [v] (if (fn? v) v #(= v %))) arg-preds)]411 + (fn [fn-name & args]412 + (every? true?413 + (map (fn [pred arg pred-form i] (if (pred arg) true414 + (unexpected-args fn-name415 + pred-form arg i)))416 + sanitized-preds args arg-pred-forms (iterate inc 0))))))417 +418 +419 +(defn make-count-checker420 + "creates the count checker that is invoked at the end of an expectation, after421 +the code under test has all been executed. The function returned takes the422 +name of the associated dependency and the invocation count as arguments."423 + [pred pred-form]424 + (let [pred-fn (if (integer? pred) #(= pred %) pred)]425 + (fn [fn-name v] (if (pred-fn v) true426 + (incorrect-invocation-count fn-name pred-form v)))))427 +428 +(defn make-mock429 + "creates a vector containing the following information for the named function:430 +1. dependent function replacement - verifies signature, calls arg checker431 +increases count, returns return value.432 +2. an atom containing the invocation count433 +3. the invocation count checker function434 +4. a symbol of the name of the function being replaced."435 + [fn-name expectation-hash]436 + {:pre [(map? expectation-hash)437 + (symbol? fn-name)]}438 + (let [arg-checker (or (expectation-hash :has-args) (fn [& args] true))439 + count-atom (atom 0)440 + ret-fn (or441 + (expectation-hash :calls)442 + (fn [& args] (expectation-hash :returns)))]443 + [(fn [& args]444 + (has-matching-signature? fn-name args)445 + (apply arg-checker fn-name args)446 + (swap! count-atom inc)447 + (apply ret-fn args))448 + count-atom449 + (or (expectation-hash :times) (fn [fn-name v] true))450 + fn-name]))451 +452 +453 +(defn validate-counts454 + "given the sequence of all mock data for the expectation, simply calls the455 +count checker for each dependency."456 + [mock-data] (doseq [[mfn i checker fn-name] mock-data] (checker fn-name @i)))457 +458 +(defn- make-bindings [expect-bindings mock-data-sym]459 + `[~@(interleave (map #(first %) (partition 2 expect-bindings))460 + (map (fn [i] `(nth (nth ~mock-data-sym ~i) 0))461 + (range (quot (count expect-bindings) 2))))])462 +463 +464 +;;------------------------------------------------------------------------------465 +;; These are convenience functions to improve the readability and use of this466 +;; library. Useful in expressions such as:467 +;; (expect [dep-fn1 (times (more-than 1) (returns 15)) etc)468 +469 +;; best used in the times function470 +(defn once [x] (= 1 x))471 +472 +(defn never [x] (zero? x))473 +474 +(defn more-than [x] #(< x %))475 +476 +(defn less-than [x] #(> x %))477 +478 +(defn between [x y] #(and (< x %) (> y %)))479 +480 +;;best used in the has-args function481 +(defn anything [x] true)482 +483 +484 +;;------------------------------------------------------------------------------485 +;; The following functions can be used to build up the expectation hash.486 +487 +(defn returns488 + "Creates or associates to an existing expectation hash the :returns key with489 +a value to be returned by the expectation after a successful invocation490 +matching its expected arguments (if applicable).491 +Usage:492 +(returns ret-value expectation-hash?)"493 +494 + ([val] (returns val {}))495 + ([val expectation-hash]496 + {:pre [(map? expectation-hash)]}497 + (assoc expectation-hash :returns val)))498 +499 +500 +(defn calls501 + "Creates or associates to an existing expectation hash the :calls key with a502 +function that will be called with the given arguments. The return value from503 +this function will be returned by the expected function. If both this504 +and returns are specified, the return value of \"calls\" will have precedence.505 +Usage:506 +(calls some-fn expectation-hash?)"507 +508 + ([val] (calls val {}))509 + ([val expectation-hash]510 + {:pre [(map? expectation-hash)]}511 + (assoc expectation-hash :calls val)))512 +513 +514 +(defmacro has-args515 + "Creates or associates to an existing expectation hash the :has-args key with516 +a value corresponding to a function that will either return true if its517 +argument expectations are met or throw an exception with the details of the518 +first failed argument it encounters.519 +Only specify as many predicates as you are interested in verifying. The rest520 +of the values are safely ignored.521 +Usage:522 +(has-args [arg-pred-1 arg-pred-2 ... arg-pred-n] expectation-hash?)"523 +524 + ([arg-pred-forms] `(has-args ~arg-pred-forms {}))525 + ([arg-pred-forms expectation-hash]526 + {:pre [(vector? arg-pred-forms)527 + (map? expectation-hash)]}528 + `(assoc ~expectation-hash :has-args529 + (make-arg-checker ~arg-pred-forms '~arg-pred-forms))))530 +531 +532 +(defmacro times533 + "Creates or associates to an existing expectation hash the :times key with a534 +value corresponding to a predicate function which expects an integer value.535 +Also, an integer can be specified, in which case the times will only be an536 +exact match. The times check is called at the end of an expect expression to537 +validate that an expected dependency function was called the expected538 +number of times.539 +Usage:540 +(times n)541 +(times #(> n %))542 +(times n expectation-hash)"543 + ([times-fn] `(times ~times-fn {}))544 + ([times-fn expectation-hash]545 + {:pre [(map? expectation-hash)]}546 + `(assoc ~expectation-hash :times (make-count-checker ~times-fn '~times-fn))))547 +548 +549 +;-------------------------------------------------------------------------------550 +; The main expect macro.551 +(defmacro expect552 + "Use expect to redirect calls to dependent functions that are made within the553 +code under test. Instead of calling the functions that would normally be used554 +temporary stubs are used, which can verify function parameters and call counts.555 +Return values of overridden functions can also be specified as needed.556 +Usage:557 +(expect [dep-fn (has-args [arg-pred1] (times n (returns x)))]558 + (function-under-test a b c))"559 +560 + [expect-bindings & body]561 + {:pre [(vector? expect-bindings)562 + (even? (count expect-bindings))]}563 + (let [mock-data (gensym "mock-data_")]564 + `(let [~mock-data (map (fn [args#]565 + (apply clojure.contrib.mock/make-mock args#))566 + ~(cons 'list (map (fn [[n m]] (vector (list 'quote n) m))567 + (partition 2 expect-bindings))))]568 + (binding ~(make-bindings expect-bindings mock-data) ~@body)569 + (clojure.contrib.mock/validate-counts ~mock-data) true)))