This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-09-23
Channels
- # announcements (8)
- # babashka (12)
- # babashka-sci-dev (6)
- # beginners (62)
- # biff (5)
- # calva (4)
- # cider (2)
- # clj-commons (8)
- # clj-kondo (17)
- # clj-yaml (40)
- # clojars (3)
- # clojure (117)
- # clojure-europe (122)
- # clojure-nl (5)
- # clojure-norway (20)
- # clojurescript (10)
- # consulting (1)
- # datomic (65)
- # events (15)
- # figwheel (1)
- # fulcro (4)
- # lsp (15)
- # mount (15)
- # music (1)
- # off-topic (53)
- # polylith (12)
- # releases (3)
- # shadow-cljs (13)
- # sql (1)
- # test-check (8)
- # xtdb (31)
What's a good simple unit-testing setup for clojure? I'm using deps I'd like a way to • make a change • rerun all the tests (ideally automatically) • get feedback as fast as possible (fast microtests completing in <5s) This works
watchexec -w src -w test clojure -Mtest
but it's a little slow, because all state needs to be recreated on every change and startup cost is relatively high. How can I speed things up? (Asking an open question here to make sure I'm not overlooking a good solution)https://github.com/lambdaisland/kaocha solves this nicely.
I've used kaocha in the past, and I liked it. Does it address the slow feedback problem of my watchexec
?
Will give it a try
> Yes, it will keep a process running so you don't get the startup costs. But does it also deal with the "dirty REPL" problem? How exactly does it work?
btw, the official syntax is clojure -M:test
: the omission of the colon only accidentally works and not always on every platform
Can you describe the dirty repl problem?
Just tried kaocha, and seems to work pretty well out of the box, with <100ms feedback
well, the dirty REPL problem is for example when you remove a function from your code base but the tests still refer to that function, which should result in failing tests, but doesn't
Seems to be using (a fork of) tools.namespace.reload
I don't now tools.namespace empties a namespace or just redefines the vars
my take it that I either just use the REPL to run tests or start with a clean slate on the command line
Basically what you're saying is that there's a risk of correctness problems if you're relying on namespace reloading: a test might pass even though the function references isn't used. Correct?
with tools namespace reload that problem is taken care of, but there's other problems that I'm not able to come up with right now, but they exist ;)
gotcha
I used to be a stickler for correctness, but these days I'm willing to accept 1% of compromise in correctness, if it gives me a significant boost in feedback speed in return
Of course there's always a safety net in CI, which always takes the slow, safe path
sure. it also depends what you're developing. when I'm developing a small library, the few seconds of startup time don't really bug me that much for running a whole test suite, but if I'm developing a big app that takes 2 minutes to start, it's a different story. There I just rely on the REPL
Right. I'm doing TDD in a larger app so I want to minimize feedback cycles as much as humanly possible
That's a good point, I also remember getting confused about the state of the repl at times, and proactively restarting repls whenever something was off
perhaps kaocha has taken care of the tools reload problems like protocol stuff, but I doubt it has solved all problems that exist in that area
@U04V70XH6 also has some opinions on the "reloaded" workflow, perhaps he has more vivid memories of what the edge cases are, but as long as it works for your purposes, go for it :)
Hehe, yeah I guess so long as you know it's not bullet-proof, you can restart every once in awhile
Most of those problem also affect evaluating a buffer manually in emacs right?
yeah, I think so, but at least you can avoid loading everything, e.g. protocols can stay stable
this is also true for reloaded workflow if you carefully don't touch anything and separate them out
I'm the author of https://github.com/jakemcc/test-refresh, one of the earliest "monitor files, reload using tools.namespace, and run tests" tools. Not really recommending it over koacha as I don't have experience with both. I'm super happy kaocha exists, especially since I dragged my feet on deps.edn
based projects.
In general, I've found these type of tools to make a huge impact on reducing feedback cycles and generally less error prone than managing a repl's state (tools.namespace is great).
A couple things that test-refresh supports that can help with faster feedback cycles include running previously failed tests first and (optionally) only running tests that could be affected by the previous code reload.
It is worth it with any of these tools that use tools.namespace to read through the https://github.com/clojure/tools.namespace#reloading-code-preparing-your-application part of the tools.namespace readme.
One tip for defprotocol
is to isolate them in their own namespace and give that namespace as few-as-possible reasons to need reloading. This is actually generally a worthwhile strategy if you have some area of code that you'd prefer not reload often.
I really like https://github.com/hyperfiddle/rcf
RCF is problematic in several ways tho' because it auto-generates test names and that means if you modify files in a running process, you get multiple modified copies of tests. I opened a ticket on the RCF repo ages ago about it and they're thinking about it. It's why Classic Expectations can also be problematic. You don't get repeatability when test names change under you: you get a mix of old, often broken tests and new tests being run -- and then you start resorting to stuff like t.n.r and then you're dealing with its fragility as well 😞
At work we use Polylith (which supports Kaocha as an optional test runner) and it provides incremental testing -- only running tests that depend on code you've changed -- and uses classloader isolation for running tests to avoid the dirty REPL problem. However, there are still problematic edge cases with that (such as reified classes that end up in the global fork/join pool's classloader and then conflict with classes loaded into different classloaders) but those are much more "edge-case-y" and you're less likely to encounter them...
Thanks for that, I'll certainly have a look at the Koacha + polylith pattern. I like how low-effort rcf style writing tests is to me since it's close to my regular workflow in both clj and cljs. It's also nice how it can double as a sort of inline documentation.
As for inline documentation: my concern would be dragging in extra dependencies in production libraries or bigger JS bundles
@U04V15CAJ It looks like they've worked on the dependency issue and now RCF only depends on Clojure? https://github.com/hyperfiddle/rcf/blob/master/deps.edn
@U04V70XH6 That's good, but using rfc in a library namespace will still pull in clojure's namespaces like cljs.test
which will negatively affect build size
So for inline tests, I wouldn't use it. I would consider using it (or #clerk or ...) for external test namespaces
Yeah, I like the idea of it but I have very mixed feelings about it in practice.
> (tests) blocks erase by default (macroexpanding to nothing), which avoids a startup time performance penalty as well as keeps tests out of prod. So that might not be an issue?
It still requires cljs.test into your production code even if the macro collapses to nothing. I think that's what Michiel was referring to.
@U04V15CAJ If you use none of those tools, how do you run tests? And you don't use the "reloaded" workflow? Do you still use something like integrant or similar? Sorry for bombarding you with questions 🙈 I'm very interested in alternative approaches
@U01DV4FGYJ0 It depends. When I develop a bigger application with db connections or http-servers, I tend to use component or integrant. I just use the REPL and evaluate forms or buffers and then reload the system. I can run tests from the REPL
Nowadays most of my time is spent with libraries which are usually simpler to develop
Can I introduce my custom var that can be used with set!
like e.g. *warn-on-reflection*
(set! *warn-on-reflection* true)
My custom one;
(set! *my-custom-var* true)
@U07FCNURX warn on reflection exists in every namespace. Is there a way to create a dynamic var that can be set for each namespace, like how warn-on-reflection is interned?
I guess you could do (in-ns 'clojure.core)
and then (def ^:dynamic *my-custom-var* 1)
but it would certainly be a very odd thing to do.
(set! *my-custom-var* 2)
does not work;
Can't change/establish root binding of: my-custom-var with set @U07FCNURX
set!
will work exactly the same for your custom var as it will for *warn-on-reflection*
. set!
can only be called when the var is bound to a thread local state, as in
(defonce ^:dynamic *my-thing* 1)
(comment
(binding [*my-thing* 2]
(prn '> (set! *my-thing* 3)))
)
The compiler basically runs inside the equivalent of binding
for things like *warn-on-reflection*
so if you just write
(set! *warn-on-reflection* true)
at the top level in your file, it's interacting with the state created by the compiler.Thank you for the info!
What is the exact behavior on *warn-on-reflection*
across namespaces?
Little experiment. Say namespace A requires B and B sets it to true. Its value seems to remain false in other namespaces. When everything is loaded and NREPL kicks in, its value is false even in B.
https://clojure.wladyka.eu/posts/how-to-improve-algorithm-performance/#avoid-reflections does it answer your question?
Not quite because it doesn't go into the kind of behavior I'm exposing (but thanks nonetheless!)
> :reload-all is the key because it forces to reload all dependency. Warnings appear when functions are defined, not when they are used. I thought it can be what you need
Well the gist is that *warn-on-reflection*
is set to true before requiring other namespaces. But when those namespaces are required, printing its value, we can see it is set to false.
The question being: why? 😄
I don’t know. I have never tried to read value of *warn-on-reflection*
. Only set it.
> s set to true before requiring other namespaces Try to set it manually in REPL and require ns with reload to be sure.
Setting it in the REPL works for some reasons, yes.
I'm really talking about main invocation. The main space requires a whole bunch of namespaces. But the first one sets *warn-on-reflection*
to true. That namespace can print it, it is set to true, not doubt. Yet it is somehow reverted to false everywhere after loading it.
> But the first one sets warn-on-reflection to true It first load ns, then execute code. So you set warn-on-reflecation after require other ns
No 😅
The first namespace to be required in the main :require
sets it.
All in all I think you need tool like https://github.com/athos/clj-check
:check-syntax-and-reflections {:extra-deps {athos/clj-check {:git/url ""
:sha "da6363a38b06d9b84976ed330a9544b69d3c4dee"}}
:main-opts ["-m" "clj-check.check"]}
I spent a while last night reading about transducers. The RH talks were especially helpful in understanding them. I think I've got a good handle on the basics now, and they remind me quite a bit of interceptors, a la pedestal or sieppari. In my mind the killer feature of interceptors is dynamism - you can inspect the interceptor queue or stack during execution and update it however you want. Is there a way to inspect the xform in a similar way in a transduce call, to modify the transducer at runtime?
Inspection should be possible with a reasonably advanced debugger and some skill, because combining transducers is done via comp
. But it's definitely not trivial.
Runtime modification is limited to comp
ing extra transducers.
I suppose between reduced
and dynamically comp
ing extra reducers you can get basically the same flexibility.
So the ^:once
meta put on fn*
calls is used when a function is going to only be called once, so that any locals it holds references to can be cleared while the function is running.
Is there a way to specify the same using higher-level function macros (like bound-fn
), or am I best served by managing binding conveyance myself after using the ^:once
meta on fn*
, the way that core.async does?
What I'm seeing as I look over core is that the fn
macro itself does not provide a way to set the once meta on the fn*
symbol that gets emitted
So I guess I just have to do what core.async does
:once is a pretty low level thing, core.async only sort of uses it for the go macro (the go macro relies on the fn keeping closed over values, so it cannot actually use :once, but I have a patch that changes this)
Yeah in this case I'm referring to how core.async uses it for thread
I'm currently writing a tiny library to monkeypatch core.async to use JDK 19 virtual threads for the go macro, and so with this in mind the implementation of go is pratically the same as for thread
+`thread-call`.
The idea is to monkeypatch core.async so that all libraries that use it and are distributed as source can be ported seamlessly to virtual threads by just requiring the monkeypatch library before the other dependencies.
the big issue there is channels, when they execute a callback, unconditionally do it by putting results on the go block threadpool
which means things will still work with virtual threads, just ping-ponging between more threads than needed
You mean the put!
callbacks? Yeah, I'm not too worried about that since using the blocking versions of put and take means that there won't be callbacks happening except in libraries that are explicitly doing that as a low-level way of interacting with core.async.
For what?
Yeah, I'm not too worried about that.
Yes it'll add some extra threads, but the point here is ease of porting, not being optimal.
The ideal case in the end would be a fork of core.async that only uses virtual threads and has nothing doing with the pool, but I'm just trying to put something easy to use together rn.
although I think I could replace the core.async thread pools with some virtual thread factories
most likely
@U5NCUG8NR Did you see my tiny example about a go
macro based on virtual threads a while back?
no, I hadn't, thanks for pointing it out!
hello i am getting this
Could not transfer artifact graphframes:graphframes:jar:0.8.1-spark3.0-s_2.12 from/to graphframes (): Checksum validation failed, no checksums available
Sounds like that repo does not have checksums so you need to tell Leiningen not to check artifacts from that repo: https://codeberg.org/leiningen/leiningen/src/branch/stable/sample.project.clj#L112-L114
i want this [graphframes/graphframes "0.8.1-spark3.0-s_2.12"]
and i added
:repositories [["" ""]]
I don't know why it is, but there don't seem to be checksums in their repo (I added it to a clj-based project and it pulled the pom and jar, but not any meta files).
But, with leiningen, you can add this to your repo def:
:checksum :ignore
like...
:repositories [[""
{:url ""
:checksum :ignore}]]
Eh. You probably didn't have the misplaced brackets. 😄
@U68Q5G1BJ If you always use threads to add extra information to your question, that won't happen (multiple people answering you in different places).
Heh, I just pointed them at the sample project example showing how to do it 🙂