Files
CljElixir/test/clj_elixir/phase2_test.exs
Adam d8719b6d48 Phases 1-7: Complete CljElixir compiler through Malli schema adapter
Bootstrap compiler (reader, analyzer, transformer, compiler, Mix plugin),
core protocols (16 protocols for Map/List/Tuple/BitString), PersistentVector
(bit-partitioned trie), domain tools (clojurify/elixirify), BEAM concurrency
(receive, spawn, GenServer), control flow & macros (threading, try/catch,
destructuring, defmacro with quasiquote/auto-gensym), and Malli schema
adapter (m/=> specs, auto @type, recursive schemas, cross-references).

537 compiler tests + 55 Malli unit tests + 15 integration tests = 607 total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:38:22 -04:00

418 lines
12 KiB
Elixir

defmodule CljElixir.Phase2Test do
use ExUnit.Case, async: false
# Helper to compile and evaluate CljElixir code
# Uses vector_as_list: true until PersistentVector is implemented (Phase 3 WS-3)
defp eval!(source) do
case CljElixir.Compiler.eval_string(source, vector_as_list: true) do
{:ok, result, _bindings} -> result
{:error, errors} -> raise "CljElixir eval error: #{inspect(errors)}"
end
end
# Protocols and core modules are compiled by the Mix compiler plugin
# (compilers: [..., :clj_elixir] in mix.exs). No setup needed.
# ==========================================================================
# ILookup - get
# ==========================================================================
describe "get (ILookup)" do
test "get from map with existing key" do
assert eval!("(get {:a 1 :b 2} :a)") == 1
end
test "get from map with missing key returns nil" do
assert eval!("(get {:a 1} :b)") == nil
end
test "get from map with missing key and default" do
assert eval!("(get {:a 1} :b 42)") == 42
end
test "get from map with existing key ignores default" do
assert eval!("(get {:a 1} :a 42)") == 1
end
end
# ==========================================================================
# IAssociative - assoc, contains?
# ==========================================================================
describe "assoc (IAssociative)" do
test "assoc adds new key to map" do
assert eval!("(assoc {:a 1} :b 2)") == %{a: 1, b: 2}
end
test "assoc updates existing key" do
assert eval!("(assoc {:a 1} :a 2)") == %{a: 2}
end
test "assoc on empty map" do
assert eval!("(assoc {} :a 1)") == %{a: 1}
end
end
describe "contains? (IAssociative)" do
test "contains? returns true for existing key" do
assert eval!("(contains? {:a 1 :b 2} :a)") == true
end
test "contains? returns false for missing key" do
assert eval!("(contains? {:a 1} :c)") == false
end
end
# ==========================================================================
# IMap - dissoc
# ==========================================================================
describe "dissoc (IMap)" do
test "dissoc removes key from map" do
assert eval!("(dissoc {:a 1 :b 2} :a)") == %{b: 2}
end
test "dissoc with missing key returns same map" do
assert eval!("(dissoc {:a 1} :b)") == %{a: 1}
end
end
# ==========================================================================
# ICounted - count
# ==========================================================================
describe "count (ICounted)" do
test "count of map" do
assert eval!("(count {:a 1 :b 2 :c 3})") == 3
end
test "count of list" do
assert eval!("(count (list 1 2 3))") == 3
end
test "count of empty map" do
assert eval!("(count {})") == 0
end
test "count of tuple" do
assert eval!("(count #el[1 2 3])") == 3
end
test "count of string" do
assert eval!("(count \"hello\")") == 5
end
end
# ==========================================================================
# ISeq - first, rest
# ==========================================================================
describe "first/rest (ISeq)" do
test "first of list" do
assert eval!("(first (list 1 2 3))") == 1
end
test "rest of list" do
assert eval!("(rest (list 1 2 3))") == [2, 3]
end
test "first of empty list" do
assert eval!("(first (list))") == nil
end
test "rest of empty list" do
assert eval!("(rest (list))") == []
end
end
# ==========================================================================
# ISeqable - seq
# ==========================================================================
describe "seq (ISeqable)" do
test "seq of non-empty list returns the list" do
assert eval!("(seq (list 1 2 3))") == [1, 2, 3]
end
test "seq of empty list returns nil" do
assert eval!("(seq (list))") == nil
end
test "seq of map returns key-value pairs" do
result = eval!("(seq {:a 1})")
assert is_list(result)
assert length(result) == 1
end
end
# ==========================================================================
# ICollection - conj
# ==========================================================================
describe "conj (ICollection)" do
test "conj onto list prepends" do
assert eval!("(conj (list 2 3) 1)") == [1, 2, 3]
end
test "conj onto map merges tuple entry" do
result = eval!("(conj {:a 1} {:b 2})")
assert result == %{a: 1, b: 2}
end
end
# ==========================================================================
# IIndexed - nth
# ==========================================================================
describe "nth (IIndexed)" do
test "nth from tuple" do
assert eval!("(nth #el[10 20 30] 1)") == 20
end
test "nth with default" do
assert eval!("(nth #el[10 20] 5 :not-found)") == :"not-found"
end
end
# ==========================================================================
# IStack - peek, pop (via protocol on List)
# ==========================================================================
# Note: peek and pop are not in the builtin dispatch yet, they go through
# the protocol directly - skip for now unless dispatch was added
# ==========================================================================
# Sequence wrapper functions
# ==========================================================================
describe "map (sequence function)" do
test "map over list" do
assert eval!("(map (fn [x] (inc x)) (list 1 2 3))") == [2, 3, 4]
end
end
describe "filter" do
test "filter list" do
assert eval!("(filter (fn [x] (> x 2)) (list 1 2 3 4))") == [3, 4]
end
end
describe "reduce" do
test "reduce with initial value" do
assert eval!("(reduce (fn [a b] (+ a b)) 0 (list 1 2 3))") == 6
end
test "reduce without initial value" do
assert eval!("(reduce (fn [a b] (+ a b)) (list 1 2 3))") == 6
end
end
describe "concat" do
test "concat two lists" do
assert eval!("(concat (list 1 2) (list 3 4))") == [1, 2, 3, 4]
end
end
describe "take and drop" do
test "take from list" do
assert eval!("(take 2 (list 1 2 3 4))") == [1, 2]
end
test "drop from list" do
assert eval!("(drop 2 (list 1 2 3 4))") == [3, 4]
end
end
describe "sort" do
test "sort list" do
assert eval!("(sort (list 3 1 2))") == [1, 2, 3]
end
end
describe "distinct" do
test "distinct removes duplicates" do
assert eval!("(distinct (list 1 2 1 3 2))") == [1, 2, 3]
end
end
describe "frequencies" do
test "frequencies counts occurrences" do
assert eval!("(frequencies (list :a :b :a :c :b :a))") == %{a: 3, b: 2, c: 1}
end
end
describe "partition" do
test "partition into chunks" do
assert eval!("(partition 2 (list 1 2 3 4))") == [[1, 2], [3, 4]]
end
end
describe "mapcat" do
test "mapcat flattens results" do
assert eval!("(mapcat (fn [x] (list x x)) (list 1 2 3))") == [1, 1, 2, 2, 3, 3]
end
end
# ==========================================================================
# Map-specific functions
# ==========================================================================
describe "keys" do
test "keys of map" do
result = eval!("(keys {:a 1 :b 2})")
assert Enum.sort(result) == [:a, :b]
end
end
describe "vals" do
test "vals of map" do
result = eval!("(vals {:a 1 :b 2})")
assert Enum.sort(result) == [1, 2]
end
end
describe "merge" do
test "merge two maps" do
assert eval!("(merge {:a 1} {:b 2})") == %{a: 1, b: 2}
end
test "merge with overwrite" do
assert eval!("(merge {:a 1} {:a 2})") == %{a: 2}
end
end
describe "select-keys" do
test "select-keys from map" do
assert eval!("(select-keys {:a 1 :b 2 :c 3} (list :a :c))") == %{a: 1, c: 3}
end
end
describe "into" do
test "into map from list of tuples" do
assert eval!("(into {} (list #el[:a 1] #el[:b 2]))") == %{a: 1, b: 2}
end
end
# ==========================================================================
# update
# ==========================================================================
describe "update" do
test "update map value with function" do
assert eval!("(update {:a 1} :a (fn [x] (inc x)))") == %{a: 2}
end
end
# ==========================================================================
# empty?
# ==========================================================================
describe "empty?" do
test "empty? on empty map" do
assert eval!("(empty? {})") == true
end
test "empty? on non-empty map" do
assert eval!("(empty? {:a 1})") == false
end
end
# ==========================================================================
# Keyword-as-function through ILookup
# ==========================================================================
describe "keyword-as-function" do
test "keyword as function on map" do
assert eval!("(:name {:name \"Ada\"})") == "Ada"
end
test "keyword with default value" do
assert eval!("(:age {:name \"Ada\"} 25)") == 25
end
end
# ==========================================================================
# Compound functions (get-in, assoc-in, update-in)
# ==========================================================================
describe "get-in" do
test "get-in nested map" do
assert eval!("(get-in {:a {:b {:c 42}}} (list :a :b :c))") == 42
end
test "get-in with missing key" do
assert eval!("(get-in {:a {:b 1}} (list :a :c))") == nil
end
end
describe "assoc-in" do
test "assoc-in nested map" do
assert eval!("(assoc-in {:a {:b 1}} (list :a :b) 2)") == %{a: %{b: 2}}
end
end
describe "update-in" do
test "update-in nested map" do
assert eval!("(update-in {:a {:b 1}} (list :a :b) (fn [x] (inc x)))") == %{a: %{b: 2}}
end
end
# ==========================================================================
# reduce-kv
# ==========================================================================
describe "reduce-kv" do
test "reduce-kv over map" do
# Reduce a map collecting keys into a list
result = eval!("(reduce-kv (fn [acc _k v] (+ acc v)) 0 {:a 1 :b 2 :c 3})")
assert result == 6
end
end
# ==========================================================================
# FFI ? and ! preservation
# ==========================================================================
describe "FFI name munging fix" do
test "Map/has-key? works correctly" do
assert eval!("(Map/has-key? {:a 1} :a)") == true
end
test "Map/has-key? returns false for missing key" do
assert eval!("(Map/has-key? {:a 1} :b)") == false
end
end
# ==========================================================================
# End-to-end integration
# ==========================================================================
describe "end-to-end integration" do
test "realistic data transformation pipeline" do
source = """
(let [data (list {:name "Alice" :age 30}
{:name "Bob" :age 25}
{:name "Carol" :age 35})]
(map (fn [p] (:name p)) (filter (fn [p] (> (get p :age) 28)) data)))
"""
assert eval!(source) == ["Alice", "Carol"]
end
test "nested map operations" do
source = """
(let [m {:a 1 :b 2 :c 3}]
(dissoc (assoc m :d 4) :b))
"""
assert eval!(source) == %{a: 1, c: 3, d: 4}
end
test "count and empty? together" do
source = """
(let [m {:a 1}]
(list (count m) (empty? m) (empty? {})))
"""
assert eval!(source) == [1, false, true]
end
end
end