The process-async function tested within the midje framework produces inconsistent results. Most of the time it checks as expected, but from time to time, it reads out.json at its initial state (""). I rely on the async-blocker function to wait on process-async before checking.
What's wrong with my approach?
(require '[[test-with-files.core :refer [with-files public-dir]])
(defn async-blocker [fun & args]
(let [chan-test (chan)]
(go (>! chan-test (apply fun args)))
(<!! chan-test)))
(defn process-async
[channel func]
(go-loop []
(when-let [response (<! channel)]
(func response)
(recur))))
(with-files [["/out.json" ""]]
(facts "About `process-async"
(let [channel (chan)
file (io/resource (str public-dir "/out.json"))
write #(spit file (str % "\n") :append true)]
(doseq [m ["m1" "m2"]] (>!! channel m))
(async-blocker process-async channel write)
(clojure.string/split-lines (slurp file)) => (just ["m1" "m2"] :in-any-order)
)
)
)
The problem is that
process-asyncreturns immediately with "[...] a channel which will receive the result of the body when completed" (sincego-loopis just syntactic sugar for(go (loop ...))andgoreturns immediately).This means that the blocking
<!!inasync-blockerwill have a value almost immediately and the order in which thegoblocks fromprocess-asyncandasync-blockerget executed is undetermined. It might be that most of the time the block fromprocess-asyncexecutes first because it gets created first, but that is not much of a guarantee in a concurrent context.According to the documentation for
<!!it "Will return nil if closed. Will block if nothing is available." This means that if you can assume that the return value of(apply fun args)is a channel returned bygo, you should be able to block by using<!!in the following way:This will unblock once there is a value in the channel (i.e. the return value from the
goblock).There are other options to wait for the result of another
goblock. You could for example provide the originalchan-testas an argument tofunand thenputa value inchan-testwhen thegoblock created infunterminates. But I think, given the code you showed, other approaches might be unnecessarily more complicated.