jolt/test/integration/cli-test.janet
Yogthos c230e70ed7 errors: unresolved symbols error with Clojure's message (jolt-2o7.3)
A typo'd symbol used to auto-intern an unbound var and die later as
'Cannot call nil as a function' with no hint which symbol. Now:

    $ jolt -e '(undefined-fn 1)'
    Error: Unable to resolve symbol: undefined-fn in this context

The analyzer's :unresolved fallthrough now punts to the interpreter
(whose resolver raises the message above when the form runs) instead of
emitting a var-ref that interned the var. A punt rather than a hard
throw because runtime-interning forms (defmulti's setup) legitimately
reference the var they're about to create from a nested do.

Pulling that thread surfaced three real bugs the leniency was masking:

- h-resolve-global resolved unqualified symbols against ctx-current-ns,
  which during analysis is jolt.analyzer — so user-ns vars NEVER
  resolved through it; the lenient arm happened to emit the right ns.
  Now resolves against the compile ns like the qualified branch.
- Top-level (do ...) wasn't split: Clojure compiles and EVALS each
  child in sequence so earlier children's runtime effects (defmulti's
  intern) are visible while later children compile. eval-toplevel now
  splits.
- The stdlib itself had forward references the auto-intern hid:
  10-seq's transducers used vreset!/vswap! from 20-coll (moved to
  10-seq); in 20-coll qualified-ident?/realized?/list*/underive
  referenced defs declared later in the file (reordered); sorted? and
  partition-all are genuinely later-tier and got (declare ...).

Test rows updated where they encoded the old leniency: ir-passes'
dead-branch row (unresolved in a dead branch is an error, as in
Clojure), compile-mode's ctx-isolation row (other ctx now errors
instead of reading nil), cli rows assert the new message. Gate green,
conformance 335/335 x3, suite 4718 steady, bench within noise.
2026-06-12 10:07:48 -04:00

74 lines
3.2 KiB
Text

# Smoke-test the command-line flags by running main.janet from source (no build
# needed, so it can't go stale).
(defn- run [& args]
(def p (os/spawn ["janet" "src/jolt/main.janet" ;args] :p {:out :pipe :err :pipe}))
(def out (:read (p :out) :all))
(os/proc-wait p)
(string (or out "")))
(defn- run-err [& args]
(def p (os/spawn ["janet" "src/jolt/main.janet" ;args] :p {:out :pipe :err :pipe}))
(def err (:read (p :err) :all))
(os/proc-wait p)
(string (or err "")))
(var fails 0)
(defn check [label got pred]
(if (pred got) (print " ok " label)
(do (++ fails) (printf " FAIL %s: got %q" label got))))
(defn- has [sub] (fn [s] (string/find sub s)))
(check "--version" (run "--version") (has "jolt v"))
(check "version" (run "version") (has "jolt v"))
(check "--help" (run "--help") (has "Usage"))
(check "help lists nrepl-server" (run "help") (has "nrepl-server"))
(check "-e" (run "-e" "(+ 1 2)") (has "3"))
(check "--eval" (run "--eval" "(* 6 7)") (has "42"))
# -m requires a namespace and calls its -main with the remaining args
(def tmp (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-cli-" (os/time)))
(os/mkdir tmp) (os/mkdir (string tmp "/app"))
(spit (string tmp "/app/core.clj")
"(ns app.core)\n(defn -main [& args] (println \"MAIN\" (count args)))\n")
(os/setenv "JOLT_PATH" tmp)
(check "-m calls -main with args" (run "-m" "app.core" "a" "b") (has "MAIN 2"))
(os/setenv "JOLT_PATH" nil)
(os/rm (string tmp "/app/core.clj")) (os/rmdir (string tmp "/app")) (os/rmdir tmp)
(if (> fails 0)
(error (string "cli-test: " fails " failing check(s)"))
(print "\nAll CLI tests passed!"))
# --- user-facing error output (jolt-2o7 rounds 1+2) --------------------------
# Messages are Clojure-shaped; traces show the USER'S fns (compiled fn names
# carry ns/fn-name, demangled by report-error) and never jolt internals.
(check "arith error message rewritten"
(run-err "-e" `(+ 1 "a")`)
(has `Cannot add 1 and "a"`))
(check "arity error names the fn"
(run-err "-e" "(defn afn [x] x) (afn 1 2)")
(has "Wrong number of args (2) passed to: user/afn"))
(check "nil-call (nil value) keeps the hint"
(run-err "-e" "(def x nil) (x 1)")
(has "Cannot call nil as a function"))
# round 3: typos die at resolve time with Clojure's message, not as nil-calls
(check "unresolved symbol named at resolve time"
(run-err "-e" "(undefined-fn 1)")
(has "Unable to resolve symbol: undefined-fn in this context"))
(check "typo inside fn body also resolves to the message"
(run-err "-e" "(defn f [] (no-such 1)) (f)")
(has "Unable to resolve symbol: no-such"))
(check "trace shows the user's call chain"
(run-err "-e" "(defn inner [x] (let [r (+ x :k)] r)) (defn outer [x] (let [v (inner x)] v)) (outer 1)")
(fn [s] (and (string/find "at user/inner" s) (string/find "at user/outer" s))))
(check "no jolt-internal frames in user errors"
(run-err "-e" `(+ 1 "a")`)
(fn [s] (nil? (string/find "src/jolt/" s))))
(check "JOLT_DEBUG restores the raw trace"
(do (os/setenv "JOLT_DEBUG" "1")
(def r (run-err "-e" `(+ 1 "a")`))
(os/setenv "JOLT_DEBUG" nil)
r)
(has "could not find method"))