Skip to content

Commit

Permalink
Add API and interface for inspecting exceptions
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
julienvincent committed Aug 29, 2024
1 parent 6a45440 commit fa55aa9
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 46 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ vim.keymap.set("n", "<localleader>ta", api.run_all_tests, { desc = "Run all test
vim.keymap.set("n", "<localleader>tr", api.run_tests, { desc = "Run tests" })
vim.keymap.set("n", "<localleader>tn", api.run_tests_in_ns, { desc = "Run tests in a namespace" })
vim.keymap.set("n", "<localleader>tp", api.rerun_previous, { desc = "Rerun the most recently run tests" })
vim.keymap.set("n", "<localleader>tl", api.run_tests_in_ns, { desc = "Find and load test namespaces in classpath" })
vim.keymap.set("n", "<localleader>tl", api.load_tests, { desc = "Find and load test namespaces in classpath" })
vim.keymap.set("n", "<localleader>!", function() api.analyze_exception("*e") end, { desc = "Inspect the most recent exception" })
```

## Feature Demo
Expand Down
6 changes: 6 additions & 0 deletions clojure/io/julienvincent/clojure_test/json.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
(:require
[io.julienvincent.clojure-test.query :as api.query]
[io.julienvincent.clojure-test.runner :as api.runner]
[io.julienvincent.clojure-test.serialization :as api.serialization]
[jsonista.core :as json]))

(defmacro ^:private with-json-out [& body]
Expand Down Expand Up @@ -38,3 +39,8 @@
(defn resolve-metadata-for-symbol [sym]
(with-json-out
(api.query/resolve-metadata-for-symbol sym)))

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn analyze-exception [sym]
(with-json-out
(api.serialization/analyze-exception (var-get (resolve sym)))))
49 changes: 5 additions & 44 deletions clojure/io/julienvincent/clojure_test/runner.clj
Original file line number Diff line number Diff line change
@@ -1,60 +1,21 @@
(ns io.julienvincent.clojure-test.runner
(:require
[clj-commons.format.exceptions :as pretty.exceptions]
[clojure.pprint :as pprint]
[clojure.test :as test]))
[clojure.test :as test]
[io.julienvincent.clojure-test.serialization :as serialization]))

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

(defn- remove-commas
"Clojures pprint function adds commas in whitespace. This removes them while maintaining
any commas that are within strings"
[s]
(let [pattern #"(?<=^|[^\\])(\"(?:[^\"\\]|\\.)*\"|[^,\"]+)|(,)"
matches (re-seq pattern s)]
(apply str (map
(fn [[_ group1]]
(or group1 ""))
matches))))

(defn pretty-print [data]
(-> (with-out-str
(pprint/pprint data))
remove-commas))

(defn- parse-diff [diff]
(when-let [mc (try (requiring-resolve 'matcher-combinators.config/disable-ansi-color!)
(catch Exception _))]
(mc))

(cond
(= :matcher-combinators.clj-test/mismatch (:type (meta diff)))
(-> diff pr-str remove-commas)

:else
(pretty-print diff)))

(defn- parse-exception [exception]
(mapv
(fn [{:keys [properties] :as ex}]
(let [props (when properties
(pretty-print properties))]
(if props
(assoc ex :properties props)
ex)))
(pretty.exceptions/analyze-exception exception {})))

(defn- parse-report [report]
(let [exceptions (when (instance? Throwable (:actual report))
(parse-exception (:actual report)))
(serialization/analyze-exception (:actual report)))

report (cond-> (select-keys report [:type])
(:expected report)
(assoc :expected (parse-diff (:expected report)))
(assoc :expected (serialization/parse-diff (:expected report)))

(and (:actual report)
(not exceptions))
(assoc :actual (parse-diff (:actual report)))
(assoc :actual (serialization/parse-diff (:actual report)))

exceptions (assoc :exceptions exceptions))]

Expand Down
42 changes: 42 additions & 0 deletions clojure/io/julienvincent/clojure_test/serialization.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
(ns io.julienvincent.clojure-test.serialization
(:require
[clj-commons.format.exceptions :as pretty.exceptions]
[clojure.pprint :as pprint]))

(defn- remove-commas
"Clojures pprint function adds commas in whitespace. This removes them while maintaining
any commas that are within strings"
[s]
(let [pattern #"(?<=^|[^\\])(\"(?:[^\"\\]|\\.)*\"|[^,\"]+)|(,)"
matches (re-seq pattern s)]
(apply str (map
(fn [[_ group1]]
(or group1 ""))
matches))))

(defn- pretty-print [data]
(-> (with-out-str
(pprint/pprint data))
remove-commas))

(defn parse-diff [diff]
(when-let [mc (try (requiring-resolve 'matcher-combinators.config/disable-ansi-color!)
(catch Exception _))]
(mc))

(cond
(= :matcher-combinators.clj-test/mismatch (:type (meta diff)))
(-> diff pr-str remove-commas)

:else
(pretty-print diff)))

(defn analyze-exception [exception]
(mapv
(fn [{:keys [properties] :as ex}]
(let [props (when properties
(pretty-print properties))]
(if props
(assoc ex :properties props)
ex)))
(pretty.exceptions/analyze-exception exception {})))
2 changes: 1 addition & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

:aliases {:build {:extra-deps {io.github.clojure/tools.build {:mvn/version "0.9.6"}
slipset/deps-deploy {:mvn/version "0.2.2"}}
:extra-paths ["./"]
:extra-paths ["./build.clj"]
:ns-default build}}}
16 changes: 16 additions & 0 deletions lua/clojure-test/api/exceptions.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
local ui_exceptions = require("clojure-test.ui.exceptions")
local config = require("clojure-test.config")

local M = {}

function M.render_exception(sym)
local exceptions = config.backend:analyze_exception(sym)
if not exceptions or exceptions == vim.NIL then
return
end

local popup = ui_exceptions.open_exception_popup()
ui_exceptions.render_exceptions_to_buf(popup.bufnr, exceptions)
end

return M
7 changes: 7 additions & 0 deletions lua/clojure-test/api/init.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local exceptions_api = require("clojure-test.api.exceptions")
local location = require("clojure-test.api.location")
local tests_api = require("clojure-test.api.tests")
local run_api = require("clojure-test.api.run")
Expand Down Expand Up @@ -84,4 +85,10 @@ function M.load_tests()
end)
end

function M.analyze_exception(sym)
nio.run(function()
exceptions_api.render_exception(sym)
end)
end

return M
5 changes: 5 additions & 0 deletions lua/clojure-test/backends/repl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local API = {
run_test = "io.julienvincent.clojure-test.json/run-test",

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

local function statement(api, ...)
Expand Down Expand Up @@ -71,6 +72,10 @@ function M.create(client)
return eval(client, API.resolve_metadata_for_symbol, "'" .. symbol)
end

function backend:analyze_exception(symbol)
return eval(client, API.analyze_exception, "'" .. symbol)
end

return backend
end

Expand Down
30 changes: 30 additions & 0 deletions lua/clojure-test/ui/exceptions.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
local config = require("clojure-test.config")
local utils = require("clojure-test.utils")

local NuiPopup = require("nui.popup")
local NuiLine = require("nui.line")
local NuiText = require("nui.text")

Expand Down Expand Up @@ -65,4 +67,32 @@ function M.render_exceptions_to_buf(buf, exception_chain)
end
end

function M.open_exception_popup()
local popup = NuiPopup({
border = {
style = "rounded",
text = {
top = " Exception ",
},
},
position = "50%",
relative = "editor",
enter = true,
size = {
width = 120,
height = 30,
},
})

for _, chord in ipairs(utils.into_table(config.keys.ui.quit)) do
popup:map("n", chord, function()
popup:unmount()
end, { noremap = true })
end

popup:mount()

return popup
end

return M

0 comments on commit fa55aa9

Please sign in to comment.