Skip to content

Commit

Permalink
Add fixture functions to basilisp.test #980 (#1067)
Browse files Browse the repository at this point in the history
I've separated out the test fixture functions from #1044 for #980
  • Loading branch information
mitch-kyle authored Sep 22, 2024
1 parent 30842eb commit 1fe7330
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 22 deletions.
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)))))

0 comments on commit 1fe7330

Please sign in to comment.