Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
* Added functions to `basilisp.test` for using and combining test fixtures (#980)

### Fixed
* Fix a bug where the reader was double counting the CRLF newline seq in metadata (#1063)
* Conform to the `cider-nrepl` `info` ops spec by ensuring result's `:file` is URI, also added missing :column number (#1066)
Expand Down
42 changes: 42 additions & 0 deletions src/basilisp/test.lpy
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Tests may take advantage of Basilisp fixtures via :lpy:fn:`use-fixtures` to perform
setup and teardown functions for each test or namespace. Fixtures are not the same
(nor are they compatible with) PyTest fixtures."
(:import types)
(:require
[basilisp.template :as template]))

Expand Down Expand Up @@ -59,6 +60,11 @@
via ``use-fixture`` and then removing them after the test is defined."
(fn [fixture-type & _] fixture-type))

(defn generator?
"Return true if ``x`` is a generator type, else false."
[x]
(instance? types/GeneratorType x))

(defmethod use-fixtures :each
[_ & fixtures]
(alter-meta! *ns* assoc ::each-fixtures fixtures))
Expand All @@ -67,6 +73,42 @@
[_ & fixtures]
(alter-meta! *ns* assoc ::once-fixtures fixtures))

(defmacro with-fixtures
"Wrap the ``body`` in the ``fixtures`` in the given order. Handle
setup and teardown for each of the ``fixtures``."
[fixtures & body]
(assert (vector? fixtures) "Expected a literal vector of fixtures")
(let [result (gensym "result")]
(reduce (fn [form fixture]
`(let [~result (~fixture)]
(if (generator? ~result)
(try
(python/next ~result)
~form
(finally
(try
(python/next ~result)
(catch python/StopIteration ~'_ nil))))
~form)))
`(do ~@body)
(reverse fixtures))))

(defmacro compose-fixtures
"Compose any number of ``fixtures``, in order, creating a new fixture
that combines their behavior. Always returns a valid fixture
function, even if no fixtures are given."
[& fixtures]
`(fn [] (with-fixtures [~@fixtures] (yield))))

(defn join-fixtures
"Composes a collection of ``fixtures``, in order. Always returns a valid
fixture function, even if the collection is empty.
Prefer :lpy:fn:`compose-fixtures` if fixtures are known at compile time."
[fixtures]
(if (seq fixtures)
(reduce #(compose-fixtures %1 %2) fixtures)
(constantly nil)))

(defmulti
^{:arglists '([expr msg line-num])}
gen-assert
Expand Down
51 changes: 29 additions & 22 deletions tests/basilisp/contrib/nrepl_server_test.lpy
Original file line number Diff line number Diff line change
Expand Up @@ -273,29 +273,36 @@
{:id @id* :ns "user" :value "1"}
{:id @id* :ns "user" :status ["done"]})
(client-send! client {:id (id-inc!) :op "complete" :prefix "clojure.test/"})
(is (= {:id @id* :status ["done"]
:completions [{:type "var" :ns "basilisp.test" :candidate "clojure.test/*test-failures*"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/*test-name*"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/*test-section*"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/are"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/deftest"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/gen-assert"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/is"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/testing"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/use-fixtures"}]}
(client-recv! client)))
(let [result (client-recv! client)
has-completion? (set (:completions result))]
(is (= @id* (:id result)))
(is (= ["done"] (:status result)))
(are [completion] (has-completion? completion)
{:type "var" :ns "basilisp.test" :candidate "clojure.test/*test-failures*"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/*test-name*"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/*test-section*"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/are"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/deftest"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/gen-assert"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/is"}
{:type "macro" :ns "basilisp.test" :candidate "clojure.test/testing"}
{:type "var" :ns "basilisp.test" :candidate "clojure.test/use-fixtures"}))

(client-send! client {:id (id-inc!) :op "complete" :prefix "test/"})
(is (= {:id @id* :status ["done"]
:completions [{:type "var" :ns "basilisp.test" :candidate "test/*test-failures*"}
{:type "var" :ns "basilisp.test" :candidate "test/*test-name*"}
{:type "var" :ns "basilisp.test" :candidate "test/*test-section*"}
{:type "macro" :ns "basilisp.test" :candidate "test/are"}
{:type "macro" :ns "basilisp.test" :candidate "test/deftest"}
{:type "var" :ns "basilisp.test" :candidate "test/gen-assert"}
{:type "macro" :ns "basilisp.test" :candidate "test/is"}
{:type "macro" :ns "basilisp.test" :candidate "test/testing"}
{:type "var" :ns "basilisp.test" :candidate "test/use-fixtures"}]}
(client-recv! client))))))))
(let [result (client-recv! client)
has-completion? (set (:completions result))]
(is (= @id* (:id result)))
(is (= ["done"] (:status result)))
(are [completion] (has-completion? completion)
{:type "var" :ns "basilisp.test" :candidate "test/*test-failures*"}
{:type "var" :ns "basilisp.test" :candidate "test/*test-name*"}
{:type "var" :ns "basilisp.test" :candidate "test/*test-section*"}
{:type "macro" :ns "basilisp.test" :candidate "test/are"}
{:type "macro" :ns "basilisp.test" :candidate "test/deftest"}
{:type "var" :ns "basilisp.test" :candidate "test/gen-assert"}
{:type "macro" :ns "basilisp.test" :candidate "test/is"}
{:type "macro" :ns "basilisp.test" :candidate "test/testing"}
{:type "var" :ns "basilisp.test" :candidate "test/use-fixtures"})))))))

(deftest nrepl-server-eval
(testing "basic"
Expand Down
79 changes: 79 additions & 0 deletions tests/basilisp/test_test.lpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
(ns tests.basilisp.test-test
(:require [basilisp.test :refer :all]))

(defn- before-after-fixture
[events]
(fn []
(swap! events conj :before)
(yield)
(swap! events conj :after)))

(defn- index-fixture
[events idx]
(fn []
(swap! events conj idx)
(yield)
(swap! events conj idx)))

(def ^:dynamic *state* nil)

(deftest with-fixtures-test
(testing "setup and teardown"
(let [events (atom [])]
(with-fixtures [(before-after-fixture events)]
(swap! events conj :during))
(is (= [:before :during :after] @events))))

(testing "teardown on exception"
(let [events (atom [])]
(try
(with-fixtures [(before-after-fixture events)]
(swap! events conj :during)
(throw (ex-info "Boom!" {})))
(catch Exception _ nil))
(is (= [:before :during :after] @events))))

(testing "teardown on fixture setup exception"
(let [events (atom [])]
(try
(with-fixtures [(before-after-fixture events)
#(throw (ex-info "Boom!" {}))]
(swap! events conj :during))
(catch Exception _ nil))
(is (= [:before :after] @events))))

(testing "teardown on fixture teardown exception"
(let [events (atom [])]
(try
(with-fixtures [(before-after-fixture events)
(fn []
(yield)
(throw (ex-info "Boom!" {})))]
(swap! events conj :during))
(catch Exception _ nil))
(is (= [:before :during :after] @events))))

(testing "applied in order"
(let [events (atom nil)]
(with-fixtures [(index-fixture events 1)
(index-fixture events 2)
(index-fixture events 3)]
(swap! events conj 4))
(is (= '(1 2 3 4 3 2 1) @events))))

(testing "nesting fixtures"
(with-fixtures [(fn []
(with-fixtures [(fn []
(binding [*state* 1]
(yield)))]
(yield)))]
(is (= 1 *state*)))))

(deftest join-fixtures-test
(testing "applied in order"
(let [events (atom nil)]
(with-fixtures [(join-fixtures [(index-fixture events 1)
(index-fixture events 2)
(index-fixture events 3)])]
(swap! events conj 4))
(is (= '(1 2 3 4 3 2 1) @events)))))
Loading