Skip to content

Commit fa55aa9

Browse files
committed
Add API and interface for inspecting exceptions
This adds a new `analyze_exception` API which will analyze a givena symbol using the same logic used for test reporting. This exception analysis will then be used to render a dedicated exception popup window.
1 parent 6a45440 commit fa55aa9

File tree

9 files changed

+114
-46
lines changed

9 files changed

+114
-46
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ vim.keymap.set("n", "<localleader>ta", api.run_all_tests, { desc = "Run all test
138138
vim.keymap.set("n", "<localleader>tr", api.run_tests, { desc = "Run tests" })
139139
vim.keymap.set("n", "<localleader>tn", api.run_tests_in_ns, { desc = "Run tests in a namespace" })
140140
vim.keymap.set("n", "<localleader>tp", api.rerun_previous, { desc = "Rerun the most recently run tests" })
141-
vim.keymap.set("n", "<localleader>tl", api.run_tests_in_ns, { desc = "Find and load test namespaces in classpath" })
141+
vim.keymap.set("n", "<localleader>tl", api.load_tests, { desc = "Find and load test namespaces in classpath" })
142+
vim.keymap.set("n", "<localleader>!", function() api.analyze_exception("*e") end, { desc = "Inspect the most recent exception" })
142143
```
143144

144145
## Feature Demo

clojure/io/julienvincent/clojure_test/json.clj

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require
33
[io.julienvincent.clojure-test.query :as api.query]
44
[io.julienvincent.clojure-test.runner :as api.runner]
5+
[io.julienvincent.clojure-test.serialization :as api.serialization]
56
[jsonista.core :as json]))
67

78
(defmacro ^:private with-json-out [& body]
@@ -38,3 +39,8 @@
3839
(defn resolve-metadata-for-symbol [sym]
3940
(with-json-out
4041
(api.query/resolve-metadata-for-symbol sym)))
42+
43+
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
44+
(defn analyze-exception [sym]
45+
(with-json-out
46+
(api.serialization/analyze-exception (var-get (resolve sym)))))

clojure/io/julienvincent/clojure_test/runner.clj

+5-44
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,21 @@
11
(ns io.julienvincent.clojure-test.runner
22
(:require
3-
[clj-commons.format.exceptions :as pretty.exceptions]
4-
[clojure.pprint :as pprint]
5-
[clojure.test :as test]))
3+
[clojure.test :as test]
4+
[io.julienvincent.clojure-test.serialization :as serialization]))
65

76
(def ^:dynamic ^:private *report* nil)
87

9-
(defn- remove-commas
10-
"Clojures pprint function adds commas in whitespace. This removes them while maintaining
11-
any commas that are within strings"
12-
[s]
13-
(let [pattern #"(?<=^|[^\\])(\"(?:[^\"\\]|\\.)*\"|[^,\"]+)|(,)"
14-
matches (re-seq pattern s)]
15-
(apply str (map
16-
(fn [[_ group1]]
17-
(or group1 ""))
18-
matches))))
19-
20-
(defn pretty-print [data]
21-
(-> (with-out-str
22-
(pprint/pprint data))
23-
remove-commas))
24-
25-
(defn- parse-diff [diff]
26-
(when-let [mc (try (requiring-resolve 'matcher-combinators.config/disable-ansi-color!)
27-
(catch Exception _))]
28-
(mc))
29-
30-
(cond
31-
(= :matcher-combinators.clj-test/mismatch (:type (meta diff)))
32-
(-> diff pr-str remove-commas)
33-
34-
:else
35-
(pretty-print diff)))
36-
37-
(defn- parse-exception [exception]
38-
(mapv
39-
(fn [{:keys [properties] :as ex}]
40-
(let [props (when properties
41-
(pretty-print properties))]
42-
(if props
43-
(assoc ex :properties props)
44-
ex)))
45-
(pretty.exceptions/analyze-exception exception {})))
46-
478
(defn- parse-report [report]
489
(let [exceptions (when (instance? Throwable (:actual report))
49-
(parse-exception (:actual report)))
10+
(serialization/analyze-exception (:actual report)))
5011

5112
report (cond-> (select-keys report [:type])
5213
(:expected report)
53-
(assoc :expected (parse-diff (:expected report)))
14+
(assoc :expected (serialization/parse-diff (:expected report)))
5415

5516
(and (:actual report)
5617
(not exceptions))
57-
(assoc :actual (parse-diff (:actual report)))
18+
(assoc :actual (serialization/parse-diff (:actual report)))
5819

5920
exceptions (assoc :exceptions exceptions))]
6021

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
(ns io.julienvincent.clojure-test.serialization
2+
(:require
3+
[clj-commons.format.exceptions :as pretty.exceptions]
4+
[clojure.pprint :as pprint]))
5+
6+
(defn- remove-commas
7+
"Clojures pprint function adds commas in whitespace. This removes them while maintaining
8+
any commas that are within strings"
9+
[s]
10+
(let [pattern #"(?<=^|[^\\])(\"(?:[^\"\\]|\\.)*\"|[^,\"]+)|(,)"
11+
matches (re-seq pattern s)]
12+
(apply str (map
13+
(fn [[_ group1]]
14+
(or group1 ""))
15+
matches))))
16+
17+
(defn- pretty-print [data]
18+
(-> (with-out-str
19+
(pprint/pprint data))
20+
remove-commas))
21+
22+
(defn parse-diff [diff]
23+
(when-let [mc (try (requiring-resolve 'matcher-combinators.config/disable-ansi-color!)
24+
(catch Exception _))]
25+
(mc))
26+
27+
(cond
28+
(= :matcher-combinators.clj-test/mismatch (:type (meta diff)))
29+
(-> diff pr-str remove-commas)
30+
31+
:else
32+
(pretty-print diff)))
33+
34+
(defn analyze-exception [exception]
35+
(mapv
36+
(fn [{:keys [properties] :as ex}]
37+
(let [props (when properties
38+
(pretty-print properties))]
39+
(if props
40+
(assoc ex :properties props)
41+
ex)))
42+
(pretty.exceptions/analyze-exception exception {})))

deps.edn

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
:aliases {:build {:extra-deps {io.github.clojure/tools.build {:mvn/version "0.9.6"}
66
slipset/deps-deploy {:mvn/version "0.2.2"}}
7-
:extra-paths ["./"]
7+
:extra-paths ["./build.clj"]
88
:ns-default build}}}

lua/clojure-test/api/exceptions.lua

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
local ui_exceptions = require("clojure-test.ui.exceptions")
2+
local config = require("clojure-test.config")
3+
4+
local M = {}
5+
6+
function M.render_exception(sym)
7+
local exceptions = config.backend:analyze_exception(sym)
8+
if not exceptions or exceptions == vim.NIL then
9+
return
10+
end
11+
12+
local popup = ui_exceptions.open_exception_popup()
13+
ui_exceptions.render_exceptions_to_buf(popup.bufnr, exceptions)
14+
end
15+
16+
return M

lua/clojure-test/api/init.lua

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local exceptions_api = require("clojure-test.api.exceptions")
12
local location = require("clojure-test.api.location")
23
local tests_api = require("clojure-test.api.tests")
34
local run_api = require("clojure-test.api.run")
@@ -84,4 +85,10 @@ function M.load_tests()
8485
end)
8586
end
8687

88+
function M.analyze_exception(sym)
89+
nio.run(function()
90+
exceptions_api.render_exception(sym)
91+
end)
92+
end
93+
8794
return M

lua/clojure-test/backends/repl.lua

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local API = {
77
run_test = "io.julienvincent.clojure-test.json/run-test",
88

99
resolve_metadata_for_symbol = "io.julienvincent.clojure-test.json/resolve-metadata-for-symbol",
10+
analyze_exception = "io.julienvincent.clojure-test.json/analyze-exception",
1011
}
1112

1213
local function statement(api, ...)
@@ -71,6 +72,10 @@ function M.create(client)
7172
return eval(client, API.resolve_metadata_for_symbol, "'" .. symbol)
7273
end
7374

75+
function backend:analyze_exception(symbol)
76+
return eval(client, API.analyze_exception, "'" .. symbol)
77+
end
78+
7479
return backend
7580
end
7681

lua/clojure-test/ui/exceptions.lua

+30
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
local config = require("clojure-test.config")
12
local utils = require("clojure-test.utils")
23

4+
local NuiPopup = require("nui.popup")
35
local NuiLine = require("nui.line")
46
local NuiText = require("nui.text")
57

@@ -65,4 +67,32 @@ function M.render_exceptions_to_buf(buf, exception_chain)
6567
end
6668
end
6769

70+
function M.open_exception_popup()
71+
local popup = NuiPopup({
72+
border = {
73+
style = "rounded",
74+
text = {
75+
top = " Exception ",
76+
},
77+
},
78+
position = "50%",
79+
relative = "editor",
80+
enter = true,
81+
size = {
82+
width = 120,
83+
height = 30,
84+
},
85+
})
86+
87+
for _, chord in ipairs(utils.into_table(config.keys.ui.quit)) do
88+
popup:map("n", chord, function()
89+
popup:unmount()
90+
end, { noremap = true })
91+
end
92+
93+
popup:mount()
94+
95+
return popup
96+
end
97+
6898
return M

0 commit comments

Comments
 (0)