From 98c72631f1b68b1c00cddfa507efb6b25d9fc3ed Mon Sep 17 00:00:00 2001 From: vemv Date: Sun, 26 Sep 2021 06:55:57 +0200 Subject: [PATCH] Remove Dynapath, accomodate `enrich-classpath` Fixes https://github.com/clojure-emacs/orchard/issues/122 Fixes https://github.com/clojure-emacs/orchard/issues/120 Fixes https://github.com/clojure-emacs/orchard/issues/105 Fixes https://github.com/clojure-emacs/orchard/issues/103 Fixes https://github.com/clojure-emacs/orchard/issues/50 --- .circleci/config.yml | 24 +- .clj-kondo/config.edn | 5 +- CHANGELOG.md | 9 + Makefile | 7 +- README.md | 15 +- dev/user.clj | 2 +- project.clj | 95 ++------ src-newer-jdks/orchard/java/parser.clj | 15 +- src/orchard/apropos.clj | 4 +- src/orchard/java.clj | 67 ++---- src/orchard/java/classpath.clj | 34 ++- src/orchard/meta.clj | 2 +- .../orchard/java/DummyClass.java | 0 test-newer-jdks/orchard/java/parser_test.clj | 104 ++++----- test/orchard/info_test.clj | 28 +-- test/orchard/java/classpath_test.clj | 97 ++++---- .../third_party_compat_test.clj | 11 +- test/orchard/java_test.clj | 213 ++++++++---------- test/orchard/test/util.clj | 6 + 19 files changed, 299 insertions(+), 439 deletions(-) rename {test => test-java}/orchard/java/DummyClass.java (100%) create mode 100644 test/orchard/test/util.clj diff --git a/.circleci/config.yml b/.circleci/config.yml index c603061a..bc9d2fb6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -161,6 +161,11 @@ jobs: - run: name: Running tests command: make test + # Eastwood is run for every item in the CI matrix, because its results are sensitive to the code in the runtime, + # so we make the most out of this linter by exercising all profiles, JDK versions, etc. + - run: + name: Running Eastwood + command: make eastwood ###################################################################### # @@ -172,10 +177,9 @@ jobs: # The ci-test-matrix does the following: # # - run tests against the target matrix -# - Java 8 and 11 +# - Java 8, 11, 16 and 17 # - Clojure 1.8, 1.9, 1.10, master # - linter, eastwood and cljfmt -# - runs code coverage report workflows: version: 2.1 @@ -186,17 +190,7 @@ workflows: parameters: jdk_version: [openjdk8, openjdk11, openjdk16, openjdk17] clojure_version: ["1.8", "1.9", "1.10", "master"] - test_profiles: ["+test", "+test,+no-dynapath"] - - util_job: - # This step exercises JDK8 specifically. Eastwood, given its runtime-based approach can detect problems specific to JDK8 - # (specifically, it will analyze the `java.legacy-parser` ns): - name: Code Linting, JDK8 (Eastwood only) - jdk_version: openjdk8 - steps: - - run: - name: Running Eastwood - command: | - make eastwood + test_profiles: ["+test", "+test,+enrich-classpath"] - util_job: name: Code Linting, (latest LTS JDK) jdk_version: openjdk17 @@ -209,7 +203,3 @@ workflows: name: Running clj-kondo command: | make kondo - - run: - name: Running Eastwood - command: | - make eastwood diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index edf08f1a..2256541c 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -1,4 +1,7 @@ {:output {:progress true :exclude-files ["analysis.cljc" "meta.cljc" "inspect_test.clj"]} :linters {:unused-private-var {:level :warning - :exclude [orchard.query-test/a-private orchard.query-test/docd-fn]}}} + :exclude [orchard.query-test/a-private orchard.query-test/docd-fn]} + ;; Enable this opt-in linter: + :unsorted-required-namespaces + {:level :warning}}} diff --git a/CHANGELOG.md b/CHANGELOG.md index be448b5f..1df946b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## master (unreleased) +### Changes + +* Remove `dynapath` dependency + * With it, defns related with mutable classloader are now deprecated and are no-ops + * `-Dorchard.use-dynapath=false` has no effect now either. +* Accomodate [`enrich-classpath`](https://github.com/clojure-emacs/enrich-classpath) + * Now, if you intend to use Orchard for its Java functionality, it is expected that you use enrich-classpath also. + * If not present, Java-related features won't work (but at least won't throw a compile-time error). + ### Bugs Fixed * [#135](https://github.com/clojure-emacs/orchard/issues/135): Fix problematic double var lookup in `orchard.xref/fn-refs`. diff --git a/Makefile b/Makefile index b1a82ebb..c69c1613 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test test-watch docs eastwood cljfmt release deploy clean +.PHONY: test test-watch docs eastwood cljfmt release deploy clean .EXPORT_ALL_VARIABLES VERSION ?= 1.10 @@ -7,10 +7,11 @@ TEST_PROFILES ?= +test resources/clojuredocs/export.edn: curl -o $@ https://github.com/clojure-emacs/clojuredocs-export-edn/raw/master/exports/export.compact.edn -test: +# .EXPORT_ALL_VARIABLES passes TEST_PROFILES to Lein so that it can inspect the active profiles, which is needed for a complete Eastwood setup: +test: clean .EXPORT_ALL_VARIABLES lein with-profile -user,-dev,+$(VERSION),$(TEST_PROFILES) test -test-watch: test-resources/clojuredocs/export.edn +test-watch: test-resources/clojuredocs/export.edn clean lein with-profile +$(VERSION),$(TEST_PROFILES) test-refresh eastwood: diff --git a/README.md b/README.md index 398c164e..de7df95c 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,6 @@ Orchard is meant to run alongside your application and we can't have a dev tools library interfere with your app right? Dependency collisions are nasty problems which are best solved by making sure there can't be any shared libraries to cause the conflict. -Currently Orchard has one runtime dependency (`dynapath`), but we hope to eliminate it at some point. - ### API Optimized for Editors Code editors can't know what symbols resolve to without consulting a REPL that's why they would typically @@ -86,18 +84,19 @@ Just add `orchard` as a dependency and start hacking. Consult the [API documentation](https://cljdoc.org/d/cider/orchard/CURRENT) to get a better idea about the functionality that's provided. +#### Using `enrich-classpath` for best results + +There are features that Orchard intends to provide (especially, those related to Java interaction) which need to assume a pre-existing initial classpath that already has various desirable items, such as the JDK sources, third-party sources, special jars such as `tools` (for JDK8), a given project's own Java sources... all that is a domain in itself, which is why our [enrich-classpath](https://github.com/clojure-emacs/enrich-classpath) project does it. + +For getting the most out of Orchard, it is therefore recommended/necessary to use `enrich-classpath`. Please refer to its installation/usage instructions. + ## Configuration options So far, Orchard follows these options, which can be specified as Java system properties (which means that end users can choose to set them globally without fiddling with tooling internals): -* `"-Dorchard.use-dynapath=false"` (default: true) - * if `false`, all features that currently depend on dynapath (and therefore alter the classpath) will be disabled. - * This is a way to avoid a number of known issues: [#103](https://github.com/clojure-emacs/orchard/issues/103), [#105](https://github.com/clojure-emacs/orchard/issues/105), [#112](https://github.com/clojure-emacs/orchard/pull/112). - * Note that if this option is `false`, Orchard clients will have to figure out themselves a way to e.g. fetch Java sources. - * It is foreseen that soon enough this will be reliably offered as a Lein plugin. * `"-Dorchard.initialize-cache.silent=true"` (default: `true`) - * if `false`, the _class info cache_ initialization may print warnings (possibly spurious ones).xx + * if `false`, the _class info cache_ initialization may print warnings (possibly spurious ones). ## History diff --git a/dev/user.clj b/dev/user.clj index f5cad740..6f4d225c 100644 --- a/dev/user.clj +++ b/dev/user.clj @@ -7,7 +7,7 @@ [clojure.set :as set] [clojure.string :as string] [clojure.test :as test] - [clojure.tools.namespace.repl :refer [refresh refresh-all clear refresh-dirs set-refresh-dirs]])) + [clojure.tools.namespace.repl :refer [clear refresh refresh-all refresh-dirs set-refresh-dirs]])) (def jdk8? (->> "java.version" System/getProperty (re-find #"^1.8."))) diff --git a/project.clj b/project.clj index 167212ce..ece7c4fa 100644 --- a/project.clj +++ b/project.clj @@ -1,63 +1,5 @@ -;;;; The following code allows to add the JDK sources without `dynapath` being present. - -(require '[clojure.java.io :as io]) - -(import '[java.util.zip ZipInputStream] - '[java.io FileOutputStream]) - -(defmacro while-let [[sym expr] & body] - `(loop [~sym ~expr] - (when ~sym - ~@body - (recur ~expr)))) - -(defn jdk-find [f] - (let [home (io/file (System/getProperty "java.home")) - parent (.getParentFile home) - paths [(io/file home f) - (io/file home "lib" f) - (io/file parent f) - (io/file parent "lib" f)]] - (->> paths (filter #(.canRead ^java.io.File %)) first str))) - -(def jdk-sources - (let [java-path->zip-path (fn [path] - (some-> (io/resource path) - ^java.net.JarURLConnection (. openConnection) - (. getJarFileURL) - io/as-file - str))] - (or (java-path->zip-path "java.base/java/lang/Object.java") ; JDK9+ - (java-path->zip-path "java/lang/Object.java") ; JDK8- - (jdk-find "src.zip")))) - -(defn uncompress [path target] - (let [zis (-> target io/input-stream ZipInputStream.)] - (while-let [entry (-> zis .getNextEntry)] - (let [size (-> entry .getSize) - bytes (byte-array 1024) - dest (->> entry .getName (io/file path)) - dir (-> entry .getName (clojure.string/split #"/") butlast) - _ (->> (clojure.string/join "/" dir) (java.io.File. path) .mkdirs) - output (FileOutputStream. dest)] - (loop [len (-> zis (.read bytes))] - (when (pos? len) - (-> output (.write bytes 0 len)) - (recur (-> zis (.read bytes))))) - (-> output .close))))) - -(defn unzipped-jdk-source [] - (when-not (-> "unzipped-jdk-source" io/file .exists) - (let [choice jdk-sources] - (-> "unzipped-jdk-source" io/file .mkdirs) - ;; For some reason simply adding a .zip to the classpath doesn't work, so one has to uncompress the contents: - (uncompress "./unzipped-jdk-source/" choice))) - "unzipped-jdk-source") - (def jdk8? (->> "java.version" System/getProperty (re-find #"^1.8."))) -;;;; Project definition - (defproject cider/orchard "0.7.3" :description "A fertile ground for Clojure tooling" :url "https://github.com/clojure-emacs/orchard" @@ -65,8 +7,7 @@ :url "http://www.eclipse.org/legal/epl-v10.html"} :scm {:name "git" :url "https://github.com/clojure-emacs/orchard"} - :dependencies [[org.tcrawley/dynapath "1.1.0"] - [org.clojure/clojurescript "1.10.520"]] + :dependencies [[org.clojure/clojurescript "1.10.520"]] :exclusions [org.clojure/clojure] ; see versions matrix below :aliases {"bump-version" ["change" "version" "leiningen.release/bump-version"]} @@ -83,8 +24,7 @@ :password :env/clojars_password :sign-releases false}]] - :jvm-opts ["-Dorchard.use-dynapath=true" - "-Dclojure.main.report=stderr"] + :jvm-opts ["-Dclojure.main.report=stderr"] :source-paths ["src" "src-jdk8" "src-newer-jdks"] :test-paths ~(cond-> ["test"] @@ -110,15 +50,15 @@ :resource-paths ["test-resources" "not-a.jar" "does-not-exist.jar"] + :java-source-paths ["test-java"] ;; Initialize the cache verbosely, as usual, so that possible issues can be more easily diagnosed: :jvm-opts ["-Dorchard.initialize-cache.silent=false" - "-Dorchard.internal.test-suite-running=true"]} + "-Dorchard.internal.test-suite-running=true" + "-Dorchard.internal.has-enriched-classpath=false"]} - :no-dynapath {:jvm-opts ["-Dorchard.use-dynapath=false"] - :resource-paths [~(unzipped-jdk-source)] - :plugins ~(if jdk8? - '[[lein-jdk-tools "0.1.1"]] - [])} + :enrich-classpath {:plugins [[mx.cider/enrich-classpath "1.5.0"]] + :middleware [cider.enrich-classpath/middleware] + :jvm-opts ["-Dorchard.internal.has-enriched-classpath=true"]} ;; Development tools :dev {:dependencies [[org.clojure/tools.namespace "1.1.0"]] @@ -132,9 +72,16 @@ letfn [[:block 1] [:inner 2]]}}} :clj-kondo [:test - {:dependencies [[clj-kondo "2021.10.19"]]}] - - :eastwood {:plugins [[jonase/eastwood "0.9.9"]] - :eastwood {:exclude-namespaces [~(if jdk8? - 'orchard.java.parser - 'orchard.java.legacy-parser)]}}}) + {:dependencies [[clj-kondo "2021.12.01"]]}] + + :eastwood {:plugins [[jonase/eastwood "1.0.0"]] + :eastwood {:exclude-namespaces ~(cond-> [] + jdk8? + (conj 'orchard.java.parser) + + (or (not jdk8?) + (not (-> "TEST_PROFILES" + System/getenv + (doto assert) + (.contains "enrich-classpath")))) + (conj 'orchard.java.legacy-parser))}}}) diff --git a/src-newer-jdks/orchard/java/parser.clj b/src-newer-jdks/orchard/java/parser.clj index 58a3baca..933dd9fd 100644 --- a/src-newer-jdks/orchard/java/parser.clj +++ b/src-newer-jdks/orchard/java/parser.clj @@ -7,8 +7,7 @@ [clojure.string :as str]) (:import (java.io StringReader StringWriter) - (javax.lang.model.element Element ElementKind ExecutableElement - TypeElement VariableElement) + (javax.lang.model.element Element ElementKind ExecutableElement TypeElement VariableElement) (javax.swing.text.html HTML$Tag HTMLEditorKit$ParserCallback) (javax.swing.text.html.parser ParserDelegator) (javax.tools ToolProvider) @@ -69,9 +68,15 @@ sources (-> (.getStandardFileManager compiler nil nil nil) (.getJavaFileObjectsFromFiles [tmpfile])) doclet (class (reify Doclet - (init [this _ _] (reset! result nil)) - (run [this root] (reset! result root) true) - (getSupportedOptions [this] #{}))) + (init [_this _ _] + (reset! result nil)) + + (run [_this root] + (reset! result root) + true) + + (getSupportedOptions [_this] + #{}))) out (StringWriter.) ; discard compiler messages opts (apply conj ["--show-members" "private" "--show-types" "private" diff --git a/src/orchard/apropos.clj b/src/orchard/apropos.clj index 3785c385..ec20056b 100644 --- a/src/orchard/apropos.clj +++ b/src/orchard/apropos.clj @@ -3,8 +3,8 @@ {:author "Jeff Valk"} (:require [orchard.meta :refer [var-name var-doc] :as m] - [orchard.query :as query] - [orchard.misc :as misc]) + [orchard.misc :as misc] + [orchard.query :as query]) (:import [clojure.lang MultiFn])) diff --git a/src/orchard/java.clj b/src/orchard/java.clj index 22061b35..998831e4 100644 --- a/src/orchard/java.clj +++ b/src/orchard/java.clj @@ -6,14 +6,12 @@ [clojure.java.javadoc :as javadoc] [clojure.reflect :as r] [clojure.string :as str] - [orchard.java.classpath :as cp] - [orchard.misc :as misc] [orchard.java.resource :as resource] + [orchard.misc :as misc] [orchard.util.io :as util.io]) (:import (clojure.lang IPersistentMap) (clojure.reflect Constructor Field JavaReflector Method) - (java.io File) (java.net JarURLConnection))) ;;; ## Java Class/Member Info @@ -44,58 +42,27 @@ ;; functions that modify the classpath. You can find a more extensive discussion ;; on that topic here https://github.com/clojure-emacs/orchard/issues/103. -(defn jdk-find +(defn ^:deprecated jdk-find "Search common JDK path configurations for a specified file name and return a URL if found. This accommodates `java.home` being set to either the JDK root (JDK9+) or a JRE directory within this (JDK 8), and searches both the home and `lib` directories." - [f] - (let [home (io/file (System/getProperty "java.home")) - parent (.getParentFile home) - paths [(io/file home f) - (io/file home "lib" f) - (io/file parent f) - (io/file parent "lib" f)]] - (->> paths (filter #(.canRead ^File %)) first io/as-url))) - -(def add-java-sources-via-dynapath? - "Should orchard use the dynapath library to use \"fetch Java sources\" functionality? - - Note that using dynapath currently implies some bugs, so you might want to disable this option." - (contains? #{"true" "1"} (System/getProperty "orchard.use-dynapath" "true"))) - -(def jdk-sources - "The JDK sources path. If found on the existing classpath, this is the - corresponding classpath entry. Otherwise, the JDK directory is searched for - the file `src.zip`." - (when add-java-sources-via-dynapath? - (let [base-url (fn [path] - (some-> (io/resource path) - ^JarURLConnection (. openConnection) - (. getJarFileURL)))] - (or (base-url "java.base/java/lang/Object.java") ; JDK9+ - (base-url "java/lang/Object.java") ; JDK8- - (jdk-find "src.zip"))))) + [_] + nil) + +(def ^:deprecated jdk-sources nil) (def jdk-tools "The `tools.jar` path, for JDK8 and earlier. If found on the existing classpath, this is the corresponding classpath entry. Otherwise, if available, this is added to the classpath." (when (<= misc/java-api-version 8) - (or (some-> (io/resource "com/sun/javadoc/Doc.class") - ^JarURLConnection (. openConnection) - (. getJarFileURL)) - (and add-java-sources-via-dynapath? - (some-> (jdk-find "tools.jar") cp/add-classpath!))))) - -(defn ensure-jdk-sources - "If `jdk-sources` is present, check that this entry is on the context - classpath and if not, add it." - [] - (when add-java-sources-via-dynapath? - (let [classpath (set (cp/classpath))] - (when (and jdk-sources (not (classpath jdk-sources))) - (cp/add-classpath! jdk-sources))))) + (some-> (io/resource "com/sun/javadoc/Doc.class") + ^JarURLConnection (. openConnection) + (. getJarFileURL)))) + +(defn ^:deprecated ensure-jdk-sources + []) ;;; ## Javadoc URLs ;; @@ -141,16 +108,16 @@ (if (>= misc/java-api-version 9) (do (require '[orchard.java.parser :as src]) (resolve 'src/source-info)) - (if jdk-tools - (do (require '[orchard.java.legacy-parser :as src]) - (resolve 'src/source-info)) - (constantly nil)))) + (if-not jdk-tools + (constantly nil) + (do + (require '[orchard.java.legacy-parser :as src]) + (resolve 'src/source-info))))) (defn source-info "Ensure that JDK sources are visible on the classpath if present, and return class info from its parsed source if available." [class] - (ensure-jdk-sources) (source-info* class)) (def module-name diff --git a/src/orchard/java/classpath.clj b/src/orchard/java/classpath.clj index 6e76ad73..420b7463 100644 --- a/src/orchard/java/classpath.clj +++ b/src/orchard/java/classpath.clj @@ -7,13 +7,12 @@ (:require [clojure.java.io :as io] [clojure.string :as str] - [dynapath.util :as dp] [orchard.misc :as misc]) (:import (java.io File) - (java.net URL) + (java.net URL URLClassLoader) (java.nio.file Paths) - (java.util.jar JarFile JarEntry))) + (java.util.jar JarEntry JarFile))) ;;; Classloaders @@ -31,14 +30,9 @@ ([] (classloaders (context-classloader)))) -(defn modifiable-classloader - "Returns the highest classloader in the hierarchy that satisfies - `dynapath.util/addable-classpath?`, or nil if none do." - ([^ClassLoader loader] - (last (filter dp/addable-classpath? - (classloaders loader)))) - ([] - (modifiable-classloader (context-classloader)))) +(defn ^:deprecated modifiable-classloader + ([_]) + ([])) (defn set-classloader! "Sets the current classloader for the current thread." @@ -56,26 +50,26 @@ (.split (System/getProperty "java.class.path") (System/getProperty "path.separator")))) +(defn classpath-urls [classloader] + (if-not (instance? URLClassLoader classloader) + nil + (-> ^URLClassLoader classloader .getURLs seq))) + (defn classpath "Returns the URLs on the classpath." ([^ClassLoader loader] (->> (classloaders loader) - (mapcat dp/classpath-urls) + (mapcat classpath-urls) (concat (system-classpath)) (distinct))) ([] (classpath (context-classloader)))) -(defn add-classpath! +(defn ^:deprecated add-classpath! "Adds the URL to the classpath and returns it if successful, or nil otherwise, ensuring that a modifiable classloader is available." - [^URL url] - (let [loader (or (modifiable-classloader) - (modifiable-classloader - (set-classloader! (clojure.lang.DynamicClassLoader. - (clojure.lang.RT/baseLoader)))))] - (when (dp/add-classpath-url loader url) - url))) + [_] + nil) ;;; Classpath resources diff --git a/src/orchard/meta.clj b/src/orchard/meta.clj index 474e2f1d..0c63939d 100644 --- a/src/orchard/meta.clj +++ b/src/orchard/meta.clj @@ -6,8 +6,8 @@ [clojure.string :as str] [clojure.walk :as walk] [orchard.clojuredocs :as cljdocs] - [orchard.namespace :as ns] [orchard.misc :as misc] + [orchard.namespace :as ns] [orchard.spec :as spec]) (:import [clojure.lang LineNumberingPushbackReader])) diff --git a/test/orchard/java/DummyClass.java b/test-java/orchard/java/DummyClass.java similarity index 100% rename from test/orchard/java/DummyClass.java rename to test-java/orchard/java/DummyClass.java diff --git a/test-newer-jdks/orchard/java/parser_test.clj b/test-newer-jdks/orchard/java/parser_test.clj index f6be8d99..ff07bbf4 100644 --- a/test-newer-jdks/orchard/java/parser_test.clj +++ b/test-newer-jdks/orchard/java/parser_test.clj @@ -1,66 +1,50 @@ (ns orchard.java.parser-test (:require + [clojure.test :refer [deftest is testing]] [orchard.java.parser :as parser] - [clojure.test :refer [deftest is testing]])) + [orchard.test.util :as util]) + (:import + (orchard.java DummyClass))) -(defn compile-class-from-source - "Compile a java file on the classpath. - Returns true if all went well." - [classname] - (let [compiler (javax.tools.ToolProvider/getSystemJavaCompiler)] - (.. compiler - (getTask - nil ;; out - nil ;; fileManager - nil ;; diagnosticListener - nil ;; compilerOptions - nil ;; classnames for annotation processing - ;; compilationUnits - [(.. compiler - (getStandardFileManager nil nil nil) - (getJavaFileForInput javax.tools.StandardLocation/CLASS_PATH - classname - javax.tools.JavaFileObject$Kind/SOURCE))]) - call))) +(when util/has-enriched-classpath? + (deftest source-info-test + (is (class? DummyClass)) -(deftest source-info-test - (is (compile-class-from-source "orchard.java.DummyClass")) + (testing "file on the filesystem" + (is (= {:class 'orchard.java.DummyClass, + :members + '{orchard.java.DummyClass + {[] + {:name orchard.java.DummyClass, + :type void, + :argtypes [], + :argnames [], + :doc nil, + :line 12, + :column 8}}, + dummyMethod + {[] + {:name dummyMethod, + :type java.lang.String, + :argtypes [], + :argnames [], + :doc "Method-level docstring. @returns the string \"hello\"", + :line 18, + :column 3}}}, + :doc + "Class level docstring.\n\n```\n DummyClass dc = new DummyClass();\n```\n\n@author Arne Brasseur", + :line 12, + :column 1, + :file "orchard/java/DummyClass.java" + :resource-url (java.net.URL. (str "file:" + (System/getProperty "user.dir") + "/test-java/orchard/java/DummyClass.java"))} + (dissoc (parser/source-info 'orchard.java.DummyClass) + :path)))) - (testing "file on the filesystem" - (is (= {:class 'orchard.java.DummyClass, - :members - '{orchard.java.DummyClass - {[] - {:name orchard.java.DummyClass, - :type void, - :argtypes [], - :argnames [], - :doc nil, - :line 12, - :column 8}}, - dummyMethod - {[] - {:name dummyMethod, - :type java.lang.String, - :argtypes [], - :argnames [], - :doc "Method-level docstring. @returns the string \"hello\"", - :line 18, - :column 3}}}, - :doc - "Class level docstring.\n\n```\n DummyClass dc = new DummyClass();\n```\n\n@author Arne Brasseur", - :line 12, - :column 1, - :file "orchard/java/DummyClass.java" - :resource-url (java.net.URL. (str "file:" - (System/getProperty "user.dir") - "/test/orchard/java/DummyClass.java"))} - (dissoc (parser/source-info 'orchard.java.DummyClass) - :path)))) - - (testing "java file in a jar" - (let [rt-info (parser/source-info 'clojure.lang.RT)] - (is (= {:file "clojure/lang/RT.java"} - (select-keys rt-info [:file]))) - (is (re-find #"jar:file:/.*/.m2/repository/org/clojure/clojure/.*/clojure-.*-sources.jar!/clojure/lang/RT.java" - (str (:resource-url rt-info))))))) + (testing "java file in a jar" + (let [rt-info (parser/source-info 'clojure.lang.RT)] + (is (= {:file "clojure/lang/RT.java"} + (select-keys rt-info [:file]))) + (is (re-find #"jar:file:/.*/.m2/repository/org/clojure/clojure/.*/clojure-.*-sources.jar!/clojure/lang/RT.java" + (str (:resource-url rt-info)))))))) diff --git a/test/orchard/info_test.clj b/test/orchard/info_test.clj index 17712f81..5cc1f523 100644 --- a/test/orchard/info_test.clj +++ b/test/orchard/info_test.clj @@ -7,7 +7,8 @@ [orchard.info :as info] [orchard.java :as java] [orchard.misc :as misc] - [orchard.test-ns])) + [orchard.test-ns] + [orchard.test.util :as util])) @java/cache-initializer ;; make tests more deterministic @@ -458,18 +459,19 @@ (deftest info-java-test (is (info/info-java 'clojure.lang.Atom 'swap))) -(deftest info-java-member-precendence-test - (testing "Integer/max - issue #86" - (let [i (info/info* {:ns 'user :sym 'Integer/max})] - (is (= (select-keys i [:class :member :modifiers :throws :argtypes :arglists :returns]) - '{:throws () - :argtypes [int int] - :member max - :modifiers #{:public :static} - :class java.lang.Integer - :arglists ([a b]) - :returns int})) - (is (re-find #"Returns the greater of two" (:doc i)))))) +(when util/has-enriched-classpath? + (deftest info-java-member-precedence-test + (testing "Integer/max - issue #86" + (let [i (info/info* {:ns 'user :sym 'Integer/max})] + (is (= (select-keys i [:class :member :modifiers :throws :argtypes :arglists :returns]) + '{:throws () + :argtypes [int int] + :member max + :modifiers #{:public :static} + :class java.lang.Integer + :arglists ([a b]) + :returns int})) + (is (re-find #"Returns the greater of two" (:doc i))))))) (def some-var nil) diff --git a/test/orchard/java/classpath_test.clj b/test/orchard/java/classpath_test.clj index ac8a6649..7f8b4e3f 100644 --- a/test/orchard/java/classpath_test.clj +++ b/test/orchard/java/classpath_test.clj @@ -56,63 +56,44 @@ (set (cp/system-classpath)) (set (cp/classpath))))))) -(when orchard.java/add-java-sources-via-dynapath? - (deftest classpath-resources-test - (testing "Iterating classpath resources" - (testing "returns non-empty lists" - ;; The non-existing .jar can get misteriously created (is it Lein?). - ;; Work around it: - (-> "does-not-exist.jar" File. .delete) +(deftest classpath-resources-test + (testing "Iterating classpath resources" + (testing "returns non-empty lists" + ;; The non-existing .jar can get misteriously created (is it Lein?). + ;; Work around it: + (-> "does-not-exist.jar" File. .delete) - (let [dev-resources-path (-> "dev-resources" File. .getAbsolutePath) - the-classpath (cp/classpath) - corpus (->> the-classpath - (filter (fn [^URL u] - (let [f (-> u io/as-file)] - ;; filter out intentionally non-existing files - ;; (which we put in the :test classpath for reproducing certain bug) - (and (-> f .exists) - (not (= (.getAbsolutePath f) - ;; remove dev-resources, only present in the :dev profile: - dev-resources-path))))))) - ^File non-existing-jar (->> the-classpath - (filter (fn [u] - ;; Find the non-existing jar declared under the :test profile: - (-> u io/as-file str (.contains "does-not-exist.jar")))) - first)] - (assert (seq corpus) - "There's something to test") - (assert non-existing-jar - "The classpath includes the non-existing jar") - (testing "Orchard will succeed even in presence of an entry in the classpath that refers to a non-existing.jar" - (is (not (-> non-existing-jar io/as-file .exists)) - (pr-str non-existing-jar))) - (doseq [item corpus - :let [entry (cp/classpath-seq item)]] - (is (seq entry) - (pr-str [item entry]))))) - (testing "returns relative paths" - (doseq [^String entry (mapcat cp/classpath-seq (cp/classpath))] - (is (not (-> entry File. .isAbsolute)))))))) + (let [dev-resources-path (-> "dev-resources" File. .getAbsolutePath) + the-classpath (cp/classpath) + corpus (->> the-classpath + (filter (fn [^URL u] + (let [f (-> u io/as-file)] + ;; filter out intentionally non-existing files + ;; (which we put in the :test classpath for reproducing certain bug) + (and (-> f .exists) + (not (= (.getAbsolutePath f) + ;; remove dev-resources, only present in the :dev profile: + dev-resources-path))))))) + ^File non-existing-jar (->> the-classpath + (filter (fn [u] + ;; Find the non-existing jar declared under the :test profile: + (-> u io/as-file str (.contains "does-not-exist.jar")))) + first)] + (assert (seq corpus) + "There's something to test") + (assert non-existing-jar + "The classpath includes the non-existing jar") + (testing "Orchard will succeed even in presence of an entry in the classpath that refers to a non-existing.jar" + (is (not (-> non-existing-jar io/as-file .exists)) + (pr-str non-existing-jar))) + (doseq [item corpus + :let [entry (cp/classpath-seq item)]] + (is (seq entry) + (pr-str [item entry]))))) + (testing "returns relative paths" + (doseq [^String entry (mapcat cp/classpath-seq (cp/classpath))] + (is (not (-> entry File. .isAbsolute))))))) -(when orchard.java/add-java-sources-via-dynapath? - (deftest classloader-test - (testing "Classloader hierarchy contains current classloader" - (is (contains? (set (cp/classloaders)) (cp/context-classloader)))) - (testing "Classpath modification" - (let [orig-classloaders (cp/classloaders) - orig-classpath (cp/classpath) - url (-> (System/getProperty "java.io.tmpdir") - (io/file "test.txt") - (io/as-url))] - (cp/add-classpath! url) - (testing "adds the URL" - (is (contains? (set (cp/classpath)) url))) - (testing "preserves prior classpath URLs" - (is (set/subset? - (set orig-classpath) - (set (cp/classpath))))) - (testing "preserves the classloader hierarchy" - (is (set/subset? - (set orig-classloaders) - (set (cp/classloaders))))))))) +(deftest classloader-test + (testing "Classloader hierarchy contains current classloader" + (is (contains? (set (cp/classloaders)) (cp/context-classloader))))) diff --git a/test/orchard/java/classpath_test/third_party_compat_test.clj b/test/orchard/java/classpath_test/third_party_compat_test.clj index 5044df6b..910daf7e 100644 --- a/test/orchard/java/classpath_test/third_party_compat_test.clj +++ b/test/orchard/java/classpath_test/third_party_compat_test.clj @@ -1,13 +1,12 @@ (ns orchard.java.classpath-test.third-party-compat-test (:require - [orchard.java] [clojure.java.classpath] - [clojure.test :refer [deftest is]])) + [clojure.test :refer [deftest is]] + [orchard.java])) ;; make this namespace's tests deterministic: @orchard.java/cache-initializer -(when-not orchard.java/add-java-sources-via-dynapath? - (deftest works - (is (seq (clojure.java.classpath/classpath-directories)) - "The presence of `clojure.java` does not affect third-party libraries"))) +(deftest works + (is (seq (clojure.java.classpath/classpath-directories)) + "The presence of `clojure.java` does not affect third-party libraries")) diff --git a/test/orchard/java_test.clj b/test/orchard/java_test.clj index f94d7a1c..63b31e82 100644 --- a/test/orchard/java_test.clj +++ b/test/orchard/java_test.clj @@ -2,50 +2,26 @@ (:require [clojure.java.io :as io] [clojure.java.javadoc :as javadoc] - [clojure.test :refer [deftest is are testing]] - [dynapath.util :as dp] - [orchard.java :refer [cache class-info class-info* javadoc-url jdk-find jdk-sources jdk-tools member-info resolve-class resolve-javadoc-path resolve-member resolve-symbol resolve-type source-info]] - [orchard.misc :as misc])) + [clojure.test :refer [are deftest is testing]] + [orchard.java :refer [cache class-info class-info* javadoc-url jdk-tools member-info resolve-class resolve-javadoc-path resolve-member resolve-symbol resolve-type source-info]] + [orchard.misc :as misc] + [orchard.test.util :as util])) (def jdk-parser? (or (>= misc/java-api-version 9) jdk-tools)) -(assert jdk-parser? "No JDK parser available!") -(assert (if orchard.java/add-java-sources-via-dynapath? - jdk-sources - true) - "No JDK sources available!") - (javadoc/add-remote-javadoc "com.amazonaws." "http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/") (javadoc/add-remote-javadoc "org.apache.kafka." "https://kafka.apache.org/090/javadoc/") -(deftest resources-test - ;; If the JDK resources we wish to load dynamically are present on the file - ;; system, test that we've resolved them and added them to the classpath. - (testing "Resource loading correctness" - (let [jdk-sources-path (jdk-find "src.zip") - jdk-tools-path (jdk-find "tools.jar") - classpath-urls (-> (.getContextClassLoader (Thread/currentThread)) - (dp/all-classpath-urls) - (set))] - (testing "of defined vars" - (when orchard.java/add-java-sources-via-dynapath? - (is (= jdk-sources jdk-sources-path))) - (is (= jdk-tools jdk-tools-path))) - (testing "of dynamically added classpath entries" - (is (= jdk-sources (classpath-urls jdk-sources))) - (is (= jdk-tools (classpath-urls jdk-tools))))))) - -(deftest source-info-test - (let [resolve-src (comp (fnil io/resource "-none-") :file source-info)] - (when jdk-parser? +(when util/has-enriched-classpath? + (deftest source-info-test + (let [resolve-src (comp (fnil io/resource "-none-") :file source-info)] (testing "Source file resolution" (testing "for Clojure classes" (is (resolve-src 'clojure.lang.Obj)) (is (resolve-src 'clojure.lang.Fn))) - (when jdk-sources - (testing "for JDK classes" - (is (resolve-src 'java.lang.String)) - (is (resolve-src 'java.util.regex.Matcher)))) + (testing "for JDK classes" + (is (resolve-src 'java.lang.String)) + (is (resolve-src 'java.util.regex.Matcher))) (testing "for non-existent classes" (is (not (resolve-src 'not.actually.AClass))))) @@ -57,107 +33,104 @@ (is (-> (source-info 'clojure.lang.Numbers$Ops) :line)) ; nested default interface (is (-> (source-info 'clojure.lang.Range$BoundsCheck) :line)) ; nested private interface (is (-> (source-info 'clojure.lang.Numbers$Category) :line))) ; nested enum - (when jdk-sources - (testing "for JDK classes" - (is (-> (source-info 'java.util.Collection) :line)) ; interface - (is (-> (source-info 'java.util.AbstractCollection) :line)) ; abstract class - (is (-> (source-info 'java.lang.Thread$UncaughtExceptionHandler) :line)) ; nested interface - (is (-> (source-info 'java.net.Authenticator$RequestorType) :line)) ; nested enum - (is (-> (source-info 'java.sql.ClientInfoStatus) :line))))) ; top-level enum + (testing "for JDK classes" + (is (-> (source-info 'java.util.Collection) :line)) ; interface + (is (-> (source-info 'java.util.AbstractCollection) :line)) ; abstract class + (is (-> (source-info 'java.lang.Thread$UncaughtExceptionHandler) :line)) ; nested interface + (is (-> (source-info 'java.net.Authenticator$RequestorType) :line)) ; nested enum + (is (-> (source-info 'java.sql.ClientInfoStatus) :line)))) ; top-level enum (testing "Source parsing" (testing "for Clojure classes" (is (-> (source-info 'clojure.lang.ExceptionInfo) :doc)) - (is (-> (get-in (source-info 'clojure.lang.BigInt) - [:members 'multiply]) - first val :line))) - (when jdk-sources - (testing "for JDK classes" - (is (-> (source-info 'java.util.AbstractCollection) :doc)) - (is (-> (get-in (source-info 'java.util.AbstractCollection) - [:members 'size]) - first val :line)))))))) + (is (some-> (get-in (source-info 'clojure.lang.BigInt) + [:members 'multiply]) + first val :line))) + (testing "for JDK classes" + (is (-> (source-info 'java.util.AbstractCollection) :doc)) + (is (some-> (get-in (source-info 'java.util.AbstractCollection) + [:members 'size]) + first val :line))))))) (deftest map-structure-test - (when jdk-parser? - (testing "Parsed map structure = reflected map structure" - (let [cols #{:file :line :column :doc :argnames :argtypes :path :resource-url} - keys= #(= (set (keys (apply dissoc %1 cols))) - (set (keys %2))) - c1 (class-info* 'clojure.lang.Compiler) - c2 (with-redefs [source-info (constantly nil)] - (class-info* 'clojure.lang.Compiler))] - ;; Class info - (is (keys= c1 c2) - (str "Difference: " - (pr-str [(remove (set (keys c1)) (keys c2)) - (remove (set (keys c2)) (keys c1))]))) - ;; Members - (is (keys (:members c1))) - (is (= (keys (:members c1)) - (keys (:members c2)))) - ;; Member info - (is (->> (map keys= - (vals (:members c1)) - (vals (:members c2))) - (every? true?))))))) - -(deftest class-info-test - (let [c1 (class-info 'clojure.lang.Agent) - c2 (class-info 'clojure.lang.Range$BoundsCheck) - c3 (class-info 'not.actually.AClass)] - (testing "Class" - (when jdk-parser? + (testing "Parsed map structure = reflected map structure" + (let [cols #{:file :line :column :doc :argnames :argtypes :path :resource-url} + keys= #(= (set (keys (apply dissoc %1 cols))) + (set (keys %2))) + c1 (class-info* 'clojure.lang.Compiler) + c2 (with-redefs [source-info (constantly nil)] + (class-info* 'clojure.lang.Compiler))] + ;; Class info + (is (keys= c1 c2) + (str "Difference: " + (pr-str [(remove (set (keys c1)) (keys c2)) + (remove (set (keys c2)) (keys c1))]))) + ;; Members + (is (keys (:members c1))) + (is (= (keys (:members c1)) + (keys (:members c2)))) + ;; Member info + (is (->> (map keys= + (vals (:members c1)) + (vals (:members c2))) + (every? true?)))))) + +(when util/has-enriched-classpath? + (deftest class-info-test + (let [c1 (class-info 'clojure.lang.Agent) + c2 (class-info 'clojure.lang.Range$BoundsCheck) + c3 (class-info 'not.actually.AClass)] + (testing "Class" (testing "source file" (is (string? (:file c1))) (is (io/resource (:file c1)))) (testing "source file for nested class" (is (string? (:file c2))) - (is (io/resource (:file c2))))) - (testing "member info" - (is (map? (:members c1))) - (is (every? map? (vals (:members c1)))) - (is (apply (every-pred :name :modifiers) - (mapcat vals (vals (:members c1)))))) - (testing "doesn't throw on classes without dots in classname" - (let [reified (binding [*ns* (create-ns 'foo)] - (clojure.core/eval - '(clojure.core/reify Object))) - sym (symbol (.getName (class reified)))] - (is (class-info sym)))) - (testing "that doesn't exist" - (is (nil? c3)))))) - -(deftest member-info-test - (let [m1 (member-info 'clojure.lang.PersistentHashMap 'assoc) - m2 (member-info 'java.util.AbstractCollection 'non-existent-member) - m3 (member-info 'not.actually.AClass 'nada) - m4 (member-info 'java.awt.Point 'x) - m5 (member-info 'java.lang.Class 'forName) - m6 (member-info 'java.util.AbstractMap 'finalize) - m7 (member-info 'java.util.HashMap 'finalize)] - (testing "Member" - (when jdk-parser? + (is (io/resource (:file c2)))) + (testing "member info" + (is (map? (:members c1))) + (is (every? map? (vals (:members c1)))) + (is (apply (every-pred :name :modifiers) + (mapcat vals (vals (:members c1)))))) + (testing "doesn't throw on classes without dots in classname" + (let [reified (binding [*ns* (create-ns 'foo)] + (clojure.core/eval + '(clojure.core/reify Object))) + sym (symbol (.getName (class reified)))] + (is (class-info sym)))) + (testing "that doesn't exist" + (is (nil? c3))))))) + +(when util/has-enriched-classpath? + (deftest member-info-test + (let [m1 (member-info 'clojure.lang.PersistentHashMap 'assoc) + m2 (member-info 'java.util.AbstractCollection 'non-existent-member) + m3 (member-info 'not.actually.AClass 'nada) + m4 (member-info 'java.awt.Point 'x) + m5 (member-info 'java.lang.Class 'forName) + m6 (member-info 'java.util.AbstractMap 'finalize) + m7 (member-info 'java.util.HashMap 'finalize)] + (testing "Member" (testing "source file" (is (string? (:file m1))) (is (io/resource (:file m1)))) (testing "line number" - (is (number? (:line m1))))) - (testing "arglists" - (is (seq? (:arglists m1))) - (is (every? vector? (:arglists m1)))) - (testing "that doesn't exist" - (is (nil? m2))) - (testing "in a class that doesn't exist" - (is (nil? m3))) - (testing "that is a field" - (is m4)) - (testing "that is static" - (is m5)) - (testing "implemented on immediate superclass" - (is (not= 'java.lang.Object (:class m6)))) - (testing "implemented on ancestor superclass" - (is (not= 'java.lang.Object (:class m7))))))) + (is (number? (:line m1)))) + (testing "arglists" + (is (seq? (:arglists m1))) + (is (every? vector? (:arglists m1)))) + (testing "that doesn't exist" + (is (nil? m2))) + (testing "in a class that doesn't exist" + (is (nil? m3))) + (testing "that is a field" + (is m4)) + (testing "that is static" + (is m5)) + (testing "implemented on immediate superclass" + (is (not= 'java.lang.Object (:class m6)))) + (testing "implemented on ancestor superclass" + (is (not= 'java.lang.Object (:class m7)))))))) (deftest arglists-test (let [+this (comp #{'this} first)] diff --git a/test/orchard/test/util.clj b/test/orchard/test/util.clj new file mode 100644 index 00000000..ddb2d134 --- /dev/null +++ b/test/orchard/test/util.clj @@ -0,0 +1,6 @@ +(ns orchard.test.util) + +(def has-enriched-classpath? + (let [v (System/getProperty "orchard.internal.has-enriched-classpath")] + (assert (#{"true" "false"} v)) + (= "true" v)))