Can't get keys from a hashmap in guestbook app - clojure

I'm playing with the luminus guestbook app.
I added some logging statements in g.test.db.core.clj to look at the keys of the data structures. Look at "Expected Keys" and "Actual Keys" below.
(deftest test-messages
(jdbc/with-db-transaction [t-conn db/conn]
(jdbc/db-set-rollback-only! t-conn)
(let [timestamp (java.util.Date.)]
(is (= 1 (db/save-message!
{:name "Bobby"
:message "Hello World!"
:timestamp timestamp}
{:connection t-conn})))
(let [actual (-> (db/get-messages {} {:connection t-conn}))
expected {:name "Bobby"
:message "Hello World!"
:timestamp timestamp}]
(log/info "Expected Keys")
(pprint (keys expected))
(log/info "Actual Keys")
(pprint (keys actual)) ;;<--this is the problem
(is (= actual
expected))))))
The "Expected Keys" print fine, but I get a runtime exception for "Actual Keys":
[2017-03-04 14:31:28,076]Expected Keys
(:name :message :timestamp)
[2017-03-04 14:31:28,089]Actual Keys
lein test :only guestbook.test.db.core/test-messages
ERROR in (test-messages) (:) Uncaught exception, not in assertion.
expected: nil actual: java.lang.ClassCastException: null at [empty
stack trace]
lein test guestbook.test.handler
However, if I do this:
(pprint actual)
I get what I want:
({:id 35,
:name "Bobby",
:message "Hello World!",
:timestamp #inst "2017-03-04T04:31:01.030000000-00:00"})
What is going on? Why can't I print the keys from the data structure returned from the database?

It looks like actual is a list and not a map. Try (-> actual first keys pprint)

Related

How to test the message of an expected exception in a Clojure test?

I have following Clojure code:
(defn is-valid-lang
[lang]
(.contains (list "ru" "en" "de") lang))
(defn s
[txt]
{
:desc txt
:lang "ru"
})
(defn s
[txt lang]
(if-not (is-valid-lang lang)
(throw (.IllegalArgumentException (format "'%s' is an invalid language." lang)))
{
:desc txt
:lang lang
}
)
)
I want to write a test that verifies the message of the exception that s throws, if lang is not valid.
I wrote this:
(deftest s-lang-invalid-lang-test
(is
(thrown-with-msg?
IllegalArgumentException
#"'invalid-language' is an invalid language."
(s "something" "invalid-language")
)
)
)
The test fails due to the following error:
error {
:cause "No matching field found: IllegalArgumentException for class java.lang.String"
:via
[{:type java.lang.IllegalArgumentException
:message "No matching field found: IllegalArgumentException for class java.lang.String"
:at [clojure.lang.Reflector getInstanceField "Reflector.java" 397]}]
:trace
How can I fix this and make sure that the test succeeds if s throws an exception with the correct message?
Update 1: Fixed the multiple arity issue pointed out by cfrick.
(defn s
([txt]
{
:desc txt
:lang "ru"
})
([txt lang]
(if-not (is-valid-lang lang)
(throw (IllegalArgumentException. (format "'%s' is an invalid language." lang)))
{
:desc txt
:lang lang
})
)
)
You're not correctly instantiating the IllegalArgumentException. Clojure uses:
(IllegalArgumentException. "your message")
to call Java constructors. See this article on Java interop
As an alternate technique, you can use a regex search of the Exception error message as demonstrated below:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn thrower
[lang]
(throw (ex-info "invalid language" {:lang lang})))
(dotest
(throws? (thrower "bogosity")) ; just verify it throws an exception, ignoring type and message
; `(throws? ...)` is a convenience fn from tupelo.test (also `dotest`)
(try
(thrower "bogosity")
(catch Exception e
(let [s (str e)]
(is (re-find #"invalid language" s)) ; returns "invalid language"
(is (re-find #"bogosity" s))) ; returns "bogosity"
)))
In a unit test, it is often easier to search for an expected string fragment (such as in in the Exception error message) than it is to do a string equality comparison. The full Exception string is:
clojure.lang.ExceptionInfo: invalid language {:lang "bogosity"}
We use the tupelo.test library for convenience. The function is-nonblank= is also handy.
From tupelo.string, we also have these helper functions:
lowercase=
nonblank-lines=
nonblank=
contains-match?
contains-str?
quotes->single
quotes->double
and many others that make writing string-based tests much easier.

How to document a defmethod function?

As described in the defmethod doc, they can be given a name. What is very useful in stack traces.
(defmethod foo "a" name-of-method [params] "was a")
When replacing name-of-method by a string. I get the following error:
:cause "Call to clojure.core/fn did not conform to spec."
:data #:clojure.spec.alpha{:problems ({:path [:fn-tail :arity-1 :params], :pred clo
jure.core/vector?, :val "A documentation", :via [:clojure.core.specs.alpha/params+bo
dy :clojure.core.specs.alpha/param-list :clojure.core.specs.alpha/param-list], :in [
0]} {:path [:fn-tail :arity-n], :pred (clojure.core/fn [%] (clojure.core/or (clojure
.core/nil? %) (clojure.core/sequential? %))), :val "A documentation", :via [:clojure
.core.specs.alpha/params+body :clojure.core.specs.alpha/params+body], :in [0]}), :sp
ec #object[clojure.spec.alpha$regex_spec_impl$reify__2509 0x24de08c1 "clojure.spec.a
lpha$regex_spec_impl$reify__2509#24de08c1"]
So what's the propper way to document a defmethod?
You can document the defmethod function using a symbol like the following:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defmulti ident
(fn [item] (type item)) )
; vvvvvv-function-name
(defmethod ident clojure.lang.Keyword ident-fn-kw [item]
"to my heart")
(defmethod ident java.lang.String ident-fn-str [item]
"like cheese")
(defmethod ident clojure.lang.Symbol ident-fn-sym [item]
"crash, bang"
;(throw (ex-info "surprise!" {:reason :dont-need-one}))
)
(dotest
(is= clojure.lang.Keyword (spyx (type :hello)))
(is= java.lang.String (spyx (type "hello")))
(is= clojure.lang.Symbol (spyx (type 'hello)))
(is= "to my heart" (ident :hello))
(is= "like cheese" (ident "hello"))
(is= "crash, bang" (ident 'hello))
)
with result:
----------------------------------
Clojure 1.10.1 Java 13-ea
----------------------------------
Testing tst.demo.core
(type :hello) => clojure.lang.Keyword
(type "hello") => java.lang.String
(type (quote hello)) => clojure.lang.Symbol
Ran 2 tests containing 6 assertions.
0 failures, 0 errors.
and if you uncomment the throw you will get the symbol listed in any Exception stacktrace like this:
ERROR in (dotest-line-19) (core.clj:16)
expected: (clojure.core/= "crash, bang" (ident (quote hello)))
actual: clojure.lang.ExceptionInfo: surprise!
{:reason :dont-need-one}
at tst.demo.core$eval23035$ident_fn_sym__23036.doInvoke (core.clj:16)
clojure.lang.RestFn.invoke (RestFn.java:408)
clojure.lang.MultiFn.invoke (MultiFn.java:229)
tst.demo.core$fn__23039.invokeStatic (core.clj:26)
tst.demo.core/fn (core.clj:19)
Notice that the "function name" is included in the stacktrace as tst.demo.core$eval23035$ident_fn_sym__23036. As with any "anonymous" function, this can be of great use in tracking down the cause of an exception. For example:
(dotest
(let [add2 (fn add2-fn [a b] (+ a b))]
(is= 5 (add2 2 3))))
Here we use add2-fn to name the function as a debugging aid in case of an exception. I like to use a -fn suffix as it helps speed grep-based searching of source code.
Update:
Here is the info from clojuredocs.org
;; Methods can be given a name. Very useful in stack traces.
(defmethod foo "a" name-of-method [params] "was a")
So name-of-method is a symbol, not a docstring.
P.S. Note that, to the user, the fact that a function is implemented as a multi-method (vs a regular function with a cond or if or something else), is really just a implementation detail. The contract of the function itself (name, args, purpose, etc) is defined in the defmulti form, and that is where the docstring belongs.

Using Clojure's data structure with MapDB

I tried to use directly Clojure's hashmap with MapDB and ran into weird behaviour. I checked Clojure and MapDB sources and couldn't understand the problem.
First everything looks fine:
lein try org.mapdb/mapdb "1.0.6"
; defining a db for the first time
(import [org.mapdb DB DBMaker])
(defonce db (-> (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
.closeOnJvmShutdown
.compressionEnable
.make))
(defonce fruits (.getTreeMap db "fruits-store"))
(do (.put fruits :banana {:qty 2}) (.commit db))
(get fruits :banana)
=> {:qty 2}
(:qty (get fruits :banana))
=> 2
(first (keys (get fruits :banana)))
=> :qty
(= :qty (first (keys (get fruits :banana))))
=> true
CTRL-D
=> Bye for now!
Then I try to access the data again:
lein try org.mapdb/mapdb "1.0.6"
; loading previsously created db
(import [org.mapdb DB DBMaker])
(defonce db (-> (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
.closeOnJvmShutdown
.compressionEnable
.make))
(defonce fruits (.getTreeMap db "fruits-store"))
(get fruits :banana)
=> {:qty 2}
(:qty (get fruits :banana))
=> nil
(first (keys (get fruits :banana)))
=> :qty
(= :qty (first (keys (get fruits :banana))))
=> false
(class (first (keys (get fruits :banana))))
=> clojure.lang.Keyword
How come the same keyword be different with respect to = ?
Is there some weird reference problem happening ?
The problem is caused by the way equality of keywords works. Looking at the
implementation of the = function we see that since keywords are not
clojure.lang.Number or clojure.lang.IPersistentCollection their equality is
determined in terms of the Object.equals method. Skimming the source of
clojure.lang.Keyword we learn that keywords don't not override
Object.equals and therefore two keywords are equal iff they are the same
object.
The default serializer of MapDB is org.mapdb.SerializerPojo, a subclass of
org.mapdb.SerializerBase. In its documentation we can read that
it's a
Serializer which uses ‘header byte’ to serialize/deserialize most of classes
from ‘java.lang’ and ‘java.util’ packages.
Unfortunately, it doesn't work that well with clojure.lang classes; It doesn't
preserve identity of keywords, thus breaking equality.
In order to fix it let's attempt to write our own serializer using the
EDN format—alternatively, you could consider, say, Nippy—and use
it in our MapDB.
(require '[clojure.edn :as edn])
(deftype EDNSeralizer []
;; See docs of org.mapdb.Serializer for semantics.
org.mapdb.Serializer
(fixedSize [_]
-1)
(serialize [_ out obj]
(.writeUTF out (pr-str obj)))
(deserialize [_ in available]
(edn/read-string (.readUTF in)))
;; MapDB expects serializers to be serializable.
java.io.Serializable)
(def edn-serializer (EDNSeralizer.))
(import [org.mapdb DB DBMaker])
(def db (.. (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
closeOnJvmShutdown
compressionEnable
make))
(def more-fruits (.. db
(createTreeMap "more-fruits")
(valueSerializer (EDNSeralizer.))
(makeOrGet)))
(.put more-fruits :banana {:qty 2})
(.commit db)
Once the more-fruits tree map is reopened in a JVM with EDNSeralizer defined
the :qty object stored inside will be the same object as any other :qty
instance. As a result equality checks will work properly.

Clojure multimethod giving unexpected null pointer

I'm having a hard time getting the multimethods in Clojure to work as I would expect. A distillation of my code is as follows.
(defn commandType [_ command] (:command-type command))
(defmulti testMulti commandType)
(defmethod testMulti :one [game command] (str "blah"))
(defmethod testMulti :default [& args] "Cannot understand")
(testMulti "something" {:command-type :one})
(commandType "something" {:command-type :one})
Now I would expect here to have the method commandType called on the arguments which would of course return :one which should send it to the first defmethod but instead I get a null pointer exception. Even the simplest invocation of a multimethod I could come up with gives me a null pointer:
(defmulti simpleMulti :key)
(defmethod simpleMulti "basic" [params] "basic value")
(simpleMulti {:key "basic"})
And yet the example in the clojure docs located here works fine. Is there something fundamental I'm doing wrong?
So far as I can see, it works.
Given
(defmulti testMulti (fn [_ command] (:command-type command)))
(defmethod testMulti :one [game command] (str "blah"))
(defmethod testMulti :default [& args] "Cannot understand")
then
(testMulti "something" {:command-type :one})
; "blah"
(testMulti "something" {:command-type :two})
; "Cannot understand"
(testMulti "something" 5)
; "Cannot understand"
as expected.
I reset the REPL before running the above afresh.
And the simple example works too. Given
(defmulti simpleMulti :key)
(defmethod simpleMulti "basic" [params] "basic value")
then
(simpleMulti {:key "basic"})
; "basic value"

Clojure annotations and Integers

I am adding Swagger annotations to JaxRs annotated services.
I have the following:
(^{
GET true
Path "/{who}"
ApiOperation {:value "Get a hello" :notes "simple clojure GET"}
Produces ["text/plain; charset=UTF-8"]
ApiResponses {:value [(ApiResponse {:code 200 :message "yay!"})]}
}
If I decompile the produced class the annotations look like this:
#ApiResponses({#com.wordnik.swagger.annotations.ApiResponse(code=200L, message="yay!")})
#Produces({"text/plain; charset=UTF-8"})
#ApiOperation(value="Get a hello", notes="simple clojure GET")
#Path("/{who}")
#GET(true)
notes that in the first annotation code = 200L
During runtime, this value must be an int, and I cannot figure out how to make this happen
if I try
ApiResponses {:value [(ApiResponse {:code (int 200) :message "yay!"})]}
I get a compilation error (using the maven swagger plugin)
Exception in thread "main" java.lang.ClassCastException: clojure.lang.Var cannot be cast to java.lang.Class, compiling:(pocclj/resourceclj.clj:14)
I have tried
(def success (int 200))
...
ApiResponses {:value [(ApiResponse {:code success :message "yay!"})]}
Which produces this compilation error:
Exception in thread "main" java.lang.IllegalArgumentException: Unsupported annotation value: success of class class java.lang.Integer, compiling:(pocclj/resourceclj.clj:14)
I have tried a bunch of other stuff (deref etc) but cant find the secret sauce.
I am fairly new to clojure and desperate for some help on this.
Thanks in advance
Martin
You are setting the type of ':code' correctly. Which can be tested independently:
user> (def something ^{:ApiResponses {:code (int 200) :message "yay!"}} {:some :data :goes :here})
#'user/something
user> (meta something)
{:ApiResponses {:code 200, :message "yay!"}}
user> (-> (meta something) :ApiResponses :code type)
java.lang.Integer
And without the cast the metadata contains the wrong type:
user> (def something-wrong ^{:ApiResponses {:code 200 :message "yay!"}} {:some :data :goes :here})
#'user/something-wrong
user> (meta something)
{:ApiResponses {:code 200, :message "yay!"}}
user> (-> (meta something-wrong) :ApiResponses :code type)
java.lang.Long
From the exception it looks like perhaps the call to ApiResponse is crashing. If ApiResponse is a macro that expects a number and not an s-expression, then I could see it not handling this properly. If it is a function you would need to look into why it is crashing.
If I provide a stub implementation for ApiResponse then It works for me:
user> (definterface Fooi (Something []))
user.Fooi
user> (def ApiResponse identity)
#'user/ApiResponse
user> (deftype Foo []
Fooi
(Something
^{GET true
Path "/{who}"
ApiOperation {:value "Get a hello" :notes "simple clojure GET"}
Produces ["text/plain; charset=UTF-8"]
ApiResponses {:value [(ApiResponse {:code (int 200) :message "yay!"})]}}
[this] (identity this)))
user.Foo
I don't know about ApiResponse, or much about annotations really, but: it looks like some macro (deftype?) is producing annotations for you, and you need it to see 200 as an int. Clojure doesn't have int literals, so the only way to hand an Integer object directly to a macro is through some other macro that calls it. It's not really possible to do this in a nice way; as far as I know you have to either use eval, or be very narrow by aiming specifically at int literals. Here's a sketch of a solution:
user> (use 'clojure.walk)
user> (defmacro with-int-literals [named-ints & body]
(prewalk-replace (into {}
(for [[k v] (partition 2 named-ints)]
[k (int v)]))
`(do ~#body)))
user> (map class (second (macroexpand-1 '(with-int-literals [x 100, y 200] [x y]))))
(java.lang.Integer java.lang.Integer)
So if you wrap your entire deftype (or whatever macro is generating these annotations) with a with-int-literals form, you can produce integers for it instead of longs. I don't actually know that this will work; perhaps there's something in the annotation processor that's fundamentally unable to handle ints for some reason. But at least this way you can offer it ints and hope for the best.
Edit
Since you actually need int literals in metadata, rather than in "ordinary" code, prewalk won't actually look at the data you care about. You'll have to write a version of walk that treats metadata in a sensible way, and then use it instead of prewalk here.