Skip to content

Add fixture functions to basilisp.test #980 #1067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 22, 2024
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