Skip to content

Support running against Basilisp#840

Merged
jeaye merged 59 commits intojank-lang:mainfrom
basilisp-lang:feature/support-basilisp
Jan 22, 2026
Merged

Support running against Basilisp#840
jeaye merged 59 commits intojank-lang:mainfrom
basilisp-lang:feature/support-basilisp

Conversation

@chrisrink10
Copy link
Contributor

@chrisrink10 chrisrink10 commented Dec 31, 2025

This PR adds support for running the test suite against Basilisp:

  • It adds a new CI job which runs the suite on PRs and merges into the default branch.
  • Added a new Babashka task for running the tests: bb test-lpy.

By virtue of running on the Python VM, there are a few idiosyncrasies which repeatedly appear in tests:

  • Python does not have fixed-size integer values and, thus, any tests involving integer overflow or underflow needed to be excluded for Basilisp as they do not apply.
  • Python's bool type is a subclass of ints so there are many instances where true and false have to be specially handled.
  • Basilisp does not implement any of the sorted collection types, so all tests against those data types had to be excluded.
  • Basilisp does not currently support standard clojure.test style fixtures.
  • I'm actually not sure why this is happening, but there are many tests in the suite which assert on exact ordered results when calling seq on maps. These seem to work consistently for all other implementations but not in Basilisp. I'm guessing this may have to do with the fact that Clojure (and perhaps others) have array maps for smaller maps and perhaps array maps have a consistent iteration order?

⚠️ There is one open issue in Basilisp that I haven't addressed: apply is not lazy right now, so I had to suppress 2 test cases in the mapcat tests which produced an infinite loop. I filed #844 to track that in this repo as well.

@CLAassistant
Copy link

CLAassistant commented Dec 31, 2025

CLA assistant check
All committers have signed the CLA.

@jeaye jeaye requested a review from E-A-Griffin December 31, 2025 23:24
@E-A-Griffin
Copy link
Collaborator

Thanks so much for adding this! I'll take a look tomorrow :)

@chrisrink10
Copy link
Contributor Author

Thanks so much for adding this! I'll take a look tomorrow :)

Thanks for the quick reply! I don't actually have this running quite yet, so you can hold off on reviewing.

If me opening the PR while I'm working on it is not preferred, I can close it again. Just let me know!

@jeaye
Copy link
Member

jeaye commented Dec 31, 2025

Thanks so much for adding this! I'll take a look tomorrow :)

Thanks for the quick reply! I don't actually have this running quite yet, so you can hold off on reviewing.

If me opening the PR while I'm working on it is not preferred, I can close it again. Just let me know!

Ah, a draft PR is ok. I requested Emma's help since I thought you were ready. Feel free to hack on this. Once you're ready, please just request a review from me and I'll loop others in.

(def ^:const max-double #?(:clj Double/MAX_VALUE
:cljr Double/MaxValue
:cljs js/Number.MAX_VALUE
:lpy (.-max sys/float-info)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is system dependent we should add a comment explaining that here

Copy link
Contributor Author

@chrisrink10 chrisrink10 Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@E-A-Griffin just to confirm: by "system dependent" you mean like "the computer I'm using may have different sized floats", yes?

(is (instance? clojure.lang.BigInt (+ 1 5N)))
(is (instance? clojure.lang.BigInt (+ 1N 5)))
(is (instance? clojure.lang.BigInt (+ 1N 5N)))]))
#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

(testing "cannot pop! after call to persistent!"
(let [t (transient [0 1]), _ (persistent! t)]
(is (thrown? #?(:cljs js/Error :cljr Exception :default Error) (pop! t)))))
#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

false :-1
false 'a-sym

#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

true 1.0M
false -1.0M

#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

;; `prn-str` adds it to the end of the string.
(is (= (str "\"a\" \"string\"" nl) (prn-str "a" "string")))
(is (= #?(:cljs (str "nil \"a\" \"string\" \"A\" \" \" 1 17 [:a :b] {:c :d} #{:e}" nl)
:lpy (str "nil \"a\" \"string\" \"A\" \" \" 1 17.0 [:a :b] {:c :d} #{:e}" nl)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add the same

;; Basilisp does not have character types, but will print floats
;; with trailing decimal place.

comment here?

#?(:cljs
nil

:lpy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here


(testing "n not being a number"
(are [n x] #?(:cljs (= [] (repeat n x))
:lpy (thrown? Exception (vec (repeat n x)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this because repeat is lazy? Do you know why wrapping this in vec is required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually might have fixed this in Basilisp to check the value before creating the lazy seq. Let me double check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually not necessary any longer, so I will revert this change.

true (sorted-map :a 1)
true (sorted-set :a)

#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

#?(:cljs true :default false) (seq [1 2 3]))))
#?(:cljs true :default false) (seq [1 2 3])

#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

'() nil
nil nil
(sorted-set 3.0 1.0 -2.5 4.0) '(-2.5 1.0 3.0 4.0)
#?@(:lpy [] :default [(sorted-set 3.0 1.0 -2.5 4.0) '(-2.5 1.0 3.0 4.0)])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

#?@(:lpy [] :default [(sorted-set 3.0 1.0 -2.5 4.0) '(-2.5 1.0 3.0 4.0)])
(range 5 10) '(5 6 7 8 9)
#?@(:cljs [(int-array 3) '(nil nil nil)]
:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

:c 800
nil 40}
input-sorted-map (into (sorted-map) input-map)
#?@(:lpy [] :default [input-sorted-map (into (sorted-map) input-map)])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

(is (= input (into #{} (seq input))))
(is (= input-hash (into (hash-set) (seq input))))
(is (= input-sorted-map (into (sorted-map) (seq input-sorted-map))))
#?(:lpy nil :default (is (= input-sorted-map (into (sorted-map) (seq input-sorted-map)))))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

true (range)
true (rseq [1 2 3])

#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

false (object-array 3))))
false (object-array 3)

#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

true "a string"
true (object-array 3)

#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

Copy link
Collaborator

@E-A-Griffin E-A-Griffin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ton of small suggestions, mostly for comments, thanks for your hard work on this!

(deftest test-set?
(are [expected x] (= expected (set? x))
true (sorted-set :a)
#?@(:lpy [] :default [true (sorted-set :a)])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

false \a
false (object-array 3))))
false (object-array 3)
#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

;; test whether it's a fixed-length integer of some sort.
(is (int? (short 0)))
#?@(:cljs []
:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

(testing "special symbols"
(are [arg] (special-symbol? 'arg)
&
#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

(is (= "ab" (subs "abcde" nil 2)))
(is (= "a" (subs "abcde" 1 nil)))]
:lpy
[(is (= "" (subs "abcde" 2 1)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the behavior here?

(is (= '(0.0) (vals {0 0.0})))
(is (= '(:b) (vals {:a :b})))
(is (= '(:b :d) (vals {:a :b :c :d})))
(is (contains? #{'(:b :d) '(:d :b)} (vals {:a :b :c :d})))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting

[1 2 3] '(1 2 3)
[1 2 3] [1 2 3]
[1 2 3] (sorted-set 1 2 3)
#?@(:lpy [] :default [[1 2 3] (sorted-set 1 2 3)])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

false \a
false (object-array 3))))
false (object-array 3)
#?@(:lpy []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment would be good here

[(is (true? (str/ends-with? 'ab "b")))
(is (false? (str/ends-with? 'ab "a")))
(is (true? (str/ends-with? :ab "b")))
(is (false? (str/ends-with? :ab "a")))])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation is weird on this file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some tabs ended up in there somehow. Fixed.

(is (thrown? Exception (str/starts-with? :ab ":a")))
(is (thrown? Exception (str/starts-with? 'a/b ":a")))
(is (thrown? Exception (str/starts-with? :a/b ":a")))]
:default
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation is weird on this file

@jeaye
Copy link
Member

jeaye commented Jan 19, 2026

I can add a section on Basilisp-specific differences to the README as part of this PR, if you all like. I do also maintain a Differences from Clojure documentation which mirrors the same document for Clojurescript. I can link to that for more a more comprehensive list.

No need to tackle that now. When we do it, I'd like to get the Clojure core team involved, so we can make a coordinated effort across dialects. For now, keeping that "Differences from Clojure" document up to date with anything you've learned by making this PR would be appreciated.

@jeaye
Copy link
Member

jeaye commented Jan 19, 2026

Excellent review, @E-A-Griffin. I've gone through the whole PR but had nothing more to add than what she identified. Once we're good with each of these comments being addressed, and you feel good about the PR Chris, I think we're golden!

Going forward, Chris, if you have anyone from the Basilisp community who would be interested in helping us write these tests, I'd appreciate the call to action. It's a lot of work and we need to kick up some more momentum for 2026.

@chrisrink10
Copy link
Contributor Author

@jeaye @E-A-Griffin I believe I have addressed all of the comments.

In general I tried to add comments where requested, but in cases where I could easily wrap a test case with (for example) (when-var-exists sorted-set ...) I did that instead since that won't require any manual intervention whenever Basilisp finally adds those Vars. However, I couldn't do that in every case since many tests are defined with are.

Let me know if you have any other comments. Thanks again for the review!

@jeaye jeaye merged commit db35f8f into jank-lang:main Jan 22, 2026
6 checks passed
@jeaye
Copy link
Member

jeaye commented Jan 22, 2026

@chrisrink10 chrisrink10 deleted the feature/support-basilisp branch January 22, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants