diff --git a/deps.edn b/deps.edn index 14ece898..dc4e8c96 100644 --- a/deps.edn +++ b/deps.edn @@ -61,7 +61,7 @@ :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"} cprop/cprop {:mvn/version "0.1.16"} comb/comb {:mvn/version "0.1.1"} - mvxcvi/arrangement {:mvn/version "1.2.0"} + mvxcvi/arrangement {:mvn/version "2.0.0"} org.clojure/data.zip {:mvn/version "1.0.0"} clojure-csv/clojure-csv {:mvn/version "2.0.2"} org.clojure/math.combinatorics {:mvn/version "0.1.6"} @@ -101,7 +101,9 @@ orchestra/orchestra {:mvn/version "2021.01.01-1"} expound/expound {:mvn/version "0.8.10"} integrant/integrant {:mvn/version "0.8.0"} - com.stuartsierra/dependency {:mvn/version "1.0.0"}} + com.stuartsierra/dependency {:mvn/version "1.0.0"} + listora/again {:mvn/version "1.0.0"} + org.clojure/tools.gitlibs {:mvn/version "2.4.172"}} :classpath-overrides {org.clojure/clojure nil org.clojure/spec.alpha nil}} :clj-nvd diff --git a/doc/dev.md b/doc/dev.md index 8b248a9d..3c72864e 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -81,12 +81,19 @@ Test the native version: ## Tests for Libraries Babashka runs tests of libraries that are compatible with it through -`script/run_lib_tests`. To add tests for a new library, do the following: +`script/run_lib_tests`. To add tests for a new library that has a git repository +and run them, use the script `add-libtest.clj` e.g. `script/add-libtest.clj +'{listora/again {:mvn/version "1.0.0"}}' https://github.com/liwp/again --test`. + +If the library you want to add doesn't work with the script, you can manually do the following: * Add an entry for the library in `deps.edn` under the `:lib-tests` alias. * Create a directory for the library in `test-resources/lib_tests/` and copy its tests to there. * Add an entry in `run_all_libtests.clj` to run the added test namespaces. +* Run the tests `script/lib_tests/run_all_libtests NS1 NS2` +Note: If you have to modify a test to have it work with bb, add an inline +comment with prefix "TEST-FIX:" explaining what you did. ## Build diff --git a/script/add-libtest.clj b/script/add-libtest.clj new file mode 100755 index 00000000..ec450aba --- /dev/null +++ b/script/add-libtest.clj @@ -0,0 +1,86 @@ +#!/usr/bin/env bb +;; Adds a library to bb-tested-libs.edn to be tested given a library version and +;; git repository. Optionally takes a --test to then test the added library. + +(ns add-libtest + (:require [babashka.deps :as deps] + [babashka.fs :as fs] + [babashka.tasks :refer [shell]] + [clojure.string :as str] + [clojure.edn :as edn])) + +(deps/add-deps '{:deps {org.clojure/tools.gitlibs {:mvn/version "2.4.172"} + borkdude/rewrite-edn {:mvn/version "0.1.0"}}}) + +(require '[clojure.tools.gitlibs :as gl]) +(require '[borkdude.rewrite-edn :as r]) + +(defn- add-lib-to-deps + [lib-name lib-coordinate] + (let [nodes (-> "deps.edn" slurp r/parse-string)] + (spit "deps.edn" + (str (r/assoc-in nodes + [:aliases :lib-tests :extra-deps (symbol lib-name)] + lib-coordinate))))) + +(defn- copy-tests + [git-url lib-name] + (let [lib-dir (or (gl/procure git-url lib-name "master") + (gl/procure git-url lib-name "main")) + test-dir (some #(when (fs/exists? (fs/file lib-dir %)) + (str (fs/file lib-dir %))) + ;; Search common test dirs + ["test" + ;; official clojure repos like https://github.com/clojure/tools.gitlibs + "src/test/clojure"])] + (shell "cp -R" (str test-dir fs/file-separator) "test-resources/lib_tests/") + {:lib-dir lib-dir + :test-dir test-dir})) + +(defn- add-lib-to-tested-libs + [lib-name git-url {:keys [lib-dir test-dir]}] + (let [git-sha (fs/file-name lib-dir) + ; (str (fs/relativize lib-dir test-dir)) + relative-test-files (map #(str (fs/relativize test-dir %)) + (fs/glob test-dir "**/*.{clj,cljc}")) + _ (when (empty? relative-test-files) + (throw (ex-info "No test files found" {:test-dir test-dir}))) + namespaces (map #(-> % + (str/replace fs/file-separator ".") + (str/replace "_" "-") + (str/replace-first #"\.clj(c?)$" "") + symbol) + relative-test-files) + lib {:git-sha git-sha + :git-url git-url + :test-namespaces namespaces} + nodes (-> "test-resources/lib_tests/bb-tested-libs.edn" slurp r/parse-string)] + (spit "test-resources/lib_tests/bb-tested-libs.edn" + (str (r/assoc-in nodes + [(symbol lib-name)] + lib))) + namespaces)) + +(defn- run-command + [args] + (let [[deps-string git-url test-option] args + deps-map (edn/read-string deps-string) + _ (when (not= 1 (count deps-map)) + (throw (ex-info "Deps map must have one key" {}))) + lib-name (ffirst deps-map) + lib-coordinate (deps-map lib-name) + _ (add-lib-to-deps lib-name lib-coordinate) + dirs (copy-tests git-url lib-name) + namespaces (add-lib-to-tested-libs lib-name git-url dirs)] + (println "Added lib" lib-name "which tests the following namespaces:" namespaces) + (when (= "--test" test-option) + (apply shell "script/lib_tests/run_all_libtests" namespaces)))) + +(defn main + [args] + (if (< (count args) 2) + (println "Usage: bb add-libtest DEPS-MAP GIT-URL [--test]") + (run-command args))) + +(when (= *file* (System/getProperty "babashka.file")) + (main *command-line-args*)) diff --git a/test-resources/lib_tests/again/core_test.clj b/test-resources/lib_tests/again/core_test.clj new file mode 100644 index 00000000..e8f2e49d --- /dev/null +++ b/test-resources/lib_tests/again/core_test.clj @@ -0,0 +1,334 @@ +(ns again.core-test + (:require [again.core :as a :refer [with-retries]] + [clojure.test :refer [is deftest testing]] + [clojure.test.check.clojure-test :refer [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop])) + +(defspec spec-max-retries + 200 + (prop/for-all + [n gen/s-pos-int] + (let [s (a/max-retries n (repeat 0))] + (= (count s) n)))) + +(defspec spec-clamp-delay + 200 + (prop/for-all + [n gen/s-pos-int + max-delay gen/s-pos-int] + (let [s (a/max-retries + n + ;; The increment is picked so that we'll cross max-delay on delay 3 + (a/clamp-delay max-delay (a/additive-strategy 0 (/ max-delay 2))))] + (every? #(<= % max-delay) s)))) + +(defspec spec-max-delay + 200 + (prop/for-all + [n gen/s-pos-int + max-delay gen/s-pos-int] + (let [s (a/max-retries + n + (a/max-delay max-delay (a/additive-strategy 0 (/ max-delay 10))))] + (and (= (count s) (min n 10)) + (every? #(<= % max-delay) s))))) + +(defspec spec-max-duration + 200 + (prop/for-all + [d gen/s-pos-int] + (let [s (take (* 2 d) (a/max-duration d (a/constant-strategy 1)))] + (and (= (count s) d) + (= (reduce + s) d))))) + +(deftest test-max-duration + (testing "with not enough delays to satisfy specified duration" + (is (= (a/max-duration 10000 [0]) [0])))) + +(defspec spec-constant-strategy + 200 + (prop/for-all + [n gen/s-pos-int + delay gen/pos-int] + (let [s (a/max-retries n (a/constant-strategy delay))] + (and (= (count s) n) + (= (set s) #{delay}))))) + +(defspec spec-immediate-strategy + 200 + (prop/for-all + [n gen/s-pos-int] + (let [s (a/max-retries n (a/immediate-strategy))] + (and (= (count s) n) + (= (set s) #{0}))))) + +(defspec spec-additive-strategy + 200 + (prop/for-all + [n gen/s-pos-int + initial-delay gen/pos-int + increment gen/pos-int] + (let [s (a/max-retries n (a/additive-strategy initial-delay increment)) + p (fn [[a b]] (= (+ a increment) b))] + (and (= (count s) n) + (= (first s) initial-delay) + (every? p (partition 2 1 s)))))) + +(defspec spec-multiplicative-strategy + 200 + (prop/for-all + [n gen/s-pos-int + initial-delay gen/s-pos-int + delay-multiplier (gen/elements [1.0 1.1 1.3 1.6 2.0 3.0 5.0 9.0 14.0 20.0])] + (let [s (a/max-retries + n + (a/multiplicative-strategy initial-delay delay-multiplier)) + p (fn [[a b]] (= (* a delay-multiplier) b))] + (and (= (count s) n) + (= (first s) initial-delay) + (every? p (partition 2 1 s)))))) + +(defspec spec-randomize-delay + 200 + (prop/for-all + [rand-factor (gen/elements [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]) + delay gen/s-pos-int] + (let [randomize-delay #'again.core/randomize-delay + min-delay (bigint (* delay (- 1 rand-factor))) + max-delay (bigint (inc (* delay (+ 1 rand-factor)))) + rand-delay (randomize-delay rand-factor delay)] + (and (<= 0 rand-delay) + (<= min-delay rand-delay max-delay))))) + +(defspec spec-randomize-strategy + 200 + (prop/for-all + [n gen/s-pos-int + rand-factor (gen/elements [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9])] + (let [initial-delay 1000 + s (a/max-retries + n + (a/randomize-strategy + rand-factor + (a/constant-strategy initial-delay))) + min-delay (bigint (* initial-delay (- 1 rand-factor))) + max-delay (bigint (inc (* initial-delay (+ 1 rand-factor))))] + (every? #(<= min-delay % max-delay) s)))) + +(deftest test-stop-strategy + (is (empty? (a/stop-strategy)) "stop strategy has no delays")) + +(defn new-failing-fn + "Returns a map consisting of the following fields: + - `f` - a function that will succeed on the `n`th call + - `attempts` - an atom counting the number of executions of `f` + - `exception` - the exception that `f` throws until it succeeds" + [& [n]] + (let [n (or n Integer/MAX_VALUE) + attempts (atom 0) + exception (Exception. "retry") + f #(let [i (swap! attempts inc)] + (if (< i n) + (throw exception) + i))] + {:attempts attempts :exception exception :f f})) + +(defn new-callback-fn + "Returns a map consisting of the following fields: + - `callback` - a callback function to pass to `with-retries` that will fail + the operation early after the `n`th call + - `args` - an atom recording the arguments passed to `callback`" + [& [n]] + (let [n (or n Integer/MAX_VALUE) + attempts (atom 0) + args (atom []) + callback #(let [i (swap! attempts inc)] + (swap! args conj %) + (when (< n i) + ::a/fail))] + {:args args :callback callback})) + +(defspec spec-with-retries + 200 + (prop/for-all + [strategy (gen/vector gen/s-pos-int)] + (let [{:keys [attempts f]} (new-failing-fn) + delays (atom [])] + (with-redefs [a/sleep #(swap! delays conj %)] + (try + (with-retries strategy (f)) + (catch Exception _))) + + (and (= @attempts (inc (count strategy))) + (= @delays strategy))))) + +(deftest test-with-retries + (with-redefs [a/sleep (constantly nil)] + (testing "with-retries" + (testing "with non-nil return value" + (is (= (with-retries [] :ok) :ok) "returns form value")) + + (testing "with nil return value" + (is (nil? (with-retries [] nil)) "returns form value")) + + (testing "with user-context" + (let [context {:a :b} + {:keys [args callback]} (new-callback-fn) + options {::a/callback callback + ::a/strategy [] + ::a/user-context context} + _ (with-retries options :ok)] + (is (= (count @args) 1) "calls callback hook once") + (is (= (::a/user-context (first @args)) context) + "calls callback hook with user context"))) + + (testing "with success on first try" + (let [{:keys [attempts f]} (new-failing-fn 1) + {:keys [args callback]} (new-callback-fn)] + (with-retries + {::a/callback callback + ::a/strategy []} + (f)) + (is (= @attempts 1) "executes operation once") + (is (= (count @args) 1) "calls callback hook once") + (is (= (first @args) + {::a/attempts 1 + ::a/slept 0 + ::a/status :success}) + "calls callback hook with success"))) + + (testing "with success on second try" + (let [{:keys [attempts exception f]} (new-failing-fn 2) + {:keys [args callback]} (new-callback-fn)] + (with-retries + {::a/callback callback + ::a/strategy [12]} + (f)) + (is (= @attempts 2) "executes operation twice") + (is (= (count @args) 2) "calls callback hook twice") + (is (= (first @args) + {::a/attempts 1 + ::a/exception exception + ::a/slept 0 + ::a/status :retry}) + "calls callback hook with failure") + (is (= (second @args) + {::a/attempts 2 + ::a/slept 12 + ::a/status :success}) + "calls callback hook with success"))) + + (testing "with permanent failure" + (let [{:keys [exception f]} (new-failing-fn) + {:keys [args callback]} (new-callback-fn)] + (is (thrown? + Exception + (with-retries + {::a/callback callback + ::a/strategy [123]} + (f))) + "throws exception") + + (is (= (count @args) 2) "calls callback hook twice") + (is (= (first @args) + {::a/attempts 1 + ::a/exception exception + ::a/slept 0 + ::a/status :retry}) + "calls callback hook with failure") + (is (= (second @args) + {::a/attempts 2 + ::a/exception exception + ::a/slept 123 + ::a/status :failure}) + "calls callback hook with permanent failure"))) + + (testing "with early failure" + (let [{:keys [exception f]} (new-failing-fn) + {:keys [args callback]} (new-callback-fn 1)] + (is (thrown? + Exception + (with-retries + {::a/callback callback + ::a/strategy [1 2 3]} + (f))) + "throws exception") + + (is (= (count @args) 2) "calls callback hook three twice") + (is (= (first @args) + {::a/attempts 1 + ::a/exception exception + ::a/slept 0 + ::a/status :retry}) + "first callback call") + (is (= (second @args) + {::a/attempts 2 + ::a/exception exception + ::a/slept 1 + ::a/status :retry}) + "last callback call")))))) + +(defmulti log-attempt ::a/status) + +(defmethod log-attempt :retry [s] + (if (< (count @(::a/user-context s)) 1) + (swap! (::a/user-context s) conj :retry) + (do + (swap! (::a/user-context s) conj :fail) + ::a/fail))) + +(defmethod log-attempt :success [s] + (swap! (::a/user-context s) conj :success)) + +(defmethod log-attempt :failure [s] + (swap! (::a/user-context s) conj :failure)) + +(defmethod log-attempt :default [s] (assert false)) + +(deftest test-multimethod-callback + (with-redefs [a/sleep (constantly nil)] + (testing "multi-method-callback" + (testing "with success" + (let [{:keys [f]} (new-failing-fn 2) + user-context (atom [])] + (with-retries + {::a/callback log-attempt + ::a/strategy [1 2] + ::a/user-context user-context} + (f)) + (is (= (count @user-context) 2) "multimethod is called twice") + (is (= (first @user-context) :retry) "first call is a retry") + (is (= (second @user-context) :success) "second call is a success"))) + + (testing "with failure" + (let [{:keys [exception f]} (new-failing-fn) + user-context (atom [])] + (try + (with-retries + {::a/callback log-attempt + ::a/strategy [1] + ::a/user-context user-context} + (f)) + (catch Exception e + (is (= e exception) "Unexpected exception"))) + (is (= (count @user-context) 2) "multimethod is called twice") + (is (= (first @user-context) :retry) "first call is a retry") + (is (= (second @user-context) :failure) "second call is a failure"))) + + (testing "with early failure" + (let [{:keys [exception f]} (new-failing-fn) + user-context (atom [])] + (try + (with-retries + {::a/callback log-attempt + ::a/strategy [1 2] + ::a/user-context user-context} + (f)) + (catch Exception e + (is (= e exception) "Unexpected exception"))) + (is (= (count @user-context) 2) "multimethod is called three times") + (is (= (first @user-context) :retry) "first call is a retry") + (is (= (second @user-context) :fail) "second call is a fail")))))) + + diff --git a/test-resources/lib_tests/arrangement/core_test.cljc b/test-resources/lib_tests/arrangement/core_test.cljc new file mode 100644 index 00000000..333a0433 --- /dev/null +++ b/test-resources/lib_tests/arrangement/core_test.cljc @@ -0,0 +1,82 @@ +(ns arrangement.core-test + (:require + [arrangement.core :as order] + [clojure.test :refer [deftest is]])) + + +(defn- is-sorted + [& values] + (dotimes [_ 10] + (is (= values (sort order/rank (shuffle values)))))) + + +(deftest primitive-ordering + (is-sorted + nil false true 0 \a "a" :a 'a)) + + +(deftest number-ordering + (is-sorted + -123 0.0 3.14159M #?(:clj 37/8) 4096N)) + + +(deftest string-ordering + (is-sorted + "alpha" "alphabet" "beta" "omega")) + + +(deftest keyword-ordering + (is-sorted + :foo :zap :a-ns/baz :my-ns/bar)) + + +(deftest symbol-ordering + (is-sorted + 'x 'y 'aaa/foo 'z/bar)) + + +(deftest sequence-ordering + (is-sorted + '(1 2 3) + [1 2 3] + [1 2 3 4] + [1 2 4] + [1 \2 "3"] + [\1])) + + +(deftest set-ordering + (is-sorted + #{:one} + #{:two} + #{:zzz} + #{:one :two} + #{:one :zzz} + #{:a :e :f} + #{:b :c :d})) + + +(deftest map-ordering + (is-sorted + {:a 1} + {:a 2} + {:b 1}) + (is-sorted + {:x 1} + {:a 1, :q 2} + {:a 1, :b 2, :c 3}) + (is-sorted + {:a 1, :b 2} + {:a 1, :b :*} + {:x 1, :y 2} + {:a 1, :b 8.0, :c 'x})) + + +#?(:clj + (deftest class-ordering + (is-sorted + ;; TEST-FIX: bb doesn't have java.util.Currency/getInstance + #_(java.util.Currency/getInstance "JPY") + #_(java.util.Currency/getInstance "USD") + (java.util.Date. 1234567890) + (java.util.Date. 1234567891)))) diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj index f95b397d..4dd03e38 100644 --- a/test-resources/lib_tests/babashka/run_all_libtests.clj +++ b/test-resources/lib_tests/babashka/run_all_libtests.clj @@ -1,6 +1,7 @@ (ns babashka.run-all-libtests (:require [clojure.java.io :as io] [clojure.string :as str] + [clojure.edn :as edn] [clojure.test :as t])) #_(require 'clojure.spec.alpha) @@ -13,17 +14,14 @@ (or (empty? ns-args) (contains? ns-args ns))) -(defmacro test-namespaces [& namespaces] - (let [namespaces (map second namespaces) - namespaces (seq (filter test-namespace? namespaces)) - quoted-namespaces (map #(list 'quote %) namespaces) - requires (map #(list 'require %) quoted-namespaces)] - (when (seq requires) - `(do - ~@requires - (let [m# (t/run-tests ~@quoted-namespaces)] - (swap! status (fn [status#] - (merge-with + status# (dissoc m# :type))))))))) +(defn test-namespaces [& namespaces] + (let [namespaces (seq (filter test-namespace? namespaces))] + (when (seq namespaces) + (doseq [n namespaces] + (require n)) + (let [m (apply t/run-tests namespaces)] + (swap! status (fn [status] + (merge-with + status (dissoc m :type)))))))) (def windows? (-> (System/getProperty "os.name") (str/lower-case) @@ -61,41 +59,13 @@ (require '[cprop.source :refer [from-env]]) (println (:cprop-env (from-env))) -;;;; comb - -;; TODO: port to test-namespaces - -(require '[comb.template :as template]) -(prn (template/eval "<% (dotimes [x 3] %>foo<% ) %>")) -(prn (template/eval "Hello <%= name %>" {:name "Alice"})) -(def hello - (template/fn [name] "Hello <%= name %>")) -(prn (hello "Alice")) - -;;;; arrangement - -;; TODO: port to test-namespaces - -(require '[arrangement.core :as order]) -(prn (sort order/rank ['a false 2 :b nil 3.14159 - "c" true \d [3 2] #{:one :two} - [3 1 2] #{:three}])) - ;;;; clj-yaml (test-namespaces 'clj-yaml.core-test) -;;;; clojure-csv - -;; TODO: port to test-namespaces - -(require '[clojure-csv.core :as csv]) -;; TODO: convert to test -(prn (csv/write-csv (csv/parse-csv "a,b,c\n1,2,3"))) - ;;;; clojure.data.zip -;; TODO: port to test-namespaces +;; TODO: port to test-namespaces. Blocked until clojure.xml is supported (require '[clojure.data.xml :as xml]) (require '[clojure.zip :as zip]) @@ -276,6 +246,10 @@ (test-namespaces 'com.stuartsierra.dependency-test) +(let [lib-tests (edn/read-string (slurp (io/resource "bb-tested-libs.edn")))] + (doseq [{tns :test-namespaces} (vals lib-tests)] + (apply test-namespaces tns))) + ;;;; final exit code (let [{:keys [:test :fail :error] :as m} @status] diff --git a/test-resources/lib_tests/bb-tested-libs.edn b/test-resources/lib_tests/bb-tested-libs.edn new file mode 100644 index 00000000..20ae0b76 --- /dev/null +++ b/test-resources/lib_tests/bb-tested-libs.edn @@ -0,0 +1,8 @@ +;; These libraries are tested against babashka and have been added by +;; script/add-libtest.clj +{listora/again + {:git-sha "b1a6793f0deaa3cc016eee7626097ba8bf36ba10", :git-url "https://github.com/liwp/again", :test-namespaces (again.core-test)} + org.clojure/tools.gitlibs {:git-sha "9f98af7631e34983d5b0886e1ab6eadc3856290b", :git-url "https://github.com/clojure/tools.gitlibs", :test-namespaces (clojure.tools.test-gitlibs clojure.tools.gitlibs.test-impl)} + comb/comb {:git-sha "625a63a9c040fa4a2b3153d8c84d08dc2fc8f660", :git-url "https://github.com/weavejester/comb", :test-namespaces (comb.test.template)} + mvxcvi/arrangement {:git-sha "360d29e7ae81abbf986b5a8e272f2086227d038d", :git-url "https://github.com/greglook/clj-arrangement", :test-namespaces (arrangement.core-test)} + clojure-csv/clojure-csv {:git-sha "b6bb882a3a9ac1f82e06eb2262ae7c8141935228", :git-url "https://github.com/davidsantiago/clojure-csv", :test-namespaces (clojure-csv.test.utils clojure-csv.test.core)}} diff --git a/test-resources/lib_tests/clojure/tools/gitlibs/test_impl.clj b/test-resources/lib_tests/clojure/tools/gitlibs/test_impl.clj new file mode 100644 index 00000000..ea732cdc --- /dev/null +++ b/test-resources/lib_tests/clojure/tools/gitlibs/test_impl.clj @@ -0,0 +1,36 @@ +(ns clojure.tools.gitlibs.test-impl + (:require + [clojure.test :refer :all] + [clojure.tools.gitlibs.impl :as impl])) + +(deftest test-clean-url + (are [url expected-path] + (= expected-path (#'impl/clean-url url)) + + ;; url formats - don't use user or port + "ssh://git@gitlab.com:3333/org/repo.git" "ssh/gitlab.com/org/repo" + "ssh://git@gitlab.org.net/org/repo.git" "ssh/gitlab.org.net/org/repo" + "ssh://user@host.xz/~user/repo.git/" "ssh/host.xz/_TILDE_user/repo" + "https://github.com/org/repo.git" "https/github.com/org/repo" + "git://host.xz/path/to/repo.git/" "git/host.xz/path/to/repo" + + ;; scp style url (most common github ssh url format) + "git@github.com:org/repo.git" "ssh/github.com/org/repo" + "git@github.com:dotted.org/dotted.repo.git" "ssh/github.com/dotted.org/dotted.repo" + "host.xz:~user/path/to/repo.git/" "ssh/host.xz/_TILDE_user/path/to/repo" + + ;; file scheme + "file:///Users/me/code/repo.git" "file/Users/me/code/repo" + "file://../foo.git" "file/REL/_DOTDOT_/foo" + "file://~/path/repo.git" "file/REL/_TILDE_/path/repo" + + ;; file repos - handle relative vs absolute, handle . .. ~ + "/Users/me/code/repo.git" "file/Users/me/code/repo" + "../foo.git" "file/REL/_DOTDOT_/foo" + "./foo.git" "file/REL/_DOT_/foo" + "~user/foo.git" "file/REL/_TILDE_user/foo" + + ;; git - unknown transport with url rewrite in gitconfig (unusual, but do something useful) + "work:repo.git" "ssh/work/repo")) + + diff --git a/test-resources/lib_tests/clojure/tools/test_gitlibs.clj b/test-resources/lib_tests/clojure/tools/test_gitlibs.clj new file mode 100644 index 00000000..7c6b39a9 --- /dev/null +++ b/test-resources/lib_tests/clojure/tools/test_gitlibs.clj @@ -0,0 +1,48 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.tools.test-gitlibs + (:require + [clojure.java.io :as jio] + [clojure.test :refer :all] + [clojure.tools.gitlibs :as gl])) + +(def repo-url "https://github.com/clojure/spec.alpha.git") + +(deftest test-resolve + (is (= (gl/resolve repo-url "739c1af5") + "739c1af56dae621aedf1bb282025a0d676eff713"))) + +(deftest test-procure + (let [wt1 (gl/procure repo-url 'org.clojure/spec.alpha "739c1af") + wt2 (gl/procure repo-url 'org.clojure/spec.alpha "6a56327")] + (is (.exists (jio/file (gl/cache-dir) "_repos" "https" "github.com" "clojure" "spec.alpha"))) + (is (= wt1 (.getAbsolutePath (jio/file (gl/cache-dir) "libs" "org.clojure" "spec.alpha" "739c1af56dae621aedf1bb282025a0d676eff713")))) + (is (= wt2 (.getAbsolutePath (jio/file (gl/cache-dir) "libs" "org.clojure" "spec.alpha" "6a56327446c909db0d11ecf93a3c3d659b739be9")))))) + +(deftest test-descendant-fixed + (is (= (gl/descendant repo-url ["607aef0" "739c1af"]) + "739c1af56dae621aedf1bb282025a0d676eff713")) + (is (= (gl/descendant repo-url ["739c1af" "607aef0"]) + "739c1af56dae621aedf1bb282025a0d676eff713")) + (is (= (gl/descendant repo-url ["607aef0" "607aef0"]) + "607aef0643f6cf920293130d45e6160d93fda908")) + + (is (nil? (gl/descendant repo-url nil))) + (is (nil? (gl/descendant repo-url []))) + (is (nil? (gl/descendant repo-url ["abcdef" "123456"])))) + +(deftest test-descendant-combos + (let [m (gl/resolve repo-url "master") + m' (gl/resolve repo-url "master~1") + m'' (gl/resolve repo-url "master~2")] + (are [rs d] (= d (gl/descendant repo-url rs)) + [m] m + [m m''] m + [m' m''] m' + [m'' m' m] m))) diff --git a/test-resources/lib_tests/clojure_csv/test/core.clj b/test-resources/lib_tests/clojure_csv/test/core.clj new file mode 100644 index 00000000..7c2b3529 --- /dev/null +++ b/test-resources/lib_tests/clojure_csv/test/core.clj @@ -0,0 +1,132 @@ +(ns clojure-csv.test.core + (:import [java.io StringReader]) + (:use clojure.test + clojure.java.io + clojure-csv.core)) + +(deftest basic-functionality + (is (= [["a" "b" "c"]] (parse-csv "a,b,c"))) + (is (= [["" ""]] (parse-csv ","))) + (is (= [["a" "b"]] (parse-csv "a,b\r\n"))) ;; Linebreak on eof won't add line. + (is (= [] (parse-csv "")))) + +(deftest alternate-sources + (is (= [["a" "b" "c"]] (parse-csv (StringReader. "a,b,c")))) + (is (= [["" ""]] (parse-csv (StringReader. ",")))) + (is (= [] (parse-csv (StringReader. "")))) + (is (= [["First", "Second"]] (parse-csv + (reader (.toCharArray "First,Second")))))) + +(deftest quoting + (is (= [[""]] (parse-csv "\""))) + (is (= [["\""]] (parse-csv "\"\"\""))) + (is (= [["Before", "\"","After"]] (parse-csv "Before,\"\"\"\",After"))) + (is (= [["Before", "", "After"]] (parse-csv "Before,\"\",After"))) + (is (= [["", "start&end", ""]] (parse-csv "\"\",\"start&end\",\"\""))) + (is (= [[",", "\"", ",,", ",,,"]] + (parse-csv "\",\",\"\"\"\",\",,\",\",,,\""))) + (is (= [["quoted", "\",\"", "comma"]] + (parse-csv "quoted,\"\"\",\"\"\",comma"))) + (is (= [["Hello"]] (parse-csv "\"Hello\""))) + (is (thrown? Exception (dorun (parse-csv "\"Hello\" \"Hello2\"")))) + (is (thrown? Exception (dorun (parse-csv "\"Hello\" \"Hello2\" \"Hello3\"")))) + (is (thrown? Exception (dorun (parse-csv "\"Hello\",\"Hello2\" \"Hello3\"")))) + (is (= [["Hello\"Hello2"]] (parse-csv "\"Hello\"\"Hello2\""))) + (is (thrown? Exception (dorun (parse-csv "\"Hello\"Hello2")))) + (is (= [["Hello"]] (parse-csv "\"Hello")))) + +(deftest newlines + (is (= [["test1","test2"] ["test3","test4"]] + (parse-csv "test1,test2\ntest3,test4"))) + (is (= [["test1","test2"] ["test3","test4"]] + (parse-csv "test1,test2\r\ntest3,test4"))) + (is (= [["embedded","line\nbreak"]] (parse-csv "embedded,\"line\nbreak\""))) + (is (= [["embedded", "line\r\nbreak"]] + (parse-csv "embedded,\"line\r\nbreak\"")))) + +(deftest writing + (is (= "test1,test2\n" (write-csv [["test1" "test2"]]))) + (is (= "test1,test2\ntest3,test4\n" + (write-csv [["test1" "test2"] ["test3" "test4"]]))) + (is (= "quoted:,\"line\nfeed\"\n" + (write-csv [["quoted:" "line\nfeed"]]))) + (is (= "quoted:,\"carriage\rreturn\"\n" + (write-csv [["quoted:" "carriage\rreturn"]]))) + (is (= "quoted:,\"embedded,comma\"\n" + (write-csv [["quoted:" "embedded,comma"]]))) + (is (= "quoted:,\"escaped\"\"quotes\"\"\"\n" + (write-csv [["quoted:" "escaped\"quotes\""]])))) + +(deftest force-quote-on-output + (is (= "test1,test2\n" (write-csv [["test1" "test2"]]))) + (is (= "test1,test2\n" (write-csv [["test1" "test2"]] :force-quote false))) + (is (= "\"test1\",\"test2\"\n" (write-csv [["test1" "test2"]] + :force-quote true))) + (is (= "stillquoted:,\"needs,quote\"\n" + (write-csv [["stillquoted:" "needs,quote"]] + :force-quote false))) + (is (= "\"allquoted:\",\"needs,quote\"\n" + (write-csv [["allquoted:" "needs,quote"]] + :force-quote true)))) + +(deftest alternate-delimiters + (is (= [["First", "Second"]] + (parse-csv "First\tSecond" :delimiter \tab))) + (is (= "First\tSecond\n" + (write-csv [["First", "Second"]] :delimiter \tab))) + (is (= "First\tSecond,Third\n" + (write-csv [["First", "Second,Third"]] :delimiter \tab))) + (is (= "First\t\"Second\tThird\"\n" + (write-csv [["First", "Second\tThird"]] :delimiter \tab)))) + +(deftest alternate-quote-char + (is (= [["a", "b", "c"]] + (parse-csv "a,|b|,c" :quote-char \|))) + (is (= [["a", "b|c", "d"]] + (parse-csv "a,|b||c|,d" :quote-char \|))) + (is (= [["a", "b\"\nc", "d"]] + (parse-csv "a,|b\"\nc|,d" :quote-char \|))) + (is (= "a,|b||c|,d\n" + (write-csv [["a", "b|c", "d"]] :quote-char \|))) + (is (= "a,|b\nc|,d\n" + (write-csv [["a", "b\nc", "d"]] :quote-char \|))) + (is (= "a,b\"c,d\n" + (write-csv [["a", "b\"c", "d"]] :quote-char \|)))) + +(deftest strictness + (is (thrown? Exception (dorun (parse-csv "a,b,c,\"d" :strict true)))) + (is (thrown? Exception (dorun (parse-csv "a,b,c,d\"e" :strict true)))) + (is (= [["a","b","c","d"]] + (parse-csv "a,b,c,\"d" :strict false))) + (is (= [["a","b","c","d"]] + (parse-csv "a,b,c,\"d\"" :strict true))) + (is (= [["a","b","c","d\""]] + (parse-csv "a,b,c,d\"" :strict false))) + (is (= [["120030" "BLACK COD FILET MET VEL \"MSC\"" "KG" "0" "1"]] + (parse-csv "120030;BLACK COD FILET MET VEL \"MSC\";KG;0;1" + :strict false :delimiter \;)))) + +(deftest reader-cases + ;; reader will be created and closed in with-open, but used outside. + ;; this is actually a java.io.IOException, but thrown at runtime so... + ;; TEST-FIX: bb throws IOException instead of RuntimeException + (is (thrown? java.io.IOException + (dorun (with-open [sr (StringReader. "a,b,c")] + (parse-csv sr)))))) + +(deftest custom-eol + ;; Test the use of this option. + (is (= [["a" "b"] ["c" "d"]] (parse-csv "a,b\rc,d" :end-of-line "\r"))) + (is (= [["a" "b"] ["c" "d"]] (parse-csv "a,babcc,d" :end-of-line "abc"))) + ;; The presence of an end-of-line option turns off the parsing of \n and \r\n + ;; as EOLs, so they can appear unquoted in fields when they do not interfere + ;; with the EOL. + (is (= [["a" "b\n"] ["c" "d"]] (parse-csv "a,b\n\rc,d" :end-of-line "\r"))) + (is (= [["a" "b"] ["\nc" "d"]] (parse-csv "a,b\r\nc,d" :end-of-line "\r"))) + ;; Custom EOL can still be quoted into a field. + (is (= [["a" "b\r"] ["c" "d"]] (parse-csv "a,\"b\r\"\rc,d" + :end-of-line "\r"))) + (is (= [["a" "bHELLO"] ["c" "d"]] (parse-csv "a,\"bHELLO\"HELLOc,d" + :end-of-line "HELLO"))) + (is (= [["a" "b\r"] ["c" "d"]] (parse-csv "a,|b\r|\rc,d" + :end-of-line "\r" :quote-char \|)))) diff --git a/test-resources/lib_tests/clojure_csv/test/utils.clj b/test-resources/lib_tests/clojure_csv/test/utils.clj new file mode 100644 index 00000000..430ab085 --- /dev/null +++ b/test-resources/lib_tests/clojure_csv/test/utils.clj @@ -0,0 +1,112 @@ +(ns clojure-csv.test.utils + "Some whitebox testing of the private utility functions used in core." + (:import [java.io StringReader]) + ;; TEST-FIX: Had to require since use caused conflict which bb failed on + (:require [clojure-csv.core]) + (:use clojure.test + clojure.java.io)) + +(def default-options {:delimiter \, :quote-char \" + :strict false :end-of-line nil}) + +(deftest eol-at-reader-pos? + ;; Testing the private function to check for EOLs + (is (= true (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "\n") nil))) + (is (= true (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "\r\n") + nil))) + (is (= true (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "\nabc") + nil))) + (is (= true (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "\r\nabc") + nil))) + (is (= false (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "\r\tabc") + nil))) + ;; Testing for user-specified EOLs + (is (= true (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "abc") + "abc"))) + (is (= true (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "abcdef") + "abc"))) + (is (= false (#'clojure-csv.core/eol-at-reader-pos? (StringReader. "ab") + "abc")))) + +(deftest skip-past-eol + (is (= (int \c) + (let [rdr (StringReader. "\nc")] + (#'clojure-csv.core/skip-past-eol rdr) + (.read rdr)))) + (is (= (int \c) + (let [rdr (StringReader. "\r\nc")] + (#'clojure-csv.core/skip-past-eol rdr) + (.read rdr)))) + (is (= (int \c) + (let [rdr (StringReader. "QQQc")] + (#'clojure-csv.core/skip-past-eol rdr "QQQ") + (.read rdr))))) + +(deftest read-unquoted-field + (let [{:keys [delimiter quote-char strict end-of-line]} default-options] + (is (= "abc" (#'clojure-csv.core/read-unquoted-field + (StringReader. "abc,def") + delimiter quote-char strict end-of-line))) + (is (= "abc" (#'clojure-csv.core/read-unquoted-field + (StringReader. "abc") + delimiter quote-char strict end-of-line))) + (is (= "abc" (#'clojure-csv.core/read-unquoted-field + (StringReader. "abc\n") + delimiter quote-char strict end-of-line))) + (is (= "abc" (#'clojure-csv.core/read-unquoted-field + (StringReader. "abc\r\n") + delimiter quote-char strict end-of-line))) + (is (= "abc" (#'clojure-csv.core/read-unquoted-field + (StringReader. "abcQQQ") + delimiter quote-char strict "QQQ"))) + (is (= "abc\n" (#'clojure-csv.core/read-unquoted-field + (StringReader. "abc\nQQQ") + delimiter quote-char strict "QQQ"))) + (is (= "abc\"" (#'clojure-csv.core/read-unquoted-field + (StringReader. "abc\",") + delimiter quote-char strict end-of-line))) + (is (= "" (#'clojure-csv.core/read-unquoted-field + (StringReader. ",,,") + delimiter quote-char strict end-of-line))) + (is (thrown? java.lang.Exception + (#'clojure-csv.core/read-unquoted-field + (StringReader. "abc\",") + delimiter quote-char true end-of-line))))) + +(deftest escaped-quote-at-reader-pos? + (is (= true (#'clojure-csv.core/escaped-quote-at-reader-pos? + (StringReader. "\"\"") + (int \")))) + (is (= true (#'clojure-csv.core/escaped-quote-at-reader-pos? + (StringReader. "\"\"abc") + (int \")))) + (is (= false (#'clojure-csv.core/escaped-quote-at-reader-pos? + (StringReader. "\"abc") + (int \")))) + (is (= false (#'clojure-csv.core/escaped-quote-at-reader-pos? + (StringReader. "abc") + (int \"))))) + +(deftest read-quoted-field + (let [{:keys [delimiter quote-char strict]} default-options + delimiter (int delimiter) + quote-char (int quote-char)] + (is (= "abc" (#'clojure-csv.core/read-quoted-field + (StringReader. "\"abc\"") + delimiter quote-char strict))) + (is (= "abc" (#'clojure-csv.core/read-quoted-field + (StringReader. "\"abc\",def") + delimiter quote-char strict))) + (is (= "ab\"c" (#'clojure-csv.core/read-quoted-field + (StringReader. "\"ab\"\"c\"") + delimiter quote-char strict))) + (is (= "ab\nc" (#'clojure-csv.core/read-quoted-field + (StringReader. "\"ab\nc\"") + delimiter quote-char strict))) + (is (= "ab,c" (#'clojure-csv.core/read-quoted-field + (StringReader. "\"ab,c\"") + delimiter quote-char strict))) + (is (thrown? java.lang.Exception + (#'clojure-csv.core/read-quoted-field + (StringReader. "\"abc") + delimiter quote-char true))))) diff --git a/test-resources/lib_tests/comb/test/template.clj b/test-resources/lib_tests/comb/test/template.clj new file mode 100644 index 00000000..3ba53b44 --- /dev/null +++ b/test-resources/lib_tests/comb/test/template.clj @@ -0,0 +1,15 @@ +(ns comb.test.template + (:use clojure.test) + (:require [comb.template :as t] :reload)) + +(deftest eval-test + (is (= (t/eval "foo") "foo")) + (is (= (t/eval "<%= 10 %>") "10")) + (is (= (t/eval "<%= x %>" {:x "foo"}) "foo")) + (is (= (t/eval "<%=x%>" {:x "foo"}) "foo")) + (is (= (t/eval "<% (doseq [x xs] %>foo<%= x %> <% ) %>" {:xs [1 2 3]}) + "foo1 foo2 foo3 "))) + +(deftest fn-test + (is (= ((t/fn [x] "foo<%= x %>") "bar") + "foobar")))