├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── CHANGES.md ├── README.md ├── autoexpect ├── bin │ └── release ├── project.clj ├── resources │ ├── fail.png │ └── pass.png └── src │ ├── autoexpect │ └── runner.clj │ └── leiningen │ └── autoexpect.clj └── sample-project ├── .gitignore ├── README.md ├── project.clj ├── src └── lein2 │ └── core.clj └── test └── lein2 ├── more_expectations.clj └── sample_expectations.clj /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Use the template below when reporting bugs. Please make sure the 2 | problem is still a problem with the latest version of 3 | lein-autoexpect before submitting.* 4 | 5 | **What version of lein-autoexpect?** 6 | 7 | **What version of Leiningen?** 8 | 9 | **What version of expectations?** 10 | 11 | **Can you recreate the issue with a minimal project and share it?** 12 | 13 | **If you can't do above, can you share your project.clj and project structure?** 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | .lein-failures 6 | .lein-deps-sum 7 | target 8 | 9 | pom.xml.asc 10 | *.iml 11 | .lsp 12 | .clj-kondo 13 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 1.10.2 4 | 5 | - added option for exit-on-pass 6 | 7 | ## 1.10.0 8 | 9 | - lein-autoexpect will only reload the directories in `:source-paths` and `:test-paths`. 10 | - If you press the Enter/Return key when focused on the terminal running lein autoexpect, the tests will rerun. 11 | 12 | ## 1.9.0 13 | 14 | - Growl notifications happen quicker and have a green or red icon 15 | depending on test status. 16 | 17 | ## 1.8.0 18 | 19 | - Adds support for only sending notifications if your test status 20 | changes state (move from failing to passing or passing to failing). To 21 | turn on supply the flag `:change-only` at the command line: `lein autoexpect :notify :change-only`. 22 | 23 | ## 1.7.0 24 | 25 | - Use `clojure.stacktrace/root-cause` and 26 | `clojure.stacktrace/print-cause-trace` to print stackstraces when 27 | there is an exception reloading an environment. 28 | 29 | ## 1.6.0 30 | 31 | - Upgrade to `[org.clojure/tools.namespace "0.2.11"]`. This should 32 | improve reader conditional support. 33 | 34 | ## 1.5.0 35 | 36 | - Printing to stdout no longer suppressed when reloading namespaces. 37 | 38 | ## 1.4.3 39 | 40 | - Upgrade to `[org.clojure/tools.namespace "0.2.10"]`. 41 | 42 | ## 1.4.2 43 | 44 | - Upgrade to `[org.clojure/tools.namespace "0.2.8"]`. 45 | 46 | ## 1.4.0 47 | 48 | - Adds support for using 49 | [terminal-notifier](https://github.com/alloy/terminal-notifier) for 50 | notifications. Use `lein autoexpect :notify` to use terminal-notifier. 51 | 52 | ## 1.3.0 53 | 54 | - Upgrade to [org.clojure/tools.namespace "0.2.7"]. 55 | - Run all expectations after reloading code instead of just 56 | expectations affected by code reloading. 57 | 58 | ## 1.2.2 59 | 60 | - lein-autoexpect prints out current time after running the tests. 61 | 62 | ## 0.2.2 63 | 64 | - Adds support for reporting test results using Growl. Use `lein 65 | autoexpect :growl` 66 | - Upgrade to `org.clojure/tools.namespace 0.2.1`. This version of 67 | tools.namespace provides better backwards compatibility with 68 | versions prior to 0.2.0. 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autoexpect 2 | 3 | Leiningen plug-in for automatically running [expectations](https://github.com/jaycfields/expectations) whenever your Clojure project's source changes. 4 | 5 | If you are using expectations clojure.test compatible syntax, you'll want to use [lein-test-refresh](https://github.com/jakemcc/lein-test-refresh). 6 | 7 | ## Features 8 | 9 | - Allows you to have extremely fast feedback cycles by automatically 10 | loading changed code and running your expectations. 11 | - Supports growl notifications of test status. `lein autoexpect :growl` 12 | - Supports OS X notifications using `terminal-notifier`: `lein autoexpect :notify`. 13 | - Supports only notifying when test status changes by adding command 14 | line flag to either of the above commands. `lein autoexpect :notify :change-only` 15 | - Supports exiting on the first fully passing run. `lein autoexpect :exit-on-pass` 16 | 17 | ## Usage 18 | 19 | Here is what using it looks like. 20 | 21 | $ lein autoexpect 22 | ********************************************* 23 | *************** Running tests *************** 24 | Ran 3 tests containing 3 assertions in 16 msecs 25 | 0 failures, 0 errors. 26 | 27 | Your terminal will just stay like that. Every half second autoexpect 28 | polls the file system to see if anything has changed. When there is a 29 | change your code is tested again. 30 | 31 | If you want to receive notifications using growl, then run `lein 32 | autoexpect :growl`. This has been tested with modern versions of Growl 33 | for [OS X](http://growl.info/), 34 | [Linux](http://mattn.github.com/growl-for-linux/), and 35 | [Windows](http://growlforwindows.com/). 36 | 37 | If you would like to use the OS X notification center, use `lein autoexpect :notify`. 38 | This requires [terminal-notifier](https://github.com/alloy/terminal-notifier), which you can install using `brew install terminal-notifier`. 39 | 40 | 41 | ### Latest version 42 | 43 | The version in the image below is the latest (and hopefully greatest) released version of `lein-autoexpect`. It is what version number should be used in any of the verion numbers specified lower than this point in the README. 44 | 45 | [![Latest version](https://clojars.org/lein-autoexpect/latest-version.svg)](https://clojars.org/lein-autoexpect) 46 | 47 | ### Using with Leiningen 2.0 48 | 49 | Add `[lein-autoexpect "1.10.2"]` to your `~/.lein/profiles.clj` as 50 | follows: 51 | 52 | {:user {:plugins [[lein-autoexpect "1.10.2"]]}} 53 | 54 | Alternatively add to your `:plugins` vector in your project.clj file. 55 | 56 | (defproject sample 57 | :dependencies [[org.clojure/clojure "1.9.0"]] 58 | :profile {:dev {:dependencies [[expectations "2.0.9"]]}} 59 | :plugins [[lein-autoexpect "1.10.2"]]) 60 | 61 | ## Compatibility 62 | 63 | autoexpect should work with any version of expectations. If there is 64 | an issue please report it. It has been tested it with versions 1.1.0, 65 | 1.3.[023678], and 1.4.*, and 2.0.9. 66 | 67 | Because of 68 | [tools.namespace](https://github.com/clojure/tools.namespace) changes 69 | `lein-autoexpect` requires that your project use Clojure >= 1.3.0. If 70 | your project also depends on a version of `tools.namespace` < 0.2.1 71 | you may see occasional exceptions. 72 | 73 | ## License 74 | 75 | Copyright (C) 2011-2016 [Jake McCrary](http://jakemccrary.com) 76 | 77 | Distributed under the Eclipse Public License, the same as Clojure. 78 | 79 | 80 | -------------------------------------------------------------------------------- /autoexpect/bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | pushd "$DIR/.." >/dev/null 5 | 6 | set -e 7 | set -x 8 | 9 | function version { 10 | grep defproject project.clj | cut -f 3 -d ' ' | sed 's/"//g' 11 | } 12 | 13 | function project_name { 14 | grep defproject project.clj | cut -f 2 -d ' ' 15 | } 16 | 17 | 18 | lein change version leiningen.release/bump-version release 19 | release_version=$(version) 20 | tag_name="$(project_name)-v${release_version}" 21 | 22 | echo "committing + tagging" 23 | git commit -avm "Preparing for release $(project_name) ${release_version}" 24 | git tag -a $tag_name -m "Release $(project_name) v${release_version}" 25 | git push --tags 26 | 27 | lein change version leiningen.release/bump-version 28 | echo "Starting development on $(version)" 29 | git commit -avm "Starting development on $(project_name) $(version)" 30 | git push 31 | 32 | git checkout $tag_name 33 | lein deploy 34 | git checkout master 35 | -------------------------------------------------------------------------------- /autoexpect/project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-autoexpect "1.10.3-SNAPSHOT" 2 | :description "Automatically run expecations when a source file changes" 3 | :url "https://github.com/jakemcc/lein-autoexpect" 4 | :developer "Jake McCrary" 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | :resource-paths ["resources"] 8 | :dependencies [[org.clojure/tools.namespace "0.2.11" :exclusions [org.clojure/clojure]] 9 | [leinjacker "0.4.2" :exclusions [org.clojure/clojure]] 10 | [gntp "0.6.0" :exclusions [org.clojure/clojure]]] 11 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.8.0"] 12 | [expectations "2.0.6"]]}} 13 | :deploy-repositories [["snapshots" {:url "https://clojars.org/repo" 14 | :username :gpg :password :gpg}] 15 | ["releases" {:url "https://clojars.org/repo" 16 | :username :gpg :password :gpg}]] 17 | :scm {:name "git" 18 | :url "https://github.com/jakemcc/lein-autoexpect"}) 19 | -------------------------------------------------------------------------------- /autoexpect/resources/fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-expectations/lein-autoexpect/479ec85b8c7546d46d8bce28fe1aa79082305573/autoexpect/resources/fail.png -------------------------------------------------------------------------------- /autoexpect/resources/pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-expectations/lein-autoexpect/479ec85b8c7546d46d8bce28fe1aa79082305573/autoexpect/resources/pass.png -------------------------------------------------------------------------------- /autoexpect/src/autoexpect/runner.clj: -------------------------------------------------------------------------------- 1 | (ns autoexpect.runner 2 | (:require clojure.tools.namespace.track 3 | clojure.tools.namespace.repl 4 | [clojure.stacktrace :as stacktrace] 5 | expectations 6 | [gntp :refer [make-growler]] 7 | [clojure.java.io :refer [input-stream resource]] 8 | [clojure.string :as str] 9 | [clojure.java.shell :refer [sh]])) 10 | 11 | (defn- turn-off-testing-at-shutdown [] 12 | (reset! expectations/run-tests-on-shutdown false)) 13 | 14 | (defn- make-change-tracker [] 15 | (clojure.tools.namespace.track/tracker)) 16 | 17 | (defn- scan-for-changes [tracker] 18 | (clojure.tools.namespace.dir/scan tracker)) 19 | 20 | (defn- refresh-environment [] 21 | (clojure.tools.namespace.repl/refresh)) 22 | 23 | (def ^:private top-stars (apply str (repeat 45 "*"))) 24 | (def ^:private side-stars (apply str (repeat 15 "*"))) 25 | 26 | (defn- print-banner [] 27 | (println) 28 | (println top-stars) 29 | (println side-stars "Running tests" side-stars)) 30 | 31 | (defn- print-end-message [] 32 | (let [date-str (.format (java.text.SimpleDateFormat. "HH:mm:ss.SSS") 33 | (java.util.Date.))] 34 | (println "Tests completed at" date-str))) 35 | 36 | (def ^:private resource-stream (comp input-stream resource)) 37 | 38 | (def ^:private growlers 39 | (delay ((make-growler "AutoExpect") 40 | "Passed" {:name "Passed"} 41 | "Failed" {:name "Failed"} 42 | "Error" {:name "Error"}))) 43 | 44 | (defn- limit [string n] 45 | (if (< (count string) n) 46 | string 47 | (str (subs string 0 n) "..."))) 48 | 49 | (defn- growl-icon [status] 50 | (case status 51 | "Passed" (resource-stream "pass.png") 52 | "Failed" (resource-stream "fail.png") 53 | "Error" (resource-stream "fail.png"))) 54 | 55 | (defn- growl [status message] 56 | (try 57 | ((get @growlers status) status :text (limit message 500) :icon (growl-icon status)) 58 | (catch Exception ex 59 | (println "Problem communicating with growl, exception:" (.getMessage ex))))) 60 | 61 | (defn- escape [message] 62 | (str/replace message "[" "\\[")) 63 | 64 | 65 | (defn- notify [title-postfix message] 66 | (try 67 | (sh "terminal-notifier" "-message" (escape message) "-title" (str "AutoExpect - " (escape title-postfix))) 68 | (catch Exception ex 69 | (println "Problem communicating with notification center, please make sure you installed terminal-notifier (e.g. using 'brew install terminal-notifier'), exception:" (.getMessage ex))))) 70 | 71 | 72 | (defn- report [results] 73 | (let [{:keys [fail error test run-time]} results] 74 | (if (< 0 (+ fail error)) 75 | {:status "Failed" :message (format "Failed %s of %s tests." (+ fail error) test)} 76 | {:status "Passed" :message (format "Passed %s tests" test)}))) 77 | 78 | 79 | (defn- mark-tests-as-unrun [] 80 | (let [all (->> (all-ns) 81 | (mapcat (comp vals ns-interns))) 82 | previously-ran-tests (filter (comp :expectations/run meta) all)] 83 | (doseq [test previously-ran-tests] 84 | (alter-meta! test dissoc :expectations/run :status)))) 85 | 86 | (defn- run-tests [] 87 | (mark-tests-as-unrun) 88 | (let [result (refresh-environment)] 89 | (if (= :ok result) 90 | (report (expectations/run-all-tests)) 91 | {:status "Error" :message (str "Error refreshing environment: " (with-out-str 92 | (stacktrace/print-cause-trace 93 | (stacktrace/root-cause clojure.core/*e))))}))) 94 | 95 | (defn- something-changed? [x y] 96 | (not= x y)) 97 | 98 | (defn notify? [notify-type change-only last-status new-status] 99 | (cond 100 | (not notify-type) false 101 | (not change-only) true 102 | :else (not= last-status new-status))) 103 | 104 | (def ^:private last-status (atom nil)) 105 | 106 | (defn- monitor-keystrokes [] 107 | (let [keystroke-pressed? (atom false)] 108 | (future 109 | (loop [c (.read System/in)] 110 | (if (= c -1) 111 | (do 112 | (println "") 113 | (println "*****************************") 114 | (println "autoexpect's stdin stream has been closed, stopping monitoring for keystrokes to run tests") 115 | (println "*****************************")) 116 | (do 117 | (reset! keystroke-pressed? true) 118 | (recur (.read System/in)))))) 119 | (fn [] 120 | (let [old-val @keystroke-pressed?] 121 | (swap! keystroke-pressed? (constantly false)) 122 | old-val)))) 123 | 124 | (defn monitor-project [& {:keys [should-growl should-notify should-exit-on-pass change-only refresh-dirs]}] 125 | (apply clojure.tools.namespace.repl/set-refresh-dirs refresh-dirs) 126 | (turn-off-testing-at-shutdown) 127 | (let [keystroke-pressed? (monitor-keystrokes)] 128 | (loop [tracker (make-change-tracker)] 129 | (let [new-tracker (scan-for-changes tracker)] 130 | (try 131 | (when (or (keystroke-pressed?) 132 | (something-changed? new-tracker tracker)) 133 | (print-banner) 134 | (let [result (run-tests) 135 | new-status (:status result)] 136 | (when (notify? should-growl change-only @last-status new-status) 137 | (growl new-status (:message result))) 138 | 139 | (when (notify? should-notify change-only @last-status new-status) 140 | (notify new-status (:message result))) 141 | 142 | (when (= new-status "Error") 143 | (println (:message result))) 144 | 145 | (reset! last-status new-status) 146 | (print-end-message) 147 | (when (and should-exit-on-pass (= new-status "Passed")) 148 | (System/exit 0)))) 149 | (Thread/sleep 200) 150 | (catch Exception ex (.printStackTrace ex))) 151 | (recur new-tracker))))) 152 | -------------------------------------------------------------------------------- /autoexpect/src/leiningen/autoexpect.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.autoexpect 2 | (:require [leinjacker.deps :as deps] 3 | [leinjacker.eval :as eval])) 4 | 5 | (defn- add-deps [project] 6 | (let [dep-specification (first 7 | (filter (fn [[name version]] (= name 'lein-autoexpect/lein-autoexpect)) 8 | (:plugins project)))] 9 | (-> project 10 | (deps/add-if-missing dep-specification) 11 | (deps/add-if-missing '[org.clojure/tools.namespace "0.2.11"])))) 12 | 13 | (defn ^{:help-arglists '([])} autoexpect 14 | "Autoruns expecations on source change 15 | 16 | USAGE: lein autoexpect 17 | Runs expectations whenever there is a change to code in classpath. 18 | Reports test successes and failures to STDOUT. 19 | 20 | USAGE: lein autoexpect :growl 21 | Runs expectations whenever code changes. 22 | Reports results to growl and STDOUT." 23 | [project & args] 24 | (let [should-growl (some #{:growl ":growl" "growl"} args) 25 | should-notify (some #{:notify ":notify" "notify"} args) 26 | should-exit-on-pass (some #{:exit-on-pass ":exit-on-pass" "exit-on-pass"} args) 27 | change-only (some #{:change-only ":change-only" "change-only"} args) 28 | ;; TODO: this might need to also do :test-path and :source-path. I think that may have changed at some point in Leiningen history. 29 | refresh-dirs (vec 30 | (concat (:test-paths project) 31 | (:source-paths project)))] 32 | (eval/eval-in-project 33 | (add-deps project) 34 | `(autoexpect.runner/monitor-project 35 | :should-growl ~should-growl 36 | :should-notify ~should-notify 37 | :should-exit-on-pass ~should-exit-on-pass 38 | :change-only ~change-only 39 | :refresh-dirs ~refresh-dirs) 40 | `(require 'autoexpect.runner)))) 41 | -------------------------------------------------------------------------------- /sample-project/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | *.jar 7 | *.class 8 | .lein-deps-sum 9 | .lein-failures 10 | .lein-plugins 11 | -------------------------------------------------------------------------------- /sample-project/README.md: -------------------------------------------------------------------------------- 1 | # lein2 2 | 3 | Project for testing lein-autoexpect with leiningen 2 4 | 5 | ## Usage 6 | 7 | run `lein autoexpect` and `lein autoexpect :growl`. Comment and uncomment various lines in `test/sample_expectations.clj` to test out lein-autoexpect. 8 | 9 | DON'T FORGET TO COMMENT OUT (and remove comment after) the autoexpect in your user/global lein profile or that will be used in preference to the SNAPSHOT- installed from autoexpect 10 | 11 | ## License 12 | 13 | Copyright © 2012 Jake McCrary 14 | 15 | Distributed under the Eclipse Public License, the same as Clojure. 16 | -------------------------------------------------------------------------------- /sample-project/project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein2 "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.6.0"]] 7 | :plugins [[lein-autoexpect ~(nth (read-string (slurp "../autoexpect/project.clj")) 2)]] 8 | :profiles {:dev {:dependencies [[expectations "2.0.9"]]}}) 9 | -------------------------------------------------------------------------------- /sample-project/src/lein2/core.clj: -------------------------------------------------------------------------------- 1 | (ns lein2.core) 2 | 3 | (def add-1 inc) 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample-project/test/lein2/more_expectations.clj: -------------------------------------------------------------------------------- 1 | (ns lein2.more-expectations 2 | (:use expectations)) 3 | 4 | (expect 1 3) 5 | 6 | 7 | -------------------------------------------------------------------------------- /sample-project/test/lein2/sample_expectations.clj: -------------------------------------------------------------------------------- 1 | (ns lein2.sample-expectations 2 | (:use expectations 3 | lein2.core)) 4 | 5 | (expect 1 1) 6 | 7 | (expect 2 (add-1 1)) 8 | 9 | ;; uncomment to test failures 10 | ;(expect 3 4) 11 | 12 | 13 | 14 | 15 | 16 | ;; uncomment to test failures to reload code 17 | ;; (expect 3 (what)) 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------