Add libtest (#1112)

* Add add-libtest script and add 2 libraries with it

* Add tests for 3 existing libraries
This commit is contained in:
Gabriel Horner 2021-12-21 13:25:10 -05:00 committed by GitHub
parent becdabe100
commit 4e7d04f672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 879 additions and 43 deletions

View file

@ -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

View file

@ -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

86
script/add-libtest.clj Executable file
View file

@ -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*))

View file

@ -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"))))))

View file

@ -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))))

View file

@ -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]

View file

@ -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)}}

View file

@ -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"))

View file

@ -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)))

View file

@ -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 \|))))

View file

@ -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)))))

View file

@ -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")))