Let's tap> with let> (a.k.a. My First Macro: taplet)

Thanks to the wonderfully helpful community I’d say my macro got pretty decent. I have already had good use for it myself.

I probably should make a write-up about the different aspects of the process. We’ll see if I find the time. But anyway if you hear of someone who want some head start to creating a library for both Clojure and ClojureScript, with unit tests for both that run in Github Actions CI, by all means I think taplet is small and to the point enough to work as a starting point.

In case I won’t find the time for a full write-up, let me share what I found was the biggest hurdle to overcome: testing what is tap>ed by the macro. @seancorfield pointed me at how I can add a tap and then check there.

After a bit of experimenting with different ways of tapping/reading, I turned to core/async sliding buffers:

(def ^:private tapped (a/chan (a/sliding-buffer 1)))
(def ^:private  save-tap (fn [v] (a/offer! tapped v)))
(defn- read-tapped [] (a/poll! tapped))

(defn fixture [f]
  (add-tap save-tap)
  (f)
  (remove-tap save-tap))

(use-fixtures :once fixture)

To me the models of the tap and the sliding buffer channels mix well in my head. The fixture makes the tests have very little of the boilerplate I had a while.

Then I still had some problems with making it predictable. And also CLJ and CLJS behaved totally different in the testing, even when the macro seemed to work the same on both.

My symptoms for the CLJ brittleness of the tests where that of my 5 tests, 0, or 1, or 2 of any of them sometimes failed. A while I thought the problem was that the tap queue was full when I tried to tap>, but it turned out I was a bit to eager to check what had been tapped. I am now using core async to pause a millisecond between calling the macro and checking the tap.

(a/<!! (a/timeout 1))

That seems to be enough to make the tests predictable.

For CLJS my reading of the tap were more predictable, they always returned nil. :smiley: @thheller pointed out it was that in ClojureScript the tap> is happening async, (in a js/setTimeout). But the function for tapping is a dynamic var so I could replace it. I let this be enough for that:

(set! *exec-tap-fn* (fn [f] (f)))

Super predictable tests after that.

Putting it all together, my tests now look like this:

I am pretty happy with this now. Thanks to everyone who helped me! :heart:

3 Likes