From e811b7b20aec05927eb11cb72099ebd9251290fe Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 24 Oct 2026 10:56:52 +0300 Subject: [PATCH] Don't fail clients for whitespace after a value Calva and Conjure ask for values printed with cider-nrepl's pprint, which ends every value with a newline (orchard.pp prints one after it). Babashka 1.13.227 implements that printer the same way, so calva.eval, conjure.eval and their load-file checks failed it for "3\n", which is just what nREPL with cider-nrepl gives those clients too. Client profile steps now ignore whitespace after a value. --- doc/hacking.md | 2 +- internal/checks/checks_test.go | 2 ++ internal/checks/client_profiles.go | 5 ++++- internal/checks/fake_test.go | 4 ++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/hacking.md b/doc/hacking.md index 5c6d7f6..3700a2b 100644 --- a/doc/hacking.md +++ b/doc/hacking.md @@ -256,7 +256,7 @@ besides `done`: | Option | What the reply needs | |---|---| | `new-session` | A `new-session`, which later steps can use as `$` and this name (e.g. `session = "$repl"`). | -| `snippet` | The value of this snippet of the server's profile, with all the parts it came in joined together. Its code goes in the request (in `file` for `load-file`), as it stands for the user's code. | +| `snippet` | The value of this snippet of the server's profile, with all the parts it came in joined together. Its code goes in the request (in `file` for `load-file`), as it stands for the user's code. Whitespace after the value doesn't count, as cider-nrepl's `pprint` ends values with a newline. | | `values` | At least this many values, told apart by the `ns` that comes with each one (or after it), which is how some clients tell them apart. | | `dicts` | These fields have to be dicts (or empty lists), if the reply has them. Each one is a list of keys, e.g. `["versions", "clojure"]`. | diff --git a/internal/checks/checks_test.go b/internal/checks/checks_test.go index 23c8da9..22ef51b 100644 --- a/internal/checks/checks_test.go +++ b/internal/checks/checks_test.go @@ -145,6 +145,8 @@ func TestChecksCatchMisbehaviour(t *testing.T) { "session.ephemeral": F, "session.persistent": F, "cider.eval": F, "cider.repl": F, "calva.eval": F, "conjure.eval": F, "fireplace.eval": F, "neorepl.eval": F, "interrupt.survives": F, "interrupt.queued": F, "load-file.value": F, "robust.wrong-types": W, "robust.disconnect": F, "robust.sessions-at-once": F, "robust.bad-frames": F})}, + // Like cider-nrepl's pprint. + {"printed values end in a newline", quirks{printNewline: true}, nil}, {"unsorted keys", quirks{unsortedKeys: true}, map[string]check.Verdict{"wire.canonical": W}}, {"invalid UTF-8", quirks{badUTF8: true}, map[string]check.Verdict{"wire.utf8": W, "eval.stdout": F}}, {"no stdin op", quirks{noStdinOp: true}, map[string]check.Verdict{ diff --git a/internal/checks/client_profiles.go b/internal/checks/client_profiles.go index ca19374..4ec91ea 100644 --- a/internal/checks/client_profiles.go +++ b/internal/checks/client_profiles.go @@ -168,7 +168,10 @@ func (c clientCheck) replay(t *check.T) { sessions[s.NewSession] = id } if s.Snippet != "" { - switch got := strings.Join(resp.Values(), ""); got { + // Whitespace after the value doesn't count, as cider-nrepl's + // pprint, which Calva and Conjure ask for, ends values with a + // newline. + switch got := strings.Join(resp.Values(), ""); strings.TrimRight(got, " \t\r\n") { case want: case "": t.Stopf("%s gave no value, so %s", step, s.Why) diff --git a/internal/checks/fake_test.go b/internal/checks/fake_test.go index ebe9929..a5ed6d5 100644 --- a/internal/checks/fake_test.go +++ b/internal/checks/fake_test.go @@ -44,6 +44,7 @@ type quirks struct { noSessionEcho bool noID bool // output messages carry no id intValue bool // value is an integer + printNewline bool // a value printed with a print function ends in a newline statusString bool // status "done" instead of ["done"] on unknown-op unsortedKeys bool // describe reply has keys out of order badUTF8 bool // out isn't valid UTF-8 @@ -634,6 +635,9 @@ func (s *fakeServer) eval(c net.Conn, req nrepl.Message, local map[string]bool) if !q.noNs { valueMsg["ns"] = "user" } + if q.printNewline && req.Has("nrepl.middleware.print/print") { + valueMsg["value"] = value + "\n" + } if q.intValue { if n, err := strconv.Atoi(value); err == nil { valueMsg["value"] = n