Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions test/language/assert_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Lua.Language.AssertTest do
use ExUnit.Case, async: true

setup do
%{lua: Lua.new(sandboxed: [])}
end

test "assert returns all arguments", %{lua: lua} do
assert {[1, 2, 3], _} = Lua.eval!(lua, "return assert(1, 2, 3)")
end
end
21 changes: 21 additions & 0 deletions test/language/assignment_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Lua.Language.AssignmentTest do
use ExUnit.Case, async: true

setup do
%{lua: Lua.new(sandboxed: [])}
end

test "vararg.lua early lines", %{lua: lua} do
code = ~S"""
function f(a, ...)
local arg = {n = select('#', ...), ...}
for i=1,arg.n do assert(a[i]==arg[i]) end
return arg.n
end
assert(f() == 0)
return f({1,2,3}, 1, 2, 3) == 3
"""

assert {[true], _} = Lua.eval!(lua, code)
end
end
145 changes: 145 additions & 0 deletions test/language/closure_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
defmodule Lua.Language.ClosureTest do
use ExUnit.Case, async: true

setup do
%{lua: Lua.new(sandboxed: [])}
end

test "closure upvalue mutation", %{lua: lua} do
code = ~S"""
local A = 0
local dummy = function () return A end
A = 1
assert(dummy() == 1)
A = 0
return true
"""

assert {[true], _} = Lua.eval!(lua, code)
end

@tag :skip
test "closure upvalue mutation through nested scope", %{lua: lua} do
# Known limitation: upvalue mutation through nested function scopes
# doesn't propagate correctly yet (upvalue cell sharing)
code = ~S"""
local A = 0
function f()
local dummy = function () return A end
A = 1
local val = dummy()
A = 0
return val
end
return f()
"""

assert {[1], _} = Lua.eval!(lua, code)
end

test "closure in loop accessing parameter through upvalue", %{lua: lua} do
code = ~S"""
function f(x)
local a = {}
for i=1,3 do
a[i] = function () return x end
end
return a[1](), a[2](), a[3]()
end
return f(10)
"""

assert {[10, 10, 10], _} = Lua.eval!(lua, code)
end

test "closure in loop with local and param upvalues", %{lua: lua} do
# Step 1: Does having a local in loop body break things?
code1 = ~S"""
local function f(x)
local a = {}
for i=1,3 do
local y = 0
a[i] = function () return y end
end
return a[1](), a[2]()
end
return f(10)
"""

assert {[0, 0], _} = Lua.eval!(lua, code1)
end

test "upvalue sharing between sibling closures", %{lua: lua} do
# closure.lua basic pattern - two closures sharing same upvalue
code = ~S"""
local a = 0
local function inc() a = a + 1 end
local function get() return a end
inc()
assert(get() == 1)
inc()
assert(get() == 2)
return true
"""

assert {[true], _} = Lua.eval!(lua, code)
end

test "upvalue through nested scopes (3 levels)", %{lua: lua} do
# Simple: just one level of upvalue
code1 = ~S"""
local x = 10
local function f() return x end
return f()
"""

assert {[10], _} = Lua.eval!(lua, code1)

# Two levels: variable captured through intermediate function's upvalue
code2 = ~S"""
local x = 10
local function outer()
local function inner()
return x
end
return inner()
end
return outer()
"""

assert {[10], _} = Lua.eval!(lua, code2)

# Mutation through nested upvalue chain
code3 = ~S"""
local x = 10
local function outer()
local function inner()
x = x + 1
return x
end
return inner()
end
assert(outer() == 11)
assert(outer() == 12)
return x
"""

assert {[12], _} = Lua.eval!(lua, code3)
end

test "closure in for loop captures loop variable", %{lua: lua} do
# closure.lua pattern - closures in loop body
code = ~S"""
local a = {}
for i = 1, 3 do
a[i] = function() return i end
end
assert(a[1]() == 1)
assert(a[2]() == 2)
assert(a[3]() == 3)
return true
"""

assert {[true], _} = Lua.eval!(lua, code)
end
end
33 changes: 33 additions & 0 deletions test/language/control_flow_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule Lua.Language.ControlFlowTest do
use ExUnit.Case, async: true

setup do
%{lua: Lua.new(sandboxed: [])}
end

test "if false should not execute body", %{lua: lua} do
# constructs.lua line 20: dead code with division by zero
code = ~S"""
if false then a = 3 // 0; a = 0 % 0 end
return true
"""

assert {[true], _} = Lua.eval!(lua, code)
end

test "semicolons as empty statements", %{lua: lua} do
# constructs.lua lines 13-16
code = ~S"""
do ;;; end
; do ; a = 3; assert(a == 3) end;
;
return true
"""

assert {[true], _} = Lua.eval!(lua, code)
end

test "dead code not evaluated", %{lua: lua} do
assert {[true], _} = Lua.eval!(lua, "if false then a = 3 // 0 end; return true")
end
end
42 changes: 42 additions & 0 deletions test/language/function_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Lua.Language.FunctionTest do
use ExUnit.Case, async: true

setup do
%{lua: Lua.new(sandboxed: [])}
end

test "redefine local function with same name", %{lua: lua} do
code = """
local function f(x) return x + 1 end
assert(f(10) == 11)
local function f(x) return x + 2 end
assert(f(10) == 12)
return true
"""

assert {[true], _} = Lua.eval!(lua, code)
end

test "multi-value return register corruption", %{lua: lua} do
assert {[55, 2], _} =
Lua.eval!(lua, ~S"""
function c12(...)
local x = {...}; x.n = #x
local res = (x.n==2 and x[1] == 1 and x[2] == 2)
if res then res = 55 end
return res, 2
end
return c12(1,2)
""")
end

test "select with multi-return function", %{lua: lua} do
# select(2, load(invalid)) should get the error message from load's two return values
code = ~S"""
local function multi() return nil, "error msg" end
return select(2, multi())
"""

assert {["error msg"], _} = Lua.eval!(lua, code)
end
end
43 changes: 43 additions & 0 deletions test/language/global_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule Lua.Language.GlobalTest do
use ExUnit.Case, async: true

setup do
%{lua: Lua.new(sandboxed: [])}
end

test "_G references the global environment", %{lua: lua} do
# _G should be a table that contains itself
assert {[true], _} = Lua.eval!(lua, "return _G ~= nil")
assert {[true], _} = Lua.eval!(lua, "return type(_G) == 'table'")
end

test "_G contains global functions", %{lua: lua} do
# Standard functions should be accessible via _G
assert {[true], _} = Lua.eval!(lua, "return _G.print == print")
assert {[true], _} = Lua.eval!(lua, "return _G.type == type")
assert {[true], _} = Lua.eval!(lua, "return _G.tostring == tostring")
end

test "_G contains itself", %{lua: lua} do
# _G._G should reference _G
assert {[true], _} = Lua.eval!(lua, "return _G._G == _G")
end

test "can set globals via _G", %{lua: lua} do
code = """
_G.myvar = 42
return myvar
"""

assert {[42], _} = Lua.eval!(lua, code)
end

test "can read globals via _G", %{lua: lua} do
code = """
myvar = 123
return _G.myvar
"""

assert {[123], _} = Lua.eval!(lua, code)
end
end
82 changes: 82 additions & 0 deletions test/language/load_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
defmodule Lua.Language.LoadTest do
use ExUnit.Case, async: true

setup do
%{lua: Lua.new(sandboxed: [])}
end

test "load compiles and returns a function", %{lua: lua} do
code = """
f = load("return 1 + 2")
return f()
"""

assert {[3], _} = Lua.eval!(lua, code)
end

test "load with syntax error returns nil", %{lua: lua} do
# Note: Multi-assignment and table constructors don't capture multiple return values yet
# So we just test that load returns nil on error
code = """
f = load("return 1 +")
return f == nil
"""

assert {[true], _} = Lua.eval!(lua, code)
end

test "loaded function can access upvalues", %{lua: lua} do
code = """
x = 10
f = load("return x + 5")
return f()
"""

assert {[15], _} = Lua.eval!(lua, code)
end

test "load can compile complex code", %{lua: lua} do
code = """
f = load("function add(a, b) return a + b end; return add(3, 4)")
return f()
"""

assert {[7], _} = Lua.eval!(lua, code)
end

test "load returns nil and error for bad code", %{lua: lua} do
code = ~S"""
local st, msg = load("invalid code $$$$")
return st, type(msg)
"""

assert {[nil, "string"], _} = Lua.eval!(lua, code)
end

@tag :skip
test "goto scope validation in load", %{lua: lua} do
# Known limitation: compiler doesn't validate goto-label scope rules
code = ~S"""
local st, msg = load(" goto l1; do ::l1:: end ")
return st, msg
"""

{[st, _msg], _} = Lua.eval!(lua, code)
assert st == nil
end

test "constructs.lua checkload pattern", %{lua: lua} do
# checkload uses select(2, load(s)) to get the error message
# This version uses assert() inside (differs from the require_test version)
code = ~S"""
local function checkload (s, msg)
local err = select(2, load(s))
assert(string.find(err, msg))
end
checkload("invalid $$", "invalid")
return true
"""

assert {[true], _} = Lua.eval!(lua, code)
end
end
Loading