├── src └── flare │ ├── main.cljs │ ├── atom.cljc │ ├── util.cljc │ ├── expectations.clj │ ├── set.cljc │ ├── cljs_test.cljs │ ├── midje.clj │ ├── report.cljc │ ├── sequential.cljc │ ├── map.cljc │ ├── clojure_test.clj │ ├── indexed.cljc │ ├── diff.cljc │ └── string.cljc ├── doc └── intro.md ├── .travis.yml ├── .gitignore ├── coveralls.sh ├── test └── flare │ ├── generators.clj │ ├── test_runner.cljs │ ├── atom_test.cljc │ ├── report_test.clj │ ├── diff_test.clj │ ├── set_test.cljc │ ├── indexed_test.cljc │ ├── sequential_test.cljc │ ├── map_test.cljc │ └── string_test.cljc ├── temp ├── scratch.cljc ├── midje-demo.clj └── demo.clj ├── CHANGELOG.md ├── README.md ├── project.clj └── LICENSE /src/flare/main.cljs: -------------------------------------------------------------------------------- 1 | (ns flare.main) 2 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to flare 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | jdk: 4 | - openjdk7 5 | - openjdk6 6 | - oraclejdk7 7 | after_script: 8 | - bash -ex coveralls.sh 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | dev/ 11 | /figwheel_server.log 12 | /resources/ 13 | out/ 14 | -------------------------------------------------------------------------------- /coveralls.sh: -------------------------------------------------------------------------------- 1 | COVERALLS_URL='https://coveralls.io/api/v1/jobs' 2 | CLOVERAGE_VERSION='1.0.4' lein cloverage -o target/coverage --coveralls 3 | curl -F 'json_file=@target/coverage/coveralls.json' "$COVERALLS_URL" 4 | -------------------------------------------------------------------------------- /src/flare/atom.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.atom) 2 | 3 | (defn report-atom-diff [{:keys [a b]} & rest] 4 | [(str "expected " (pr-str a) ", was " (pr-str b))]) 5 | 6 | (defn diff-atom 7 | [a b _] 8 | [{:type :atom 9 | :a a 10 | :b b}]) 11 | -------------------------------------------------------------------------------- /test/flare/generators.clj: -------------------------------------------------------------------------------- 1 | (ns flare.generators 2 | (:require [clojure.test.check.generators :as gen])) 3 | 4 | (defn distinct-values 5 | [& generators] 6 | (gen/such-that (comp (partial = (count generators)) count set) 7 | (apply gen/tuple generators))) 8 | -------------------------------------------------------------------------------- /src/flare/util.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.util) 2 | 3 | (defn pluralize [x noun] 4 | (let [n (if (number? x) x (count x))] 5 | (if (> n 1) 6 | (str noun "s") 7 | noun))) 8 | 9 | (defn flatten-when-single 10 | [coll] 11 | (if (= 1 (count coll)) 12 | (first coll) 13 | coll)) 14 | -------------------------------------------------------------------------------- /temp/scratch.cljc: -------------------------------------------------------------------------------- 1 | (ns scratch) 2 | 3 | (defn diff 4 | [a b] 5 | (if-let [diff (flare/diff a b)] 6 | (flare/generate-reports diff) 7 | [(type a) (type b)])) 8 | 9 | (defn diff-match-patch-string 10 | [a b] 11 | (let [dmp (js/diff_match_patch.) 12 | diff (.diff_main dmp a b)] 13 | diff)) 14 | 15 | -------------------------------------------------------------------------------- /src/flare/expectations.clj: -------------------------------------------------------------------------------- 1 | (ns flare.expectations 2 | (:require [clojure.string :as str] 3 | [expectations] 4 | [flare.diff :refer [diff]] 5 | [flare.report :refer [report]])) 6 | 7 | (defn install! [] 8 | (defmethod expectations/compare-expr :expectations/strings [e a str-e str-a] 9 | {:type :fail 10 | :raw [str-e str-a] 11 | :result (->> (diff e a) 12 | report 13 | (map #(str/replace % "\n" "\n ")))})) 14 | -------------------------------------------------------------------------------- /test/flare/test_runner.cljs: -------------------------------------------------------------------------------- 1 | (ns flare.test-runner 2 | (:require [doo.runner :refer-macros [doo-tests]] 3 | [flare.cljs-test] 4 | [flare.string-test] 5 | [flare.indexed-test] 6 | [flare.atom-test] 7 | [flare.map-test] 8 | [flare.sequential-test] 9 | [flare.set-test])) 10 | 11 | (doo-tests 12 | 'flare.string-test 13 | 'flare.indexed-test 14 | 'flare.atom-test 15 | 'flare.map-test 16 | 'flare.sequential-test 17 | 'flare.set-test) 18 | -------------------------------------------------------------------------------- /test/flare/atom_test.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.atom-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [flare.atom :refer [diff-atom report-atom-diff]])) 4 | 5 | (deftest diff-atom-test 6 | 7 | (is (= (diff-atom "foo" 1 nil) 8 | [{:type :atom 9 | :a "foo" 10 | :b 1}]))) 11 | 12 | (deftest report-atom-test 13 | 14 | (is (= (report-atom-diff {:type :atom 15 | :a "foo" 16 | :b 1}) 17 | ["expected \"foo\", was 1"]))) 18 | -------------------------------------------------------------------------------- /test/flare/report_test.clj: -------------------------------------------------------------------------------- 1 | (ns flare.report-test 2 | (:require [clojure.test.check :as tc] 3 | [clojure.test.check.clojure-test :refer [defspec]] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.properties :as prop] 6 | [clojure.test :refer [is deftest]] 7 | [flare.diff :refer [diff*]] 8 | [flare.generators :refer [distinct-values]] 9 | [flare.report :refer [report* report]])) 10 | 11 | (deftest reports-swallows-exceptions-test 12 | (with-redefs [report* (fn [_] (throw (ex-info "what ever" {})))] 13 | (is (nil? (report :whatever))))) 14 | 15 | #_(defspec generate-report-always-returns-non-empty-list-when-given-diffs 16 | 100 17 | (prop/for-all [[a b] (distinct-values gen/any gen/any)] 18 | (not-empty (report (diff* a b))))) 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.9 2 | 3 | Features: 4 | 5 | - Support for integration with [expectations](https://github.com/jaycfields/expectations). At the time being, `flare` Only overriding how string diffs get reported in `expectations` tests. 6 | 7 | ## 0.2.8 8 | 9 | Changes: 10 | 11 | - Longer equal parts of string diffs get elided. 12 | 13 | ## 0.2.7 14 | 15 | Changes: 16 | 17 | - Changed how string diffs are reported. No more ANSI colored diffs. 18 | 19 | ## 0.2.6 20 | 21 | Bugfixes: 22 | 23 | - Sequences of unequal sizes now generate a diff. 24 | 25 | ## 0.2.5 26 | 27 | Bugfixes: 28 | 29 | - Swallowing all exceptions (from `diff` and `generate-reports`) to insulating users from having exceptions from flare blow up in their faces. 30 | 31 | ## 0.2.4 32 | 33 | Features: 34 | 35 | - sorting diffs by natural ordering of 'key path' 36 | - dependency to `midje` is now declared as provided 37 | -------------------------------------------------------------------------------- /src/flare/set.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.set 2 | (:require [flare.util :refer [flatten-when-single]])) 3 | 4 | (defn report-set 5 | [{:keys [only-in values]} & rest] 6 | [(case only-in 7 | :a 8 | (str "expected set to contain: " 9 | (pr-str (flatten-when-single values)) 10 | ", but not found.") 11 | 12 | :b 13 | (str "set contained: " 14 | (pr-str (flatten-when-single values)) 15 | ", but not expected."))]) 16 | 17 | (defn diff-set 18 | [a b] 19 | (let [only-in-a (clojure.set/difference a b) 20 | only-in-b (clojure.set/difference b a)] 21 | (-> [] 22 | (cond-> (seq only-in-a) 23 | (conj {:type :set 24 | :only-in :a 25 | :values only-in-a})) 26 | (cond-> (seq only-in-b) 27 | (conj {:type :set 28 | :only-in :b 29 | :values only-in-b}))))) 30 | -------------------------------------------------------------------------------- /src/flare/cljs_test.cljs: -------------------------------------------------------------------------------- 1 | (ns flare.cljs-test 2 | (:require [flare.report :as report] 3 | [flare.diff :as diff] 4 | [cljs.test :as ct])) 5 | 6 | (defn render-diff [m] 7 | (let [[pred & values] (second (:actual m))] 8 | (try 9 | (when (and (= pred '=) (= 2 (count values))) 10 | (when-let [diff (apply diff/diff* (reverse values))] 11 | (println (str "\n" (clojure.string/join "\n" (report/report* diff)))))) 12 | (catch js/Error e 13 | (println "*** Oh noes! Flare threw an exception diffing the following values:") 14 | (println values) 15 | (println "*** Exception thrown is:" e))))) 16 | 17 | (defmethod cljs.test/report [:cljs.test/default :fail] [m] 18 | (ct/inc-report-counter! :fail) 19 | (println "\nFAIL in" (ct/testing-vars-str m)) 20 | (when (seq (:testing-contexts (ct/get-current-env))) 21 | (println (ct/testing-contexts-str))) 22 | (when-let [message (:message m)] (println message)) 23 | (ct/print-comparison m) 24 | (render-diff m)) 25 | -------------------------------------------------------------------------------- /src/flare/midje.clj: -------------------------------------------------------------------------------- 1 | (ns flare.midje 2 | (:require [flare.diff :refer [diff]] 3 | [flare.report :refer [report]] 4 | [midje.emission.plugins.default :as default] 5 | [midje.emission.plugins.util :as util] 6 | [midje.emission.state :as state])) 7 | 8 | (defn install! [] 9 | 10 | (defn emit-reports 11 | [reports] 12 | (when (seq reports) 13 | (util/emit-one-line "") 14 | (doseq [report reports] 15 | (util/emit-one-line report)))) 16 | 17 | (def default-fail-emissioner (:fail default/emission-map)) 18 | 19 | (defn flare-fail-emissioner 20 | [failure] 21 | (default-fail-emissioner failure) 22 | (when (= (:type failure) :actual-result-did-not-match-expected-value) 23 | (some-> (diff (:expected-result failure) (:actual failure)) 24 | report 25 | emit-reports))) 26 | 27 | (def emission-map (assoc default/emission-map 28 | :fail flare-fail-emissioner)) 29 | 30 | (state/install-emission-map-wildly emission-map)) 31 | -------------------------------------------------------------------------------- /test/flare/diff_test.clj: -------------------------------------------------------------------------------- 1 | (ns flare.diff-test 2 | (:require [flare.diff :as sut] 3 | [flare.generators :refer [distinct-values]] 4 | [clojure.test.check :as tc] 5 | [clojure.test.check.clojure-test :refer [defspec]] 6 | [clojure.test.check.generators :as gen] 7 | [clojure.test.check.properties :as prop] 8 | [clojure.test :refer [is deftest]] 9 | [flare.report :as report])) 10 | 11 | (deftest diff-swallows-exceptions-test 12 | (with-redefs [sut/diff* (fn [_ _] (throw (ex-info "what ever" {})))] 13 | (is (nil? (sut/diff :what :ever))))) 14 | 15 | (defspec diff-never-returns-a-diff-when-inputs-are-equal 16 | 100 17 | (prop/for-all [v gen/any] 18 | (= (sut/diff* v v) nil))) 19 | 20 | ;; TODO - Implement generator that generates similiar but unequal values. This test is probably futile. 21 | (defspec diff-always-returns-a-diff-when-inputs-are-not-equal 22 | 100 23 | (prop/for-all [[a b] (distinct-values gen/any gen/any)] 24 | (not (nil? (sut/diff* a b))))) 25 | -------------------------------------------------------------------------------- /src/flare/report.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.report 2 | (:require [flare.atom :as atom] 3 | [flare.string :as string] 4 | [flare.indexed :as indexed] 5 | [flare.map :as map] 6 | [flare.sequential :as sequential] 7 | [flare.set :as set])) 8 | 9 | (def reporters 10 | {:sequential-size sequential/report-size-diff 11 | :keys map/report-keys-diff 12 | :set set/report-set 13 | :string string/report-string-diff 14 | :atom atom/report-atom-diff 15 | :indexed indexed/report-indexed-diff}) 16 | 17 | (declare report*) 18 | 19 | (defn report-diff [diff] 20 | (let [reporter (-> diff :type reporters)] 21 | (reporter diff report*))) 22 | 23 | (defn report* [diff] 24 | (mapcat report-diff (if (sequential? diff) 25 | diff 26 | (vector diff)))) 27 | 28 | (defn report 29 | [diff] 30 | (try 31 | (report* diff) 32 | (catch #?(:clj Exception :cljs js/Error) e 33 | ;; (println "report threw exception" e) 34 | ))) 35 | -------------------------------------------------------------------------------- /test/flare/set_test.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.set-test 2 | (:require [clojure.test :refer [deftest is]] 3 | #?(:clj flare.clojure-test) 4 | [flare.set :refer [diff-set report-set]])) 5 | 6 | (deftest diff-set-test 7 | 8 | (is (= (diff-set #{1 2 3} #{1 2}) 9 | [{:type :set 10 | :only-in :a 11 | :values #{3}}])) 12 | 13 | (is (= (diff-set #{1 2} #{1 2 3}) 14 | [{:type :set 15 | :only-in :b 16 | :values #{3}}])) 17 | 18 | (is (= (diff-set #{1 2 3} #{2 3 4}) 19 | [{:type :set 20 | :only-in :a 21 | :values #{1}} 22 | {:type :set 23 | :only-in :b 24 | :values #{4}}]))) 25 | 26 | (deftest report-set-test 27 | 28 | (is (= (report-set {:type :set 29 | :only-in :a 30 | :values [:x]}) 31 | ["expected set to contain: :x, but not found."])) 32 | 33 | (is (= (report-set {:type :set 34 | :only-in :b 35 | :values [:x]}) 36 | ["set contained: :x, but not expected."]))) 37 | -------------------------------------------------------------------------------- /src/flare/sequential.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.sequential 2 | (:require [flare.indexed :as indexed] 3 | [flare.util :refer [pluralize]])) 4 | 5 | (defn report-size-diff 6 | [{:keys [excess-idx only-in-a only-in-b]} & rest] 7 | (let [missing-count (count (or (seq only-in-a) (seq only-in-b)))] 8 | [(str "expected length of sequence is " (+ (count only-in-a) excess-idx) 9 | ", actual length is " (+ (count only-in-b) excess-idx) ".") 10 | (if (seq only-in-a) 11 | (str "actual is missing " missing-count " " (pluralize only-in-a "element") ": " (pr-str only-in-a)) 12 | (str "actual has " missing-count " " (pluralize only-in-b "element") " in excess: " (pr-str only-in-b)))])) 13 | 14 | (defn diff-size 15 | [a b _] 16 | (when-not (= (count a) (count b)) 17 | (let [excess-idx (min (count a) (count b))] 18 | [{:type :sequential-size 19 | :excess-idx excess-idx 20 | :only-in-a (drop excess-idx a) 21 | :only-in-b (drop excess-idx b)}]))) 22 | 23 | (defn diff-by-index 24 | [a b diff-fn] 25 | [(indexed/indexed-diff 26 | (->> (map vector a b) 27 | (keep-indexed (fn [i [a b]] (when-not (= a b) [i (diff-fn a b)]))) 28 | (into {})))]) 29 | -------------------------------------------------------------------------------- /src/flare/map.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.map 2 | (:require [clojure.set :as set] 3 | [flare.indexed :as indexed] 4 | [flare.util :refer [pluralize flatten-when-single]])) 5 | 6 | (defn report-keys-diff 7 | [{:keys [only-in keys]} & rest] 8 | (case only-in 9 | :a [(str "expected map to contain " (pluralize keys "key") ": " 10 | (pr-str (flatten-when-single keys)) 11 | ", but not found.")] 12 | :b [(str "map contained " (pluralize keys "key") ": " 13 | (pr-str (flatten-when-single keys)) 14 | ", but not expected.")])) 15 | 16 | (defn diff-keys 17 | [a b _] 18 | (let [a-keys (set (keys a)) 19 | b-keys (set (keys b))] 20 | (when (not= a-keys b-keys) 21 | (let [only-in-a (set/difference a-keys b-keys) 22 | only-in-b (set/difference b-keys a-keys)] 23 | (-> [] 24 | (cond-> (seq only-in-a) 25 | (conj {:type :keys :only-in :a :keys only-in-a})) 26 | (cond-> (seq only-in-b) 27 | (conj {:type :keys :only-in :b :keys only-in-b}))))))) 28 | 29 | (defn diff-values 30 | [a b diff-fn] 31 | [(indexed/indexed-diff 32 | (->> (set/intersection (set (keys a)) (set (keys b))) 33 | (map (fn [k] [k (diff-fn (get a k) (get b k))])) 34 | (filter second) 35 | (into {})))]) 36 | -------------------------------------------------------------------------------- /temp/midje-demo.clj: -------------------------------------------------------------------------------- 1 | (ns midje-demo 2 | (:require [flare.midje :refer [install!]] 3 | [midje.sweet :refer :all])) 4 | 5 | (install!) 6 | 7 | 8 | 9 | 10 | 11 | 12 | ;; Numbers 13 | 14 | (fact 1 => 2) 15 | 16 | (fact 1 => "1") 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ;; Strings 28 | 29 | (fact "kodemaker for the win" 30 | => "kdoemaker for teh win") 31 | 32 | 33 | 34 | 35 | (fact (inc 32) => even?) 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ;; Vector and lists 45 | 46 | (fact [10 20 30] 47 | => [10 25 30]) 48 | 49 | (fact [10 20 :a] 50 | => [10 25 :b]) 51 | 52 | (fact ["foo" "bar" "baz" "kodemaker for the win" "wat"] 53 | => ["foo" "bar" "bat" "kdoemaker for teh win" "wat"]) 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ;; Maps 68 | 69 | (fact {:a 1 :b 2} 70 | => {:a 1 :b 3}) 71 | 72 | (fact {:a {:b "kodemaker"} 73 | :b {:c 1} 74 | :c {:x 2}} 75 | => {:a {:b "kdoemaker"} 76 | :b {:c 2} 77 | :c {:y 3 78 | :z 4}}) 79 | 80 | 81 | (def a {:a 1}) 82 | 83 | (fact a => {:b 2}) 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ;; Sets 95 | 96 | (fact #{1 2 3} => #{3 4 5}) 97 | 98 | (fact {:a {:b #{1 "foo" 2}}} => {:a {:b #{2 "bar" 3}}}) 99 | 100 | 101 | (fact '() => [1]) 102 | 103 | (fact [] => '(1)) 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flare [![Build Status](https://travis-ci.org/andersfurseth/flare.svg?branch=master)](https://travis-ci.org/andersfurseth/flare) [![Coverage Status](https://img.shields.io/coveralls/andersfurseth/flare.svg)](https://coveralls.io/r/andersfurseth/flare) 2 | 3 | Flare brings enlightened failure reports to your [clojure.test](http://richhickey.github.io/clojure/clojure.test-api.html), [Midje](https://github.com/marick/Midje) and [expectations](https://github.com/jaycfields/expectations) tests. 4 | 5 | Latest version is `[flare "0.2.9"]`. 6 | 7 | ## Usage 8 | 9 | Flare is activated by calling `flare.clojure-test/install!` (for use with `clojure.test`), `flare.midje/install!` (for use with `Midje`) or `flare.expectations/install!` (for use with `expectations`). 10 | 11 | To use Flare with [Leiningen](http://leiningen.org/), merge the following with your project.clj: 12 | 13 | ```clojure 14 | {:profiles 15 | {:dev 16 | {:injections 17 | [(require 'flare.clojure-test) 18 | (flare.clojure-test/install!)]}}} 19 | ``` 20 | 21 | Run your tests, using your favourite test runner, and enjoy the enlightenment Flare brings to your failing tests. 22 | 23 | ## Using with ClojureScript 24 | 25 | For ClojureScript, just require `flare.cljs-test` somewhere in your test setup, and voila! Enlightened tests are yours to behold. 26 | 27 | ## License 28 | 29 | Copyright © 2015 Anders Furseth 30 | 31 | Distributed under the Eclipse Public License either version 1.0 or (at 32 | your option) any later version. 33 | -------------------------------------------------------------------------------- /temp/demo.clj: -------------------------------------------------------------------------------- 1 | (ns flare.demo 2 | (:require [flare.clojure-test :refer [install!]] 3 | [clojure.test :refer [is]] 4 | [clojure.pprint :as pp] 5 | ; [clj-diff.core :as clj-diff] 6 | )) 7 | 8 | (install!) 9 | 10 | 11 | ;; Numbers 12 | 13 | (is (= 1 2)) 14 | 15 | (is (= 1 "1")) 16 | 17 | 18 | ;; Strings 19 | 20 | (is (= "kodemaker for the win" 21 | "kdoemaker for teh win")) 22 | 23 | 24 | ;; Vector and lists 25 | 26 | (is (= '() [1])) 27 | 28 | (is (= [] '(1))) 29 | 30 | (is (= [10 20 30] 31 | [10 25 30])) 32 | 33 | (is (= [10 20 :a] 34 | [10 25 :b])) 35 | 36 | (is (= ["foo" "bar" "baz" "kodemaker for the win" "wat"] 37 | ["foo" "bar" "bat" "kdoemaker for teh win" "wat"])) 38 | 39 | (is (= [[1 2 3 4 5 6 7]] 40 | [[2 3 4 5 6 7 8]])) 41 | 42 | 43 | ;; Maps 44 | 45 | (is (= {:a 1 :b 2} 46 | {:a 1 :b 3})) 47 | 48 | (is (= {:a {:b "kodemaker"} 49 | :b {:c 1} 50 | :c {:x 2}} 51 | {:a {:b "kdoemaker"} 52 | :b {:c 2} 53 | :c {:y 3 54 | :z 4}})) 55 | 56 | 57 | ;; Sets 58 | 59 | (is (= #{1 2 3} #{3 4 5})) 60 | 61 | (is (= {:a {:b #{1 "foo" 2}}} {:a {:b #{2 "bar" 3}}})) 62 | 63 | 64 | (comment 65 | ;; clj-diff 66 | 67 | (clj-diff/diff [:a :b :c] [:c :b :a]) 68 | ;;=> {:+ [[-1 :c :b]], :- [1 2]} 69 | 70 | (clj-diff/diff [:a :b :c] [:a :b]) 71 | ;;=> {:+ [], :- [2]} 72 | 73 | (clj-diff/diff [:a :b] [:a :b :c :d]) 74 | ;;=> {:+ [[1 :c :d]], :- []} 75 | 76 | (clj-diff/diff [:b :c] [:a :b :c :d])) 77 | ;;=> {:+ [[-1 :a] [1 :d]], :- []} 78 | -------------------------------------------------------------------------------- /src/flare/clojure_test.clj: -------------------------------------------------------------------------------- 1 | (ns flare.clojure-test 2 | (:require [clojure.test :as ct] 3 | [flare.diff :as diff] 4 | [flare.report :as report])) 5 | 6 | (defn report 7 | [diff] 8 | (println (str "\n" (clojure.string/join "\n" (report/report diff))))) 9 | 10 | (defn diff 11 | [args opts] 12 | (when (= 2 (count args)) 13 | (let [args (if (= (:expected opts) :first) 14 | args 15 | (reverse args))] 16 | (apply diff/diff args)))) 17 | 18 | (defn install! 19 | ([] 20 | (install! {})) 21 | ([opts] 22 | (defmethod ct/report :fail [m] 23 | (ct/with-test-out 24 | (ct/inc-report-counter :fail) 25 | (println "\nFAIL in" (ct/testing-vars-str m)) 26 | (when (seq ct/*testing-contexts*) (println (ct/testing-contexts-str))) 27 | (when-let [message (:message m)] (println message)) 28 | (println "expected:" (pr-str (:expected m))) 29 | (println " actual:" (pr-str (:actual m))) 30 | (when-let [diff (::difference m)] 31 | (report diff)))) 32 | 33 | (defmethod ct/assert-expr '= 34 | [msg form] 35 | (let [args (rest form)] 36 | `(let [args# (list ~@args) 37 | result# (apply = args#)] 38 | (if result# 39 | (ct/do-report {:type :pass, :message ~msg, 40 | :expected '~form, :actual (cons '= args#)}) 41 | (ct/do-report {:type :fail, :message ~msg, 42 | :expected '~form, :actual (list '~'not (cons '~'=== args#)) 43 | ::difference (diff args# ~opts)})) 44 | result#))))) 45 | -------------------------------------------------------------------------------- /src/flare/indexed.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.indexed 2 | (:require [clojure.string :as str])) 3 | 4 | (defn indexed-diff? [m] 5 | (and (map? m) 6 | (= (:type m) :indexed))) 7 | 8 | (defn flatten-indexed-diffs 9 | ([diff-or-coll] 10 | (flatten-indexed-diffs {} [] diff-or-coll)) 11 | ([a ks diff-or-coll] 12 | (cond 13 | (indexed-diff? diff-or-coll) 14 | (reduce into 15 | {} 16 | (map (fn [[k v]] (flatten-indexed-diffs a (conj ks k) v)) 17 | (:diff diff-or-coll))) 18 | 19 | (sequential? diff-or-coll) 20 | (let [groups (group-by indexed-diff? diff-or-coll) 21 | indexed-diffs (first (groups true)) 22 | other-diffs (groups false)] 23 | (-> a 24 | (cond-> indexed-diffs (flatten-indexed-diffs ks indexed-diffs)) 25 | (cond-> (seq other-diffs) (assoc ks other-diffs)))) 26 | 27 | :else (throw (ex-info "diff-or-coll must be vector or map" diff-or-coll))))) 28 | 29 | (defn join-with-newlines [coll] 30 | (str/join "\n" coll)) 31 | 32 | (defn report-indexed-diff-entry [[path diffs] report] 33 | (let [reports (flatten (map report diffs)) 34 | indent-reports? (and (seq path) (< 1 (count reports)))] 35 | (-> reports 36 | (cond->> indent-reports? (map #(str " " %))) 37 | join-with-newlines 38 | (cond->> (seq path) (str "in " (pr-str path) (if indent-reports? "\n" " ")))))) 39 | 40 | (defn report-indexed-diff [diff report] 41 | (->> diff 42 | flatten-indexed-diffs 43 | sort 44 | (map #(report-indexed-diff-entry % report)))) 45 | 46 | (defn indexed-diff [diff] 47 | {:type :indexed 48 | :diff diff}) 49 | -------------------------------------------------------------------------------- /test/flare/indexed_test.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.indexed-test 2 | (:require [clojure.test :refer [is deftest testing]] 3 | [flare.indexed :refer [flatten-indexed-diffs report-indexed-diff]])) 4 | 5 | (deftest flatten-indexed-diffs-test 6 | 7 | (is (= (flatten-indexed-diffs 8 | [:x]) 9 | {[] [:x]})) 10 | 11 | (is (= (flatten-indexed-diffs 12 | [:x :y]) 13 | {[] [:x :y]})) 14 | 15 | (is (= (flatten-indexed-diffs 16 | [{:type :indexed 17 | :diff {:a [:x :y]}}]) 18 | {[:a] [:x :y]})) 19 | 20 | (is (= (flatten-indexed-diffs 21 | [{:type :indexed 22 | :diff {:a [:x :y] 23 | :b [:z]}}]) 24 | {[:a] [:x :y] 25 | [:b] [:z]})) 26 | 27 | (is (= (flatten-indexed-diffs 28 | [:x 29 | {:type :indexed 30 | :diff {:a [:x :y] 31 | :b [:z]}}]) 32 | {[] [:x] 33 | [:a] [:x :y] 34 | [:b] [:z]})) 35 | 36 | (is (= (flatten-indexed-diffs 37 | [:x 38 | {:type :indexed 39 | :diff {:a [:foo 40 | {:type :indexed 41 | :diff {:x [:bar :y :baz]}}] 42 | :b [:z]}}]) 43 | {[] [:x] 44 | [:a] [:foo] 45 | [:a :x] [:bar :y :baz] 46 | [:b] [:z]})) 47 | 48 | #_(is (thrown? IllegalArgumentException 49 | (flatten-indexed-diffs 50 | 1)))) 51 | 52 | (deftest report-indexed-diff-test 53 | 54 | (is (= (report-indexed-diff {:type :indexed 55 | :diff {:a [:foo 56 | {:type :indexed 57 | :diff {:x [:bar :y :baz]}}] 58 | :b [:z]}} identity) 59 | ["in [:a] :foo" 60 | "in [:b] :z" 61 | "in [:a :x]\n :bar\n :y\n :baz"]))) 62 | -------------------------------------------------------------------------------- /src/flare/diff.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.diff 2 | (:require [clojure.data :refer [equality-partition]] 3 | [flare.atom :as atom] 4 | [flare.string :as string] 5 | [flare.map :as map] 6 | [flare.sequential :as sequential] 7 | [flare.set :as set])) 8 | 9 | (defn compose-diffs 10 | [& diff-fns] 11 | (fn [a b diff-recursive-fn] 12 | (->> diff-fns 13 | (mapcat #(% a b diff-recursive-fn)) 14 | (remove empty?)))) 15 | 16 | #?(:clj (defn resolve-diff-type [x] 17 | (condp instance? x 18 | java.util.List :sequential 19 | java.lang.String :string 20 | java.util.Set :set 21 | java.util.Map :map 22 | :atom)) 23 | :cljs (defn resolve-diff-type [x] 24 | (cond 25 | (array? x) :sequential 26 | (string? x) :string 27 | (map? x) :map 28 | (set? x) :set 29 | (implements? ISeqable x) :sequential 30 | :else :atom))) 31 | 32 | (def differs 33 | {:sequential (compose-diffs sequential/diff-size sequential/diff-by-index) 34 | :map (compose-diffs map/diff-values map/diff-keys) 35 | :set set/diff-set 36 | :string string/diff-string 37 | :atom atom/diff-atom}) 38 | 39 | (declare diff*) 40 | 41 | (defn diff-similar [a b] 42 | (let [diff-type (resolve-diff-type a) 43 | diff (get differs diff-type)] 44 | (diff a b diff*))) 45 | 46 | (defn similar-types [a b] 47 | (when (= (equality-partition a) (equality-partition b)) 48 | (if (string? a) 49 | (string? b) 50 | true))) 51 | 52 | (defn diff* 53 | [a b] 54 | (when-not (= a b) 55 | (if (similar-types a b) 56 | (diff-similar a b) 57 | (atom/diff-atom a b nil)))) 58 | 59 | (defn diff 60 | [a b] 61 | (try 62 | (diff* a b) 63 | (catch #?(:clj Exception :cljs js/Error) e 64 | ;; (println "diff threw exception" e) 65 | ))) 66 | -------------------------------------------------------------------------------- /test/flare/sequential_test.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.sequential-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [flare.sequential :refer [diff-by-index diff-size report-size-diff]])) 4 | 5 | (def diff-recursive (constantly [:recursive-diff])) 6 | 7 | (deftest diff-size-test 8 | 9 | (is (= (diff-size [0 1 2] [0 1 2] diff-recursive) 10 | nil)) 11 | 12 | (is (= (diff-size [0 1 2] [2 1 0] diff-recursive) 13 | nil)) 14 | 15 | (is (= (diff-size [:a :b] [:a :b :c :d] diff-recursive) 16 | [{:type :sequential-size 17 | :excess-idx 2 18 | :only-in-a [] 19 | :only-in-b [:c :d]}])) 20 | 21 | (is (= (diff-size [:a :b :c :d] [:a :b] diff-recursive) 22 | [{:type :sequential-size 23 | :excess-idx 2 24 | :only-in-a [:c :d] 25 | :only-in-b []}]))) 26 | 27 | (deftest diff-by-index-test 28 | 29 | (is (= (diff-by-index [0 1 2] [2 1 0] diff-recursive) 30 | [{:type :indexed 31 | :diff {0 [:recursive-diff] 32 | 2 [:recursive-diff]}}])) 33 | 34 | (is (= (diff-by-index [0 1 2] [0 1 42] diff-recursive) 35 | [{:type :indexed 36 | :diff {2 [:recursive-diff]}}])) 37 | 38 | (is (= (diff-by-index [:x :b :c :d] [:a :b] diff-recursive) 39 | [{:type :indexed 40 | :diff {0 [:recursive-diff]}}]))) 41 | 42 | (deftest report-sequential-test 43 | 44 | (is (= (report-size-diff 45 | {:type :sequential-size 46 | :excess-idx 3 47 | :only-in-a [:a :b] 48 | :only-in-b []}) 49 | ["expected length of sequence is 5, actual length is 3." 50 | "actual is missing 2 elements: [:a :b]"])) 51 | 52 | (is (= (report-size-diff 53 | {:type :sequential-size 54 | :excess-idx 3 55 | :only-in-a [] 56 | :only-in-b [:a :b]}) 57 | ["expected length of sequence is 3, actual length is 5." 58 | "actual has 2 elements in excess: [:a :b]"]))) 59 | -------------------------------------------------------------------------------- /test/flare/map_test.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.map-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [flare.map :refer [diff-keys diff-values report-keys-diff]])) 4 | 5 | (def diff-recursive (constantly [:recursive-diff])) 6 | 7 | (deftest diff-keys-test 8 | 9 | (is (= (diff-keys {:x 1 :y 2} {:x 1 :y 3} nil) 10 | nil)) 11 | 12 | (is (= (diff-keys {:x 1 :y 2} {:x 1} nil) 13 | [{:type :keys :only-in :a :keys #{:y}}])) 14 | 15 | (is (= (diff-keys {:x 1} {:x 1 :z 3} nil) 16 | [{:type :keys :only-in :b :keys #{:z}}])) 17 | 18 | (is (= (diff-keys {:x 1 :y 2} {:x 1 :z 3} nil) 19 | [{:type :keys :only-in :a :keys #{:y}} 20 | {:type :keys :only-in :b :keys #{:z}}]))) 21 | 22 | (deftest diff-values-test 23 | 24 | (is (= (diff-values {:x 1 :y 2} {:x 1 :y 3 :z 4} diff-recursive) 25 | [{:type :indexed 26 | :diff {:x [:recursive-diff] 27 | :y [:recursive-diff]}}])) 28 | 29 | (is (= (diff-values {:x 1 :y 2 :z 4} {:x 1 :y 3} diff-recursive) 30 | [{:type :indexed 31 | :diff {:x [:recursive-diff] 32 | :y [:recursive-diff]}}]))) 33 | 34 | (deftest report-map-test 35 | 36 | (is (= (report-keys-diff {:type :keys 37 | :only-in :a 38 | :keys #{:x}}) 39 | ["expected map to contain key: :x, but not found."])) 40 | 41 | (is (= (report-keys-diff {:type :keys 42 | :only-in :a 43 | :keys #{:x :y}}) 44 | ["expected map to contain keys: #{:y :x}, but not found."])) 45 | 46 | (is (= (report-keys-diff {:type :keys 47 | :only-in :b 48 | :keys #{:x}}) 49 | ["map contained key: :x, but not expected."])) 50 | 51 | (is (= (report-keys-diff {:type :keys 52 | :only-in :b 53 | :keys #{:x :y}}) 54 | ["map contained keys: #{:y :x}, but not expected."]))) 55 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject flare "0.3.0-SNAPSHOT" 2 | :description "Enlightened failure reports for clojure.test and Midje" 3 | :url "http://github.com/andersfurseth/flare" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :min-lein-version "2.6.1" 8 | 9 | :dependencies [[org.clojure/clojure "1.9.0-alpha12"] 10 | [org.clojure/clojurescript "1.9.229"] 11 | [org.clojars.brenton/google-diff-match-patch "0.1"] 12 | [cljsjs/google-diff-match-patch "20121119-1"]] 13 | 14 | :plugins [[lein-figwheel "0.5.4-7"] 15 | [lein-cljsbuild "1.1.3" :exclusions [[org.clojure/clojure]]] 16 | [lein-cloverage "1.0.2"] 17 | [lein-doo "0.1.6"]] 18 | 19 | :source-paths ["src"] 20 | 21 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "out"] 22 | 23 | :profiles {:provided {:dependencies [[midje "1.6.3"] 24 | [expectations "2.1.1"]]} 25 | :dev {:dependencies [[org.clojure/test.check "0.9.0"] 26 | [binaryage/devtools "0.7.2"] 27 | [figwheel-sidecar "0.5.4-7"] 28 | [com.cemerick/piggieback "0.2.1"]] 29 | :source-paths ["src" "dev"] 30 | :repl-options {:init (set! *print-length* 50) 31 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 32 | :injections [(require 'flare.clojure-test) 33 | (flare.clojure-test/install!)]}} 34 | 35 | :cljsbuild {:builds 36 | [{:id "test" 37 | :source-paths ["src" "test"] 38 | :compiler {:output-to "resources/public/js/compiled/flare-test.js" 39 | :main flare.test-runner 40 | :optimizations :none}}]} 41 | 42 | :figwheel {:css-dirs ["resources/public/css"]} 43 | 44 | :lein-release {:deploy-via :clojars}) 45 | -------------------------------------------------------------------------------- /test/flare/string_test.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.string-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [flare.string :refer [diff-string report-string-diff]])) 4 | 5 | (def diff-recursive (constantly [:recursive-diff])) 6 | 7 | (deftest string-diff-test 8 | 9 | (is (= (diff-string "foo" "fjoo" diff-recursive) 10 | [{:type :string 11 | :a->b [[:equal "f" :first] 12 | [:insert "j"] 13 | [:equal "oo" :last]] 14 | :b->a [[:equal "f" :first] 15 | [:delete "j"] 16 | [:equal "oo" :last]] 17 | :differences-count 1 18 | :similarity #?(:cljs 0.75 :clj 3/4)}]))) 19 | 20 | 21 | (deftest report-string-diff-test 22 | 23 | (testing "base case" 24 | (is (= (report-string-diff {:type :string 25 | :a->b [[:equal "f" :first] 26 | [:insert "j"] 27 | [:equal "oo" :last]] 28 | :b->a [[:equal "f" :first] 29 | [:delete "j"] 30 | [:equal "oo" :last]] 31 | :differences-count 1 32 | :similarity #?(:cljs 0.75 :clj 3/4)}) 33 | ["strings have 1 difference (75% similarity)" 34 | "expected: \"f(-)oo\"" 35 | "actual: \"f(j)oo\""]))) 36 | 37 | (testing "equal parts of longer strings get elided" 38 | (is (= (report-string-diff {:type :string 39 | :a->b [[:equal "aaaaaaaaaaaaa " :first] 40 | [:change [[:insert "b"] 41 | [:delete "f"]]] 42 | [:equal "oo aaaaaaaaaaaaaa "] 43 | [:change [[:insert "f"] 44 | [:delete "b"]]] 45 | [:equal "ar aaaaaaaaaaaaa" :last]] 46 | :b->a [[:equal "aaaaaaaaaaaaa " :first] 47 | [:change [[:insert "f"] 48 | [:delete "b"]]] 49 | [:equal "oo aaaaaaaaaaaaaa "] 50 | [:change [[:insert "b"] 51 | [:delete "f"]]] 52 | [:equal "ar aaaaaaaaaaaaa" :last]] 53 | :differences-count 2 54 | :similarity #?(:cljs 0.96 :clj 24/25)}) 55 | ["strings have 2 differences (96% similarity)" 56 | "expected: \"...aaaaa (f)oo aaa...aaaaa (b)ar aaa...\"" 57 | "actual: \"...aaaaa (b)oo aaa...aaaaa (f)ar aaa...\""])))) 58 | -------------------------------------------------------------------------------- /src/flare/string.cljc: -------------------------------------------------------------------------------- 1 | (ns flare.string 2 | (:require [flare.util :refer [pluralize]] 3 | #?(:cljs cljsjs.google-diff-match-patch)) 4 | #?(:clj (:import [name.fraser.neil.plaintext diff_match_patch] 5 | [name.fraser.neil.plaintext diff_match_patch$Operation]))) 6 | 7 | (def operation->keyword 8 | #?(:clj {diff_match_patch$Operation/DELETE :delete 9 | diff_match_patch$Operation/INSERT :insert 10 | diff_match_patch$Operation/EQUAL :equal} 11 | :cljs {-1 :delete 12 | 0 :equal 13 | 1 :insert})) 14 | 15 | #?(:clj 16 | (defn diff->tuple 17 | [diff] 18 | [(operation->keyword (.operation diff)) (.text diff)])) 19 | 20 | (defn create-dmp [] 21 | #?(:clj (diff_match_patch.) 22 | :cljs (js/diff_match_patch.))) 23 | 24 | (defn dmp-diff->tuples [diff] 25 | #?(:clj (map diff->tuple diff) 26 | :cljs (->> diff 27 | js->clj 28 | (map #(update % 0 operation->keyword))))) 29 | 30 | (defn diff-match-patch-string [a b] 31 | (let [dmp (create-dmp) 32 | diff (.diff_main dmp a b)] 33 | (.diff_cleanupSemantic dmp diff) 34 | diff)) 35 | 36 | (defn insert-operation? 37 | [[operation _]] 38 | (= operation :insert)) 39 | 40 | (defn delete-operation? 41 | [[operation _]] 42 | (= operation :delete)) 43 | 44 | (defn equal-operation? 45 | [[operation _]] 46 | (= operation :equal)) 47 | 48 | (defn change-operation? 49 | [diff] 50 | (or (insert-operation? diff) (delete-operation? diff))) 51 | 52 | (defn change-operations? 53 | [prev curr] 54 | (and (change-operation? prev) (change-operation? curr))) 55 | 56 | (defn find-first 57 | [pred coll] 58 | (first (filter pred coll))) 59 | 60 | (defn consolidate-tuples 61 | [diff-tuples] 62 | (if (= 1 (count diff-tuples)) 63 | (first diff-tuples) 64 | [:change [(find-first insert-operation? diff-tuples) 65 | (find-first delete-operation? diff-tuples)]])) 66 | 67 | (defn partition-between 68 | [pred? coll] 69 | (->> (map pred? coll (rest coll)) 70 | (reductions not= true) 71 | (map list coll) 72 | (partition-by second) 73 | (map (partial map first)))) 74 | 75 | (defn consolidate 76 | [diff-tuples] 77 | (->> diff-tuples 78 | (partition-between (complement change-operations?)) 79 | (mapv consolidate-tuples))) 80 | 81 | (defn add-context 82 | [diff-tuples] 83 | (-> diff-tuples 84 | (update-in [0] conj :first) 85 | (update-in [(dec (count diff-tuples))] conj :last))) 86 | 87 | (defn diff-tuples 88 | [a b] 89 | (->> (diff-match-patch-string a b) 90 | dmp-diff->tuples 91 | consolidate 92 | add-context)) 93 | 94 | (defn count-differences 95 | [diff-tuples] 96 | (->> diff-tuples 97 | (remove equal-operation?) 98 | count)) 99 | 100 | (defn levenshtein-distance 101 | [diff] 102 | (.diff_levenshtein (create-dmp) diff)) 103 | 104 | (defn string-similarity 105 | [a b] 106 | (let [longest (max (count a) (count b)) 107 | distance (levenshtein-distance (diff-match-patch-string a b))] 108 | (/ (- longest distance) 109 | longest))) 110 | 111 | (defn create-string-diff 112 | [a b] 113 | (let [a->b (diff-tuples a b) 114 | b->a (diff-tuples b a)] 115 | {:type :string 116 | :a b 117 | :b b 118 | :a->b a->b 119 | :b->a b->a 120 | :differences-count (count-differences a->b) 121 | :similarity (string-similarity a b)})) 122 | 123 | (def render-tuple-dispatch first) 124 | 125 | (defmulti render-tuple #'render-tuple-dispatch) 126 | 127 | (defn enclose-in-parenthesis 128 | [s] 129 | (str "(" s ")")) 130 | 131 | (defn dashes 132 | [n] 133 | (apply str (repeat n "-"))) 134 | 135 | (defn string->dashes 136 | [s] 137 | (-> s count dashes)) 138 | 139 | (defmethod render-tuple :insert 140 | [[_ s]] 141 | (-> s 142 | string->dashes 143 | enclose-in-parenthesis)) 144 | 145 | (defmethod render-tuple :delete 146 | [[_ s]] 147 | (enclose-in-parenthesis s)) 148 | 149 | (defn append-dashes 150 | [s max] 151 | (->> s 152 | count 153 | (- max) 154 | dashes 155 | (str s))) 156 | 157 | (defmethod render-tuple :change 158 | [[_ [[_ insert] [_ delete]]]] 159 | (-> delete 160 | (append-dashes (count insert)) 161 | enclose-in-parenthesis)) 162 | 163 | (defn append-str 164 | [s n to-append] 165 | (str (apply str (take n to-append)) s)) 166 | 167 | (defn prepend-str 168 | [s n to-prepend] 169 | (str s (apply str (take-last n to-prepend)))) 170 | 171 | (defmethod render-tuple :equal 172 | [[_ s context]] 173 | (if (> (count s) 10) 174 | (-> "..." 175 | (cond-> (not= context :first) (append-str 6 s)) 176 | (cond-> (not= context :last) (prepend-str 6 s))) 177 | s)) 178 | 179 | (defn diff-tuples->string 180 | [diff-tuples] 181 | (->> diff-tuples 182 | (map render-tuple) 183 | (apply str))) 184 | 185 | (defn fraction->percent 186 | [n] 187 | (long (* n 100))) 188 | 189 | (defn report-string-diff 190 | [{:keys [a->b b->a differences-count similarity]} & rest] 191 | [(str "strings have " differences-count (pluralize differences-count " difference") " " 192 | "(" (fraction->percent similarity) "% similarity)") 193 | (str "expected: " (pr-str (diff-tuples->string a->b))) 194 | (str "actual: " (pr-str (diff-tuples->string b->a)))]) 195 | 196 | (defn diff-string 197 | [a b _] 198 | [(let [a->b (diff-tuples a b) 199 | b->a (diff-tuples b a)] 200 | {:type :string 201 | :a->b a->b 202 | :b->a b->a 203 | :differences-count (count-differences a->b) 204 | :similarity (string-similarity a b)})]) 205 | 206 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | --------------------------------------------------------------------------------