diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 44e909a9..2e95cdd0 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -2,6 +2,22 @@ All notable changes to this project will be documented in this file. This change == Unreleased (dev) +== 0.11.0 (2021-02-06) + +// {{{ +=== Added + +* Added support to display diff URLs for outdated dependencies. + +=== Changed + +* Changed default error message for `format` reporter to add diff URLs. + +=== Fixed + +* Fixed to return actual tag name for outdated GitHub Actions. +// }}} + == 0.10.3 (2021-02-01) // {{{ === Changed diff --git a/README.adoc b/README.adoc index 9f13af3c..4cedee62 100644 --- a/README.adoc +++ b/README.adoc @@ -140,6 +140,9 @@ You can use following variables: | `{{latest-version}}` | The latest version. +| `{{diff-url}}` +| The diff URL for Version Control System. (Nullable) + | `{{message}}` | Default error message. diff --git a/pom.xml b/pom.xml index db06d1a4..8c4cabe4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 antq antq - 0.10.3 + 0.11.0 antq Point out your outdated dependencies https://github.com/liquidz/antq diff --git a/src/antq/core.clj b/src/antq/core.clj index 9621b8be..d9c547c9 100644 --- a/src/antq/core.clj +++ b/src/antq/core.clj @@ -14,6 +14,11 @@ [antq.dep.leiningen :as dep.lein] [antq.dep.pom :as dep.pom] [antq.dep.shadow :as dep.shadow] + [antq.diff :as diff] + [antq.diff.git-sha] + [antq.diff.github-tag] + [antq.diff.java] + [antq.log :as log] [antq.record :as r] [antq.report :as report] [antq.report.edn] @@ -100,7 +105,7 @@ :name dep-name}) (ver/get-sorted-versions) (first) - (println)))) + (log/info)))) (defn- assoc-latest-version [dep] @@ -146,6 +151,12 @@ assoc-latest-version)) (remove ver/latest?)))) +(defn assoc-diff-url + [version-checked-dep] + (if-let [url (diff/get-diff-url version-checked-dep)] + (assoc version-checked-dep :diff-url url) + version-checked-dep)) + (defn exit [outdated-deps] (System/exit (if (seq outdated-deps) 1 0))) @@ -184,7 +195,8 @@ deps (fetch-deps options) deps (unify-org-clojure-deps deps)] (if (seq deps) - (let [outdated (outdated-deps deps options)] + (let [outdated (outdated-deps deps options) + outdated (map assoc-diff-url outdated)] (report/reporter outdated options) (cond-> outdated @@ -193,5 +205,5 @@ true (exit))) - (do (println "No project file") + (do (log/info "No project file") (System/exit 1))))) diff --git a/src/antq/dep/github_action.clj b/src/antq/dep/github_action.clj index c1b21f54..04516524 100644 --- a/src/antq/dep/github_action.clj +++ b/src/antq/dep/github_action.clj @@ -2,7 +2,6 @@ (:require [antq.record :as r] [antq.util.dep :as u.dep] - [antq.util.ver :as u.ver] [clj-yaml.core :as yaml] [clojure.java.io :as io] [clojure.string :as str] @@ -28,7 +27,7 @@ :version version :extra {:url (name->url name)}} {:type :github-tag - :version (u.ver/normalize-version version)})) + :version version})) (defn extract-deps [file-path workflow-content-str] diff --git a/src/antq/diff.clj b/src/antq/diff.clj new file mode 100644 index 00000000..a3a82ce5 --- /dev/null +++ b/src/antq/diff.clj @@ -0,0 +1,9 @@ +(ns antq.diff) + +(defmulti get-diff-url + (fn [version-checked-dep] + (:type version-checked-dep))) + +(defmethod get-diff-url :default + [_dep] + nil) diff --git a/src/antq/diff/git_sha.clj b/src/antq/diff/git_sha.clj new file mode 100644 index 00000000..89b7f6bd --- /dev/null +++ b/src/antq/diff/git_sha.clj @@ -0,0 +1,19 @@ +(ns antq.diff.git-sha + (:require + [antq.diff :as diff] + [antq.log :as log] + [antq.util.url :as u.url] + [clojure.string :as str])) + +(defmethod diff/get-diff-url :git-sha + [dep] + (when-let [url (get-in dep [:extra :url])] + (cond + (str/starts-with? url "https://github.com/") + (format "%scompare/%s...%s" + (u.url/ensure-tail-slash url) + (:version dep) + (:latest-version dep)) + + :else + (log/error (str "Diff is not supported for " url))))) diff --git a/src/antq/diff/github_tag.clj b/src/antq/diff/github_tag.clj new file mode 100644 index 00000000..4e5cf230 --- /dev/null +++ b/src/antq/diff/github_tag.clj @@ -0,0 +1,11 @@ +(ns antq.diff.github-tag + (:require + [antq.diff :as diff] + [clojure.string :as str])) + +(defmethod diff/get-diff-url :github-tag + [dep] + (format "https://github.com/%s/compare/%s...%s" + (str/join "/" (take 2 (str/split (:name dep) #"/"))) + (:version dep) + (:latest-version dep))) diff --git a/src/antq/diff/java.clj b/src/antq/diff/java.clj new file mode 100644 index 00000000..c95ad500 --- /dev/null +++ b/src/antq/diff/java.clj @@ -0,0 +1,93 @@ +(ns antq.diff.java + (:require + [antq.diff :as diff] + [antq.log :as log] + [antq.util.git :as u.git] + [antq.util.maven :as u.mvn] + [antq.util.url :as u.url] + [clojure.string :as str]) + (:import + (org.eclipse.aether.resolution + ArtifactRequest))) + +(defn memoize-by + [f key-fn] + (let [mem (atom {})] + (fn [m & args] + (if-let [res (get @mem (get m key-fn))] + res + (let [ret (apply f m args)] + (swap! mem assoc (get m key-fn) ret) + ret))))) + +(defn- get-repository-url* + [{:keys [name version] :as dep}] + (try + (let [opts (u.mvn/dep->opts dep) + {:keys [system session artifact remote-repos]} (u.mvn/repository-system name version opts) + req (doto (ArtifactRequest.) + (.setArtifact artifact) + (.setRepositories remote-repos))] + (some-> (.resolveArtifact system session req) + (.getRepository) + (.getUrl))) + ;; Skip showing diff URL when fetching repository URL is failed + (catch Exception _ nil))) +(def get-repository-url (memoize-by get-repository-url* :name)) + +(defn- dep->pom-url + [dep] + (let [{:keys [version]} dep + [group-id artifact-id] (str/split (:name dep) #"/" 2) + repo-url (get-repository-url dep)] + (when repo-url + (format "%s%s/%s/%s/%s-%s.pom" + (u.url/ensure-tail-slash repo-url) + (str/replace group-id "." "/") + artifact-id + version + artifact-id + version)))) + +(defn- get-scm-url* + [dep] + (try + (when-let [model (some-> dep + (dep->pom-url) + (u.mvn/read-pom))] + (-> model + (u.mvn/get-scm) + (u.mvn/get-scm-url) + ;; fallback + (or (u.mvn/get-url model)) + ;; normalize + (u.url/ensure-https) + (u.url/ensure-git-https-url))) + + ;; Skip showing diff URL when POM file is not found + (catch java.io.FileNotFoundException _ nil))) +(def get-scm-url (memoize-by get-scm-url* :name)) + +(defmethod diff/get-diff-url :java + [dep] + (when-let [url (get-scm-url dep)] + (cond + (str/starts-with? url "https://github.com/") + (let [tags (u.git/tags-by-ls-remote url) + current (first (filter #(str/includes? % (:version dep)) tags)) + latest (or (first (filter #(str/includes? % (:latest-version dep)) tags)) + ;; If there isn't a tag for latest version + "head")] + (if current + (format "%scompare/%s...%s" + (u.url/ensure-tail-slash url) + current + latest) + (do (log/error (str "The tag for current version is not found: " url)) + ;; not diff, but URL is useful for finding the differences. + nil))) + + :else + (do (log/error (str "Diff is not supported for " url)) + ;; not diff, but URL is useful for finding the differences. + nil)))) diff --git a/src/antq/log.clj b/src/antq/log.clj new file mode 100644 index 00000000..3327da31 --- /dev/null +++ b/src/antq/log.clj @@ -0,0 +1,10 @@ +(ns antq.log) + +(defn info + [s] + (println s)) + +(defn error + [s] + (binding [*out* *err*] + (println s))) diff --git a/src/antq/record.clj b/src/antq/record.clj index 559b73e5..7f76c073 100644 --- a/src/antq/record.clj +++ b/src/antq/record.clj @@ -1,5 +1,23 @@ (ns antq.record) (defrecord Dependency - [type file name version latest-version - repositories project]) + [;; Dependency type keyword + ;; e.g. :java, :git-sha or :github-tag + type + ;; File path for project configuration file + file + ;; Dependency name + ;; e.g. "org.clojure/clojure", "medley/medley" + name + ;; Current version string + version + ;; Latest version string (Nullable) + latest-version + ;; Additional Maven repositories (Nullable) + ;; e.g. {"nexus-snapshots" {:url "http://localhost:8081/repository/maven-snapshots/"}} + repositories + ;; Project type keyword + ;; e.g. :clojure, :leiningen, :shadow-cljs and so on. + project + ;; Diff URL for Version Control System (Nullable) + diff-url]) diff --git a/src/antq/report.clj b/src/antq/report.clj index 894963d8..d70d0fb1 100644 --- a/src/antq/report.clj +++ b/src/antq/report.clj @@ -1,4 +1,6 @@ -(ns antq.report) +(ns antq.report + (:require + [antq.log :as log])) (defmulti reporter (fn [_deps options] @@ -6,4 +8,4 @@ (defmethod reporter :default [_ options] - (println "Unknown reporter:" (:reporter options))) + (log/error (str "Unknown reporter: " (:reporter options)))) diff --git a/src/antq/report/format.clj b/src/antq/report/format.clj index c65998d0..b682dfaf 100644 --- a/src/antq/report/format.clj +++ b/src/antq/report/format.clj @@ -6,7 +6,7 @@ [clojure.string :as str])) (def ^:private default-outdated-message-format - "{{name}} {{version}} is outdated. Latest version is {{latest-version}}.") + "{{name}} {{version}} is outdated. Latest version is {{latest-version}}. {{diff-url}}") (def ^:private default-failed-message-format "Failed to fetch the latest version of {{name}} {{version}}.") @@ -15,7 +15,7 @@ [dep format-string] (let [dep (-> dep (assoc :latest-version (u.ver/normalize-latest-version dep)) - (select-keys [:file :name :version :latest-version :message]))] + (select-keys [:file :name :version :latest-version :message :diff-url]))] (reduce-kv (fn [s k v] (str/replace s (str "{{" (name k) "}}") (or v ""))) format-string diff --git a/src/antq/report/table.clj b/src/antq/report/table.clj index 92687439..1f19c9c0 100644 --- a/src/antq/report/table.clj +++ b/src/antq/report/table.clj @@ -3,7 +3,8 @@ [antq.report :as report] [antq.util.dep :as u.dep] [antq.util.ver :as u.ver] - [clojure.pprint :as pprint])) + [clojure.pprint :as pprint] + [clojure.set :as set])) (defn skip-duplicated-file-name [sorted-deps] @@ -18,10 +19,23 @@ (defmethod report/reporter "table" [deps _options] + ;; Show table (if (seq deps) (->> deps (sort u.dep/compare-deps) skip-duplicated-file-name (map #(assoc % :latest-version (u.ver/normalize-latest-version %))) - (pprint/print-table [:file :name :version :latest-version])) - (println "All dependencies are up-to-date."))) + (map #(set/rename-keys % {:version :current + :latest-version :latest})) + (pprint/print-table [:file :name :current :latest])) + (println "All dependencies are up-to-date.")) + + ;; Show diff URLs + (let [urls (->> deps + (sort u.dep/compare-deps) + (keep :diff-url) + (distinct))] + (when (seq urls) + (println "\nAvailable diffs:") + (doseq [u urls] + (println "-" u))))) diff --git a/src/antq/upgrade.clj b/src/antq/upgrade.clj index 374491eb..0fa94758 100644 --- a/src/antq/upgrade.clj +++ b/src/antq/upgrade.clj @@ -1,5 +1,6 @@ (ns antq.upgrade (:require + [antq.log :as log] [antq.util.zip :as u.zip])) (defmulti upgrader @@ -8,7 +9,7 @@ (defmethod upgrader :default [dep] - (println + (log/error (format "%s: Not supported yet." (name (:project dep))))) @@ -16,7 +17,7 @@ [dep force?] (cond (not u.zip/rewrite-cljc-supported?) - (do (println "Upgrading is only supported Clojure 1.9 or later.") + (do (log/error "Upgrading is only supported Clojure 1.9 or later.") false) (and (:latest-version dep) @@ -42,16 +43,20 @@ (defn upgrade! "Return only non-upgraded deps" [version-checked-deps force?] + (when (and (seq version-checked-deps) + (not force?)) + (log/info "")) + (doall (remove (fn [dep] (if (confirm dep force?) (if-let [upgraded-content (upgrader dep)] - (do (println (format "Upgraded %s '%s' to '%s' in %s." - (:name dep) - (:version dep) - (:latest-version dep) - (:file dep))) + (do (log/info (format "Upgraded %s '%s' to '%s' in %s." + (:name dep) + (:version dep) + (:latest-version dep) + (:file dep))) (spit (:file dep) upgraded-content) true) false) diff --git a/src/antq/util/git.clj b/src/antq/util/git.clj new file mode 100644 index 00000000..56114428 --- /dev/null +++ b/src/antq/util/git.clj @@ -0,0 +1,19 @@ +(ns antq.util.git + (:require + [clojure.java.shell :as sh] + [clojure.string :as str])) + +(defn- extract-tags + [ls-remote-resp] + (->> (:out ls-remote-resp) + (str/split-lines) + (keep #(second (str/split % #"\t" 2))) + (filter #(= 0 (.indexOf ^String % "refs/tags"))) + (map #(str/replace % #"^refs/tags/" "")))) + +(defn tags-by-ls-remote* + [url] + (-> (sh/sh "git" "ls-remote" url) + (extract-tags))) +(def tags-by-ls-remote + (memoize tags-by-ls-remote*)) diff --git a/src/antq/util/maven.clj b/src/antq/util/maven.clj new file mode 100644 index 00000000..0b80befe --- /dev/null +++ b/src/antq/util/maven.clj @@ -0,0 +1,97 @@ +(ns antq.util.maven + (:require + [antq.log :as log] + [clojure.java.io :as io] + [clojure.string :as str] + [clojure.tools.deps.alpha.util.maven :as deps.util.maven] + [clojure.tools.deps.alpha.util.session :as deps.util.session]) + (:import + (org.apache.maven.model + Model + Scm) + org.apache.maven.model.io.xpp3.MavenXpp3Reader + (org.eclipse.aether + DefaultRepositorySystemSession + RepositorySystem) + (org.eclipse.aether.transfer + TransferEvent + TransferListener))) + +(def default-repos + {"central" {:url "https://repo1.maven.org/maven2/"} + "clojars" {:url "https://repo.clojars.org/"}}) + +(defn normalize-repo-url + "c.f. https://clojure.org/reference/deps_and_cli#_maven_s3_repos" + [url] + (str/replace url #"^s3p://" "s3://")) + +(defn normalize-repos + [repos] + (reduce-kv + (fn [acc k v] + (assoc acc k (if (contains? v :url) + (update v :url normalize-repo-url) + v))) + {} repos)) + +(defn snapshot? + [s] + (if s + (str/includes? (str/lower-case s) "snapshot") + false)) + +(defn dep->opts + [dep] + {:repositories (-> default-repos + (merge (:repositories dep)) + (normalize-repos)) + :snapshots? (snapshot? (:version dep))}) + +(def ^TransferListener custom-transfer-listener + "Copy from clojure.tools.deps.alpha.util.maven/console-listener + But no outputs for `transferStarted`" + (reify TransferListener + (transferStarted [_ event]) + (transferCorrupted [_ event] + (log/info "Download corrupted:" (.. ^TransferEvent event getException getMessage))) + (transferFailed [_ event] + ;; This happens when Maven can't find an artifact in a particular repo + ;; (but still may find it in a different repo), ie this is a common event + ) + (transferInitiated [_ _event]) + (transferProgressed [_ _event]) + (transferSucceeded [_ _event]))) + +(defn repository-system + [name version opts] + (let [lib (cond-> name (string? name) symbol) + local-repo deps.util.maven/default-local-repo + system ^RepositorySystem (deps.util.session/retrieve :mvn/system #(deps.util.maven/make-system)) + session ^DefaultRepositorySystemSession (deps.util.maven/make-session system local-repo) + ;; Overwrite TransferListener not to show "Downloading" messages + _ (.setTransferListener session custom-transfer-listener) + ; c.f. https://stackoverflow.com/questions/35488167/how-can-you-find-the-latest-version-of-a-maven-artifact-from-java-using-aether + artifact (deps.util.maven/coord->artifact lib {:mvn/version version}) + remote-repos (deps.util.maven/remote-repos (:repositories opts))] + {:system system + :session session + :artifact artifact + :remote-repos remote-repos})) + +(defn ^Model read-pom + [^String url] + (with-open [reader (io/reader url)] + (.read (MavenXpp3Reader.) reader))) + +(defn ^String get-url + [^Model model] + (.getUrl model)) + +(defn ^Scm get-scm + [^Model model] + (.getScm model)) + +(defn ^String get-scm-url + [^Scm scm] + (.getUrl scm)) diff --git a/src/antq/util/url.clj b/src/antq/util/url.clj new file mode 100644 index 00000000..16ee4cb6 --- /dev/null +++ b/src/antq/util/url.clj @@ -0,0 +1,25 @@ +(ns antq.util.url + (:require + [clojure.string :as str])) + +(defn ensure-tail-slash + [s] + (cond-> s + (not (str/ends-with? s "/")) (str "/"))) + +(defn ensure-git-https-url + [url] + (if-not (str/starts-with? url "git@") + (-> url + (str/replace #"\.git$" "") + (ensure-tail-slash)) + (let [[_ s] (str/split url #"@" 2) + [domain s] (str/split s #":" 2) + path (str/replace s #"\.git$" "")] + (-> (format "https://%s/%s" domain path) + (ensure-tail-slash))))) + +(defn ensure-https + [url] + (cond-> url + (str/starts-with? url "http://") (str/replace #"^http://" "https://"))) diff --git a/src/antq/ver/github_tag.clj b/src/antq/ver/github_tag.clj index 0ce81e93..ea810ec7 100644 --- a/src/antq/ver/github_tag.clj +++ b/src/antq/ver/github_tag.clj @@ -1,13 +1,12 @@ (ns antq.ver.github-tag (:require + [antq.log :as log] + [antq.util.git :as u.git] [antq.util.ver :as u.ver] [antq.ver :as ver] [cheshire.core :as json] - [clojure.java.shell :as sh] [clojure.string :as str] - [version-clj.core :as version]) - (:import - java.io.PrintWriter)) + [version-clj.core :as version])) (defonce ^:private failed-to-fetch-from-api (atom false)) @@ -17,28 +16,16 @@ (format "https://api.github.com/repos/%s/tags" (str/join "/" (take 2 (str/split (:name dep) #"/"))))) -(defn- github-ls-remote +(defn get-sorted-versions-by-ls-remote* [dep] (let [url (format "https://github.com/%s" (str/join "/" (take 2 (str/split (:name dep) #"/"))))] - (sh/sh "git" "ls-remote" url))) - -(defn- extract-tags - [ls-remote-resp] - (->> (:out ls-remote-resp) - (str/split-lines) - (keep #(second (str/split % #"\t" 2))) - (filter #(= 0 (.indexOf ^String % "refs/tags"))) - (map #(u.ver/normalize-version (str/replace % #"^refs/tags/" ""))) - (filter u.ver/sem-ver?) - (sort version/version-compare) - (reverse))) - -(defn get-sorted-versions-by-ls-remote* - [dep] - (-> dep - (github-ls-remote) - (extract-tags))) + (->> (u.git/tags-by-ls-remote url) + (filter (comp u.ver/sem-ver? u.ver/normalize-version)) + (sort (fn [& args] + (apply version/version-compare + (map u.ver/normalize-version args)))) + (reverse)))) (def get-sorted-versions-by-ls-remote (memoize get-sorted-versions-by-ls-remote*)) @@ -48,9 +35,11 @@ (-> url (slurp) (json/parse-string true) - (->> (map (comp u.ver/normalize-version :name)) - (filter u.ver/sem-ver?) - (sort version/version-compare) + (->> (map :name) + (filter (comp u.ver/sem-ver? u.ver/normalize-version)) + (sort (fn [& args] + (apply version/version-compare + (map u.ver/normalize-version args)))) (reverse)))) (def get-sorted-versions-by-url @@ -61,8 +50,8 @@ (try (get-sorted-versions-by-ls-remote dep) (catch Exception ex - (.println ^PrintWriter *err* (str "Failed to fetch versions from GitHub: " - (.getMessage ex)))))) + (log/error (str "Failed to fetch versions from GitHub: " + (.getMessage ex)))))) (defmethod ver/get-sorted-versions :github-tag [dep] @@ -74,7 +63,8 @@ (get-sorted-versions-by-url)) (catch Exception ex (reset! failed-to-fetch-from-api true) - (.println ^PrintWriter *err* (str "Failed to fetch versions from GitHub, so fallback to `git ls-remote`: " (.getMessage ex))) + (log/error (str "Failed to fetch versions from GitHub, so fallback to `git ls-remote`: " + (.getMessage ex))) (fallback-to-ls-remote dep))))) (defn- nth-newer? @@ -86,8 +76,8 @@ (defmethod ver/latest? :github-tag [dep] - (let [current (some-> dep :version version/version->seq) - latest (some-> dep :latest-version version/version->seq)] + (let [current (some-> dep :version u.ver/normalize-version version/version->seq) + latest (some-> dep :latest-version u.ver/normalize-version version/version->seq)] (when (and current latest) (case (count (first current)) 1 (nth-newer? current latest 0) diff --git a/src/antq/ver/java.clj b/src/antq/ver/java.clj index bd780b1c..525dd132 100644 --- a/src/antq/ver/java.clj +++ b/src/antq/ver/java.clj @@ -1,64 +1,15 @@ (ns antq.ver.java (:require + [antq.util.maven :as u.mvn] [antq.ver :as ver] - [clojure.string :as str] - [clojure.tools.deps.alpha.util.maven :as deps.util.maven] - [clojure.tools.deps.alpha.util.session :as deps.util.session] [version-clj.core :as version]) (:import - (org.eclipse.aether - DefaultRepositorySystemSession - RepositorySystem) (org.eclipse.aether.resolution - VersionRangeRequest) - (org.eclipse.aether.transfer - TransferEvent - TransferListener))) - -(def default-repos - {"central" {:url "https://repo1.maven.org/maven2/"} - "clojars" {:url "https://repo.clojars.org/"}}) - -(defn- normalize-repo-url - [url] - (-> url - (str/replace #"^s3p://" "s3://"))) - -(defn normalize-repos - [repos] - (reduce-kv - (fn [acc k v] - (assoc acc k (if (contains? v :url) - (update v :url normalize-repo-url) - v))) - {} repos)) - -(def ^TransferListener custom-transfer-listener - "Copy from clojure.tools.deps.alpha.util.maven/console-listener - But no outputs for `transferStarted`" - (reify TransferListener - (transferStarted [_ event]) - (transferCorrupted [_ event] - (println "Download corrupted:" (.. ^TransferEvent event getException getMessage))) - (transferFailed [_ event] - ;; This happens when Maven can't find an artifact in a particular repo - ;; (but still may find it in a different repo), ie this is a common event - ) - (transferInitiated [_ _event]) - (transferProgressed [_ _event]) - (transferSucceeded [_ _event]))) + VersionRangeRequest))) (defn get-versions [name opts] - (let [lib (cond-> name (string? name) symbol) - local-repo deps.util.maven/default-local-repo - system ^RepositorySystem (deps.util.session/retrieve :mvn/system #(deps.util.maven/make-system)) - session ^DefaultRepositorySystemSession (deps.util.maven/make-session system local-repo) - ;; Overwrite TransferListener not to show "Downloading" messages - _ (.setTransferListener session custom-transfer-listener) - ; c.f. https://stackoverflow.com/questions/35488167/how-can-you-find-the-latest-version-of-a-maven-artifact-from-java-using-aether - artifact (deps.util.maven/coord->artifact lib {:mvn/version "[0,)"}) - remote-repos (deps.util.maven/remote-repos (:repositories opts)) + (let [{:keys [system session artifact remote-repos]} (u.mvn/repository-system name "[0,)" opts) req (doto (VersionRangeRequest.) (.setArtifact artifact) (.setRepositories remote-repos))] @@ -81,7 +32,4 @@ [dep] (get-sorted-versions-by-name (:name dep) - {:repositories (-> default-repos - (merge (:repositories dep)) - (normalize-repos)) - :snapshots? (ver/snapshot? (:version dep))})) + (u.mvn/dep->opts dep))) diff --git a/test/antq/dep/github_action_test.clj b/test/antq/dep/github_action_test.clj index fc4e9625..01747d58 100644 --- a/test/antq/dep/github_action_test.clj +++ b/test/antq/dep/github_action_test.clj @@ -23,7 +23,7 @@ (slurp (io/resource "dep/github_action.yml")))] (t/is (sequential? deps)) (t/is (every? #(instance? antq.record.Dependency %) deps)) - (t/is (= #{(git-tag-dependency {:name "foo/bar" :version "1.0.0"}) + (t/is (= #{(git-tag-dependency {:name "foo/bar" :version "v1.0.0"}) (git-tag-dependency {:name "bar/baz" :version "master"}) (git-sha-dependency {:name "git/sha" :version "8be09192b01d78912b03852f5d6141e8c48f4179" :extra {:url "https://github.com/git/sha.git"}}) diff --git a/test/antq/diff/git_sha_test.clj b/test/antq/diff/git_sha_test.clj new file mode 100644 index 00000000..85fb2da0 --- /dev/null +++ b/test/antq/diff/git_sha_test.clj @@ -0,0 +1,20 @@ +(ns antq.diff.git-sha-test + (:require + [antq.diff :as diff] + [antq.diff.git-sha] + [antq.record :as r] + [clojure.test :as t])) + +(t/deftest get-diff-url-test + (let [dep (r/map->Dependency {:type :git-sha + :extra {:url "https://github.com/foo/bar"} + :version "1.0" + :latest-version "2.0"})] + (t/is (= "https://github.com/foo/bar/compare/1.0...2.0" + (diff/get-diff-url dep))) + + (t/testing "missing extra" + (t/is (nil? (diff/get-diff-url (dissoc dep :extra))))) + + (t/testing "not supported extra URL" + (t/is (nil? (diff/get-diff-url (assoc-in dep [:extra :url] "INVALID"))))))) diff --git a/test/antq/diff/github_tag_test.clj b/test/antq/diff/github_tag_test.clj new file mode 100644 index 00000000..3cdc1e71 --- /dev/null +++ b/test/antq/diff/github_tag_test.clj @@ -0,0 +1,14 @@ +(ns antq.diff.github-tag-test + (:require + [antq.diff :as diff] + [antq.diff.github-tag] + [antq.record :as r] + [clojure.test :as t])) + +(t/deftest get-diff-url-test + (let [dep (r/map->Dependency {:type :github-tag + :name "foo/bar" + :version "1.0" + :latest-version "2.0"})] + (t/is (= "https://github.com/foo/bar/compare/1.0...2.0" + (diff/get-diff-url dep))))) diff --git a/test/antq/diff/java_test.clj b/test/antq/diff/java_test.clj new file mode 100644 index 00000000..000ade2b --- /dev/null +++ b/test/antq/diff/java_test.clj @@ -0,0 +1,63 @@ +(ns antq.diff.java-test + (:require + [antq.diff :as diff] + [antq.diff.java :as sut] + [antq.record :as r] + [antq.util.git :as u.git] + [antq.util.maven :as u.mvn] + [clojure.test :as t]) + (:import + (org.apache.maven.model + Model + Scm))) + +(defn- gen-dummy-model + [^String scm-url] + (let [scm (doto (Scm.) + (.setUrl scm-url))] + (doto (Model.) + (.setScm scm)))) + +(t/deftest get-diff-url-test + (let [dep (r/map->Dependency {:type :java + :name "foo/bar" + :version "1.0" + :latest-version "2.0"})] + (t/testing "https://github.com" + (with-redefs [sut/get-repository-url (constantly "https://example.com") + u.mvn/read-pom (fn [url] + (when (= "https://example.com/foo/bar/1.0/bar-1.0.pom" url) + (gen-dummy-model "https://github.com/bar/baz"))) + u.git/tags-by-ls-remote (fn [url] + (when (= "https://github.com/bar/baz/" url) + ["v0.0" "v1.0" "v2.0" "v3.0"]))] + (t/is (= "https://github.com/bar/baz/compare/v1.0...v2.0" + (diff/get-diff-url dep))))) + + (t/testing "git@github.com" + (with-redefs [sut/get-repository-url (constantly "https://example.com") + u.mvn/read-pom (fn [url] + (when (= "https://example.com/git/at/1.0/at-1.0.pom" url) + (gen-dummy-model "git@github.com:git/at"))) + u.git/tags-by-ls-remote (fn [url] + (when (= "https://github.com/git/at/" url) + ["v0.0" "v1.0" "v2.0" "v3.0"]))] + (t/is (= "https://github.com/git/at/compare/v1.0...v2.0" + (diff/get-diff-url (assoc dep :name "git/at")))))) + + (t/testing "Failed to fetch repository URL" + (with-redefs [sut/get-repository-url (constantly nil)] + (t/is (nil? (diff/get-diff-url (assoc dep :name "fetch/repo-url")))))) + + (t/testing "POM not found" + (with-redefs [sut/get-repository-url (constantly "https://example2.com") + u.mvn/read-pom (fn [_] (throw (java.io.FileNotFoundException. "test exception")))] + (t/is (nil? (diff/get-diff-url (assoc dep :name "pom/not-found")))))) + + + (t/testing "not supported URL" + (with-redefs [sut/get-repository-url (constantly "https://example.com") + u.mvn/read-pom (fn [url] + (when (= "https://example.com/not/supported/1.0/supported-1.0.pom" url) + (gen-dummy-model "https://not-supported.com")))] + (t/is (nil? (diff/get-diff-url (assoc dep :name "not/supported")))))))) diff --git a/test/antq/log_test.clj b/test/antq/log_test.clj new file mode 100644 index 00000000..225cc496 --- /dev/null +++ b/test/antq/log_test.clj @@ -0,0 +1,15 @@ +(ns antq.log-test + (:require + [antq.log :as sut] + [clojure.test :as t])) + +(t/deftest info-test + (t/is (= "INFO\n" + (with-out-str (sut/info "INFO"))))) + +(t/deftest error-test + (let [sw (java.io.StringWriter.) + err-str (binding [*err* sw] + (sut/error "ERROR") + (str sw))] + (t/is (= "ERROR\n" err-str)))) diff --git a/test/antq/report/format_test.clj b/test/antq/report/format_test.clj index 7091465f..67a1007a 100644 --- a/test/antq/report/format_test.clj +++ b/test/antq/report/format_test.clj @@ -13,16 +13,19 @@ (t/deftest reporter-test (let [dummy-deps [(h/test-dep {:file "a" :name "foo" :version "1" :latest-version "2"}) - (h/test-dep {:file "b" :name "bar" :version "1" :latest-version nil})]] + (h/test-dep {:file "b" :name "bar" :version "1" :latest-version nil}) + (h/test-dep {:file "c" :name "baz" :version "1" :latest-version "3" + :diff-url "https://example.com"})]] (t/is (seq (with-out-str (reporter dummy-deps "::error file={{file}}::{{message}}")))) - (t/is (= ["::error file=a::foo,1,2" - "::error file=b::bar,1,Failed to fetch"] + (t/is (= ["::error file=a::foo,1,2. " + "::error file=b::bar,1,Failed to fetch. " + "::error file=c::baz,1,3. https://example.com"] (str/split-lines (with-out-str (reporter dummy-deps - "::error file={{file}}::{{name}},{{version}},{{latest-version}}"))))))) + "::error file={{file}}::{{name}},{{version}},{{latest-version}}. {{diff-url}}"))))))) diff --git a/test/antq/upgrade_test.clj b/test/antq/upgrade_test.clj index 88ca9f98..e6f7662c 100644 --- a/test/antq/upgrade_test.clj +++ b/test/antq/upgrade_test.clj @@ -55,8 +55,11 @@ :file temp1})] (t/is (= "before0" (slurp temp1))) - (let [out-str (with-out-str (sut/upgrade! [dep] true))] - (t/is (not= -1 (.indexOf out-str "Not supported")))) + (let [sw (java.io.StringWriter.) + err-str (binding [*err* sw] + (sut/upgrade! [dep] true) + (str sw))] + (t/is (not= -1 (.indexOf err-str "Not supported")))) (t/is (= "before0" (slurp temp1))))) diff --git a/test/antq/util/git_test.clj b/test/antq/util/git_test.clj new file mode 100644 index 00000000..2bb611f0 --- /dev/null +++ b/test/antq/util/git_test.clj @@ -0,0 +1,23 @@ +(ns antq.util.git-test + (:require + [antq.util.git :as sut] + [clojure.java.shell :as sh] + [clojure.string :as str] + [clojure.test :as t])) + +(def ^:private dummy-ls-remote-out + (->> [["dummy-sha" "HEAD"] + ["dummy-sha" "refs/heads/foo"] + ["dummy-sha" "refs/pull/1/head"] + ["dummy-sha" "refs/tags/v1"] + ["dummy-sha" "refs/tags/v2"]] + (map #(str/join "\t" %)) + (str/join "\n"))) + +(t/deftest tags-by-ls-remote*-test + (with-redefs [sh/sh (constantly {:exit 0 + :out dummy-ls-remote-out + :err ""})] + (t/is (= ["v1" "v2"] + (sut/tags-by-ls-remote* "dummy url"))))) + diff --git a/test/antq/util/maven_test.clj b/test/antq/util/maven_test.clj new file mode 100644 index 00000000..6874b6c5 --- /dev/null +++ b/test/antq/util/maven_test.clj @@ -0,0 +1,54 @@ +(ns antq.util.maven-test + (:require + [antq.record :as r] + [antq.util.maven :as sut] + [clojure.test :as t])) + +(t/deftest normalize-repo-url-test + (t/are [expected in] (= expected (sut/normalize-repo-url in)) + "" "" + "foo" "foo" + "s3://foo/bar" "s3p://foo/bar")) + +(t/deftest normalize-repos-test + (t/is (= sut/default-repos + (sut/normalize-repos sut/default-repos))) + (t/is (= {"foo" {:url "s3://bar"}} + (sut/normalize-repos {"foo" {:url "s3://bar"}}))) + (t/is (= {"foo" {:invalid "invalid"}} + (sut/normalize-repos {"foo" {:invalid "invalid"}}))) + + (t/testing "replace s3p:// to s3://" + (t/is (= {"foo" {:url "s3://bar"}} + (sut/normalize-repos {"foo" {:url "s3p://bar"}}))) + (t/is (= {"foo" {:url "s3://bar" :no-auth true}} + (sut/normalize-repos {"foo" {:url "s3p://bar" :no-auth true}}))))) + +(t/deftest snapshot?-test + (t/are [expected in] (= expected (sut/snapshot? in)) + false "" + false "foo" + true "foo-snapshot" + true "foo-SnapShot" + true "foo-SNAPSHOT")) + +(t/deftest dep->opts-test + (t/is (= {:repositories sut/default-repos + :snapshots? false} + (sut/dep->opts (r/map->Dependency {:version "1.0.0"})))) + (t/is (= {:repositories (assoc sut/default-repos + "foo" {:url "s3://foo"}) + :snapshots? true} + (sut/dep->opts (r/map->Dependency {:repositories {"foo" {:url "s3p://foo"}} + :version "1.0.0-SNAPSHOT"}))))) + +(t/deftest get-url-test + (let [model (sut/read-pom "pom.xml")] + (t/is (= "https://github.com/liquidz/antq" + (sut/get-url model))))) + +(t/deftest get-scm-url-test + (let [model (sut/read-pom "pom.xml") + scm (sut/get-scm model)] + (t/is (= "https://github.com/liquidz/antq" + (sut/get-scm-url scm))))) diff --git a/test/antq/util/url_test.clj b/test/antq/util/url_test.clj new file mode 100644 index 00000000..5058696d --- /dev/null +++ b/test/antq/util/url_test.clj @@ -0,0 +1,26 @@ +(ns antq.util.url-test + (:require + [antq.util.url :as sut] + [clojure.test :as t])) + +(t/deftest ensure-tail-slash-test + (t/is (= "foo/" (sut/ensure-tail-slash "foo"))) + (t/is (= "foo/" (sut/ensure-tail-slash "foo/")))) + +(t/deftest ensure-git-https-url-test + (t/is (= "https://github.com/foo/bar/" + (sut/ensure-git-https-url "https://github.com/foo/bar"))) + (t/is (= "https://github.com/foo/bar/" + (sut/ensure-git-https-url "https://github.com/foo/bar.git"))) + (t/is (= "https://github.com/foo/bar/" + (sut/ensure-git-https-url "git@github.com:foo/bar")))) + +(t/deftest ensure-https + (t/is (= "https://github.com" + (sut/ensure-https "https://github.com"))) + (t/is (= "https://github.com" + (sut/ensure-https "http://github.com"))) + (t/is (= "git@github.com" + (sut/ensure-https "git@github.com"))) + (t/is (= "" + (sut/ensure-https "")))) diff --git a/test/antq/ver/github_tag_test.clj b/test/antq/ver/github_tag_test.clj index 614cdf47..ec52e5d5 100644 --- a/test/antq/ver/github_tag_test.clj +++ b/test/antq/ver/github_tag_test.clj @@ -4,6 +4,7 @@ [antq.ver :as ver] [antq.ver.github-tag :as sut] [cheshire.core :as json] + [clojure.java.shell :as sh] [clojure.string :as str] [clojure.test :as t])) @@ -35,11 +36,11 @@ (t/deftest get-sorted-versions-test (with-redefs [slurp (constantly dummy-json)] - (t/is (= ["3.0.0" "2.0.0" "1.0.0"] + (t/is (= ["v3.0.0" "v2.0.0" "v1.0.0"] (get-sorted-versions {:name "foo/bar"})))) (t/testing "response should be cached" - (t/is (= ["3.0.0" "2.0.0" "1.0.0"] + (t/is (= ["v3.0.0" "v2.0.0" "v1.0.0"] (get-sorted-versions {:name "foo/bar"}))))) (t/deftest get-sorted-versions-fallback-test @@ -53,9 +54,9 @@ (with-redefs [slurp (fn [& _] (reset! api-errored true) (throw (Exception. "test exception"))) - sut/github-ls-remote (fn [dep] - (when (= "bar/baz" (:name dep)) - {:out dummy-out}))] + sh/sh (fn [& args] + (when (= ["git" "ls-remote" "https://github.com/bar/baz"] args) + {:out dummy-out}))] (t/testing "pre" (t/is (false? @api-errored)) (t/is (false? @(deref #'sut/failed-to-fetch-from-api)))) diff --git a/test/antq/ver/java_test.clj b/test/antq/ver/java_test.clj index 11af835f..425e5e81 100644 --- a/test/antq/ver/java_test.clj +++ b/test/antq/ver/java_test.clj @@ -1,6 +1,7 @@ (ns antq.ver.java-test (:require [antq.record :as r] + [antq.util.maven :as u.mvn] [antq.ver :as ver] [antq.ver.java :as sut] [clojure.edn :as edn] @@ -11,24 +12,9 @@ (get-in (edn/read-string (slurp "deps.edn")) [:deps 'org.clojure/clojure :mvn/version])) -(t/deftest normalize-repos-test - (t/is (= sut/default-repos - (sut/normalize-repos sut/default-repos))) - (t/is (= {"foo" {:url "s3://bar"}} - (sut/normalize-repos {"foo" {:url "s3://bar"}}))) - - (t/is (= {"foo" {:invalid "invalid"}} - (sut/normalize-repos {"foo" {:invalid "invalid"}}))) - - (t/testing "replace s3p:// to s3://" - (t/is (= {"foo" {:url "s3://bar"}} - (sut/normalize-repos {"foo" {:url "s3p://bar"}}))) - (t/is (= {"foo" {:url "s3://bar" :no-auth true}} - (sut/normalize-repos {"foo" {:url "s3p://bar" :no-auth true}}))))) - (t/deftest get-versions-test (let [vers (sut/get-versions 'org.clojure/clojure - {:repositories sut/default-repos})] + {:repositories u.mvn/default-repos})] (t/is (seq vers)) (t/is (contains? (set (map str vers)) current-clojure-version)))) @@ -57,6 +43,6 @@ (with-redefs [sut/get-sorted-versions-by-name (fn [_ opts] opts)] (let [res (get-sorted-versions {:repositories {"foo" {:url "s3p://bar"}}}) diff (set/difference (set (:repositories res)) - (set sut/default-repos))] + (set u.mvn/default-repos))] (t/is (= #{["foo" {:url "s3://bar"}]} diff))))))