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>
233 lines
5.8 KiB
Elixir
233 lines
5.8 KiB
Elixir
defmodule CljElixir.Phase7Test do
|
|
use ExUnit.Case, async: false
|
|
|
|
defp eval!(source) do
|
|
case CljElixir.Compiler.eval_string(source) do
|
|
{:ok, result, _} -> result
|
|
{:error, errors} -> raise "Compilation failed: #{inspect(errors)}"
|
|
end
|
|
end
|
|
|
|
defp compile!(source) do
|
|
case CljElixir.Compiler.compile_to_beam(source) do
|
|
{:ok, modules} -> modules
|
|
{:error, errors} -> raise "Compilation failed: #{inspect(errors)}"
|
|
end
|
|
end
|
|
|
|
describe "m/=> function specs" do
|
|
test "simple function spec compiles" do
|
|
compile!("""
|
|
(defmodule SpecTest1
|
|
(defn hello [name]
|
|
(str "hello " name))
|
|
(m/=> hello [:=> [:cat :string] :string]))
|
|
""")
|
|
|
|
assert true
|
|
after
|
|
:code.purge(SpecTest1)
|
|
:code.delete(SpecTest1)
|
|
end
|
|
|
|
test "multi-param function spec" do
|
|
compile!("""
|
|
(defmodule SpecTest2
|
|
(defn add [a b]
|
|
(+ a b))
|
|
(m/=> add [:=> [:cat :int :int] :int]))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(SpecTest2)
|
|
:code.delete(SpecTest2)
|
|
end
|
|
|
|
test "optional param generates multiple specs" do
|
|
compile!("""
|
|
(defmodule SpecTest3
|
|
(defn greet
|
|
([name] (greet name "hello"))
|
|
([name greeting] (str greeting " " name)))
|
|
(m/=> greet [:=> [:cat :string [:? :string]] :string]))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(SpecTest3)
|
|
:code.delete(SpecTest3)
|
|
end
|
|
|
|
test "multi-arity via :function" do
|
|
compile!("""
|
|
(defmodule SpecTest4
|
|
(defn greet
|
|
([name] (greet name "hello"))
|
|
([name greeting] (str greeting " " name)))
|
|
(m/=> greet [:function
|
|
[:=> [:cat :string] :string]
|
|
[:=> [:cat :string :string] :string]]))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(SpecTest4)
|
|
:code.delete(SpecTest4)
|
|
end
|
|
|
|
test "spec with various types" do
|
|
compile!("""
|
|
(defmodule SpecTest5
|
|
(defn process [x]
|
|
x)
|
|
(m/=> process [:=> [:cat :any] [:or :int :string :nil]]))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(SpecTest5)
|
|
:code.delete(SpecTest5)
|
|
end
|
|
|
|
test "spec with map type" do
|
|
compile!("""
|
|
(defmodule SpecTest6
|
|
(defn get-name [user]
|
|
(:name user))
|
|
(m/=> get-name [:=> [:cat [:map [:name :string] [:age :int]]] :string]))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(SpecTest6)
|
|
:code.delete(SpecTest6)
|
|
end
|
|
|
|
test "spec with tuple return" do
|
|
compile!("""
|
|
(defmodule SpecTest7
|
|
(defn fetch [id]
|
|
#el[:ok id])
|
|
(m/=> fetch [:=> [:cat :int] [:tuple :atom :int]]))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(SpecTest7)
|
|
:code.delete(SpecTest7)
|
|
end
|
|
end
|
|
|
|
describe "auto @type from def schemas" do
|
|
test "def with map schema generates type" do
|
|
compile!("""
|
|
(defmodule TypeTest1
|
|
(def User [:map [:name :string] [:age :int]])
|
|
(defn get-name [user]
|
|
(:name user)))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(TypeTest1)
|
|
:code.delete(TypeTest1)
|
|
end
|
|
|
|
test "def with or schema" do
|
|
compile!("""
|
|
(defmodule TypeTest2
|
|
(def Status [:enum :active :inactive :pending])
|
|
(defn check [s] s))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(TypeTest2)
|
|
:code.delete(TypeTest2)
|
|
end
|
|
|
|
test "def with and schema" do
|
|
compile!("""
|
|
(defmodule TypeTest3
|
|
(def PositiveInt [:and :int [:> 0]])
|
|
(defn check [n] n))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(TypeTest3)
|
|
:code.delete(TypeTest3)
|
|
end
|
|
end
|
|
|
|
describe "schema cross-references" do
|
|
test "spec references a named schema type" do
|
|
compile!("""
|
|
(defmodule CrossRefTest1
|
|
(def User [:map [:name :string] [:age :int]])
|
|
(defn get-user [id]
|
|
{:name "alice" :age 30})
|
|
(m/=> get-user [:=> [:cat :int] User]))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(CrossRefTest1)
|
|
:code.delete(CrossRefTest1)
|
|
end
|
|
|
|
test "schema references another schema" do
|
|
compile!("""
|
|
(defmodule CrossRefTest2
|
|
(def PositiveInt [:and :int [:> 0]])
|
|
(def Config [:map
|
|
[:host :string]
|
|
[:port PositiveInt]
|
|
[:ssl? :boolean]])
|
|
(defn load-config []
|
|
{:host "localhost" :port 8080 :"ssl?" true}))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(CrossRefTest2)
|
|
:code.delete(CrossRefTest2)
|
|
end
|
|
end
|
|
|
|
describe "recursive schemas" do
|
|
test "recursive schema with registry" do
|
|
compile!("""
|
|
(defmodule RecursiveTest1
|
|
(def Tree [:schema {:registry {:tree [:or :int [:tuple [:ref :tree] [:ref :tree]]]}} [:ref :tree]])
|
|
(defn make-leaf [n] n))
|
|
""")
|
|
assert true
|
|
after
|
|
:code.purge(RecursiveTest1)
|
|
:code.delete(RecursiveTest1)
|
|
end
|
|
end
|
|
|
|
describe "functions still work correctly" do
|
|
test "module with spec can be called" do
|
|
result = eval!("""
|
|
(defmodule FuncSpecTest1
|
|
(defn hello [name]
|
|
(str "hello " name))
|
|
(m/=> hello [:=> [:cat :string] :string]))
|
|
(FuncSpecTest1/hello "world")
|
|
""")
|
|
assert result == "hello world"
|
|
after
|
|
:code.purge(FuncSpecTest1)
|
|
:code.delete(FuncSpecTest1)
|
|
end
|
|
|
|
test "module with type and spec" do
|
|
result = eval!("""
|
|
(defmodule FuncSpecTest2
|
|
(def User [:map [:name :string] [:age :int]])
|
|
(defn make-user [name age]
|
|
{:name name :age age})
|
|
(m/=> make-user [:=> [:cat :string :int] User]))
|
|
(FuncSpecTest2/make-user "alice" 30)
|
|
""")
|
|
assert result == %{name: "alice", age: 30}
|
|
after
|
|
:code.purge(FuncSpecTest2)
|
|
:code.delete(FuncSpecTest2)
|
|
end
|
|
end
|
|
end
|