I'm using clojure new CLI to create a project and to run the tests I do:
clojure -M:test:runner
this is my test
(ns auth.authorize-test
(:require [clojure.test :refer :all]
[auth.authorize :as auth]))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 0))))
;; this is working good and responding:
;; Ran 1 tests containing 1 assertions.
This project will read from stdin. My first step is to get data from a file and then write it to the stdout for testing. Because of this I want import BufferedReader to read the file:
(ns auth.authorize-test
(:require [clojure.test :refer :all]
[auth.authorize :as auth])
;; I just added this line
(:import (java.io BufferedReader))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 0))))
;; This is not working and it's returning
;; Ran 0 tests containing 0 assertions.
So now is no longer doing any tests.
The most likely reason is that you have some sort of syntax error in your code.
Your code should be:
(ns auth.authorize-test
(:require [clojure.test :refer :all]
[auth.authorize :as auth])
;; I just added this line
(:import (java.io BufferedReader)))
Note: three closing ) there, not two like you have in your code.
Here's what I just did:
(! 734)-> clojure -X:new :name auth.authorize
Generating a project called auth.authorize based on the 'lib' template.
The lib template is intended for library projects, not applications.
(! 735)-> cd auth.authorize/
(! 736)-> vi test/auth/authorize_test.clj
# to change the test to (= 0 0)
(! 737)-> cat test/auth/authorize_test.clj
(ns auth.authorize-test
(:require [clojure.test :refer :all]
[auth.authorize :refer :all]))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 0))))
(! 738)-> clojure -M:test:runner
Running tests in #{"test"}
Testing auth.authorize-test
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
(! 739)-> vi test/auth/authorize_test.clj
# add in the :import
(! 740)-> cat test/auth/authorize_test.clj
(ns auth.authorize-test
(:require [clojure.test :refer :all]
[auth.authorize :refer :all])
(:import (java.io BufferedReader)))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 0))))
(! 741)-> clojure -M:test:runner
Running tests in #{"test"}
Testing auth.authorize-test
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
And you can see that it runs the test correctly.
Adding to Sean's answer, this is what the compiler sees:
(ns auth.authorize-test
(:require [clojure.test :refer :all]
[auth.authorize :as auth])
(:import (java.io BufferedReader)) ; should terminate ns form here with another `)` char
; Due to missing `)` above, this whole `deftest` form is inside the 'ns' form
; and is ignored w/o an error msg (you could consider this a compiler defect)
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 0))))
) ; end of ns form - should be after `import` keyword
So you see, the missing ) on the :import line is the cause of the problem. It causes the deftest form to be inside of the ns form, which makes no sense and is a syntax error. I'm surprised the compiler doesn't have an error for this, which I would consider a compiler bug/defect/problem, since it just silently fails instead of printing an error message of some sort.
Update
Perhaps you are using an older release of Clojure? I just tried it with this config:
--------------------------------------
Clojure 1.10.2-alpha1 Java 15
--------------------------------------
and I get an error:
Syntax error macroexpanding clojure.core/ns at (tst/demo/core.clj:1:1).
deftest - failed: #{:refer-clojure} at: [:ns-clauses :refer-clojure :clause] spec: :clojure.core.specs.alpha/ns-refer-clojure
deftest - failed: #{:require} at: [:ns-clauses :require :clause] spec: :clojure.core.specs.alpha/ns-require
deftest - failed: #{:import} at: [:ns-clauses :import :clause] spec: :clojure.core.specs.alpha/ns-import
deftest - failed: #{:use} at: [:ns-clauses :use :clause] spec: :clojure.core.specs.alpha/ns-use
deftest - failed: #{:refer} at: [:ns-clauses :refer :clause] spec: :clojure.core.specs.alpha/ns-refer
deftest - failed: #{:load} at: [:ns-clauses :load :clause] spec: :clojure.core.specs.alpha/ns-load
deftest - failed: #{:gen-class} at: [:ns-clauses :gen-class :clause] spec: :clojure.core.specs.alpha/ns-gen-class
Full report at:
/tmp/clojure-8770599721946785921.edn
Tests failed.
and the /tmp/clojure.....edn file looks like:
{:clojure.main/message
"Syntax error macroexpanding clojure.core/ns at (tst/demo/core.clj:1:1).\ndeftest - failed: #{:refer-clojure} at: [:ns-clauses :refer-clojure :clause] spec: :clojure.core.specs.alpha/ns-refer-clojure\ndeftest - failed: #{:require} at: [:ns-clauses :require :clause] spec: :clojure.core.specs.alpha/ns-require\ndeftest - failed: #{:import} at: [:ns-clauses :import :clause] spec: :clojure.core.specs.alpha/ns-import\ndeftest - failed: #{:use} at: [:ns-clauses :use :clause] spec: :clojure.core.specs.alpha/ns-use\ndeftest - failed: #{:refer} at: [:ns-clauses :refer :clause] spec: :clojure.core.specs.alpha/ns-refer\ndeftest - failed: #{:load} at: [:ns-clauses :load :clause] spec: :clojure.core.specs.alpha/ns-load\ndeftest - failed: #{:gen-class} at: [:ns-clauses :gen-class :clause] spec: :clojure.core.specs.alpha/ns-gen-class\n",
:clojure.main/triage
{:clojure.error/cause
"Call to clojure.core/ns did not conform to spec.",
:clojure.error/phase :macro-syntax-check,
:clojure.error/symbol clojure.core/ns,
:clojure.error/column 1,
:clojure.error/line 1,
:clojure.error/class clojure.lang.ExceptionInfo,
:clojure.error/source "core.clj",
:clojure.error/spec
{:clojure.spec.alpha/problems
({:path [:ns-clauses :refer-clojure :clause],
:pred #{:refer-clojure},
:val deftest,
:via
[:clojure.core.specs.alpha/ns-form
:clojure.core.specs.alpha/ns-refer-clojure
:clojure.core.specs.alpha/ns-refer-clojure],
:in [3 0]}
{:path [:ns-clauses :require :clause],
:pred #{:require},
:val deftest,
:via
[:clojure.core.specs.alpha/ns-form
:clojure.core.specs.alpha/ns-require
:clojure.core.specs.alpha/ns-require],
:in [3 0]}
{:path [:ns-clauses :import :clause],
:pred #{:import},
:val deftest,
:via
[:clojure.core.specs.alpha/ns-form
:clojure.core.specs.alpha/ns-import
:clojure.core.specs.alpha/ns-import],
:in [3 0]}
....
So it appears the current version of Clojure (which uses clojure.spec to check ns forms) does detect the error in the code.
Related
I have the following code as a test:
(defn test-fn [] (println "This is a test"))
(def my-test "test")
((resolve (symbol (str my-test "-fn"))))
Which runs as I would expect producing This is a test.
So I put it inside of a separate view like so:
(ns my-test.template-views
(:require
[hiccup.core :refer :all]
[hiccup.page :refer :all]
[my-test.home-views :refer :all]
[my-test.page1-views :refer :all]))
(defn template-body
[uri]
(html5 {:lang "en"}
[:body
(let [the-page (if (> (count uri) 1)
(clojure.string/replace uri #"/" "")
"home")]
((resolve (symbol (str the-page "-body")))))]))
Which gets called from Compojure like this:
(ns the-test.reporting-dashboard
(:gen-class)
(:require
[the-test.template-views :refer :all]
[compojure.core :refer [defroutes GET POST context]]
[compojure.route :as route]
[org.httpkit.server :refer [run-server]]
))
(defn wrap-request
[handler]
(fn [request]
(let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
(println (str "REQUEST: " request)))
(handler request)))
(defroutes app
(wrap-request
(GET "/" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (home-views/home-body (:uri request))}))
(wrap-request
(GET "/foo" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(route/resources "/")
(route/not-found {:status 404
:headers {"Content-Type" "text/html"}
:body "<h1>Not Found</h1>"}))
When I call https//mydomain.tld/foo I get a java.lang.NullPointerException:
user=> Sat Apr 24 17:59:16 MDT 2021 [worker-2] ERROR - GET /foo
java.lang.NullPointerException
at the-test.template_views$template_body.invokeStatic(template_views.clj:14)
at the-test.template_views$template_body.invoke(template_views.clj:12)
at the-test.my_test$fn__269541.invokeStatic(my_test.clj:49)
at the-test.my_test$fn__269541.invoke(my_test.clj:46)
at compojure.core$wrap_response$fn__269102.invoke(core.clj:158)
at compojure.core$wrap_route_middleware$fn__269086.invoke(core.clj:128)
at compojure.core$wrap_route_info$fn__269091.invoke(core.clj:137)
at compojure.core$wrap_route_matches$fn__269095.invoke(core.clj:146)
at the-test.my_test$wrap_request$fn__269531.invoke(my_test.clj:22)
at compojure.core$routing$fn__269110.invoke(core.clj:185)
at clojure.core$some.invokeStatic(core.clj:2705)
at clojure.core$some.invoke(core.clj:2696)
at compojure.core$routing.invokeStatic(core.clj:185)
at compojure.core$routing.doInvoke(core.clj:182)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invokeStatic(core.clj:669)
at clojure.core$apply.invoke(core.clj:662)
at compojure.core$routes$fn__269114.invoke(core.clj:192)
at org.httpkit.server.HttpHandler.run(RingHandler.java:117)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
What is going on that this function is no longer able to be called when in Compojure?
Not sure about the root cause, but I was able to find a similar, minimal example and a workaround.
I created a simple project with lein new app demo, then replaced the code for core.clj with:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((resolve 'capitalize) "hello")))
Running this with lein run crashes:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Syntax error (NullPointerException) compiling at (/tmp/form-init1769060018979063159.clj:1:73).
null
Full report at:
/tmp/clojure-17609112051049758700.edn
The error report shows something about an error in Compiler.java so maybe it's some lazy initialization issue or just a bug. It seems to me the var cannot be resolved at the time you are attempting to call it as a function, hence the NullPointerException.
A workaround would be to use ns-resolve instead:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((ns-resolve 'clojure.string 'capitalize) "hello")))
The above works as expected:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Hello
See if you can replace your usage of resolve in the view with ns-resolve if you know the namespace where the symbol needs to be looked-up.
In our Clojure codebase we have a protocol:
(ns project.repository)
(defprotocol Repository
(index [this fields unique]))
A type
(ns project.mongo (:require
[monger.collection :as mc]
[monger.core :as mg]
[project.repository :refer :all]))
(deftype MongoRepository [db collection-name]
Repository
(index [this fields unique]
(mc/ensure-index db collection-name fields {:unique unique})))
(defn mongo-repository [db coll] (MongoRepository. db coll))
(def mongo-db ((mg/connect-via-uri "mongodb://127.0.0.1/bots") :db))
And an instantiation
(ns project.users (:require
[lp-bots.storages.repository :refer :all]
[lp-bots.storages.mongo :refer [mongo-repository mongo-db]]))
(def users-storage (mongo-repository mongo-db "users"))
(index users-storage [:key1 :key2] true)
This works fine when used interactively from REPL or launched with lein run, but lein uberjar invariably throws an exception:
Exception in thread "main" java.lang.ExceptionInInitializerError, compiling:(/tmp/form-init118199196859405970.clj:1:72)
...
Caused by: java.lang.ExceptionInInitializerError
...
Caused by: java.lang.IllegalArgumentException: No implementation of method: :index of protocol: #'project.repository/Repository found for class: project.mongo.MongoRepository
at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:568)
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:560)
at project.repository$fn__557$G__514__566.invoke(repository.clj)
at project.users__init.load(Unknown Source)
at project.users__init.<clinit>(Unknown Source)
The weirdest part is that the problem goes away when (index) is called in a (let):
(def users-storage
(let [u (mongo-repository mongo-db "leads")]
(index u [:key1 :key2] true)
u))
Any thoughts on what might be causing the difference?
I would suspect that it's something to do with trying to connect to MongoDB when compiling the project.mongo ns, or the project.users ns. These lines should probably be in a function call that's called at startup, rather than as the code is loaded:
(def mongo-db ((mg/connect-via-uri "mongodb://127.0.0.1/bots") :db))
and
(index users-storage [:key1 :key2] true)
In my clojure project I built several java classes using gen-class command. They are [extractor.yaml YAMLExtractor YAMLExtractorFactory]. I wanted now build unit test against those classes but I have error: java.lang.ClassNotFoundException: extractor.yaml.YAMLExtractor when I run test.
File which cause the error is:
yaml_extrator_factory.clj
(ns extractor.yaml-extractor-factory
(:gen-class :name extractor.yaml.YAMLExtractorFactory
:extends org.apache.any23.extractor.SimpleExtractorFactory
:implements [org.apache.any23.extractor.ExtractorFactory]
:init init
:constructors {[] [String org.apache.any23.rdf.Prefixes java.util.Collection String]}
:main false)
(:require [extractor.yaml-extractor])
(:import [extractor.yaml YAMLExtractor]
[org.apache.any23.rdf Prefixes]
[org.apache.any23.extractor SimpleExtractorFactory ExtractorFactory Extractor ExtractionContext ]))
The error occurs only during testing. Whole project is AOT-compilable with no error and it is fine when I build a jar file as well.
The test simple.clj contains head:
(ns extractor.simple
(:use [clojure.tools.logging :as log]
[clojure.java.io :as jio]
[clojure.test :as test])
(:require [extractor.yaml-extractor-factory])
(:import [java.util Arrays]
[extractor.yaml.YAMLExtractorFactory]))
And test which prints CLASSPATH. yaml-extractor-factory was not used.
Test is run with command:
boot aot -a update-classpath run-test -t extractor.simple
where task update-classpath adds (get-env :directories) into classpath
and run-test runs a test. run-test works fine with normal clojure code.
run-test is my task with following content:
(deftask run-test "Run unit tests"
[t test-name NAME str "Test to execute. Run all tests if not given."]
(require '[extractor.simple])
(with-pass-thru [_]
(if (nil? test-name)
(do
(util/info "Run all tests")
(test/run-all-tests))
(do
(util/info (format "Run test: %s" (:test-name *opts*)))
(test/run-tests (symbol (:test-name *opts*)))
))))
I've a luminus project with some simple compojure-api routes.
I've added carmine to communicate with a redis server, using the wcar* macro (defined in services.clj) to make calls to it, and everything works fine.
Now I'm trying to add some tests, but seems that the redis connection doesn't works properly during them, because I'm receiving this error with lein test:
ERROR Carmine connection error
clojure.lang.ExceptionInfo: Carmine connection error {}
Since it's working in dev e prod environments, I think that is something related to a missing env load in the test environment, but I didn't find a way to solve it.
These are relevant parts of the code in use:
test.clj
(ns app.test.handler
(:require [clojure.test :refer :all]
[ring.mock.request :refer :all]
[app.handler :refer :all]))
(deftest test-app
(testing "redis ping"
(let [response ((app) (request :get "/api/redis-ping"))]
(is (= 200 (:status response))))))
services.clj
(ns app.routes.services
(:require [ring.util.http-response :refer :all]
[compojure.api.sweet :refer :all]
[schema.core :as s]
[app.config :refer [env]]
[clojure.tools.logging :as log]
[mount.core :refer [defstate]]
[taoensso.carmine :as car :refer (wcar)]))
(defmacro wcar* [& body] `(car/wcar
{:spec {:host (:redis-host env) :port (:redis-port env)}}
~#body))
(defapi service-routes
(context "/api" []
:tags ["myapi"]
(GET "/redis-ping" []
:return String
:summary "A redis client test."
(ok (wcar* (car/ping "hello"))))))
handler.clj
(ns app.handler
(:require [compojure.core :refer [routes wrap-routes]]
[app.routes.services :refer [service-routes]]
[compojure.route :as route]
[app.env :refer [defaults]]
[mount.core :as mount]
[app.middleware :as middleware]))
(mount/defstate init-app
:start ((or (:init defaults) identity))
:stop ((or (:stop defaults) identity)))
(def app-routes
(routes
#'service-routes
(route/not-found
"page not found")))
(defn app [] (middleware/wrap-base #'app-routes))
Profiles.clj
{:profiles/dev {:env {:redis-host "127.0.0.1" :redis-port 6381}}
:profiles/test {:env {:redis-host "127.0.0.1" :redis-port 6381}}}
Config.clj
(ns app.config
(:require [cprop.core :refer [load-config]]
[cprop.source :as source]
[mount.core :refer [args defstate]]))
(defstate env :start (load-config
:merge
[(args)
(source/from-system-props)
(source/from-env)]))
SOLUTION
Add a text fixture with the mount/start command that's executed before tests.
Add to test.clj:
(defn my-test-fixture [f]
(mount/start)
(f))
(use-fixtures :once my-test-fixture)
You are using mount to manage your application state lifecycle. I think you are not calling (mount/start) in your tests thus your app.config/env state is not initialized properly. On the other hand when you start your application (mount/start) is probably called and thus it's working correctly.
I have created a project with two namespaces, familja-moderne.core and familja-moderne.visualization.svg.
src/familja_moderne/core.clj
(ns familja-moderne.core
(:require [clojure.set :as set])
(:import (java.io ByteArrayInputStream)))
(def try-out
[(set/map-invert
{:1 :2
:3 :4})
(ByteArrayInputStream. (.getBytes "myBytes"))
(familja-moderne.visualization.svg/points heists)])
src/familja_moderne/visualization/svg.clj
(ns familja-moderne.visualization.svg
(:refer-clojure :exclude [max min]))
(def some-map {:this :that
:foo :bar})
(def many-dependencies
{:something (ByteArrayInputStream. (.getBytes "something"))
:another-map (set/map-invert some-map)})
When I run
lein slamhound code/familja-moderne/src/familja_moderne/visualization/svg.clj
I get
WARNING: ex-info already refers to: #'clojure.core/ex-info in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-info
WARNING: ex-data already refers to: #'clojure.core/ex-data in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-data
which I don't understand but it works and my ns form is reconstructed as
(ns familja-moderne.visualization.svg
(:require [clojure.set :as set])
(:import (java.io ByteArrayInputStream))
(:refer-clojure :exclude [max min]))
Running
lein slamhound code/familja-moderne/src/familja_moderne/core.clj
fails and I get the following messages
WARNING: ex-info already refers to: #'clojure.core/ex-info in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-info
WARNING: ex-data already refers to: #'clojure.core/ex-data in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-data
Failed to reconstruct: #<File code/familja-moderne/src/familja_moderne/core.clj>
java.lang.ClassNotFoundException: familja-moderne.visualization.svg, compiling:(NO_SOURCE_PATH:0:0)
Running
lein slamhound code/familja-moderne/src/familja_moderne/
which is supposed to reconstruct the ns forms in both namespaces results in something different
WARNING: ex-info already refers to: #'clojure.core/ex-info in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-info
WARNING: ex-data already refers to: #'clojure.core/ex-data in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-data
Failed to reconstruct: #<File code/familja-moderne/src/familja_moderne/core.clj>
Couldn't resolve familja-moderne.visualization.svg, got as far as {:import #{java.io.ByteArrayInputStream}, :alias {clojure.set set}, :old {:load nil, :exclude {}, :xrefer #{}, :require #{}, :refer-all #{}, :verbose #{}, :rename {}, :alias {clojure.set set}, :reload #{}, :reload-all #{}, :gen-class nil, :import #{java.io.ByteArrayInputStream}, :refer {}}, :meta nil, :name familja-moderne.core}
If I remove any reference to familja-moderne.visualization.svg from familja-moderne.core it works OK.
I was having the same issue and opened a PR to Slamhound with a fix: https://github.com/technomancy/slamhound/pull/87.
You can try it by doing a lein install from the dotted-alias branch in my fork.
My theory is that this issue happens because of the clojure bug CLJ-1403 which makes the exception to be thrown when Slamhound is trying to "regrow" the ns form.
Hope that helps!