I'm currently in the start of making a tool that should modify all the functions in another namespace, and the run the "main" function in that (other) namespace. Almost like mocking in unit-testing, but for another purpose. The first step to this is to redefine functions from another namespace. The first listing shows code that works, where I explicitly name the function to be replaced.
(ns one)
(defn a-fun []
(println "original"))
(defn caller []
(a-fun))
(ns two)
(with-redefs [one/a-fun #(println "replaced")]
(one/caller))
;; replaced
;; nil
The next step is getting the functions programmatically from the other namespace. This is where I have run into a wall. I've experimented much with ns-publics, but so far no luck. So I try to break the problem down. And the problem is that the replacement of the function is not working. The output should print "replaced" not "original".
(ns two)
(def target (ns-resolve 'one (symbol "a-fun")))
(def caller (ns-resolve 'one (symbol "caller")))
(with-redefs [target #(println "replaced")]
(caller))
;; original
;; nil
Now, the #'two/target shows #'one/a-fun when I evaluate it in the repl. I can call one/a-fun by entering ((ns-resolve 'one (symbol "a-fun"))) in the repl, so I don't understand what part is not working.
I've been through a lot of documentation today, and I'm not really getting any closer.
You could try using with-redefs-fn like so:
(defn p [] "old")
(with-redefs-fn
{(ns-resolve *ns* 'p) #(println "new")}
(fn []
(p)))
;; => new
This does mean that the body of with-redefs-fn must be a function that uses your redefined Vars.
Related
Trying to alias ns inside let to be able to use it locally, but got an error CompilerException java.lang.RuntimeException: No such namespace: sss when just trying to use alias
(ns core
(:require [clojure.set]
[clojure.string])
)
(let []
(alias 'sss 'clojure.string)
(println (ns-aliases *ns*) "hi1")
(println (sss/capitalize "hONdURas"))
;(println (clojure.string/capitalize "hONdURas")) ;;this works
(ns-unalias *ns* 'sss)
(+ 1 2)
)
(println (ns-aliases *ns*))
https://repl.it/repls/NoxiousRubberyComputationallinguistics
(alias ...) doesn't have to be top level. The way clojure works is that there is something called the reader that takes text data and turns it into data structures that are inputs to the compiler. See
https://clojure.org/reference/reader
Any namespaces referred to in the code have to already be defined for the reader prior to use. (Similarly for functions).
So, in
(let []
(alias 'sss 'clojure.string)
(println (sss/capitalize "aaa")))
the alias hasn't been assigned when the reader is trying to turn
(sss/capitalize)
into data.
In core.async, if you call the >! function outside of a go block, it throws error saying <! used not in (go ...) block. How does the function know that its not being executed inside of a go block? I need to create something very similar where I need to ensure that a function can only be called from inside of a macro. How can I approach that? I've looked at the core.async source but I couldn't really figure out how it worked.
Here is the source code for <!:
(defn <!
"Takes a value from the channel. Must be called inside a (go ...) block. Will
return nil if closed. Will park if nothing is available."
[port]
(assert nil "<! used not in (go ...) block"))
Note that assert will throw an exception for any falsey value, so you get this error message if you use <! outside of a go block.
When you use the go macro, it recursively walks all of the code expressions it encloses, replacing symbols like <! with other code that does not call the "error case" code shown above.
Addition to Alan Thompson's answer here is the code(go macro) that replacing symbols like <! with other code that does not call the error case:
Also [clojure.core.async.impl.ioc-macros :as ioc] namespace used for this purpose.
(dispatch/run
(^:once fn* []
(let [~#(mapcat (fn [[l sym]] [sym `(^:once fn* [] ~(vary-meta l dissoc :tag))]) crossing-env)
f# ~(ioc/state-machine `(do ~#body) 1 [crossing-env &env] ioc/async-custom-terminators)
state# (-> (f#)
(ioc/aset-all! ioc/USER-START-IDX c#
ioc/BINDINGS-IDX captured-bindings#))]
(ioc/run-state-machine-wrapped state#))))
I am reading a book to teach myself Clojure called Clojure for the Brave and True. Chapter 9 covers basic concurrent programming including delays, futures, and promises. The first exercise at the end of the chapter states:
"Write a function that takes a string as an argument and searches for it on Bing and Google using the slurp function. Your function should return the HTML of the first page returned by the search"
My solution is as follows:
(defn search-bing-google
[search-term]
(let [search-results (promise)]
(future (deliver search-results
(slurp (str "https://www.bing.com/search?q%3D" search-term))))
(future (deliver search-results
(slurp (str "https://www.google.com/search?q%3D" search-term))))
#search-results))
And can be called like:
(search-bing-google "clojure")
The second exercise is stated as:
"Update your function so it takes a second argument consisting of the search engines to use."
I attempted to edit my first solution to meet the new argument requirement as follows:
(def search-engines
{:bing "https://www.bing.com/"
:google "https://www.google.com/"})
(defn search
[search-term & engines]
(let [results (promise)]
(map #(future (deliver results
(slurp (str (% search-engines)
"search?q%3D"
search-term)))) engines)
#results))
and can be called like:
(search "clojure" :bing :google)
However this implementation hangs unlike it's predecessor. Its as if the slurp never gets invoked because of the "map" in the second implementation. Can anyone help me figure out what's causing this to hang when I load it up in the REPL and execute it?
EDIT:
With Josh's answer below I came up with the following solution that no longer hangs using doseq instead of dorun and a map:
(def search-engines
{:bing "https://www.bing.com/"
:google "https://www.google.com/"})
(defn search
[search-term & engines]
(let [results (promise)]
(doseq [engine engines]
(future (deliver results
(slurp (str (engine search-engines)
"search?q%3D"
search-term)))))
#results))
Because map results in lazy evaluation, something is required in order to realize it. In your code, nothing does this, so the futures are never actually created. Instead of just map, do:
(dorun (map ...
I'd like to mock out a macro in a namespace.
For instance, clojure.tools.logging/error.
I tried with-redefs with no luck
(def logged false)
(defmacro testerror
{:arglists '([message & more] [throwable message & more])}
[& args]
`(def logged true))
(deftest foo
...
(with-redefs
[log/error testerror]
...
That gave this error:
CompilerException java.lang.RuntimeException: Can't take value of a macro
Amalloy provided you the answer for your direct question on how to mock a macro - you cannot.
However, you can solve your problem with other solutions (simpler than moving your whole application to component dependency injection). Let me suggest two alternative implementations (unfortunately, not very straightforward but still simpler than using component).
Mock the function called by logging macro
You cannot mock a macro but you can mock a function that will be used when the logging macro get expanded.
(require '[clojure.tools.logging :as log])
(require '[clojure.pprint :refer [pprint]])
(pprint (macroexpand `(log/error (Exception. "Boom") "There was a failure")))
Gives:
(let*
[logger__739__auto__
(clojure.tools.logging.impl/get-logger
clojure.tools.logging/*logger-factory*
#object[clojure.lang.Namespace 0x2c50fafc "boot.user"])]
(if
(clojure.tools.logging.impl/enabled? logger__739__auto__ :error)
(clojure.core/let
[x__740__auto__ (java.lang.Exception. "Boom")]
(if
(clojure.core/instance? java.lang.Throwable x__740__auto__)
(clojure.tools.logging/log*
logger__739__auto__
:error
x__740__auto__
(clojure.core/print-str "There was a failure"))
(clojure.tools.logging/log*
logger__739__auto__
:error
nil
(clojure.core/print-str x__740__auto__ "There was a failure"))))))
As you can see, the function that does actual logging (if a given level is enabled) is done with clojure.tools.logging/log* function.
We can mock it and write our test:
(require '[clojure.test :refer :all])
(def log-messages (atom []))
(defn log*-mock [logger level throwable message]
(swap! log-messages conj {:logger logger :level level :throwable throwable :message message}))
(with-redefs [clojure.tools.logging/log* log*-mock]
(let [ex (Exception. "Boom")]
(log/error ex "There was a failure")
(let [logged (first #log-messages)]
(is (= :error (:level logged)))
(is (= "There was a failure!" (:message logged)))
(is (= ex (:throwable logged))))))
Use your logging library API to collect and inspect log messages
Your logging library API might provide features that would allow you to plug into in your test to collect and assert logging events. For example with java.util.logging you can write your own implementation of Handler that would collect all logged log records and add it to a specific (or root) logger.
You cannot do this. The point of macros is that they are expanded when the code is compiled, and after that they are gone. The original code that included a call to the macro is unrecoverable. You cannot retroactively redefine a macro at runtime: you're too late already.
An alternative approach, if you want to have swappable logging implementations, would be to use something like Component for dependency injection, and use a different logging component depending on whether you are running tests or running your real program. Arguably that's a bit heavy-handed, and maybe there is a simpler approach, but I don't know it.
The following test fails:
(ns clojure_refactoring.rename-fn-test
(:use clojure.test))
(deftest test-fn-location
(in-ns 'refactoring-test-fn-rename)
(clojure.core/refer-clojure)
(defn a [b] (inc b))
(in-ns 'clojure_refactoring.rename-fn-test)
(is (not= (find-var 'refactoring-test-fn-rename/a)
nil))
(remove-ns 'refactoring-test-fn-rename))
That is, find-var (of a var I've just created, in a namespace I've just create) returns nil. This behaviour doesn't happen at the repl, where typing out the steps of the test works just fine.
Am I doing something wrong, or is this just something that doesn't work in clojure right now?
Updated to a version which really seems to do the intended thing, in contrast to my original answer...
This version seems to work:
(ns clojure-refactoring.rename-fn-test
(:use clojure.test
[clojure.contrib.with-ns :only [with-ns]]))
(deftest test-fn-location
(create-ns 'refactoring-test-fn-rename)
(with-ns 'refactoring-test-fn-rename
(clojure.core/refer-clojure)
(defn a [b] (inc b)))
(is (not= (find-var 'refactoring-test-fn-rename/a)
nil))
(remove-ns 'refactoring-test-fn-rename))
Also, you really need to change all occurrences of _ in namespace names to -, and the other way around for filenames.
With these changes in place, the test runs fine for me. (I haven't even tried to run it Apparently it still works without making the _ / - changes, but really, you need to do it! That's the accepted convention and things are not guaranteed to work if you don't follow it.)
For some reason, the code from the question seems to have been creating the Var a in the namespace the test was being defined in, thus (find-var 'clojure-refactoring.rename-fn-test/a) was returning a Var, whereas the test failed. With the above, (find-var 'clojure-refactoring.rename-fn-test/a) returns nil, as expected.