From fa55aa99c91e2f1ee109698271a21e3469fc35e6 Mon Sep 17 00:00:00 2001 From: Julien Vincent Date: Thu, 29 Aug 2024 22:07:54 +0200 Subject: [PATCH] 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. --- README.md | 3 +- .../io/julienvincent/clojure_test/json.clj | 6 +++ .../io/julienvincent/clojure_test/runner.clj | 49 ++----------------- .../clojure_test/serialization.clj | 42 ++++++++++++++++ deps.edn | 2 +- lua/clojure-test/api/exceptions.lua | 16 ++++++ lua/clojure-test/api/init.lua | 7 +++ lua/clojure-test/backends/repl.lua | 5 ++ lua/clojure-test/ui/exceptions.lua | 30 ++++++++++++ 9 files changed, 114 insertions(+), 46 deletions(-) create mode 100644 clojure/io/julienvincent/clojure_test/serialization.clj create mode 100644 lua/clojure-test/api/exceptions.lua diff --git a/README.md b/README.md index e3232d1..fc93a74 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,8 @@ vim.keymap.set("n", "ta", api.run_all_tests, { desc = "Run all test vim.keymap.set("n", "tr", api.run_tests, { desc = "Run tests" }) vim.keymap.set("n", "tn", api.run_tests_in_ns, { desc = "Run tests in a namespace" }) vim.keymap.set("n", "tp", api.rerun_previous, { desc = "Rerun the most recently run tests" }) -vim.keymap.set("n", "tl", api.run_tests_in_ns, { desc = "Find and load test namespaces in classpath" }) +vim.keymap.set("n", "tl", api.load_tests, { desc = "Find and load test namespaces in classpath" }) +vim.keymap.set("n", "!", function() api.analyze_exception("*e") end, { desc = "Inspect the most recent exception" }) ``` ## Feature Demo diff --git a/clojure/io/julienvincent/clojure_test/json.clj b/clojure/io/julienvincent/clojure_test/json.clj index 3d397d9..10d7cba 100644 --- a/clojure/io/julienvincent/clojure_test/json.clj +++ b/clojure/io/julienvincent/clojure_test/json.clj @@ -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] @@ -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))))) diff --git a/clojure/io/julienvincent/clojure_test/runner.clj b/clojure/io/julienvincent/clojure_test/runner.clj index d2cfa2c..2a6166f 100644 --- a/clojure/io/julienvincent/clojure_test/runner.clj +++ b/clojure/io/julienvincent/clojure_test/runner.clj @@ -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))] diff --git a/clojure/io/julienvincent/clojure_test/serialization.clj b/clojure/io/julienvincent/clojure_test/serialization.clj new file mode 100644 index 0000000..a045b26 --- /dev/null +++ b/clojure/io/julienvincent/clojure_test/serialization.clj @@ -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 {}))) diff --git a/deps.edn b/deps.edn index 82de985..1606053 100644 --- a/deps.edn +++ b/deps.edn @@ -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}}} diff --git a/lua/clojure-test/api/exceptions.lua b/lua/clojure-test/api/exceptions.lua new file mode 100644 index 0000000..8a2e99f --- /dev/null +++ b/lua/clojure-test/api/exceptions.lua @@ -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 diff --git a/lua/clojure-test/api/init.lua b/lua/clojure-test/api/init.lua index 4497ab3..b70292d 100644 --- a/lua/clojure-test/api/init.lua +++ b/lua/clojure-test/api/init.lua @@ -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") @@ -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 diff --git a/lua/clojure-test/backends/repl.lua b/lua/clojure-test/backends/repl.lua index dfb15a4..ef296de 100644 --- a/lua/clojure-test/backends/repl.lua +++ b/lua/clojure-test/backends/repl.lua @@ -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, ...) @@ -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 diff --git a/lua/clojure-test/ui/exceptions.lua b/lua/clojure-test/ui/exceptions.lua index 7bd131b..019e907 100644 --- a/lua/clojure-test/ui/exceptions.lua +++ b/lua/clojure-test/ui/exceptions.lua @@ -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") @@ -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