From de05a43afe93d6732f8630b16dc512302703a30c Mon Sep 17 00:00:00 2001 From: Cam Saul <1455846+camsaul@users.noreply.github.com> Date: Thu, 8 Sep 2022 12:15:57 -0700 Subject: [PATCH] Switch to Clojure CLI & GitHub Actions (#117) * Switch to Clojure CLI & GitHub Actions * Fix linter errors * Empty commit to re-trigger CI * Add codecov.yml * Fix build artifact name * Remove Slack badge --- .circleci/config.yml | 172 ------------------ .clj-kondo/config.edn | 13 +- .codespellrc | 3 + .dir-locals.el | 25 +-- .github/PULL_REQUEST_TEMPLATE.md | 13 +- .github/workflows/deploy.yml | 39 ++++ .github/workflows/linters.yml | 12 -- .github/workflows/tests.yml | 124 +++++++++++++ .gitignore | 1 + README.md | 16 +- VERSION.txt | 1 + build.clj | 43 +++++ codecov.yml | 16 ++ deps.edn | 64 +++++++ project.clj | 147 --------------- .../methodical/methodical/config.edn | 12 +- .../methodical/hooks/methodical/core.clj | 37 +++- scripts/lint-and-test.sh | 58 ++++++ src/methodical/impl.clj | 8 +- src/methodical/impl/cache/watching.clj | 2 +- src/methodical/impl/combo/clos.clj | 4 +- src/methodical/impl/combo/operator.clj | 2 +- src/methodical/impl/combo/threaded.clj | 2 +- src/methodical/impl/dispatcher/common.clj | 10 +- src/methodical/impl/dispatcher/everything.clj | 8 +- .../impl/dispatcher/multi_default.clj | 8 +- src/methodical/impl/dispatcher/standard.clj | 4 +- src/methodical/impl/multifn/standard.clj | 2 +- src/methodical/interface.clj | 8 +- src/methodical/util.clj | 2 +- test/methodical/cloverage_runner.clj | 15 ++ test/methodical/impl/combo/clos_test.clj | 2 +- test/methodical/impl/combo/operator_test.clj | 4 +- .../impl/dispatcher/common_test.clj | 2 +- .../impl/dispatcher/multi_default_test.clj | 4 +- .../impl/method_table/standard_test.clj | 14 +- test/methodical/macros_test.clj | 4 +- test/methodical/test_runner.clj | 121 ++++++++++++ test/methodical/util/trace_test.clj | 4 +- test/methodical/util_test.clj | 77 ++++---- 40 files changed, 656 insertions(+), 447 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .codespellrc create mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/linters.yml create mode 100644 .github/workflows/tests.yml create mode 100644 VERSION.txt create mode 100644 build.clj create mode 100644 codecov.yml create mode 100644 deps.edn delete mode 100644 project.clj create mode 100755 scripts/lint-and-test.sh create mode 100644 test/methodical/cloverage_runner.clj create mode 100644 test/methodical/test_runner.clj diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c763728..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,172 +0,0 @@ -version: 2.1 - -######################################################################################################################## -# EXECUTORS # -######################################################################################################################## - -executors: - default: - working_directory: /home/circleci/camsaul/methodical/ - docker: - - image: circleci/clojure:lein-2.9.1 - - java-11: - working_directory: /home/circleci/camsaul/methodical/ - docker: - - image: circleci/clojure:openjdk-11-lein-2.9.1 - - -######################################################################################################################## -# COMMANDS # -######################################################################################################################## - -commands: - - attach-workspace: - steps: - - attach_workspace: - at: /home/circleci/ - - restore-deps-cache: - steps: - - restore_cache: - keys: - - deps-{{ checksum "project.clj" }} - - deps- - -jobs: - - checkout: - executor: default - steps: - - restore_cache: - keys: - - source-{{ .Branch }}-{{ .Revision }} - - source-{{ .Branch }} - - source- - - checkout - - save_cache: - key: source-{{ .Branch }}-{{ .Revision }} - paths: - - .git - - persist_to_workspace: - root: /home/circleci/ - paths: - - camsaul/methodical - - lein: - parameters: - e: - type: executor - default: default - lein-command: - type: string - after-steps: - type: steps - default: [] - executor: << parameters.e >> - steps: - - attach-workspace - - restore-deps-cache - - run: - command: lein << parameters.lein-command >> - no_output_timeout: 5m - - steps: << parameters.after-steps >> - - reflection-warnings: - executor: default - steps: - - attach-workspace - - restore-deps-cache - - run: - command: ./check-for-reflection-warnings.sh - no_output_timeout: 3m - - -######################################################################################################################## -# WORKFLOWS # -######################################################################################################################## - -workflows: - version: 2 - build: - jobs: - - checkout - - - lein: - name: deps - requires: - - checkout - lein-command: deps - after-steps: - - save_cache: - key: deps-{{ checksum "project.clj" }} - paths: - - /home/circleci/.m2 - - - lein: - name: tests - requires: - - deps - lein-command: test - - - lein: - name: tests-java-11 - requires: - - deps - e: java-11 - lein-command: test - - - lein: - name: eastwood - requires: - - deps - lein-command: eastwood - - - lein: - name: docstring-checker - requires: - - deps - lein-command: docstring-checker - - - lein: - name: namespace-decls - requires: - - deps - lein-command: check-namespace-decls - - - lein: - name: whitespace-linter - requires: - - deps - lein-command: whitespace-linter - - - lein: - name: cloverage - requires: - - deps - lein-command: cloverage --codecov - after-steps: - - run: - command: bash <(curl -s https://codecov.io/bash) - - - reflection-warnings: - requires: - - deps - - - lein: - name: deploy - requires: - - whitespace-linter - - docstring-checker - - eastwood - - namespace-decls - # - kibit - - cloverage - - reflection-warnings - - tests - - tests-java-11 - lein-command: deploy clojars - filters: - branches: - only: master diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 1a0616b..f1a0fb1 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -12,4 +12,15 @@ :lint-as {potemkin.types/deftype+ clojure.core/deftype} - :skip-comments true} + :skip-comments true + + :ns-groups + [{:pattern ".*-test$" + :name tests}] + + :config-in-ns + {tests + {:linters + {:inline-def {:level :off} + :missing-docstring {:level :off} + :shadowed-var {:level :off}}}}} diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..15d84fe --- /dev/null +++ b/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +ignore-words-list = CLOS,clos,befores,edn,juxt +skip = *#,./target/* diff --git a/.dir-locals.el b/.dir-locals.el index 58369a9..d6e3e16 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,12 +1,15 @@ -((nil . ((indent-tabs-mode . nil) ; always use spaces for tabs +((nil . ((indent-tabs-mode . nil) ; always use spaces for tabs (require-final-newline . t))) ; add final newline on save - (clojure-mode . (;; prefer keeping source width about ~118, GitHub seems to cut off stuff at either 119 or 120 and - ;; it's nicer to look at code in GH when you don't have to scroll back and forth - (fill-column . 118) - (clojure-docstring-fill-column . 118) - (eval . (define-clojure-indent - (p.types/defprotocol+ '(1 (:defn))) - (p.types/definterface+ '(1 (:defn))) - (p.types/def-abstract-type '(1 (:defn))) - (p.types/deftype+ '(2 nil nil (:defn))) - (p.types/defrecord+ '(2 nil nil (:defn)))))))) + (clojure-mode . ((cider-preferred-build-tool . clojure-cli) + (cider-clojure-cli-aliases . "dev") + (fill-column . 120) + (clojure-docstring-fill-column . 120) + (eval . (put 'p/defprotocol+ 'clojure-doc-string-elt 2)) + (eval . (put 'p.types/defprotocol+ 'clojure-doc-string-elt 2)) + (eval . (put-clojure-indent 'p.types/defprotocol+ '(1 (:defn)))) + (eval . (put-clojure-indent 'p.types/definterface+ '(1 (:defn)))) + (eval . (put-clojure-indent 'p.types/def-abstract-type '(1 (:defn)))) + ;; (eval . (put-clojure-indent 'p.types/deftype+ '(2 nil nil (:defn)))) + ;; (eval . (put-clojure-indent 'p.types/defrecord+ '(2 nil nil (:defn)))) + (eval . (put-clojure-indent 'with-meta '(:form))) + (eval . (put-clojure-indent 'with-bindings* '(:form)))))) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8a47fdb..d75de2b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,22 +1,19 @@ Thanks for contributing to Methodical. Before open a pull request, please take a moment to: - [ ] Ensure the PR follows the [Clojure Style Guide](https://github.com/bbatsov/clojure-style-guide). -- [ ] Tests and linters pass. You can run them locally as follows: +- [ ] Tests and linters pass. You can run all of the tests and linters locally with - lein test && lein lint + ./scripts/lint-and-test.sh - CircleCI will also run these same tests against your PR. + GitHub Actions will also run these same tests against your PR. - [ ] Make sure you've included new tests for any new features or bugfixes. - [ ] New features are documented, or documentation is updated appropriately for any changed features. - [ ] Carefully review your own changes and revert any superfluous ones. (A good example would be moving words in the Markdown documentation to different lines in a way that wouldn't change how the rendered page itself would appear. These sorts of changes make a PR bigger than it needs to be, and, thus, harder to review.) - Of course, indentation and typo fixes are not covered by this rule and are always appreciated. + Of course, indentation and typo fixes are not covered by this rule and are always appreciated. - [ ] Include a detailed explanation of what changes you're making and why you've made them. This will help me understand what's going on while we review it. -Once you've done all that, open a PR! Make sure to at-mention @camsaul in the PR description. Otherwise I won't get an -email about it and might not get review it right away. :) - -Thanks for your contribution! +Once you've done all that, open a PR! Thanks for your contribution! diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..5c337c6 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,39 @@ +name: Deploy + +on: + push: + branches: + - master + +jobs: + deploy: + runs-on: ubuntu-20.04 + environment: Deployment + steps: + - uses: actions/checkout@v3 + - name: Prepare JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + - name: Setup Clojure + uses: DeLaGuardo/setup-clojure@9.5 + with: + cli: 1.11.1.1155 + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + ~/.m2/repository + ~/.gitlibs + ~/.deps.clj + key: deploy + - name: Build JAR + run: clojure -T:build build + env: + GITHUB_SHA: ${{ env.GITHUB_SHA }} + - name: Deploy to Clojars + run: clojure -T:build deploy + env: + CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} + CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml deleted file mode 100644 index 6dd47eb..0000000 --- a/.github/workflows/linters.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Linters -on: [push] -jobs: - Clj-Kondo: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v1 - - uses: DeLaGuardo/clojure-lint-action@master - with: - clj-kondo-args: --lint src --parallel - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..ba416c8 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,124 @@ +name: Tests + +on: [push] + +jobs: + kondo: + runs-on: ubuntu-20.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - uses: DeLaGuardo/clojure-lint-action@master + with: + check-name: Run clj-kondo + clj-kondo-args: >- + --lint src test + github_token: ${{ secrets.GITHUB_TOKEN }} + + tests: + runs-on: ubuntu-20.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - name: Prepare JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + - name: Setup Clojure + uses: DeLaGuardo/setup-clojure@9.5 + with: + cli: 1.11.1.1155 + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + ~/.m2/repository + ~/.gitlibs + ~/.deps.clj + key: tests-postgres + - run: clojure -X:dev:test + name: Run tests + env: + CI: TRUE + + whitespace-linter: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Prepare JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + - name: Setup Clojure + uses: DeLaGuardo/setup-clojure@9.5 + with: + cli: 1.11.1.1155 + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + ~/.m2/repository + ~/.gitlibs + ~/.deps.clj + key: whitespace-linter + - run: clojure -T:whitespace-linter + name: Run whitespace linter + + check: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Prepare JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + - name: Setup Clojure + uses: DeLaGuardo/setup-clojure@9.5 + with: + cli: 1.11.1.1155 + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + ~/.m2/repository + ~/.gitlibs + ~/.deps.clj + key: check + - run: clojure -M:check + name: Check namespaces + + codecov: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Prepare JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + - name: Setup Clojure + uses: DeLaGuardo/setup-clojure@9.5 + with: + cli: 1.11.1.1155 + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + ~/.m2/repository + ~/.gitlibs + ~/.deps.clj + key: codecov + - run: clojure -X:dev:test-h2:cloverage + name: Run tests with Cloverage + - name: Upload results to codecov.io + uses: codecov/codecov-action@v3 + with: + files: ./target/coverage/codecov.json + + codespell: + runs-on: ubuntu-20.04 + steps: + - uses: codespell-project/actions-codespell@master diff --git a/.gitignore b/.gitignore index 544b1be..4beb7c2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .\#* /*.iml /.clj-kondo/.cache +/.cpcache /.eastwood /.env /.envrc diff --git a/README.md b/README.md index 87b47fb..4bec6b8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -[![Downloads](https://versions.deps.co/camsaul/methodical/downloads.svg)](https://versions.deps.co/camsaul/methodical) -[![Dependencies Status](https://versions.deps.co/camsaul/methodical/status.svg)](https://versions.deps.co/camsaul/methodical) -[![Circle CI](https://circleci.com/gh/camsaul/methodical.svg?style=svg)](https://circleci.com/gh/camsaul/methodical) -[![codecov](https://codecov.io/gh/camsaul/methodical/branch/master/graph/badge.svg)](https://codecov.io/gh/camsaul/methodical) -[![License](https://img.shields.io/badge/license-Eclipse%20Public%20License-blue.svg)](https://raw.githubusercontent.com/camsaul/methodical/master/LICENSE) -[![cljdoc badge](https://cljdoc.org/badge/methodical)](https://cljdoc.org/d/methodical/methodical/CURRENT) -[![GitHub Sponsors](https://img.shields.io/github/sponsors/camsaul)](https://github.com/sponsors/camsaul) +[![ClojarsDownloads](https://img.shields.io/clojars/dt/methodical?style=for-the-badge)](http://clojars.org/methodical) +[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/camsaul/methodical/Tests/master?style=for-the-badge)](https://github.com/camsaul/methodical/actions/workflows/tests.yml) +[![License](https://img.shields.io/badge/license-Eclipse%20Public%20License-blue.svg?style=for-the-badge)](https://raw.githubusercontent.com/camsaul/methodical/master/LICENSE) +[![GitHub last commit](https://img.shields.io/github/last-commit/camsaul/methodical?style=for-the-badge)](https://github.com/camsaul/methodical/commits/) +[![Codecov](https://img.shields.io/codecov/c/github/camsaul/methodical?style=for-the-badge)](https://codecov.io/gh/camsaul/methodical) +[![GitHub Sponsors](https://img.shields.io/github/sponsors/camsaul?style=for-the-badge)](https://github.com/sponsors/camsaul) +[![cljdoc badge](https://img.shields.io/badge/dynamic/json?color=informational&label=cljdoc&query=results%5B%3F%28%40%5B%22artifact-id%22%5D%20%3D%3D%20%22methodical%22%29%5D.version&url=https%3A%2F%2Fcljdoc.org%2Fapi%2Fsearch%3Fq%3Dmethodical%2Fmethodical&style=for-the-badge)](https://cljdoc.org/d/methodical/methodical/CURRENT) [![Clojars Project](https://clojars.org/methodical/latest-version.svg)](http://clojars.org/methodical) @@ -106,7 +106,7 @@ supports Clojure's functional programming style. `:before` methods unlock a whole new range of solutions that would be tedious with vanilla Clojure multimethods: suppose you wanted add logging to all invocations of a multimethod. With vanilla multimethods, you'd have to add an -individual log statment to every method! With Methodical, just add a new `:default` `:before` method: +individual log statement to every method! With Methodical, just add a new `:default` `:before` method: ```clj (m/defmethod my-multimethod :before :default diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..85a7357 --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +0.14.0-SNAPSHOT diff --git a/build.clj b/build.clj new file mode 100644 index 0000000..09c8f87 --- /dev/null +++ b/build.clj @@ -0,0 +1,43 @@ +(ns build + (:require [clojure.java.shell :as sh] + [clojure.pprint :as pprint] + [clojure.string :as str] + [org.corfield.build :as bb])) + +(def scm-url "git@github.com:camsaul/methodical.git") +(def lib 'methodical/methodical) +(def version (str/trim (slurp "VERSION.txt"))) + +(defn sha [& _] + (or (not-empty (System/getenv "GITHUB_SHA")) + (not-empty (-> (sh/sh "git" "rev-parse" "HEAD") + :out + str/trim)))) + +(def default-options + {:lib lib + :version version + :scm {:tag (sha) + :connection (str "scm:git:" scm-url) + :developerConnection (str "scm:git:" scm-url) + :url scm-url}}) + +(println "Options:") +(pprint/pprint default-options) +(println) + +(defn build [opts] + (doto (merge default-options opts) + bb/clean + bb/jar)) + +(defn install [opts] + (printf "Installing %s to local Maven repository...\n" version) + (bb/install (merge default-options opts))) + +(defn build-and-install [opts] + (build opts) + (install opts)) + +(defn deploy [opts] + (bb/deploy (merge default-options opts))) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..87ce6a9 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,16 @@ +codecov: + bot: "codecov-io" + require_ci_to_pass: no + +coverage: + status: + project: + default: + # Project must always have at least 80% coverage (by line) + target: 85% + # Whole-project test coverage is allowed to drop up to 5%. (For situtations where we delete code with full coverage) + threshold: 5% + patch: + default: + # Changes must have at least 80% test coverage (by line) + target: 90% diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..78eb341 --- /dev/null +++ b/deps.edn @@ -0,0 +1,64 @@ +{:paths + ["src" "resources"] + + :deps + {mvxcvi/puget {:mvn/version "1.3.2"} + pretty/pretty {:mvn/version "1.0.5"} + potemkin/potemkin {:mvn/version "0.4.5"}} + + :aliases + {:dev + {:extra-paths ["dev" "test"] + + :extra-deps + {eftest/eftest {:mvn/version "0.5.9"} + environ/environ {:mvn/version "1.2.0"} + io.github.camsaul/humane-are {:mvn/version "1.0.2"} + org.clojure/java.classpath {:mvn/version "1.0.0"} + org.clojure/math.combinatorics {:mvn/version "0.1.6"} + org.clojure/tools.namespace {:mvn/version "1.3.0"} + pjstadig/humane-test-output {:mvn/version "0.11.0"}} + + :jvm-opts + [ ;; if compilation on launch fails or whatever print to console instead of a temp file. + "-Dclojure.main.report=stderr" + ;; [LEVEL logger-name] message stacktrace + "-Djava.util.logging.SimpleFormatter.format=%n[%4$s %3$s] %5$s%n%6$s%n" + ;; Exceptions that get thrown repeatedly are created without stacktraces as a performance optimization in newer Java + ;; versions. This makes debugging pretty hard when working on stuff locally -- prefer debuggability over performance + ;; for local dev work. + "-XX:-OmitStackTraceInFastThrow"]} + + ;; clojure -M:check + :check + {:extra-deps {athos/clj-check {:git/url "https://github.com/athos/clj-check.git" + :sha "518d5a1cbfcd7c952f548e6dbfcb9a4a5faf9062"}} + :main-opts ["-m" "clj-check.check"]} + + ;; clj -T:whitespace-linter + :whitespace-linter + {:deps {com.github.camsaul/whitespace-linter {:sha "e35bc252ccf5cc74f7d543ef95ad8a3e5131f25b"}} + :ns-default whitespace-linter + :exec-fn whitespace-linter/lint + :exec-args {:paths ["deps.edn" "src" "test" ".github" #_"doc"] + :include-patterns ["\\.clj[cs]?$" "\\.edn$" "\\.yaml$" "\\.md$"]}} + + ;; clojure -X:dev:test + :test + {:exec-fn methodical.test-runner/run-tests + :exec-args {:only ["test"]}} + + ;; clj -X:dev:cloverage + :cloverage + {:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}} + :exec-fn methodical.cloverage-runner/run-project + :exec-args {:codecov? true + :src-ns-path ["src"] + :test-ns-path ["test"] + :exclude-call [] + :ns-regex ["^methodical\\..*"]}} + + ;; clojure -T:build + :build + {:deps {io.github.seancorfield/build-clj {:git/tag "v0.8.3", :git/sha "7ac1f8d"}} + :ns-default build}}} diff --git a/project.clj b/project.clj deleted file mode 100644 index 9790773..0000000 --- a/project.clj +++ /dev/null @@ -1,147 +0,0 @@ -(defproject methodical "0.14.0-SNAPSHOT" - :url "https://github.com/camsaul/methodical" - :min-lein-version "2.5.0" - - :license {:name "Eclipse Public License" - :url "https://raw.githubusercontent.com/camsaul/methodical/master/LICENSE"} - - :aliases - {"repl" ["with-profile" "+repl" "repl"] - ;; run lein deps with all dependencies from all the various profiles merged in. Useful for CI so we can cache - ;; everything - "deploy" ["with-profile" "+deploy" "deploy"] - "all-deps" ["with-profile" "-user,+all-profiles" "deps"] - "test" ["with-profile" "+test" "test"] - "cloverage" ["with-profile" "+cloverage" "cloverage"] - "profile" ["with-profile" "+profile" "run"] - "eastwood" ["with-profile" "+eastwood" "eastwood"] - "kibit" ["with-profile" "+kibit" "kibit"] - "check-namespace-decls" ["with-profile" "+check-namespace-decls" "check-namespace-decls"] - "docstring-checker" ["with-profile" "+docstring-checker" "docstring-checker"] - "check-reflection-warnings" ["with-profile" "+reflection-warnings" "check"] - "whitespace-linter" ["with-profile" "+whitespace-linter" - "run" "-m" "clojure.main" "-e" (do - (require 'whitespace-linter) - (whitespace-linter/lint {:paths ["src" "test"] - :include-patterns [#".clj[cs]?$"]}))] - ;; `lein lint` will run all linters. Except for reflecion warnings, use the script for that - "lint" ["do" ["eastwood"] ["kibit"] ["check-namespace-decls"] ["cloverage"] - ["whitespace-linter"]["docstring-checker"]]} - - :dependencies - [[mvxcvi/puget "1.3.2"] - [pretty "1.0.5"] - [potemkin "0.4.5"]] - - :profiles - {:dev - {:dependencies - [[criterium "0.4.6"] - [io.github.camsaul/humane-are "1.0.2"] - [org.clojure/clojure "1.11.1"] - [org.clojure/math.combinatorics "0.1.6"] - [pjstadig/humane-test-output "0.11.0"]] - - :injections - [(require 'pjstadig.humane-test-output) - (pjstadig.humane-test-output/activate!) - (require 'humane-are.core) - (humane-are.core/install!)] - - :source-paths ["dev"]} - - :whitespace-linter - {:dependencies [[com.camsaul/whitespace-linter "2022.07.21.02.09" :exclusions [org.clojure/clojure]]]} - - :repl - {:global-vars {*warn-on-reflection* true}} - - :test - {} - - :cloverage - {:dependencies - ;; Cloverage dependency is normally injected when the plugin is ran. By explicitly specifying it here we can - ;; cache it in CI - [[cloverage "1.2.4"] - ;; Required by both Potemkin and Cloverage, but Potemkin uses an older version that breaks Cloverage's ablity to - ;; understand certain forms. Explicitly specify newer version here. - [riddley "0.2.0"]] - - :plugins - [[lein-cloverage "1.2.2"]] - - ;; don't count ./dev stuff for code coverage calcualations. - :source-paths ^:replace ["src"] - - :cloverage - {:fail-threshold 90}} - - :profile - {:main ^:skip-aot methodical.profile} - - :eastwood - {:plugins - [[jonase/eastwood "0.3.11" :exclusions [org.clojure/clojure]]] - - :eastwood - {:config-files - ["./.eastwood-config.clj"] - - :exclude-namespaces [:test-paths] - - ;; disabled for now until I figure out how to disable it in the one place it's popping up - #_:remove-linters - #_ [:unused-ret-vals] - - :add-linters - [:unused-private-vars - :unused-locals]}} - - :bikeshed - {:dependencies - ;; use latest tools.namespace instead of older version so we only need to fetch it once for all plugins. - [[org.clojure/tools.namespace "1.3.0"]] - - :plugins - [[lein-bikeshed "0.5.2" - :exclusions [org.clojure/tools.namespace]]]} - - :kibit - {:plugins - [[lein-kibit "0.1.8" - :exclusions [org.clojure/clojure]]]} - - :check-namespace-decls - {:plugins [[lein-check-namespace-decls "1.0.4" - :exclusions [org.clojure/clojure]]] - :source-paths ["test"] - :check-namespace-decls {:prefix-rewriting false - :prune-ns-form false}} - - :docstring-checker - {:plugins - [[docstring-checker "1.1.0"]] - - :docstring-checker - {:include [#"^methodical"] - :exclude [#"test" #"^methodical\.profile$"]}} - - ;; run `lein check-reflection-warnings` to check for reflection warnings - :reflection-warnings - {:global-vars {*warn-on-reflection* true}} - - :all-profiles - [:test :cloverage :profile :eastwood :whitespace-linter :kibit :check-namespace-decls :docstring-checker - :reflection-warnings - {}] - - :deploy - {:dependencies [[org.clojure/clojure "1.11.1"]]}} - - :deploy-repositories - [["clojars" - {:url "https://clojars.org/repo" - :username :env/clojars_username - :password :env/clojars_password - :sign-releases false}]]) diff --git a/resources/clj-kondo.exports/methodical/methodical/config.edn b/resources/clj-kondo.exports/methodical/methodical/config.edn index 308bc3c..345a793 100644 --- a/resources/clj-kondo.exports/methodical/methodical/config.edn +++ b/resources/clj-kondo.exports/methodical/methodical/config.edn @@ -1,14 +1,16 @@ {:config-paths ["macros"] :lint-as - {methodical.macros/defmulti clojure.core/defmulti - methodical.core/defmulti clojure.core/defmulti - methodical.macros/defmethod clj-kondo.lint-as/def-catch-all - methodical.core/defmethod clj-kondo.lint-as/def-catch-all} + {} :hooks {:analyze-call - {methodical.core/defmethod hooks.methodical.core/defmethod} + {methodical.core/defmethod hooks.methodical.core/defmethod + methodical.core/defmulti hooks.methodical.core/defmulti + methodical.macros/define-aux-method hooks.methodical.core/defmethod + methodical.macros/define-primary-method hooks.methodical.core/defmethod + methodical.macros/defmethod hooks.methodical.core/defmethod + methodical.macros/defmulti hooks.methodical.core/defmulti} :macroexpand {methodical.impl.combo.operator/defoperator macros.methodical.impl.combo.operator/defoperator}}} diff --git a/resources/clj-kondo.exports/methodical/methodical/hooks/methodical/core.clj b/resources/clj-kondo.exports/methodical/methodical/hooks/methodical/core.clj index c626625..c92a889 100644 --- a/resources/clj-kondo.exports/methodical/methodical/hooks/methodical/core.clj +++ b/resources/clj-kondo.exports/methodical/methodical/hooks/methodical/core.clj @@ -16,7 +16,8 @@ (hooks/list-node (add-next-method (:children list-node)))))) (defn defmethod - [{{[_ multimethod & [first-arg :as args]] :children} :node}] + [{{[_ multimethod & [first-arg :as args]] :children, :as node} :node}] + #_(clojure.pprint/pprint (hooks/sexpr node)) (let [[aux-qualifier dispatch-value & fn-tail] (if (#{:before :after :around} (hooks/sexpr first-arg)) (cons (hooks/sexpr first-arg) (rest args)) (cons nil args)) @@ -31,3 +32,37 @@ #_(println "=>") #_(clojure.pprint/pprint (hooks/sexpr result)) {:node result})) + +(defn defmulti + [{{[_ multimethod-name & args] :children, :as node} :node}] + #_(clojure.pprint/pprint (hooks/sexpr node)) + (let [[docstring & args] (if (hooks/string-node? (first args)) + args + (cons nil args)) + [attribute-map & args] (if (hooks/map-node? (first args)) + args + (cons nil args)) + ;; if there wasn't a positional dispatch function arg passed just use (constantly nil) so Kondo won't complain + [dispatch-fn & kv-options] (if (odd? (count args)) + args + (cons (hooks/list-node + (list + (hooks/token-node 'clojure.core/constantly) + (hooks/token-node 'nil))) + args))] + (let [defmulti-form (hooks/list-node + (filter + some? + [(hooks/token-node 'clojure.core/defmulti) + multimethod-name + docstring + attribute-map + dispatch-fn])) + result (hooks/list-node + (list* + (hooks/token-node 'do) + defmulti-form + kv-options))] + #_(println "=>") + #_(clojure.pprint/pprint (hooks/sexpr result)) + {:node result}))) diff --git a/scripts/lint-and-test.sh b/scripts/lint-and-test.sh new file mode 100755 index 0000000..5b46ce2 --- /dev/null +++ b/scripts/lint-and-test.sh @@ -0,0 +1,58 @@ +#! /usr/bin/env bash + +set -euo pipefail + +# switch to project root directory if we're not already there +script_directory=`dirname "${BASH_SOURCE[0]}"` +cd "$script_directory/.." +project_root=$(pwd) + +# make sure everything is installed + +if [ -z `which clojure` ]; then + cat <ClojureMethodCombination)) (defn clos-method-combination - "Method combination stategy that mimics the standard method combination in the Common Lisp Object System (CLOS). + "Method combination strategy that mimics the standard method combination in the Common Lisp Object System (CLOS). Supports `:before`, `:after`, and `:around` auxiliary methods. The values returned by `:before` and `:after` methods are ignored. Primary methods and around methods get an implicit `next-method` arg (see Methodical dox for more on what this means)." @@ -81,14 +81,14 @@ (combo.operator/operator-method-combination :min)) (defn max-method-combination - "Executes *all* applicable primary methods, and returns the maximum value returned by any one implemenation. Same - constraints as othe CLOS operator-style method combinations." + "Executes *all* applicable primary methods, and returns the maximum value returned by any one implementation. Same + constraints as other CLOS operator-style method combinations." ^MethodCombination [] (combo.operator/operator-method-combination :max)) (defn +-method-combination "Executes *all* applicable primary methods, returnings the sum of the values returned by each method. Same constraints - as othe CLOS operator-style method combinations." + as other CLOS operator-style method combinations." ^MethodCombination [] (combo.operator/operator-method-combination :+)) diff --git a/src/methodical/impl/cache/watching.clj b/src/methodical/impl/cache/watching.clj index 6088aa4..642b18d 100644 --- a/src/methodical/impl/cache/watching.clj +++ b/src/methodical/impl/cache/watching.clj @@ -66,7 +66,7 @@ function this function returns `cache` as-is. * If `cache` is a WatchingCache with a *different* set of refs, this returns a flattened WatchingCache that both the - orignal refs and the new ones. The original `cache` is unmodified." + original refs and the new ones. The original `cache` is unmodified." ^Cache [^Cache cache refs] {:pre [(every? (partial instance? clojure.lang.IRef) refs)]} (cond diff --git a/src/methodical/impl/combo/clos.clj b/src/methodical/impl/combo/clos.clj index 5c08750..a9983fb 100644 --- a/src/methodical/impl/combo/clos.clj +++ b/src/methodical/impl/combo/clos.clj @@ -1,5 +1,5 @@ (ns methodical.impl.combo.clos - "Method combination stategy that mimics the standard method combination in the Common Lisp Object System (CLOS). + "Method combination strategy that mimics the standard method combination in the Common Lisp Object System (CLOS). Supports `:before`, `:after`, and `:around` auxiliary methods. The values returned by `:before` and `:after` methods are ignored. Primary methods and around methods get an implicit `next-method` arg (see Methodical dox for more on what this means)." @@ -11,7 +11,7 @@ (comment methodical.interface/keep-me) -;; TODO - I'm 90% sure we can leverage the `reducing-operator` stuff in `combo.operator` to implemet this +;; TODO - I'm 90% sure we can leverage the `reducing-operator` stuff in `combo.operator` to implement this (defn- apply-befores [combined-method befores] (if (empty? befores) combined-method diff --git a/src/methodical/impl/combo/operator.clj b/src/methodical/impl/combo/operator.clj index 0566287..8d48622 100644 --- a/src/methodical/impl/combo/operator.clj +++ b/src/methodical/impl/combo/operator.clj @@ -25,7 +25,7 @@ below. We actually do one better than CLOS and return a lazy sequence, but `lazy-cat` seemed like a cumbersome name for the combo. - One last difference: unlike CLOS operator method combinations, primary method implementations *are not* qualfied by + One last difference: unlike CLOS operator method combinations, primary method implementations *are not* qualified by their operator. ;; CLOS diff --git a/src/methodical/impl/combo/threaded.clj b/src/methodical/impl/combo/threaded.clj index 21cb4e7..9d6c69c 100644 --- a/src/methodical/impl/combo/threaded.clj +++ b/src/methodical/impl/combo/threaded.clj @@ -21,7 +21,7 @@ (defn combine-with-threader "Combine primary and auxiliary methods using a threading invoker, i.e. something you'd get by calling - `threading-invoker`. The way these methods are combined/reduced is the same, regarless of how args are threaded; + `threading-invoker`. The way these methods are combined/reduced is the same, regardless of how args are threaded; thus, various strategies such as `:thread-first` and `:thread-last` can both share the same `reducer-fn`. " ([threader before-primary-afters] (comp (reducer-fn before-primary-afters) threader)) diff --git a/src/methodical/impl/dispatcher/common.clj b/src/methodical/impl/dispatcher/common.clj index a766622..3e73032 100644 --- a/src/methodical/impl/dispatcher/common.clj +++ b/src/methodical/impl/dispatcher/common.clj @@ -2,7 +2,7 @@ "Utility functions for implementing Dispatchers.") (defn prefers? - "True if `x` or one of its ancestors is prefered over `y` or one of its ancestors." + "True if `x` or one of its ancestors is preferred over `y` or one of its ancestors." [hierarchy prefs x y] (or ;; direct preference for x over y @@ -35,7 +35,7 @@ (not= x default-dispatch-value) (= y default-dispatch-value))))) -(defn domination-comparitor +(defn domination-comparator "Given a `hierarchy` and `prefs` return a function that can be used to sort dispatch values from most-specific to least-specific." ([dominates?-pred] @@ -47,10 +47,10 @@ :else 0))) ([hierarchy prefs] - (domination-comparitor (partial dominates? hierarchy prefs))) + (domination-comparator (partial dominates? hierarchy prefs))) ([hierarchy prefs dispatch-value] - (let [f (domination-comparitor hierarchy prefs)] + (let [f (domination-comparator hierarchy prefs)] (fn [x y] (condp = dispatch-value x -2 @@ -61,7 +61,7 @@ "True if neither `dispatch-val-x` nor `dispatch-val-y` dominate one another, e.g. because they are the same value or are both equally-specific ancestors." [hierarchy prefs dispatch-value dispatch-val-x dispatch-val-y] - (zero? ((domination-comparitor hierarchy prefs dispatch-value) dispatch-val-x dispatch-val-y))) + (zero? ((domination-comparator hierarchy prefs dispatch-value) dispatch-val-x dispatch-val-y))) (defn distinct-by "Like `distinct`, but uses value of `(f item)` to determine whether to keep each `item` in the resulting collection." diff --git a/src/methodical/impl/dispatcher/everything.clj b/src/methodical/impl/dispatcher/everything.clj index 3320a69..61c7a82 100644 --- a/src/methodical/impl/dispatcher/everything.clj +++ b/src/methodical/impl/dispatcher/everything.clj @@ -36,15 +36,15 @@ (matching-primary-methods [_ method-table _] (let [primary-methods (i/primary-methods method-table) - comparitor (dispatcher.common/domination-comparitor (deref hierarchy-var) prefs)] - (for [[dispatch-value method] (sort-by first comparitor primary-methods)] + comparatorr (dispatcher.common/domination-comparator (deref hierarchy-var) prefs)] + (for [[dispatch-value method] (sort-by first comparatorr primary-methods)] (vary-meta method assoc :dispatch-value dispatch-value)))) (matching-aux-methods [_ method-table _] (let [aux-methods (i/aux-methods method-table) - comparitor (dispatcher.common/domination-comparitor (deref hierarchy-var) prefs)] + comparatorr (dispatcher.common/domination-comparator (deref hierarchy-var) prefs)] (into {} (for [[qualifier dispatch-value->methods] aux-methods] - [qualifier (for [[dispatch-value methods] (sort-by first comparitor dispatch-value->methods) + [qualifier (for [[dispatch-value methods] (sort-by first comparatorr dispatch-value->methods) method methods] (vary-meta method assoc :dispatch-value dispatch-value))])))) diff --git a/src/methodical/impl/dispatcher/multi_default.clj b/src/methodical/impl/dispatcher/multi_default.clj index a513b52..153b152 100644 --- a/src/methodical/impl/dispatcher/multi_default.clj +++ b/src/methodical/impl/dispatcher/multi_default.clj @@ -74,8 +74,8 @@ (defn matching-primary-methods "Return a lazy sequence of applicable priamry methods for `dispatch-value`, sorted from most-specific to - least-specific. Similar to the implementation in `methodical.impl.dispatcher.standard`, but supports - partially-specialized default methods; see explaination in ns docstring." + least-specific. Similar to the implementation in [[methodical.impl.dispatcher.standard]], but supports + partially-specialized default methods; see explanation in ns docstring." [{:keys [default-value method-table unambiguous-pairs-seq-fn] :or {unambiguous-pairs-seq-fn dispatcher.standard/unambiguous-pairs-seq} :as opts}] @@ -100,11 +100,11 @@ (map second (dispatcher.common/distinct-by first pairs)))) (defn- aux-dispatch-values [qualifier {:keys [default-value method-table dispatch-value hierarchy prefs]}] - (let [comparitor (dispatcher.common/domination-comparitor hierarchy prefs dispatch-value)] + (let [comparatorr (dispatcher.common/domination-comparator hierarchy prefs dispatch-value)] (distinct (sort-by identity - comparitor + comparatorr (for [dispatch-value (concat [dispatch-value] (partially-specialized-default-dispatch-values dispatch-value default-value) [default-value]) diff --git a/src/methodical/impl/dispatcher/standard.clj b/src/methodical/impl/dispatcher/standard.clj index 9445efe..3d68b28 100644 --- a/src/methodical/impl/dispatcher/standard.clj +++ b/src/methodical/impl/dispatcher/standard.clj @@ -17,7 +17,7 @@ :when (isa? hierarchy dispatch-value a-dispatch-val)] [a-dispatch-val method])] (when (seq matches) - (sort-by first (dispatcher.common/domination-comparitor hierarchy prefs dispatch-value) matches)))) + (sort-by first (dispatcher.common/domination-comparator hierarchy prefs dispatch-value) matches)))) (defn- ambiguous-error-fn [dispatch-val this-dispatch-val next-dispatch-val] (fn [& _] @@ -72,7 +72,7 @@ :when (isa? hierarchy dispatch-value dv) method methods] [dv method])] - (sort-by first (dispatcher.common/domination-comparitor hierarchy prefs dispatch-value) pairs))) + (sort-by first (dispatcher.common/domination-comparator hierarchy prefs dispatch-value) pairs))) (defn matching-aux-pairs "Return pairs of `[dispatch-value method]` of applicable aux methods, *including* default aux methods. Pairs are diff --git a/src/methodical/impl/multifn/standard.clj b/src/methodical/impl/multifn/standard.clj index 4c06629..b97d158 100644 --- a/src/methodical/impl/multifn/standard.clj +++ b/src/methodical/impl/multifn/standard.clj @@ -14,7 +14,7 @@ [dispatcher dispatch-values] (sort-by identity - (dispatcher.common/domination-comparitor (partial i/dominates? dispatcher)) + (dispatcher.common/domination-comparator (partial i/dominates? dispatcher)) dispatch-values)) (defn non-composite-effective-dispatch-value diff --git a/src/methodical/interface.clj b/src/methodical/interface.clj index 4559272..ccd2f3a 100644 --- a/src/methodical/interface.clj +++ b/src/methodical/interface.clj @@ -12,9 +12,9 @@ allowed, and how `defmethod` macro forms using those qualifiers are expanded (e.g., whether they get an implicit `next-method` arg)." (allowed-qualifiers [method-combination] - "The set containg all qualifiers supported by this method combination. `nil` in the set means the method + "The set containing all qualifiers supported by this method combination. `nil` in the set means the method combination supports primary methods (because primary methods have no qualifier); all other values refer to - auxiliary methods with that qualifer, e.g. `:before`, `:after`, or `:around`. + auxiliary methods with that qualifier, e.g. `:before`, `:after`, or `:around`. (allowed-qualifiers (clojure-method-combination)) ;-> #{nil} (allowed-qualifiers (clos-method-combination)) ;-> #{nil :before :after :around} @@ -34,7 +34,7 @@ "A *method table* stores primary and auxiliary methods, and returns them when asked. The default implementation, `standard-method-table`, uses simple Clojure immutable maps." (primary-methods [method-table] - "Get a `dispatch-value -> fn` map of all primary methods assoicated with this method table.") + "Get a `dispatch-value -> fn` map of all primary methods associated with this method table.") (aux-methods [method-table] "Get a `qualifier -> dispatch-value -> [fn]` map of all auxiliary methods associated with this method table.") @@ -46,7 +46,7 @@ "Remove the primary method for `dispatch-value`.") (add-aux-method ^methodical.interface.MethodTable [method-table qualifier dispatch-value f] - "Add an auxiliary method implementation for `qualifer` (e.g. `:before`) and `dispatch-value`. Unlike primary + "Add an auxiliary method implementation for `qualifier` (e.g. `:before`) and `dispatch-value`. Unlike primary methods, auxiliary methods are not limited to one method per dispatch value; thus this method does not remove existing methods for this dispatch value. existing ") diff --git a/src/methodical/util.clj b/src/methodical/util.clj index 3df13b4..1c32e52 100644 --- a/src/methodical/util.clj +++ b/src/methodical/util.clj @@ -86,7 +86,7 @@ (primary-method multifn (i/default-dispatch-value multifn))) (defn default-aux-methods - "Get a map of aux qualifer -> methods for the default dispatch value, if any exist." + "Get a map of aux qualifier -> methods for the default dispatch value, if any exist." [multifn] (aux-methods multifn (i/default-dispatch-value multifn))) diff --git a/test/methodical/cloverage_runner.clj b/test/methodical/cloverage_runner.clj new file mode 100644 index 0000000..5a20af6 --- /dev/null +++ b/test/methodical/cloverage_runner.clj @@ -0,0 +1,15 @@ +(ns methodical.cloverage-runner + (:require cloverage.coverage)) + +(defn run-project + "Shim for running tests using Cloverage to get code coverage metrics. See comments in `deps.edn` for more + information." + [options] + ;; parse regex lists into actual regex Patterns since regex literals aren't allowed in EDN. + (let [options (reduce + (fn [options k] + (cond-> options + (seq (k options)) (update k (partial map re-pattern)))) + options + [:ns-regex :ns-exclude-regex])] + (cloverage.coverage/run-project options))) diff --git a/test/methodical/impl/combo/clos_test.clj b/test/methodical/impl/combo/clos_test.clj index 296ffa6..7091a13 100644 --- a/test/methodical/impl/combo/clos_test.clj +++ b/test/methodical/impl/combo/clos_test.clj @@ -16,7 +16,7 @@ * `make-method`; which makes a method impl that adds its invocation (`(method-key & args)`) to calls, and returns its first arg (if any) with `method-key` appended. - * `record-call!`, which records the invocation (just like `make-method` does, bu for cases where you don't want to + * `record-call!`, which records the invocation (just like `make-method` does, but for cases where you don't want to use this.)" [] (let [calls* (atom [])] diff --git a/test/methodical/impl/combo/operator_test.clj b/test/methodical/impl/combo/operator_test.clj index ff937c9..ec7c6cb 100644 --- a/test/methodical/impl/combo/operator_test.clj +++ b/test/methodical/impl/combo/operator_test.clj @@ -225,7 +225,7 @@ (t/deftest no-methods-test (t/is (= nil (i/combine-methods (combo.operator/operator-method-combination :do) nil nil)) - "Combine-methods should return nil if there are no matcing primary methods.")) + "Combine-methods should return nil if there are no matching primary methods.")) (def ^:private hierarchy (-> (make-hierarchy) @@ -284,7 +284,7 @@ [x] (next-method (str/lower-case x))) -(t/deftest e2e-test +(t/deftest e2e-test-2 (t/is (= '((string "wow") (object "wow")) (seq-multimethod "WOW")) diff --git a/test/methodical/impl/dispatcher/common_test.clj b/test/methodical/impl/dispatcher/common_test.clj index 228eb39..6215d22 100644 --- a/test/methodical/impl/dispatcher/common_test.clj +++ b/test/methodical/impl/dispatcher/common_test.clj @@ -77,4 +77,4 @@ 5 (t/is (dominates? [String ::bird] ::default))) (t/is (not (dominates? ::default [Object ::parrot]))))))))) -;; TODO - add tests for `domination-comparitor`, and `ambiguous?`? +;; TODO - add tests for `domination-comparator`, and `ambiguous?`? diff --git a/test/methodical/impl/dispatcher/multi_default_test.clj b/test/methodical/impl/dispatcher/multi_default_test.clj index ddecee4..95e95ac 100644 --- a/test/methodical/impl/dispatcher/multi_default_test.clj +++ b/test/methodical/impl/dispatcher/multi_default_test.clj @@ -22,7 +22,7 @@ [default default default]] (multi-default/partially-specialized-default-dispatch-values [x y z] default)))))) -(t/deftest partially-specialized-default-dispatch-values-test +(t/deftest partially-specialized-default-dispatch-values-test-2 (doseq [default [:default ::default :a]] (doseq [x [:x nil]] (t/testing (format "dispatch value = %s" (pr-str x)) @@ -64,7 +64,7 @@ [nil :y] '[dy dd d] [default default] '[dd d] default '[d]} - ;; these are merged seperately in in case default *is* nil + ;; these are merged separately in in case default *is* nil {[nil nil] '[dd d] nil '[d]} {[default nil] '[dd d]} diff --git a/test/methodical/impl/method_table/standard_test.clj b/test/methodical/impl/method_table/standard_test.clj index f45106d..259f51d 100644 --- a/test/methodical/impl/method_table/standard_test.clj +++ b/test/methodical/impl/method_table/standard_test.clj @@ -1,32 +1,32 @@ (ns methodical.impl.method-table.standard-test (:require [clojure.test :as t] - [methodical.impl.method-table.standard :refer [->StandardMethodTable]] + [methodical.impl.method-table.standard :as standard] [methodical.interface :as i])) (t/deftest print-test (t/is (= "(standard-method-table)" - (pr-str (->StandardMethodTable {} {}))) + (pr-str (standard/->StandardMethodTable {} {}))) "Empty method tables should print simply.") (t/is (= "(standard-method-table 2 primary)" - (pr-str (->StandardMethodTable {:a +, :b +} {}))) + (pr-str (standard/->StandardMethodTable {:a +, :b +} {}))) "Method tables should print the count of primary methods if it has any.") (t/is (= "(standard-method-table 1 :after 3 :before)" - (pr-str (->StandardMethodTable {} {:before {:a [+] :b [+ +]}, :after {:a [+]}}))) + (pr-str (standard/->StandardMethodTable {} {:before {:a [+] :b [+ +]}, :after {:a [+]}}))) "Method tables should print the count of aux methods if it hash any.") (t/is (= "(standard-method-table 1 primary 1 :after 3 :before)" - (pr-str (->StandardMethodTable {:a +} {:before {:a [+] :b [+ +]}, :after {:a [+]}}))) + (pr-str (standard/->StandardMethodTable {:a +} {:before {:a [+] :b [+ +]}, :after {:a [+]}}))) "Method tables should be able to print both primary + aux counts.") (t/is (= "(standard-method-table 2 :before)" - (pr-str (->StandardMethodTable {} {:before {:b [+ +]}, :after {:a []}, :around {}}))) + (pr-str (standard/->StandardMethodTable {} {:before {:b [+ +]}, :after {:a []}, :around {}}))) "Method tables shouldn't print counts aux qualifiers that are empty.")) (t/deftest add-dispatch-value-metadata-test (t/testing "should add ^:dispatch-value metadata to methods when you add them" - (let [table (-> (->StandardMethodTable {} {}) + (let [table (-> (standard/->StandardMethodTable {} {}) (i/add-primary-method [:x :y] 'f) (i/add-aux-method :before :x 'f))] (t/testing "primary method" diff --git a/test/methodical/macros_test.clj b/test/methodical/macros_test.clj index 04c1579..6a97cfb 100644 --- a/test/methodical/macros_test.clj +++ b/test/methodical/macros_test.clj @@ -67,7 +67,7 @@ [m] (assoc m :method :x)) -(m/define-aux-method mf2 :before :default +(m/define-aux-method mf2 :before :default [m] (assoc m :before? true)) @@ -88,7 +88,7 @@ ;; make a var that is basically an alias for another var. This mimics the way Potemkin import-vars works (intern *ns* 'mf4 mf3) -(alter-meta! #'mf4 (constantly (meta #'mf3))) +(alter-meta! #_{:clj-kondo/ignore [:unresolved-symbol]} #'mf4 (constantly (meta #'mf3))) (p.namespaces/link-vars #'mf3 #'mf4) diff --git a/test/methodical/test_runner.clj b/test/methodical/test_runner.clj new file mode 100644 index 0000000..beac1b5 --- /dev/null +++ b/test/methodical/test_runner.clj @@ -0,0 +1,121 @@ +(ns methodical.test-runner + "Simple wrapper to let us use eftest with the Clojure CLI. Pass `:only` to specify where to look for tests (see dox + for [[find-tests]] for more info.)" + (:require + [clojure.java.classpath :as classpath] + [clojure.java.io :as io] + [clojure.pprint :as pprint] + [clojure.string :as str] + [clojure.tools.namespace.find :as ns.find] + [eftest.report.pretty] + [eftest.report.progress] + [eftest.runner] + [environ.core :as env] + [humane-are.core :as humane-are] + [pjstadig.humane-test-output :as humane-test-output])) + +(set! *warn-on-reflection* true) + +(humane-test-output/activate!) +(humane-are/install!) + +(defmulti ^:private find-tests + "Find test vars in `arg`, which can be a string directory name, symbol naming a specific namespace or test, or a + collection of one or more of the above." + {:arglists '([arg])} + type) + +;; collection of one of the things below +(defmethod find-tests clojure.lang.Sequential + [coll] + (mapcat find-tests coll)) + +;; directory name +(defmethod find-tests String + [dir-name] + (find-tests (io/file dir-name))) + +(defmethod find-tests java.io.File + [^java.io.File file] + (when (.isDirectory file) + (println "Looking for test namespaces in directory" (str file)) + (->> (ns.find/find-namespaces-in-dir file) + (filter (fn [ns-symb] + (str/ends-with? ns-symb "-test"))) + (mapcat find-tests)))) + +;; a test namespace or individual test +(defmethod find-tests clojure.lang.Symbol + [symb] + (if-let [ns-symb (some-> (namespace symb) symbol)] + ;; a actual test var e.g. `toucan2.whatever-test/my-test` + (do + (require ns-symb) + [(resolve symb)]) + ;; a namespace e.g. `toucan2.whatever-test` + (do + (require symb) + (eftest.runner/find-tests symb)))) + +;; default -- look in all dirs on the classpath +(defmethod find-tests nil + [_] + (find-tests (classpath/system-classpath))) + +(defn- tests [{:keys [only]}] + (when only + (println "Running tests in" (pr-str only))) + (find-tests only)) + +;;;; Running tests & reporting the output + +(def ^:private ci? (some-> (env/env :ci) str/lower-case parse-boolean)) + +(defn- reporter + "Create a new test reporter/event handler, a function with the signature `(handle-event event)` that gets called once + for every [[clojure.test]] event, including stuff like `:begin-test-run`, `:end-test-var`, and `:fail`." + [] + (let [stdout-reporter (if ci? + eftest.report.pretty/report + eftest.report.progress/report)] + (fn handle-event [event] + #_(test-runner.junit/handle-event! event) ; TODO + (stdout-reporter event)))) + +(defn run + "Run `test-vars` with `options`, which are passed directly to [[eftest.runner/run-tests]]. + + ;; run tests in a single namespace + (run (find-tests 'toucan2.bad-test)) + + ;; run tests in a directory + (run (find-tests \"test/toucan2/my_test\"))" + ([test-vars] + (run test-vars nil)) + + ([test-vars options] + (merge + (eftest.runner/run-tests + test-vars + (merge + {:capture-output? true + :multithread? :vars + :report (reporter)} + options))))) + +;;;; `clojure -X` entrypoint + +(defn run-tests + "`clojure -X` entrypoint for the test runner. `options` are passed directly to `eftest`; see + https://github.com/weavejester/eftest for full list of options. + + To use our test runner from the REPL, use [[run]] instead." + [options] + (let [start-time-ms (System/currentTimeMillis) + summary (run (tests options) options) + fail? (pos? (+ (:error summary) (:fail summary)))] + (println "Running tests with options" (pr-str options)) + (pprint/pprint summary) + (printf "Finding and running tests took %.1f seconds.\n" (/ (- (System/currentTimeMillis) start-time-ms) 1000.0)) + (println (if fail? "Tests failed." "All tests passed.")) + (System/exit (if fail? 1 0)))) diff --git a/test/methodical/util/trace_test.clj b/test/methodical/util/trace_test.clj index a8390a8..6e0468c 100644 --- a/test/methodical/util/trace_test.clj +++ b/test/methodical/util/trace_test.clj @@ -80,7 +80,7 @@ (def ^:private lots-of-args-multifn (-> (m/default-multifn - (fn [a b c d e f] [a (class b) c d e])) + (fn [a b c d e _f] [a (class b) c d e])) (m/add-primary-method :default (fn [_ a _ _ _ _ f] {:a a, :f f})) (m/add-primary-method [::x :default :default :default :default] @@ -126,7 +126,7 @@ ;; ;; #object[clojure.core$int_QMARK_ 0x3e07ccbf \"clojure.core$int_QMARK_@3e07ccbf\"] ;; - ;; so to make this test pass in either situation highjack `pr-str` which we use to print functions. + ;; so to make this test pass in either situation hijack `pr-str` which we use to print functions. (let [orig-pr-str pr-str] (with-redefs [pr-str (fn [x] (if (identical? x int?) diff --git a/test/methodical/util_test.clj b/test/methodical/util_test.clj index 31bd459..91d7dcb 100644 --- a/test/methodical/util_test.clj +++ b/test/methodical/util_test.clj @@ -1,9 +1,11 @@ (ns methodical.util-test - (:require [clojure.test :as t] - [methodical.core :as m] - [methodical.impl :as impl] - [methodical.interface :as i] - [methodical.util :as u])) + (:require + [clojure.test :as t] + [clojure.walk :as walk] + [methodical.core :as m] + [methodical.impl :as impl] + [methodical.interface :as i] + [methodical.util :as u])) (t/deftest multifn?-test (t/is (= false @@ -67,34 +69,39 @@ ;; aux-methods (t/deftest aux-methods-test - (let [m1 #(conj % :before-1) - m2 #(conj % :before-2) - m3 #(conj % :before-3) - f (-> (m/default-multifn class) - (m/add-aux-method :before String m1) - (m/add-aux-method :before String m2) - (m/add-aux-method :before Object m3) - (m/add-aux-method :after String m2) - (m/add-aux-method :after Object m3))] - (t/testing "aux-methods" - (t/is (= - {:before [m1 m2]} - (u/aux-methods f :before String)) - "3-arity") - - (t/is (= - {:before [m1 m2] - :after [m2]} - (u/aux-methods f String)) - "2-ariy") - - (t/is (= - {:before {String [m1 m2] - Object [m3]} - :after {String [m2] - Object [m3]}} - (u/aux-methods)) - "1-arity")))) + (t/testing "aux-methods" + (let [m1 #(conj % :before-1) + m2 #(conj % :before-2) + m3 #(conj % :before-3) + f (-> (m/default-multifn class) + (m/add-aux-method :before String m1) + (m/add-aux-method :before String m2) + (m/add-aux-method :before Object m3) + (m/add-aux-method :after String m2) + (m/add-aux-method :after Object m3))] + (letfn [(replace-fns-with-dispatch-value-metadata [form] + (walk/postwalk + (fn [form] + (if (fn? form) + (:dispatch-value (meta form)) + form)) + form))] + (t/testing "3-arity" + (t/is (= [String String] + (replace-fns-with-dispatch-value-metadata + (u/aux-methods f :before String))))) + (t/testing "2-ariy" + (t/is (= {:before [String String] + :after [String]} + (replace-fns-with-dispatch-value-metadata + (u/aux-methods f String))))) + (t/testing "1-arity" + (t/is (= {:before {String [String String] + Object [Object]} + :after {String [String] + Object [Object]}} + (replace-fns-with-dispatch-value-metadata + (u/aux-methods f))))))))) (t/deftest default-methods-test (let [m1 (constantly [:char-sequence]) @@ -121,7 +128,7 @@ (def ^:private lots-of-args-multifn (-> (m/default-multifn - (fn [a b c d e f] [a (class b) c d e])) + (fn [a b c d e _f] [a (class b) c d e])) (m/add-primary-method :default (fn [_ a _ _ _ _ f] {:a a, :f f})) (m/add-primary-method [::x :default :default :default :default] @@ -270,7 +277,7 @@ (t/is (= nil (seq (i/primary-methods remove-primary-method-multifn))))))) -(t/deftest aux-methods-test +(t/deftest aux-methods-test-2 (let [f (-> (m/default-multifn class) (m/add-aux-method :before String 'm1) (m/add-aux-method :before Object 'm2)