Skip to content

Commit

Permalink
Add --md support
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahTheDuke committed Oct 17, 2024
1 parent 1de9d9b commit df773c3
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 123 deletions.
6 changes: 6 additions & 0 deletions .clj-kondo/cond_plus/cond_plus/config.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{:linters {:cond-plus/empty-else {:level :error}
:cond-plus/missing-fn {:level :error}
:cond-plus/non-final-else {:level :error}
:cond-plus/sequence {:level :error}
:unresolved-symbol {:exclude [(cond-plus.core/cond+ [=> else])]}}
:hooks {:analyze-call {cond-plus.core/cond+ hooks.cond-plus-hook/cond+}}}
65 changes: 65 additions & 0 deletions .clj-kondo/cond_plus/cond_plus/hooks/cond_plus_hook.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
(ns hooks.cond-plus-hook
(:require [clj-kondo.hooks-api :as api]))

(defn analyze-clauses [clauses]
(reduce
(fn [found-else? clause]
;; non-sequence clause
(if (not (or (api/list-node? clause)
(api/vector-node? clause)))
(let [{:keys [row col]} (meta clause)]
(api/reg-finding!
{:message "must be sequence"
:type :cond-plus/sequence
:row row
:col col})
found-else?)
(let [[sym arrow fn-expr] (api/sexpr clause)]
(cond
;; non-final else
found-else?
(do (api/reg-finding!
(merge
{:message ":else must be in final position"
:type :cond-plus/non-final-else}
found-else?))
(reduced nil))
;; check fn-exprs
(and (or (= :> arrow)
(= '=> arrow))
(nil? fn-expr))
(let [{:keys [row col]} (meta clause)]
(api/reg-finding!
{:message "fn-expr must have third position symbol"
:type :cond-plus/missing-fn
:row row
:col col})
found-else?)
;; else handling
(or (= :else sym)
(= 'else sym))
(if found-else?
(let [{:keys [row col]} (meta clause)]
(api/reg-finding!
{:message "only one :else clause allowed"
:type :cond-plus/empty-else
:row row
:col col})
;; early exit cuz not worth analyzing the rest
(reduced nil))
(do (when-not arrow
(let [{:keys [row col]} (meta clause)]
(api/reg-finding!
{:message ":else must have a body"
:type :cond-plus/empty-else
:row row
:col col})))
;; Store row and col from existing else as we don't throw until
;; we've seen a following clause
(select-keys (meta clause) [:row :col])))))))
nil
clauses))

(defn cond+ [{:keys [node]}]
(analyze-clauses (rest (:children node)))
node)
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ jobs:
- name: tests
run: |
clojure -T:prep javac
clojure -M:provided:dev:test:run
clojure -M:provided:dev:test:ci
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Added

- Add support for [Expectations v2](https://github.com/clojure-expectations/clojure-test) in `lazytest.extensions.expectations`. Only ports over `expect` and related helpers.
- Add rudimentary test-doc support, which allows for specifying markdown files to parse and treat as tests for the purposes of test runs.
- `--md FILE` is how to specify markdown files to parse and treat as Lazytest tests.

## 1.2.0

Expand Down
68 changes: 37 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ An alternative to `clojure.test`, aiming to be feature-rich and easily extensibl

Add it to your deps.edn or project.clj:

```clojure
```clojure skip=true
{:aliases
{:test {:extra-deps {io.github.noahtheduke/lazytest {:mvn/version "1.2.0"}}
:extra-paths ["test"]
:main-opts ["-m" "lazytest.main"]}}}
```

In a test file:
In a test file, import with:

```clojure
(ns example.readme-test
(:require [lazytest.core :refer [defdescribe describe expect it]]))
(require '[lazytest.core :refer [after around before before-each defdescribe describe expect expect-it it]])
```

And then write a simple test:

```clojure skip=true
(defdescribe seq-fns-test
(describe keep
(it "should reject nils"
Expand Down Expand Up @@ -144,11 +147,13 @@ To help write meaningful tests, a couple aliases have been defined for those who
These can be used interchangeably:

```clojure
(defdescribe +-test
(require '[lazytest.core :refer [context specify should]])

(defdescribe context-test
(context "with integers"
(specify "that sums work"
(should (= 7 (+ 3 4)) "follows basic math")
(expect (not= 7 (1 + 1)))))
(expect (not= 7 (+ 1 1))))))
```

There are a number of experimental namespaces that define other aliases, with distinct behavior, if the base set of vars don't fit your needs:
Expand All @@ -170,49 +175,51 @@ In addition to finding the tests defined with `defdescribe`, Lazytest also check
How to write them:

```clojure
(ns example.metadata-test ...)

(defn fn-example {:test #(expect ...)})
(defn test-case-example {:test (it "test case example docstring" ...)})
(defn suite-example {:test (suite ...)})
(defn describe-example {:test (describe "top level docstring" ...)})
(defn fn-example
{:test #(expect (= 1 1))}
[])
(defn test-case-example
{:test (it "test case example docstring" (expect (= 1 1)))}
[])
(defn describe-example
{:test (describe "top level docstring"
(it "first test case" (expect (= 1 1)))
(it "second test case" (expect (= 1 1))))}
[])
```

How they're printed:

```
example.metadata-test
#'example.metadata-test/fn-example
lazytest.readme-test
#'lazytest.readme-test/fn-example
√ `:test` metadata
#'example.metadata-test/test-case-example
#'lazytest.readme-test/test-case-example
√ test case example docstring
#'example.metadata-test/suite-example
first test case
second test case
#'example.metadata-test/describe-example
#'lazytest.readme-test/describe-example
top level docstring
third test case
fourth test case
first test case
second test case
```

These can get unweildy if multiple test cases are included before a given implementation, so I recommend either moving them to a dedicated test file or moving the `attr-map` to the end of the function definition:

```clojure
(defn describe-example
(defn post-attr-example
([a b]
(+ a b))
{:test (describe "Should be simple addition"
(it "handles ints"
(expect (= 2 (describe-example 1 1))))
(expect (= 2 (post-attr-example 1 1))))
(it "handles floats"
(expect (= 2.0 (describe-example 1.0 1.0)))))})
(expect (= 2.0 (post-attr-example 1.0 1.0)))))})
```

## Partitioning Individual Tests and Suites

All of the test suite and test case macros (`defdescribe`, `describe`, `it`, `expect-it`) take a metadata map after the docstring. Adding `:focus true` to this map will cause *only* that test/suite to be run. Removing it will return to the normal behavior (run all tests).

```clojure
```clojure skip=true
(defdescribe focus-test
(it "will be run"
{:focus true}
Expand All @@ -223,7 +230,7 @@ All of the test suite and test case macros (`defdescribe`, `describe`, `it`, `ex

And adding `:skip true` to the metadata map will cause that test/suite to be *not* run:

```clojure
```clojure skip=true
(defdescribe skip-test
(it "will be skipped"
{:skip true}
Expand All @@ -249,7 +256,7 @@ To handle set up and tear down of stateful architecture, Lazytest provides the h
(describe "before and after example"
(before (vswap! state conj :before))
(after (vswap! state conj :after))
(expect-it "temp" (vswap! state conj :expect)))
(expect-it "can do side effects" (vswap! state conj :expect)))
(describe "results"
(expect-it "has been properly tracked"
(= [:before :expect :after] @state)))))
Expand All @@ -261,7 +268,7 @@ To handle set up and tear down of stateful architecture, Lazytest provides the h
(vswap! state conj :around-before)
(f)
(vswap! state conj :around-after))]}
(expect-it "temp" true))
(expect-it "can do side effects" true))
(describe "results"
(expect-it "correctly ran the whole thing"
(= [:around-before :around-after] @state)))))
Expand All @@ -271,8 +278,8 @@ To handle set up and tear down of stateful architecture, Lazytest provides the h
(describe "each examples"
{:context [(before (vswap! state conj :before))
(before-each (vswap! state conj :before-each))]}
(expect-it "temp" (vswap! state conj :expect-1))
(expect-it "temp" (vswap! state conj :expect-2)))
(expect-it "can do side effects" (vswap! state conj :expect-1))
(expect-it "can do side effects" (vswap! state conj :expect-2)))
(expect-it "has been properly tracked"
(= [:before :before-each :expect-1 :before-each :expect-2] @state))))
```
Expand All @@ -293,7 +300,6 @@ The default Lazytest reporter. Inspired heavily by [Mocha's Spec][mocha spec] re

[mocha spec]: https://mochajs.org/#spec


```
lazytest.core-test
it-test
Expand Down
3 changes: 3 additions & 0 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
org.clojure/tools.namespace {:mvn/version "1.5.0"}
io.github.tonsky/clj-reload {:mvn/version "0.7.0"}
dev.weavejester/medley {:mvn/version "1.8.1"}
cond-plus/cond-plus {:mvn/version "1.1.1"}
metosin/malli {:mvn/version "0.16.3"}}
:deps/prep-lib {:ensure "target/classes"
:alias :prep
Expand All @@ -29,6 +30,8 @@
;; Run the tests with `-M:test:run`.
:test {:extra-paths ["test/clojure"]}

:ci {:main-opts ["-m" "lazytest.main" "--md" "README.md"]}

:examples {:extra-paths ["."]}

;; Build a jar or uberjar with `-M:clein jar/uberjar`
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test *args:
[no-exit-message]
test-all *args:
just clojure-lsp
just test {{args}}
just test --md README.md {{args}}

repl arg="":
clojure -T:prep javac
Expand Down
10 changes: 7 additions & 3 deletions src/clojure/lazytest/cli.clj
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
(ns lazytest.cli
(:require
[clojure.java.io :as io]
[clojure.string :as str]
[clojure.tools.cli :as cli]
[lazytest.config :refer [lazytest-version]]))

(defn update-args [m k v]
(defn update-vec [m k v]
(update m k #(conj (or % []) v)))

(defn update-set [m k v]
(update m k #(conj (or % #{}) v)))

(def cli-options
[["-d" "--dir DIR" "Directory containing tests. (Defaults to \"test\".)"
:assoc-fn update-args]
:assoc-fn update-vec]
["-n" "--namespace SYMBOL" "Run only the specified test namespaces. Can be given multiple times."
:id :ns-filter
:parse-fn symbol
Expand All @@ -33,7 +34,10 @@
(let [output (if (qualified-symbol? v)
v
(symbol "lazytest.reporters" (name v)))]
(update-args args k output)))]
(update-vec args k output)))]
[nil "--md FILE" "Run doctests for given file. Can be given multiple times."
:parse-fn io/file
:assoc-fn update-vec]
[nil "--watch" "Run under Watch mode. Uses clj-reload to reload changed and dependent namespaces, then reruns test suite."]
[nil "--delay NUM" "(Watch mode) How many milliseconds to wait before checking for changes. (Defaults to 500.)"
:parse-fn parse-long]
Expand Down
14 changes: 8 additions & 6 deletions src/clojure/lazytest/config.clj
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
:else reporter))

(defn ->config [config]
(let [runner (resolve-reporter
(or (:reporter config) 'lazytest.reporters/nested))]
(-> config
(assoc :lazytest.runner/depth 1)
(assoc :lazytest.runner/suite-history [])
(assoc :reporter runner))))
(if (:lazytest.runner/depth config)
config
(let [reporter (resolve-reporter
(or (:reporter config) 'lazytest.reporters/nested))]
(-> config
(assoc :lazytest.runner/depth 1)
(assoc :lazytest.runner/suite-history [])
(assoc :reporter reporter)))))
7 changes: 5 additions & 2 deletions src/clojure/lazytest/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@
(expect-fn expr msg-gensym)
(expect-any expr msg-gensym))
(catch ExpectationFailed ex#
(let [data# (update (ex-data ex#) :message #(or % ~msg-gensym))]
(let [data# (-> (ex-data ex#)
(update :message #(or % ~msg-gensym))
(assoc :line ~(:line (meta &form)))
(assoc :column ~(:column (meta &form))))]
(throw (->ex-failed nil data#))))
(catch Throwable t#
(throw (->ex-failed ~&form ~expr {:message ~msg-gensym
Expand Down Expand Up @@ -304,7 +307,7 @@
{:arglists '([doc & body]
[doc|sym? attr-map? & body])}
[doc & body]
(assert (pos? (count body)) "Must provide body")
(assert (pos? (count body)) (str "Must provide body for " (pr-str doc)))
(let [doc (if (symbol? doc)
(if (contains? &env doc)
doc
Expand Down
13 changes: 11 additions & 2 deletions src/clojure/lazytest/main.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
[lazytest.cli :refer [validate-opts]]
[lazytest.config :refer [->config]]
[lazytest.malli]
[lazytest.md-parser :as md]
[lazytest.results :refer [summarize summary-exit-value]]
[lazytest.runner :refer [run-tests]]
[lazytest.watch :as watch]))
Expand All @@ -26,11 +27,19 @@
(filter ns-filter))
dirs)))

(defn add-md-tests
[config]
(->> (:md config)
(map io/file)
(keep #(md/build-tests-for-file % (slurp %)))))

(defn require-dirs [config dir]
(let [dirs (map io/file (or dir #{"test"}))
nses (find-ns-decls config dirs)]
md-nses (add-md-tests config)
nses (into (find-ns-decls config dirs)
md-nses)]
(when (empty? nses)
(throw (ex-info "No namespaces to run" {})))
(throw (ex-info "No namespaces to run" {:dir dir})))
(apply require nses)
nses))

Expand Down
Loading

0 comments on commit df773c3

Please sign in to comment.