# testing special comment on first line -- $Id: testes/main.lua $ -- See Copyright Notice in file all.lua -- most (all?) tests here assume a reasonable "Unix-like" shell if _port then return end -- use only "double quotes" inside shell scripts (better change to -- run on Windows) print ("testing stand-alone interpreter") assert(os.execute()) -- machine has a system command local arg = arg or ARG local prog = os.tmpname() local otherprog = os.tmpname() local out = os.tmpname() local progname do local i = 0 while arg[i] do i=i-1 end progname = arg[i+1] end print("progname: "..progname) local prepfile = function (s, mod, p) mod = mod and "wb" or "w" -- mod true means binary files p = p or prog -- file to write the program local f = io.open(p, mod) f:write(s) assert(f:close()) end local function getoutput () local f = io.open(out) local t = f:read("a") f:close() assert(os.remove(out)) return t end local function checkprogout (s) -- expected result must end with new line assert(string.sub(s, -1) == "\n") local t = getoutput() for line in string.gmatch(s, ".-\n") do assert(string.find(t, line, 1, true)) end end local function checkout (s) local t = getoutput() if s ~= t then print(string.format("'%s' - '%s'\n", s, t)) end assert(s == t) return t end local function RUN (p, ...) p = string.gsub(p, "lua", '"'..progname..'"', 1) local s = string.format(p, ...) assert(os.execute(s)) end local function NoRun (msg, p, ...) p = string.gsub(p, "lua", '"'..progname..'"', 1) local s = string.format(p, ...) s = string.format("%s >%s 2>&1", s, out) -- send output and error to 'out' assert(not os.execute(s)) assert(string.find(getoutput(), msg, 1, true)) -- check error message end RUN('lua -v') print(string.format("(temporary program file used in these tests: %s)", prog)) -- running stdin as a file prepfile"" RUN('lua - < %s > %s', prog, out) checkout("") prepfile[[ print( 1, a ) ]] RUN('lua - < %s > %s', prog, out) checkout("1\tnil\n") RUN('echo "print(10)\nprint(2)\n" | lua > %s', out) checkout("10\n2\n") -- testing BOM prepfile("\xEF\xBB\xBF") RUN('lua %s > %s', prog, out) checkout("") prepfile("\xEF\xBB\xBFprint(3)") RUN('lua %s > %s', prog, out) checkout("3\n") prepfile("\xEF\xBB\xBF# comment!!\nprint(3)") RUN('lua %s > %s', prog, out) checkout("3\n") -- bad BOMs prepfile("\xEF", true) NoRun("unexpected symbol", 'lua %s', prog) prepfile("\xEF\xBB", true) NoRun("unexpected symbol", 'lua %s', prog) prepfile("\xEFprint(3)", true) NoRun("unexpected symbol", 'lua %s', prog) prepfile("\xEF\xBBprint(3)", true) NoRun("unexpected symbol", 'lua %s', prog) -- test option '-' RUN('echo "print(arg[1])" | lua - -h > %s', out) checkout("-h\n") -- test environment variables used by Lua prepfile("print(package.path)") -- test LUA_PATH RUN('env LUA_INIT= LUA_PATH=x lua %s > %s', prog, out) checkout("x\n") -- test LUA_PATH_version RUN('env LUA_INIT= LUA_PATH_5_4=y LUA_PATH=x lua %s > %s', prog, out) checkout("y\n") -- test LUA_CPATH prepfile("print(package.cpath)") RUN('env LUA_INIT= LUA_CPATH=xuxu lua %s > %s', prog, out) checkout("xuxu\n") -- test LUA_CPATH_version RUN('env LUA_INIT= LUA_CPATH_5_4=yacc LUA_CPATH=x lua %s > %s', prog, out) checkout("yacc\n") -- test LUA_INIT (and its access to 'arg' table) prepfile("print(X)") RUN('env LUA_INIT="X=tonumber(arg[1])" lua %s 3.2 > %s', prog, out) checkout("3.2\n") -- test LUA_INIT_version prepfile("print(X)") RUN('env LUA_INIT_5_4="X=10" LUA_INIT="X=3" lua %s > %s', prog, out) checkout("10\n") -- test LUA_INIT for files prepfile("x = x or 10; print(x); x = x + 1") RUN('env LUA_INIT="@%s" lua %s > %s', prog, prog, out) checkout("10\n11\n") -- test errors in LUA_INIT NoRun('LUA_INIT:1: msg', 'env LUA_INIT="error(\'msg\')" lua') -- test option '-E' local defaultpath, defaultCpath do prepfile("print(package.path, package.cpath)") RUN('env LUA_INIT="error(10)" LUA_PATH=xxx LUA_CPATH=xxx lua -E %s > %s', prog, out) local output = getoutput() defaultpath = string.match(output, "^(.-)\t") defaultCpath = string.match(output, "\t(.-)$") -- running with an empty environment RUN('env -i lua %s > %s', prog, out) local out = getoutput() assert(defaultpath == string.match(output, "^(.-)\t")) assert(defaultCpath == string.match(output, "\t(.-)$")) end -- paths did not change assert(not string.find(defaultpath, "xxx") and string.find(defaultpath, "lua") and not string.find(defaultCpath, "xxx") and string.find(defaultCpath, "lua")) -- test replacement of ';;' to default path local function convert (p) prepfile("print(package.path)") RUN('env LUA_PATH="%s" lua %s > %s', p, prog, out) local expected = getoutput() expected = string.sub(expected, 1, -2) -- cut final end of line if string.find(p, ";;") then p = string.gsub(p, ";;", ";"..defaultpath..";") p = string.gsub(p, "^;", "") -- remove ';' at the beginning p = string.gsub(p, ";$", "") -- remove ';' at the end end assert(p == expected) end convert(";") convert(";;") convert("a;;b") convert(";;b") convert("a;;") convert("a;b;;c") -- test -l over multiple libraries prepfile("print(1); a=2; return {x=15}") prepfile(("print(a); print(_G['%s'].x)"):format(prog), false, otherprog) RUN('env LUA_PATH="?;;" lua -l %s -l%s -lstring -l io %s > %s', prog, otherprog, otherprog, out) checkout("1\n2\n15\n2\n15\n") -- test explicit global names in -l prepfile("print(str.upper'alo alo', m.max(10, 20))") RUN("lua -l 'str=string' '-lm=math' -e 'print(m.sin(0))' %s > %s", prog, out) checkout("0.0\nALO ALO\t20\n") -- test module names with version sufix ("libs/lib2-v2") RUN("env LUA_CPATH='./libs/?.so' lua -l lib2-v2 -e 'print(lib2.id())' > %s", out) checkout("true\n") -- test 'arg' table local a = [[ assert(#arg == 3 and arg[1] == 'a' and arg[2] == 'b' and arg[3] == 'c') assert(arg[-1] == '--' and arg[-2] == "-e " and arg[-3] == '%s') assert(arg[4] == undef and arg[-4] == undef) local a, b, c = ... assert(... == 'a' and a == 'a' and b == 'b' and c == 'c') ]] a = string.format(a, progname) prepfile(a) RUN('lua "-e " -- %s a b c', prog) -- "-e " runs an empty command -- test 'arg' availability in libraries prepfile"assert(arg)" prepfile("assert(arg)", false, otherprog) RUN('env LUA_PATH="?;;" lua -l%s - < %s', prog, otherprog) -- test messing up the 'arg' table RUN('echo "print(...)" | lua -e "arg[1] = 100" - > %s', out) checkout("100\n") NoRun("'arg' is not a table", 'echo "" | lua -e "arg = 1" -') -- test error in 'print' RUN('echo 10 | lua -e "print=nil" -i > /dev/null 2> %s', out) assert(string.find(getoutput(), "error calling 'print'")) -- test 'debug.debug' RUN('echo "io.stderr:write(1000)\ncont" | lua -e "require\'debug\'.debug()" 2> %s', out) checkout("lua_debug> 1000lua_debug> ") print("testing warnings") -- no warnings by default RUN('echo "io.stderr:write(1); warn[[XXX]]" | lua 2> %s', out) checkout("1") prepfile[[ warn("@allow") -- unknown control, ignored warn("@off", "XXX", "@off") -- these are not control messages warn("@off") -- this one is warn("@on", "YYY", "@on") -- not control, but warn is off warn("@off") -- keep it off warn("@on") -- restart warnings warn("", "@on") -- again, no control, real warning warn("@on") -- keep it "started" warn("Z", "Z", "Z") -- common warning ]] RUN('lua -W %s 2> %s', prog, out) checkout[[ Lua warning: @offXXX@off Lua warning: @on Lua warning: ZZZ ]] prepfile[[ warn("@allow") -- create two objects to be finalized when closing state -- the errors in the finalizers must generate warnings u1 = setmetatable({}, {__gc = function () error("XYZ") end}) u2 = setmetatable({}, {__gc = function () error("ZYX") end}) ]] RUN('lua -W %s 2> %s', prog, out) checkprogout("ZYX)\nXYZ)\n") -- bug since 5.2: finalizer called when closing a state could -- subvert finalization order prepfile[[ -- should be called last print("creating 1") setmetatable({}, {__gc = function () print(1) end}) print("creating 2") setmetatable({}, {__gc = function () print("2") print("creating 3") -- this finalizer should not be called, as object will be -- created after 'lua_close' has been called setmetatable({}, {__gc = function () print(3) end}) print(collectgarbage()) -- cannot call collector here os.exit(0, true) end}) ]] RUN('lua -W %s > %s', prog, out) checkout[[ creating 1 creating 2 2 creating 3 nil 1 ]] -- test many arguments prepfile[[print(({...})[30])]] RUN('lua %s %s > %s', prog, string.rep(" a", 30), out) checkout("a\n") RUN([[lua "-eprint(1)" -ea=3 -e "print(a)" > %s]], out) checkout("1\n3\n") -- test iteractive mode prepfile[[ (6*2-6) -- === a = 10 print(a) a]] RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) checkprogout("6\n10\n10\n\n") prepfile("a = [[b\nc\nd\ne]]\n=a") RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) checkprogout("b\nc\nd\ne\n\n") local prompt = "alo" prepfile[[ -- a = 2 ]] RUN([[lua "-e_PROMPT='%s'" -i < %s > %s]], prompt, prog, out) local t = getoutput() assert(string.find(t, prompt .. ".*" .. prompt .. ".*" .. prompt)) -- using the prompt default prepfile[[ -- a = 2 ]] RUN([[lua -i < %s > %s]], prog, out) local t = getoutput() prompt = "> " -- the default assert(string.find(t, prompt .. ".*" .. prompt .. ".*" .. prompt)) -- non-string prompt prompt = "local C = 0;\z _PROMPT=setmetatable({},{__tostring = function () \z C = C + 1; return C end})" prepfile[[ -- a = 2 ]] RUN([[lua -e "%s" -i < %s > %s]], prompt, prog, out) local t = getoutput() assert(string.find(t, [[ 1 -- 2a = 2 3 ]], 1, true)) -- test for error objects prepfile[[ debug = require "debug" m = {x=0} setmetatable(m, {__tostring = function(x) return tostring(debug.getinfo(4).currentline + x.x) end}) error(m) ]] NoRun(progname .. ": 6\n", [[lua %s]], prog) prepfile("error{}") NoRun("error object is a table value", [[lua %s]], prog) -- chunk broken in many lines local s = [=[ -- function f ( x ) local a = [[ xuxu ]] local b = "\ xuxu\n" if x == 11 then return 1 + 12 , 2 + 20 end --[[ test multiple returns ]] return x + 1 --\\ end return( f( 100 ) ) assert( a == b ) do return f( 11 ) end ]=] s = string.gsub(s, ' ', '\n\n') -- change all spaces for newlines prepfile(s) RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) checkprogout("101\n13\t22\n\n") prepfile[[#comment in 1st line without \n at the end]] RUN('lua %s', prog) -- first-line comment with binary file prepfile("#comment\n" .. string.dump(load("print(3)")), true) RUN('lua %s > %s', prog, out) checkout('3\n') -- close Lua with an open file prepfile(string.format([[io.output(%q); io.write('alo')]], out)) RUN('lua %s', prog) checkout('alo') -- bug in 5.2 beta (extra \0 after version line) RUN([[lua -v -e"print'hello'" > %s]], out) t = getoutput() assert(string.find(t, "PUC%-Rio\nhello")) -- testing os.exit prepfile("os.exit(nil, true)") RUN('lua %s', prog) prepfile("os.exit(0, true)") RUN('lua %s', prog) prepfile("os.exit(true, true)") RUN('lua %s', prog) prepfile("os.exit(1, true)") NoRun("", "lua %s", prog) -- no message prepfile("os.exit(false, true)") NoRun("", "lua %s", prog) -- no message -- to-be-closed variables in main chunk prepfile[[ local x = setmetatable({}, {__close = function (self, err) assert(err == nil) print("Ok") end}) local e1 = setmetatable({}, {__close = function () print(120) end}) os.exit(true, true) ]] RUN('lua %s > %s', prog, out) checkprogout("120\nOk\n") -- remove temporary files assert(os.remove(prog)) assert(os.remove(otherprog)) assert(not os.remove(out)) -- invalid options NoRun("unrecognized option '-h'", "lua -h") NoRun("unrecognized option '---'", "lua ---") NoRun("unrecognized option '-Ex'", "lua -Ex") NoRun("unrecognized option '-vv'", "lua -vv") NoRun("unrecognized option '-iv'", "lua -iv") NoRun("'-e' needs argument", "lua -e") NoRun("syntax error", "lua -e a") NoRun("'-l' needs argument", "lua -l") if T then -- test library? print("testing 'not enough memory' to create a state") NoRun("not enough memory", "env MEMLIMIT=100 lua") -- testing 'warn' warn("@store") warn("@123", "456", "789") assert(_WARN == "@123456789"); _WARN = false warn("zip", "", " ", "zap") assert(_WARN == "zip zap"); _WARN = false warn("ZIP", "", " ", "ZAP") assert(_WARN == "ZIP ZAP"); _WARN = false warn("@normal") end do -- 'warn' must get at least one argument local st, msg = pcall(warn) assert(string.find(msg, "string expected")) -- 'warn' does not leave unfinished warning in case of errors -- (message would appear in next warning) st, msg = pcall(warn, "SHOULD NOT APPEAR", {}) assert(string.find(msg, "string expected")) end print('+') print('testing Ctrl C') do -- interrupt a script local function kill (pid) return os.execute(string.format('kill -INT %s 2> /dev/null', pid)) end -- function to run a script in background, returning its output file -- descriptor and its pid local function runback (luaprg) -- shell script to run 'luaprg' in background and echo its pid local shellprg = string.format('%s -e "%s" & echo $!', progname, luaprg) local f = io.popen(shellprg, "r") -- run shell script local pid = f:read() -- get pid for Lua script print("(if test fails now, it may leave a Lua script running in \z background, pid " .. pid .. ")") return f, pid end -- Lua script that runs protected infinite loop and then prints '42' local f, pid = runback[[ pcall(function () print(12); while true do end end); print(42)]] -- wait until script is inside 'pcall' assert(f:read() == "12") kill(pid) -- send INT signal to Lua script -- check that 'pcall' captured the exception and script continued running assert(f:read() == "42") -- expected output assert(f:close()) print("done") -- Lua script in a long unbreakable search local f, pid = runback[[ print(15); string.find(string.rep('a', 100000), '.*b')]] -- wait (so script can reach the loop) assert(f:read() == "15") assert(os.execute("sleep 1")) -- must send at least two INT signals to stop this Lua script local n = 100 for i = 0, 100 do -- keep sending signals if not kill(pid) then -- until it fails n = i -- number of non-failed kills break end end assert(f:close()) assert(n >= 2) print(string.format("done (with %d kills)", n)) end print("OK")