shadow-cljs

borkdude 2025-10-28T10:48:32.862779Z

I have a problem with a node script that does return the right exit code when running tests top level but it doesn't exit with the right exit code when I wrap it in an init function.

(ns test-runner-cljs
  (:require
   [basic-test]
   [event-listener-test]
   [install-jsdom]
   [svg-test])
  #?@(:squint []
      :cljs [(:require-macros [test-runner-cljs :refer [find-tests]])]))

#?(:squint (defn find-tests [_]))

(basic-test/render-test)

(defn init []
  (basic-test/render-test)
  #_#_#_(find-tests basic-test)
  (find-tests event-listener-test)
  (find-tests svg-test))
(ignore the squint reader conditionals)

borkdude 2025-10-28T10:53:41.455789Z

I don't see anything special in the generated code, so it might just be a Node JS weirdness

thheller 2025-10-28T17:53:56.637769Z

what is the "right exit code"? does the test throw? otherwise I'd always expect 0?

thheller 2025-10-28T17:56:59.623199Z

:node-test has extra special handling so it actually sets an exit code. :node-script does not do this. https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/test/node.cljs#L13

borkdude 2025-10-28T17:58:15.958029Z

I just use browser. I figured out that when doing node:assert/equal or whatever other assertion on the top level makes the script exit with 1 on failure, but not inside a function (at least with shadow but probably not related to shadow)

borkdude 2025-10-28T17:59:13.222139Z

I mean, target esm with browser

borkdude 2025-10-28T17:59:23.337919Z

{:source-paths ["src" "test"]
 :builds
 {:reagami
  {:js-options {;; don't bundle any npm libs
                :js-provider :import}
   :compiler-options {:infer-externs :auto}
   :target :esm
   :runtime :browser
   :devtools {:enabled false}
   :output-dir "lib"
   :modules
   {:reagami {:exports
              {render reagami.core/render}}
    :tests {:init-fn test-runner-cljs/init
            :depends-on #{:reagami}}}
   :build-hooks [(shadow.cljs.build-report/hook
                  {:output-to "report.html"})]}}}

thheller 2025-10-28T18:00:03.507519Z

:compiler-options {:infer-externs :auto} this has been the default for like 10 years 😉 no need to set it

borkdude 2025-10-28T18:00:05.748669Z

I also did try node, didn't make any difference

borkdude 2025-10-28T18:00:13.188069Z

yeah, copy paste :)

thheller 2025-10-28T18:00:57.970279Z

I don't recommend tests as a module as part of a regular build. almost guaranteed to keep more stuff alive and in the :reagami module than needed

borkdude 2025-10-28T18:01:57.161589Z

I know, this is just for testing the library, not anything that ships to production (then why do I have two modules? I don't remember)

borkdude 2025-10-28T18:07:28.869719Z

this does exit with 1

import assert from "node:assert";

function f() {
  assert.strictEqual(1, 2);
}

f();
console.log("done");

borkdude 2025-10-28T18:09:02.430359Z

oooh look:

try { test_runner_cljs.init(); } catch (e) { console.error("An error occurred when calling (test-runner-cljs/init)"); console.error(e); }

borkdude 2025-10-28T18:09:15.085609Z

looks like shadow's produced code swallows the exception

borkdude 2025-10-28T18:10:20.177789Z

runtime node doesn't fix this

borkdude 2025-10-28T18:10:50.320169Z

maybe just let the environment handle the exception?

thheller 2025-10-28T19:47:53.389139Z

that is only there for development builds

thheller 2025-10-28T19:48:35.110929Z

without it the errors and stacktraces kinda sucked and werent immediately obvious that init-fn was the issue because it would sometimes be way down in the stack

borkdude 2025-10-28T19:50:02.754459Z

I'm running watch or compile locally to test. Advanced testing is something CI can do for me, takes too long to iterate

borkdude 2025-10-28T19:51:12.863129Z

but then again, I think swallowing exceptions even in non-advanced builds is surprising when it affects the exit code. having this code on the top level fixes it for me though

thheller 2025-10-28T20:09:59.410199Z

well you could try/catch in your init and just do whatever you want 😛

borkdude 2025-10-28T20:23:44.330289Z

fair enough ;)

borkdude 2025-10-28T20:24:18.224859Z

I guess in shadow you could add process.exit(1) or process.setExit(1) for node targets