Skip to content
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

orchard.xref: include info for test vars #177

Merged
merged 3 commits into from
Aug 9, 2023
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## master (unreleased)

### Changes

- [#176](https://github.com/clojure-emacs/orchard/issues/176): `orchard.xref`: include info for test vars.
- `orchard.xref`: avoid duplicate vars that might appear following REPL re-evaluation.

## 0.14.1 (2023-08-05)

### Changes
Expand Down
38 changes: 30 additions & 8 deletions src/orchard/xref.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@
[clojure.string :as string]
[orchard.query :as q]))

(defn- var->symbol
;; TODO: use `symbol` once we start targeting Clojure >= 1.10 after CIDER 1.8 is released.
"Normally one could just use `(symbol var-ref)`,
but that doesn't work in older Clojures."
vemv marked this conversation as resolved.
Show resolved Hide resolved
[var-ref]
(let [{:keys [ns name]} (meta var-ref)]
(symbol (str (ns-name ns))
(str name))))

(defn- var->fn [var-ref]
(let [{:keys [test]} (meta var-ref)]
(if (fn? test)
test ;; for deftests, :test metadata contains the actual test implementation, with all the interesting contents.
(var-get var-ref))))

(defn- to-fn
"Convert `thing` to a function value."
[thing]
(cond
(var? thing) (var-get thing)
(symbol? thing) (var-get (find-var thing))
(var? thing) (var->fn thing)
(symbol? thing) (var->fn (find-var thing))
(fn? thing) thing))

(defn- fn-name [^java.lang.Class f]
Expand Down Expand Up @@ -71,12 +86,19 @@
(map (fn [[_k value]]
(.get ^java.lang.ref.Reference value)))
(mapcat fn-deps-class))
class-cache)]
;; if there's no deps the class is most likely AoT compiled,
;; try to access it directly
(if (empty? deps)
(-> v .getClass fn-deps-class)
deps))))
class-cache)
result
;; if there's no deps the class is most likely AoT compiled,
;; try to access it directly
(if (empty? deps)
(-> v .getClass fn-deps-class)
deps)]
(into #{}
(map resolve) ;; choose the freshest one
;; group duplicates. This is important
;; because there can be two seemingly equal #'foo.bar/baz var objects in the result.
;; That can happen as one re-evaluates code and the old var hasn't been GC'd yet.
(keys (group-by var->symbol result))))))

(defn fn-transitive-deps
"Returns a set with all the functions invoked inside `v` or inside those functions.
Expand Down
82 changes: 58 additions & 24 deletions test/orchard/xref_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,38 @@
(defn- dummy-fn [_x]
(map #(fn-dep % 2) (filter even? (range 1 10))))

;; Supports #'fn-deps-test
(deftest sample-test
(is (some? xref/eval-lock))
(is (some? (xref/fn-refs #'dummy-fn))))

(deftest fn-deps-test
(testing "with a fn value"
(is (= (xref/fn-deps dummy-fn)
#{#'clojure.core/map #'clojure.core/filter
#'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep})))
(is (= #{#'clojure.core/map #'clojure.core/filter
#'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep}
(xref/fn-deps dummy-fn))))

(testing "with a var"
(is (= (xref/fn-deps #'dummy-fn)
#{#'clojure.core/map #'clojure.core/filter
#'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep})))
(is (= #{#'clojure.core/map #'clojure.core/filter
#'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep}
(xref/fn-deps #'dummy-fn))))

(testing "with a var that backs a deftest"
(is (= #{#'orchard.xref-test/dummy-fn
#'orchard.xref/eval-lock
#'clojure.test/do-report
#'clojure.core/cons
#'clojure.core/some?
#'clojure.core/apply
#'orchard.xref/fn-refs
#'clojure.core/list}
(xref/fn-deps #'sample-test))))

(testing "with a symbol"
(is (= (xref/fn-deps 'orchard.xref-test/dummy-fn)
#{#'clojure.core/map #'clojure.core/filter
#'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep})))
(is (= #{#'clojure.core/map #'clojure.core/filter
#'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep}
(xref/fn-deps 'orchard.xref-test/dummy-fn))))

(testing "AoT compiled functions return deps"
(is (= #{#'clojure.core/conj}
(xref/fn-deps reverse)))))
Expand All @@ -38,19 +57,6 @@
(def yyy (symbol (str (gensym))
(str (gensym))))

(deftest fn-refs-test
(testing "with a fn value"
(is (= (xref/fn-refs dummy-fn) '()))
(is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn)))
(testing "with a var"
(is (= (xref/fn-refs #'dummy-fn) '()))
(is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn)))
(testing "with a symbol"
(is (= (xref/fn-refs 'orchard.xref-test/dummy-fn) '()))
(is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn)))
(testing "that usage from inside an anonymous function is found"
(is (contains? (into #{} (xref/fn-refs #'fn-dep)) #'orchard.xref-test/dummy-fn))))

(deftest fn-transitive-deps-test
(testing "basics"
(let [expected #{#'orchard.xref-test/fn-deps-test #'orchard.xref-test/fn-dep #'clojure.core/even?
Expand All @@ -59,8 +65,36 @@
(is (contains? expected #'orchard.xref-test/fn-transitive-dep)
"Specifically includes `#'fn-transitive-dep`, which is a transitive dep of `#'dummy-fn` (via `#'fn-dep`)")
(is (contains? expected #'clojure.core/inc')
"Specifically includes `#'clojure.core/inc'`, which is a transitive dep of `#'dummy-fn`
(via `#'clojure.core/range'`). Unlike other AoT compiled core transitive dependancies
"Specifically includes `#'clojure.core/inc'`, which is a transitive dep of `#'dummy-fn`
(via `#'clojure.core/range'`). Unlike other AoT compiled core transitive dependancies
it gets found because its a non `:static` dependancy.")
(is (= expected
(xref/fn-transitive-deps dummy-fn))))))

(deftest fn-refs-test
(testing "with a fn value"
(is (= #{#'orchard.xref-test/fn-deps-test
#'orchard.xref-test/fn-transitive-deps-test
#'orchard.xref-test/sample-test
#'orchard.xref-test/fn-refs-test}
(set (xref/fn-refs dummy-fn))))
(is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn)))

(testing "with a var"
(is (= #{#'orchard.xref-test/fn-deps-test
#'orchard.xref-test/fn-transitive-deps-test
#'orchard.xref-test/sample-test
#'orchard.xref-test/fn-refs-test}
(set (xref/fn-refs #'dummy-fn))))
(is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn)))

(testing "with a symbol"
(is (= #{#'orchard.xref-test/fn-deps-test
#'orchard.xref-test/fn-transitive-deps-test
#'orchard.xref-test/sample-test
#'orchard.xref-test/fn-refs-test}
(set (xref/fn-refs 'orchard.xref-test/dummy-fn))))
(is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn)))

(testing "that usage from inside an anonymous function is found"
(is (contains? (into #{} (xref/fn-refs #'fn-dep)) #'orchard.xref-test/dummy-fn))))