From 86e36e8bee8b317df51aff11cd4f74f0627e0d98 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Thu, 2 Jul 2026 05:57:57 -0400 Subject: [PATCH] clojure.test/are substitutes via clojure.template are let-bound its template vars, so a var inside quote never substituted: (are [x] (special-symbol? 'x) if def) tested the literal symbol x twice. Rebuild are on clojure.template/do-template (postwalk substitution), the same architecture as upstream, with the same arg-count check. This un-aborts every suite namespace whose are rows need substitution: cts baseline moves 5302->5614 pass, 236->192 errors, 88->84 baselined namespaces. The newly-reachable assertions also surface real divergences now baselined and filed (edn reader strictness, Boolean ctor). --- docs/spec/09-core-library.md | 36 ++++++++++++++++++++++++++++++++ stdlib/clojure/test.clj | 20 +++++++++++------- test/chez/clojure-test.clj | 10 ++++++--- test/chez/cts-known-failures.txt | 16 ++++++-------- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/docs/spec/09-core-library.md b/docs/spec/09-core-library.md index c7ce14f..1ef1907 100644 --- a/docs/spec/09-core-library.md +++ b/docs/spec/09-core-library.md @@ -196,6 +196,42 @@ cases; clojure-test-suite `core_test/parse_uuid.cljc`, --- +### clojure.template/apply-template, clojure.test/are — since 1.1 + +``` +(apply-template argv expr values) +(are argv expr & args) +``` + +**Semantics** + +- S1. `apply-template` MUST replace every occurrence of each `argv` symbol + in `expr` with its corresponding value by structural walk (postwalk symbol + substitution), not by lexical binding. Occurrences inside `quote` and at + any nesting depth substitute: `(apply-template '[x] '(f 'x) '[if])` ⇒ + `(f 'if)`. +- S2. `do-template` MUST partition `args` by `(count argv)` and expand to a + `do` of one substituted `expr` per group. +- S3. `clojure.test/are` MUST expand through `do-template` with `expr` + wrapped in `is`. Consequently `(are [x] (special-symbol? 'x) if def)` + asserts `(special-symbol? 'if)` and `(special-symbol? 'def)` — a + let-binding implementation is non-conforming (the quoted symbol would not + substitute). + +**Errors** + +- X1. `are` MUST throw at macroexpansion when `(count args)` is not a + positive multiple of a non-empty `(count argv)` (empty/empty is allowed). +- X2. `apply-template` MUST throw when `argv` is not a vector of symbols. + +**Conformance** + +S1–S3 → `test/chez/clojure-test.clj` (are with quoted template var); +clojure-test-suite `core_test/special_symbol_qmark.cljc` and every +`are`-based suite namespace. + +--- + ## Authoring notes - Source examples from the ClojureDocs export (`clojuredocs-export.edn`, diff --git a/stdlib/clojure/test.clj b/stdlib/clojure/test.clj index 3866b4a..17eef32 100644 --- a/stdlib/clojure/test.clj +++ b/stdlib/clojure/test.clj @@ -11,7 +11,8 @@ ; is a drop-in superset. (ns clojure.test - (:require [clojure.string :as str])) + (:require [clojure.string :as str] + [clojure.template :as temp])) ;; --- state ----------------------------------------------------------------- @@ -216,13 +217,16 @@ :fn ~name}) (var ~name))) -(defmacro are [argv expr & data] - (let [n (count argv) - rows (partition n data)] - `(do ~@(map (fn [row] - `(let [~@(interleave argv row)] - (clojure.test/is ~expr))) - rows)))) +;; Template substitution (not let-binding), so argv symbols substitute inside +;; quote and nested forms: (are [x] (special-symbol? 'x) if def) tests 'if. +(defmacro are [argv expr & args] + (if (or (and (empty? argv) (empty? args)) + (and (pos? (count argv)) + (pos? (count args)) + (zero? (mod (count args) (count argv))))) + `(clojure.template/do-template ~argv (clojure.test/is ~expr) ~@args) + (throw (IllegalArgumentException. + "The number of args doesn't match are's argv or neither are empty")))) ;; --- fixtures + run -------------------------------------------------------- diff --git a/test/chez/clojure-test.clj b/test/chez/clojure-test.clj index 69db89c..a323dc9 100644 --- a/test/chez/clojure-test.clj +++ b/test/chez/clojure-test.clj @@ -36,6 +36,10 @@ (are [x y] (= x y) 2 (+ 1 1) 6 (* 2 3)) + ;; template vars substitute inside quote (are is clojure.template, not let) + (are [x] (special-symbol? 'x) + if + def) (is (thrown? clojure.lang.ExceptionInfo (throw (ex-info "x" {})))) (is (thrown-with-msg? Exception #"bad" (throw (ex-info "bad" {})))) (is (near? 1.0 1.005)) @@ -52,10 +56,10 @@ (t/do-report {:type ::trial}) (t/do-report {:type ::trial}) -;; 8 pass (= + vector? + 2 are rows + thrown? + thrown-with-msg? + near? + p/thrown?), +;; 10 pass (= + vector? + 4 are rows + thrown? + thrown-with-msg? + near? + p/thrown?), ;; 2 fail (= 1 2, near? 1.0 5.0), 0 error, 2 fixture runs, 2 custom reports -(let [ok (and (= (t/n-pass) 8) (= (t/n-fail) 2) (= (t/n-error) 0) - (= 2 (:test r1)) (= 8 (:pass r1)) (= 2 (:fail r1)) +(let [ok (and (= (t/n-pass) 10) (= (t/n-fail) 2) (= (t/n-error) 0) + (= 2 (:test r1)) (= 10 (:pass r1)) (= 2 (:fail r1)) (= 0 (:test r2)) (= 0 (:pass r2)) (= @setups 2) (= @trials 2))] (println (if ok diff --git a/test/chez/cts-known-failures.txt b/test/chez/cts-known-failures.txt index a52ec59..c50a198 100644 --- a/test/chez/cts-known-failures.txt +++ b/test/chez/cts-known-failures.txt @@ -7,7 +7,7 @@ clojure.core-test.ancestors 9 0 clojure.core-test.atom 14 0 clojure.core-test.bigint 6 0 clojure.core-test.bit-set 1 0 -clojure.core-test.boolean-qmark 0 1 +clojure.core-test.boolean-qmark 0 3 clojure.core-test.byte 7 5 clojure.core-test.char 1 0 clojure.core-test.compare 1 0 @@ -26,8 +26,6 @@ clojure.core-test.eval 0 3 clojure.core-test.even-qmark 1 0 clojure.core-test.float 4 4 clojure.core-test.get 0 1 -clojure.core-test.gt 0 2 -clojure.core-test.gt-eq 0 2 clojure.core-test.ifn-qmark 2 0 clojure.core-test.inc 1 0 clojure.core-test.int 4 5 @@ -36,8 +34,6 @@ clojure.core-test.intern 2 0 clojure.core-test.keys 0 4 clojure.core-test.lazy-seq 3 0 clojure.core-test.long 2 5 -clojure.core-test.lt 0 2 -clojure.core-test.lt-eq 0 2 clojure.core-test.max 3 1 clojure.core-test.min 3 1 clojure.core-test.minus 2 0 @@ -56,23 +52,23 @@ clojure.core-test.plus 11 0 clojure.core-test.plus-squote 11 0 clojure.core-test.pop 0 1 clojure.core-test.pos-int-qmark 1 0 -clojure.core-test.quot 12 31 +clojure.core-test.quot 12 23 clojure.core-test.rand-nth 0 1 clojure.core-test.rational-qmark 3 0 clojure.core-test.rationalize 10 0 clojure.core-test.realized-qmark 3 0 clojure.core-test.reduce 0 1 -clojure.core-test.rem 12 30 +clojure.core-test.rem 12 22 clojure.core-test.remove-watch 0 1 clojure.core-test.run-bang 1 0 clojure.core-test.select-keys 2 0 clojure.core-test.seqable-qmark 1 0 clojure.core-test.short 7 5 clojure.core-test.shuffle 1 0 -clojure.core-test.slash 2 48 +clojure.core-test.slash 2 22 clojure.core-test.some-fn 3 0 clojure.core-test.sort-by 2 0 -clojure.core-test.special-symbol-qmark 20 0 +clojure.core-test.special-symbol-qmark 4 0 clojure.core-test.star 22 0 clojure.core-test.star-squote 19 0 clojure.core-test.transient 23 0 @@ -82,7 +78,7 @@ clojure.core-test.vals 0 3 clojure.core-test.vec 1 0 clojure.core-test.when-let 1 0 clojure.core-test.with-precision 17 0 -clojure.edn-test.read-string 0 1 +clojure.edn-test.read-string 46 5 clojure.string-test.capitalize 0 4 clojure.string-test.ends-with-qmark 1 4 clojure.string-test.escape 1 0