diff --git a/FORMULAS-BACKLOG-AND-FEATURE-IDEAS.md b/FORMULAS-BACKLOG-AND-FEATURE-IDEAS.md new file mode 100644 index 000000000..5baeee33d --- /dev/null +++ b/FORMULAS-BACKLOG-AND-FEATURE-IDEAS.md @@ -0,0 +1,53 @@ + +* [x] improve formula failure reporting + a. [x] report the first failure + b. [x] make sure the fact count only increases once per formula + +* [x] make number of generated facts per formula dynamically bindable + a. [x] throw an exception if this value is set < 1 + +* [x] figure out how to make syntax validation errors show something more sensible than the + error message you'd see for a problem with a fact validation problem + +* [x] don't run more tests than need be if there is already failure in this formula's batch. + +* [x] syntax validate thata formula only has one check in it + a. [x] need to make this more thorough... right now the only test of this feature checks a + simple provided case, but needs to work with against-background, background and + other more interesting cases + b. [ ] it is more thorough now, but let's make it *seriously* thoough :) + +* [x] formula macro calls a (constantly []) version of shrink on failures + +* [x] add future-formula (and variant names) + +* [x] cleaner syntax for overriding number of trials per formula. Use the + *num-trials* var just for global changes or changes to be visible + for groups of formulas. + +* [x] validate that opt-map is only used with valid keys. + +* [x] validate that :num-trials is 1+ + +* [ ] Work with Meikel Brandmeyer to combine ClojureCheck's Generators with Shrink. + implement shrinking. Report only the first fully shrunken failure + [ ] 'shrink' depends on domain of 'generate' + +* [ ] ability to override shrink function on a per generator basis + a. [ ] ablity to not shrink at all on a per generator basis (make a nice syntactic + sugar for this... as it is one of the cases of the above. + +* [ ] fix strange error if you run (formula [a 1] 1 =>) + ... since the formula macro splices in :formula :formula-in-progress + possibly solution is to not using fact macro inside of formula, + but instead do something like tabular + + +* [ ] if line numbers shift, then ensure that they always report correctly -- so far I + don't know if this even needs to change, since it seems to work fine. Think about + it and decide if tests to prevent regressions are useful here. + +* [ ] consider implementing with @marick's metaconstant syntax + a. [ ] if we do metaconstant style, implement generator overriding + +* [ ] figure out what part of t-formulas is registering as a lot more than 1 report per formula. (100???) \ No newline at end of file diff --git a/HISTORY.md b/HISTORY.md index e3533228a..3d78f770f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,28 +1,9 @@ -1.3.2-SNAPSHOT -------------- -* throws now accepts any extending Throwable. For example, this now passes: - (throw (NullPointerException.)) => (throws Exception) -* each item in the right hand side of =streams=> will evaluate lazily: - (provided - (volatile-fn) =streams=> [(throw (Exception.) (throw (Exception.)) :evaluates-succesfully] -* new function midje.sweet/expose-testables will allow you to write facts against - functions defined with the metadata of ^{:testable true} [or ^:testable, - or #^testable depending on which Clojure version you're running] -* Can now have prerequisites that throw Throwables using =throws=> - (provided (foo) =throws=> (Exception.)) -* Chatty checkers can destructure their single argument. -* tabular no longer requires variables names to begin with '?' -* throws checker has been updated: args are now any combination, in - any order of messages (or regexes), predicates, or 0 or 1 Throwable classes -* many more common syntax mistakes give helpful error messages -* prevented an infinite loop caused by ill-formed tabular facts -* fact doc-strings now show in report output - i. nested facts show nested doc-strings - ii. tabular's doc-string shows in a similar manner -* =stream=> prerequisites give helpful error messages when they run out of items to return -* removed reflection warnings +1.4.0 (forthcoming) +------------- +See https://github.com/marick/Midje/wiki/New-in-1.4 + 1.3.1 --------- * Fix mysterious type conversion error in some cases diff --git a/HOW-TO-CONFIGURATION-TEST b/HOW-TO-CONFIGURATION-TEST deleted file mode 100644 index bbde42251..000000000 --- a/HOW-TO-CONFIGURATION-TEST +++ /dev/null @@ -1,29 +0,0 @@ -The process is too manual. - -== Assumptions: -* The midje-version is something like 1.3.0-SNAPSHOT - -== To pick a new Clojure version for the main test suite: - -* rake fresh -* One of: - -bin/version 1.2.0 1.2.0 -bin/version 1.2.1 1.2.0 -bin/version 1.3.0 -bin/version 1.4.0-alphaN - -* Then: - -bin/run-tests - -[TODO: Make all this work with lein multi.] - -== To run a larger compatibility suite: - -* bin/update-project-files -* rake fresh -* rake upload -* bin/compatibility - - diff --git a/HOW-TO-RELEASE b/HOW-TO-RELEASE index 8b9babfd4..1a0992ef2 100644 --- a/HOW-TO-RELEASE +++ b/HOW-TO-RELEASE @@ -1,33 +1,22 @@ -The process for pushing a new version is too complicated and -manual. - - * Update HISTORY * Do the THINGS TO CHECK BEFORE DEPLOYING (below) -* Run configuration tests (HOW-TO-CONFIGURATION-TEST - with midje-version set to a non-snapshot value. - (Note that this uploads.) -* DO NOT CLEAN. -* Use bin/update-project-files and bin/version to set the examples - appropriately. For example: - bin/update-project-files 1.3.0 - bin/version 1.3.0 1.2.1 1.2.0 -* bin/gather-downloads +* rake fresh +* rake upload + * Update README.md (including version number) * Commit * git tag -a -m "message" v.x.x.x ;; git push --tags * Push to github -* Update example page, if needed. -* Try out downloads. +THINGS TO CHECK BEFORE DEPLOYING: +* `lein multi deps; lein multi midje` -THINGS TO CHECK BEFORE DEPLOYING: * uncomment the `:warn-on-reflection true` line in project.clj, and run `lein midje` to check for any reflection warning inadvertently added. (there will be warnings from libraries we use that we cannot remove) * Check for any public vars that shouldn't be visible to users. Hide them. (map str (vals (ns-publics 'midje.sweet))) - (pp) \ No newline at end of file + (pp) diff --git a/README.md b/README.md index e500a344b..5929c4431 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ Available via [clojars](http://clojars.org/search?q=midje) -Current stable version: [midje "1.3.1"] -Development version: [midje "1.3.2-SNAPSHOT"] +Current stable version: [midje "1.4.0"] [Changes from 1.3](https://github.com/marick/Midje/wiki/New-in-1.4) +Development version: [midje "1.5.0-SNAPSHOT"] -[User guide](https://github.com/marick/Midje/wiki) + +[User guide](https://github.com/marick/Midje/wiki) +[Tutorial](https://github.com/marick/Midje-quickstart/wiki) About Midje ======================= @@ -46,30 +48,15 @@ that are more useful for testing than `odd?` is. Learning Midje ======================= +There is a [tutorial introduction](https://github.com/marick/Midje-quickstart/wiki). Midje's features are described in the **[user guide](https://github.com/marick/Midje/wiki)**. If anything there is unclear, ask in the [mailing -list](http://groups.google.com/group/midje). +list](http://groups.google.com/group/midje) or in the #midje +channel on freenode. If you like videos of people programming, here's an [8-minute infomercial](http://www.youtube.com/watch?v=a7YtkcIiLGI) that concentrates on transitioning from clojure.test. -If -you like looking straight at code, [this heavily annotated -example](http://github.com/marick/Midje/blob/master/examples/basic/test/basic/core_test.clj) - has a bunch. If you want to run that example, you - can download it: - -1. [Click here](http://github.com/marick/Midje/raw/master/downloads/examples.zip). -2. In a shell, go to the directory your browser unpacked the example into. It should be named `examples`. -3. Go to `examples/basic/`. -4. Type "./run" or "lein test". -5. The facts in `test/*/core_test.clj` will be checked. - -You can [download -everything](http://github.com/marick/Midje/downloads) to get -more examples. - -Or in the #midje channel on freenode! Contributors ============== @@ -77,9 +64,11 @@ Contributors * Alex Baranosky * Phillip Calçado * Stuart Halloway +* Wilkes Joiner * Ben Mabey * Alan Malloy * Brian Marick * Bob Martin +* Dmitri Naumov * Sébastien RoccaSerra -* Wilkes Joiner +* Greg Spurrier diff --git a/Rakefile b/Rakefile index b5950dd7a..f9930a063 100644 --- a/Rakefile +++ b/Rakefile @@ -8,8 +8,8 @@ def jar_name unless /midje\s+"(\d+\.\d+\.\d+(-RC\d+)?)"/ =~ text || /midje\s+"(\d+\.\d+(\.\d+)*-SNAPSHOT)"/ =~ text || /midje\s+"(\d+\.\d-alpha\d)"/ =~ text || - /midje\s+"(\d+\.\d-beta\d)"/ =~ text - puts "Couldn't find version in project file." + /midje\s+"(\d+\.\d\.\d-beta\d)"/ =~ text + puts "Rake task error: couldn't find version in project file." exit 1 end jar = "midje-#{$1}.jar" diff --git a/TIDY b/TIDY deleted file mode 100644 index 70f2f1f10..000000000 --- a/TIDY +++ /dev/null @@ -1,42 +0,0 @@ -src/midje/checkers: -total 88 --rw-r--r-- 1 marick marick 2523 Jul 26 16:03 chatty.clj --rw-r--r-- 1 marick marick 19137 Jul 26 16:03 collection.clj --rw-r--r-- 1 marick marick 1861 Feb 26 17:50 defining.clj --rw-r--r-- 1 marick marick 1564 Jul 26 16:03 deprecated.clj --rw-r--r-- 1 marick marick 1185 Jul 26 16:03 extended_equality.clj --rw-r--r-- 1 marick marick 2037 Jul 26 16:03 simple.clj --rw-r--r-- 1 marick marick 824 Jul 26 16:03 util.clj - -src/midje/error_handling: -total 16 --rw-r--r-- 1 marick marick 1530 Jul 26 16:03 monadic.clj --rw-r--r-- 1 marick marick 787 Jul 26 16:03 semi_sweet_errors.clj - -src/midje/ideas: -total 80 --rw-r--r-- 1 marick marick 251 Jul 26 16:03 arrow_symbols.clj --rw-r--r-- 1 marick marick 1744 Jul 26 16:03 arrows.clj --rw-r--r-- 1 marick marick 4269 Jul 26 16:03 background.clj --rw-r--r-- 1 marick marick 4736 Jul 26 16:03 prerequisites.clj --rw-r--r-- 1 marick marick 2468 Jul 26 16:03 tabular.clj - -src/midje/internal_ideas: -total 40 --rw-r--r-- 1 marick marick 2022 Jul 26 16:03 expect.clj --rw-r--r-- 1 marick marick 3233 Jul 26 16:03 file_position.clj --rw-r--r-- 1 marick marick 1733 Jul 26 17:04 wrapping.clj - -src/midje/util: -total 96 --rw-r--r-- 1 marick marick 954 Feb 26 17:50 debugging.clj --rw-r--r-- 1 marick marick 1031 Jul 26 16:03 exceptions.clj --rw-r--r-- 1 marick marick 3485 Jul 26 16:03 form_utils.clj --rw-r--r-- 1 marick marick 807 Feb 26 17:50 laziness.clj --rw-r--r-- 1 marick marick 573 Jul 26 16:03 namespace.clj --rw-r--r-- 1 marick marick 4807 Jul 26 16:03 report.clj --rw-r--r-- 1 marick marick 213 Feb 26 17:50 strings.clj --rw-r--r-- 1 marick marick 2375 Jul 26 16:03 thread_safe_var_nesting.clj --rw-r--r-- 1 marick marick 204 Jul 26 16:03 treelike.clj --rw-r--r-- 1 marick marick 914 Jul 26 16:03 unify.clj --rw-r--r-- 1 marick marick 655 Jul 26 16:03 zip.clj diff --git a/bin/compatibility b/bin/compatibility deleted file mode 100755 index 8c3b94886..000000000 --- a/bin/compatibility +++ /dev/null @@ -1,20 +0,0 @@ -MIDJE_VERSION=$1 -if [ "" = "$MIDJE_VERSION" ]; then - echo "No midje version given." - exit 1 -fi - -set -e -set -x - -bin/version $MIDJE_VERSION 1.2.0 1.2.0 -bin/run-tests - -bin/version $MIDJE_VERSION 1.2.1 1.2.0 -bin/run-tests - -bin/version $MIDJE_VERSION 1.3.0 -bin/run-tests - -bin/version $MIDJE_VERSION 1.4.0-alpha3 -bin/run-tests diff --git a/bin/gather-downloads b/bin/gather-downloads deleted file mode 100755 index a416e8dd2..000000000 --- a/bin/gather-downloads +++ /dev/null @@ -1,2 +0,0 @@ -rm downloads/examples.zip -zip -r downloads/examples.zip examples/basic diff --git a/bin/run-tests b/bin/run-tests deleted file mode 100755 index 9a11434ef..000000000 --- a/bin/run-tests +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env ruby - -require 'fileutils' -include FileUtils - -ENV["MIDJE_COLORIZE"]="false" - -### IMPORTANT -### If you add any new test directories here, -### you must also add them to bin/version. - -lein_test_runs = %w{examples/semi-sweet-examples - examples/leiningen-test} - -lein_midje_runs = %w{examples/basic - examples/adder-webapp - examples/compound-checkers - examples/leiningen-midje} - -cake_midje_runs = %w{} - -run_runs = %w{ } # I doubt these are worthwhile any more. - -def lein_clean; system("lein clean 2>&1 | grep -v 'Cleaning up'"); end -def lein_deps; system("lein deps 2>&1 | grep -v 'Copying .* files'"); end - -puts "++++++++++++++++++++ Main Tests" -# system("ls lib") -puts " lein test" -system("lein test | grep failure") -puts " lein midje" -system("lein midje | grep FAILURE") - -puts "++++++++++++++++++++ Lein test tests" -lein_test_runs.each do | dir | - puts("= " + dir) - Dir.chdir(dir) do - # system("ls lib") - lein_clean - puts " lein test" - system("lein test > ~/tmp/lein.out; diff ~/tmp/lein.out lein-expected-output") - end -end - -puts "++++++++++++++++++++ Lein midje tests" -lein_midje_runs.each do | dir | - puts("= " + dir) - Dir.chdir(dir) do - # system("ls -R lib") - lein_clean - # Evidently, lein deps now tries to fetch even if the file is already present. - # lein_deps - puts " lein midje" - system("lein midje > ~/tmp/lein.out; diff ~/tmp/lein.out lein-expected-output") - end -end - -puts "++++++++++++++++++++ Cake midje tests" -cake_midje_runs.each do | dir | - puts("= " + dir) - Dir.chdir(dir) do - # system("ls lib") - puts " cake midje" - system("cake midje > ~/tmp/cake.out; diff ~/tmp/cake.out cake-expected-output") - end -end - - -puts "++++++++++++++++++++ Raw running" -run_runs.each do | dir | - puts("= " + dir) - Dir.chdir(dir) do - # system("ls lib") - lein_clean - puts " run" - system("run > ~/tmp/run.out; diff ~/tmp/run.out run-expected-output") - end -end - diff --git a/bin/update-project-files b/bin/update-project-files deleted file mode 100755 index 0eb72f124..000000000 --- a/bin/update-project-files +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env ruby - -MIDJE_VERSION=ARGV[0] -unless MIDJE_VERSION - puts "need version" - exit 1 -end - -PATTERN = /([\[\s]midje\s+)".*"/ -SUBST = '\1'+%Q{"#{MIDJE_VERSION}"} - -Dir["**/project.clj"].each do | file | - puts file - lines = File.readlines(file) - new_lines = lines.collect do | line | - if PATTERN =~ line - puts " " + line - puts " " + line.gsub(PATTERN, SUBST) - line.gsub(PATTERN, SUBST) - else - line - end - end - File.open(file, "w") do | io | - io.puts new_lines.join - end -end - diff --git a/bin/version b/bin/version deleted file mode 100755 index 8ce3e6ab3..000000000 --- a/bin/version +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env ruby - -$directories = %w{examples/adder-webapp examples/basic examples/cake-midje - examples/compound-checkers examples/leiningen-midje - examples/leiningen-test examples/semi-sweet-examples - .} - -require 'fileutils' -include FileUtils - -def arg(index, error) - unless ARGV[index] - puts "No #{error} version" - exit 1 - end - ARGV[index] -end - - -$midje_version = arg(0, "midje") -$clojure_version = arg(1, "clojure") -$clojure_contrib_version = ARGV[2] - - -$clojure_jar="#{ENV['HOME']}/.m2/repository/org/clojure/clojure/#{$clojure_version}/clojure-#{$clojure_version}.jar" -if $clojure_contrib_version - $contrib_jar="#{ENV['HOME']}/.m2/repository/org/clojure/clojure-contrib/#{$clojure_contrib_version}/clojure-contrib-#{$clojure_contrib_version}.jar" -end -$midje_jar="midje-#{$midje_version}.jar" - -def remove_jars(*dirs) - dirs.each do | dir | - `rm -f #{File.join(dir, "lib", "clojure-*")}` - `rm -f #{File.join(dir, "lib", "dev", "*")}` - end -end - -def install_jars(*dirs) - dirs.each do | dir | - libdir = File.join(dir, "lib") - mkdir(libdir) unless File.exist?(libdir) - cp $clojure_jar, libdir - cp $contrib_jar, libdir if $clojure_contrib_version - devdir = File.join(dir, "lib", "dev") - mkdir(devdir) unless File.exist?(devdir) - cp $midje_jar, devdir - Dir.glob("lib/*.jar").delete_if { | name | name =~ /clojure.*jar/ }. - each { | jar | `cp #{jar} #{devdir}`} - end -end - -def make_runs(*dirs) - dirs.each do | dir | - if File.exist?("#{dir}/make-run") - `(cd #{dir}; make-run)` - end - end -end - -remove_jars(*$directories) -install_jars(*$directories) -make_runs(*$directories) -exit 0 diff --git a/downloads/examples.zip b/downloads/examples.zip deleted file mode 100644 index 9525dabe7..000000000 Binary files a/downloads/examples.zip and /dev/null differ diff --git a/examples/README b/examples/README deleted file mode 100644 index f8bf24f8c..000000000 --- a/examples/README +++ /dev/null @@ -1,28 +0,0 @@ -adder-webapp/ - Mark McGranaghan's "Developing and Deploying a Simple Clojure Web - Application", converted to use Midje. - -basic/ - Various tests that show the use of Midje's default "sweet" interface. - -cake-midje/ - An example of running Midje tests with Cake. - -compound-checkers/ - Writing checkers that check more than one thing. (The - tricky bit is getting the line numbers to come out - right.) - -leiningen-midje/ - The Midje plugin for leiningen - -leiningen-test/ - You can use 'lein test'. You'll see all the individual - failures from both midje and clojure.test, but the - summary will be wrong. - -semi-sweet-examples/ - Midje's default "sweet" interface is built on top of a - "semi-sweet" interface (which in turn is built on an - "unprocessed" interface. These examples show how - the semi-sweet interface is used. diff --git a/examples/adder-webapp/README b/examples/adder-webapp/README deleted file mode 100644 index ce8db0d81..000000000 --- a/examples/adder-webapp/README +++ /dev/null @@ -1,9 +0,0 @@ -In this example, I've converted the tests from from Mark -McGranaghan's "Developing and Deploying a Simple Clojure Web -Application" to use Midje. I'm including them with his permission. - -http://mmcgrana.github.com/2010/07/develop-deploy-clojure-web-applications.html - -To run the tests, type: - -% lein midje diff --git a/examples/adder-webapp/lein-expected-output b/examples/adder-webapp/lein-expected-output deleted file mode 100644 index 2444a73cf..000000000 --- a/examples/adder-webapp/lein-expected-output +++ /dev/null @@ -1 +0,0 @@ -All claimed facts (7) have been confirmed. diff --git a/examples/adder-webapp/project.clj b/examples/adder-webapp/project.clj deleted file mode 100644 index a8b2af85d..000000000 --- a/examples/adder-webapp/project.clj +++ /dev/null @@ -1,14 +0,0 @@ -(defproject adder "0.4.0" - :description "Add two numbers." - :dependencies - [[org.clojure/clojure "[1.2.0],[1.2.1],[1.3.0]"] - [ring/ring "1.0.0-beta2"] - [ring/ring-devel "1.0.0-beta2"] - [ring/ring-jetty-adapter "1.0.0-beta2"] - [compojure "0.6.5"] - [hiccup "0.3.7"]] - :dev-dependencies - [[lein-run "1.0.0"] - [lein-midje "[1.0.0,)"] - [midje "1.3.1-SNAPSHOT"] - ]) diff --git a/examples/adder-webapp/public/adder.css b/examples/adder-webapp/public/adder.css deleted file mode 100644 index 719c7309a..000000000 --- a/examples/adder-webapp/public/adder.css +++ /dev/null @@ -1,5 +0,0 @@ -.math { - font-family: Monaco, monospace; } - -.action { - margin-top: 2em; } diff --git a/examples/adder-webapp/script/run.clj b/examples/adder-webapp/script/run.clj deleted file mode 100644 index 17a2dee55..000000000 --- a/examples/adder-webapp/script/run.clj +++ /dev/null @@ -1,5 +0,0 @@ -(use 'ring.adapter.jetty) -(require 'adder.core) - -(let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))] - (run-jetty #'adder.core/app {:port port})) diff --git a/examples/adder-webapp/src/adder/core.clj b/examples/adder-webapp/src/adder/core.clj deleted file mode 100644 index 54b061e82..000000000 --- a/examples/adder-webapp/src/adder/core.clj +++ /dev/null @@ -1,75 +0,0 @@ -(ns adder.core - (:use compojure.core) - (:use hiccup.core) - (:use hiccup.page-helpers) - (:use ring.middleware.reload) - (:use ring.util.response) - (:use ring.middleware.stacktrace) - (:use adder.middleware) - (:use ring.middleware.file) - (:use ring.middleware.file-info) -) - -(defn view-layout [& content] - (html - (doctype :xhtml-strict) - (xhtml-tag "en" - [:head - [:meta {:http-equiv "Content-type" - :content "text/html; charset=utf-8"}] - [:title "adder"] - [:link {:href "/adder.css" :rel "stylesheet" :type "text/css"}]] - [:body content]))) - -(defn view-input [& [a b]] - (view-layout - [:h2 "add two numbers"] - [:form {:method "post" :action "/"} - (if (and a b) - [:p "those are not both numbers!"]) - [:input.math {:type "text" :name "a" :value a}] [:span.math " + "] - [:input.math {:type "text" :name "b" :value b}] [:br] - [:input.action {:type "submit" :value "add"}]])) - -(defn view-output [a b sum] - (view-layout - [:h2 "two numbers added"] - [:p.math a " + " b " = " sum] - [:a.action {:href "/"} "add more numbers"])) - -(defn parse-input [a b] - [(Integer/parseInt a) (Integer/parseInt b)]) - -(defroutes handler - (GET "/" [] - (view-input)) - - (POST "/" [a b] - (try - (let [[a b] (parse-input a b) - sum (+ a b)] - (view-output a b sum)) - (catch NumberFormatException e - (view-input a b)))) - - (ANY "/*" [path] - (redirect "/")) -) - - -(def production? - (= "production" (get (System/getenv) "APP_ENV"))) - -(def development? - (not production?)) - -(def app - (-> #'handler - (wrap-file "public") - (wrap-file-info) - (wrap-request-logging) - (wrap-if development? wrap-reload '[adder.middleware adder.core]) - (wrap-bounce-favicon) - (wrap-exception-logging) - (wrap-if production? wrap-failsafe) - (wrap-if development? wrap-stacktrace))) diff --git a/examples/adder-webapp/src/adder/middleware.clj b/examples/adder-webapp/src/adder/middleware.clj deleted file mode 100644 index ac483470c..000000000 --- a/examples/adder-webapp/src/adder/middleware.clj +++ /dev/null @@ -1,46 +0,0 @@ -(ns adder.middleware - (:require [clj-stacktrace.repl :as strp]) -) - -(letfn [(log [msg & vals] - (let [line (apply format msg vals)] - (locking System/out (println line))))] - - (defn wrap-request-logging [handler] - (fn [{:keys [request-method uri] :as req}] - (let [start (System/currentTimeMillis) - resp (handler req) - finish (System/currentTimeMillis) - total (- finish start)] - (log "request %s %s (%dms)" request-method uri total) - resp))) - - (defn wrap-exception-logging [handler] - (fn [req] - (try - (handler req) - (catch Exception e - (log "Exception:\n%s" (strp/pst-str e)) - (throw e)))))) - -(defn wrap-bounce-favicon [handler] - (fn [req] - (if (= [:get "/favicon.ico"] [(:request-method req) (:uri req)]) - {:status 404 - :headers {} - :body ""} - (handler req)))) - -(defn wrap-if [handler pred wrapper & args] - (if pred - (apply wrapper handler args) - handler)) - -(defn wrap-failsafe [handler] - (fn [req] - (try - (handler req) - (catch Exception e - {:status 500 - :headers {"Content-Type" "text/plain"} - :body "We're sorry, something went wrong."})))) diff --git a/examples/adder-webapp/test/adder/core_test.clj b/examples/adder-webapp/test/adder/core_test.clj deleted file mode 100644 index 8f29b09f0..000000000 --- a/examples/adder-webapp/test/adder/core_test.clj +++ /dev/null @@ -1,32 +0,0 @@ -(ns adder.core-test - (:use midje.sweet) - (:use adder.core)) - -(facts "about utilities" - "parse-input" - (parse-input "1" "2") => [1 2] - (parse-input "foo" "2") => (throws NumberFormatException) - - "view-output" - (view-output 1 2 3) => (contains #"two numbers added")) - -(facts "about the main page" - "Bare request produces prompt" - (handler {:uri "/" :request-method :get}) - => (contains {:status 200 - :body #"add two numbers"}) - - "Valid numbers produce sum" - (handler {:uri "/" :request-method :post :params {"a" "1" "b" "2"}}) - => (contains {:status 200 - :body #"1 \+ 2 = 3"}) - - "Non-numbers produce error message" - (handler {:uri "/" :request-method :post :params {"a" "2" "b" "f"}}) - => (contains {:status 200 - :body #"those are not both numbers"})) - -(fact "Other pages produce redirect" - (handler {:uri "/anything" :request-method :get}) - => (contains {:status 302, - :headers (contains {"Location" "/"})})) diff --git a/examples/basic/.gitignore b/examples/basic/.gitignore deleted file mode 100644 index d9148e9fa..000000000 --- a/examples/basic/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -pom.xml -*jar -lib -classes \ No newline at end of file diff --git a/examples/basic/README.html b/examples/basic/README.html deleted file mode 100644 index 7e08d7898..000000000 --- a/examples/basic/README.html +++ /dev/null @@ -1,85 +0,0 @@ - - -Sweet Example - Basic - - - - -

-Did you download the zip or tar file described as the -easiest way to try out Midje? -

- -
    -
  • Yes! -

    - If you're on Unix (including the Mac), the shell script run runs the tests: -

    - -
    -     % run
    -  
    - -

    - If you're on Windows, you can look at the contents of the - script (two lines) and translate them into Windows terms. -

    - -

    - The file in this folder called - run-expected-output shows what - the results should look like. -

    -
  • - - - - -
  • No. - -

    -

      -
    1. -

      - You must have Leiningen installed to follow these - instructions. It's easy to install. Start here. - -

      -
    2. -
    3. -

      - To download and install everything you need to - run the example, including Midje, type this: -

      -
      -     % lein deps
      -  
      -

      - The downloaded jar files are put in the - lib/ subdirectory. -

      -
    4. - -
    5. -

      - The file - test/basic/core_test.clj is - intended to be good documentation of the different ways - you can use midje.sweet. You can run - the tests like this: -

      -
      -     % lein midje
      -  
      -

      - The file in this directory called - lein-expected-output shows what - the results should look like. -

      -
    6. -
    - -
  • -
- - diff --git a/examples/basic/lein-expected-output b/examples/basic/lein-expected-output deleted file mode 100644 index 0e878c007..000000000 --- a/examples/basic/lein-expected-output +++ /dev/null @@ -1,43 +0,0 @@ - -FAIL: at (core_test.clj:34) - Expected: 3 - Actual: 4 -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:45) -Actual result did not agree with the checking function. - Actual result: 4 - Checking function: odd? -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:80) -Actual result did not agree with the checking function. - Actual result: [3 3 1 2] - Checking function: (just [1 2 3] :in-any-order) - The checker said this about the reason: - Expected three elements. There were four. -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:130) -You claimed the following was needed, but it was never used: - (alive? ...cell...) - -FAIL: at (core_test.clj:131) -You claimed the following was needed, but it was never used: - (neighbor-count ...cell...) - -FAIL: at (core_test.clj:128) -Actual result did not agree with the checking function. - Actual result: nil - Checking function: truthy -^^^^ The previous three failures were expected ^^^^ - -FAIL: at (core_test.clj:231) -You claimed the following was needed, but it was never used: - (property-of ...data-structure-value-1...) - -FAIL: at (core_test.clj:231) -You claimed the following was needed, but it was never used: - (data-structure) -^^^^ The previous two failures were expected ^^^^ -FAILURE: 8 facts were not confirmed. (But 14 were.) diff --git a/examples/basic/make-run b/examples/basic/make-run deleted file mode 100755 index e9041046d..000000000 --- a/examples/basic/make-run +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby -# Yeah, I know, I should use Clojure. - -Dir.chdir "test" -jars=Dir.glob('../lib/*')+Dir.glob('../lib/dev/*') -classpath=jars.join(':') -clojure = %q{(ns basic.core-test)(run-tests)} -command="java -cp #{classpath} clojure.main -i basic/core_test.clj -e '#{clojure}'" -Dir.chdir ".." - -File.open("run", "w") do | io | - io.puts "cd test" - io.puts command -end - -File.chmod(0777, "run") diff --git a/examples/basic/project.clj b/examples/basic/project.clj deleted file mode 100644 index e3a62f883..000000000 --- a/examples/basic/project.clj +++ /dev/null @@ -1,8 +0,0 @@ -(defproject basic "1.0.0" - :description "An example of using Midje's sweet interface" - :dependencies [[org.clojure/clojure "[1.2.0,1.2.1]"] - [org.clojure/clojure-contrib "[1.2.0,1.2.1]"] - ] - :dev-dependencies [[midje "1.3.1-SNAPSHOT"] - [lein-midje "[1.0.0,)"]]) - diff --git a/examples/basic/run b/examples/basic/run deleted file mode 100755 index a79532d7a..000000000 --- a/examples/basic/run +++ /dev/null @@ -1,2 +0,0 @@ -cd test -java -cp ../lib/clojure-1.3.0.jar:../lib/dev:../lib/dev/algo.monads-0.1.0.jar:../lib/dev/colorize-0.1.1.jar:../lib/dev/core.incubator-0.1.0.jar:../lib/dev/core.logic-0.6.5.jar:../lib/dev/core.match-0.2.0-alpha9.jar:../lib/dev/core.unify-0.5.1.jar:../lib/dev/joda-time-2.0.jar:../lib/dev/math.combinatorics-0.0.1.jar:../lib/dev/midje-1.3.2-alpha1.jar:../lib/dev/ordered-1.0.0.jar:../lib/dev/tools.macro-0.1.1.jar:../lib/dev/utilize-0.2.2.jar clojure.main -i basic/core_test.clj -e '(ns basic.core-test)(run-tests)' diff --git a/examples/basic/run-expected-output b/examples/basic/run-expected-output deleted file mode 100644 index 7744606b3..000000000 --- a/examples/basic/run-expected-output +++ /dev/null @@ -1,48 +0,0 @@ - -FAIL at (core_test.clj:34) - Expected: 3 - Actual: 4 -^^^^ The previous failure was expected ^^^^ - -FAIL at (core_test.clj:45) -Actual result did not agree with the checking function. - Actual result: 4 - Checking function: odd? -^^^^ The previous failure was expected ^^^^ - -FAIL at (core_test.clj:80) -Actual result did not agree with the checking function. - Actual result: [3 3 1 2] - Checking function: (just [1 2 3] :in-any-order) - The checker said this about the reason: - Expected three elements. There were four. -^^^^ The previous failure was expected ^^^^ - -FAIL at (core_test.clj:130) -You claimed the following was needed, but it was never used: - (alive? ...cell...) - -FAIL at (core_test.clj:131) -You claimed the following was needed, but it was never used: - (neighbor-count ...cell...) - -FAIL at (core_test.clj:128) -Actual result did not agree with the checking function. - Actual result: nil - Checking function: truthy -^^^^ The previous three failures were expected ^^^^ - -FAIL at (core_test.clj:231) -You claimed the following was needed, but it was never used: - (property-of ...data-structure-value-1...) - -FAIL at (core_test.clj:231) -You claimed the following was needed, but it was never used: - (data-structure) -^^^^ The previous two failures were expected ^^^^ - -Testing basic.core-test - -Ran 0 tests containing 0 assertions. -0 failures, 0 errors. -{:type :summary, :pass 0, :test 0, :error 0, :fail 0} diff --git a/examples/basic/test/basic/core_test.clj b/examples/basic/test/basic/core_test.clj deleted file mode 100644 index dc42a9c87..000000000 --- a/examples/basic/test/basic/core_test.clj +++ /dev/null @@ -1,236 +0,0 @@ - -(ns basic.core-test - (:use clojure.test) - (:use midje.sweet) -) - -(defn note-expected-failure [] (println "^^^^ The previous failure was expected ^^^^")) - - ;;; - -;; This is an example of the Midje version of a clojure.test test that would -;; look like this: -;; (is (= (+ 1 1) 2)) -;; In Midje's preferred metaphor, such statements are facts about the -;; world, so its version looks like this: - -(fact (+ 1 1) => 2) - -;; You can have multiple assertions within the fact form. The function -;; has an alias, facts, for those of you who are sticklers about -;; grammar: - -(facts "arithmetic" - (+ 1 1) => 2 - (+ 1 0) => 1) - -;; Notice also that you can add "doc strings" to facts. Right now, they're just ignored. - -;; Failing tests should look familiar: -;; FAIL at (core_test.clj:34) -;; Expected: 3 -;; Actual: 4 - -(fact ( #(+ 1 %) 3) => 3) (note-expected-failure) - -;; You can use clojure.test to wrap facts in deftest. -;; See the examples/sweet-examples/leiningen-test example and -;; http://github.com/marick/Midje/wiki/Lein-test -;; In this example, I don't wrap facts. Instead, I use Midje's -;; leiningen plugin or run the tests directly. - -;; If the right-hand side of an arrow is a function, the actual result -;; is passed as the function's single argument. - -(fact ( #(+ 1 %) 3) => odd?) (note-expected-failure) -;; The failing test will look slightly different: -;; FAIL at (core_test.clj:45) -;; Actual result did not agree with the checking function. -;; Actual result: 4 -;; Checking function: odd? - - -;; If you're testing something that produces a function, use -;; (exactly): -;(fact (first [even? odd?]) => (exactly odd?)) (note-expected-failure) - -;; NOTE: the previous line is commented out because Midje prints the -;; function name nicely in Clojure 1.2. A breaking change to Clojure -;; 1.3 makes it harder to accomplish this, so an ugly name that -;; can vary between runs is printed instead. Leaving that name in the -;; output would prevent this from being used as part of the -;; just-before-release regression test suite. - -;; FAIL at (core_test.clj:56) -;; Actual result did not agree with the checking function. -;; Actual result: a function named 'even?' -;; Checking function: (exactly odd?) - -;; (Midje can't always determine the name of the function. If not, it'll print -;; gobbledeegook like #.) - -;; There are a number of matching functions available. You can find them all with -;; (ns-publics 'midje.checkers) -;; They have doc strings. -;; (Or you can look at the wiki: http://github.com/marick/Midje/wiki/Checkers) -;; Here's one of them: - -(fact - [3 1 2] => (just [1 2 3] :in-any-order) ;; succeeds - [3 3 1 2] => (just [1 2 3] :in-any-order)) (note-expected-failure) -;; FAIL at (core_test.clj:74) -;; Actual result did not agree with the checking function. -;; Actual result: [3 3 1 2] -;; Checking function: (just [1 2 3] :in-any-order) -;; The checker said this about the reason: -;; Expected three elements. There were four. - -;; You can negate the sense of a check: - -(fact (set [1 2 3]) =not=> sequential?) - -;; Facts work with variables bound both within and outside of the fact form: -(let [a 3] - (fact (+ a a) => 6) - (fact (let [b 33] - (+ a b) => 36))) - -;; Sometimes facts are only true provided other facts are true. Midje -;; has a notation for that. Suppose we're testing the rules for -;; Conway's Life (as used in Corey Haines' Code Retreat workshops). In -;; it, a cell "comes to life" if it is dead but has three living -;; neighbors. Let's say that rule will be part of an -;; alive-in-next-generation? function. As normal, we have to declare -;; that function before using it in a fact: - -(defn alive-in-next-generation? [cell] - ; not started yet. -) - -;; As we write the fact, we'll decide on prerequisite facts, which are -;; themselves functions that need to be declared. We could use the -;; Clojure's `declare` macro, but I prefer this one: - -(unfinished alive? neighbor-count) - -;; It makes it a little clearer what role those vars are playing, and -;; it defines the functions to blow up informatively if they're ever -;; called. Also, the Midje source contains an Emacs minor mode to make -;; it more convenient to write facts and add prerequisites to the -;; unfinished list. See -;; http://github.com/marick/Midje/wiki/Midje-mode - -;; All that given, here's an expression of the rule that lets you get the -;; logic of alive-in-next-generation? right before fussing with -;; counting neighbor cells: - -(fact - (alive-in-next-generation? ...cell...) => truthy - (provided - (alive? ...cell...) => false - (neighbor-count ...cell...) => 3)) -(println "^^^^ The previous three failures were expected ^^^^") - -;; Notes: -;; ...cell... : I use this shorthand to represent some -;; random value. None of its characteristics -;; matter except those defined by prerequisites. -;; You don't have to define such "metaconstants" - -;; Midje does it for you. -;; -;; truthy : Truthy is another one of Midje's checking -;; functions. It accepts any value other than -;; false or nil. -;; -;; provided : Provided gives a list of prerequisites the original -;; fact depends upon. (Note that the "provided" -;; form isn't within the original claim, but follows -;; just after it.) - -;; When the fact is checked, it will fail like this: - -;; FAIL for (core_test.clj:120) -;; You claimed the following was needed, but it was never used: -;; (alive? ...cell...) -;; FAIL for (core_test.clj:121) -;; You claimed the following was needed, but it was never used: -;; (neighbor-count ...cell...) -;; FAIL at (core_test.clj:118) -;; Actual result did not agree with the checking function. -;; Actual result: nil -;; Checking function: truthy - - -;; You can mention a particular unimplemented function more than once, -;; giving it a different argument each time: - -(unfinished g) - -(defn g-adder [n1 n2] - (+ (g n1) (g n2))) - -(fact - (g-adder 2 3) => 11 - (provided - (g 2) => 4 - (g 3) => 7)) - -;; When looking for a matching prerequisite, Midje 1.1 uses the same rules as -;; when checking a function-under-test's actual result. Because that's -;; been found to be confusing, it'll change. What follows is future-proof -;; test-writing. - -(unfinished subfunction) - -(defn function-under-test [value-to-pass] - (subfunction value-to-pass)) - -(facts "about functions in the argument list of a prerequisite" - "Ordinarily, functions in an argument list have to be matched exactly" - (function-under-test odd?) => 11 - (provided (subfunction odd?) => 11) - - "If you want to use an ordinary function as a checker, wrap it in as-checker." - (function-under-test 3) => 11 - (provided (subfunction (as-checker odd?)) => 11) - - "Predefined checkers can be used unadorned" - (function-under-test 'blue-cow) => 11 - (provided (subfunction anything) => 11) - - (function-under-test 3.00000001) => 11 - (provided (subfunction (roughly 3.0)) => 11)) - -;; The return values of a prerequisite function don't follow the rules -;; for arguments. They're literal constant values. - -;; I often find myself with one prerequisite that returns a data structure -;; that's immediately given to another one. That would look like this: - -(unfinished property-of data-structure) -(defn function-under-test [] - (property-of (data-structure))) - -(fact - (function-under-test) => 3 - (provided - (data-structure) => ...data-structure... - (property-of ...data-structure...) => 3)) - -;; I'd use a metaconstant for the data structure because I don't care to -;; commit myself to an exact format yet. -;; -;; Because this form is so common, you're allowed to "fold" the two -;; prerequisites together: - -(defn function-under-test [] 3) ; so we get failures. - -(fact - (function-under-test) => 3 - (provided - (property-of (data-structure)) => 3)) -(println "^^^^ The previous two failures were expected ^^^^") - -;; Notice that one of the failures refers to a generated metaconstant, -;; ...data-structure-value-1... - diff --git a/examples/compound-checkers/README b/examples/compound-checkers/README deleted file mode 100644 index e8959dc0d..000000000 --- a/examples/compound-checkers/README +++ /dev/null @@ -1,4 +0,0 @@ -Different ways of creating checkers that check more than one -thing. - -The tests are with the source in src/compound/two_finder.clj diff --git a/examples/compound-checkers/lein-expected-output b/examples/compound-checkers/lein-expected-output deleted file mode 100644 index 4c7bd40be..000000000 --- a/examples/compound-checkers/lein-expected-output +++ /dev/null @@ -1,43 +0,0 @@ - -FAIL at (two_finder.clj:35) -Actual result did not agree with the checking function. - Actual result: ["SO WRONG!"] - Checking function: (finds [1 3]) - -FAIL at (two_finder.clj:36) -Actual result did not agree with the checking function. - Actual result: (1 3) - Checking function: (finds [1 3]) - -FAIL at (two_finder.clj:58) -Actual result did not agree with the checking function. - Actual result: ["SO WRONG!"] - Checking function: (finds [1 3]) - During checking, these intermediate values were seen: - (= actual expected) => false - (= (clojure.core/deref count-atom) (count expected)) => true - -FAIL at (two_finder.clj:59) -Actual result did not agree with the checking function. - Actual result: (1 3) - Checking function: (finds [1 3]) - During checking, these intermediate values were seen: - (= actual expected) => true - (= (clojure.core/deref count-atom) (count expected)) => false - -FAIL at (two_finder.clj:99) - Expected: [1 3] - Actual: ["SO WRONG!"] - -FAIL at (two_finder.clj:100) - Expected: 2 - Actual: 900 - -FAIL at (two_finder.clj:126) - Expected: [111 333] - Actual: ["SO WRONG!"] - -FAIL at (two_finder.clj:127) - Expected: 2 - Actual: 900 -FAILURE: 8 facts were not confirmed. (But 8 were.) diff --git a/examples/compound-checkers/project.clj b/examples/compound-checkers/project.clj deleted file mode 100644 index 21092d338..000000000 --- a/examples/compound-checkers/project.clj +++ /dev/null @@ -1,7 +0,0 @@ -(defproject compound "0.4.0" - :description "Compound checker" - :dependencies - [[org.clojure/clojure "[1.2.0,1.2.1]"] - [org.clojure/clojure-contrib "1.2.0"]] - :dev-dependencies [[midje "1.3.1-SNAPSHOT"] - [lein-midje "[1.0.0,)"]]) diff --git a/examples/compound-checkers/src/compound/two_finder.clj b/examples/compound-checkers/src/compound/two_finder.clj deleted file mode 100644 index 665c45a81..000000000 --- a/examples/compound-checkers/src/compound/two_finder.clj +++ /dev/null @@ -1,131 +0,0 @@ -(ns compound.two-finder - (use midje.sweet)) - - ;;Source - -(def count-atom (atom 0)) - -(defn two-finder - "filters N elements in input collection and sets count-atom to number found" - [pred coll] - (let [filtered (filter pred coll)] - (swap! count-atom (constantly (count filtered))) - filtered)) - -(defn two-finder-bad-list [pred coll] - (let [filtered (filter pred coll)] - (swap! count-atom (constantly (count filtered))) - ["SO WRONG!"])) - -(defn two-finder-bad-atom [pred coll] - [pred coll] - (let [filtered (filter pred coll)] - (swap! count-atom (constantly 900)) - filtered)) - - - ;;Simple function - easy, but not recommended - -(defn finds [expected] - (fn [actual] - (and (= actual expected) - (= @count-atom (count expected))))) - -(fact - (two-finder-bad-list odd? [1 2 3]) => (finds [1 3]) - (two-finder-bad-atom odd? [1 2 3]) => (finds [1 3])) - -;; Virtues: -;; * Easy to remember how to do it. - -;; Flaws: -;; * You only find out that something failed, not which check: -;; Actual result: (1 3) -;; Checking function: (finds [1 3]) -;; At first glance, the above surely looks as if it should succeed. - - - ;; Chatty checkers - -;; Easy to create by changing the 'fn' to chatty-checker: - -(defn finds [expected] - (chatty-checker [actual] - (and (= actual expected) - (= @count-atom (count expected))))) - -(fact - (two-finder-bad-list odd? [1 2 3]) => (finds [1 3]) - (two-finder-bad-atom odd? [1 2 3]) => (finds [1 3])) - -;; Virtues: -;; * Tells you intermediate results: -;; FAIL at (two_finder.clj:56) -;; Actual result did not agree with the checking function. -;; Actual result: (1 3) -;; Checking function: (finds [1 3]) -;; During checking, these intermediate values were seen: -;; (= actual expected) => true -;; (= (clojure.core/deref count-atom) (count expected)) => false - -;; Flaws: -;; * Chatty checkers currently don't obey short-circuiting AND, -;; so second check will be evaluated even if first was false. -;; * More verbose output. -;; * Only works if the checks you're interested fit into a simple form: -;; (f ..check.. ..check..) - - - - ;;A macro containing expects - -;; #'fact is a macro that expands out into a "semi-sweet" macro named -;; #'expect. You can use #'expect directly: - -(defmacro finds [expected] - `(fn [actual#] - (and (expect actual# => ~expected) - (expect @count-atom => (count ~expected))) - true)) - -;; Explanation: -;; * You have to use a macro so that line numbers can be found correctly. -;; * Because expect returns false if the check fails, you can avoid -;; redundant checks. -;; * The ending true is to that a failure isn't forwarded on to the check -;; in the calling form. - -(fact - (two-finder-bad-list odd? [1 2 3]) => (finds [1 3]) - (two-finder-bad-atom odd? [1 2 3]) => (finds [1 3])) - -;; Virtues: -;; * Terser output than chatty-checkers and works when they don't. - -;; Flaws: -;; * It messes up totals. For example, the second check above looks -;; like one check that fails, but it's actually two succeeding -;; checks and one failing. (There are two checks within the macro -;; and one outside. The one outside will always succeed - it's really -;; there only for decoration. -;; * Harder to remember how to make it. - - - ;; One final stylistic point - -;; The various versions of #'finds say nothing to the reader about the -;; count, so this is probably preferable: - -(defmacro finds [expected-coll _ expected-count] - `(fn [actual#] - (and (expect actual# => ~expected-coll) - (expect @count-atom => ~expected-count)) - true)) - -(fact - (two-finder-bad-list odd? [111 222 333]) => (finds [111 333] :stashes 2) - (two-finder-bad-atom odd? [111 222 333]) => (finds [111 333] :stashes 2)) - -;; Notice that I also changed the numbers in the list to avoid a -;; misinterpretation like "Ah, it stashes the number (numbers?) that -;; were excluded from the result." diff --git a/examples/leiningen-midje/.gitignore b/examples/leiningen-midje/.gitignore deleted file mode 100644 index 6eae6a532..000000000 --- a/examples/leiningen-midje/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -pom.xml -*jar -lib -classes diff --git a/examples/leiningen-midje/README b/examples/leiningen-midje/README deleted file mode 100644 index 1e1547af0..000000000 --- a/examples/leiningen-midje/README +++ /dev/null @@ -1,15 +0,0 @@ -This shows the use of a Midje plugin for Leiningen. It's in -the file leiningen/midje.clj - -You run it like this: - -% lein midje - -As with `lein test`, you can give namespace arguments to -check only particular namespaces. - -`lein midje` checks both the `test` and `src` directories. - -It will also run clojure.test deftests if any exist. - - diff --git a/examples/leiningen-midje/lein-expected-output b/examples/leiningen-midje/lein-expected-output deleted file mode 100644 index a30d655bd..000000000 --- a/examples/leiningen-midje/lein-expected-output +++ /dev/null @@ -1,25 +0,0 @@ -The following two failures are intentional. - -FAIL: at (t_core.clj:10) -You claimed the following was needed, but it was never used: - (g 1) - -FAIL: at (t_core.clj:8) - Expected: 1 - Actual: nil - -FAIL: at (embedded_facts.clj:10) - Expected: 2 - Actual: 1 -The following failure (from deftest) is intentional. ->>> Output from clojure.test tests: - -FAIL in (a-failing-test) (t_core.clj:14) -expected: (= 33 (+ 1 2)) - actual: (not (= 33 3)) - ->>> clojure.test summary: -Ran 1 tests containing 1 assertions. -1 failures, 0 errors. ->>> Midje summary: -FAILURE: 3 facts were not confirmed. (But 1 was.) diff --git a/examples/leiningen-midje/project.clj b/examples/leiningen-midje/project.clj deleted file mode 100644 index 3db4fffa2..000000000 --- a/examples/leiningen-midje/project.clj +++ /dev/null @@ -1,6 +0,0 @@ -(defproject leiningen-midje "1.0.0-SNAPSHOT" - :description "FIXME: write" - :dependencies [[org.clojure/clojure "[1.2.0,1.2.1]"] - [org.clojure/clojure-contrib "1.2.0"]] - :dev-dependencies [[midje "1.3.1-SNAPSHOT"] - [lein-midje "[1.0.0,)"]]) diff --git a/examples/leiningen-midje/src/leiningen_midje/core.clj b/examples/leiningen-midje/src/leiningen_midje/core.clj deleted file mode 100644 index 1671494ec..000000000 --- a/examples/leiningen-midje/src/leiningen_midje/core.clj +++ /dev/null @@ -1,5 +0,0 @@ -(ns leiningen-midje.core) - -(defn g [n]) - -(defn f [n]) diff --git a/examples/leiningen-midje/src/leiningen_midje/embedded_facts.clj b/examples/leiningen-midje/src/leiningen_midje/embedded_facts.clj deleted file mode 100644 index c940a8d69..000000000 --- a/examples/leiningen-midje/src/leiningen_midje/embedded_facts.clj +++ /dev/null @@ -1,10 +0,0 @@ -(ns leiningen-midje.embedded-facts - [:use [midje.sweet]]) - - -(defn embedded [n] 1) - - -(facts - (embedded 1) => 1 - (embedded 2) => 2) diff --git a/examples/leiningen-midje/test/leiningen_midje/t_core.clj b/examples/leiningen-midje/test/leiningen_midje/t_core.clj deleted file mode 100644 index d3f310ed3..000000000 --- a/examples/leiningen-midje/test/leiningen_midje/t_core.clj +++ /dev/null @@ -1,14 +0,0 @@ -(ns leiningen-midje.t-core - (:use leiningen-midje.core - clojure.test - midje.sweet)) - -(println "The following two failures are intentional.") -(fact - (f 1) => 1 - (provided - (g 1) => 1)) - -(deftest a-failing-test - (println "The following failure (from deftest) is intentional.") - (is (= 33 (+ 1 2)))) diff --git a/examples/leiningen-test/README b/examples/leiningen-test/README deleted file mode 100644 index bf8568c78..000000000 --- a/examples/leiningen-test/README +++ /dev/null @@ -1,5 +0,0 @@ -This shows how to use Midje so that "lein test" prints -something useful. - -Look in the test directory to see how tests are set up. - diff --git a/examples/leiningen-test/lein-expected-output b/examples/leiningen-test/lein-expected-output deleted file mode 100644 index fade25404..000000000 --- a/examples/leiningen-test/lein-expected-output +++ /dev/null @@ -1,23 +0,0 @@ - -Testing lein-test.test.deftest - -FAIL: at (deftest.clj:10) - Expected: 4 - Actual: 3 - -FAIL: at (deftest.clj:11) - Expected: 4 - Actual: 5 - -FAIL: at (deftest.clj:7) - Expected: 3 - Actual: 2 - -Testing lein-test.test.with-test - -FAIL: at (with_test.clj:8) - Expected: 3 - Actual: 2 - -Ran 3 tests containing 4 assertions. -4 failures, 0 errors. diff --git a/examples/leiningen-test/project.clj b/examples/leiningen-test/project.clj deleted file mode 100644 index 087a3ede6..000000000 --- a/examples/leiningen-test/project.clj +++ /dev/null @@ -1,5 +0,0 @@ -(defproject lein-test "1.0.0-SNAPSHOT" - :description "FIXME: write" - :dependencies [[org.clojure/clojure "[1.2.0,1.2.1]"] - [org.clojure/clojure-contrib "1.2.0"]] - :dev-dependencies [[midje "1.3.1-SNAPSHOT"]]) diff --git a/examples/leiningen-test/src/lein_test/core.clj b/examples/leiningen-test/src/lein_test/core.clj deleted file mode 100644 index 500996a30..000000000 --- a/examples/leiningen-test/src/lein_test/core.clj +++ /dev/null @@ -1 +0,0 @@ -(ns lein-test.core) diff --git a/examples/leiningen-test/test/lein_test/test/deftest.clj b/examples/leiningen-test/test/lein_test/test/deftest.clj deleted file mode 100644 index f9b15ab3a..000000000 --- a/examples/leiningen-test/test/lein_test/test/deftest.clj +++ /dev/null @@ -1,11 +0,0 @@ -(ns lein-test.test.deftest - (:use [lein-test.core] :reload) - (:use [midje.sweet] - [clojure.test])) - -(deftest one-fact-in-a-test - (fact (+ 1 1) => 3)) - -(deftest two-facts-in-a-test - (fact (+ 1 2) => 4) - (fact (+ 2 3) => 4)) diff --git a/examples/leiningen-test/test/lein_test/test/with_test.clj b/examples/leiningen-test/test/lein_test/test/with_test.clj deleted file mode 100644 index 5180865c9..000000000 --- a/examples/leiningen-test/test/lein_test/test/with_test.clj +++ /dev/null @@ -1,8 +0,0 @@ -(ns lein-test.test.with-test - (:use [lein-test.core] :reload) - (:use [clojure.test] - [midje.sweet])) - -(with-test - (defn foo [x y] (* x y)) - (fact (foo 1 2) => 3)) diff --git a/examples/semi-sweet-examples/.gitignore b/examples/semi-sweet-examples/.gitignore deleted file mode 100644 index d9148e9fa..000000000 --- a/examples/semi-sweet-examples/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -pom.xml -*jar -lib -classes \ No newline at end of file diff --git a/examples/semi-sweet-examples/README.html b/examples/semi-sweet-examples/README.html deleted file mode 100644 index fba0263c5..000000000 --- a/examples/semi-sweet-examples/README.html +++ /dev/null @@ -1,85 +0,0 @@ - - -Semi-Sweet Example - Simple - - - - -

-Did you download the zip or tar file described as the -easiest way to try out Midje? -

- -
    -
  • Yes! -

    - If you're on Unix (including the Mac), the shell script run runs the tests: -

    - -
    -     % run
    -  
    - -

    - If you're on Windows, you can look at the contents of the - script (two lines) and translate them into Windows terms. -

    - -

    - The file in this folder called - run-expected-output shows what - the results should look like. -

    -
  • - - - - -
  • No. - -

    -

      -
    1. -

      - You must have Leiningen installed to follow these - instructions. It's easy to install. Start here. - -

      -
    2. -
    3. -

      - To download and install everything you need to - run the example, including Midje, type this: -

      -
      -     % lein deps
      -  
      -

      - The downloaded jar files are put in the - lib/ subdirectory. -

      -
    4. - -
    5. -

      - The file - test/semi_sweet_simple/core_test.clj is - intended to be good documentation of the different ways - you can use midje.semi-sweet. You can run - the tests like this: -

      -
      -     % lein test
      -  
      -

      - The file in this directory called - lein-expected-output shows what - the results should look like. -

      -
    6. -
    - -
  • -
- - diff --git a/examples/semi-sweet-examples/lein-expected-output b/examples/semi-sweet-examples/lein-expected-output deleted file mode 100644 index 3c5ff928a..000000000 --- a/examples/semi-sweet-examples/lein-expected-output +++ /dev/null @@ -1,29 +0,0 @@ - -Testing semi-sweet-simple.core-test - -FAIL: at (core_test.clj:25) - Expected: 3 - Actual: 4 -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:30) -Actual result did not agree with the checking function. - Actual result: 4 - Checking function: odd? -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:43) -Actual result did not agree with the checking function. - Actual result: [3 3 1 2] - Checking function: (just [1 2 3] :in-any-order) - The checker said this about the reason: - Expected three elements. There were four. -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:70) -You claimed the following was needed, but it was never used: - (first-fake 3) -^^^^ The previous failure was expected ^^^^ - -Ran 10 tests containing 16 assertions. -4 failures, 0 errors. diff --git a/examples/semi-sweet-examples/make-run b/examples/semi-sweet-examples/make-run deleted file mode 100755 index 8ee099c1e..000000000 --- a/examples/semi-sweet-examples/make-run +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby -# Yeah, I know, I should use Clojure. - -Dir.chdir "test" -jars=Dir.glob('../lib/*')+Dir.glob('../lib/dev/*') -classpath=jars.join(':') -clojure = %q{(ns semi-sweet-simple.core-test)(run-tests)} -command="java -cp #{classpath} clojure.main -i semi_sweet_simple/core_test.clj -e '#{clojure}'" -Dir.chdir ".." - -File.open("run", "w") do | io | - io.puts "cd test" - io.puts command -end - -File.chmod(0777, "run") diff --git a/examples/semi-sweet-examples/project.clj b/examples/semi-sweet-examples/project.clj deleted file mode 100644 index 1dae36046..000000000 --- a/examples/semi-sweet-examples/project.clj +++ /dev/null @@ -1,5 +0,0 @@ -(defproject semi-sweet-simple "1.0.0" - :description "An example of using Midje semi-sweet mocking" - :dependencies [[org.clojure/clojure "[1.2.0,1.2.1]"] - [org.clojure/clojure-contrib "1.2.0"]] - :dev-dependencies [[midje "1.3.1-SNAPSHOT"]]) diff --git a/examples/semi-sweet-examples/run b/examples/semi-sweet-examples/run deleted file mode 100755 index ab7f4188f..000000000 --- a/examples/semi-sweet-examples/run +++ /dev/null @@ -1,2 +0,0 @@ -cd test -java -cp ../lib/clojure-1.3.0.jar:../lib/dev:../lib/dev/algo.monads-0.1.0.jar:../lib/dev/colorize-0.1.1.jar:../lib/dev/core.incubator-0.1.0.jar:../lib/dev/core.logic-0.6.5.jar:../lib/dev/core.match-0.2.0-alpha9.jar:../lib/dev/core.unify-0.5.1.jar:../lib/dev/joda-time-2.0.jar:../lib/dev/math.combinatorics-0.0.1.jar:../lib/dev/midje-1.3.2-alpha1.jar:../lib/dev/ordered-1.0.0.jar:../lib/dev/tools.macro-0.1.1.jar:../lib/dev/utilize-0.2.2.jar clojure.main -i semi_sweet_simple/core_test.clj -e '(ns semi-sweet-simple.core-test)(run-tests)' diff --git a/examples/semi-sweet-examples/run-expected-output b/examples/semi-sweet-examples/run-expected-output deleted file mode 100644 index 5affe52a1..000000000 --- a/examples/semi-sweet-examples/run-expected-output +++ /dev/null @@ -1,36 +0,0 @@ - -Testing semi-sweet-simple.core-test - -FAIL: at (core_test.clj:25) - Expected: 3 - Actual: 4 -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:30) -Actual result did not agree with the checking function. - Actual result: 4 - Checking function: odd? -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:40) -Actual result did not agree with the checking function. - Actual result: a function named 'even?' - Checking function: (exactly odd?) -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:51) -Actual result did not agree with the checking function. - Actual result: [3 3 1 2] - Checking function: (just [1 2 3] :in-any-order) - The checker said this about the reason: - Expected three elements. There were four. -^^^^ The previous failure was expected ^^^^ - -FAIL: at (core_test.clj:78) -You claimed the following was needed, but it was never used: - (first-fake 3) -^^^^ The previous failure was expected ^^^^ - -Ran 11 tests containing 17 assertions. -5 failures, 0 errors. -{:type :summary, :test 11, :pass 12, :fail 5, :error 0} diff --git a/examples/semi-sweet-examples/src/semi_sweet_simple/core.clj b/examples/semi-sweet-examples/src/semi_sweet_simple/core.clj deleted file mode 100644 index d74b221da..000000000 --- a/examples/semi-sweet-examples/src/semi_sweet_simple/core.clj +++ /dev/null @@ -1 +0,0 @@ -(ns semi-sweet-simple.core) diff --git a/examples/semi-sweet-examples/test/semi_sweet_simple/core_test.clj b/examples/semi-sweet-examples/test/semi_sweet_simple/core_test.clj deleted file mode 100644 index 17ab85b65..000000000 --- a/examples/semi-sweet-examples/test/semi_sweet_simple/core_test.clj +++ /dev/null @@ -1,164 +0,0 @@ - -(ns semi-sweet-simple.core-test - (:use clojure.test) - (:use midje.semi-sweet) -) - -(defn note-expected [] (println "^^^^ The previous failure was expected ^^^^")) - - ;;; - -;; This is an example of the Midje version of a clojure.test test that would -;; look like this: -;; (is (= (+ 1 1) 2)) -;; -;; Midje uses the clojure.test reporting mechanism, so that you can continue to -;; use tools that assume clojure.test. -(deftest example-of-a-simple-equality-test - (expect (+ 1 1) => 2)) - -;; Failing tests should look familiar: -;; FAIL at (core_test.clj:19) -;; expected: 3 -;; actual: 4 -(deftest example-of-a-simple-equality-test-failure - (expect ( #(+ 1 %) 3) => 3) (note-expected)) - -;; You can also use functions on the right-hand side. In that case, -;; the actual result is passed as the function's single argument. -(deftest example-of-a-function-as-right-hand-side - (expect ( #(+ 1 %) 3) => odd?) (note-expected)) -;; The failing test will look slighly different: -;; FAIL at (core_test.clj:28) -;; Actual result did not pass expected function. -;; expected function: odd? -;; actual result: 4 - -;; There are a number of matching functions available. You can find them all with -;; (ns-publics 'midje.checkers) -;; They have doc strings. -;; Here's one of them: -(deftest example-of-a-predefined-checker - (expect '[3 1 2] => (just [1 2 3] :in-any-order)) ;; succeeds - (expect '[3 3 1 2] => (just [1 2 3] :in-any-order)) (note-expected)) -;; Actual result did not agree with the checking function. -;; Actual result: [3 3 1 2] -;; Checking function: (just [1 2 3] :in-any-order) -;; The checker said this about the reason: -;; Expected three elements. There were four. - -;; In the semi-sweet version of Midje, functions can be faked out as follows. -;; As with normal functions, faked functions have to be declared before use. -(declare first-fake another-fake) - -;; Let's suppose we want to test this function but we haven't gotten around to -;; writing first-fake yet. -(defn function-under-test-1 [& rest] - (apply first-fake rest)) - -;; The following test fakes the first-fake so that it returns a -;; predefined value when it's called. After that, the result of -;; function-under-test is checked in the normal way. -(deftest example-of-a-simple-fake - (expect (function-under-test-1 3) => 5 - (fake (first-fake 3) => 5))) - -;; Here's the failure when a fake is never called -(defn function-under-test-2 [_] 5) -(deftest example-of-a-simple-fake-failure - (expect (function-under-test-2 3) => 5 - (fake (first-fake 3) => 5)) (note-expected)) -;; FAIL for (core_test.clj:80) -;; This expectation was never satisfied: -;; (first-fake 3) should be called at least once. - -;; If you rerun this test, you'll find that the line number in the -;; actual error will point to the line containing the (fake) -;; call. I go to some trouble to get line numbers right. For example, -;; they should be correct even if the expect and fake are generated by -;; a macro. Let me know of cases where line numbers are wrong. - - -;; You can describe more than one call to the faked function, and you -;; can fake more than one function. -(defn function-under-test-3 [] - (+ (first-fake 1) (first-fake 2 2) (another-fake))) -(deftest example-of-multiple-faked-functions - (expect (function-under-test-3) => 111 - (fake (first-fake 1) => 1) - (fake (first-fake 2 2) => 10) - (fake (another-fake) => 100))) - -;; When looking for a matching fake, Midje 1.1 uses the same rules as -;; when checking a function-under-test's actual result. Because that's -;; been found to be confusing, it'll change. Here's future-proof -;; behavior. Given this function: - -(defn function-under-test-4 [value-to-pass] - (first-fake value-to-pass)) - - -(deftest example-of-interesting-functional-args - ;; Function arguments in checkers are matched literally. - (expect (function-under-test-4 odd?) => 11 - (fake (first-fake odd?) => 11)) - ;; If you want to use an ordinary function as a checker, wrap it in - ;; as-checker: - (expect (function-under-test-4 3) => 11 - (fake (first-fake (as-checker odd?)) => 11)) - ;; You can use predefined checkers as arguments. - (expect (function-under-test-4 'hops) => 11 - (fake (first-fake anything) => 11)) - (expect (function-under-test-4 3.0) => 11 - (fake (first-fake (roughly 3.0 0.1)) => 11))) - -;; The return values of a fake don't follow the rules for fake -;; arguments. I suppose I could be convinced that a "returning" a -;; function should be interpreted as returning the value that function -;; produces when given the actual arguments. I've never seen a need -;; for that. If you do, let me know. - -;; You can insist that a function not be called: - -(defn not-caller [n] (+ 1 n)) -(defn some-function-never-called []) -(deftest example-of-not-called - (expect (not-caller 3) => 4 - (not-called some-function-never-called))) - -;; You can fake a function that's part of Clojure. Suppose we -;; have a function that operates on two sets. We want to override -;; clojure.set/intersection so that our tests can only talk about properties -;; of tests, rather than have to laboriously construct actual sets with those -;; properties. So we pass in descriptive strings and fake out intersection. -;; -;; There is more support for this style in midje.sweet. - -(use 'clojure.set) -(defn set-handler [set1 set2] - (if (empty? (intersection set1 set2)) - set1 - (intersection set1 set2))) - -(deftest example-of-faking-function-from-another-namespace - "For disjoint sets, return the first." - (expect (set-handler "some set" "some disjoint set") => "some set" - (fake (intersection "some set" "some disjoint set") => #{})) - "For overlapping sets, return the intersection" - (expect (set-handler "set" "overlapping set") => #{"intersection"} - (fake (intersection "set" "overlapping set") => #{"intersection"}))) - -(defn test-ns-hook [] - "This calls the functions in order." - (example-of-a-simple-equality-test) - (example-of-a-simple-equality-test-failure) - (example-of-a-function-as-right-hand-side) - (example-of-a-predefined-checker) - (example-of-a-simple-fake) - (example-of-a-simple-fake-failure) - (example-of-multiple-faked-functions) - (example-of-interesting-functional-args) - (example-of-not-called) - (example-of-faking-function-from-another-namespace) -) - diff --git a/leiningen/timings.clj b/leiningen/timings.clj index 31b32c0d1..7684a5738 100644 --- a/leiningen/timings.clj +++ b/leiningen/timings.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns leiningen.timings (:refer-clojure :exclude [test]) (:use [leiningen.util.ns :only [namespaces-in-dir]] diff --git a/project.clj b/project.clj index 277491a0a..88d657d7f 100644 --- a/project.clj +++ b/project.clj @@ -1,27 +1,31 @@ -(def common-deps '[[ordered "1.1.0"] +(def common-deps '[[ordered "1.2.0" :exclusions [org.clojure/clojure]] [org.clojure/math.combinatorics "0.0.1"] [org.clojure/algo.monads "0.1.0"] - [org.clojure/core.unify "0.5.2"] - [utilize "0.2.2"] - [colorize "0.1.1"] + [org.clojure/core.unify "0.5.2" :exclusions [org.clojure/clojure]] + [utilize "0.2.3" :exclusions [org.clojure/clojure]] + [colorize "0.1.1" :exclusions [org.clojure/clojure]] [org.clojure/tools.macro "0.1.1"] [org.clojure/core.incubator "0.1.0"] [org.clojure/core.match "0.2.0-alpha9"] - [org.clojure/clojurescript "0.0-971"]]) + [org.clojure/clojurescript "0.0-1443"] + [swiss-arrows "0.1.0"]]) -(defproject midje "1.3.2-alpha1" +(defproject midje "1.5.0-SNAPSHOT" :description "A TDD library for Clojure that supports top-down ('mockish') TDD, encourages readable tests, provides a smooth migration path from clojure.test, balances abstraction and concreteness, and strives for graciousness." :url "https://github.com/marick/Midje" - :dependencies ~(conj common-deps - '[org.clojure/clojure "1.3.0"]) + :dependencies ~(cons '[org.clojure/clojure "1.4.0"] + common-deps) + :multi-deps {"1.2.0" [[org.clojure/clojure "1.2.0"]] "1.2.1" [[org.clojure/clojure "1.2.1"]] "1.3.0" [[org.clojure/clojure "1.3.0"]] - "1.4.0" [[org.clojure/clojure "1.4.0-beta1"]] + "1.4.0" [[org.clojure/clojure "1.4.0"]] +; "1.5.0" [[org.clojure/clojure "1.5.0-SNAPSHOT"]] :all ~common-deps } :dev-dependencies [[slamhound "1.2.0"] - ;;[com.stuartsierra/lazytest "1.2.3"] - ] + [jonase/kibit "0.0.3"] + [jonase/eastwood "0.0.2"] + [com.stuartsierra/lazytest "1.2.3"]] ;; automatically detects when your :dependencies key changes and runs ;; lein deps behind the scenes when necessary. @@ -29,4 +33,5 @@ ; :warn-on-reflection true ;; turn on to check production code for reflection ;; For Clojure snapshots - :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"}) + :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/" + "stuartsierra-releases" "http://stuartsierra.com/maven2"}) diff --git a/src/midje/checkers.clj b/src/midje/checkers.clj index 3aa41f651..a6d2a4f00 100644 --- a/src/midje/checkers.clj +++ b/src/midje/checkers.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Checkers are for checking results of expectations, or checking that appropriate arguments are passed to prerequisites"} midje.checkers @@ -20,6 +18,8 @@ '[chatty-checker]) (republish 'midje.checkers.simple '[truthy falsey TRUTHY FALSEY anything irrelevant exactly throws roughly]) + (republish 'midje.checkers.combining + '[every-checker some-checker]) (republish 'midje.checkers.collection '[has has-suffix has-prefix just contains n-of one-of two-of three-of four-of five-of six-of seven-of eight-of nine-of ten-of]) diff --git a/src/midje/checkers/chatty.clj b/src/midje/checkers/chatty.clj index e09be84e8..f5e4d9375 100644 --- a/src/midje/checkers/chatty.clj +++ b/src/midje/checkers/chatty.clj @@ -1,44 +1,28 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Checkers that explain more about a failure."} midje.checkers.chatty (:use [midje.checkers.util :only [named-as-call]] + [midje.checkers.extended-falsehood :only [data-laden-falsehood? + as-data-laden-falsehood + extended-false?]] [midje.checkers.defining :only [as-checker]] - [midje.util.form-utils :only [pairs quoted? single-arg-into-form-and-name]])) + [midje.util.form-utils :only [pairs quoted? single-destructuring-arg->form+name]])) ;; Note: checkers need to be exported in ../checkers.clj -;; TODO: It might make sense to split the notion of extended-falsehood out of -;; that of chatty checkers, since there are now other kinds of checkers that -;; generate chatty falsehoods. - -(defn as-chatty-falsehood [value] - (with-meta value {:midje/chatty-checker-falsehood true})) - -(defn chattily-false? [value] - (or (not value) - (:midje/chatty-checker-falsehood (meta value)))) - (defn as-chatty-checker [function] (as-checker (vary-meta function assoc :midje/chatty-checker true))) -(defn chatty-falsehood-to-map [value] - (with-meta value {})) - -(defn chatty-checker-falsehood? [value] - (:midje/chatty-checker-falsehood (meta value))) +(defn chatty-checker? [fn] + (:midje/chatty-checker (meta fn))) (defn add-actual [actual result] - (if (chatty-checker-falsehood? result) + (if (data-laden-falsehood? result) (assoc result :actual actual) result)) -(defn chatty-checker? [fn] - (:midje/chatty-checker (meta fn))) - (defn chatty-worth-reporting-on? [arg] (and (or (list? arg) (seq? arg)) ; what started as a list (fn x y) might now be a seq. - (> (count arg) 0) + (pos? (count arg)) (not (quoted? arg)))) (defn chatty-untease [result-symbol arglist] @@ -55,12 +39,12 @@ [ [actual-arg] [f & args] ] (let [result-symbol (gensym "chatty-intermediate-results-") [complex-forms substituted-args] (chatty-untease result-symbol args) - [arg-form arg-name] (single-arg-into-form-and-name actual-arg)] + [arg-form arg-name] (single-destructuring-arg->form+name actual-arg)] `(as-chatty-checker (fn [~arg-form] - (let [~result-symbol (vector ~@complex-forms)] - (if (chattily-false? (~f ~@substituted-args)) + (let [~result-symbol (vec ~complex-forms)] + (if (extended-false? (~f ~@substituted-args)) (let [pairs# (pairs '~complex-forms ~result-symbol)] - (as-chatty-falsehood {:actual ~arg-name, + (as-data-laden-falsehood {:actual ~arg-name :intermediate-results pairs#})) true)))))) diff --git a/src/midje/checkers/collection.clj b/src/midje/checkers/collection.clj index abcb3e332..30d9b901c 100644 --- a/src/midje/checkers/collection.clj +++ b/src/midje/checkers/collection.clj @@ -1,15 +1,12 @@ -;; -*- indent-tabs-mode: nil -*- - ;; Note: checkers need to be exported in ../checkers.clj (ns ^{:doc "Checkers for collections and strings."} midje.checkers.collection (:use [clojure.set :only [union]] [clojure.pprint :only [cl-format]] - [clojure.core.match :only [match]] [midje.util.backwards-compatible-utils :only [every-pred-m]] [midje.util.form-utils :only [regex? record? classic-map? pred-cond macro-for]] - [midje.checkers collection-util util extended-equality chatty defining collection-comparison] + [midje.checkers collection-util util extended-equality extended-falsehood chatty defining collection-comparison] [midje.error-handling.exceptions :only [user-error]])) @@ -51,7 +48,7 @@ (and (record? expected) (map? actual) - (not (= (class expected) (class actual)))) + (not= (class expected) (class actual))) (throw (user-error (str "You expected a " (.getName (class expected)) " but the actual value was a " (if (classic-map? actual) "map" (.getName (class actual))) @@ -68,17 +65,18 @@ ;;Reduce arguments to standard forms so there are fewer combinations to ;;consider. Also blow up for some incompatible forms." [actual expected looseness] - (compatibility-check actual expected looseness) - (match [actual expected] - [(a :when sequential?) (e :when set?)] [actual (vec expected) (union looseness #{:in-any-order })] - [(a :when sequential?) (e :when right-hand-singleton?)] [actual [expected] (union looseness #{:in-any-order })] - [(a :when sequential?) _] [actual expected looseness] - [(a :when map?) (b :when map?)] [actual expected looseness] - [(a :when map?) _] [actual (into {} expected) looseness] - [(a :when set?) _] (recur (vec actual) expected looseness-modifiers) - [(a :when string?) (e :when [(complement string?) - (complement regex?)])] (recur (vec actual) expected looseness-modifiers) - [_ _] [actual expected looseness])) + (compatibility-check actual expected looseness) + (cond + (and (sequential? actual) (set? expected)) [actual (vec expected) (union looseness #{:in-any-order })] + (and (sequential? actual) (right-hand-singleton? expected)) [actual [expected] (union looseness #{:in-any-order })] + (sequential? actual) [actual expected looseness] + (and (map? actual) (map? expected)) [actual expected looseness] + (map? actual) [actual (into {} expected) looseness] + (set? actual) (recur (vec actual) expected looseness-modifiers) + (and (string? actual) + (not (string? expected)) + (not (regex? expected))) (recur (vec actual) expected looseness-modifiers) + :else [actual expected looseness])) (match? [actual expected looseness] (let [comparison (compare-results actual expected looseness)] @@ -234,6 +232,7 @@ just is also useful if you don't care about order: Ex. (fact (repeat 100 :a) => (n-of :a 100))" [expected expected-count] + (chatty-checker [actual] (and (= (count actual) expected-count) (every? #(extended-= % expected) actual)))) @@ -252,4 +251,4 @@ just is also useful if you don't care about order: [expected#] (n-of expected# ~num))))) -(generate-n-of-checkers) \ No newline at end of file +(generate-n-of-checkers) diff --git a/src/midje/checkers/collection_comparison.clj b/src/midje/checkers/collection_comparison.clj index 945c884eb..e265d21f1 100644 --- a/src/midje/checkers/collection_comparison.clj +++ b/src/midje/checkers/collection_comparison.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - ;; Note: checkers need to be exported in ../checkers.clj (ns ^{:doc "Code to use to compare collections."} @@ -18,33 +16,32 @@ (defmulti ^{:private true} collection-string "Given a list of stringified elements, convert them into appropriate collection text." - (fn [midje-classification elements] midje-classification)) + (fn [midje-classification _elements_] midje-classification)) -(defmethod collection-string ::map [midje-classification elements] +(defmethod collection-string ::map [_midje-classification_ elements] (str "{" (join ", " (sort elements)) "}")) -(defmethod collection-string ::not-map [midje-classification elements] +(defmethod collection-string ::not-map [_midje-classification_ elements] (str "[" (join " " elements) "]")) ;;- (defmulti best-actual-match "Describe the best actuals found in the comparison." - (fn [midje-classification comparison] midje-classification)) + (fn [midje-classification _comparison_] midje-classification)) -(defmethod best-actual-match ::not-map [midje-classification comparison] +(defmethod best-actual-match ::not-map [_midje-classification_ comparison] (str "Best match found: " (pr-str (:actual-found comparison)))) -(defmethod best-actual-match ::map [midje-classification comparison] +(defmethod best-actual-match ::map [_midje-classification_ comparison] (str "Best match found: " (pr-str (sort-map (:actual-found comparison))))) (defmulti best-expected-match "Describe the best list of expected values found in the comparison." - (fn [midje-classification comparison expected] midje-classification)) + (fn [midje-classification _comparison_ _expected_] midje-classification)) (letfn [(best-expected-match-wrapper [midje-classification comparison expected element-maker suffix] - (if (not-any? inexact-checker? expected) - nil + (when (some inexact-checker? expected) [(str " It matched: " (->> comparison :expected-found (map element-maker) (collection-string midje-classification)) suffix @@ -166,12 +163,12 @@ (rotations checkers)))] (defmulti compare-results - (fn [actual expected looseness] + (fn [actual _expected_ looseness] (if (= ::map (midje-classification actual)) ::map [::not-map (or (some #{:in-any-order} looseness) :strict-order)]))) - (defmethod compare-results ::map [actual expected looseness] + (defmethod compare-results ::map [actual expected _looseness_] (order-free-compare-results expected (feasible-permutations (keys expected)) (fn [permutation] diff --git a/src/midje/checkers/collection_util.clj b/src/midje/checkers/collection_util.clj index c7021ca9d..53047d60c 100644 --- a/src/midje/checkers/collection_util.clj +++ b/src/midje/checkers/collection_util.clj @@ -1,7 +1,6 @@ (ns midje.checkers.collection-util - (:use [midje.util.form-utils :only [regex?]] - [midje.checkers.extended-equality :only [extended-fn?]] - [midje.checkers.chatty :only [as-chatty-falsehood]])) + (:use [midje.util.form-utils :only [extended-fn? regex?]] + [midje.checkers.extended-falsehood :only [as-data-laden-falsehood]])) (defn same-lengths? [actual expected] (= (count actual) (count expected))) @@ -46,7 +45,7 @@ "Produce a partially constructed chatty falsehood that contains a :notes key with the strings." [& strings ] - (as-chatty-falsehood {:notes strings})) + (as-data-laden-falsehood {:notes strings})) (defn try-re "Use the function (re-find or re-matches) to apply re to the thing. diff --git a/src/midje/checkers/combining.clj b/src/midje/checkers/combining.clj new file mode 100644 index 000000000..2f3093ee6 --- /dev/null +++ b/src/midje/checkers/combining.clj @@ -0,0 +1,54 @@ +(ns ^{:doc "Checkers that combine other checkers."} + midje.checkers.combining + (:use [midje.checkers.defining] + [midje.checkers.chatty] + [midje.util.backwards-compatible-utils :only [every-pred-m some-fn-m]] + + [midje.checkers.extended-falsehood])) + +(defn report-failure [actual checker-form result] + (as-data-laden-falsehood {:actual actual + :intermediate-results + [ [checker-form (user-friendly-falsehood result)]]})) + +(defn- ^{:testable true} + wrap-in-and-checking-form [checker-form to-wrap result-sym actual-sym] + `(let [~result-sym ( ~checker-form ~actual-sym)] + (if (extended-false? ~result-sym) + (report-failure ~actual-sym '~checker-form ~result-sym) + ~to-wrap))) + +(defmacro every-checker + "Combines multiple checkers into one checker that passes + when all component checkers pass. If one checker fails, + the remainder are not run." + [& checker-forms] + (let [actual-gensym (gensym "actual-result-") + check-result-gensym (gensym "check-result-") + checks-form (reduce (fn [to-wrap checker-form] + (wrap-in-and-checking-form checker-form to-wrap + check-result-gensym + actual-gensym)) + 'true + (reverse checker-forms))] + `(checker [~actual-gensym] ~checks-form))) + + +(defn- ^{:testable true} + wrap-in-or-checking-form [checker-form to-wrap actual-sym] + `(if (extended-true? (~checker-form ~actual-sym)) + true + ~to-wrap)) + +(defmacro some-checker + "Combines multiple checkers into one checker that passes + when any of the component checkers pass. If one checker + passes, the remainder are not run." + [& checker-forms] + (let [actual-gensym (gensym "actual-result-") + checks-form (reduce (fn [to-wrap checker-form] + (wrap-in-or-checking-form checker-form to-wrap + actual-gensym)) + 'false + (reverse checker-forms))] + `(checker [~actual-gensym] ~checks-form))) diff --git a/src/midje/checkers/defining.clj b/src/midje/checkers/defining.clj index 4b8931d36..c7eed5105 100644 --- a/src/midje/checkers/defining.clj +++ b/src/midje/checkers/defining.clj @@ -1,7 +1,6 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Various ways to define checkers."} - midje.checkers.defining) + midje.checkers.defining + (:use [midje.util.form-utils :only [pop-docstring pop-opts-map]])) (defn as-checker "Turns an already existing function into a checker. Checkers can be used @@ -13,9 +12,8 @@ (let [metavars (merge {:midje/checker true :arglists `'~arglists} (when docstring {:doc docstring}) attr-map) - name (vary-meta checker-name merge metavars) - checker-fn `(as-checker (fn ~checker-name ~@arglists+bodies))] - `(def ~name ~checker-fn))) + name (vary-meta checker-name merge metavars)] + `(def ~name (as-checker (fn ~checker-name ~@arglists+bodies))))) (working-with-arglists+bodies [checker-name docstring attr-map arglists+bodies] ;; Note: it's not strictly necessary to convert a single @@ -33,20 +31,11 @@ (defmacro defchecker "Like defn, but tags the variable created and the function it refers to as checkers. Checkers can be used to check fact results, as well as prerequisite calls." - {:arglists '([name docstring? attr-map? arglists arglists+bodies])} - [name & stuff] - (cond - (and (string? (first stuff)) (map? (second stuff))) - (working-with-arglists+bodies name (first stuff) (second stuff) (drop 2 stuff)) - - (map? (first stuff)) - (working-with-arglists+bodies name nil (first stuff) (rest stuff)) - - (string? (first stuff)) - (working-with-arglists+bodies name (first stuff) {} (rest stuff)) - - :else - (working-with-arglists+bodies name nil {} stuff)))) + {:arglists '([name docstring? attr-map? bindings+bodies])} + [name & args] + (let [[docstring more-args] (pop-docstring args) + [attr-map bindings+bodies] (pop-opts-map more-args)] + (working-with-arglists+bodies name docstring attr-map bindings+bodies)))) (defmacro checker "Creates an anonymous function tagged as a checker. Checkers can be used diff --git a/src/midje/checkers/deprecated.clj b/src/midje/checkers/deprecated.clj index a0efbcd93..c00a422a8 100644 --- a/src/midje/checkers/deprecated.clj +++ b/src/midje/checkers/deprecated.clj @@ -1,7 +1,7 @@ (ns ^{:doc "Deprecated checkers."} midje.checkers.deprecated (:use [midje.checkers.defining :only [defchecker]] - [midje.checkers.collection :only [just contains]])) + [midje.checkers.collection :only [just contains]])) ;; Note: checkers need to be exported in ../checkers.clj diff --git a/src/midje/checkers/extended_equality.clj b/src/midje/checkers/extended_equality.clj index cabc5f2cb..c0dabde48 100644 --- a/src/midje/checkers/extended_equality.clj +++ b/src/midje/checkers/extended_equality.clj @@ -1,30 +1,23 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "`=` extended for regular expressions, functions, etc."} midje.checkers.extended-equality - (:use [clojure.core.match :only [match]] - [midje.checkers.chatty :only [chatty-checker-falsehood?]] - [midje.util.form-utils :only [classic-map? pairs record? regex?]])) - -(defn extended-fn? [x] - (or (fn? x) - (= (class x) clojure.lang.MultiFn))) + (:use [midje.checkers.extended-falsehood :only [data-laden-falsehood?]] + [midje.util.form-utils :only [classic-map? extended-fn? pairs record? regex?]])) (defn extended-= [actual expected] (letfn [(evaluate-extended-fn [] (let [function-result (expected actual)] - (if (chatty-checker-falsehood? function-result) + (if (data-laden-falsehood? function-result) false function-result)))] - (try - (match [actual expected] - [(a :when chatty-checker-falsehood?) _] actual - [_ (e :when chatty-checker-falsehood?)] expected - [_ (e :when extended-fn?)] (evaluate-extended-fn) - [(a :when regex?) (e :when regex?)] (= (str actual) (str expected)) - [_ (e :when regex?)] (re-find expected actual) - [(a :when record?) (e :when classic-map?)] (= (into {} actual) expected) - [_ _] (= actual expected)) + (try + (cond + (data-laden-falsehood? actual) actual + (data-laden-falsehood? expected) expected + (extended-fn? expected) (evaluate-extended-fn) + (every? regex? [actual expected]) (= (str actual) (str expected)) + (regex? expected) (re-find expected actual) + (and (record? actual) (classic-map? expected)) (= (into {} actual) expected) + :else (= actual expected)) (catch Throwable ex false)))) (defn extended-list-= diff --git a/src/midje/checkers/extended_falsehood.clj b/src/midje/checkers/extended_falsehood.clj new file mode 100644 index 000000000..482eb26f7 --- /dev/null +++ b/src/midje/checkers/extended_falsehood.clj @@ -0,0 +1,23 @@ +(ns ^{:doc "Some failing checks carry additional information."} + midje.checkers.extended-falsehood) + +(defn as-data-laden-falsehood [value] + (vary-meta value assoc :midje/data-laden-falsehood true)) + +(defn data-laden-falsehood? [value] + (:midje/data-laden-falsehood (meta value))) + +(defn data-laden-falsehood-to-map [value] + (with-meta value {})) + +(defn extended-false? [value] + (or (not value) + (data-laden-falsehood? value))) + +(defn extended-true? [value] + (not (extended-false? value))) + +(defn user-friendly-falsehood [value] + (if (data-laden-falsehood? value) + false + value)) diff --git a/src/midje/checkers/simple.clj b/src/midje/checkers/simple.clj index 3e839df09..88e23364a 100644 --- a/src/midje/checkers/simple.clj +++ b/src/midje/checkers/simple.clj @@ -1,16 +1,16 @@ -;; -*- indent-tabs-mode: nil -*- - ;; Note: checkers need to be exported in ../checkers.clj (ns ^{:doc "Prepackaged functions that perform common checks."} midje.checkers.simple (:use [midje.checkers.defining :only [as-checker checker defchecker]] - [midje.checkers.extended-equality :only [extended-=]] - [midje.checkers.util :only [named-as-call]] - [midje.error-handling.exceptions :only [captured-throwable?]] - [midje.util.ecosystem :only [clojure-1-3? +M -M *M]] - [midje.util.form-utils :only [defalias def-many-methods pred-cond regex?]] - [midje.util.backwards-compatible-utils :only [every-pred-m some-fn-m]]) + [midje.checkers.extended-falsehood :only [extended-false?]] + [midje.checkers.extended-equality :only [extended-=]] + [midje.checkers.util :only [named-as-call]] + [midje.error-handling.exceptions :only [captured-throwable?]] + [midje.util.ecosystem :only [clojure-1-3? +M -M *M]] + [midje.util.form-utils :only [defalias def-many-methods pred-cond regex?]] + [midje.util.backwards-compatible-utils :only [every-pred-m some-fn-m]] + [clojure.algo.monads :only [domonad set-m]]) (:import [midje.error_handling.exceptions ICapturedThrowable])) (defchecker truthy @@ -35,7 +35,7 @@ (defchecker exactly "Checks for equality. Use to avoid default handling of functions." [expected] - (named-as-call 'exactly expected + (named-as-call "exactly" expected (checker [actual] (= expected actual)))) (letfn [(abs [n] @@ -57,8 +57,14 @@ ;; Concerning Throwables -(defmulti throws - "Checks for a thrown Throwable. +(letfn [(throwable-as-desired? [throwable desideratum] + (pred-cond desideratum + fn? (desideratum throwable) + (some-fn-m string? regex?) (extended-= (.getMessage ^Throwable throwable) desideratum) + class? (instance? desideratum throwable)))] + + (defchecker throws + "Checks for a thrown Throwable. The most common cases are: (fact (foo) => (throws IOException) @@ -74,27 +80,17 @@ Arguments can be in any order. Except for a class argument, they can be repeated. So, for example, you can write this: (fact (foo) => (throws #\"one part\" #\"another part\"))" - {:arglists '([& args])} - (fn [& args] - (set (for [arg args] - (pred-cond arg - fn? :predicate - (some-fn-m string? regex?) :message - class? :throwable ))))) - -(defmethod throws #{:message } [& expected-msgs] - (checker [^ICapturedThrowable wrapped-throwable] - (let [actual-msg (.getMessage ^Throwable (.throwable wrapped-throwable))] - (every? (partial extended-= actual-msg) expected-msgs)))) - -(defmethod throws #{:predicate} [& preds] - (checker [^ICapturedThrowable wrapped-throwable] - ((apply every-pred-m preds) (.throwable wrapped-throwable)))) - -(defmethod throws #{:throwable} [clazz] - (checker [^ICapturedThrowable wrapped-throwable] - (instance? clazz (.throwable wrapped-throwable)))) - -(def-many-methods throws [#{:throwable :predicate}, #{:message :predicate }, - #{:throwable :message}, #{:throwable :message :predicate}] [& args] - (as-checker (apply every-pred-m (map throws args)))) \ No newline at end of file + [& desiderata] + (checker [wrapped-throwable] + (if-not (instance? ICapturedThrowable wrapped-throwable) + false + (let [throwable (.throwable wrapped-throwable) + evaluations (map (partial throwable-as-desired? throwable) + desiderata) + failures (filter extended-false? evaluations)] + ;; It might be nice to return some sort of composite + ;; failure, but I bet just returning the first one is fine, + ;; especially since I expect people will use the class as + ;; the first desiderata. + (or (empty? failures) (first failures)))))) +) diff --git a/src/midje/checkers/util.clj b/src/midje/checkers/util.clj index bbd269239..32f5b2f96 100644 --- a/src/midje/checkers/util.clj +++ b/src/midje/checkers/util.clj @@ -1,4 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- (ns midje.checkers.util (:use [midje.util.form-utils :only [classic-map?]] [midje.util.object-utils :only [name-object]])) diff --git a/src/midje/cljs.clj b/src/midje/cljs.clj index 34e815335..ad10d7e1f 100644 --- a/src/midje/cljs.clj +++ b/src/midje/cljs.clj @@ -3,7 +3,7 @@ (ns midje.cljs (:require [cljs.closure :as cljsc] [cljs.repl :as repl] - [cljs.compiler :as comp] + [cljs.analyzer :as ana] [cljs.repl.rhino :as rhino])) (def ^:dynamic *env* (rhino/repl-env)) @@ -27,7 +27,7 @@ (cljs-eval '(+ 1 2)) (cljs-eval '(doubler 4) 'midje.cljs.basic)" [form & [cljs-ns]] - (let [env {:ns (@comp/namespaces comp/*cljs-ns*) + (let [env {:ns (@ana/namespaces ana/*cljs-ns*) :context :statement :locals {}}] (read-string diff --git a/src/midje/config.clj b/src/midje/config.clj new file mode 100644 index 000000000..f20d81139 --- /dev/null +++ b/src/midje/config.clj @@ -0,0 +1,8 @@ +(ns ^{:doc "Customizable configuration"} + midje.config) + +(def ^{:dynamic true + :doc "some doc here"} + *allow-default-prerequisites* false) + + diff --git a/src/midje/error_handling/background_validations.clj b/src/midje/error_handling/background_validations.clj index 4b155d0cd..af82ed755 100644 --- a/src/midje/error_handling/background_validations.clj +++ b/src/midje/error_handling/background_validations.clj @@ -1,63 +1,50 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Validation methods confirming the proper syntax of (against-)background macros."} midje.error-handling.background-validations - (:use - [clojure.pprint :only [cl-format]] - [midje.error-handling.validation-errors :only [simple-report-validation-error report-validation-error - validate when-valid]] - [midje.ideas.arrows :only [is-start-of-checking-arrow-sequence? take-arrow-sequence]] - [midje.ideas.background :only [seq-headed-by-setup-teardown-form?]] - [midje.ideas.prerequisites :only [metaconstant-prerequisite?]] - [midje.util.form-utils :only [named? pred-cond]] - [midje.util.backwards-compatible-utils :only [some-fn-m]])) - -(def #^:private valid-wrapping-targets #{:facts, :contents, :checks }) - -(letfn [(validate-state-description [[state-description wrapping-target expression :as form]] - (cond - (and (#{"after" "around"} (name state-description)) (not= 3 (count form))) - (report-validation-error form - (cl-format nil " In this form: ~A" form) - (cl-format nil "~A forms should look like: (~A :contents/:facts/:checks (your-code))" - (name state-description) (name state-description))) - - (and (= "before" (name state-description)) - (not= 3 (count form)) - (or (not= 5 (count form)) - (and (= 5 (count form)) + (:use [clojure.pprint :only [cl-format]] + [midje.error-handling.validation-errors :only [simple-validation-error-report-form + validation-error-report-form validate when-valid]] + [midje.ideas.arrows :only [start-of-checking-arrow-sequence? take-arrow-sequence]] + [midje.ideas.background :only [seq-headed-by-setup-teardown-form?]] + [midje.ideas.prerequisites :only [metaconstant-prerequisite?]] + [midje.util.form-utils :only [def-many-methods named? pred-cond]] + [midje.util.backwards-compatible-utils :only [some-fn-m]])) + +(def #^:private possible-wrapping-targets #{:facts, :contents, :checks }) +(def #^:private possible-state-descriptions #{"before" "after" "around"}) + +(def-many-methods validate ["before" "after" "around"] [[state-description wrapping-target expression :as form]] + (cond + (and (#{"after" "around"} (name state-description)) (not= 3 (count form))) + (validation-error-report-form form + (cl-format nil " In this form: ~A" form) + (cl-format nil "~A forms should look like: (~A :contents/:facts/:checks (your-code))" + (name state-description) (name state-description))) + + (and (= "before" (name state-description)) + (not= 3 (count form)) + (or (not= 5 (count form)) + (and (= 5 (count form)) (not= :after (nth form 3))))) - (report-validation-error form - (cl-format nil " In this form: ~A" form) - "before forms should look like: (before :contents/:facts/:checks (your-code)) or " - "(before :contents/:facts/:checks (your-code) :after (final-code))") - - ((complement valid-wrapping-targets) wrapping-target) - (report-validation-error form - (cl-format nil " In this form: ~A" form) - (cl-format nil "The second element (~A) should be one of: :facts, :contents, or :checks" - wrapping-target)) - - :else (rest form)))] - - (defmethod validate "before" [forms] - (validate-state-description forms)) - - (defmethod validate "after" [forms] - (validate-state-description forms)) + (validation-error-report-form form + (cl-format nil " In this form: ~A" form) + "before forms should look like: (before :contents/:facts/:checks (your-code)) or " + "(before :contents/:facts/:checks (your-code) :after (final-code))") - (defmethod validate "around" [forms] - (validate-state-description forms))) + ((complement possible-wrapping-targets) wrapping-target) + (validation-error-report-form form + (cl-format nil " In this form: ~A" form) + (cl-format nil "The second element (~A) should be one of: :facts, :contents, or :checks" + wrapping-target)) -(def #^:private valid-state-descriptions #{"before" "after" "around"}) + :else (rest form))) -(letfn [(valid-state-descriptions+fakes? [forms] +(letfn [(possible-state-descriptions+fakes? [forms] (loop [in-progress forms] (pred-cond in-progress empty? true - (some-fn-m is-start-of-checking-arrow-sequence? metaconstant-prerequisite?) + (some-fn-m start-of-checking-arrow-sequence? metaconstant-prerequisite?) (let [arrow-seq (take-arrow-sequence in-progress)] (recur (drop (count arrow-seq) in-progress))) @@ -68,23 +55,23 @@ (state-description? [form] (and (sequential? form) - (valid-state-descriptions (name (first form))))) ] + (contains? possible-state-descriptions (name (first form))))) ] (defmethod validate "against-background" [[_against-background_ state-descriptions+fakes & _body_ :as form]] (cond (< (count form) 3) - (simple-report-validation-error form + (simple-validation-error-report-form form "You need a minimum of three elements to an against-background form:") (vector? state-descriptions+fakes) (when-valid (filter state-description? state-descriptions+fakes) (pred-cond state-descriptions+fakes empty? - (simple-report-validation-error form + (simple-validation-error-report-form form "You didn't enter any background fakes or wrappers:") - (comp not valid-state-descriptions+fakes?) - (simple-report-validation-error form + (comp not possible-state-descriptions+fakes?) + (simple-validation-error-report-form form "Badly formatted against-background fakes:") :else @@ -96,7 +83,7 @@ (rest form)) :else - (simple-report-validation-error form + (simple-validation-error-report-form form "Malformed against-background. against-background requires" "at least one background fake or background wrapper: " ))) @@ -104,10 +91,10 @@ (when-valid (filter state-description? state-descriptions+fakes) (pred-cond state-descriptions+fakes empty? - (simple-report-validation-error form "You didn't enter any background fakes or wrappers:") + (simple-validation-error-report-form form "You didn't enter any background fakes or wrappers:") - (comp not valid-state-descriptions+fakes?) - (simple-report-validation-error form "Badly formatted background fakes:") + (comp not possible-state-descriptions+fakes?) + (simple-validation-error-report-form form "Badly formatted background fakes:") :else state-descriptions+fakes)))) \ No newline at end of file diff --git a/src/midje/error_handling/exceptions.clj b/src/midje/error_handling/exceptions.clj index bec6bdd12..796c19cf3 100644 --- a/src/midje/error_handling/exceptions.clj +++ b/src/midje/error_handling/exceptions.clj @@ -1,12 +1,10 @@ (ns ^{:doc "Functions for Midje to deal elegantly with exceptions."} midje.error-handling.exceptions (:use [clojure.string :only [join]] + [midje.util.ecosystem :only [line-separator]] [midje.util.colorize :only [colorize-choice]])) -(def #^:private line-separator (System/getProperty "line.separator")) - - ;;; Creating (defn user-error @@ -56,4 +54,7 @@ (CapturedThrowable. ex)) (defn captured-throwable? [x] - (instance? CapturedThrowable x)) \ No newline at end of file + (instance? CapturedThrowable x)) + +(defn captured-message [ex] + (.getMessage ^Throwable (throwable ex))) diff --git a/src/midje/error_handling/semi_sweet_validations.clj b/src/midje/error_handling/semi_sweet_validations.clj index 4aa03ec9b..15d35592b 100644 --- a/src/midje/error_handling/semi_sweet_validations.clj +++ b/src/midje/error_handling/semi_sweet_validations.clj @@ -1,50 +1,58 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Validation methods confirming the proper syntax of semi-sweet macros."} midje.error-handling.semi-sweet-validations - (:use - [clojure.pprint :only [cl-format]] - [midje.error-handling.validation-errors :only [report-validation-error validate]] - [midje.util.namespace :only [matches-symbols-in-semi-sweet-or-sweet-ns?]] - [midje.ideas.metaconstants :only [metaconstant-symbol?]] - [midje.ideas.arrow-symbols :only [=contains=>]])) - -(letfn [(compiler-will-inline-fn? [fn] - (contains? (meta (resolve fn)) :inline ))] + (:use [clojure.pprint :only [cl-format]] + [midje.error-handling.validation-errors :only [validation-error-report-form validate]] + [midje.util.namespace :only [matches-symbols-in-semi-sweet-or-sweet-ns?]] + [midje.ideas.metaconstants :only [metaconstant-symbol?]] + [midje.ideas.arrow-symbols :only [=contains=>]] + [midje.util.form-utils :only [fnref-var-object]])) + +(letfn [(compiler-will-inline-fn? [fnref] + (contains? (meta (fnref-var-object fnref)) :inline)) + (exposed-testable? [fnref] + (contains? (meta (fnref-var-object fnref)) :testable))] (defmethod validate "fake" [[_fake_ & fake-form :as form]] (let [funcall (first fake-form)] (cond (not (list? funcall)) - (report-validation-error + (validation-error-report-form form "The left-hand-side of a prerequisite must look like a function call." (cl-format nil "`~S` doesn't." funcall)) (compiler-will-inline-fn? (first funcall)) - (report-validation-error + (validation-error-report-form form (cl-format nil "You cannot override the function `~S`: it is inlined by the Clojure compiler." (first funcall))) - :else - fake-form)))) + (exposed-testable? (first funcall)) + (validation-error-report-form + form + "A prerequisite cannot use a symbol exposed via `expose-testables` or `testable-privates`." + (cl-format nil "Instead, use the var directly: #'~S/~S" + (-> (first funcall) fnref-var-object meta :ns ns-name) + (first funcall))) + + :else + form)))) -(defmethod validate "data-fake" [[header metaconstant arrow hash & remainder :as form]] +(defmethod validate "data-fake" [[_data-fake_ metaconstant arrow hash & remainder :as form]] (cond (not (metaconstant-symbol? metaconstant)) - (report-validation-error + (validation-error-report-form form "You seem to be assigning values to a metaconstant, but there's no metaconstant.") (not= (matches-symbols-in-semi-sweet-or-sweet-ns? '(arrow) =contains=>)) - (report-validation-error + (validation-error-report-form form "Assigning values to a metaconstant requires =contains=>") :else - (rest form))) + form)) (defmethod validate "expect" [form] (if (< (count form) 4) - (report-validation-error form + (validation-error-report-form form (cl-format nil " This form: ~A" form) (cl-format nil "Doesn't match: (~A => [*])" (first form))) (rest form))) diff --git a/src/midje/error_handling/validation_errors.clj b/src/midje/error_handling/validation_errors.clj index e36a7c042..07b51e74a 100644 --- a/src/midje/error_handling/validation_errors.clj +++ b/src/midje/error_handling/validation_errors.clj @@ -1,72 +1,55 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Code for identifying invalid Midje syntax. Includes control flow macros, validation error creation, etc."} midje.error-handling.validation-errors - (:use - [clojure.algo.monads :only [defmonad domonad with-monad m-lift]] - [clojure.test :only [report]] - [midje.internal-ideas.file-position :only [form-position]] - [midje.util.form-utils :only [named?]] - [utilize.seq :only (find-first)])) + (:use [clojure.algo.monads :only [defmonad domonad]] + [clojure.test :only [report]] + [midje.internal-ideas.file-position :only [form-position]] + [midje.util.form-utils :only [named?]])) ;; Making validation errors -(defn- ^{:testable true } as-validation-error [form] - (vary-meta form assoc :midje-validation-error true)) +(defn- ^{:testable true} as-validation-error [form] + (vary-meta form assoc :midje/validation-error true)) (defn validation-error-form? [form] - (:midje-validation-error (meta form))) + (:midje/validation-error (meta form))) -(defn report-validation-error [form & notes] +(defn validation-error-report-form [form & notes] (as-validation-error `(report {:type :validation-error :notes '~notes :position '~(form-position form)}))) -(defn simple-report-validation-error [form & notes] - (apply report-validation-error form (conj (vec notes) (pr-str form)))) - - -;; Special validation control flow macros - -(defmonad midje-maybe-m - "Monad describing form processing with possible failures. Failure - is represented by any form with metadata :midje-validation-error" - [m-result identity - m-bind (fn [mv f] (if (validation-error-form? mv) mv (f mv))) - ]) +(defn simple-validation-error-report-form [form & notes] + (apply validation-error-report-form form (conj (vec notes) (pr-str form)))) -(defmacro valid-let [let-vector & body] - `(domonad midje-maybe-m ~let-vector ~@body)) -(defn- ^{:testable true } spread-validation-error [collection] - (or (find-first validation-error-form? collection) - collection)) +;; Validation control flow macros -;; This is a pretty dubious addition. Not using it now - found -;; a better way - but might need it later. -(defmacro with-valid [symbol & body] - `(let [~symbol (#'spread-validation-error ~symbol)] - (if (validation-error-form? ~symbol) - (eval ~symbol) - (do ~@body)))) +(defmonad validate-m + "Monad describing form processing with possible failures. Failure + is represented by any form with metadata :midje/validation-error" + [m-result identity + m-bind (fn [form f] + (if (validation-error-form? form) form (f form))) ]) -(defmacro when-valid [validatable-form-or-forms & body-to-execute-if-valid] - `(let [result# (validate ~validatable-form-or-forms)] - (if (validation-error-form? result#) - result# - (do ~@body-to-execute-if-valid)))) +(defmacro when-valid [validatable-form & body-to-execute-if-valid] + `(domonad validate-m [_# (validate ~validatable-form)] + ~@body-to-execute-if-valid)) ;; Validate -(defmulti validate (fn [form & options] +(defmulti validate (fn [form & _options_] (if (named? (first form)) (name (first form)) :validate-seq))) -(defmethod validate :validate-seq [form & options] - (spread-validation-error (map validate form))) +(defmethod validate :validate-seq [seq-of-forms & _options_] + (let [first-validation-error (->> seq-of-forms + (map validate) + (filter validation-error-form?) + first)] + (or first-validation-error seq-of-forms))) -(defmethod validate :default [form & options] (rest form)) \ No newline at end of file +(defmethod validate :default [form & _options_] (rest form)) \ No newline at end of file diff --git a/src/midje/ideas/arrows.clj b/src/midje/ideas/arrows.clj index 7af774746..62ed08084 100644 --- a/src/midje/ideas/arrows.clj +++ b/src/midje/ideas/arrows.clj @@ -1,10 +1,8 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Arrows either indicate a form of expected result, or a stubbed prerequisite value."} midje.ideas.arrows (:use midje.ideas.arrow-symbols [clojure.set :only [union]] - [midje.util treelike namespace]) + [midje.util form-utils treelike namespace]) (:require [clojure.zip :as zip])) ;; Arrow groupings @@ -13,16 +11,22 @@ (def fake-arrows #{=> =contains=> =streams=> =throws=>}) (def all-arrows (union expect-arrows fake-arrows)) +(defn leaf-expect-arrows [nested-form] + (let [named-form-leaves (map name (filter named? (flatten nested-form)))] + (filter expect-arrows named-form-leaves))) + +(defn leaves-contain-arrow? [nested-form] + (not (empty? (leaf-expect-arrows nested-form)))) ;; Recognizing -(defmulti is-start-of-checking-arrow-sequence? tree-variant) +(defmulti start-of-checking-arrow-sequence? tree-variant) -(defmethod is-start-of-checking-arrow-sequence? :zipper [loc] +(defmethod start-of-checking-arrow-sequence? :zipper [loc] (and (zip/right loc) (matches-symbols-in-semi-sweet-or-sweet-ns? expect-arrows (zip/right loc)))) -(defmethod is-start-of-checking-arrow-sequence? :form [form] +(defmethod start-of-checking-arrow-sequence? :form [form] (and (sequential? form) (matches-symbols-in-semi-sweet-or-sweet-ns? expect-arrows (second form)))) diff --git a/src/midje/ideas/background.clj b/src/midje/ideas/background.clj index bf73fd579..1ade003a2 100644 --- a/src/midje/ideas/background.clj +++ b/src/midje/ideas/background.clj @@ -1,19 +1,17 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Code to be run before, after or around facts. Also, prerequisites that pertain to a group of facts."} midje.ideas.background - (:use [midje.ideas.arrows :only [is-start-of-checking-arrow-sequence? take-arrow-sequence]] - [midje.ideas.metaconstants :only [define-metaconstants]] - [midje.ideas.prerequisites :only [metaconstant-prerequisite? prerequisite-to-fake]] - [midje.internal-ideas.fakes :only [fake? tag-as-background-fake with-installed-fakes]] - [midje.internal-ideas.wrapping :only [for-wrapping-target? with-wrapping-target]] - [midje.util.form-utils :only [first-named? map-first pred-cond separate-by - symbol-named? translate-zipper]] - [midje.util.laziness :only [eagerly]] - [midje.util.thread-safe-var-nesting :only [namespace-values-inside-out - with-pushed-namespace-values]] - [utilize.seq :only [separate]]) + (:use [midje.ideas.arrows :only [start-of-checking-arrow-sequence? take-arrow-sequence]] + [midje.ideas.metaconstants :only [define-metaconstants]] + [midje.ideas.prerequisites :only [metaconstant-prerequisite? prerequisite-to-fake]] + [midje.internal-ideas.fakes :only [fake? tag-as-background-fake with-installed-fakes]] + [midje.internal-ideas.wrapping :only [for-wrapping-target? with-wrapping-target]] + [midje.util.form-utils :only [first-named? map-first pred-cond separate-by + symbol-named? translate-zipper]] + [midje.util.laziness :only [eagerly]] + [midje.util.thread-safe-var-nesting :only [namespace-values-inside-out + with-pushed-namespace-values]] + [utilize.seq :only [separate]]) (:require [clojure.zip :as zip] [midje.util.unify :as unify])) @@ -43,7 +41,7 @@ "Code to run before a given wrapping target (:facts, :contents, :checks). Can take an optional keyword argument :after, for any code to run afterward. Used with background and against-background" - [wrapping-target before-form & {:keys [after]}] + [_wrapping-target_ before-form & {:keys [after]}] (ensure-correct-form-variable `(try ~before-form ?form @@ -52,7 +50,7 @@ (defmacro after "Code to run after a given wrapping target (:facts, :contents, :checks). Used with background and against-background" - [wrapping-target after-form] + [_wrapping-target_ after-form] (ensure-correct-form-variable `(try ?form (finally ~after-form)))) (defmacro around @@ -65,7 +63,7 @@ (print a))) Used with background and against-background" - [wrapping-target around-form] + [_wrapping-target_ around-form] (ensure-correct-form-variable around-form))) (defn seq-headed-by-setup-teardown-form? [forms] @@ -80,21 +78,21 @@ empty? expanded - is-start-of-checking-arrow-sequence? + start-of-checking-arrow-sequence? (let [arrow-seq (take-arrow-sequence in-progress)] (recur (conj expanded (-> arrow-seq prerequisite-to-fake tag-as-background-fake)) - (drop (count arrow-seq) in-progress))) + (drop (count arrow-seq) in-progress))) metaconstant-prerequisite? (let [arrow-seq (take-arrow-sequence in-progress)] (recur (conj expanded (-> arrow-seq prerequisite-to-fake)) - (drop (count arrow-seq) in-progress))) + (drop (count arrow-seq) in-progress))) seq-headed-by-setup-teardown-form? (recur (conj expanded (first in-progress)) - (rest in-progress))))) + (rest in-progress))))) -(defn- ^{:testable true } state-wrapper [[_before-after-or-around_ wrapping-target & _ :as state-description]] +(defn- ^{:testable true } state-wrapper [[_before-after-or-around_ wrapping-target & _ :as state-description]] (with-wrapping-target (macroexpand-1 (map-first #(symbol "midje.ideas.background" (name %)) state-description)) wrapping-target)) @@ -122,9 +120,9 @@ (defn against-background-contents-wrappers [[_against-background_ background-forms & _]] (filter (for-wrapping-target? :contents ) (background-wrappers background-forms))) -(defn against-background-children-wrappers [[_against-background_ background-forms & _]] +(defn against-background-facts-and-checks-wrappers [[_against-background_ background-forms & _]] (remove (for-wrapping-target? :contents ) (background-wrappers background-forms))) (defn surround-with-background-fakes [forms] `(with-installed-fakes (background-fakes) - (do ~@forms))) \ No newline at end of file + ~@forms)) \ No newline at end of file diff --git a/src/midje/ideas/facts.clj b/src/midje/ideas/facts.clj index 1a9b4bb85..cf671e0b7 100644 --- a/src/midje/ideas/facts.clj +++ b/src/midje/ideas/facts.clj @@ -1,7 +1,7 @@ (ns ^{:doc "Facts are the core abstraction of Midje."} midje.ideas.facts - (:use [midje.error-handling.validation-errors :only [simple-report-validation-error validate]] - [midje.util.namespace :only [is-semi-sweet-keyword?]] + (:use [midje.error-handling.validation-errors :only [simple-validation-error-report-form validate when-valid]] + [midje.util.namespace :only [semi-sweet-keyword?]] [midje.internal-ideas.fakes :only [unfold-fakes]] [midje.internal-ideas.expect :only [expect? @@ -13,46 +13,46 @@ with-additional-wrappers forms-to-wrap-around]] [midje.util.debugging :only [nopret]] - [midje.ideas.prerequisites :only [is-head-of-form-providing-prerequisites? + [midje.ideas.prerequisites :only [head-of-form-providing-prerequisites? insert-prerequisites-into-expect-form-as-fakes]] - [midje.ideas.arrows :only [is-start-of-checking-arrow-sequence? expect-arrows]] + [midje.ideas.arrows :only [start-of-checking-arrow-sequence? leaves-contain-arrow?]] [midje.ideas.background :only [surround-with-background-fakes body-of-against-background against-background-contents-wrappers - against-background-children-wrappers + against-background-facts-and-checks-wrappers against-background?]] [midje.ideas.metaconstants :only [define-metaconstants]] - [midje.util.form-utils :only [first-named? translate-zipper preserve-type quoted? - pred-cond reader-line-number named?]] + [midje.util.form-utils :only [def-many-methods first-named? translate-zipper pop-docstring + preserve-type quoted? pred-cond reader-line-number named?]] [midje.util.laziness :only [eagerly]] [midje.util.zip :only [skip-to-rightmost-leaf]] - [midje.error-handling.validation-errors :only [when-valid]]) + [swiss-arrows.core :only [-<>]]) (:require [clojure.zip :as zip]) - (:require [midje.internal-ideas.report :as report])) + (:require [midje.ideas.reporting.report :as report])) (declare midjcoexpand) (defn fact? [form] (or (first-named? form "fact") (first-named? form "facts"))) -(def future-fact-variant-names [ "future-fact" - "future-facts" - "pending-fact" - "pending-facts" - "incipient-fact" - "incipient-facts" - "antiterminologicaldisintactitudinarian-fact" - "antiterminologicaldisintactitudinarian-facts" ]) +(def future-prefixes ["future-" + "pending-" + "incipient-" + "antiterminologicaldisintactitudinarian-"]) + +(def future-fact-variant-names (for [prefix future-prefixes + fact-or-facts ["fact" "facts"]] + (str prefix fact-or-facts))) (defn future-fact? [form] (some (partial first-named? form) future-fact-variant-names )) -(defn future-fact* [[_name_ doc-string? & _rest_ :as forms]] +(defn future-fact* [[_name_ & args :as forms]] (let [lineno (reader-line-number forms) - description (when (string? doc-string?) doc-string?)] + [description _] (pop-docstring args)] `(within-fact-context ~description (clojure.test/report {:type :future-fact - :description (midje.internal-ideas.fact-context/nested-fact-description) + :description @midje.internal-ideas.fact-context/nested-descriptions :position (midje.internal-ideas.file-position/line-number-known ~lineno)})))) (defn to-semi-sweet @@ -61,35 +61,35 @@ 2) (provided ...) become fakes inserted into preceding expect." [multi-form] (translate-zipper multi-form - is-start-of-checking-arrow-sequence? + start-of-checking-arrow-sequence? wrap-with-expect__then__at-rightmost-expect-leaf - is-head-of-form-providing-prerequisites? + head-of-form-providing-prerequisites? insert-prerequisites-into-expect-form-as-fakes - is-semi-sweet-keyword? + semi-sweet-keyword? skip-to-rightmost-leaf)) -(letfn [(expand-against-background [form wrappers] - (with-additional-wrappers wrappers (midjcoexpand form)))] +(defn midjcoexpand + "Descend form, macroexpanding *only* midje forms and placing background wrappers where appropriate." + [form] + (pred-cond form + already-wrapped? form + quoted? form + future-fact? (macroexpand form) + against-background? (when-valid form + (-<> form + body-of-against-background + midjcoexpand + (with-additional-wrappers (against-background-facts-and-checks-wrappers form) <>) + (multiwrap <> (against-background-contents-wrappers form)))) - (defn midjcoexpand - "Descend form, macroexpanding *only* midje forms and placing background wrappers where appropriate." - [form] - (pred-cond form - already-wrapped? form - quoted? form - future-fact? (macroexpand form) - against-background? (when-valid form - (-> (body-of-against-background form) - (expand-against-background (against-background-children-wrappers form)) - (multiwrap (against-background-contents-wrappers form)))) - - expect? (multiwrap form (forms-to-wrap-around :checks )) - fact? (multiwrap (midjcoexpand (macroexpand form)) - (forms-to-wrap-around :facts )) - sequential? (preserve-type form (eagerly (map midjcoexpand form))) - :else form))) + expect? (multiwrap form (forms-to-wrap-around :checks )) + fact? (-<> form + macroexpand + (multiwrap <> (forms-to-wrap-around :facts))) + sequential? (preserve-type form (eagerly (map midjcoexpand form))) + :else form)) (defn complete-fact-transformation [description forms] (let [form-to-run (-> forms @@ -102,16 +102,9 @@ (define-metaconstants form-to-run) (report/form-providing-friendly-return-value `(within-fact-context ~description ~form-to-run)))) - -(letfn [(validate-fact [[fact & _ :as form]] - (let [named-form-leaves (map name (filter named? (flatten (rest form))))] - (if (not-any? expect-arrows named-form-leaves) - (simple-report-validation-error form - (format "There is no arrow in your %s form:" (name fact))) - (rest form))))] - - (defmethod validate "fact" [form] - (validate-fact form)) - (defmethod validate "facts" [form] - (validate-fact form))) \ No newline at end of file +(def-many-methods validate ["fact" "facts"] [[fact-or-facts & args :as form]] + (if-not (leaves-contain-arrow? (rest form)) + (simple-validation-error-report-form form + (format "There is no arrow in your %s form:" (name fact-or-facts))) + (pop-docstring args))) \ No newline at end of file diff --git a/src/midje/ideas/formulas.clj b/src/midje/ideas/formulas.clj new file mode 100644 index 000000000..ca45f7e5f --- /dev/null +++ b/src/midje/ideas/formulas.clj @@ -0,0 +1,123 @@ +(ns ^{:doc "EXPERIMENTAL, thus subject to change. Midje's special blend of generative-style testing."} + midje.ideas.formulas + (:use [midje.util.form-utils :only [first-named? named? pop-docstring pop-opts-map]] + [midje.error-handling.validation-errors :only [simple-validation-error-report-form validate-m validate]] + [midje.ideas.prerequisites :only [head-of-form-providing-prerequisites?]] + [midje.ideas.arrows :only [leaf-expect-arrows leaves-contain-arrow?]] + [midje.ideas.facts :only [future-prefixes]] + [clojure.algo.monads :only [domonad]] + [clojure.string :only [join]] + [clojure.walk :only [prewalk]])) + +;; Formulas work by running up to *num-trials* trials per formula. If there is a failure, +;; it will then shrink the values that caused that particular failure. + +(def ^{:doc "The number of trials generated per formula." + :dynamic true} + *num-trials* 100) + +(set-validator! #'*num-trials* + (fn [new-val] + (if (pos? new-val) + true + (throw (RuntimeException. (str "*num-trials* must be an integer 1 or greater. You tried to set it to: " new-val)))))) + + +(defn shrink [& _args] []) + +(defn- formula-fact [docstring body] + `(midje.sweet/fact ~docstring + ~@body :formula :formula-in-progress)) + +(defmacro shrink-failure-case [docstring binding-leftsides failed-binding-rightsides body] + `(loop [shrunk-binding-rightsides# (map midje.ideas.formulas/shrink ~failed-binding-rightsides)] + (when (and (not-any? empty? shrunk-binding-rightsides#) + (let [~binding-leftsides (map first shrunk-binding-rightsides#)] + ~(formula-fact docstring body))) + (recur (map rest shrunk-binding-rightsides#))))) + +(defn- deconstruct-formula-args [args] + (let [[docstring? more-args] (pop-docstring args) + [opts-map [bindings & body]] (pop-opts-map more-args)] + [docstring? opts-map bindings body])) + +(defmacro formula + "ALPHA/EXPERIMENTAL (subject to change) - Generative-style fact macro. + + Ex. (formula \"any two strings concatenated begins with the first\" + [a (gen/string) b (gen/string)] + (str a b) => (has-prefix a)) + + Currently, we recommend you use generators from test.generative.generators. + (However we are in the works to create a library of generators with shrinkers, so + don't get too attached to test.generative) + + opts-map keys: + + :num-trials - Used to override the number of trials for this formula only. + This is higher precedence than *num-trials* + Must be set to a number 1 or greater. + + The midje.ideas.formulas/*num-trials* dynamic var determines + how many facts are generated per formula." + {:arglists '([docstring? opts-map? bindings & body])} + [& _args] + (domonad validate-m [[docstring? opts-map bindings body] (validate &form) + fact (formula-fact docstring? body) + conclusion-signal `(midje.sweet/fact + :ignored midje.sweet/=> :ignored + :formula :formula-conclude )] + + `(try + (loop [num-trials-left# (or (:num-trials ~opts-map) midje.ideas.formulas/*num-trials*)] + (when (pos? num-trials-left#) + (let [binding-rightsides# ~(vec (take-nth 2 (rest bindings))) + ~(vec (take-nth 2 bindings)) binding-rightsides#] + (if ~fact + (recur (dec num-trials-left#)) + (shrink-failure-case ~docstring? + ~(vec (take-nth 2 bindings)) + binding-rightsides# + ~body))))) + (finally + ~conclusion-signal)))) + +(defmacro with-num-trials [num-trials & formulas] + `(binding [midje.ideas.formulas/*num-trials* ~num-trials] + ~@formulas)) + +(def future-formula-variant-names (map #(str % "formula") future-prefixes)) + +(defn- check-part-of [form] + (prewalk (fn [form] + (if (some (partial first-named? form) ["against-background" "background" "provided"]) + '() + form)) + form)) + +(defmethod validate "formula" [[_formula_ & args :as form]] + (let [[docstring? opts-map bindings body] (deconstruct-formula-args args) + invalid-keys (remove (partial = :num-trials) (keys opts-map))] + (cond (not (leaves-contain-arrow? (check-part-of args))) + (simple-validation-error-report-form form "There is no expection in your formula form:") + + (> (count (leaf-expect-arrows (check-part-of args))) 1) + (simple-validation-error-report-form form "There are too many expections in your formula form:") + + (or (not (vector? bindings)) + (odd? (count bindings)) + (< (count bindings) 2)) + (simple-validation-error-report-form form "Formula requires bindings to be an even numbered vector of 2 or more:") + + (some #(and (named? %) (= "background" (name %))) (flatten args)) + (simple-validation-error-report-form form "background cannot be used inside of formula") + + (not (empty? invalid-keys)) + (simple-validation-error-report-form form (format "Invalid keys (%s) in formula's options map. Valid keys are: :num-trials" (join ", " invalid-keys))) + + (and (:num-trials opts-map) + (not (pos? (:num-trials opts-map)))) + (simple-validation-error-report-form form (str ":num-trials must be an integer 1 or greater. You tried to set it to: " (:num-trials opts-map))) + + :else + [docstring? opts-map bindings body]))) \ No newline at end of file diff --git a/src/midje/ideas/metaconstants.clj b/src/midje/ideas/metaconstants.clj index f5031ab1f..f71f277ce 100644 --- a/src/midje/ideas/metaconstants.clj +++ b/src/midje/ideas/metaconstants.clj @@ -1,9 +1,7 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "A notation that avoids confusion between what’s essential about data and what’s accidental. A stand in for constant data."} midje.ideas.metaconstants - (:use [midje.util.form-utils :only [quoted? translate-zipper]] + (:use [midje.util.form-utils :only [translate-zipper fnref-symbol]] [midje.util.zip :only [skip-down-then-rightmost-leaf]] [midje.util.thread-safe-var-nesting :only [unbound-marker]] [midje.error-handling.exceptions :only [user-error]] @@ -12,16 +10,22 @@ (:require [clojure.zip :as zip] [midje.util.ecosystem :as ecosystem])) +(defn- normalized-metaconstant* [dot-or-dash mc-symbol] + (let [re (re-pattern (str "^" dot-or-dash "+(.+?)" dot-or-dash "+$")) + three-dots-or-dashes (apply str (repeat 3 dot-or-dash))] + (-?>> mc-symbol + name + (re-matches re) + second + (format (str three-dots-or-dashes "%s" three-dots-or-dashes))))) + (defn- normalized-metaconstant - "Turns '..m. to \"...m...\", or nil if mc-symbol isn't a valid metaconstant symbol" + "Turns '..m. to \"...m...\", '--m- to \"---m---\", or to + nil if mc-symbol isn't a valid metaconstant symbol" [mc-symbol] - (let [normalize (fn [re metaconstant-format mc-symbol] - (-?>> mc-symbol name (re-matches re) second (format metaconstant-format))) - - dot-metaconstant (partial normalize #"^\.+(.+?)\.+$" "...%s...") - dash-metaconstant (partial normalize #"^-+(.+?)-+$" "---%s---")] - - (->> mc-symbol ((juxt dot-metaconstant dash-metaconstant)) (find-first identity)))) + (->> mc-symbol + ((juxt (partial normalized-metaconstant* "\\.") (partial normalized-metaconstant* "-"))) + (find-first identity))) (defn metaconstant-symbol? [x] (and (symbol? x) @@ -30,7 +34,7 @@ (deftype Metaconstant [name ^clojure.lang.Associative storage] Object (toString [this] - (.toString (.name this))) + (str (.name this))) (equals [^Metaconstant this that] (if (instance? (class this) that) (= (normalized-metaconstant (.name this)) (normalized-metaconstant (.name ^Metaconstant that))) @@ -85,6 +89,9 @@ "If you have a compelling case for equality, please create an issue:" ecosystem/issues-url))))) +(defn merge-metaconstants [^Metaconstant mc1 ^Metaconstant mc2] + (Metaconstant. (.name mc1) (merge (.storage mc1) (.storage mc2)))) + (defmethod print-method Metaconstant [^Metaconstant o ^java.io.Writer w] (print-method (.name o) w)) @@ -103,11 +110,7 @@ (defn metaconstant-for-form [[function-symbol & _ :as inner-form]] (let [swap-fn (fn [current-value function-symbol] - (if-let [cur-val (current-value function-symbol)] - (assoc current-value function-symbol (inc cur-val)) - (assoc current-value function-symbol 1))) + (assoc current-value function-symbol ((fnil inc 0) (current-value function-symbol)))) number ((swap! *metaconstant-counts* swap-fn function-symbol) function-symbol)] - (symbol (format "...%s-value-%s..." (name function-symbol) number)))) - - + (symbol (format "...%s-value-%s..." (name (fnref-symbol function-symbol)) number)))) diff --git a/src/midje/ideas/prerequisites.clj b/src/midje/ideas/prerequisites.clj index 2ed2a03e2..fff6dd573 100644 --- a/src/midje/ideas/prerequisites.clj +++ b/src/midje/ideas/prerequisites.clj @@ -11,7 +11,7 @@ tack-on__then__at-rightmost-expect-leaf]]) (:require [clojure.zip :as zip])) -(defn is-head-of-form-providing-prerequisites? [loc] +(defn head-of-form-providing-prerequisites? [loc] (matches-symbols-in-semi-sweet-or-sweet-ns? '(provided) loc)) (defn metaconstant-prerequisite? [[lhs arrow rhs & overrides :as fake-body]] @@ -32,7 +32,7 @@ (map prerequisite-to-fake fake-bodies))) (defn delete_prerequisite_form__then__at-previous-full-expect-form [loc] - (assert (is-head-of-form-providing-prerequisites? loc)) + (assert (head-of-form-providing-prerequisites? loc)) (-> loc zip/up zip/remove up-to-full-expect-form)) (defn insert-prerequisites-into-expect-form-as-fakes [loc] diff --git a/src/midje/ideas/reporting/junit_xml_format.clj b/src/midje/ideas/reporting/junit_xml_format.clj new file mode 100644 index 000000000..f9b6342ac --- /dev/null +++ b/src/midje/ideas/reporting/junit_xml_format.clj @@ -0,0 +1,19 @@ +(ns ^{:doc "Reports as JUnit compatible XML."} + midje.ideas.reporting.junit-xml-format) + + +(defmulti report-junit-xml :type + ) + +(defmethod report-junit-xml :default [m] + ;; code to create XML for the different types of failure report maps + ;; see string-format.clj for inspiration + ) + +(defn report-junit-xml-summary [exit-after-tests?] + ;; code to create XML for summary of failed facts + ;; see string-format.clj for inspiration + ) + +(def junit-xml-format-config { :single-fact-fn report-junit-xml + :summary-fn report-junit-xml-summary }) \ No newline at end of file diff --git a/src/midje/ideas/reporting/report.clj b/src/midje/ideas/reporting/report.clj new file mode 100644 index 000000000..ec37a8058 --- /dev/null +++ b/src/midje/ideas/reporting/report.clj @@ -0,0 +1,68 @@ +(ns ^{:doc "Renders the various reported fact evaluation results."} + midje.ideas.reporting.report + (:use clojure.test + [midje.ideas.reporting.string-format :only [report-strings-format-config]] + [midje.ideas.reporting.junit-xml-format :only [junit-xml-format-config]])) + + +;;; Configuration + +;; *report-format-config* expects to be bound to a map like: +;; { :single-fact-fn (fn [reported-map] ...) ; handle each failing fact +;; :summary-fn (fn [exit-after-tests?] ...) ; output a summary of successes/failures etc +;; } + +(def ^{:dynamic true + :doc "Midje will report the fact failures and summary using the + fns in the configuration map bound to this var."} + *report-format-config* report-strings-format-config) + +(def formatters { "default" report-strings-format-config + "junit-xml" junit-xml-format-config }) + + +;;; Reporting + +(intern (the-ns 'clojure.test) 'old-report clojure.test/report) + +(def #^:dynamic #^:private *renderer* println) + + +;;; This mechanism is only used to make `fact` return appropriate values of +;;; true or false. It doesn't piggyback off clojure.test/*report-counters* +;;; partly because that's not normally initialized and partly to reduce +;;; dependencies. + +(def #^:dynamic #^:private *failure-in-fact* false) + +(defn note-failure-in-fact + ([] (note-failure-in-fact true)) + ([val] (alter-var-root #'*failure-in-fact* (constantly val)))) + +(defn- fact-begins [] + (note-failure-in-fact false)) + +(defn- fact-checks-out? [] + (not *failure-in-fact*)) + +(defn form-providing-friendly-return-value [test-form] + `(do + (#'fact-begins) + ~test-form + (#'fact-checks-out?))) + +(letfn [(render [m] + (->> m + ((:single-fact-fn *report-format-config*)) + flatten + (remove nil?) + (map *renderer*) + doall))] + + (defmethod clojure.test/old-report :default [m] + (inc-report-counter :fail ) + (note-failure-in-fact) + (render m)) + + (defmethod clojure.test/old-report :future-fact [m] + (render m))) diff --git a/src/midje/ideas/reporting/string_format.clj b/src/midje/ideas/reporting/string_format.clj new file mode 100644 index 000000000..80e6f7195 --- /dev/null +++ b/src/midje/ideas/reporting/string_format.clj @@ -0,0 +1,177 @@ +(ns ^{:doc "The default report format. Prints in colorized strings."} + midje.ideas.reporting.string-format + (:use [clojure.pprint :only [cl-format]] + [midje.util.object-utils :only [function-name function-name-or-spewage named-function?]] + midje.error-handling.exceptions + [midje.internal-ideas.fact-context :only [format-nested-descriptions]] + [midje.util.form-utils :only [pred-cond]]) + (:require [midje.util.colorize :as color])) + + +;;; Formatting Single Fact + +(defn midje-position-string [[filename line-num]] + (format "(%s:%s)" filename line-num)) + +(defn- ^{:testable true} attractively-stringified-form [form] + (pred-cond form + named-function? (format "a function named '%s'" (function-name form)) + captured-throwable? (friendly-stacktrace form) + :else (pr-str form))) + +(letfn [(fail-at [m] + (let [description (when-let [doc (format-nested-descriptions (:description m))] + (str (pr-str doc) " ")) + position (midje-position-string (:position m)) + table-substitutions (when-let [substitutions (:binding-note m)] + (str "With table substitutions: " substitutions))] + (list + (str "\n" (color/fail "FAIL") " " description "at " position) + table-substitutions))) + + (indented [lines] + (map (partial str " ") lines))] + + (defmulti report-strings :type) + + (defmethod report-strings :mock-expected-result-failure [m] + (list + (fail-at m) + (str " Expected: " (pr-str (:expected m))) + (str " Actual: " (attractively-stringified-form (:actual m))))) + + (defmethod report-strings :mock-expected-result-inappropriately-matched [m] + (list + (fail-at m) + (str " Expected: Anything BUT " (pr-str (:expected m))) + (str " Actual: " (attractively-stringified-form (:actual m))))) + + (defmethod report-strings :mock-expected-result-functional-failure [m] + (list + (fail-at m) + "Actual result did not agree with the checking function." + (str " Actual result: " (attractively-stringified-form (:actual m))) + (str " Checking function: " (pr-str (:expected m))) + (if (:intermediate-results m) + (cons " During checking, these intermediate values were seen:" + (for [[form value] (:intermediate-results m)] + (format " %s => %s" (pr-str form) (pr-str value))))) + (if (:notes m) + (cons " The checker said this about the reason:" + (indented (:notes m)))))) + + (defmethod report-strings :mock-actual-inappropriately-matches-checker [m] + (list + (fail-at m) + "Actual result was NOT supposed to agree with the checking function." + (str " Actual result: " (attractively-stringified-form (:actual m))) + (str " Checking function: " (pr-str (:expected m))))) + + (defmethod report-strings :future-fact [m] + (list + (str "\n" (color/note "WORK TO DO") " " + (when-let [doc (format-nested-descriptions (:description m))] (str (pr-str doc) " ")) + "at " (midje-position-string (:position m))))) + + (defmethod report-strings :mock-argument-match-failure [m] + (list + (fail-at m) + (str "You never said " + (function-name-or-spewage (:var m)) + " would be needed with these arguments:") + (str " " (pr-str (:actual m))))) + + (defmethod report-strings :mock-incorrect-call-count [m] + (letfn [ + (format-one-failure [fail] + (let [exp (:expected-count fail) + act (:actual-count fail) + msg (cond + (and (nil? exp) (zero? act)) + "[expected at least once, actually never called]" + + (nil? exp) + (cl-format nil "[expected at least once, actually called ~R time~:P]" act) + + :else + (cl-format nil "[expected :times ~A, actually called ~R time~:P]" exp act))] + (str " " (:expected fail) " " msg)))] + + (concat + (list (fail-at (first (:failures m))) + "These calls were not made the right number of times:") + (map format-one-failure (:failures m))))) + + (defmethod report-strings :validation-error [m] + (list + (fail-at m) + (str " Midje could not understand something you wrote: ") + (indented (:notes m)))) + + (defmethod report-strings :exceptional-user-error [m] + (list + (fail-at m) + (str " Midje caught an exception when translating this form:") + (str " " (pr-str (:macro-form m))) + (str " " "This stack trace *might* help:") + (indented (:stacktrace m))))) + + +;;; Formatting Summary of Facts + +(defn report-strings-summary [exit-after-tests?] + `(fn [namespaces#] + (let [midje-passes# (:pass @clojure.test/*report-counters*) + midje-fails# (:fail @clojure.test/*report-counters*) + midje-failure-message# (condp = midje-fails# + 0 (color/pass (format "All claimed facts (%d) have been confirmed." midje-passes#)) + 1 (str (color/fail "FAILURE:") + (format " %d fact was not confirmed." midje-fails#)) + (str (color/fail "FAILURE:") + (format " %d facts were not confirmed." midje-fails#))) + + potential-consolation# (condp = midje-passes# + 0 "" + 1 "(But 1 was.)" + (format "(But %d were.)" midje-passes#)) + + midje-consolation# (if (> midje-fails# 0) potential-consolation# "") + + ; Stashed clojure.test output + ct-output-catcher# (java.io.StringWriter.) + ct-result# (binding [clojure.test/*test-out* ct-output-catcher#] + (apply ~'clojure.test/run-tests namespaces#)) + ct-output# (-> ct-output-catcher# + .toString + clojure.string/split-lines) + ct-failures-and-errors# (+ (:fail ct-result#) (:error ct-result#)) + ct-some-kind-of-fail?# (> ct-failures-and-errors# 0)] + + (when ct-some-kind-of-fail?# + ;; For some reason, empty lines are swallowed, so I use >>> to + ;; demarcate sections. + (println (color/note ">>> Output from clojure.test tests:")) + (dorun (map (comp println color/colorize-deftest-output) + (drop-last 2 ct-output#)))) + + (when (> (:test ct-result#) 0) + (println (color/note ">>> clojure.test summary:")) + (println (first (take-last 2 ct-output#))) + (println ( (if ct-some-kind-of-fail?# color/fail color/pass) (last ct-output#))) + (println (color/note ">>> Midje summary:"))) + + (println midje-failure-message# midje-consolation#) + + ;; A non-nil return value is printed, so I'll just exit here. + (when ~exit-after-tests? + (System/exit (+ midje-fails# + (:error ct-result#) + (:fail ct-result#))))))) + + +;; Config to expose to reporting namespace, which it will use to show +;; reported failures to the user + +(def report-strings-format-config + { :single-fact-fn report-strings + :summary-fn report-strings-summary }) \ No newline at end of file diff --git a/src/midje/ideas/tabular.clj b/src/midje/ideas/tabular.clj index 3834a0e66..5c07f2093 100644 --- a/src/midje/ideas/tabular.clj +++ b/src/midje/ideas/tabular.clj @@ -1,20 +1,18 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "A way to create multiple facts with the same template, but different data points."} midje.ideas.tabular - (:use - [clojure.string :only [join]] - [midje.error-handling.validation-errors :only [valid-let simple-report-validation-error validate]] - [midje.internal-ideas.fact-context :only [within-fact-context]] - [midje.internal-ideas.file-position :only [form-with-copied-line-numbers - form-position]] ; for deprecation - [midje.internal-ideas.report :only [midje-position-string]] ; for deprecation - [midje.util.form-utils :only [translate-zipper]] - [midje.util.zip :only [skip-to-rightmost-leaf]] - [midje.internal-ideas.expect :only [expect?]] - [midje.ideas.arrows :only [above-arrow-sequence__add-key-value__at-arrow]] - [midje.ideas.metaconstants :only [metaconstant-symbol?]] - [utilize.map :only [ordered-zipmap]]) + (:use [clojure.string :only [join]] + [clojure.algo.monads :only [domonad]] + [midje.error-handling.validation-errors :only [simple-validation-error-report-form validate-m validate]] + [midje.internal-ideas.fact-context :only [within-fact-context]] + [midje.internal-ideas.file-position :only [form-with-copied-line-numbers + form-position]] ; for deprecation + [midje.ideas.reporting.string-format :only [midje-position-string]] ; for deprecation + [midje.util.form-utils :only [pop-docstring translate-zipper]] + [midje.util.zip :only [skip-to-rightmost-leaf]] + [midje.internal-ideas.expect :only [expect?]] + [midje.ideas.arrows :only [above-arrow-sequence__add-key-value__at-arrow]] + [midje.ideas.metaconstants :only [metaconstant-symbol?]] + [utilize.map :only [ordered-zipmap]]) (:require [midje.util.unify :as unify])) (def #^:private deprecation-hack:file-position (atom "")) @@ -39,9 +37,8 @@ (not ((set locals) s))))] (split-with table-variable? (remove-pipes+where table)))) -(defn- ^{:testable true } table-binding-maps [table locals] - (let [[headings-row values] (headings-rows+values table locals) - value-rows (partition (count headings-row) values)] +(defn- ^{:testable true } table-binding-maps [headings-row values] + (let [value-rows (partition (count headings-row) values)] (map (partial ordered-zipmap headings-row) value-rows))) (defn- format-binding-map [binding-map] @@ -63,32 +60,32 @@ (partial form-with-copied-line-numbers fact-form) (partial unify/substitute fact-form)))] - (valid-let [[description? fact-form table] (validate form locals) - _ (swap! deprecation-hack:file-position - (constantly (midje-position-string (form-position fact-form)))) - ordered-binding-maps (table-binding-maps table locals) - expect-forms (map (macroexpander-for fact-form) ordered-binding-maps) - expect-forms-with-binding-notes (map add-binding-note - expect-forms - ordered-binding-maps)] + (domonad validate-m [[description? fact-form headings-row values] (validate form locals) + _ (swap! deprecation-hack:file-position + (constantly (midje-position-string (form-position fact-form)))) + ordered-binding-maps (table-binding-maps headings-row values) + expect-forms (map (macroexpander-for fact-form) ordered-binding-maps) + expect-forms-with-binding-notes (map add-binding-note + expect-forms + ordered-binding-maps)] `(within-fact-context ~description? ~@expect-forms-with-binding-notes)))) (defmethod validate "tabular" [[_tabular_ & form] locals] - (let [[[description? & _] [fact-form & table]] (split-with string? form) + (let [[description? [fact-form & table]] (pop-docstring form) [headings-row values] (headings-rows+values table locals)] (cond (empty? table) - (simple-report-validation-error form + (simple-validation-error-report-form form "There's no table. (Misparenthesized form?)") (empty? values) - (simple-report-validation-error form + (simple-validation-error-report-form form "It looks like the table has headings, but no values:") (empty? headings-row) - (simple-report-validation-error form + (simple-validation-error-report-form form "It looks like the table has no headings, or perhaps you" "tried to use a non-literal string for the doc-string?:") :else - [description? fact-form table]))) \ No newline at end of file + [description? fact-form headings-row values]))) \ No newline at end of file diff --git a/src/midje/inner_functions.clj b/src/midje/inner_functions.clj index 94f9c236c..c9806c404 100644 --- a/src/midje/inner_functions.clj +++ b/src/midje/inner_functions.clj @@ -1,9 +1,7 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.inner-functions) -(defmacro testable-defn [name args & body] +(defmacro testable-defn [_name_ _args_ & _body_] ) diff --git a/src/midje/internal_ideas/expect.clj b/src/midje/internal_ideas/expect.clj index 9f4a2ec17..817618d63 100644 --- a/src/midje/internal_ideas/expect.clj +++ b/src/midje/internal_ideas/expect.clj @@ -1,4 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- (ns ^{:doc "Mostly functions for identifying semi-sweet expects, and for converting midje.sweet arrow forms into semi-sweet expcet forms."} midje.internal-ideas.expect @@ -6,7 +5,7 @@ [midje.util.namespace :only [matches-symbols-in-semi-sweet-or-sweet-ns?]] [midje.util.form-utils :only [first-named?]] [midje.util.zip :only [skip-to-rightmost-leaf n-times remove-moving-right]] - [midje.ideas.arrows :only [is-start-of-checking-arrow-sequence? arrow-sequence-overrides]] + [midje.ideas.arrows :only [start-of-checking-arrow-sequence? arrow-sequence-overrides]] [midje.internal-ideas.file-position :only [arrow-line-number]]) (:require [clojure.zip :as zip])) @@ -46,7 +45,7 @@ (defn wrap-with-expect__then__at-rightmost-expect-leaf [loc] - (assert (is-start-of-checking-arrow-sequence? loc)) + (assert (start-of-checking-arrow-sequence? loc)) (let [right-hand (-> loc zip/right zip/right) arrow-sequence (-> loc zip/right zip/node) additions (arrow-sequence-overrides (zip/rights right-hand)) @@ -58,5 +57,5 @@ assoc :line line-number)))] (->> edited-loc zip/right - (n-times (+ 1 (count additions)) remove-moving-right) + (n-times (inc (count additions)) remove-moving-right) zip/remove))) \ No newline at end of file diff --git a/src/midje/internal_ideas/fact_context.clj b/src/midje/internal_ideas/fact_context.clj index 6a3bb4900..417ddb3ff 100644 --- a/src/midje/internal_ideas/fact_context.clj +++ b/src/midje/internal_ideas/fact_context.clj @@ -3,13 +3,13 @@ midje.internal-ideas.fact-context (:use [clojure.string :only [join]])) -(def #^:private nested-description (atom [])) +(def nested-descriptions (atom [])) (defn- enter-context [description] - (swap! nested-description conj description)) + (swap! nested-descriptions conj description)) (defn- leave-context [] - (swap! nested-description #(vec (butlast %)))) + (swap! nested-descriptions #(vec (butlast %)))) (defmacro within-fact-context [description & body] `(try @@ -18,6 +18,14 @@ (finally (#'leave-context)))) -(defn nested-fact-description [] - (when-let [non-nil (seq (remove nil? @nested-description))] + +;; A way to format the description - keeping formatting separate from representation. + +; Used in the report + +(defn format-nested-descriptions + "Takes vector like [\"about cars\" nil \"sports cars are fast\"] and returns non-nils joined with -'s + => \"about cars - sports cars are fast\"" + [nested-description-vector] + (when-let [non-nil (seq (remove nil? nested-description-vector))] (join " - " non-nil))) \ No newline at end of file diff --git a/src/midje/internal_ideas/fakes.clj b/src/midje/internal_ideas/fakes.clj index 51b0d05e2..4dd328150 100644 --- a/src/midje/internal_ideas/fakes.clj +++ b/src/midje/internal_ideas/fakes.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "The semi-sweet representation of provided forms."} midje.internal-ideas.fakes (:use [utilize.seq :only (separate find-first)] @@ -9,10 +7,15 @@ [midje.checkers.defining :only [checker? checker-makers]] [midje.internal-ideas.expect :only [expect? up-to-full-expect-form]] [midje.util.form-utils :only [first-named? translate-zipper map-difference - hash-map-duplicates-ok pred-cond to-thunks]] - [midje.ideas.metaconstants :only [metaconstant-for-form + hash-map-duplicates-ok pred-cond to-thunks + quoted-list-form? extended-fn? + fnref-call-form + fnref-var-object + fnref-symbol + fnref-dereference-form]] + [midje.ideas.metaconstants :only [merge-metaconstants metaconstant-for-form with-fresh-generated-metaconstant-names]] - [midje.checkers.extended-equality :only [extended-= extended-list-= extended-fn?]] + [midje.checkers.extended-equality :only [extended-= extended-list-=]] [midje.internal-ideas.file-position :only [user-file-position]] [midje.util.thread-safe-var-nesting :only [namespace-values-inside-out with-pushed-namespace-values @@ -20,6 +23,7 @@ [midje.error-handling.exceptions :only [user-error]] [midje.internal-ideas.wrapping :only [with-wrapping-target]] [midje.ideas.arrow-symbols] + [midje.config :only [*allow-default-prerequisites*]] [clojure.tools.macro :only [macrolet]]) (:require [clojure.zip :as zip]) (:import midje.ideas.metaconstants.Metaconstant)) @@ -35,139 +39,162 @@ (first-named? form "data-fake"))) -;;; Creation +;;; Creating fake maps (defn arg-matcher-maker "Based on an expected value, generates a function that returns true if the actual value matches it." [expected] (if (and (extended-fn? expected) - (not (checker? expected))) + (not (checker? expected))) (fn [actual] (extended-= actual (exactly expected))) (fn [actual] (extended-= actual expected)))) -(defmulti make-result-supplier* (fn [arrow & _] arrow)) +(defmulti fn-fake-result-supplier (fn [arrow & _] arrow)) -(defmethod make-result-supplier* => [_arrow_ result] (constantly result)) +(defmethod fn-fake-result-supplier => [_arrow_ result] (constantly result)) -(defmethod make-result-supplier* =streams=> [_arrow_ result-stream-of-thunks] - (let [the-stream (atom result-stream-of-thunks)] +(defmethod fn-fake-result-supplier =streams=> [_arrow_ result-stream] + (let [the-stream (atom result-stream)] (fn [] (when (empty? @the-stream) (throw (user-error "Your =stream=> ran out of values."))) (let [current-result (first @the-stream)] (swap! the-stream rest) - (current-result))))) + current-result)))) -(defmethod make-result-supplier* =throws=> [_arrow_ throwable] +(defmethod fn-fake-result-supplier =throws=> [_arrow_ throwable] (fn [] - (when (not (instance? Throwable throwable)) + (when-not (instance? Throwable throwable) (throw (user-error "Right side of =throws=> should extend Throwable."))) (throw throwable))) -(defmethod make-result-supplier* :default [arrow result-stream] +(defmethod fn-fake-result-supplier :default [arrow result-stream] (throw (user-error "It's likely you misparenthesized your metaconstant prerequisite," "or that you forgot to use an arrow in your provided form."))) -(defmacro make-result-supplier [arrow rhs] - (if (= (name =streams=>) (name arrow)) - `(make-result-supplier* ~arrow (to-thunks ~rhs)) - `(make-result-supplier* ~arrow ~rhs))) -(letfn [(make-fake-map - [var-sym special-to-fake-type user-override-pairs] - (let [common-to-all-fakes `{:lhs (var ~var-sym) - :count-atom (atom 0) - :position (user-file-position)}] +(defn #^:private + statically-disallowed-prerequisite-function + "To prevent people from mocking functions that Midje itself uses, + we mostly rely on dynamic checking. But there are functions within + the dynamic checking code that must also not be replaced. These are + the ones that are known." + [some-var] + (#{#'deref} some-var)) + +(defn #^:private + raise-disallowed-prerequisite-error [function-name] + (throw + (user-error + "You seem to have created a prerequisite for" + (str (pr-str function-name) " that interferes with that function's use in Midje's") + (str "own code. To fix, define a function of your own that uses " + (or (:name (meta function-name)) function-name) ", then") + "describe that function in a provided clause. For example, instead of this:" + " (provided (every? even? ..xs..) => true)" + "do this:" + " (def all-even? (partial every? even?))" + " ;; ..." + " (provided (all-even? ..xs..) => true)"))) + +(letfn [(make-fake-map [call-form arrow rhs fnref special-to-fake-type user-override-pairs] + (let [common-to-all-fakes `{:var ~(fnref-call-form fnref) + :call-count-atom (atom 0) + :position (user-file-position) + + ;; for Midje tool creators: + :call-form '~call-form + :arrow '~arrow + :rhs '~rhs}] (merge common-to-all-fakes special-to-fake-type (apply hash-map-duplicates-ok user-override-pairs)))) ] - (defn fake* [ [[var-sym & args :as call-form] arrow result & overrides] ] + (defn fake* [ [[fnref & args :as call-form] arrow result & overrides] ] ;; The (vec args) keeps something like (...o...) from being ;; evaluated as a function call later on. Right approach would ;; seem to be '~args. That causes spurious failures. Debug ;; someday. - (make-fake-map var-sym + (when (statically-disallowed-prerequisite-function (fnref-var-object fnref)) + (raise-disallowed-prerequisite-error (fnref-var-object fnref))) + (make-fake-map call-form arrow (cons result overrides) + fnref `{:arg-matchers (map midje.internal-ideas.fakes/arg-matcher-maker ~(vec args)) :call-text-for-failures (str '~call-form) - :value-at-time-of-faking (if (bound? (var ~var-sym)) ~var-sym) - :result-supplier (make-result-supplier ~arrow ~result) + :value-at-time-of-faking (if (bound? ~(fnref-call-form fnref)) + ~(fnref-dereference-form fnref)) + :result-supplier (fn-fake-result-supplier ~arrow ~result) :type :fake} overrides)) (defn data-fake* [[metaconstant arrow contained & overrides]] - (make-fake-map metaconstant + (make-fake-map metaconstant arrow (cons contained overrides) + metaconstant `{:contained ~contained - :count-atom (atom 1) ;; CLUDKJE! + :call-count-atom (atom 1) ;; CLUDKJE! :type :fake - :data-fake :data-fake} + :data-fake true} overrides)) (defn not-called* [var-sym & overrides] - (make-fake-map var-sym + (make-fake-map nil nil nil ;; deprecated, so no support for fields for tool creators + var-sym `{:call-text-for-failures (str '~var-sym " was called.") :result-supplier (constantly nil) :type :not-called} overrides))) (defn tag-as-background-fake [fake] - (concat fake `(:background :background :times (~'range 0)))) + `(~@fake :background :background :times (~'range 0))) + + +;;; Handling mocked calls + +(defmulti ^{:private true} call-handled-by-fake? (fn [function-var actual-args fake] + (:type fake))) + +(defmethod call-handled-by-fake? :not-called [function-var actual-args fake] + (= function-var (:var fake))) +(defmethod call-handled-by-fake? :default [function-var actual-args fake] + (and (= function-var (:var fake)) + (= (count actual-args) (count (:arg-matchers fake))) + (extended-list-= actual-args (:arg-matchers fake)))) -;;; Binding (defn usable-default-function? [fake] - (and (bound? (:lhs fake)) - (let [value-in-var (var-get (:lhs fake)) - unfinished-fun (:midje/unfinished-fun (meta (:lhs fake)))] - (and (extended-fn? value-in-var) - (or (nil? unfinished-fun) - (not= unfinished-fun value-in-var)))))) - -(letfn [(var-handled-by-fake? [function-var fake] - (= function-var (:lhs fake)))] - - (defmulti ^{:private true} call-handled-by-fake? (fn [function-var actual-args fake] - (:type fake))) - - (defmethod call-handled-by-fake? :not-called [function-var actual-args fake] - (var-handled-by-fake? function-var fake)) - - (defmethod call-handled-by-fake? :default [function-var actual-args fake] - (and (var-handled-by-fake? function-var fake) - (= (count actual-args) (count (:arg-matchers fake))) - (extended-list-= actual-args (:arg-matchers fake)))) - - - (def #^:dynamic #^:private *call-action-count* (atom 0)) - - (defn- ^{:testable true } best-call-action [function-var actual-args fakes] - (when (= 2 @*call-action-count*) - (throw (user-error "You seem to have created a prerequisite for" - (str (pr-str function-var) " that interferes with that function's use in Midje's") - (str "own code. To fix, define a function of your own that uses " - (or (:name (meta function-var)) function-var) ", then") - "describe that function in a provided clause. For example, instead of this:" - " (provided (every? even? ..xs..) => true)" - "do this:" - " (def all-even? (partial every? even?))" - " ;; ..." - " (provided (all-even? ..xs..) => true)"))) - (if-let [found (find-first (partial call-handled-by-fake? function-var actual-args) - fakes)] - found - (let [possible-fakes (filter (partial var-handled-by-fake? function-var) fakes)] - (pred-cond possible-fakes - empty? nil - (comp not usable-default-function? first) nil ;; Finding default, any possible fake works - :else (:value-at-time-of-faking - (first possible-fakes))))))) - -(defn- ^{:testable true } call-faker - "This is the function that handles all mocked calls." + (and *allow-default-prerequisites* + (bound? (:var fake)) + (let [value-in-var (var-get (:var fake)) + unfinished-fun (:midje/unfinished-fun (meta (:var fake)))] + (and (extended-fn? value-in-var) + (or (nil? unfinished-fun) + (not= unfinished-fun value-in-var)))))) + +;; Used for IFn interface +(def #^:private ^{:testable true} + default-function :value-at-time-of-faking) + +(def #^:dynamic #^:private *call-action-count* (atom 0)) + + +(defn- ^{:testable true } best-call-action + "Returns a fake: when one can handle the call + Else returns a function: from the first fake with a usable-default-function. + Returns nil otherwise." [function-var actual-args fakes] + (when (= 2 @*call-action-count*) + (raise-disallowed-prerequisite-error function-var)) + (if-let [found (find-first (partial call-handled-by-fake? function-var actual-args) fakes)] + found + (when-let [fake-with-usable-default (find-first #(and (= function-var (:var %)) + (usable-default-function? %)) + fakes)] + (default-function fake-with-usable-default)))) + +(defn- ^{:testable true } handle-mocked-call [function-var actual-args fakes] (macrolet [(counting-nested-calls [& forms] `(try (swap! *call-action-count* inc) @@ -176,80 +203,70 @@ (let [action (counting-nested-calls (best-call-action function-var actual-args fakes))] (pred-cond action - nil? (clojure.test/report {:type :mock-argument-match-failure - :lhs function-var - :actual actual-args - :position (:position (first fakes))}) extended-fn? (apply action actual-args) - :else (do - (swap! (:count-atom action) inc) - ((:result-supplier action ))))))) + map? (do + (swap! (:call-count-atom action) inc) + ((:result-supplier action ))) + :else (clojure.test/report {:type :mock-argument-match-failure + :var function-var + :actual actual-args + :position (:position (first fakes))}))))) ;; Binding map related -(defn- ^{:testable true } unique-vars [fakes] - (distinct (map :lhs fakes))) - -(defn- ^{:testable true } binding-map-with-function-fakes [fakes] - (letfn [(fn-that-implements-a-fake [function] - (vary-meta function assoc :midje/faked-function true)) - (make-faker [var] - (fn-that-implements-a-fake (fn [& actual-args] (call-faker var actual-args fakes))))] - (into {} - (for [var (unique-vars fakes)] - [var (make-faker var)])))) - -(defn- ^{:testable true } merge-metaconstant-bindings [bindings] - (apply merge-with (fn [^Metaconstant v1 ^Metaconstant v2] - (Metaconstant. (.name v1) (merge (.storage v1) (.storage v2)))) - bindings)) - -(defn- ^{:testable true } data-fakes-to-metaconstant-bindings [fakes] - (for [{var :lhs, contents :contained} fakes] - {var (Metaconstant. (object-name var) contents)})) - -(letfn [(binding-map-with-data-fakes [data-fakes] - (merge-metaconstant-bindings (data-fakes-to-metaconstant-bindings data-fakes)))] - - (defn binding-map [fakes] - (let [[data-fakes function-fakes] (separate :data-fake fakes)] - (merge (binding-map-with-function-fakes function-fakes) - (binding-map-with-data-fakes data-fakes))))) +(defn- fn-fakes-binding-map [fn-fakes] + (let [var->faker-fn (fn [the-var] + (-> (fn [& actual-args] + (handle-mocked-call the-var actual-args fn-fakes)) + (vary-meta assoc :midje/faked-function true))) + fn-fake-vars (map :var fn-fakes)] + (zipmap fn-fake-vars + (map var->faker-fn fn-fake-vars)))) + +(defn- data-fakes-binding-map [data-fakes] + (apply merge-with merge-metaconstants (for [{:keys [var contained]} data-fakes] + {var (Metaconstant. (object-name var) contained)}))) + +(defn binding-map [fakes] + (let [[data-fakes fn-fakes] (separate :data-fake fakes)] + (merge (fn-fakes-binding-map fn-fakes) + (data-fakes-binding-map data-fakes)))) (defmacro with-installed-fakes [fakes & forms] `(with-altered-roots (binding-map ~fakes) ~@forms)) -;;; Checking -(defn fake-count [fake] @(:count-atom fake)) +;;; Checking (defmulti call-count-incorrect? :type) (defmethod call-count-incorrect? :fake [fake] (let [method (or (:times fake) :default ) - count (fake-count fake)] + count @(:call-count-atom fake)] (pred-cond method #(= % :default) (zero? count) number? (not= method count) - coll? (not (some #{count} method)) + coll? (not-any? (partial = count) method) fn? (not (method count))))) (defmethod call-count-incorrect? :not-called [fake] - (not (zero? (fake-count fake)))) + (not (zero? @(:call-count-atom fake)))) -(defn check-call-counts [fakes] - (doseq [fake fakes] - (when (call-count-incorrect? fake) - (report {:type :mock-incorrect-call-count - :actual-count @(:count-atom fake) - :expected-call (:call-text-for-failures fake) - :position (:position fake) - :expected (:call-text-for-failures fake)})))) +(defn report-incorrect-call-counts [fakes] + (when-let [failures (seq (for [fake fakes + :when (call-count-incorrect? fake)] + {:actual-count @(:call-count-atom fake) + :expected-count (:times fake) + :expected-call (:call-text-for-failures fake) + :position (:position fake) + :expected (:call-text-for-failures fake)}))] + (report {:type :mock-incorrect-call-count + :failures failures} ))) -;; Folded prerequisites +;;; Folded prerequisites ;; Note that folded prerequisites are in semi-sweet-style. (That is, they can only ;; be recognized after sweet style has been converted to semi-sweet.) @@ -263,13 +280,13 @@ (let [constructor? (fn [symbol] (.endsWith (name symbol) ".")) special-forms '[quote fn let new] - mockable-function-symbol? (fn [symbol] - (not (or (some #{symbol} special-forms) - (some #{symbol} checker-makers) - (constructor? symbol) - (checker? (resolve symbol)))))] + mockable-function? (fn [fnref] + (not (or (some #{fnref} special-forms) + (some #{fnref} checker-makers) + (constructor? (fnref-symbol fnref)) + (checker? (fnref-var-object fnref)))))] (and (list? x) - (mockable-function-symbol? (first x))))) + (mockable-function? (first x))))) (letfn [(fake-form-funcall-arglist [[fake funcall => value & overrides :as _fake-form_]] (rest funcall))] @@ -327,4 +344,4 @@ (with-fresh-generated-metaconstant-names (translate-zipper form expect? - unfold-expect-form__then__stay_put)))) \ No newline at end of file + unfold-expect-form__then__stay_put)))) diff --git a/src/midje/internal_ideas/file_position.clj b/src/midje/internal_ideas/file_position.clj index eb1962466..f1727d9ed 100644 --- a/src/midje/internal_ideas/file_position.clj +++ b/src/midje/internal_ideas/file_position.clj @@ -1,9 +1,7 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Functions to help in finding the lines you care about."} midje.internal-ideas.file-position (:use [midje.util.zip :only [skip-to-rightmost-leaf]] - [midje.util.form-utils :only [translate-zipper]] + [midje.util.form-utils :only [quoted? translate-zipper]] [midje.util.namespace :only [matches-symbols-in-semi-sweet-or-sweet-ns?]] [midje.ideas.arrows :only [all-arrows at-arrow__add-key-value-to-end__no-movement]]) (:require [clojure.zip :as zip])) @@ -29,8 +27,9 @@ (reset! fallback-line-number raw-lineno) (swap! fallback-line-number inc)))) -(defn arrow-line-number-from-form [form] +(defn arrow-line-number-from-form "Form is of the form [ => .* ]" + [form] (-> form zip/seq-zip zip/down zip/right arrow-line-number)) (defn form-position [form] @@ -88,6 +87,9 @@ (defn annotate-embedded-arrows-with-line-numbers [form] (translate-zipper form + quoted? + skip-to-rightmost-leaf + (fn [loc] (matches-symbols-in-semi-sweet-or-sweet-ns? all-arrows loc)) (fn [loc] (at-arrow__add-line-number-to-end__no-movement (arrow-line-number loc) loc)))) diff --git a/src/midje/internal_ideas/report.clj b/src/midje/internal_ideas/report.clj deleted file mode 100644 index 8bfab8763..000000000 --- a/src/midje/internal_ideas/report.clj +++ /dev/null @@ -1,142 +0,0 @@ -;; -*- indent-tabs-mode: nil -*- -(ns ^{:doc "Renders the various reported fact evaluation results."} - midje.internal-ideas.report - (:use clojure.test - [clojure.pprint :only [cl-format]] - [midje.util.object-utils :only [function-name function-name-or-spewage named-function?]] - midje.error-handling.exceptions - [midje.util.form-utils :only [pred-cond]]) - (:require [midje.util.colorize :as color])) - -(intern (the-ns 'clojure.test) 'old-report clojure.test/report) - -(def #^:dynamic #^:private *renderer* println) - - -;;; This mechanism is only used to make `fact` return appropriate values of -;;; true or false. It doesn't piggyback off clojure.test/*report-counters* -;;; partly because that's not normally initialized and partly to reduce -;;; dependencies. - -(def #^:dynamic #^:private *failure-in-fact* false) - -(defn note-failure-in-fact - ([] (note-failure-in-fact true)) - ([val] (alter-var-root #'*failure-in-fact* (constantly val)))) - -(defn- fact-begins [] - (note-failure-in-fact false)) - -(defn- fact-checks-out? [] - (not *failure-in-fact*)) - -(defn form-providing-friendly-return-value [test-form] - `(do - (#'fact-begins) - ~test-form - (#'fact-checks-out?))) - - - -(defn midje-position-string [[filename line-num]] - (format "(%s:%s)" filename line-num)) - -(letfn [(attractively-stringified-form [form] - (pred-cond form - named-function? (format "a function named '%s'" (function-name form)) - captured-throwable? (friendly-stacktrace form) - :else (pr-str form))) - - (fail-at [m] - [(str "\n" (color/fail "FAIL:") " " - (when-let [doc (:description m)] (str (pr-str doc) " ")) - "at " (midje-position-string (:position m))) - (when-let [substitutions (:binding-note m)] - (str "With table substitutions: " substitutions))]) - - (indented [lines] - (map (partial str " ") lines))] - - (defmulti report-strings :type) - - (defmethod report-strings :mock-expected-result-failure [m] - (list - (fail-at m) - (str " Expected: " (pr-str (:expected m))) - (str " Actual: " (attractively-stringified-form (:actual m))))) - - (defmethod report-strings :mock-expected-result-inappropriately-matched [m] - (list - (fail-at m) - (str " Expected: Anything BUT " (pr-str (:expected m))) - (str " Actual: " (attractively-stringified-form (:actual m))))) - - (defmethod report-strings :mock-expected-result-functional-failure [m] - (list - (fail-at m) - "Actual result did not agree with the checking function." - (str " Actual result: " (attractively-stringified-form (:actual m))) - (str " Checking function: " (pr-str (:expected m))) - (if (:intermediate-results m) - (cons " During checking, these intermediate values were seen:" - (for [[form value] (:intermediate-results m)] - (format " %s => %s" (pr-str form) (pr-str value))))) - (if (:notes m) - (cons " The checker said this about the reason:" - (indented (:notes m)))))) - - (defmethod report-strings :mock-actual-inappropriately-matches-checker [m] - (list - (fail-at m) - "Actual result was NOT supposed to agree with the checking function." - (str " Actual result: " (attractively-stringified-form (:actual m))) - (str " Checking function: " (pr-str (:expected m))))) - - (defmethod report-strings :future-fact [m] - (list - (str "\n" (color/note "WORK TO DO:") " " - (when-let [doc (:description m)] (str (pr-str doc) " ")) - "at " (midje-position-string (:position m))))) - - (defmethod report-strings :mock-argument-match-failure [m] - (list - (fail-at m) - (str "You never said " - (function-name-or-spewage (:lhs m)) - " would be needed with these arguments:") - (str " " (pr-str (:actual m))))) - - (defmethod report-strings :mock-incorrect-call-count [m] - (list - (fail-at m) - (if (zero? (:actual-count m)) - "You claimed the following was needed, but it was never used:" - (cl-format nil - "The following prerequisite was used ~R time~:P. That's not what you predicted." - (:actual-count m))) - (str " " (:expected m)))) - - (defmethod report-strings :validation-error [m] - (list - (fail-at m) - (str " Midje could not understand something you wrote: ") - (indented (:notes m)))) - - (defmethod report-strings :exceptional-user-error [m] - (list - (fail-at m) - (str " Midje caught an exception when translating this form:") - (str " " (pr-str (:macro-form m))) - (str " " "This stack trace *might* help:") - (indented (:stacktrace m))))) - -(letfn [(render [m] - (->> m report-strings flatten (remove nil?) (map *renderer*) doall))] - - (defmethod clojure.test/old-report :default [m] - (inc-report-counter :fail ) - (note-failure-in-fact) - (render m)) - - (defmethod clojure.test/old-report :future-fact [m] - (render m))) diff --git a/src/midje/internal_ideas/wrapping.clj b/src/midje/internal_ideas/wrapping.clj index 442d6ad24..1cea818aa 100644 --- a/src/midje/internal_ideas/wrapping.clj +++ b/src/midje/internal_ideas/wrapping.clj @@ -1,14 +1,11 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "midje.background uses these to wrap extra code around :contents, :facts, or :expects"} midje.internal-ideas.wrapping - (:use - [midje.util.form-utils :only [first-named?]] - [utilize.seq :only [separate]] - [midje.util.thread-safe-var-nesting :only [namespace-values-inside-out - set-namespace-value - with-pushed-namespace-values]]) + (:use [midje.util.form-utils :only [first-named?]] + [utilize.seq :only [separate]] + [midje.util.thread-safe-var-nesting :only [namespace-values-inside-out + set-namespace-value + with-pushed-namespace-values]]) (:require [clojure.zip :as zip] [midje.util.unify :as unify])) @@ -34,7 +31,8 @@ (vary-meta what assoc :midje/wrapping-target target)) (defn for-wrapping-target? [target] - (fn [actual] (= target (:midje/wrapping-target (meta actual))))) + (fn [actual] + (= target (:midje/wrapping-target (meta actual))))) (defmacro with-additional-wrappers [final-wrappers form] `(with-pushed-namespace-values :midje/wrappers ~final-wrappers diff --git a/src/midje/open_protocols.clj b/src/midje/open_protocols.clj index 0c3df3d81..ad30f5aea 100644 --- a/src/midje/open_protocols.clj +++ b/src/midje/open_protocols.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Macros for using protocols in prerequisites. The strategy for open protocols is to rewrite each function defined in the diff --git a/src/midje/production_mode.clj b/src/midje/production_mode.clj index d286f1fd9..301da9180 100644 --- a/src/midje/production_mode.clj +++ b/src/midje/production_mode.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.production-mode) (letfn [(value-within [namespace-symbol variable-symbol] @@ -7,7 +5,10 @@ (var-get ((ns-map namespace) variable-symbol)) true))] - (defn user-desires-checking? [] + (defn user-desires-checking? + "If any of clojure.test/*load-tests* or midje.sweet/*include-midje-checks* + or midje.semi-sweet/*include-midje-checks* are false, facts won't run." + [] (and (value-within 'clojure.test '*load-tests*) (value-within 'midje.sweet '*include-midje-checks*) (value-within 'midje.semi-sweet '*include-midje-checks*)))) diff --git a/src/midje/semi_sweet.clj b/src/midje/semi_sweet.clj index 8126547fa..48b723402 100644 --- a/src/midje/semi_sweet.clj +++ b/src/midje/semi_sweet.clj @@ -1,28 +1,30 @@ -;; -*- indent-tabs-mode: nil -*- - ;;; This namespace is mainly responsible for converting particular macros ;;; into the arguments used by midje.unprocessed's `expect*`. (ns ^{:doc "Macros that provide less syntactic sugaring than those from midje.sweet. midje.sweet is built on top of it."} midje.semi-sweet + (:refer-clojure :exclude [replace]) (:use clojure.test midje.internal-ideas.fakes midje.internal-ideas.file-position - [midje.internal-ideas.fact-context :only [within-fact-context]] + [midje.internal-ideas.fact-context :only [nested-descriptions within-fact-context]] [midje.util debugging form-utils namespace] - [midje.error-handling validation-errors semi-sweet-validations] + midje.error-handling.validation-errors + midje.error-handling.semi-sweet-validations [midje.error-handling.exceptions :only [user-error]] - [midje.util.namespace :only [is-semi-sweet-keyword?]] - [midje.production-mode] - [clojure.pprint]) - (:require [midje.cljs :as cljs] - [clojure.string :as str])) - + [midje.util.namespace :only [semi-sweet-keyword?]] + [midje.util.ecosystem :only [line-separator]] + midje.production-mode + [clojure.algo.monads :only [domonad]] + clojure.pprint + [clojure.string :only [join replace]] + [midje.cljs :only [load-cljs cljs-eval]] + )) (immigrate 'midje.unprocessed) (immigrate 'midje.ideas.arrow-symbols) - ;;; Conversions to unprocessed form +;;; Conversions to unprocessed form ;; I want to use resolve() to compare calls to fake, rather than the string ;; value of the symbol, but for some reason when the tests run, *ns* is User, @@ -31,19 +33,21 @@ ;; ;; FURTHERMORE, I wanted to use set operations to check for fake and not-called, ;; but those fail for reasons I don't understand. Bah. -(defn- ^{:testable true } check-for-arrow [arrow] - (get {=> :check-match - =expands-to=> :check-match - =not=> :check-negated-match - =deny=> :check-negated-match} (name arrow))) +(defn- ^{:testable true} check-for-arrow [arrow] + (condp = (name arrow) + => :check-match + =expands-to=> :check-match + =not=> :check-negated-match + =deny=> :check-negated-match + nil)) (defn ^{:private true} cljs-file->ns "given the cljs file name produce the cljs ns" [cljs-file] (-> cljs-file - (str/replace #"\.cljs" "") - (str/replace #"_" "-") - (str/replace #"/" ".") + (replace #"\.cljs" "") + (replace #"_" "-") + (replace #"/" ".") symbol)) (defn ^{:private true} cljs-ns-meta @@ -60,8 +64,8 @@ [call-form] (if-let [[cljs-ns cljs-file] (cljs-ns-meta)] `(do - (cljs/load-cljs ~cljs-file) ; TODO: run only once for ns-- how? - (cljs/cljs-eval '~call-form '~cljs-ns)) + (load-cljs ~cljs-file) ; TODO: run only once for ns-- how? + (cljs-eval '~call-form '~cljs-ns)) call-form)) (defmacro unprocessed-check @@ -70,49 +74,50 @@ failure. See 'expect*'." [call-form arrow expected-result overrides] `(merge - {:function-under-test (fn [] (process-call-form ~call-form)) + {:description @nested-descriptions + :function-under-test (fn [] (process-call-form ~call-form)) :expected-result ~expected-result :desired-check ~(check-for-arrow arrow) :expected-result-text-for-failures '~expected-result - :position (user-file-position)} - (hash-map-duplicates-ok ~@overrides))) + :position (user-file-position) + + ;; for Midje tool creators: + :call-form '~call-form + :arrow '~arrow } + (hash-map-duplicates-ok ~@overrides))) -(letfn [(how-to-handle-check [call-form arrow & _] - (get {=> :expect* - =not=> :expect* - =deny=> :expect* - =expands-to=> :expect-macro* - =future=> :report-future-fact} (name arrow)))] +(defmulti ^{:private true} expect-expansion (fn [_call-form_ arrow & _rhs_] + (name arrow))) - (defmulti ^{:private true} expect-expansion how-to-handle-check)) - -(defmethod expect-expansion :expect* +(def-many-methods expect-expansion [=> =not=> =deny=>] [call-form arrow expected-result fakes overrides] `(let [check# (unprocessed-check ~call-form ~arrow ~expected-result ~overrides)] - (expect* check# ~fakes))) + (midje.semi-sweet/*expect-checking-fn* check# ~fakes))) -(defmethod expect-expansion :expect-macro* - [call-form arrow expected-result fakes overrides] +(defmethod expect-expansion =expands-to=> + [call-form _arrow_ expected-result fakes overrides] (let [expanded-macro `(macroexpand-1 '~call-form) escaped-expected-result `(quote ~expected-result)] (expect-expansion expanded-macro => escaped-expected-result fakes overrides))) -(defmethod expect-expansion :report-future-fact - [call-form arrow expected-result fakes overrides] - `(let [check# (unprocessed-check ~call-form ~arrow ~expected-result ~overrides)] - (within-fact-context ~(str call-form) - (clojure.test/report {:type :future-fact - :description (midje.internal-ideas.fact-context/nested-fact-description) - :position (:position check#)})))) +(defmethod expect-expansion =future=> + [call-form arrow expected-result _fakes_ overrides] + `(let [check# (unprocessed-check ~call-form ~arrow ~expected-result ~overrides)] + (within-fact-context ~(str call-form) + (clojure.test/report {:type :future-fact + :description @midje.internal-ideas.fact-context/nested-descriptions + :position (:position check#)})))) - ;;; Interface: unfinished +;;; Interface: unfinished (letfn [(unfinished* [names] (macro-for [name names] `(do (defn ~name [& args#] - (throw (user-error (str "#'" '~name - " has no implementation. It's used as a prerequisite in Midje tests.")))) + (let [pprint# (partial cl-format nil "~S")] + (throw (user-error (format "#'%s has no implementation, but it was called like this:%s(%s %s)" + '~name line-separator '~name (join " " (map pprint# args#))))))) + ;; A reliable way of determining if an `unfinished` function has since been defined. (alter-meta! (var ~name) assoc :midje/unfinished-fun ~name))))] @@ -130,7 +135,7 @@ - ;;; Interface: production mode +;;; Interface: production mode (defonce ^{:doc "True by default. If set to false, Midje checks are not @@ -139,7 +144,7 @@ *include-midje-checks* true) - ;;; Interface: Main macros +;;; Interface: Main macros (defmacro fake "Creates a fake map that a particular call will be made. When it is made, @@ -166,7 +171,7 @@ (defn- ^{:testable true } a-fake? [x] (and (seq? x) - (is-semi-sweet-keyword? (first x)))) + (semi-sweet-keyword? (first x)))) (defmacro expect "Run the call form, check that all the mocks defined in the fakes @@ -179,9 +184,13 @@ {:arglists '([call-form arrow expected-result & fakes+overrides])} [& _] (when (user-desires-checking?) - (valid-let [[call-form arrow expected-result & fakes+overrides] (validate &form) - [fakes overrides] (separate-by a-fake? fakes+overrides)] - (when-valid fakes - (expect-expansion call-form arrow expected-result fakes overrides))))) - - + (domonad validate-m [[call-form arrow expected-result & fakes+overrides] (validate &form) + [fakes overrides] (separate-by a-fake? fakes+overrides) + _ (validate fakes)] + (expect-expansion call-form arrow expected-result fakes overrides)))) + +(def ^{:dynamic true + :doc (str "For Midje tool creators. Hooks into Midje's internal compiler results. + Can be bound to a function with arglists like:" line-separator + " " (:arglists (meta #'midje.unprocessed/expect*)))} + *expect-checking-fn* midje.unprocessed/expect*) diff --git a/src/midje/sweet.clj b/src/midje/sweet.clj index db76fe35c..1eec39359 100644 --- a/src/midje/sweet.clj +++ b/src/midje/sweet.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "A TDD library for Clojure that supports top-down ('mockish') TDD, encourages readable tests, provides a smooth migration path from clojure.test, balances abstraction and concreteness, and strives for @@ -11,21 +9,24 @@ midje.error-handling.exceptions midje.error-handling.validation-errors midje.util.debugging - [midje.util.form-utils :only [macro-for]] + [midje.util.form-utils :only [macro-for pop-docstring]] [midje.internal-ideas.wrapping :only [put-wrappers-into-effect]] - [midje.internal-ideas.fact-context :only [nested-fact-description]] + [midje.internal-ideas.fact-context :only [nested-descriptions]] [midje.internal-ideas.file-position :only [set-fallback-line-number-from]] [midje.ideas.tabular :only [tabular*]] [midje.ideas.facts :only [complete-fact-transformation future-fact* midjcoexpand - future-fact-variant-names]]) + future-fact-variant-names]] + [midje.ideas.formulas :only [future-formula-variant-names]] + [clojure.algo.monads :only [domonad]]) (:require [midje.ideas.background :as background] + [midje.ideas.formulas :as formulas] midje.checkers - [midje.internal-ideas.report :as report])) + [midje.ideas.reporting.report :as report])) (immigrate 'midje.unprocessed) (immigrate 'midje.semi-sweet) -;; Following is required because `intern` doesn't transfer "dynamicity". +;; Following two are required because `intern` doesn't transfer "dynamicity". (def ^{:doc "True by default. If set to false, Midje checks are not included into production code, whether compiled or loaded." :dynamic true} @@ -39,17 +40,10 @@ :dynamic true} *cljs-file-under-test* *cljs-file-under-test*) -(intern+keep-meta *ns* 'before #'background/before) -(intern+keep-meta *ns* 'after #'background/after) -(intern+keep-meta *ns* 'around #'background/around) - -(defmacro expose-testables - "Enables testing of vars in the target ns which have ^:testable metadata" - [target-ns] - (macro-for [testable-sym (for [[sym var] (ns-interns target-ns) - :when (:testable (meta var))] - sym) ] - `(def ~testable-sym (intern '~target-ns '~testable-sym)))) +(intern+keep-meta *ns* 'before #'background/before) +(intern+keep-meta *ns* 'after #'background/after) +(intern+keep-meta *ns* 'around #'background/around) +(intern+keep-meta *ns* 'formula #'formulas/formula) (defmacro background "Runs facts against setup code which is run before, around, or after @@ -91,23 +85,22 @@ metaconstants, checkers, arrows and specifying call counts" [& forms] (when (user-desires-checking?) - (when-valid &form - (let [description (when (string? (first forms)) (first forms))] - (try - (set-fallback-line-number-from &form) - (let [[background remainder] (background/separate-background-forms forms)] - (if (seq background) - `(against-background ~background (midje.sweet/fact ~@remainder)) - (complete-fact-transformation description remainder))) - (catch Exception ex - `(do - (midje.internal-ideas.fact-context/within-fact-context ~description - (clojure.test/report {:type :exceptional-user-error - :description (midje.internal-ideas.fact-context/nested-fact-description) - :macro-form '~&form - :stacktrace '~(user-error-exception-lines ex) - :position (midje.internal-ideas.file-position/line-number-known ~(:line (meta &form)))})) - false))))))) + (domonad validate-m [[description forms] (validate &form)] + (try + (set-fallback-line-number-from &form) + (let [[background remainder] (background/separate-background-forms forms)] + (if (seq background) + `(against-background ~background (midje.sweet/fact ~@remainder)) + (complete-fact-transformation description remainder))) + (catch Exception ex + `(do + (midje.internal-ideas.fact-context/within-fact-context ~description + (clojure.test/report {:type :exceptional-user-error + :description @midje.internal-ideas.fact-context/nested-descriptions + :macro-form '~&form + :stacktrace '~(user-error-exception-lines ex) + :position (midje.internal-ideas.file-position/line-number-known ~(:line (meta &form)))})) + false)))))) (defmacro facts "Alias for fact." @@ -120,14 +113,23 @@ (macro-for [name future-fact-variant-names] `(defmacro ~(symbol name) "Fact that will not be run. Generates 'WORK TO DO' report output as a reminder." - {:arglists '([& ~'forms])} + {:arglists '([& forms])} + [& forms#] + (future-fact* ~'&form)))) + +(defmacro ^{:private true} generate-future-formula-variants [] + (macro-for [name future-formula-variant-names] + `(defmacro ~(symbol name) + "ALPHA/EXPERIMENTAL (subject to change) + Formula that will not be run. Generates 'WORK TO DO' report output as a reminder." + {:arglists '([& forms])} [& forms#] (future-fact* ~'&form)))) (generate-future-fact-variants) +(generate-future-formula-variants) -(defmacro - tabular +(defmacro tabular "Generate a table of related facts. Ex. (tabular \"table of simple math\" @@ -139,4 +141,19 @@ 9 10 19 )" {:arglists '([doc-string? fact table])} [& _] - (tabular* (keys &env) &form)) \ No newline at end of file + (tabular* (keys &env) &form)) + + +(defmacro metaconstants + "For a few operations, such as printing and equality checking, + the Clojure AOT-compiler becomes confused by Midje's auto-generation + of metaconstants. If AOT-compiled tests fail when on-the-fly + compiled tests failed, declare your metaconstants before use. + Example: + (metaconstants ..m.. ..m.... .mc.)" + [& names] + (let [defs (map (fn [name] + `(def ~name (midje.ideas.metaconstants.Metaconstant. '~name {}))) + names)] + `(do ~@defs))) + diff --git a/src/midje/unprocessed.clj b/src/midje/unprocessed.clj index e1da95450..0e9f8f6f6 100644 --- a/src/midje/unprocessed.clj +++ b/src/midje/unprocessed.clj @@ -1,61 +1,100 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Core Midje functions that process expects and report on their results."} midje.unprocessed (:use clojure.test - [midje.internal-ideas.fakes] - [midje.internal-ideas.fact-context :only [nested-fact-description]] [midje.ideas.background :only [background-fakes]] - midje.util.laziness - midje.internal-ideas.report [midje.checkers.extended-equality :only [extended-=]] [midje.checkers.chatty :only [chatty-checker?]] [midje.error-handling.exceptions :only [captured-throwable]] - [midje.util.namespace :only [immigrate]])) + midje.internal-ideas.fakes + midje.ideas.reporting.report + midje.util.laziness + [midje.util.namespace :only [immigrate]] + [midje.util.ecosystem :only [line-separator]] + [utilize.seq :only [find-first]])) (immigrate 'midje.checkers) -(defmulti ^{:private true} check-result (fn [actual call] - (:desired-check call))) +;; Formula Reporting - +;; one report for every batch of trials generated by a single formula run + +(def ^{:private true} formula-reports (atom [])) + +(defn ^{:private true} report-formula [report-map] + (when-not (= :pass (:type report-map)) + (note-failure-in-fact)) + (swap! formula-reports conj report-map)) + +(defn ^{:private true} report-formula-conclusion [_] + (if-let [failure (find-first #(not= :pass (:type %)) (reverse @formula-reports))] + (report failure) + (report {:type :pass}) ) + (reset! formula-reports [])) + + +;; Depending on the result, report appropriately (letfn [(fail [type actual call] {:type type - :description (nested-fact-description) + :description (:description call) :binding-note (:binding-note call) :position (:position call) :actual actual - :expected (:expected-result-text-for-failures call)})] - - (defmethod check-result :check-match [actual call] - (cond (extended-= actual (:expected-result call)) - (report {:type :pass}) - - (chatty-checker? (:expected-result call)) - (report (merge (fail :mock-expected-result-functional-failure actual call) - (let [chatty-result ((:expected-result call) actual)] - (if (map? chatty-result) - chatty-result - {:notes ["Midje program error. Please report." - (str "A chatty checker returned " - (pr-str chatty-result) - " instead of a map.")]})))) - - (fn? (:expected-result call)) - (report (fail :mock-expected-result-functional-failure actual call)) - - :else - (report (assoc (fail :mock-expected-result-failure actual call) - :expected (:expected-result call) )))) - - (defmethod check-result :check-negated-match [actual call] - (cond (not (extended-= actual (:expected-result call))) - (report {:type :pass}) - - (fn? (:expected-result call)) - (report (fail :mock-actual-inappropriately-matches-checker actual call)) + :expected (:expected-result-text-for-failures call)}) + + (check-result-positive [report-fn actual call] + (cond (extended-= actual (:expected-result call)) + (report-fn {:type :pass}) + + (fn? (:expected-result call)) + (report-fn (merge (fail :mock-expected-result-functional-failure + actual call) + ;; TODO: It is very lame that the + ;; result-function has to be called again to + ;; retrieve information that extended-= + ;; knows and threw away. + (or ( (:expected-result call) actual) + {}))) + + :else + (report-fn (assoc (fail :mock-expected-result-failure actual call) + :expected (:expected-result call))))) - :else - (report (fail :mock-expected-result-inappropriately-matched actual call))))) + (check-result-negated [report-fn actual call] + (cond (not (extended-= actual (:expected-result call))) + (report-fn {:type :pass}) + + (fn? (:expected-result call)) + (report-fn (fail :mock-actual-inappropriately-matches-checker actual call)) + + :else + (report-fn (fail :mock-expected-result-inappropriately-matched actual call))))] + + + (defmulti ^{:private true} check-result (fn [_actual_ call] + [(:desired-check call) (or (:formula call) :fact)] )) + + ;; Methods for processing =>/=not=> facts + + (defmethod check-result [:check-match :fact] [actual call] + (check-result-positive report actual call)) + + (defmethod check-result [:check-negated-match :fact] [actual call] + (check-result-negated report actual call)) + + + ;; Methods for processing formulas + + (defmethod check-result [:check-match :formula-in-progress] [actual call] + (check-result-positive report-formula actual call)) + + (defmethod check-result [:check-negated-match :formula-in-progress] [actual call] + (check-result-negated report-formula actual call)) + + (defmethod check-result [:check-match :formula-conclude] [actual call] + (check-result-positive report-formula-conclusion actual call)) + + (defmethod check-result [:check-negated-match :formula-conclude] [actual call] + (check-result-negated report-formula-conclusion actual call))) (defn expect* "The core function in unprocessed Midje. Takes a map describing a @@ -69,6 +108,6 @@ ((:function-under-test unprocessed-check))) (catch Throwable ex (captured-throwable ex)))] - (check-call-counts local-fakes) + (report-incorrect-call-counts local-fakes) (check-result code-under-test-result unprocessed-check) - :irrelevant-return-value))) \ No newline at end of file + :irrelevant-return-value))) diff --git a/src/midje/util.clj b/src/midje/util.clj new file mode 100644 index 000000000..e3fd18774 --- /dev/null +++ b/src/midje/util.clj @@ -0,0 +1,20 @@ +(ns ^{:doc "Utility code for testing private vars."} + midje.util + (:use [midje.util.form-utils :only [macro-for]])) + +(defmacro expose-testables + "Enables testing of vars in the target ns which have ^:testable metadata" + [target-ns] + (macro-for [[sym var] (ns-interns target-ns) + :when (:testable (meta var))] + `(-> (def ~sym ~var) + (alter-meta! merge (meta ~var))))) + +(defmacro testable-privates + "Intern into the current namespace the symbols from the specified namespace" + [namespace & symbols] + (macro-for [sym symbols, :let [var (ns-resolve namespace sym)]] + `(-> (def ~sym ~var) + (alter-meta! merge + (assoc (meta ~var) + :testable true))))) diff --git a/src/midje/util/backwards_compatible_utils.clj b/src/midje/util/backwards_compatible_utils.clj index b3366f325..452d26ca7 100644 --- a/src/midje/util/backwards_compatible_utils.clj +++ b/src/midje/util/backwards_compatible_utils.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Functions grabbed from newer versions of Clojure, so we can maintain backwards compatibility."} midje.util.backwards-compatible-utils) diff --git a/src/midje/util/colorize.clj b/src/midje/util/colorize.clj index c3529b42d..ad20a2c60 100644 --- a/src/midje/util/colorize.clj +++ b/src/midje/util/colorize.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Functions dealing with making various forms of Midje output be ergonomically colorful."} midje.util.colorize diff --git a/src/midje/util/debugging.clj b/src/midje/util/debugging.clj index 32b465afe..05cd82ab7 100644 --- a/src/midje/util/debugging.clj +++ b/src/midje/util/debugging.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Functions for printing indented output for use in debugging."} midje.util.debugging (:use [utilize.string :only (but-last-str)])) @@ -32,7 +30,7 @@ "Print the given value at current indent level, then decrease the level" [val] (p val) - (when (> @indent-count 0) + (when (pos? @indent-count) (swap! indent-count dec) (swap! indent #(str (but-last-str 2 %) ">"))) val) diff --git a/src/midje/util/ecosystem.clj b/src/midje/util/ecosystem.clj index d63436865..8e7db8fc8 100644 --- a/src/midje/util/ecosystem.clj +++ b/src/midje/util/ecosystem.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Environmental factors."} midje.util.ecosystem) @@ -14,11 +12,14 @@ (= 2 (:minor *clojure-version*)) (= 0 (:incremental *clojure-version*)))) +(defmacro when-1-3+ [& body] + (when-not (= 2 (:minor *clojure-version*)) + `(do ~@body))) + (defmacro unless-1-2-0 "Skip body completely - including 'Unable to resolve classname' errors." [& body] - (if (clojure-1-2-0?) - nil + (when-not (clojure-1-2-0?) `(do ~@body))) ;; The following works because in 1.2 it's parsed as [+ '1]. @@ -34,3 +35,5 @@ (defn on-windows? [] (re-find #"[Ww]in" (System/getProperty "os.name"))) + +(def line-separator (System/getProperty "line.separator")) diff --git a/src/midje/util/form_utils.clj b/src/midje/util/form_utils.clj index 80b669b5b..32c01cf5b 100644 --- a/src/midje/util/form_utils.clj +++ b/src/midje/util/form_utils.clj @@ -1,34 +1,11 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Utility functions dealing with checking or tranforming forms."} midje.util.form-utils (:use [midje.util.treelike :only [tree-variant]] + [midje.util.backwards-compatible-utils :only [every-pred-m]] [clojure.set :only [difference]] [utilize.seq :only (first-truthy-fn)]) (:require [clojure.zip :as zip])) -(defn unique-argument-name [] - (gensym 'symbol-for-destructured-arg)) - -(defn single-arg-into-form-and-name [arg-form] - (cond (vector? arg-form) - (if (= :as (second (reverse arg-form))) ; use existing as - [ arg-form (last arg-form)] - (let [as-symbol (unique-argument-name)] - [ (-> arg-form (conj :as) (conj as-symbol)) - as-symbol])) - - (map? arg-form) - (if (contains? arg-form :as) - [ arg-form (:as arg-form)] - (let [as-symbol (unique-argument-name)] - [ (assoc arg-form :as as-symbol) - as-symbol])) - - :else - [arg-form arg-form])) - - (defn regex? [x] (= (class x) java.util.regex.Pattern)) @@ -37,6 +14,10 @@ (defn record? [x] (and (map? x) (not (classic-map? x)))) + +(defn extended-fn? [x] + (or (fn? x) + (= (class x) clojure.lang.MultiFn))) (defn symbol-named? "Is the thing a symbol with the name given by the string?" @@ -56,6 +37,18 @@ (defmethod quoted? :form [form] (first-named? form "quote")) + +(defn reader-list-form? + "True if the form is a parenthesized list of the sort the reader can return." + [form] + (or (list? form) (= (type form) clojure.lang.Cons))) + +(defn quoted-list-form? + "True if the form is a quoted list such as the reader might return" + [form] + (and (reader-list-form? form) + (quoted? form))) + (defn preserve-type "If the original form was a vector, make the transformed form a vector too." [original-form transformed-form] @@ -125,13 +118,9 @@ (defn rotations "Returns a lazy seq of all rotations of a seq" - [x] - (if (seq x) - (map - (fn [n _] - (lazy-cat (drop n x) (take n x))) - (iterate inc 0) x) - (list nil))) + [coll] + (for [i (range 0 (count coll))] + (lazy-cat (drop i coll) (take i coll)))) (defmacro pred-cond "Checks each predicate against the item, returning the corresponding @@ -144,6 +133,17 @@ ~result (pred-cond ~item ~@preds+results)))) +(defn single-destructuring-arg->form+name [arg-form] + (let [as-symbol (gensym 'symbol-for-destructured-arg) + snd-to-last-is-as? #(= :as (second (reverse %))) + has-key-as? #(contains? % :as)] + (pred-cond arg-form + (every-pred-m vector? snd-to-last-is-as?) [arg-form (last arg-form)] + vector? [(-> arg-form (conj :as) (conj as-symbol)) as-symbol] + (every-pred-m map? has-key-as?) [arg-form (:as arg-form)] + map? [(assoc arg-form :as as-symbol) as-symbol] + :else [arg-form arg-form] ))) + (defmacro macro-for "Macroexpands the body once for each of the elements in the right-side argument of the bindings, which should be a seq" @@ -171,7 +171,7 @@ `(defmethod ~name ~dval ~args ~@body))) -;; stolen from `useful` +;;;; stolen from `useful` (defn var-name "Get the namespace-qualified name of a var." @@ -196,8 +196,64 @@ metadata (as provided by def) merged into the metadata of the original." [dst src] `(alias-var (quote ~dst) (var ~src))) +;;;; + (defmacro to-thunks - "Takes a seq of unevaluated exprs. Returns a seq of no argument fns, that call each of the exprs in turn" + "Takes a seq of unevaluated exprs. Returns a seq of no argument fns, + that call each of the exprs in turn" [exprs] - (into [] (for [x exprs] - `(fn [] ~x)))) \ No newline at end of file + (vec (for [x exprs] + `(fn [] ~x)))) + +(defn pop-if + "Extracts optional arg (that we assume is present if the pred is true) from head of args" + [pred args] + (if (pred (first args)) + [(first args) (rest args)] + [nil args])) + +(def pop-docstring + ;; "Extracts optional map from head of args" + (partial pop-if string?)) + +(def pop-opts-map + ;; "Extracts optional docstring from head of args" + (partial pop-if map?)) + + +;; Function references in forms to expand. These are usually symbols +;; but are sometimes the readable representation of vars: (var foo). +(defn classify-function-reference [reference] + (pred-cond reference + symbol? :symbol + sequential? :var-form + :else (throw (Exception. "Programmer error")))) + +(defmulti fnref-symbol classify-function-reference) +(defmethod fnref-symbol :symbol [reference] + reference) +(defmethod fnref-symbol :var-form [reference] + (second reference)) + +(defmulti fnref-call-form classify-function-reference) +(defmethod fnref-call-form :symbol [reference] + `(var ~reference)) +(defmethod fnref-call-form :var-form [reference] + reference) + +(defmulti fnref-dereference-form classify-function-reference) +(defmethod fnref-dereference-form :symbol [reference] + reference) +(defmethod fnref-dereference-form :var-form [reference] + `(deref ~reference)) + +;; Unlike other functions, this doesn't return homoiconic forms to +;; substitute into macroexpansions. Instead, it returns the actual +;; clojure.lang.Var object. +(defmulti fnref-var-object classify-function-reference) +(defmethod fnref-var-object :symbol [reference] + (resolve reference)) +(defmethod fnref-var-object :var-form [reference] + reference) + + diff --git a/src/midje/util/laziness.clj b/src/midje/util/laziness.clj index c8b637368..9c56a781c 100644 --- a/src/midje/util/laziness.clj +++ b/src/midje/util/laziness.clj @@ -1,9 +1,8 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "To evaluate a fact it needs to be eagerly evaluated."} midje.util.laziness (:use [midje.util.form-utils :only [pred-cond]] [midje.util.backwards-compatible-utils :only [some-fn-m]])) + (defn eagerly "Descend form, converting all lazy seqs into lists. Metadata is preserved. In the result all non-collections @@ -15,8 +14,8 @@ (let [m #(with-meta % (meta form))] (pred-cond form (some-fn-m seq? list?) (m (apply list (map eagerly form))) - vector? (m (vec (map eagerly form))) - map? (m (into form (map eagerly form))) - set? (m (into (if (sorted? form) (sorted-set) #{}) (map eagerly form))) - :else form))) + vector? (m (vec (map eagerly form))) + map? (m (into form (map eagerly form))) + set? (m (into (if (sorted? form) (sorted-set) #{}) (map eagerly form))) + :else form))) diff --git a/src/midje/util/namespace.clj b/src/midje/util/namespace.clj index d57be4b36..bd4a78801 100644 --- a/src/midje/util/namespace.clj +++ b/src/midje/util/namespace.clj @@ -4,18 +4,18 @@ (:require [clojure.zip :as zip])) -(defmulti matches-symbols-in-semi-sweet-or-sweet-ns? (fn [symbols treelike] (tree-variant treelike))) +(defmulti matches-symbols-in-semi-sweet-or-sweet-ns? (fn [_symbols_ treelike] (tree-variant treelike))) (defmethod matches-symbols-in-semi-sweet-or-sweet-ns? :zipper [symbols loc] (matches-symbols-in-semi-sweet-or-sweet-ns? symbols (zip/node loc))) (defmethod matches-symbols-in-semi-sweet-or-sweet-ns? :form [symbols node] - (let [base-names (map name symbols) - qualified-names (concat (map #(str "midje.semi-sweet/" %) base-names) - (map #(str "midje.sweet/" %) base-names))] - ( (set (concat base-names qualified-names)) (str node)))) + (let [base-names (map name symbols) + semi-sweet-names (map #(str "midje.semi-sweet/" %) base-names) + sweet-names (map #(str "midje.sweet/" %) base-names)] + (some #(= % (str node)) (concat base-names semi-sweet-names sweet-names)))) -(defn is-semi-sweet-keyword? [loc] +(defn semi-sweet-keyword? [loc] (matches-symbols-in-semi-sweet-or-sweet-ns? '(expect fake not-called data-fake) loc)) (defn immigrate diff --git a/src/midje/util/object_utils.clj b/src/midje/util/object_utils.clj index f5960350f..b13f1c723 100644 --- a/src/midje/util/object_utils.clj +++ b/src/midje/util/object_utils.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Functions having to do with looking at an object at runtime."} midje.util.object-utils) diff --git a/src/midje/util/thread_safe_var_nesting.clj b/src/midje/util/thread_safe_var_nesting.clj index f9c3f23f4..5a29716a1 100644 --- a/src/midje/util/thread_safe_var_nesting.clj +++ b/src/midje/util/thread_safe_var_nesting.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Code used to swap out vars for faking prerequisites."} midje.util.thread-safe-var-nesting) @@ -20,31 +18,30 @@ ;; Variables that might not be bound (def unbound-marker :midje/special-midje-unbound-marker) -(defn restore-one-root [[^clojure.lang.Var variable new-value]] +(defn restore-one-root [[^clojure.lang.Var the-var new-value]] (if (= new-value unbound-marker) - (.unbindRoot variable) - (alter-var-root variable (fn [current-value] new-value)))) - -(defn alter-one-root [[^clojure.lang.Var variable new-value]] - (if (bound? variable) - (let [old-value (deref variable)] - (alter-var-root variable (fn [current-value] new-value)) - [variable old-value]) - (do - (.bindRoot variable new-value) - [variable unbound-marker]))) - -(defn with-altered-roots* [binding-map function] - (let [old-bindings (into {} (for [pair binding-map] (alter-one-root pair)))] - (try (function) - ;; Can't use doseq inside a finally clause. - (finally (dorun (map restore-one-root old-bindings)))))) + (.unbindRoot the-var) + (alter-var-root the-var (fn [_current-value_] new-value)))) + +(defn alter-one-root [[^clojure.lang.Var the-var new-value]] + (if (bound? the-var) + (let [old-value (deref the-var)] + (alter-var-root the-var (fn [_current-value_] new-value)) + [the-var old-value]) + (do + (.bindRoot the-var new-value) + [the-var unbound-marker]))) + +(defn with-altered-roots* [binding-map f] + (let [old-bindings (into {} (for [var+new-value binding-map] (alter-one-root var+new-value)))] + (try (f) + (finally (dorun (map restore-one-root old-bindings)))))) (defmacro with-altered-roots "Used instead of with-bindings because bindings are thread-local and will require specially declared vars in Clojure 1.3" - [binding-map & rest] - `(with-altered-roots* ~binding-map (fn [] ~@rest))) + [binding-map & body] + `(with-altered-roots* ~binding-map (fn [] ~@body))) ;; Values associated with namespaces @@ -77,4 +74,4 @@ (finally (pop-from-namespace ~key-name)))) (defn namespace-values-inside-out [key-name] - (apply concat (namespace-value key-name))) + (apply concat (namespace-value key-name))) \ No newline at end of file diff --git a/src/midje/util/unify.clj b/src/midje/util/unify.clj index 35e2b701e..dea18d9a6 100644 --- a/src/midje/util/unify.clj +++ b/src/midje/util/unify.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns ^{:doc "Unification is used in tabular and (against-)background code."} midje.util.unify (:use [clojure.walk :only [prewalk]]) diff --git a/test/as_documentation/t_assumptions.clj b/test/as_documentation/t_assumptions.clj new file mode 100644 index 000000000..b0e06cfe5 --- /dev/null +++ b/test/as_documentation/t_assumptions.clj @@ -0,0 +1,116 @@ +(ns as-documentation.t-assumptions + (:use midje.sweet + midje.test-util)) + + +;; (comment + + +;; ;;; how to share prerequisites from other namespaces. Define a +;; ;;; function and call it. + +;; (comment + +;; (fact "something" +;; (assume-prerequisites (ping 1) => 200 +;; (ping "1") => 200 +;; (ping 'symbol) => 404) +;; (f 3) => 5 +;; (provided ...)) + +;; (prerequisite-group [(only-numberish-values-succeed?) => true] +;; (ping 1) => 200 +;; (ping "1") => 200 +;; (ping 'symbol) => 404) + +;; (fact "text" +;; (assume-prerequisite (only-numberish-values-succeed?) => true) +;; ...) + + + +;; (prerequisite-group [(numberish-value-status) => ?desired-status] +;; (ping 1) => ?desired-status +;; (ping "1") => ?desired-status +;; (ping 'symbol) => 404) + +;; (fact "text" +;; (assume-prerequisite (numberish-value-status) => 200) +;; ...) + + +;; (to-ensure [(log-contents) => ?checker] +;; (case ?checker +;; empty? (reset! log []) +;; (contains 5) (reset! log [1 2 5]))) + +;; (to-ensure [(log-contents) => ?checker] +;; (reset! log [])) +;; (to-ensure [(log-contents) => (contains ?value)] +;; (reset! log (range 0 ?value))) + + +;; (fact +;; (ensure-prerequisite (horse-count) => 5 +;; (cow-count) => 5) +;; ...) + +;; (to-ensure [(horse-count) => ?number] +;; (ensure-prerequisite (animal-count :species :horse) => ?number)) + + +;; (to-ensure [(animal-count :species ?species) => ?number] +;; (dotimes [n ?number] +;; (create-animal :species ?species +;; :name (str "horse-" n)))) + + + +;; (to-ensure [(horses + +;; (to-ensure [@log => empty?] +;; (reset! log [])) + +;; (fact +;; (ensure-prerequisite @log => empty?) +;; ;; Empty the log before every example +;; ) + + +;; (fact +;; (f 1) => 2 ; log in undefined state +;; (ensure-prerequisite @log => empty?) +;; (f 1) => 2) ; log is forced to be empty +;; ...) + + + +;; (fact +;; (ensure-prerequisite-here @log => empty?)) + + +;; (fact "big claim" +;; (assume-prerequisite (outer 1) => 5) +;; (fact "subclaim" +;; (assume-prerequisite (inner 1) => 5) +;; ;; examples here see both the inner and outer prerequisites +;; ) +;; ;; An example here would see only the outer prerequisite. +;; (fact "another subclaim") +;; (assume-prerequisite (inner 1) => "55555555555555555555") +;; ;; examples here see both prerequisites, but note that +;; ;; `(inner 1)` returns a different value. +;; ) + +;; ) + +;; (fact "you can insert into the database" +;; (ensure-prerequisite-here (table :table) => empty?) +;; (insert :table, :greeting "hi", :person "mom!") => truthy +;; (let [matches (select :table, :greeting "hi") +;; match (first matches)] +;; (count matches) => 1 +;; (:greeting match) => "hi" +;; (:person match) => "mom!")) + +;; ) diff --git a/test/as_documentation/t_defining_checkers.clj b/test/as_documentation/t_defining_checkers.clj new file mode 100644 index 000000000..e23cd3d15 --- /dev/null +++ b/test/as_documentation/t_defining_checkers.clj @@ -0,0 +1,166 @@ +(ns as-documentation.t-defining-checkers + (:use midje.sweet + midje.test-util)) + + ;;; Checkers that do not take arguments + +;; A checker can be any predicate you define. + +(defn even-length [actual-result] + (even? (count actual-result))) + +(fact [0 1 2 1 3] =not=> even-length) + +;; Such a checker works fine, so long as you use it on the +;; right-hand-side of an arrow. However, checkers are sometimes also +;; used in the argument list of prerequisites. So consider this +;; contrived example + +(unfinished utility-function) + +(defn function-under-test [sequence] + (if (utility-function sequence) 1 2)) + + +(run-silently + (fact + (function-under-test [:a :b]) => 2 + (provided + (utility-function even-length) => false))) + +(fact @reported => has-bad-result) + +;; The provided statement does NOT fake out the value of a call to +;; `utility-function` that gives it an even-length sequence. Instead, +;; it fakes out a call to `utility-function` that gives it the +;; **function** even-length. This behavior has proven to be less +;; confusing to people testing functions that take functions as +;; arguments. +;; +;; To get `even-length` to behave as a checker in prerequisites, you +;; need to define it specially: + +(defchecker even-length [actual-result] + (even? (count actual-result))) + +(run-silently + (fact + (function-under-test [:a :b]) => 2 + (provided + (utility-function even-length) => false)) +) +(fact @reported => passes) + + + ;;; Checkers that do take arguments + +;; Consider a checker that requires a collection to have only +;; certain elements. That's defined like this: + +(defchecker only-these [& expected-elements] + (checker [actual] + (every? (set expected-elements) actual))) + +;; This is a function that generates a checker function. The `checker` +;; on the second line is just like `fn` but additionally marks the +;; generated function as a checker, so that it can be used in +;; prerequisite argument lists. + +(run-silently + (fact + [1 2 3] => (only-these 1 2 3) + [1 2 3] => (only-these 1)) +) +(fact @reported => (just [pass checker-fails])) + + + ;;; Chatty checkers + +;; Suppose your function-under-test produces a map that must contain +;; the key `:foo` and must have the value of the key `:bar` be a +;; string with a given number of characters. You could easily check +;; one call of that function like this: + +(defn function-under-test [] + {:foo 1, :bar "foo"}) + +(fact + (let [actual (function-under-test)] + (contains? actual :foo) => truthy + (:bar actual) => string? + (count (:bar actual)) => 3)) + +;; But suppose these checks were ones you'd want to apply to many +;; different calls of many different functions, not just this +;; one. It'd be tedious and perhaps misleading to repeat the three +;; checks over and over. So you might produce a checker: + +(defchecker foobared [expected-count] + (checker [actual-map] + (and (contains? actual-map :foo) + (string? (:bar actual-map)) + (= (count (:bar actual-map)) expected-count)))) + +(run-silently + (fact + (function-under-test) => (foobared 30000)) +) +(fact @reported => (just [checker-fails])) + +;; This fails with this message (as of April 2012): +;; +;; FAIL at (t_defining_checkers.clj:105) +;; Actual result did not agree with the checking function. +;; Actual result: {:foo 1, :bar "foo"} +;; Checking function: (foobared 30000) +;; +;; That's not so helpful. You'd rather it told you *which* clause of +;; the `and` failed. You can accomplish that with a one-token change +;; to the definition: +;; + +(defchecker foobared [expected-count] + (chatty-checker [actual-map] ; <<== on this line + (and (contains? actual-map :foo) + (string? (:bar actual-map)) + (= (count (:bar actual-map)) expected-count)))) + +;; Running this function appends some "chattier" output: +;; +;; FAIL at (t_defining_checkers.clj:129) +;; Actual result did not agree with the checking function. +;; Actual result: {:foo 1, :bar "foo"} +;; Checking function: (foobared 30000) +;; During checking, these intermediate values were seen: +;; (contains? actual-map :foo) => true +;; (string? (:bar actual-map)) => true +;; (= (count (:bar actual-map)) expected-count) => false + +(run-silently + (fact + (function-under-test) => (foobared 30000)) +) +(fact @reported => (just [checker-fails])) + +;; If you want a simpler checker that doesn't take arguments, just use +;; `chatty-checker` with `def`: + +(def one-or-two + (chatty-checker [actual] + (or (= actual 1) + (= actual 2)))) + +(run-silently + (fact + 3 => one-or-two) + ) + +;; FAIL at (t_defining_checkers.clj:155) +;; Actual result did not agree with the checking function. +;; Actual result: 3 +;; Checking function: one-or-two +;; During checking, these intermediate values were seen: +;; (= actual 1) => false +;; (= actual 2) => false + +(fact @reported => (just [checker-fails])) diff --git a/test/as_documentation/t_prerequisites.clj b/test/as_documentation/t_prerequisites.clj new file mode 100644 index 000000000..73143706d --- /dev/null +++ b/test/as_documentation/t_prerequisites.clj @@ -0,0 +1,138 @@ +(ns as-documentation.t-prerequisites + (:use midje.sweet + midje.util + midje.test-util)) + +;; One development path is to work top-down and use the `provided` +;; clause to substitute prerequisite values. + +(unfinished lower-function) + +(defn top-function [n] + (+ (lower-function n) (lower-function (inc n)))) + +(fact + (top-function 5) => 55 + (provided + (lower-function 5) => 50 + (lower-function 6) => 5)) + +;; If you leave off a prerequisite, you get a helpful failure: + +(capturing-output + (fact + (top-function 5) => 55 + (provided + ;; (lower-function 5) => 50 ; omit this one. + (lower-function 6) => 5)) + ;; So... + (fact + @test-output => #"You never said lower-function would be needed with these arguments:" + @test-output => #"\(5\)")) + + +;; You also get a helpful failure for an unused prerequisite: + +(capturing-output + (fact + (top-function 5) => 5555 + (provided + (lower-function 3) => 5000 ; unused + (lower-function 4) => 500 ; unused + (lower-function 5) => 50 ; omit this one. + (lower-function 6) => 5)) + ;; So... + (fact + @test-output => #"These calls were not made the right number of times:" + @test-output => #"\(lower-function 3\) \[expected at least once, actually never called\]" + @test-output => #"\(lower-function 4\) \[expected at least once, actually never called\]" + ;; You also get a message about the failure: + @test-output => #"Expected: 5555" + @test-output => #"Actual: 55")) + + +;; By default, prerequisites can be called one or more times. The +;; :times modifier lets you change that. Here's how you can insist +;; a prerequisite be called twice: + +(capturing-output + (fact + (top-function 5) => 55 + (provided + (lower-function 5) => 50 :times 2 + (lower-function 6) => 5)) + ;; So... + (fact + @test-output => #"\(lower-function 5\) \[expected :times 2, actually called one time\]")) + +; You can also give a range of allowed values. Here's how you'd ask +; for a function to be called one or two times: + +(after-silently + (fact + (top-function 5) => 55 + (provided + (lower-function 5) => 50 :times [1 2] + (lower-function 6) => 5)) + (fact + @reported => (just pass))) + +;; You can also use a lazy sequence: + +(after-silently + (fact + (top-function 5) => 55 + (provided + (lower-function 5) => 50 :times (range 3 33) + (lower-function 6) => 5)) + (fact + @reported => (just wrong-call-count + pass))) ;; It does give the right answer, for the wrong reason. + + +;; Here is the idiom for "this call is optional" (zero or more times) + +(after-silently + (fact + (top-function 5) => 55 + (provided + (lower-function 0) => 88 :times (range) + (lower-function 5) => 50 + (lower-function 6) => 5)) + (fact + @reported => (just pass))) + + +;;; Default prerequisites + +;; Sometimes the prerequisite function already exists. What should +;; happen if there's no prerequisite for a particular argument list? +;; Should it default to the existing function or not? The Midje users +;; who care prefer that such a case be an error: + +(defn I-am-I-cried [n] n) + +(defn using-function [n] + (+ (I-am-I-cried n) (I-am-I-cried (inc n)))) + +(after-silently + (fact + (using-function 4) => (+ 80 4) + (provided + (I-am-I-cried 5) => 80)) + (fact + @reported => (contains no-matching-prerequisite bad-result))) + +;; However, it's also possible to ask that unmatched calls default to +;; the real values: + +(binding [midje.config/*allow-default-prerequisites* true] + + (after-silently + (fact + (using-function 4) => (+ 80 4) + (provided + (I-am-I-cried 5) => 80)) + (fact + @reported => (just pass)))) + diff --git a/test/as_documentation/t_streaming_prerequisites.clj b/test/as_documentation/t_streaming_prerequisites.clj new file mode 100644 index 000000000..bd233bfa0 --- /dev/null +++ b/test/as_documentation/t_streaming_prerequisites.clj @@ -0,0 +1,111 @@ +(ns as-documentation.t-streaming-prerequisites + (:use midje.sweet + midje.util + midje.test-util + midje.error-handling.exceptions)) + + +;; A typical called/caller relationship. The key thing is that +;; the caller calls the called more than once. +(defn number [] ) +(defn two-numbers [] + (+ (number) (number))) + + +;; This demonstrates the ordinary use of Midje. As with Clojure +;; itself, we assume idempotency: the called function will always +;; produce the same value. +(fact "a number can be doubled" + (two-numbers) => 6 + (provided + (number) => 3)) + +;; But what if the called function is stateful in some way? In that +;; case, successive calls can produce different values. That's +;; implemented with the =streams=> arrow: + +(fact "You can stream a sequence of values" + (two-numbers) => 7 + (provided + (number) =streams=> [3 4])) + +;; The idea of streaming values naturally suggests that the +;; right-hand-side of =streams=> could be a lazy sequence, with just +;; enough values created as they are consumed. + +(fact "That sequence of values can be a lazy sequence" + (two-numbers) => 1 + (provided + (number) =streams=> (range))) + +;; You could imagine a test in which the programmer said (1) the +;; function-under-test consumes values from a lazy sequence, but (2) +;; only a fixed number of them. That could look like this: + +(fact "Two values are consumed" + (two-numbers) => 1 + (provided + (number) =streams=> (range) :times 2)) + + +;; We want to be gracious about errors, so it should be that asking +;; for the n+1th value when there are only N fails helpfully: + +(def useful-message #"Your =stream=> ran out of values") + +(run-silently + (fact + (two-numbers) => 2 + (provided + (number) =streams=> [1]))) + +(fact + @reported => has-bad-result + @reported => (has-thrown-message useful-message)) + + +;; You can stream strings as seqs of characters +(unfinished a-char) + +(defn two-chars [] (list (a-char) (a-char))) + +(fact + (two-chars) => [\1 \2] + (provided (a-char) =streams=> "12")) + + + + +;;; Things that go without saying (though not without testing) + +;; The use of :times with a stream applies also to explicitly-named +;; stream values: + +(fact "Two values are consumed" + (two-numbers) => 7 + (provided + (number) =streams=> '[3 4 5 6 7 8] :times 2)) + +;; The :times case can also fail +(run-silently + (fact + (two-numbers) => 7 + (provided + (number) =streams=> '[3 4 5 6 7 8] :times 1))) + +(fact + @reported => has-wrong-call-count) + + +;; Lazy sequences that run out of values generate the +;; same error message as non-lazy sequentials. + +(run-silently + (fact + (two-numbers) => 2 + (provided + (number) =streams=> (range 1 2)))) + +(fact + @reported => has-bad-result + @reported => (has-thrown-message useful-message)) \ No newline at end of file diff --git a/test/as_documentation/testing_privates/privates_for_direct_access.clj b/test/as_documentation/testing_privates/privates_for_direct_access.clj new file mode 100644 index 000000000..fa2fe1546 --- /dev/null +++ b/test/as_documentation/testing_privates/privates_for_direct_access.clj @@ -0,0 +1,7 @@ +(ns as-documentation.testing-privates.privates-for-direct-access) + +(defn- da-function-without-prerequisite [n] (inc n)) + +(defn- da-called [n] (dec n)) + +(defn- da-caller [n] (da-called n)) diff --git a/test/as_documentation/testing_privates/privates_for_expose_testables.clj b/test/as_documentation/testing_privates/privates_for_expose_testables.clj new file mode 100644 index 000000000..5ab2679b4 --- /dev/null +++ b/test/as_documentation/testing_privates/privates_for_expose_testables.clj @@ -0,0 +1,13 @@ +(ns as-documentation.testing-privates.privates-for-expose-testables) + +(defn- ^{:testable true} + et-function-without-prerequisite [n] + (inc n)) + +(defn- ^{:testable true} + et-called [n] + (dec n)) + +(defn- ^{:testable true} + et-caller [n] + (et-called n)) diff --git a/test/as_documentation/testing_privates/privates_for_testable_privates.clj b/test/as_documentation/testing_privates/privates_for_testable_privates.clj new file mode 100644 index 000000000..7c138c4fa --- /dev/null +++ b/test/as_documentation/testing_privates/privates_for_testable_privates.clj @@ -0,0 +1,7 @@ +(ns as-documentation.testing-privates.privates-for-testable-privates) + +(defn- tp-function-without-prerequisite [n] (inc n)) + +(defn- tp-called [n] (dec n)) + +(defn- tp-caller [n] (tp-called n)) diff --git a/test/as_documentation/testing_privates/t_test.clj b/test/as_documentation/testing_privates/t_test.clj new file mode 100644 index 000000000..f9e203408 --- /dev/null +++ b/test/as_documentation/testing_privates/t_test.clj @@ -0,0 +1,53 @@ +(ns as-documentation.testing-privates.t-test + (:use midje.sweet + midje.util + midje.test-util)) + +;;; Calling private functions through vars (a common clojure idiom) + +(require '[as-documentation.testing-privates.privates-for-direct-access :as da]) + +(fact "calling private functions through their #'var" + (fact "allows direct testing" + (#'da/da-function-without-prerequisite 1) => 2) + + (fact "works with `provided`" + (#'da/da-caller 1) => 8988 + (provided + (#'da/da-called 1) => 8988))) + + ;;; Creating local vars for all privates marked testable. + +(require '[as-documentation.testing-privates.privates-for-expose-testables]) +(midje.util/expose-testables as-documentation.testing-privates.privates-for-expose-testables) + +(fact "expose-testables interns some privates in this namespace" + (fact "allows direct testing" + (et-function-without-prerequisite 1) => 2) + + (after-silently + (fact "DOES NOT works with `provided`" + (et-caller 1) => 8988 + (provided + (et-called 1) => 8988)) + (fact (first @reported) => a-validation-error))) + + + ;;; Creating local vars for all privates in another namespace + +(require '[as-documentation.testing-privates.privates-for-testable-privates]) +(midje.util/testable-privates as-documentation.testing-privates.privates-for-testable-privates + tp-function-without-prerequisite tp-called tp-caller) + +(fact "making privates testable by interning them" + (fact "allows direct testing" + (tp-function-without-prerequisite 1) => 2) + + (after-silently + (fact "DOES NOT works with `provided`" + (tp-caller 1) => 8988 + (provided + (tp-called 1) => 8988)) + (fact (first @reported) => a-validation-error))) + + diff --git a/test/behaviors/ProtocolsSupport.clj b/test/behaviors/ProtocolsSupport.clj index 47d30a118..5c04db3b7 100644 --- a/test/behaviors/ProtocolsSupport.clj +++ b/test/behaviors/ProtocolsSupport.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-protocols-support (:use midje.open-protocols)) diff --git a/test/behaviors/background_nesting/t_background_command.clj b/test/behaviors/background_nesting/t_background_command.clj index 7fc09bab9..d24157e44 100644 --- a/test/behaviors/background_nesting/t_background_command.clj +++ b/test/behaviors/background_nesting/t_background_command.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background_nesting.t-background-command (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_background_command_no_deftest.clj b/test/behaviors/background_nesting/t_background_command_no_deftest.clj index f12b9fb2a..93d7a94a6 100644 --- a/test/behaviors/background_nesting/t_background_command_no_deftest.clj +++ b/test/behaviors/background_nesting/t_background_command_no_deftest.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background_nesting.t-background-command-no-deftest (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_exception.clj b/test/behaviors/background_nesting/t_exception.clj index 9771f5a3e..b5aa06517 100644 --- a/test/behaviors/background_nesting/t_exception.clj +++ b/test/behaviors/background_nesting/t_exception.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background-nesting.t-exception (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_left_to_right.clj b/test/behaviors/background_nesting/t_left_to_right.clj index 4b2ce488a..66cc2e2db 100644 --- a/test/behaviors/background_nesting/t_left_to_right.clj +++ b/test/behaviors/background_nesting/t_left_to_right.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background-nesting.t-left-to-right (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_outside.clj b/test/behaviors/background_nesting/t_outside.clj index 662b18d3a..1cc649827 100644 --- a/test/behaviors/background_nesting/t_outside.clj +++ b/test/behaviors/background_nesting/t_outside.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background-nesting.t-outside (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_shadowing.clj b/test/behaviors/background_nesting/t_shadowing.clj index 28fc24f77..bb4ba25d5 100644 --- a/test/behaviors/background_nesting/t_shadowing.clj +++ b/test/behaviors/background_nesting/t_shadowing.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background-nesting.t-shadowing (:use clojure.test [midje sweet test-util] diff --git a/test/behaviors/background_nesting/t_shadowing_outside_background.clj b/test/behaviors/background_nesting/t_shadowing_outside_background.clj index bcdf5c072..ddb95d83f 100644 --- a/test/behaviors/background_nesting/t_shadowing_outside_background.clj +++ b/test/behaviors/background_nesting/t_shadowing_outside_background.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background-nesting.t-shadowing-outside-background (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_three_levels.clj b/test/behaviors/background_nesting/t_three_levels.clj index f246f4c3d..e601af1a8 100644 --- a/test/behaviors/background_nesting/t_three_levels.clj +++ b/test/behaviors/background_nesting/t_three_levels.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background-nesting.t-three-levels (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_three_levels_outside.clj b/test/behaviors/background_nesting/t_three_levels_outside.clj index fb520285b..dd23ce4a4 100644 --- a/test/behaviors/background_nesting/t_three_levels_outside.clj +++ b/test/behaviors/background_nesting/t_three_levels_outside.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background-nesting.t-three-levels-outside (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/background_nesting/t_without_deftest.clj b/test/behaviors/background_nesting/t_without_deftest.clj index 3158dcd17..2fcc6b568 100644 --- a/test/behaviors/background_nesting/t_without_deftest.clj +++ b/test/behaviors/background_nesting/t_without_deftest.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.background_nesting.t-without-deftest (:use clojure.test) (:use [midje.sweet]) diff --git a/test/behaviors/t_background_production_mode.clj b/test/behaviors/t_background_production_mode.clj index e1b3aa581..060d52ba2 100644 --- a/test/behaviors/t_background_production_mode.clj +++ b/test/behaviors/t_background_production_mode.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-background-production-mode (:use [midje.sweet]) (:use clojure.pprint)) diff --git a/test/behaviors/t_canary.clj b/test/behaviors/t_canary.clj index e41cbac9e..94d36158e 100644 --- a/test/behaviors/t_canary.clj +++ b/test/behaviors/t_canary.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - ;;; Not wildly important, these tests are older descriptions of ;;; bugs. They've been fixed, and more specific tests have been ;;; written, but no harm in keeping them around. Delete if they start diff --git a/test/behaviors/t_default_prerequisites.clj b/test/behaviors/t_default_prerequisites.clj index 3be48ce61..034db5eff 100644 --- a/test/behaviors/t_default_prerequisites.clj +++ b/test/behaviors/t_default_prerequisites.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-default-prerequisites (:use [midje sweet test-util] [midje.internal-ideas.fakes :only [tag-as-background-fake]])) diff --git a/test/behaviors/t_deprecation.clj b/test/behaviors/t_deprecation.clj index fe87e229b..93324435a 100644 --- a/test/behaviors/t_deprecation.clj +++ b/test/behaviors/t_deprecation.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-deprecation (:use [midje sweet test-util])) diff --git a/test/behaviors/t_error_handling_line_numbers.clj b/test/behaviors/t_error_handling_line_numbers.clj index 66d8a5cf0..872a8159a 100644 --- a/test/behaviors/t_error_handling_line_numbers.clj +++ b/test/behaviors/t_error_handling_line_numbers.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-error-handling-line-numbers (:use [midje sweet test-util] [midje.error-handling.validation-errors])) @@ -12,19 +10,19 @@ (unfinished f) (after-silently (fact (f) => 3 (provided ...movie... => (exactly odd?))) - (fact @reported => (just (this-file 14)))) + (fact @reported => (just (this-file 12)))) (after-silently (expect (f) => 3 (fake ...movie... => (exactly odd?))) - (fact @reported => (just (this-file 18)))) + (fact @reported => (just (this-file 16)))) (after-silently (fake ...movie... => 3) - (fact @reported => (just (this-file 22)))) + (fact @reported => (just (this-file 20)))) - ;; Different kinds of errors in facts. +;; Different kinds of errors in facts. (after-silently (fact (f) =>) - (fact @reported => (just (this-file 28)))) + (fact @reported => (just (this-file 26)))) diff --git a/test/behaviors/t_isolated_metaconstants.clj b/test/behaviors/t_isolated_metaconstants.clj index 89526f386..15594b4a4 100644 --- a/test/behaviors/t_isolated_metaconstants.clj +++ b/test/behaviors/t_isolated_metaconstants.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-isolated-metaconstants (:use [clojure.pprint]) (:use [midje sweet])) diff --git a/test/behaviors/t_lazy_evaluation_cases.clj b/test/behaviors/t_lazy_evaluation_cases.clj index 6cefde2fb..ba10dd4a9 100644 --- a/test/behaviors/t_lazy_evaluation_cases.clj +++ b/test/behaviors/t_lazy_evaluation_cases.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-lazy-evaluation-cases (:use [midje.sweet] [midje.util laziness thread-safe-var-nesting] diff --git a/test/behaviors/t_line_number_reporting.clj b/test/behaviors/t_line_number_reporting.clj index 1fc503652..392b866bb 100644 --- a/test/behaviors/t_line_number_reporting.clj +++ b/test/behaviors/t_line_number_reporting.clj @@ -1,14 +1,12 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-line-number-reporting (:use [midje.sweet]) (:use [clojure.test]) (:use [midje.test-util])) - +(binding [midje.config/*allow-default-prerequisites* false] (defn f [n] n) -(def position-1 11) +(def position-1 9) (after-silently (fact (+ 1 1) => 3) @@ -39,15 +37,15 @@ :position ["t_line_number_reporting.clj" (+ position-1 21)]})))) (defn g [n] n) -(def position-2 42) +(def position-2 40) (after-silently (fact (g 1) => 1 (provided (f 2) => 2)) (fact @reported => (just [ (contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ position-2 4)]}) - pass]))) + :failures (contains (contains {:position ["t_line_number_reporting.clj" (+ position-2 4)]})) }) + pass]))) (after-silently (fact (g 1) => 1 @@ -58,7 +56,7 @@ (f 2) => 2)) (fact @reported => (just [ (contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ position-2 16)]}) + :failures (contains (contains {:position ["t_line_number_reporting.clj" (+ position-2 16)]})) }) pass]))) (unfinished favorite-animal) @@ -76,7 +74,7 @@ (name (favorite-animal)) => "betsy")) (fact @reported => (just pass))) - (def line-number 79) + (def line-number 77) (after-silently (fact (favorite-animal-empty) => "betsy" @@ -84,15 +82,15 @@ (name (favorite-animal)) => "betsy")) (fact @reported => (just [ (contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ line-number 5)] - :expected-call "(name ...favorite-animal-value-1...)" }) - (contains {:type :mock-incorrect-call-count - :expected-call "(favorite-animal)" - :position ["t_line_number_reporting.clj" (+ line-number 5)]}) + :failures (just [(contains {:position ["t_line_number_reporting.clj" (+ line-number 5)] + :expected-call "(name ...favorite-animal-value-1...)" }) + (contains {:expected-call "(favorite-animal)" + :position ["t_line_number_reporting.clj" (+ line-number 5)]})] + )}) (contains {:type :mock-expected-result-failure :position ["t_line_number_reporting.clj" (+ line-number 3)]})]))) - (def line-number 95) + (def line-number 93) (after-silently (fact (favorite-animal-only-animal) => "betsy" @@ -100,11 +98,11 @@ (name (favorite-animal)) => "betsy")) (fact @reported => (just [ (contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ line-number 5)]}) + :failures (contains (contains {:position ["t_line_number_reporting.clj" (+ line-number 5)]})) }) (contains {:type :mock-expected-result-failure :position ["t_line_number_reporting.clj" (+ line-number 3)]})]))) - (def line-number 107) + (def line-number 105) (after-silently (fact (favorite-animal-only-name) => "betsy" @@ -112,20 +110,18 @@ (name (favorite-animal)) => "betsy")) (fact @reported => (just [ - ;; This used to produce a :mock-argument-match-failure because of - ;; (name "fred"). Since the name function actually exists, it's - ;; used. - (contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ line-number 5)] - :expected-call "(name ...favorite-animal-value-1...)"}) + (contains {:type :mock-argument-match-failure + :position ["t_line_number_reporting.clj" (+ line-number 5)]}) (contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ line-number 5)] - :expected-call "(favorite-animal)"}) + :failures (just [(contains {:position ["t_line_number_reporting.clj" (+ line-number 5)] + :expected-call "(name ...favorite-animal-value-1...)"}) + (contains {:position ["t_line_number_reporting.clj" (+ line-number 5)] + :expected-call "(favorite-animal)"})] )}) (contains {:type :mock-expected-result-failure :position ["t_line_number_reporting.clj" (+ line-number 3)]})]))) - (def line-number 128) + (def line-number 125) (after-silently (fact (favorite-animal-one-call) => "betsy" @@ -134,14 +130,14 @@ (name (favorite-animal 2)) => "jake")) ;; a folded prerequisite can have two errors. (fact @reported => (just [(contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ line-number 6)] - :expected-call "(name ...favorite-animal-value-2...)"}) - (contains {:type :mock-incorrect-call-count - :position ["t_line_number_reporting.clj" (+ line-number 6)] - :expected-call "(favorite-animal 2)"}) - pass ])))) - -(def line-number-separate 144) + :failures (just [(contains {:position ["t_line_number_reporting.clj" (+ line-number 5)] + :expected-call "(name ...favorite-animal-value-2...)"}) + (contains {:position ["t_line_number_reporting.clj" (+ line-number 5)] + :expected-call "(favorite-animal 2)"}) ])}) + pass])))) + + + (def line-number-separate 140) (unfinished outermost middlemost innermost) (in-separate-namespace (background (outermost) => 2) @@ -161,18 +157,18 @@ ;; future facts (after-silently (future-fact "text") - (fact @reported => (just (contains {:position '("t_line_number_reporting.clj" 163) - :description "text" })))) + (fact @reported => (just (contains {:position '("t_line_number_reporting.clj" 159) + :description ["text"] })))) (after-silently (pending-fact (+ 1 1) => 2) - (fact @reported => (just (contains {:position '("t_line_number_reporting.clj" 168) - :description nil })))) + (fact @reported => (just (contains {:position '("t_line_number_reporting.clj" 164) + :description [nil] })))) ;; Improved error handling for pathological cases -(def line-number-pathological 175) +(def line-number-pathological 171) ;; statements without lists guess 1+ most recent" (after-silently (fact @@ -201,7 +197,7 @@ (+ line-number-pathological 23)]})]))) -(def facts-position 204) +(def facts-position 200) (after-silently (facts "... also use fallback line number" 1 => even? @@ -221,7 +217,7 @@ ;; Line number reporting for variant expect arrows -(def variant-position 224) +(def variant-position 220) (after-silently (fact (+ 1 1) =deny=> 2 @@ -235,7 +231,7 @@ (+ variant-position 5)]})))) -(def tabular-position 238) +(def tabular-position 234) (after-silently (tabular (fact (inc ?n) => ?n) @@ -249,3 +245,4 @@ (contains {:position ["t_line_number_reporting.clj" (+ tabular-position 3)] :binding-note "[?n 2\n ?comment \"2\"]"})))) +) diff --git a/test/behaviors/t_macros.clj b/test/behaviors/t_macros.clj index ba9e5640e..0b4b203fd 100644 --- a/test/behaviors/t_macros.clj +++ b/test/behaviors/t_macros.clj @@ -24,15 +24,12 @@ `(str ~arg (first (macro-with-only-expansion (list ~@others))))) (facts "about expecting on macro expansion" - (fact "macros with expansion code only work" - (macro-with-only-expansion (list 1 2 3)) => (list 10 20 30) - (macro-with-only-expansion (list 1 2 3)) =expands-to=> (clojure.core/map behaviors.t-macros/times-ten (list 1 2 3))) - - (fact "macros with expander code work" - (macro-with-expander 666 10 20 30) => "666-suffix(was 666)60" - (macro-with-expander 666 10 20 30) =expands-to=> (clojure.core/str "666-suffix" "(was " 666 ")" (clojure.core/apply clojure.core/+ '(10 20 30)))) - - (fact "macros calling other macros are macroexpand-1" - (macro-calling-other-macro 999 [ 10 20 30]) => "999100" - (macro-calling-other-macro 999 [ 10 20 30]) =expands-to=> (clojure.core/str 999 (clojure.core/first (behaviors.t-macros/macro-with-only-expansion (clojure.core/list 10 20 30)))))) + (fact "macros with expansion code only" + (macro-with-only-expansion (list 1 2 3)) =expands-to=> (clojure.core/map behaviors.t-macros/times-ten (list 1 2 3))) + + (fact "macros with expander code" + (macro-with-expander 666 10 20 30) =expands-to=> (clojure.core/str "666-suffix" "(was " 666 ")" (clojure.core/apply clojure.core/+ '(10 20 30)))) + + (fact "macros calling other macros use macroexpand-1" + (macro-calling-other-macro 999 [10 20 30]) =expands-to=> (clojure.core/str 999 (clojure.core/first (behaviors.t-macros/macro-with-only-expansion (clojure.core/list 10 20 30)))))) diff --git a/test/behaviors/t_protocols.clj b/test/behaviors/t_protocols.clj index b84b7583b..a042efc96 100644 --- a/test/behaviors/t_protocols.clj +++ b/test/behaviors/t_protocols.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-protocols (:use [midje sweet test-util] [midje.open-protocols] diff --git a/test/behaviors/t_protocols_support.clj b/test/behaviors/t_protocols_support.clj index 47d30a118..5c04db3b7 100644 --- a/test/behaviors/t_protocols_support.clj +++ b/test/behaviors/t_protocols_support.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-protocols-support (:use midje.open-protocols)) diff --git a/test/behaviors/t_setup_teardown.clj b/test/behaviors/t_setup_teardown.clj index 538738c9c..fac80b166 100644 --- a/test/behaviors/t_setup_teardown.clj +++ b/test/behaviors/t_setup_teardown.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns behaviors.t-setup-teardown (:use [midje.sweet]) (:use [midje.test-util]) diff --git a/test/midje/checkers/t_chatty.clj b/test/midje/checkers/t_chatty.clj index 1c623b6ec..3fad14862 100644 --- a/test/midje/checkers/t_chatty.clj +++ b/test/midje/checkers/t_chatty.clj @@ -1,23 +1,15 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.checkers.t-chatty (:use midje.sweet [midje.checkers.defining :only [checker?]] - [midje.checkers.chatty :only [chattily-false? as-chatty-falsehood - chatty-worth-reporting-on? - chatty-checker-falsehood? chatty-untease + [midje.checkers.chatty :only [chatty-worth-reporting-on? + chatty-untease chatty-checker?]] + [midje.checkers.extended-falsehood :only [data-laden-falsehood?]] midje.test-util clojure.pprint)) -(facts "about an extended notion of falsehood" - (chattily-false? false) => truthy - (chattily-false? true) => falsey - (chattily-false? {:intermediate-results 3}) => falsey - (chattily-false? (as-chatty-falsehood {})) => truthy) (facts "about chatty-checking utility functions" - (as-chatty-falsehood [5]) => chatty-checker-falsehood? (chatty-untease 'g-101 '()) => [[] []] @@ -53,12 +45,12 @@ (actual-plus-one-equals-4 3) => true (let [result (actual-plus-one-equals-4 4)] - result => chatty-checker-falsehood? + result => data-laden-falsehood? result => {:actual 4 :intermediate-results [ ['(inc actual) 5] ] }) (let [result (no-longer-limited-form 4)] - result => chatty-checker-falsehood? + result => data-laden-falsehood? result => {:actual 4 :intermediate-results [ ['(inc actual) 5] ['(+ 2 actual) 6] ]})) diff --git a/test/midje/checkers/t_collection.clj b/test/midje/checkers/t_collection.clj index fd7ec899c..dda7880a7 100644 --- a/test/midje/checkers/t_collection.clj +++ b/test/midje/checkers/t_collection.clj @@ -1,10 +1,9 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.checkers.t-collection (:use [midje sweet test-util] [midje.checkers.defining :only [checker?]] - [midje.checkers.chatty :only [chatty-falsehood-to-map - chatty-checker-falsehood?]])) + [midje.checkers.extended-falsehood :only [data-laden-falsehood-to-map + data-laden-falsehood?]] + midje.util)) (expose-testables midje.checkers.collection) (defrecord AB [a b]) @@ -142,11 +141,11 @@ ( (has-suffix #"\d+") "12x") => falsey ( (has-prefix #"\d+") "x12") => falsey - (chatty-falsehood-to-map ( (contains #"a" :in-any-order) "a")) + (data-laden-falsehood-to-map ( (contains #"a" :in-any-order) "a")) => (contains {:actual "a", :notes (just #"regular expression.*:in-any-order")}) ["a"] => (contains #"a" :in-any-order) ; this is OK because the singleton becomes a vector - (chatty-falsehood-to-map ( (contains #"a" :gaps-ok) "a")) + (data-laden-falsehood-to-map ( (contains #"a" :gaps-ok) "a")) => (contains {:actual "a", :notes (just #"regular expression.*:gaps-ok")}) ["a"] => (contains #"a" :gaps-ok) ; this is OK because the singleton becomes a vector @@ -243,13 +242,13 @@ #{{:a 1} {:b 2}} => (contains [{:a 1} {:b 2}]) #{{:a 1} {:b 2}} => (contains {:a 1} {:b 2}) - (chatty-falsehood-to-map ( (has-prefix 1) #{1})) + (data-laden-falsehood-to-map ( (has-prefix 1) #{1})) => (contains {:actual #{1} :notes ["Sets don't have prefixes."]}) - (chatty-falsehood-to-map ( (has-suffix 1) #{1})) + (data-laden-falsehood-to-map ( (has-suffix 1) #{1})) => (contains {:actual #{1} :notes ["Sets don't have suffixes."]}) - (chatty-falsehood-to-map ( (has-prefix 1) {:a 1})) + (data-laden-falsehood-to-map ( (has-prefix 1) {:a 1})) => (contains {:actual {:a 1} :notes ["Maps don't have prefixes."]}) - (chatty-falsehood-to-map ( (has-suffix 1) {:a 1})) + (data-laden-falsehood-to-map ( (has-suffix 1) {:a 1})) => (contains {:actual {:a 1} :notes ["Maps don't have suffixes."]}) ) @@ -300,10 +299,10 @@ {:a 1, :b odd?} => (contains [:a 1] [:b (exactly odd?)]) ( (just [ [:a 1] ]) {:a 1, :b 1}) => falsey - (chatty-falsehood-to-map ( (contains [:a 1]) {:a 1})) + (data-laden-falsehood-to-map ( (contains [:a 1]) {:a 1})) => (contains {:actual {:a 1} :notes (just #"\{:a 1\} is a map.*\[:a 1\]")}) ;; By the way, that means it'll be counted as false: - ( (contains [:a 1]) {:a 1}) => chatty-checker-falsehood? + ( (contains [:a 1]) {:a 1}) => data-laden-falsehood? ( (contains [1]) {:a 1}) => (contains {:actual {:a 1} :notes (just #"\{:a 1\} is a map.*\[1\]")}) @@ -353,103 +352,103 @@ (facts "where actual values are of wrong type for legitimate expected" - (chatty-falsehood-to-map ( (just "string") 1)) + (data-laden-falsehood-to-map ( (just "string") 1)) => (contains {:actual 1}) - (chatty-falsehood-to-map ( (just {:a 1}) 1)) + (data-laden-falsehood-to-map ( (just {:a 1}) 1)) => (contains {:actual 1 :notes (just #"compare 1.*to \{:a 1\}")}) - (chatty-falsehood-to-map ( (contains \s) 1)) + (data-laden-falsehood-to-map ( (contains \s) 1)) => (contains {:actual 1 :notes (just #"compare 1.*to \\s")}) ( (contains [1 2]) 1) => (contains {:actual 1 :notes (just #"compare 1.*to \[1 2\]")}) - (chatty-falsehood-to-map ( (just #"ab") 1)) + (data-laden-falsehood-to-map ( (just #"ab") 1)) => (contains {:actual 1 :notes (just #"#\"ab\" can't be used on 1")}) - (chatty-falsehood-to-map ( (contains #"ab") 1)) + (data-laden-falsehood-to-map ( (contains #"ab") 1)) => (contains {:actual 1 :notes (just #"#\"ab\" can't be used on 1")}) - (chatty-falsehood-to-map ( (has-prefix #"ab") 1)) + (data-laden-falsehood-to-map ( (has-prefix #"ab") 1)) => (contains {:actual 1 :notes (just #"#\"\^ab\" can't be used on 1")}) - (chatty-falsehood-to-map ( (has-suffix #"ab") 1)) + (data-laden-falsehood-to-map ( (has-suffix #"ab") 1)) => (contains {:actual 1 :notes (just #"#\"ab\$\" can't be used on 1")}) - (chatty-falsehood-to-map ( (contains {:a 1, :b 2}) {:a 1})) + (data-laden-falsehood-to-map ( (contains {:a 1, :b 2}) {:a 1})) => (contains {:actual {:a 1} :notes (just "Best match found: {:a 1}")}) - (chatty-falsehood-to-map ( (just {:a 1, :b 2}) {:a 1})) + (data-laden-falsehood-to-map ( (just {:a 1, :b 2}) {:a 1})) => (contains {:actual {:a 1} :notes (just #"Expected two elements.*one")}) - (chatty-falsehood-to-map ( (contains {:a {:b 1}}) {:a 1}) ) + (data-laden-falsehood-to-map ( (contains {:a {:b 1}}) {:a 1}) ) => (contains {:actual {:a 1} :notes (just "Best match found: {}")}) ;; Won't work in Clojure 1.3 without some major rework. - (chatty-falsehood-to-map ( (contains {:a odd?, :f odd? :g odd?}) {:f 3, :g 6, :a 1}) ) + (data-laden-falsehood-to-map ( (contains {:a odd?, :f odd? :g odd?}) {:f 3, :g 6, :a 1}) ) =future=> (contains {:actual {:f 3, :g 6, :a 1} :notes (just [#"Best match found: \{:a 1, :f 3\}\." #"It matched: \{:a odd\?, :f odd\?\}\."])}) - (chatty-falsehood-to-map ( (contains :a) {:a 1})) + (data-laden-falsehood-to-map ( (contains :a) {:a 1})) => (contains {:actual {:a 1}, :notes (just #"\{:a 1\}.*:a.*map entries")}) ) (facts "about the notes given to reporting functions" "functions and such are printed nicely in the actual match section" - (chatty-falsehood-to-map ( (contains [#"1" #"1+" #"1+2"]) [#"1" #"1+"])) + (data-laden-falsehood-to-map ( (contains [#"1" #"1+" #"1+2"]) [#"1" #"1+"])) => (contains {:notes (contains #"Best match.*\[#\"1\" #\"1\+\"\]")}) ; It'd be nice to make all kinds of recursive function printing work nicely. ; [odd? even?] => (contains [(exactly odd?) (exactly odd?)]) "checkers are printed nicely in the expected matched: section" - (chatty-falsehood-to-map ( (contains [5 (exactly 4)] :in-any-order) [1 2 4])) + (data-laden-falsehood-to-map ( (contains [5 (exactly 4)] :in-any-order) [1 2 4])) => (contains {:notes (contains #"It matched.*\[\(exactly 4\)\]")}) - (chatty-falsehood-to-map ( (contains [(just 3) 6]) [[3] 5])) + (data-laden-falsehood-to-map ( (contains [(just 3) 6]) [[3] 5])) => (contains {:notes (contains #"It matched.*\[\(just 3\)\]")}) - (chatty-falsehood-to-map ( (contains [(contains 3) 6]) [[3] 5])) + (data-laden-falsehood-to-map ( (contains [(contains 3) 6]) [[3] 5])) => (contains {:notes (contains #"It matched.*\[\(contains 3\)\]")}) - (chatty-falsehood-to-map ( (contains [(has-prefix 3) 6]) [[3] 5])) + (data-laden-falsehood-to-map ( (contains [(has-prefix 3) 6]) [[3] 5])) => (contains {:notes (contains #"It matched.*\[\(has-prefix 3\)\]")}) - (chatty-falsehood-to-map ( (contains [(has-suffix 3) 6]) [[3] 5])) + (data-laden-falsehood-to-map ( (contains [(has-suffix 3) 6]) [[3] 5])) => (contains {:notes (contains #"It matched.*\[\(has-suffix 3\)\]")}) - (chatty-falsehood-to-map ( (contains [#"fo+\[" "ba"]) ["foo[" "bar"])) + (data-laden-falsehood-to-map ( (contains [#"fo+\[" "ba"]) ["foo[" "bar"])) => (contains {:notes (contains #"It matched.*\[#\"fo\+\\\[\"\]")}) - (chatty-falsehood-to-map ( (contains [1 "1\"2" [even?] odd?]) [1 "1\"2" [3]])) + (data-laden-falsehood-to-map ( (contains [1 "1\"2" [even?] odd?]) [1 "1\"2" [3]])) => (contains {:notes (contains #"It matched.*\[1 \"1\\\"2\"\]")}) "Proper grammar for just errors" - (chatty-falsehood-to-map ( (just 1) [1 2])) + (data-laden-falsehood-to-map ( (just 1) [1 2])) => (contains {:notes ["Expected one element. There were two."]}) - (chatty-falsehood-to-map ( (just 1) [])) + (data-laden-falsehood-to-map ( (just 1) [])) => (contains {:notes ["Expected one element. There were zero."]}) - (chatty-falsehood-to-map ( (just []) [1])) + (data-laden-falsehood-to-map ( (just []) [1])) => (contains {:notes ["Expected zero elements. There was one."]}) - (chatty-falsehood-to-map ( (just [1 2]) [1])) + (data-laden-falsehood-to-map ( (just [1 2]) [1])) => (contains {:notes ["Expected two elements. There was one."]}) - (chatty-falsehood-to-map ( (just #{1}) [1 1])) + (data-laden-falsehood-to-map ( (just #{1}) [1 1])) => (contains {:notes ["Expected one element. There were two."]}) - (chatty-falsehood-to-map ((has-prefix '(a b c)) '(a))) + (data-laden-falsehood-to-map ((has-prefix '(a b c)) '(a))) => (contains {:notes ["A collection with one element cannot match a prefix of size three."]}) - (chatty-falsehood-to-map ((has-suffix '(1)) '())) + (data-laden-falsehood-to-map ((has-suffix '(1)) '())) => (contains {:notes ["A collection with zero elements cannot match a suffix of size one."]}) ) (facts "where expected values are of wrong type for legitimate actual" - (chatty-falsehood-to-map ( (just "hi") '(1))) + (data-laden-falsehood-to-map ( (just "hi") '(1))) => (contains {:actual (list 1) :notes (just #"\[\]")}) - (chatty-falsehood-to-map ( (just (atom 0)) '(0))) + (data-laden-falsehood-to-map ( (just (atom 0)) '(0))) => (contains {:actual '(0) :notes (just #"\[\]")}) - (chatty-falsehood-to-map ( (contains :a) {:a 1})) + (data-laden-falsehood-to-map ( (contains :a) {:a 1})) => (contains {:actual {:a 1} :notes (just #"\{:a 1\}.*:a.*map entries")}) - (chatty-falsehood-to-map ( (contains 1) {:a 1})) + (data-laden-falsehood-to-map ( (contains 1) {:a 1})) => (contains {:actual {:a 1} :notes (just #"\{:a 1\}.*1.*map entries")}) - (chatty-falsehood-to-map ( (just (AB. 1 2)) {:a 1 :b 2})) + (data-laden-falsehood-to-map ( (just (AB. 1 2)) {:a 1 :b 2})) => (contains {:actual {:a 1 :b 2} :notes (just #"AB.*but.*was.*map")}) - (chatty-falsehood-to-map ( (just (AB. 1 2)) (AB-different-class. 1 2))) + (data-laden-falsehood-to-map ( (just (AB. 1 2)) (AB-different-class. 1 2))) => (contains {:actual (AB-different-class. 1 2) :notes (just #"AB.*but.*was.*AB-different-class")}) ) @@ -468,8 +467,8 @@ [1 3 ] => (n-of odd? 2) ["ab" "aab" "aaab"] => (n-of #"a+b" 3) - ( (n-of odd? 1) [1 3]) => chatty-checker-falsehood? - ( (n-of odd? 3) [1 2 3]) => chatty-checker-falsehood? + ( (n-of odd? 1) [1 3]) => data-laden-falsehood? + ( (n-of odd? 3) [1 2 3]) => data-laden-falsehood? [1 1 3 3 5 5 7 7 9 9] => (ten-of odd?) [1 1 3 3 5 5 7 7 9] => (nine-of odd?) @@ -487,8 +486,8 @@ [1 3 2] =not=> (n-of odd? 2) ["ab" "aab" "aaab" "ccc"] =not=> (n-of #"a+b" 3) - ( (n-of odd? 1) [1]) =not=> chatty-checker-falsehood? - ( (n-of odd? 3) [1 3 5]) =not=> chatty-checker-falsehood? + ( (n-of odd? 1) [1]) =not=> data-laden-falsehood? + ( (n-of odd? 3) [1 3 5]) =not=> data-laden-falsehood? [1 1 3 3 5 5 7 7 9 9 11] =not=> (ten-of odd?) [1 1 3 3 5 5 7 7 9 9] =not=> (nine-of odd?) diff --git a/test/midje/checkers/t_collection_old.clj b/test/midje/checkers/t_collection_old.clj index a563838e1..c51e1a175 100644 --- a/test/midje/checkers/t_collection_old.clj +++ b/test/midje/checkers/t_collection_old.clj @@ -1,9 +1,7 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.checkers.t-collection-old (:use [midje sweet test-util] - [midje.checkers.chatty :only [chatty-falsehood-to-map - chatty-checker-falsehood?]])) + [midje.checkers.extended-falsehood :only [data-laden-falsehood-to-map + data-laden-falsehood?]])) ;; These are still potentially useful tests from a misguided code organization. ;; Delete them as they fail (after moving better tests to t-collection. @@ -48,9 +46,9 @@ ((has-prefix :a) [{ :a 1 }]) => falsey "sets" - ((has-prefix :a) #{:a}) => chatty-checker-falsehood? - ((has-prefix #{:a}) #{:a 1}) => chatty-checker-falsehood? - ((has-prefix [:a]) #{:a 1}) => chatty-checker-falsehood? + ((has-prefix :a) #{:a}) => data-laden-falsehood? + ((has-prefix #{:a}) #{:a 1}) => data-laden-falsehood? + ((has-prefix [:a]) #{:a 1}) => data-laden-falsehood? "mixtures" [1 2 4] => (has-prefix '(1)) diff --git a/test/midje/checkers/t_combining.clj b/test/midje/checkers/t_combining.clj new file mode 100644 index 000000000..bd21ba302 --- /dev/null +++ b/test/midje/checkers/t_combining.clj @@ -0,0 +1,101 @@ +(ns midje.checkers.t-combining + (:use midje.sweet + clojure.pprint + [midje.checkers.defining :only [checker?]] + midje.checkers.extended-falsehood + midje.test-util)) + +(fact "about 'every' combinations" + (let [checker (every-checker odd? + (roughly 5 3) + (fn [actual] (= (str actual) "5" )))] + + checker => checker? + + (checker 5) => truthy + (checker 400) => falsey ; not odd + (checker 99) => falsey ; not close to 5 + (checker 3) => falsey ; not "5" + + 5 => checker + 400 =not=> checker + 99 =not=> checker + 3 =not=> checker)) + + +;; every-checker +(facts "about form of the failure value" + (let [sanitized (fn [actual] + (data-laden-falsehood-to-map + ( (every-checker odd? + (roughly 5 3) + (fn [actual] (= (str actual) "5" ))) + actual)))] + "In case of error, the actual value is reported" + (sanitized 400) => (contains {:actual 400}) + + (sanitized 400) => (contains {:intermediate-results + [['odd? false]]}) + (sanitized 99) => (contains {:intermediate-results + [['(roughly 5 3) false]]}) + (sanitized 3) => (contains {:intermediate-results + [['(fn [actual] (= (str actual) "5" )) false]]}) + )) + +(def mychatty (chatty-checker [actual] (or (= actual 88) (= actual 99)))) + +(fact "chatty checkers can be wrapped in every-checker" + ;; But their chatty results are not propagated. + (let [checker (every-checker mychatty)] + (data-laden-falsehood-to-map (checker 3)) => {:actual 3 + :intermediate-results + [['mychatty false]]}) + "containers are another form of chatty checker" + {:a 1} =not=> (every-checker (contains {:b 1}))) + +(fact "the empty every-checker passes" + 5 => (every-checker)) + +(def hit-count (atom 0)) +(fact "the first failure short-circuits the rest" + 5 =not=> (every-checker even? + (fn [_] (swap! hit-count inc))) + @hit-count => 0) + + + ;; some-checker + + +(facts "about some-checker" + (some-checker truthy falsey) => checker? + 3 => (some-checker truthy falsey) + 3 =not=> (some-checker falsey string?) + {:a 1} =not=> (some-checker (contains {:b 1}))) + +(facts "about form of the failure value" + (let [checker (some-checker odd? + (roughly 5 3) + (fn [actual] (= (str actual) "6" )))] + (checker 400) => false + (checker 501) => true + (checker 4) => true + (checker 6) => true)) + +(fact "chatty checkers can be wrapped in some-checker" + (let [checker (some-checker (chatty-checker [actual] + (or (= actual 88) (= actual 99))) + even?)] + (checker 2) => true + (checker 88) => true + (checker 3) => false)) + +(fact "the empty some-checker false" + 5 =not=> (some-checker)) + +(def hit-count (atom 0)) +(fact "the first success short-circuits the rest" + 4 => (some-checker even? + (fn [_] (swap! hit-count inc))) + @hit-count => 0) + + diff --git a/test/midje/checkers/t_defining.clj b/test/midje/checkers/t_defining.clj index 5199b1819..33fc9bcb2 100644 --- a/test/midje/checkers/t_defining.clj +++ b/test/midje/checkers/t_defining.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.checkers.t-defining (:use midje.sweet [midje.checkers.defining :only [checker?]] diff --git a/test/midje/checkers/t_deprecated.clj b/test/midje/checkers/t_deprecated.clj index 0a8807775..f8f038ab2 100644 --- a/test/midje/checkers/t_deprecated.clj +++ b/test/midje/checkers/t_deprecated.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.checkers.t-deprecated (:use midje.sweet [midje.checkers.defining :only [checker?]] diff --git a/test/midje/checkers/t_extended_equality.clj b/test/midje/checkers/t_extended_equality.clj index 31e1d1a2f..6152a5831 100644 --- a/test/midje/checkers/t_extended_equality.clj +++ b/test/midje/checkers/t_extended_equality.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.checkers.t-extended-equality (:use midje.sweet midje.checkers.extended-equality diff --git a/test/midje/checkers/t_extended_falsehood.clj b/test/midje/checkers/t_extended_falsehood.clj new file mode 100644 index 000000000..a98914f6a --- /dev/null +++ b/test/midje/checkers/t_extended_falsehood.clj @@ -0,0 +1,29 @@ +(ns midje.checkers.t-extended-falsehood + (:use midje.sweet + [midje.checkers.extended-falsehood] + midje.test-util + clojure.pprint)) + +(facts "about an extended notion of falsehood" + (extended-false? false) => truthy + (extended-false? true) => falsey + (extended-false? {:intermediate-results 3}) => falsey + (extended-false? (as-data-laden-falsehood {})) => truthy + + ".. and its inverse" + (extended-true? false) => falsey + (extended-true? true) => truthy + (extended-true? {:intermediate-results 3}) => truthy + (extended-true? (as-data-laden-falsehood {})) => falsey) + +(facts "about data-laden falsehoods" + (as-data-laden-falsehood [5]) => data-laden-falsehood? + (meta (as-data-laden-falsehood (with-meta [5] {:foo true}))) => (contains {:foo true})) + +(facts "user-friendly-falsehood converts extended-falsehood into just false" + (user-friendly-falsehood false) => false + (user-friendly-falsehood nil) => nil + (user-friendly-falsehood (as-data-laden-falsehood {})) => false) + + + diff --git a/test/midje/checkers/t_simple.clj b/test/midje/checkers/t_simple.clj index e3fe7999d..b1b0ae669 100644 --- a/test/midje/checkers/t_simple.clj +++ b/test/midje/checkers/t_simple.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.checkers.t-simple (:use midje.sweet [midje.checkers.defining :only [checker?]] @@ -121,6 +119,12 @@ (throw-exception "msg") => (throws "msg" #(= "msg" (.getMessage %)) Error) (throw-exception "msg") => (throws "msg" Error #(= "msg" (.getMessage %))) ) +(fact "throws works with checkers that use Midje's extended notion of false" + (throw-exception "msg") => (throws #( (contains "m") (.getMessage %))) + (throw-exception "msg") =deny=> (throws #( (contains "message") (.getMessage %))) + ;; Following would be a user error, but the results should be helpful. + (throw-exception "msg") =deny=> (contains "msg")) + (fact "`throws` can even accept multiple predicates" (throw-exception "msg") => (throws #(= "msg" (.getMessage %)) #(= "msg" (.getMessage %)) #(= "msg" (.getMessage %))) (throw-exception "msg") => (throws "msg" #(= "msg" (.getMessage %)) #(= "msg" (.getMessage %)) #(= "msg" (.getMessage %))) @@ -129,11 +133,19 @@ (fact "`throws` can accept multiple messages - imagine regexs for large error mesages" (throw-exception "msg") => (throws #"^m" #"g$") - (throw-exception "msg") => (throws Error #"^m" #"g$")) + (throw-exception "msg") => (throws Error #"^m" #"g$") + ;; Note that both regexps should match. + (throw-exception "msg") =deny=> (throws Error #"m" #"h")) (fact "`throws` matches any exception that is an instance of expected" (throw (NullPointerException.)) => (throws Exception)) +(after-silently + (fact "`throws` fails when not given an exception" + 1 => (throws Exception)) + (fact + @reported => (just checker-fails))) + ;; Unexpected exceptions (after-silently (facts @@ -142,4 +154,3 @@ (throw-exception "throws Error") => truthy) (fact @reported => (three-of checker-fails))) - diff --git a/test/midje/error_handling/t_background_validations.clj b/test/midje/error_handling/t_background_validations.clj index d3ec0baab..3686d1d7d 100644 --- a/test/midje/error_handling/t_background_validations.clj +++ b/test/midje/error_handling/t_background_validations.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.error-handling.t_background_validations (:require [clojure.zip :as zip]) (:use [midje sweet test-util] @@ -80,41 +78,45 @@ (after :BAD (do "something")))) => validation-error-form? (validate `(background (before :BAD (do "something")))) => validation-error-form? ) ) - ;; Validation end-to-end facts +;;;; Validation end-to-end facts -;;;;;;;;;;;;;;;;;;;;;;;; ** `against-background` end-to-end ** ;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(each-causes-validation-error #"second element \(:invalid-wrapping-target\) should be one of: :facts, :contents, or :checks" + (against-background [(before :invalid-wrapping-target (do "something"))] + "body") -;; ~~ Vectory + (against-background (before :invalid-wrapping-target (do "something")) + "body") -;; invalid wrapping targets -(causes-validation-error #"second element \(:invalid-wrapping-target\) should be one of: :facts, :contents, or :checks" - (against-background [(before :invalid-wrapping-target (do "something"))] - "body")) + (background (before :invalid-wrapping-target (do "something")))) -;; check for vectors w/ no state-descriptions or background fakes -(causes-validation-error #"Badly formatted against-background fakes" - (against-background [:not-a-state-description-or-fake] - (fact nil => nil))) +(defn f []) +(each-causes-validation-error #"Badly formatted against-background fakes" -(defn f [] ) + ;; check for vectors w/ no state-descriptions or background fakes + (against-background [:not-a-state-description-or-fake] + (fact nil => nil)) -;; check for vectors w/ one thing that isn't a state-description or background fake -(causes-validation-error #"Badly formatted against-background fakes" + ;; check for vectors w/ one thing that isn't a state-description or background fake (against-background [(before :contents (do "something")) (f) => 5 :other-odd-stuff] (fact nil => nil))) +(each-causes-validation-error #"Badly formatted background fakes" + + ;; invalid when anything doesn't look like a state-description or background fake + (background (before :contents (do "something")) + (:not-a-state-description-or-fake)) + + ;; invalid when one thing isn't a state-description or background fake + (background :invalid-stuff-here)) + + ;; invalid if missing background fakes or state descriptions -(causes-validation-error #"You didn't enter any background fakes or wrappers" +(each-causes-validation-error #"You didn't enter any background fakes or wrappers" (against-background [] - (fact nil => nil))) - -;; ~~Sequency + (fact nil => nil)) -;; invalid wrapping targets -(causes-validation-error #"second element \(:invalid-wrapping-target\) should be one of: :facts, :contents, or :checks" - (against-background (before :invalid-wrapping-target (do "something")) - "body")) + (background)) ;; invalid when list w/ no state-descriptions or background fakes (after-silently @@ -132,24 +134,4 @@ ;; invalid if missing background fakes or state descriptions (causes-validation-error #"need a minimum of three elements to an against-background form" (against-background - (fact nil => nil))) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ** `background` end-to-end ** ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; invalid wrapping targets -(causes-validation-error #"second element \(:invalid-wrapping-target\) should be one of: :facts, :contents, or :checks" - (background (before :invalid-wrapping-target (do "something")))) - -;; invalid when anything doesn't look like a state-description or background fake -(causes-validation-error #"Badly formatted background fakes" - (background (before :contents (do "something")) - (:not-a-state-description-or-fake))) - -; invalid when one thing isn't a state-description or background fake -(causes-validation-error #"Badly formatted background fakes" - (background :invalid-stuff-here)) - -;; invalid if missing background fakes or state descriptions -(causes-validation-error #"You didn't enter any background fakes or wrappers" - (background)) \ No newline at end of file + (fact nil => nil))) \ No newline at end of file diff --git a/test/midje/error_handling/t_exceptional_errors.clj b/test/midje/error_handling/t_exceptional_errors.clj index 0c4b5b27b..3e0922f00 100644 --- a/test/midje/error_handling/t_exceptional_errors.clj +++ b/test/midje/error_handling/t_exceptional_errors.clj @@ -1,12 +1,10 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.error-handling.t-exceptional-errors (:use [midje sweet test-util])) (after-silently (fact "description" =>) (fact @reported => (just (contains {:type :exceptional-user-error - :description "description" + :description ["description"] :macro-form '(fact "description" =>)})))) ;; report ONLY top level fact's description when there's a error in nested facts @@ -15,6 +13,6 @@ (fact "fine" (fact "description" =>)) (fact @reported => (just (contains {:type :exceptional-user-error - :description "fine" + :description ["fine"] :macro-form '(fact "fine" (fact "description" =>))})))) diff --git a/test/midje/error_handling/t_exceptions.clj b/test/midje/error_handling/t_exceptions.clj index 0a8c717be..5ac87377f 100644 --- a/test/midje/error_handling/t_exceptions.clj +++ b/test/midje/error_handling/t_exceptions.clj @@ -1,9 +1,8 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.error-handling.t-exceptions (:use [midje.error-handling.exceptions] [midje.util.colorize :only [colorize-choice]] - [midje sweet test-util])) + [midje sweet test-util] + midje.util)) (expose-testables midje.error-handling.exceptions) (defrecord R [a]) diff --git a/test/midje/error_handling/t_semi_sweet_validations.clj b/test/midje/error_handling/t_semi_sweet_validations.clj index f55758847..2dcd9d45a 100644 --- a/test/midje/error_handling/t_semi_sweet_validations.clj +++ b/test/midje/error_handling/t_semi_sweet_validations.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.error-handling.t_semi_sweet_validations (:use [midje.sweet] [midje.error-handling validation-errors semi-sweet-validations] @@ -11,6 +9,20 @@ (validate correct) =not=> validation-error-form? (validate correct) => '[(f 1) => 3])) +(facts "fake validation returns whole fake form" + (let [valid-fake '(fake (f 1) => 3)] + (validate valid-fake) =not=> validation-error-form? + (validate valid-fake) => valid-fake + (validate (list valid-fake valid-fake valid-fake)) + => (list valid-fake valid-fake valid-fake))) + +(facts "data fake validation returns whole data-fake form" + (let [valid-data-fake '(data-fake ..mc.. =contains=> {:foo 'bar})] + (validate valid-data-fake) =not=> validation-error-form? + (validate valid-data-fake) => valid-data-fake + (validate (list valid-data-fake valid-data-fake valid-data-fake)) + => (list valid-data-fake valid-data-fake valid-data-fake))) + ; Duplication of validate is because of bug in against-background. (facts "errors are so tagged and contain file position" (against-background (form-position anything) => ...position...) diff --git a/test/midje/error_handling/t_validation_errors.clj b/test/midje/error_handling/t_validation_errors.clj index 581255109..f238eceda 100644 --- a/test/midje/error_handling/t_validation_errors.clj +++ b/test/midje/error_handling/t_validation_errors.clj @@ -1,66 +1,49 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.error-handling.t-validation-errors (:use [midje sweet test-util] [midje.error-handling.validation-errors] [midje.internal-ideas.file-position :only [form-position]] - [clojure.algo.monads])) + [clojure.algo.monads] + midje.util)) (expose-testables midje.error-handling.validation-errors) (fact "any form can be turned into a validation-error form" - (meta (as-validation-error '(form))) => (contains {:midje-validation-error true}) + (meta (as-validation-error '(form))) => (contains {:midje/validation-error true}) (as-validation-error '(form)) => validation-error-form?) +(def my-valid-form '(expect 1 => 1)) (def my-favorite-error-form (as-validation-error '(error form))) -(fact "there is an error monad for Midje" - (domonad midje-maybe-m +(fact "there is a validation monad for Midje" + (domonad validate-m [a 1 b (inc a)] - b) => 2 + b) => 2 - (let [result (domonad midje-maybe-m + (let [result (domonad validate-m [a my-favorite-error-form b (inc a)] b)] result => my-favorite-error-form result => validation-error-form?)) -(fact "there is syntactic sugar for it" - (valid-let [a my-favorite-error-form - b (inc a)] - b) => my-favorite-error-form) - -(fact "errors can spread to infect whole collections" - (spread-validation-error [1 2 3]) => '(1 2 3) - (spread-validation-error [1 my-favorite-error-form]) => my-favorite-error-form) - -(fact "you can insist a collection of items be fully valid" - (let [suspect [1 2 3]] - (with-valid suspect (second suspect)) => 2) - (let [suspect [1 (as-validation-error '(str "this would report an error"))]] - (with-valid suspect "this is the wrong return value") => "this would report an error")) - - - (fact "there is a helper function that produces error-reporting forms" - (report-validation-error '(anything) "note 1" "note 2") + (validation-error-report-form '(anything) "note 1" "note 2") => '(clojure.test/report {:type :validation-error :notes '["note 1" "note 2"] :position '...form-position... }) (provided (form-position '(anything)) => ...form-position...) - (report-validation-error '(whatever)) => validation-error-form?) + (validation-error-report-form '(whatever)) => validation-error-form?) (fact "can produce a basic error-reporting form, w/ form always as final note" - (simple-report-validation-error '(anything) "note 1" "note 2") + (simple-validation-error-report-form '(anything) "note 1" "note 2") => '(clojure.test/report {:type :validation-error :notes '["note 1" "note 2" "(anything)"] :position '...form-position... }) (provided (form-position '(anything)) => ...form-position...) - (simple-report-validation-error '(whatever)) => validation-error-form?) + (simple-validation-error-report-form '(whatever)) => validation-error-form?) diff --git a/test/midje/ideas/reporting/t_junit_xml_format.clj b/test/midje/ideas/reporting/t_junit_xml_format.clj new file mode 100644 index 000000000..73224b14e --- /dev/null +++ b/test/midje/ideas/reporting/t_junit_xml_format.clj @@ -0,0 +1,5 @@ +(ns midje.ideas.reporting.t-junit-xml-format + (:use midje.sweet + midje.test-util)) + +(future-fact "reported XML validates vs JUnit XML schema") \ No newline at end of file diff --git a/test/midje/ideas/reporting/t_report.clj b/test/midje/ideas/reporting/t_report.clj new file mode 100644 index 000000000..6c8415bcd --- /dev/null +++ b/test/midje/ideas/reporting/t_report.clj @@ -0,0 +1,16 @@ +(ns midje.ideas.reporting.t-report + (:use midje.ideas.reporting.report + [midje sweet test-util])) + +;; These tests generate failures to examine. We don't want them to be +;; added to the total failure count, which should always be zero. +(without-counting-failures + +(fact "report formatter is dynamically rebindindable" + (binding [*report-format-config* {:single-fact-fn (fn [_] "successfully rebound") + :summary-fn :irrelevant}] + (let [raw-report (with-identity-renderer (clojure.test/old-report :irrelevant))] + (nth raw-report 0) => #"FAIL.*some description.*foo.clj:3" + (nth raw-report 1) => "successfully rebound"))) + +) ; end without-counting-failures diff --git a/test/midje/ideas/reporting/t_string_format_full_output.clj b/test/midje/ideas/reporting/t_string_format_full_output.clj new file mode 100644 index 000000000..d2c50104e --- /dev/null +++ b/test/midje/ideas/reporting/t_string_format_full_output.clj @@ -0,0 +1,77 @@ +(ns midje.ideas.reporting.t-string-format-full-output + (:use midje.ideas.reporting.report + [midje sweet test-util])) + +;; These tests generate failures to examine. We don't want them to be +;; added to the total failure count, which should always be zero. +(without-counting-failures + +(let [output (with-out-str + (fact (+ 1 1) => 3))] + (fact + output => #"FAIL" + output => #"Expected:\s+3" + output => #"Actual:\s+2")) + + +(let [output (with-out-str + (fact (+ 1 1) =not=> 2))] + (fact + output => #"FAIL" + output => #"Expected: Anything BUT 2" + output => #"Actual:\s+2")) + +(let [output (with-out-str + (fact (+ 1 1) => odd?))] + (fact + output => #"FAIL" + output => #"checking function" + output => #"Actual result:\s+2" + output => #"Checking function:\s+odd\?")) + +(let [output (with-out-str + (fact (+ 1 1) =not=> even?))] + (fact + output => #"FAIL" + output => #"NOT supposed to agree.*checking function" + output => #"Actual result:\s+2" + output => #"Checking function:\s+even\?")) + +(let [output (with-out-str + (fact (cons 1 nil) => (contains 2)))] + (fact + output => #"FAIL" + output => #"checking function" + output => #"Actual result:\s+\(1\)" + output => #"Checking function:\s+\(contains 2\)" + output => #"Best match found:\s+\[\]")) + +(let [output (with-out-str + (fact (cons 1 nil) =not=> (just 1)))] + (fact + output => #"FAIL" + output => #"NOT supposed to agree.*checking function" + output => #"Actual result:\s\(1\)" + output => #"Checking function:\s+\(just 1\)")) + +(let [output (with-out-str + (fact 5 => (chatty-checker [a] (and (= a 5) (= a 6)))))] + (fact + output => #"FAIL" + output => #"checking function" + output => #"Actual result:\s+5" + output => #"Checking function:\s+\(chatty-checker \[a\] \(and \(= a 5\) \(= a 6\)\)\)" + output => #"\(= a 5\) => true" + output => #"\(= a 6\) => false")) + + +(let [output (with-out-str + (fact 5 => (every-checker even? (throws "message"))))] + (fact + output => #"FAIL" + output => #"checking function" + output => #"Actual result:\s+5" + output => #"Checking function:\s+\(every-checker even\? \(throws \"message\"\)\)" + output => #"even\? => false")) + +) ; end without-counting-failures diff --git a/test/midje/internal_ideas/t_report.clj b/test/midje/ideas/reporting/t_string_format_map_translation.clj similarity index 76% rename from test/midje/internal_ideas/t_report.clj rename to test/midje/ideas/reporting/t_string_format_map_translation.clj index e1f5f02de..d77be3bb6 100644 --- a/test/midje/internal_ideas/t_report.clj +++ b/test/midje/ideas/reporting/t_string_format_map_translation.clj @@ -1,26 +1,16 @@ -;; -*- indent-tabs-mode: nil -*- - -(ns midje.internal-ideas.t-report - (:use [midje.internal-ideas.report :only [midje-position-string]] +(ns midje.ideas.reporting.t-string-format-map-translation + (:use midje.ideas.reporting.report + [midje.ideas.reporting.string-format :only [midje-position-string]] [midje.error-handling.exceptions :only [captured-throwable]] - [midje sweet test-util])) - -(expose-testables midje.internal-ideas.report) + [midje sweet test-util] + midje.util)) -;; This set of tests generate failures. The following code prevents -;; them from being counted as failures when the final summary is -;; printed. The disadvantage is that legitimate failures won't appear -;; in the final summary. They will, however, produce failure output, -;; so that's an acceptable compromise. +(expose-testables midje.ideas.reporting.string-format) -(when (nil? clojure.test/*report-counters*) - (alter-var-root #'clojure.test/*report-counters* - (constantly (ref clojure.test/*initial-report-counters*)))) - -(background (around :facts (let [report-counters @clojure.test/*report-counters*] - ?form - (dosync (commute clojure.test/*report-counters* (constantly report-counters)))) )) +;; These tests generate failures to examine. We don't want them to be +;; added to the total failure count, which should always be zero. +(without-counting-failures (fact "string positions have filenames and line numbers" (midje-position-string ["filename.clj" 33]) => "(filename.clj:33)") @@ -34,7 +24,7 @@ (fact "rendering functional failures" (let [failure-map {:type :mock-expected-result-functional-failure - :description "some description" + :description ["some description"] :actual 2 :intermediate-results [ ['(f 1) 33] ] :position ["foo.clj" 3] @@ -49,7 +39,7 @@ (nth raw-report 5) => #"\(f 1\) => 33") (let [failure-map {:type :mock-expected-result-functional-failure - :description "some description" + :description ["some description"] :actual 2 :position ["foo.clj" 3] :expected 'odd?} @@ -61,7 +51,7 @@ "values in strings are formatted via pr-str" (let [failure-map {:type :mock-expected-result-functional-failure - :description "some description" + :description ["some description"] :actual nil :expected '(sloobom "forp") :intermediate-results [['(+ 1 "ate") nil]] @@ -72,7 +62,7 @@ (nth raw-report 5) => #"\(\+ 1 \"ate\"\) => nil") (let [failure-map {:type :mock-expected-result-functional-failure - :description "some description" + :description ["some description"] :actual 2 :notes ["NOTE ME!" "ME TOO"] :position ["foo.clj" 3] @@ -89,7 +79,7 @@ (fact "rendering inappropriate checker matches" (let [failure-map {:type :mock-actual-inappropriately-matches-checker - :description "some description" + :description ["some description"] :actual 2 :position ["foo.clj" 3] :expected '(test-checker 33)} @@ -98,16 +88,16 @@ (nth raw-report 0) => #"FAIL.*some description.*foo.clj:3" (nth raw-report 1) => #"Actual.*was NOT supposed to agree" (nth raw-report 2) => #"Actual.*2" - (nth raw-report 3) => #"Checking function.*test-checker 33")) + (nth raw-report 3) => #"Checking function.*test-checker 33" + )) (fact "excess matches" (let [failure-map {:type :mock-argument-match-failure - :description "some description" + :description ["some description"] :actual '(nil) :position ["foo.clj" 3] - :lhs odd?} + :var odd?} raw-report (with-identity-renderer (clojure.test/old-report failure-map))] - (prn raw-report) (nth raw-report 0) => #"FAIL.*some description.* at .*foo.clj:3" (nth raw-report 1) => #"never said .*odd.* would be needed" (nth raw-report 1) =future=> #"never said odd\? would be needed" @@ -115,31 +105,36 @@ (fact "mock never called" (let [failure-map {:type :mock-incorrect-call-count - :description "some description" - :actual-count 0 - :position ["foo.clj" 3] - :expected "(f a)"} + :failures [{ :description ["some description"] + :actual-count 0 + :expected-count nil + :position ["foo.clj" 3] + :expected "(f a)"}] } raw-report (with-identity-renderer (clojure.test/old-report failure-map))] (nth raw-report 0) => #"FAIL.*some description.* at .*foo.clj:3" - (nth raw-report 1) => #"claimed the following was needed" - (nth raw-report 2) => #"\(f a\)")) + (nth raw-report 1) => #"These calls were not made the right number of times" + (nth raw-report 2) => #"\(f a\)" + (nth raw-report 2) => #"expected at least once")) (fact "mock called an incorrect number of times" (let [failure-map {:type :mock-incorrect-call-count - :description "some description" - :actual-count 3 - :position ["foo.clj" 3] - :expected "(f a)"} + :failures [{ :description ["some description"] + :actual-count 3 + :expected-count 1 + :position ["foo.clj" 3] + :expected "(f a)" }] } raw-report (with-identity-renderer (clojure.test/old-report failure-map))] (nth raw-report 0) => #"FAIL.*some description.* at .*foo.clj:3" - (nth raw-report 1) => #"used three times" - (nth raw-report 2) => #"\(f a\)")) + (nth raw-report 1) => #"These calls were not made the right number of times" + (nth raw-report 2) => #"\(f a\)" + (nth raw-report 2) => #"expected :times 1")) + (fact "ordinary bad result from equality" (let [failure-map {:type :mock-expected-result-failure - :description "some description" + :description ["some description"] :position ["foo.clj" 3] :actual nil :expected "s"} @@ -150,7 +145,7 @@ (fact "equality when inequality expected" (let [failure-map {:type :mock-expected-result-inappropriately-matched - :description "some description" + :description ["some description"] :position ["foo.clj" 3] :actual "s" :expected "s"} @@ -161,7 +156,7 @@ (facts "about reporting exceptions" (let [failure-map {:type :mock-expected-result-failure - :description "some description" + :description ["some description"] :position ["foo.clj" 3] :actual (captured-throwable (Error. "message")) :expected "hi"} @@ -174,7 +169,7 @@ (facts "about reporting specific user errors" (let [failure-map {:type :validation-error - :description "some description" + :description ["some description"] :notes ["message"] :position ["foo.clj" 3]} raw-report (with-identity-renderer (clojure.test/old-report failure-map))] @@ -183,7 +178,7 @@ (facts "about reporting user errors detected because of an exception" (let [failure-map {:type :exceptional-user-error - :description "some description" + :description ["some description"] :macro-form '(foo bar) :stacktrace ["one" "two"] :position ["foo.clj" 3]} @@ -195,11 +190,13 @@ (fact "binding notes are considered part of the position" (let [failure-map {:type :mock-expected-result-failure - :description "some description" + :description ["some description"] :binding-note "a note" :position ["foo.clj" 3] :actual nil :expected "s"} raw-report (with-identity-renderer (clojure.test/old-report failure-map))] (nth raw-report 0) => #"FAIL.*some description.* at .*foo.clj:3" - (nth raw-report 1) => #"a note")) \ No newline at end of file + (nth raw-report 1) => #"a note")) + +) ; end without-counting-failures diff --git a/test/midje/ideas/t_arrows.clj b/test/midje/ideas/t_arrows.clj index 8dd237de2..b819692b4 100644 --- a/test/midje/ideas/t_arrows.clj +++ b/test/midje/ideas/t_arrows.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.ideas.t-arrows (:use midje.ideas.arrows) (:use [midje sweet test-util]) @@ -7,26 +5,26 @@ (fact "can ask if at first element of X =?> Y :possible :keywords" (let [possible (fn [nested-form] (zip/down (zip/seq-zip nested-form)))] - "a string" =not=> is-start-of-checking-arrow-sequence? - '(foo) =not=> is-start-of-checking-arrow-sequence? + "a string" =not=> start-of-checking-arrow-sequence? + '(foo) =not=> start-of-checking-arrow-sequence? - '( (f 1) ) =not=> is-start-of-checking-arrow-sequence? - (possible '( (f 1) )) =not=> is-start-of-checking-arrow-sequence? + '( (f 1) ) =not=> start-of-checking-arrow-sequence? + (possible '( (f 1) )) =not=> start-of-checking-arrow-sequence? - '( (f 1) (f 2)) =not=> is-start-of-checking-arrow-sequence? - (possible '( (f 1) (f 2))) =not=> is-start-of-checking-arrow-sequence? + '( (f 1) (f 2)) =not=> start-of-checking-arrow-sequence? + (possible '( (f 1) (f 2))) =not=> start-of-checking-arrow-sequence? - '( (f 1) => 2) => is-start-of-checking-arrow-sequence? - (possible '( (f 1) => 2)) => is-start-of-checking-arrow-sequence? + '( (f 1) => 2) => start-of-checking-arrow-sequence? + (possible '( (f 1) => 2)) => start-of-checking-arrow-sequence? - '( (f 1) =not=> 2) => is-start-of-checking-arrow-sequence? - (possible '( (f 1) =not=> 2)) => is-start-of-checking-arrow-sequence? + '( (f 1) =not=> 2) => start-of-checking-arrow-sequence? + (possible '( (f 1) =not=> 2)) => start-of-checking-arrow-sequence? - '( (f 1) => 2 :key 'value) => is-start-of-checking-arrow-sequence? - (possible '( (f 1) => 2 :key 'value)) => is-start-of-checking-arrow-sequence? + '( (f 1) => 2 :key 'value) => start-of-checking-arrow-sequence? + (possible '( (f 1) => 2 :key 'value)) => start-of-checking-arrow-sequence? - '( (f 1) midje.semi-sweet/=> 2) => is-start-of-checking-arrow-sequence? - (possible '( (f 1) midje.semi-sweet/=> 2)) => is-start-of-checking-arrow-sequence?)) + '( (f 1) midje.semi-sweet/=> 2) => start-of-checking-arrow-sequence? + (possible '( (f 1) midje.semi-sweet/=> 2)) => start-of-checking-arrow-sequence?)) (fact "when at end of required part of arrow form, can ask for overrides" "empty rest of form" diff --git a/test/midje/ideas/t_background.clj b/test/midje/ideas/t_background.clj index ba1143307..153ef39a8 100644 --- a/test/midje/ideas/t_background.clj +++ b/test/midje/ideas/t_background.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.ideas.t-background (:require [clojure.zip :as zip]) (:use [midje sweet test-util] @@ -7,7 +5,8 @@ [midje.util unify] [midje.error-handling validation-errors] [midje.ideas.background :only [separate-background-forms setup-teardown-bindings - seq-headed-by-setup-teardown-form? background-wrappers]])) + seq-headed-by-setup-teardown-form? background-wrappers]] + midje.util)) (expose-testables midje.ideas.background) (unfinished unused used) @@ -69,7 +68,7 @@ - ;; wrapping +;; wrapping (fact "human-friendly background forms can be canonicalized appropriately" "fakes" diff --git a/test/midje/ideas/t_facts.clj b/test/midje/ideas/t_facts.clj index a69dcf768..8f9d07e5f 100644 --- a/test/midje/ideas/t_facts.clj +++ b/test/midje/ideas/t_facts.clj @@ -1,11 +1,9 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.ideas.t-facts (:use midje.ideas.facts midje.sweet midje.test-util) (:require [clojure.zip :as zip])) - ;; Translating sweet forms into their semi-sweet equivalent +;; Translating sweet forms into their semi-sweet equivalent (fact "translating entire fact forms" @@ -39,10 +37,10 @@ (fake (m 1) => 33))] (to-semi-sweet form) => form)) -;; invalid if fact form is too short -(causes-validation-error #"There is no arrow in your fact form" - (fact)) + +(each-causes-validation-error #"There is no arrow in your fact form" + (fact) + (fact "vector fact" [1 2 3 4] (contains 3))) + (causes-validation-error #"There is no arrow in your facts form" - (facts 1)) -(causes-validation-error #"There is no arrow in your fact form" - (fact "vector fact" [1 2 3 4] (contains 3))) \ No newline at end of file + (facts 1)) \ No newline at end of file diff --git a/test/midje/ideas/t_formulas.clj b/test/midje/ideas/t_formulas.clj new file mode 100644 index 000000000..7defb561e --- /dev/null +++ b/test/midje/ideas/t_formulas.clj @@ -0,0 +1,209 @@ +(ns midje.ideas.t-formulas + (:use midje.test-util + midje.sweet + midje.util.ecosystem + [midje.ideas.formulas :only [*num-trials* with-num-trials]] )) + + +;;;; Formulas + +;; First we create our own generator functions since Midje doesn't include any. +(defn make-string [] + (rand-nth ["a" "b" "c" "d" "e" "f" "g" "i"])) +(defn- gen-int [pred] + (rand-nth (filter pred [-999 -100 -20 -5 -4 -3 -2 -1 0 1 2 3 4 5 20 100 999]))) + +;; Formulas are a generative style test macro. Each binding has a generator on +;; the right and a symbol on the left that will hold the generated value + +(formula "can now use simple generative-style formulas - with multiple bindings" + [a (make-string) b (make-string) c (make-string)] + (str a b c) => (has-prefix (str a b))) + +;; You can use provided to make fakes inside of a formula +(unfinished f) +(defn g [x] (str (f x) x)) + +(formula "'provided' works" + [a (make-string)] + (g a) => (str "foo" a) + (provided + (f anything) => "foo")) + + +;; Failed formulas report once per formula regardless how many trials were run +(after-silently + (formula "some description" [a "y"] a => :foo)) +(fact @reported => (one-of (contains {:type :mock-expected-result-failure + :description ["some description"]}))) + + +;; Passing formulas run the generator many times, and evaluate +;; their body many times - number of trials is rebindable +(defn-call-countable y-maker [] "y") +(defn-call-countable my-str [s] (str s)) + +(binding [*num-trials* 77] + (formula [y (y-maker)] + (my-str y) => "y")) +(fact @y-maker-count => 77) +(fact @my-str-count => 77) + + +;; There is syntactic sugar for binding *num-trials* +(defn-call-countable k-maker [] "k") +(with-num-trials 1000 + (formula [a 1] (k-maker) => "k") + (formula [a 1] (k-maker) => "k") + (formula [a 1] (k-maker) => "k")) +(fact @k-maker-count => 3000) + +;; Can specify number of trials to run in options map - overrides *num-trials* var value +(defn-call-countable foo-maker [] "foo") +(defn-call-countable my-double-str [s] (str "double" s)) + +(binding [*num-trials* 111] ;; this will be overridden by opt map + (formula "asdf" {:num-trials 88} [foo (foo-maker)] + (my-double-str foo) => "doublefoo")) +(fact @foo-maker-count => 88) +(fact @my-double-str-count => 88) + + +;; Runs only as few times as needed to see a failure +(defn-call-countable z-maker [] "z") +(defn-call-countable my-identity [x] (identity x)) + +(after-silently + (formula [z (z-maker)] + (my-identity z) => "clearly not 'z'")) +(fact "calls generator once" @z-maker-count => 1) +(fact "evalautes body once" @my-identity-count => 1) + +;; Shrinks failure case to smallest possible failure -- shrinks each binding result +(when-1-3+ + (with-redefs [midje.ideas.formulas/shrink (fn [x] + (if (integer? x) + [0 1 2 3 4 5] + ["a" "b" "c" "d" "e"]))] + (after-silently + (formula [x 100 a "abc"] + [x a] => #(neg? (first %)))) + (fact @reported => (one-of (contains {:type :mock-expected-result-functional-failure + :actual [0 "a"]} ))))) ;; note that it shrank both 100 and "abc" + +;; If any of the seqs of shrunken failure cases vals run out before finding a smaller +;; failure, then uses the original failure case, not a shrunken one. +(when-1-3+ + (with-redefs [midje.ideas.formulas/shrink (fn [x] + (if (integer? x) + [-11 11] ;; not a failure since -11 is neg? and we never check 11, which would fail + ["z"]))] + (after-silently + (formula [x 100 a "abc"] + [x a] => #(neg? (first %)))) + (fact @reported => (one-of (contains {:type :mock-expected-result-functional-failure + :actual [100 "abc"]} ))))) +;; Shrunken failure case is in the same domain as the generator +;; used to create the input case in the first place. +(after-silently + (formula [x (gen-int odd?)] ;;(guard (gs/int) odd?)] + x => neg?)) +(future-fact "shrunken failure case is in the same domain as the generator" + @reported => (one-of (contains {:type :mock-expected-result-failure + :actual 1}))) + + +;;;; Other + +(future-formula "demonstrating the ability to create future formulas" + [a 1] + a => 1) + + +;;;; Validation + +;; The following facts express an assortment of ways that formulas +;; could be expressed with invalid syntax + +(unfinished h) + +(each-causes-validation-error #"There is no expection in your formula form" + (formula [a 1]) + (formula [a 1] 1) + (formula "a doc string" [a 1] (contains 3)) + + (formula "ignores arrows in provideds" [a 1] + (contains 3) + (provided (h anything) => 5)) + + (formula "ignores arrows in against-background" [a 1] + (contains 3) + (against-background (h anything) => 5)) + + (formula "ignores arrows in against-background - even when it comes first" + [a 1] + (against-background (h anything) => 5) + (contains 3)) + + (formula "ignores arrows in background" [a 1] + (contains 3) + (background (h anything) => 5)) + + (formula "ignores arrows in background - even when it comes first" + [a 1] + (background (h anything) => 5) + (contains 3))) + +(each-causes-validation-error #"Formula requires bindings to be an even numbered vector of 2 or more" + (formula "a doc string" :not-vector 1 => 1) + (formula "a doc string" [a 1 1] 1 => 1) + (formula "a doc string" [] 1 => 1) + (formula "a doc string" {:num-trials 50} 1 => 1)) + +(causes-validation-error #"There are too many expections in your formula form" + (formula "a doc string" [a 1] a => 1 a => 1)) + +(causes-validation-error #"Invalid keys \(:foo, :bar\) in formula's options map. Valid keys are: :num-trials" + (formula {:foo 5 :bar 6 :num-trials 5} [a 1] a => 1)) + +(each-causes-validation-error #":num-trials must be an integer 1 or greater" + (formula {:num-trials 0 } [a 1] a => 1) + (formula {:num-trials -1} [a 1] a => 1) + (formula {:num-trials -2} [a 1] a => 1) + (formula {:num-trials -3} [a 1] a => 1) + (formula {:num-trials -4} [a 1] a => 1)) + +(defn z [x] ) +(causes-validation-error #"background cannot be used inside of formula" + (formula [a 1] + (background (h 1) => 5) + (z a) => 10)) + +;; Things that should be valid + +(defn k [x] (* 2 (h x))) +(formula "against-backgrounds at the front of the body are fine" [a 1] + (against-background (h 1) => 5) + (k a) => 10) + +;; :num-trials can be any number 1+ +(formula {:num-trials 1} [a 1] a => 1) +(formula {:num-trials 2} [a 1] a => 1) +(formula {:num-trials 3} [a 1] a => 1) +(formula {:num-trials 4} [a 1] a => 1) +(formula {:num-trials 10000} [a 1] a => 1) + + +;; *num-trials* binding validation + +(formula + "binding too small a value - gives nice error msg" + [n (gen-int #(< % 1))] + (binding [*num-trials* n] nil) + => (throws #"must be an integer 1 or greater")) + +(formula + "allows users to dynamically rebind to 1+" + [n (gen-int #(>= % 1))] + (binding [*num-trials* n] nil) + =not=> (throws Exception)) \ No newline at end of file diff --git a/test/midje/ideas/t_metaconstant_compilation.clj b/test/midje/ideas/t_metaconstant_compilation.clj new file mode 100644 index 000000000..c396a7ac8 --- /dev/null +++ b/test/midje/ideas/t_metaconstant_compilation.clj @@ -0,0 +1,15 @@ +(ns midje.ideas.t-metaconstant-compilation + (:use [midje sweet])) + +(metaconstants ..m.. ..m.... .mc.) + +(fact "printing" + (str .mc.) => ".mc." + (pr-str .mc.) => ".mc.") + +(fact "equality checking" + (= ..m.. ..m....) => truthy) + +(future-fact "Make it so the metaconstant compilation tests are compiled.") +;; Right now, this just serves as a check that the `metaconstants` +;; check works. diff --git a/test/midje/ideas/t_metaconstants.clj b/test/midje/ideas/t_metaconstants.clj index dcd3ff27d..bee582027 100644 --- a/test/midje/ideas/t_metaconstants.clj +++ b/test/midje/ideas/t_metaconstants.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.ideas.t-metaconstants (:use midje.ideas.metaconstants [midje sweet test-util] @@ -8,7 +6,7 @@ (:import midje.ideas.metaconstants.Metaconstant)) - ;;; Notation +;;; Notation (tabular (fact "metaconstants begin and end with dots" @@ -45,7 +43,7 @@ (--v-- 1 2 3) => 8)) - ;;; About the datatype +;;; About the datatype (let [mc (Metaconstant. '...name... {})] (fact "Metaconstants print as their name" @@ -107,7 +105,7 @@ (str .mc.) => ".mc." (pr-str .mc.) => ".mc.") -;;; Use with prerequisite functions +;;; Use with prerequisite functions (unfinished m) @@ -149,7 +147,7 @@ (metaconstant-for-form '(metaconstant-for-form)) => '...metaconstant-for-form-value-1...)) - ;;; Metaconstants-that-contain: as used in code +;;; Metaconstants-that-contain: as used in code (fact "all three types of lookup" (against-background --mc-- =contains=> {:a 5}) diff --git a/test/midje/ideas/t_prerequisites.clj b/test/midje/ideas/t_prerequisites.clj index d5d4fbd97..b219252fe 100644 --- a/test/midje/ideas/t_prerequisites.clj +++ b/test/midje/ideas/t_prerequisites.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.ideas.t-prerequisites (:use midje.ideas.prerequisites [midje.internal-ideas.expect :only [expect?]] @@ -9,9 +7,9 @@ (fact "can ask whether at the beginning of a form that provides prerequisites" (let [values (zip/seq-zip '(provided midje.semi-sweet/provided fluke))] - (-> values zip/down) => is-head-of-form-providing-prerequisites? - (-> values zip/down zip/right) => is-head-of-form-providing-prerequisites? - (-> values zip/down zip/right zip/right) =not=> is-head-of-form-providing-prerequisites?)) + (-> values zip/down) => head-of-form-providing-prerequisites? + (-> values zip/down zip/right) => head-of-form-providing-prerequisites? + (-> values zip/down zip/right zip/right) =not=> head-of-form-providing-prerequisites?)) (fact "can convert prerequisites into fake calls" (let [original '( provided (f 1) => 3 (f 2) => (+ 1 1)) @@ -39,7 +37,7 @@ resulting-loc (delete_prerequisite_form__then__at-previous-full-expect-form original-loc)] - original-loc => is-head-of-form-providing-prerequisites? + original-loc => head-of-form-providing-prerequisites? resulting-loc => expect? (zip/root resulting-loc) => edited)) diff --git a/test/midje/ideas/t_tabular.clj b/test/midje/ideas/t_tabular.clj index b60040fc4..baffbb132 100644 --- a/test/midje/ideas/t_tabular.clj +++ b/test/midje/ideas/t_tabular.clj @@ -1,11 +1,10 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.ideas.t-tabular (:use [midje.ideas.tabular :except [add-binding-note table-binding-maps]] [midje.ideas.metaconstants :only [metaconstant-symbol?]] [midje.error-handling.validation-errors] [midje sweet test-util] - [ordered.map :only (ordered-map)])) + [ordered.map :only (ordered-map)] + midje.util)) (expose-testables midje.ideas.tabular) @@ -98,22 +97,20 @@ (let [s "string"] (validate '?forms []) => '?expected)) ?forms ?expected - (tabular fact ?a ?b 1 1) [nil fact [?a ?b 1 1]] - (tabular "string" fact ?a ?b 1 1) ["string" fact [?a ?b 1 1]] ) + (tabular fact ?a ?b 1 1) [nil fact [?a ?b] [1 1]] + (tabular "string" fact ?a ?b 1 1) ["string" fact [?a ?b] [1 1]] ) -(causes-validation-error #"There's no table\. \(Misparenthesized form\?\)" +(each-causes-validation-error #"There's no table\. \(Misparenthesized form\?\)" (tabular (fact (tabular-forms '?forms) => '?expected ?forms ?expect - [ fact table ] [fact table]))) + [ fact table ] [fact table])) -(causes-validation-error #"There's no table\. \(Misparenthesized form\?\)" - (tabular - (fact nil => nil))) + (tabular + (fact nil => nil)) -(causes-validation-error #"There's no table\. \(Misparenthesized form\?\)" (tabular "doc string present" (fact nil => nil))) @@ -214,19 +211,11 @@ - ;; Util: table-binding-maps +;; Util: table-binding-maps (fact "gets the bindings off fact table" - (table-binding-maps (list '?a '?b '?result - 1 2 3), []) - => [ (ordered-map '?a 1, '?b 2, '?result 3) ]) - -(fact "won't count as table variables any specified local symbols" - (table-binding-maps (list '?a - '?result ; it thinks of '?result as just any old symbol - 1 - 3), ['?result]) - => [ (ordered-map '?a '?result) (ordered-map '?a 1) (ordered-map '?a 3) ]) + (table-binding-maps ['?a '?b '?result] [1 2 3]) + => [ (ordered-map '?a 1, '?b 2, '?result 3) ]) (tabular (fact ?comment (let [line-no-free-original ?original @@ -268,16 +257,16 @@ (+ a b) => result) a b result - 2 4 999 ) ;; WRONG!! - (fact @reported => (one-of (contains {:description "table of results - add stuff"} )))) + 2 4 999 ) ;; PURPOSELY FAIL + (fact @reported => (one-of (contains {:description ["table of results" "add stuff"]} )))) (after-silently (tabular "table of results" (fact (+ a b) => result) a b result - 2 4 999 ) ;; WRONG!! - (fact @reported => (one-of (contains {:description "table of results"} )))) + 2 4 999 ) ;; PURPOSELY FAIL + (fact @reported => (one-of (contains {:description ["table of results" nil]} )))) (after-silently (tabular @@ -285,5 +274,5 @@ (+ a b) => result) a b result - 2 4 999 ) ;; WRONG!! - (fact @reported => (one-of (contains {:description "add stuff"} )))) + 2 4 999 ) ;; PURPOSELY FAIL + (fact @reported => (one-of (contains {:description [nil "add stuff"]} )))) diff --git a/test/midje/inner_functions_test.clj b/test/midje/inner_functions_test.clj index 55c7d20df..2d8e1962a 100644 --- a/test/midje/inner_functions_test.clj +++ b/test/midje/inner_functions_test.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.inner-functions-test (:use clojure.test) (:use [midje.semi-sweet]) diff --git a/test/midje/internal_ideas/t_expect.clj b/test/midje/internal_ideas/t_expect.clj index c502e2d08..fdfefffe1 100644 --- a/test/midje/internal_ideas/t_expect.clj +++ b/test/midje/internal_ideas/t_expect.clj @@ -1,8 +1,6 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.internal-ideas.t-expect (:use [midje.internal-ideas.expect] - [midje.ideas.arrows :only [is-start-of-checking-arrow-sequence?]] + [midje.ideas.arrows :only [start-of-checking-arrow-sequence?]] midje.sweet midje.test-util) (:require [clojure.zip :as zip]) @@ -48,7 +46,7 @@ original-loc (-> z zip/down) resulting-loc (wrap-with-expect__then__at-rightmost-expect-leaf original-loc)] original-loc => (node '(f 1)) - original-loc => is-start-of-checking-arrow-sequence? + original-loc => start-of-checking-arrow-sequence? (zip/root resulting-loc) => edited (zip/next resulting-loc) => (node "next")) @@ -61,7 +59,7 @@ original-loc (-> z zip/down) resulting-loc (wrap-with-expect__then__at-rightmost-expect-leaf original-loc)] original-loc => (node '(f 1)) - original-loc => is-start-of-checking-arrow-sequence? + original-loc => start-of-checking-arrow-sequence? (zip/root resulting-loc) => edited (zip/next resulting-loc) => (node "next")) @@ -73,7 +71,7 @@ original-loc (-> z zip/down) resulting-loc (wrap-with-expect__then__at-rightmost-expect-leaf original-loc)] original-loc => (node '(f 1)) - original-loc => is-start-of-checking-arrow-sequence? + original-loc => start-of-checking-arrow-sequence? (zip/root resulting-loc) => edited (zip/next resulting-loc) => (node "next")) diff --git a/test/midje/internal_ideas/t_fact_context.clj b/test/midje/internal_ideas/t_fact_context.clj index 799f8dcd2..d249d65d4 100644 --- a/test/midje/internal_ideas/t_fact_context.clj +++ b/test/midje/internal_ideas/t_fact_context.clj @@ -1,47 +1,40 @@ (ns midje.internal-ideas.t-fact-context (:use [midje.internal-ideas.fact-context] clojure.test + midje.sweet midje.test-util)) -(def #^:private some-atom (atom nil)) - ;"creates nested doc-strings from each surrounding context" (deftest within-the-nested-contexts-the-doc-strings-build-up (within-fact-context "level 1" (within-fact-context "level 2" (within-fact-context "level 3" - (reset! some-atom (nested-fact-description))))) - (is (= @some-atom "level 1 - level 2 - level 3"))) - -(reset! some-atom nil) + (is (= @nested-descriptions ["level 1" "level 2" "level 3"])))))) -(deftest non-existant-context-descriptions-are-ignored-when-printed +(deftest context-descriptions-can-be-nil (within-fact-context "level 1" (within-fact-context nil (within-fact-context "level 3" - (reset! some-atom (nested-fact-description))))) - (is (= @some-atom "level 1 - level 3"))) + (is (= @nested-descriptions ["level 1" nil "level 3"])))))) -(deftest non-existant-context-descriptions-are-ignored-when-printed +(deftest exceptions-dont-disturbed-the-description-nesting (within-fact-context "level 1" (try (within-fact-context "level 2" (throw (Exception. "boom"))) (catch Exception e nil)) - - (reset! some-atom (nested-fact-description))) - - (is (= @some-atom "level 1"))) - -(reset! some-atom nil) + (is (= @nested-descriptions ["level 1"])))) (deftest outside-of-the-contexts-there-is-no-fact-description-at-all - (is (= (nested-fact-description) nil))) - -(reset! some-atom nil) + (is (= @nested-descriptions []))) -(deftest nil-descriptions-produces-blank-nested-fact-description - (within-fact-context nil - (reset! some-atom (nested-fact-description))) - (is (= @some-atom nil))) \ No newline at end of file +(tabular "nested-descriptions can be formatted as '-' separated" + (fact + (format-nested-descriptions descriptions) => result) + + descriptions result + ["a" "b" "c"] "a - b - c" + ["a" nil "c"] "a - c" + nil nil + [] nil ) \ No newline at end of file diff --git a/test/midje/internal_ideas/t_fakes.clj b/test/midje/internal_ideas/t_fakes.clj index 104c1d215..9763792fc 100644 --- a/test/midje/internal_ideas/t_fakes.clj +++ b/test/midje/internal_ideas/t_fakes.clj @@ -1,14 +1,12 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.internal-ideas.t-fakes (:use [midje sweet test-util] - [midje.internal-ideas.fakes :except [mockable-funcall? - unfolding-step merge-metaconstant-bindings - data-fakes-to-metaconstant-bindings binding-map-with-function-fakes unique-vars - call-faker best-call-action ]] + [midje.internal-ideas.fakes :except [mockable-funcall? unfolding-step merge-metaconstant-bindings + unique-vars handle-mocked-call best-call-action ]] [midje.ideas.metaconstants :only [metaconstant-for-form]] [utilize.seq :only (find-first only)] - [midje.test-util]) + [midje.test-util] + midje.util + clojure.pprint) (:import midje.ideas.metaconstants.Metaconstant)) (expose-testables midje.internal-ideas.fakes) @@ -39,26 +37,18 @@ odd? odd? TRUTHY odd? 3 falsey) - (declare f g) -(fact "unique variables can be found in fakes" - (let [fakes [ (fake (f 1) => 2) - (fake (f 2) => 4) - (fake (g) => 3)] ] - (unique-vars fakes) => (contains [#'f #'g] :in-any-order)) - "Same applies to data-fakes" - (let [fakes [ (data-fake ...f... => {:a 2}) - (data-fake ...f... => {:b 4}) - (data-fake ...g... => {:d 4})] ] - (unique-vars fakes) => (contains [#'...f... #'...g...] :in-any-order))) - - - -(fact "binding maps contain functions that increment a call count" - (let [fake (fake (f 1) => 3) - result-map (binding-map [fake])] - ( (result-map #'f) 1) => 3 - (fake-count fake) => 1)) + +(tabular + (fact "binding maps contain functions that increment a call count" + (let [fake (fake (?function-reference 1) => 3) + result-map (binding-map [fake])] + ( (result-map #'f) 1) => 3 + @(:call-count-atom fake) => 1)) + ?function-reference + f + #'f + ) (fact "binding maps can also contain Metaconstants to assign" (let [data-fakes [(data-fake ...mc... =contains=> {:a 1, :b ...even...}) @@ -72,13 +62,13 @@ odd? 3 falsey) result-map (binding-map fakes)] ( (result-map #'f) 1) => 3 - (map fake-count fakes) => [1 0])) + (map #(deref (:call-count-atom %)) fakes) => [1 0])) (tabular (fact "The number of calls can be described" - (let [fake-fake {:count-atom (atom ?actual-count), + (let [fake-fake {:call-count-atom (atom ?actual-count), :type ?type :times ?specified-count}] (call-count-incorrect? fake-fake) => ?expected)) @@ -115,84 +105,91 @@ odd? 3 falsey) (facts "about result suppliers used" "returns identity for =>" (let [arrow "=>"] - ((make-result-supplier arrow [1 2 3])) => [1 2 3]) + ((fn-fake-result-supplier arrow [1 2 3])) => [1 2 3]) "returns stream for =streams=>" - (let [supplier (make-result-supplier "=streams=>" [1 2 3])] + (let [supplier (fn-fake-result-supplier "=streams=>" [1 2 3])] (supplier) => 1 (supplier) => 2 (supplier) => 3)) -(unfinished called) - -(defn caller [] - (try - (called) - (catch Exception e nil)) - (try - (called) - (catch Exception e nil)) - (called)) - -(fact "=streams=> makes thunks of each item on right hand side" - (caller) => 7 - (provided - (called) =streams=> [(throw (Exception. "first!")) (throw (Exception. "second!")) 7])) - ;;; Handling of default values for fakes -;; In this example, one call to `internal` is faked and one is left alone. - -(defn internal [x] 33) -(defn external [x] (+ (internal x) (internal (inc x)))) +(binding [midje.config/*allow-default-prerequisites* true] -(fact "calls not mentioned in prerequisites are passed through to real code" - (external 1) => 0 - (provided - (internal 1) => -33)) + ;; In this example, one call to `internal` is faked and one is left alone. + (defn internal [x] 33) + (defn external [x] (+ (internal x) (internal (inc x)))) -;; The same thing can be done with clojure.core functions + (fact "calls not mentioned in prerequisites are passed through to real code" + (external 1) => 0 + (provided + (internal 1) => -33)) -(defn double-partition [first-seq second-seq] - (concat (partition-all 1 first-seq) (partition-all 1 second-seq))) -(fact (double-partition [1 2] [3 4]) => [ [1] [2] [3] [4] ]) + ;; The same thing can be done with clojure.core functions -(fact - (double-partition [1 2] ..xs..) => [[1] [2] [..x1..] [..x2..]] - (provided (partition-all 1 ..xs..) => [ [..x1..] [..x2..] ])) + (defn double-partition [first-seq second-seq] + (concat (partition-all 1 first-seq) (partition-all 1 second-seq))) + (fact (double-partition [1 2] [3 4]) => [ [1] [2] [3] [4] ]) -;; However you can't override functions that are used by Midje itself + (fact + (double-partition [1 2] ..xs..) => [[1] [2] [..x1..] [..x2..]] + (provided (partition-all 1 ..xs..) => [ [..x1..] [..x2..] ])) + -(defn all-even? [xs] (every? even? xs)) + ;; However you can't override functions that are used by Midje itself + ;; These are reported thusly: -(after-silently - (fact "get a user error from nested call to faked `every?`" + (defn message-about-mocking-midje-functions [reported] + (let [important-error + (find-first #(= (:type %) :mock-expected-result-functional-failure) + reported)] + (and important-error + (.getMessage (.throwable (:actual important-error)))))) + + (defn all-even? [xs] (every? even? xs)) + + (after-silently + (fact "get a user error from nested call to faked `every?`" (all-even? ..xs..) => truthy (provided (every? even? ..xs..) => true)) - (let [important-error (find-first #(= (:type %) :mock-expected-result-functional-failure) - @reported) - text (.getMessage (.throwable (:actual important-error)))] - - (fact + (fact + (let [text (message-about-mocking-midje-functions @reported)] text => #"seem to have created a prerequisite" text => #"clojure\.core/every\?" text => #"interferes with.*Midje"))) + ;; deref is a known special case that has to be detected differently + ;; than the one above. + + (def throwable-received nil) + + (try + (macroexpand '(fake (deref anything) => 5)) + (catch Throwable ex + ;; Weird things happen with atoms within a catch. + (alter-var-root #'throwable-received (constantly ex)))) + + (fact + throwable-received =not=> nil? + (let [text (.getMessage throwable-received)] + text => #"deref" + text => #"interferes with.*Midje")) -;; And inlined functions can't be faked + ;; And inlined functions can't be faked -(defn doubler [n] (+ n n)) + (defn doubler [n] (+ n n)) -(after-silently -(fact - (doubler 3) => 0 - (provided - (+ 3 3) => 0)) - (fact @reported => (validation-error-with-notes #"inlined"))) + (after-silently + (fact + (doubler 3) => 0 + (provided + (+ 3 3) => 0)) + (fact @reported => (validation-error-with-notes #"inlined"))) ;; How it works @@ -229,14 +226,17 @@ odd? 3 falsey) (def not-a-function 3) (def a-function (fn [x] x)) (usable-default-function? (fake (not-a-function 3) => 1)) => falsey - (usable-default-function? (fake (a-function 3) => 1)) => truthy) + (usable-default-function? (fake (a-function 3) => 1)) => truthy + (usable-default-function? (fake (#'a-function 3) => 1)) => truthy) (fact "It may not have been marked `unfinished`" (unfinished tbd) (usable-default-function? (fake (tbd 3) => 1)) => falsey + (usable-default-function? (fake (#'tbd 3) => 1)) => falsey ;; However, an unfinished-then-redefined function is allowed (unfinished forget-to-remove) (def forget-to-remove (fn [x] (+ 3 (* 3 x)))) - (usable-default-function? (fake (forget-to-remove 3) => 1)) => truthy) + (usable-default-function? (fake (forget-to-remove 3) => 1)) => truthy + (usable-default-function? (fake (#'forget-to-remove 3) => 1)) => truthy) (fact "It can be a multimethod" (defmulti multimethod type) (defmethod multimethod java.lang.String [x] "string me!") @@ -245,31 +245,41 @@ odd? 3 falsey) (defmulti multimethod type) (defmethod multimethod java.lang.String [x] "string me!") (fact "fakes can call default functions" - (call-faker #'multimethod ["some string"] [(fake (multimethod 4) => 3)]) + (handle-mocked-call #'multimethod ["some string"] [(fake (multimethod 4) => 3)]) => (multimethod "some string")) (fact "fakes keep track of their call counts" (let [fakes [(fake (f 1) => 3) (fake (g 1) => 4) - (fake (f 2) => 5)] - counts #(map fake-count fakes)] - (call-faker #'f [1] fakes) (counts) => [1 0 0] - (call-faker #'f [1] fakes) (counts) => [2 0 0] - (call-faker #'f [2] fakes) (counts) => [2 0 1] - (call-faker #'g [1] fakes) (counts) => [2 1 1])) + (fake (#'f 2) => 5)] + counts (fn [] + (map #(deref (:call-count-atom %)) fakes))] + (handle-mocked-call #'f [1] fakes) (counts) => [1 0 0] + (handle-mocked-call #'f [1] fakes) (counts) => [2 0 0] + (handle-mocked-call #'f [2] fakes) (counts) => [2 0 1] + (handle-mocked-call #'g [1] fakes) (counts) => [2 1 1])) +) +;; Closing the binding just above because Clojure 1.3 (and only +;; Clojure 1.3) becomes confused about unbound vars that are defined +;; inside of a `binding` scope. The binding in the fact below +;; causes `bound?` to return true, but dereferencing the var still returns +;; the magic value #. (def unbound-var) (def bound-var 3) (def #^:dynamic rebound) - + +(binding [midje.config/*allow-default-prerequisites* true] + (fact "fakes contain the value of their function-var at moment of binding" (:value-at-time-of-faking (fake (unbound-var) => 2)) => nil (:value-at-time-of-faking (fake (bound-var) => 888)) => 3 + (:value-at-time-of-faking (fake (#'bound-var) => 888)) => 3 (binding [rebound 88] (:value-at-time-of-faking (fake (rebound) => 3)) => 88)) +) - - ;; Folded fakes +;; Folded fakes (defmacro some-macro [& rest] ) @@ -343,7 +353,7 @@ odd? 3 falsey) (provided (metaconstant-for-form '(h 1)) => '...h-1...) "Which means that already-existing substitutions are reused" - (augment-substitutions {'(h 1) ...h-1...} '(fake (f (h 1)))) => '{ (h 1) ...h-1... }) + (augment-substitutions {'(h 1) ...h-1...} '(fake (#'f (h 1)))) => '{ (h 1) ...h-1... }) (fact "fakes are flattened by making substitutions" (flatten-fake '(fake (f (g 1) 2 (h 3)) =test=> 33 ...overrides...) @@ -352,27 +362,42 @@ odd? 3 falsey) (fact "generated fakes maintain overrrides" (let [g-fake '(midje.semi-sweet/fake (g 1) midje.semi-sweet/=> ...g-1... ...overrides...) - h-fake '(midje.semi-sweet/fake (h 3) midje.semi-sweet/=> ...h-1... ...overrides...)] - (set (generate-fakes '{ (g 1) ...g-1..., (h 3) ...h-1... } '(...overrides...))) + h-fake '(midje.semi-sweet/fake (#'h 3) midje.semi-sweet/=> ...h-1... ...overrides...)] + (set (generate-fakes '{ (g 1) ...g-1..., (#'h 3) ...h-1... } '(...overrides...))) => #{g-fake h-fake})) - -;;; Internal functions - (fact "data-fakes can be converted to metaconstant-bindings" - (let [bindings (data-fakes-to-metaconstant-bindings [{:lhs #'name :contained {:a 1}}]) - [_var_ metaconstant] (only (only bindings))] + (let [bindings (binding-map [{:data-fake true :var #'name :contained {:a 1}}]) + [_var_ metaconstant] (only bindings)] (.name metaconstant) => 'name (.storage metaconstant) => {:a 1} )) (declare var-for-merged var-for-irrelevant) -(fact "metaconstant bindings can have their values merged together" - (let [first-half (Metaconstant. 'merged {:retained 1, :replaced 2}) - second-half (Metaconstant. 'merged {:replaced 222, :extra 3}) - irrelevant (Metaconstant. 'irrelevant {:retained :FOO :extra :BAR}) - all [{#'var-for-merged first-half} {#'var-for-merged second-half} {#'var-for-irrelevant irrelevant}] - result (merge-metaconstant-bindings all)] - - (.storage (result #'var-for-merged)) => {:retained 1, :replaced 222, :extra 3} - (.storage (result #'var-for-irrelevant)) => {:retained :FOO, :extra :BAR})) +(fact "metaconstant bindings can have their values merged together" + (let [first-half {:data-fake true :var #'var-for-merged :contained {:retained 1, :replaced 2}} + second-half {:data-fake true :var #'var-for-merged :contained {:replaced 222, :extra 3}} + irrelevant {:data-fake true :var #'var-for-irrelevant :contained {:retained :FOO :extra :BAR}} + result (binding-map [first-half second-half irrelevant])] + (.storage (result #'midje.internal-ideas.t-fakes/var-for-merged)) => {:retained 1, :replaced 222, :extra 3} + (.storage (result #'midje.internal-ideas.t-fakes/var-for-irrelevant)) => {:retained :FOO, :extra :BAR})) + +(unfinished faked-fn) +(facts "fake and datafake maps include form info, so tool creators can introspect them" + (fake (faked-fn 1 1) => 2 :key :value) => (contains {:call-form '(faked-fn 1 1) + :arrow '=> + :rhs (contains [2 :key :value] :gaps-ok)}) + + (data-fake ..d.. =contains=> {:key :value}) => (contains {:call-form '..d.. + :arrow '=contains=> + :rhs (contains [{:key :value}])})) + + +;;; DO NOT DELETE +;;; These are used to test the use of vars to fake private functions +;;; in another namespace. + +(defn- var-inc [x] (inc x)) +(defn- var-inc-user [x] (* x (var-inc x))) +(defn- var-twice [] + (var-inc (var-inc 2))) diff --git a/test/midje/internal_ideas/t_file_position.clj b/test/midje/internal_ideas/t_file_position.clj index 023025a37..d8ebf263e 100644 --- a/test/midje/internal_ideas/t_file_position.clj +++ b/test/midje/internal_ideas/t_file_position.clj @@ -1,9 +1,7 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.internal-ideas.t-file-position (:use [midje.internal-ideas.file-position] [midje sweet test-util] - [midje.ideas.arrows :only [is-start-of-checking-arrow-sequence?]]) + [midje.ideas.arrows :only [start-of-checking-arrow-sequence?]]) (:require [clojure.zip :as zip])) (defn this-file [line-number] @@ -18,7 +16,7 @@ ;; came before sweet-mode and its use of the alternate method of determining file position. ;; That alternate could be back-ported to (fake) and (expect) -- it's less accurate, though. -(def line-marker-1 21) +(def line-marker-1 19) (let [position (user-file-position)] (fact "you can capture the filename/linenumber of a code line" position => (this-file (+ line-marker-1 1)))) @@ -70,32 +68,32 @@ "Typical case is form on left. (f 1) => 5" (let [form `( ~(at-line 33 '(f 1)) => 5) loc (-> form zip/seq-zip zip/down)] - loc => is-start-of-checking-arrow-sequence? + loc => start-of-checking-arrow-sequence? (arrow-line-number (zip/right loc)) => 33) "When form on the left is has no line, check right: ...a... => (exactly 1)" (let [form `( ...a... => ~(at-line 33 '(exactly 1))) loc (-> form zip/seq-zip zip/down)] - loc => is-start-of-checking-arrow-sequence? + loc => start-of-checking-arrow-sequence? (arrow-line-number (zip/right loc)) => 33) "If both sides have line numbers, the left takes precedence: (f 1) => (exactly 1)" (let [form `( ~(at-line 33 '(f 1)) => ~(at-line 34 '(exactly 1))) loc (-> form zip/seq-zip zip/down)] - loc => is-start-of-checking-arrow-sequence? + loc => start-of-checking-arrow-sequence? (arrow-line-number (zip/right loc)) => 33) "If neither side has a line number, look to the left and add 1: (let [a 2] a => b)" (let [form `( (let ~(at-line 32 '[a 2]) a => b)) loc (-> form zip/seq-zip zip/down zip/down zip/right zip/right)] - loc => is-start-of-checking-arrow-sequence? + loc => start-of-checking-arrow-sequence? (arrow-line-number (zip/right loc)) => 33) "Default result is is one plus the fallback line number." (set-fallback-line-number-from (at-line 333 '(previous form))) (let [form '(1 => 2) loc (-> form zip/seq-zip zip/down)] - loc => is-start-of-checking-arrow-sequence? + loc => start-of-checking-arrow-sequence? (arrow-line-number (zip/right loc)) => 334 ;; incrementing happens more than once @@ -212,3 +210,5 @@ (take-last 2 expansion) => '(:position (midje.internal-ideas.file-position/line-number-known 33))))) +(fact "Issue #117 - arrows inside quoted forms will not have :position info added" + '(fact foo => bar) => '(fact foo => bar)) \ No newline at end of file diff --git a/test/midje/internal_ideas/t_unfinished.clj b/test/midje/internal_ideas/t_unfinished.clj new file mode 100644 index 000000000..e068baca9 --- /dev/null +++ b/test/midje/internal_ideas/t_unfinished.clj @@ -0,0 +1,12 @@ +(ns midje.internal-ideas.t-unfinished + (:use [midje sweet test-util])) + +(unfinished backing-function) + +(fact "unfinished produces a function that throws an exception" + (backing-function 2) => (throws Error) + "it prints useful information about how the call was made" + (backing-function 2 "string") => (throws Error + #"no implementation" + #"\(backing-function 2 \"string\"\)")) + diff --git a/test/midje/t_checkers.clj b/test/midje/t_checkers.clj index 8cb1808bf..489869ad9 100644 --- a/test/midje/t_checkers.clj +++ b/test/midje/t_checkers.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.t-checkers (:use midje.sweet midje.test-util)) diff --git a/test/midje/t_open_protocols.clj b/test/midje/t_open_protocols.clj index 00313f765..bcac53da4 100644 --- a/test/midje/t_open_protocols.clj +++ b/test/midje/t_open_protocols.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.t-open-protocols (:use midje.open-protocols) (:use [midje sweet test-util])) diff --git a/test/midje/t_semi_sweet.clj b/test/midje/t_semi_sweet.clj index eadbf5270..7cfc45c3b 100644 --- a/test/midje/t_semi_sweet.clj +++ b/test/midje/t_semi_sweet.clj @@ -1,10 +1,9 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.t-semi-sweet (:use [clojure.test] ;; This is used to check production mode with deftest. [midje.sweet] [midje.util form-utils] - [midje.test-util]) + [midje.test-util] + midje.util) (:require [clojure.zip :as zip])) (expose-testables midje.semi-sweet) @@ -59,9 +58,9 @@ fake-streamed (fake (faked-function 0) =streams=> ['r1 'r2])] (fact "The basic parts" - (:lhs fake-0) => #'midje.t-semi-sweet/faked-function + (:var fake-0) => #'midje.t-semi-sweet/faked-function (:call-text-for-failures fake-1) => "(faked-function some-variable)" - (deref (:count-atom fake-0)) => 0) + (deref (:call-count-atom fake-0)) => 0) (fact "argument matching" (count (:arg-matchers fake-0)) => 0) @@ -93,9 +92,9 @@ (facts "about not-called" (let [fake-0 (not-called faked-function)] - (:lhs fake-0) => #'midje.t-semi-sweet/faked-function + (:var fake-0) => #'midje.t-semi-sweet/faked-function (:call-text-for-failures fake-0) => "faked-function was called." - @(:count-atom fake-0) => 0 + @(:call-count-atom fake-0) => 0 (:arg-matchers fake-0) => nil? ((:result-supplier fake-0)) => nil?)) @@ -159,17 +158,6 @@ (fake (other-function 12) => 1)) @reported => (just pass))) - (fact "call that matches none of the expected arguments" - (after-silently - (expect (+ (mocked-function 12) (mocked-function 33)) => "result irrelevant because of earlier failure" - (fake (mocked-function 12) => "hi")) - ;; At the moment, this does not produce a :mock-argument-match-failure. What it does - ;; is call the "unfinished" function, which blows up, producing a `bad-result` (the exception - ;; object). That's not horrible, especially at the semi-sweet level, but it does suggest - ;; that the implementation of the sweet behavior (where an unfinished function produces - ;; an argument-match failure) is fragile. - @reported =future=> (just (contains {:type :mock-argument-match-failure :actual '(33)}) - bad-result))) (fact "failure because one variant of multiply-mocked function is not called" (after-silently @@ -178,9 +166,20 @@ (fake (mocked-function 22) => 2) (fake (mocked-function 33) => 3)) @reported => (just (contains {:type :mock-incorrect-call-count - :expected-call "(mocked-function 33)" }) + :failures (contains (contains {:expected-call "(mocked-function 33)"})) }) pass))) ; Right result, but wrong reason. + (fact "failure because one variant of multiply-mocked function is not called" + (after-silently + (expect (+ (mocked-function 12) (mocked-function 22)) => 3 + (fake (mocked-function 12) => 1) + (fake (mocked-function 22) => 2) + (fake (mocked-function 33) => 3)) + @reported => (just (contains {:type :mock-incorrect-call-count + :failures (contains (contains {:expected-call "(mocked-function 33)"})) }) + pass))) ; Right result, but wrong reason. + + (fact "multiple calls to a mocked function are perfectly fine" (expect (+ (mocked-function 12) (mocked-function 12)) => 2 (fake (mocked-function 12) => 1) ))) @@ -205,7 +204,15 @@ :expected-result expected (fake (mocked-function 1) => 5 :result-supplier "IGNORED" :result-supplier (fn [] expected))))) - + + +(defn backing-function [s] s) + + +(fact "mocks can be partial: they fall through to any previously defined function" + (binding [midje.config/*allow-default-prerequisites* true] + (expect (str (backing-function "returned") " " (backing-function "overridden")) => "returned new value" + (fake (backing-function "overridden") => "new value")))) (facts "about checkers" (fact "expected results can be functions" @@ -290,7 +297,7 @@ (after-silently (expect (+ 1 "3") =future=> 3) @reported => (one-of (contains {:type :future-fact - :description "about =future=> - (+ 1 \"3\")" })))) + :description ["about =future=>" "(+ 1 \"3\")"] })))) @@ -305,3 +312,8 @@ @reported => (one-of (contains {:type :mock-expected-result-failure :actual `(clojure.core/+ 100 200 1) :expected `(clojure.core/- 100 200 1)}))))) + +(fact "add form info to unprocessed check so tool creators can introspect them" + (unprocessed-check (+ 1 1) ..arrow.. 2 []) => (contains {:call-form '(+ 1 1) + :arrow ..arrow.. + :expected-result 2})) diff --git a/test/midje/t_sweet.clj b/test/midje/t_sweet.clj index d63c604da..208eaa999 100644 --- a/test/midje/t_sweet.clj +++ b/test/midje/t_sweet.clj @@ -1,13 +1,14 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.t-sweet - (:use [midje.sweet]) - (:use [midje.test-util])) + (:use midje.sweet + midje.util + midje.test-util) + (:require midje.internal-ideas.t-fakes)) (fact "all of Midje's public, API-facing vars have docstrings" (map str (remove (comp :doc meta) (vals (ns-publics 'midje.sweet)))) => [] (map str (remove (comp :doc meta) (vals (ns-publics 'midje.semi-sweet)))) => [] - (map str (remove (comp :doc meta) (vals (ns-publics 'midje.unprocessed)))) => []) + (map str (remove (comp :doc meta) (vals (ns-publics 'midje.unprocessed)))) => [] + (map str (remove (comp :doc meta) (vals (ns-publics 'midje.util)))) => []) (after-silently ; failing (fact (+ 1 1) => 3) @@ -27,24 +28,13 @@ (+ 1 1) => 2) (fact @reported => (just bad-result (contains {:type :future-fact - :description "(+ 1 \"1\")"}) + :description [nil "(+ 1 \"1\")"]}) pass))) (defn number [] ) (defn two-numbers [] (+ (number) (number))) -(letfn [(stream-overflow-exception? [captured-throwable] - (= "Your =stream=> ran out of values." (.getMessage (.throwable captured-throwable))))] - - (after-silently ;; streams give sensible error when they run dry - (fact - (two-numbers) => 2 - (provided - (number) =streams=> [1])) - - (fact @reported => (just (contains {:type :mock-expected-result-failure - :actual stream-overflow-exception? } ))))) (letfn [(throws-arrow-exception? [captured-throwable] (= "Right side of =throws=> should extend Throwable." (.getMessage (.throwable captured-throwable))))] @@ -161,20 +151,20 @@ (facts "A" (fact "B" (+ 1 2) => 1)) - (fact @reported => (one-of (contains {:description "A - B"} )))) + (fact @reported => (one-of (contains {:description ["A" "B"]} )))) (after-silently (facts "level 1" (fact "level 2" (fact "level 3" (throw (Exception. "BOOM")) => anything))) - (fact @reported => (one-of (contains {:description "level 1 - level 2 - level 3"} )))) + (fact @reported => (one-of (contains {:description ["level 1" "level 2" "level 3"]} )))) (after-silently (facts "about mathemtics" (future-fact "do in future" nil => 1)) - (fact @reported => (one-of (contains {:description "about mathemtics - do in future"} )))) + (fact @reported => (one-of (contains {:description ["about mathemtics" "do in future"]} )))) ;; Background prerequisites (unfinished check-f check-g check-h) @@ -253,7 +243,7 @@ (provided (called 1) => 1 :times 2)) (fact @reported => (contains (contains {:type :mock-incorrect-call-count - :actual-count 1})))) + :failures (contains (contains {:actual-count 1}))})))) (after-silently (fact @@ -261,7 +251,7 @@ (provided (called 1) => 1 :times (range 2 8))) (fact @reported => (contains (contains {:type :mock-incorrect-call-count - :actual-count 1})))) + :failures (contains (contains {:actual-count 1}))})))) (after-silently @@ -270,21 +260,16 @@ (provided (called 1) => 1 :times even?)) (fact @reported => (contains (contains {:type :mock-incorrect-call-count - :actual-count 3})))) + :failures (contains (contains {:actual-count 3}))})))) (fact (do (called 1) (called 1)) => 1 (provided (called 1) => 1)) -(defn f [x] (inc x)) -(defn g [x] (* x (f x))) -(future-fact "can fake vars directly" - (#'g 2) => 6 - (provided - (#'f 2) => 2)) - + + ;; Possibly the most common case (after-silently (fact @@ -292,7 +277,7 @@ (provided (called irrelevant) => 1 :times 0)) (fact @reported => (contains (contains {:type :mock-incorrect-call-count - :actual-count 1})))) + :failures (contains (contains {:actual-count 1}))})))) (def #^:dynamic *fact-retval* (fact @@ -304,7 +289,7 @@ (def #^:dynamic *fact-retval* (fact (+ 1 1) => 2 - (midje.internal-ideas.report/note-failure-in-fact) + (midje.ideas.reporting.report/note-failure-in-fact) "some random return value")) (fact "fact returns false on failure" *fact-retval* => false) @@ -316,17 +301,89 @@ (fact "a fact's return value is not affected by previous failures" *fact-retval* => true) - (defn a []) (defn b [] (a)) -(fact "two ways prerequisites can throw throwables" +(fact "prerequisites can throw throwables" (b) => (throws Exception) (provided - (a) =throws=> (Exception.)) + (a) =throws=> (Exception.))) - (b) => (throws Exception) + +;; Tool creators can hook into the maps generated by the Midje compilation process + +(unfinished foo) +(defn-call-countable noop-fn [& args] :do-nothing) +(binding [midje.semi-sweet/*expect-checking-fn* noop-fn] + (fact :ignored => :ignored + (provided + (foo) => :bar))) +(fact @noop-fn-count => 1) + + + ;; In prerequisites, functions can be referred to by vars as well as symbols + +;;; These functions are duplicated in t_fakes.clj, since the whole +;;; point of allowing vars is to refer to private vars in another +;;; namespace. To make sure there's no mistakes, these local versions +;;; are so tagged. +;;; +(defn var-inc-local [x] (inc x)) +(defn var-inc-user-local [x] (* x (var-inc-local x))) +(defn var-twice-local [] + (var-inc-local (var-inc-local 2))) + +(fact "can fake private remote-namespace functions using vars" + (#'midje.internal-ideas.t-fakes/var-inc-user 2) => 400 + (provided + (#'midje.internal-ideas.t-fakes/var-inc 2) => 200)) + +(fact "and can fake local functions using vars" + (#'var-inc-user-local 2) => 400 + (provided + (#'var-inc-local 2) => 200)) + +(fact "default prerequisites work with vars" + (binding [midje.config/*allow-default-prerequisites* true] + (#'midje.internal-ideas.t-fakes/var-twice) => 201 + (provided + (#'midje.internal-ideas.t-fakes/var-inc 2) => 200))) + +;;; Unfolded prerequisites + +(fact "vars also work with unfolded prerequisites" + (var-twice-local) => 201 + (provided + (var-inc-local (var-inc-local 2)) => 201)) + +(defn here-and-there [] + (var-inc-local (#'midje.internal-ideas.t-fakes/var-inc 2))) + +(fact "vars also work with unfolded prerequisites" + (here-and-there) => 201 + (provided + (var-inc-local (#'midje.internal-ideas.t-fakes/var-inc 2)) => 201)) + +(defn there-and-here [] + (#'midje.internal-ideas.t-fakes/var-inc (var-inc-local 2))) + +(fact "vars also work with unfolded prerequisites" + (there-and-here) => 201 (provided - (a) =streams=> [(throw (Exception.))])) + (#'midje.internal-ideas.t-fakes/var-inc (var-inc-local 2)) => 201)) +(defn over-there-over-there-spread-the-word-to-beware [] + (#'midje.internal-ideas.t-fakes/var-inc + (#'midje.internal-ideas.t-fakes/var-inc 2))) + +(fact "vars also work with unfolded prerequisites" + (over-there-over-there-spread-the-word-to-beware) => 201 + (provided + (#'midje.internal-ideas.t-fakes/var-inc + (#'midje.internal-ideas.t-fakes/var-inc 2)) => 201)) +(after-silently + (future-fact "exceptions do not blow up" + "foo" => odd?) + (future-fact @reported => (just bad-result))) + diff --git a/test/midje/t_unprocessed.clj b/test/midje/t_unprocessed.clj index 371cc71e4..7b1d50a60 100644 --- a/test/midje/t_unprocessed.clj +++ b/test/midje/t_unprocessed.clj @@ -1,9 +1,6 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.t-unprocessed - (:use clojure.test) - (:use [midje.unprocessed]) - (:use [midje.test-util])) + (:use midje.sweet + midje.test-util)) ;;; Everything is tested indirectly through semi-sweet, including some of diff --git a/test/midje/test_util.clj b/test/midje/test_util.clj index 6e05c25db..3541096b6 100644 --- a/test/midje/test_util.clj +++ b/test/midje/test_util.clj @@ -1,17 +1,19 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.test-util (:use [clojure.test] midje.checkers + [midje.checkers.extended-equality :only [extended-=]] + [midje.checkers.extended-falsehood :only [extended-false?]] + [midje.sweet :only [against-background]] + midje.error-handling.exceptions [clojure.set :only [subset?]] - [midje.util.form-utils :only [macro-for alias-var]])) + [midje.util.form-utils :only [macro-for]])) (def reported (atom [])) -(defn run-without-reporting [function] +(defn run-without-reporting [f] (binding [report (fn [report-map#] (swap! reported conj report-map#))] (reset! reported []) - (function))) + (f))) (defmacro run-silently [run-form] `(run-without-reporting (fn [] ~run-form))) @@ -52,21 +54,97 @@ (def checker-fails (contains {:type :mock-expected-result-functional-failure})) (def wrong-call-count (contains {:type :mock-incorrect-call-count})) (def a-validation-error (contains {:type :validation-error})) +(def no-matching-prerequisite (contains {:type :mock-argument-match-failure})) + + +;; Applied to lists of result maps +(letfn [(make-collection-checker [unit-checker] + (checker [reporteds] + (some (comp not extended-false?) (map unit-checker reporteds))))] + (defchecker has-bad-result [reporteds] + (make-collection-checker bad-result)) + (defchecker has-wrong-call-count [reporteds] + (make-collection-checker wrong-call-count)) + + (defn passes [reporteds] + (every? pass reporteds)) +) + + +(defchecker has-thrown-message [expected] + (checker [reporteds] + (some (fn [one-report] + (and (:actual one-report) + (captured-throwable? (:actual one-report)) + (extended-= (.getMessage (throwable (:actual one-report))) + expected))) + reporteds))) (defn at-line [line-no form] - (with-meta form {:line line-no})) + (with-meta form {:line line-no})) (defmacro validation-error-with-notes [& notes] `(just (contains {:notes (just ~@notes) :type :validation-error}))) -(defmacro causes-validation-error [error-msg & body] +(defmacro causes-validation-error + "check if the body, when executed, creates a syntax validation error" + [error-msg & body] `(after-silently ~@body (midje.sweet/fact @reported midje.sweet/=> (one-of (contains {:type :validation-error :notes (contains ~error-msg)}))))) +(defmacro each-causes-validation-error + "check if each row of the body, when executed, creates a syntax validation error" + [error-msg & body] + (macro-for [row body] + `(causes-validation-error ~error-msg ~row))) + (defmacro with-identity-renderer [& forms] - `(binding [midje.internal-ideas.report/*renderer* identity] ~@forms)) \ No newline at end of file + `(binding [midje.ideas.reporting.report/*renderer* identity] ~@forms)) + +(defmacro defn-call-countable + "Note: For testing Midje code that couldn't use provided. + + Creates a function that records how many times it is called, and records + that count in an atom with the same name as the function with \"-count\" appended" + [name args & body] + (let [atom-name (symbol (str name "-count"))] + `(do + (def ~atom-name (atom 0)) + (defn ~name ~args + (swap! ~atom-name inc) + ~@body)))) + + + +;; Some sets of tests generate failures. The following code prevents +;; them from being counted as failures when the final summary is +;; printed. The disadvantage is that legitimate failures won't appear +;; in the final summary. They will, however, produce failure output, +;; so that's an acceptable compromise. + +(defmacro without-counting-failures [& forms] + `(do + (when (nil? clojure.test/*report-counters*) + (alter-var-root #'clojure.test/*report-counters* + (constantly (ref clojure.test/*initial-report-counters*)))) + + (against-background + [(around :facts + (let [report-counters# @clojure.test/*report-counters*] + ?form + (dosync (commute clojure.test/*report-counters* + (constantly report-counters#)))))] + ~@forms))) + +(def test-output (atom nil)) + +(defmacro capturing-output [fact1 fact2] + `(do + (reset! test-output + (with-out-str (without-counting-failures ~fact1))) + ~fact2)) diff --git a/test/midje/util/t_colorize.clj b/test/midje/util/t_colorize.clj index 708e0ea13..85a092f50 100644 --- a/test/midje/util/t_colorize.clj +++ b/test/midje/util/t_colorize.clj @@ -39,20 +39,16 @@ (provided (getenv "MIDJE_COLORIZE") => anything :times 0)) + + (fact (colorize-deftest-output "FAIL in deftest failure message") - => "\u001b[31mFAIL\u001b[0m in deftest failure message" - (provided - (fail "FAIL") => "\u001b[31mFAIL\u001b[0m" )) + => "\u001b[31mFAIL\u001b[0m in deftest failure message") (fact (colorize-deftest-output "ERROR in deftest failure message") - => "\u001b[31mERROR\u001b[0m in deftest failure message" - (provided - (fail "ERROR") => "\u001b[31mERROR\u001b[0m" )) + => "\u001b[31mERROR\u001b[0m in deftest failure message") (fact (colorize-deftest-output "ERROR in deftest failure message ERROR") - =not=> "\u001b[31mERROR\u001b[0m in deftest failure message \u001b[31mERROR\u001b[0m" - (provided - (fail "ERROR") => "\u001b[31mERROR\u001b[0m" )) \ No newline at end of file + =not=> "\u001b[31mERROR\u001b[0m in deftest failure message \u001b[31mERROR\u001b[0m") diff --git a/test/midje/util/t_form_utils.clj b/test/midje/util/t_form_utils.clj index 0c252f278..bf2be1093 100644 --- a/test/midje/util/t_form_utils.clj +++ b/test/midje/util/t_form_utils.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.util.t-form-utils (:use [midje.util.form-utils]) (:use [midje.sweet]) @@ -76,17 +74,19 @@ (tabular (fact "A single argument can be converted into a structured-form and a arg-value-name" - (against-background (unique-argument-name) => 'unique-3) - (let [[form name] (single-arg-into-form-and-name ?original)] + (against-background (gensym 'symbol-for-destructured-arg) => 'unique-3) + (let [[form name] (single-destructuring-arg->form+name ?original)] form => ?form name => ?name)) - ?original ?form ?name - 'a 'a 'a - '[a b] '[a b :as unique-3] 'unique-3 - '[a b & c :as all] '[a b & c :as all] 'all + ?original ?form ?name + 'a 'a 'a + '[a b] '[a b :as unique-3] 'unique-3 + '[a b & c :as all] '[a b & c :as all] 'all + '{:keys [a b]} '{:keys [a b] :as unique-3} 'unique-3 + '{:keys [a b] :as all} '{:keys [a b] :as all} 'all ;; pathological cases - '[a] '[a :as unique-3] 'unique-3 - '[a :as b] '[a :as b] 'b) + '[a] '[a :as unique-3] 'unique-3 + '[a :as b] '[a :as b] 'b) (defrecord ExampleNamed [] diff --git a/test/midje/util/t_laziness.clj b/test/midje/util/t_laziness.clj index 4bb4fe263..43cfbb9fa 100644 --- a/test/midje/util/t_laziness.clj +++ b/test/midje/util/t_laziness.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.util.t-laziness (:use [midje.sweet] [midje.util laziness thread-safe-var-nesting] diff --git a/test/midje/util/t_namespace.clj b/test/midje/util/t_namespace.clj index d686377d8..bc045ef8f 100644 --- a/test/midje/util/t_namespace.clj +++ b/test/midje/util/t_namespace.clj @@ -1,10 +1,8 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.util.t-namespace - (:use [midje.util.namespace]) - (:use midje.sweet) - (:require [clojure.zip :as zip]) - (:use midje.test-util)) + (:use midje.util.namespace + midje.sweet + midje.test-util) + (:require [clojure.zip :as zip])) (fact "matches-symbols-in-semi-sweet-or-sweet-ns? accepts symbols from different midje namespaces" (let [values (zip/seq-zip '(m midje.semi-sweet/expect)) @@ -20,5 +18,5 @@ data-fake midje.semi-sweet/data-fake)] (let [z (zip/seq-zip `(111 (~skippable 1 2 '(3)) "next")) skippable (-> z zip/down zip/next zip/down)] - skippable => is-semi-sweet-keyword?))) + skippable => semi-sweet-keyword?))) diff --git a/test/midje/util/t_thread_safe_var_nesting.clj b/test/midje/util/t_thread_safe_var_nesting.clj index 0860f77a4..e81f66783 100644 --- a/test/midje/util/t_thread_safe_var_nesting.clj +++ b/test/midje/util/t_thread_safe_var_nesting.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.util.t-thread-safe-var-nesting (:use [midje.util.thread-safe-var-nesting]) (:use midje.sweet) diff --git a/test/midje/util/t_unify.clj b/test/midje/util/t_unify.clj index f3c9df473..bcb9c48a2 100644 --- a/test/midje/util/t_unify.clj +++ b/test/midje/util/t_unify.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.util.t-unify (:use midje.util.unify midje.sweet diff --git a/test/midje/util/t_zip.clj b/test/midje/util/t_zip.clj index 7bedc3585..1069b05db 100644 --- a/test/midje/util/t_zip.clj +++ b/test/midje/util/t_zip.clj @@ -1,5 +1,3 @@ -;; -*- indent-tabs-mode: nil -*- - (ns midje.util.t-zip (:use [midje.util.zip] midje.sweet