summaryrefslogtreecommitdiff
path: root/spec/util
diff options
context:
space:
mode:
Diffstat (limited to 'spec/util')
-rw-r--r--spec/util/git_repo.lua108
-rw-r--r--spec/util/mock-server.lua98
-rw-r--r--spec/util/quick.lua472
-rw-r--r--spec/util/test_env.lua1187
-rw-r--r--spec/util/versions.lua17
5 files changed, 1882 insertions, 0 deletions
diff --git a/spec/util/git_repo.lua b/spec/util/git_repo.lua
new file mode 100644
index 0000000..b3ddd9e
--- /dev/null
+++ b/spec/util/git_repo.lua
@@ -0,0 +1,108 @@
+local git_repo = {}
+
+local test_env = require("spec.util.test_env")
+local lfs = require("lfs")
+
+local files = {
+----------------------------------------
+["testrock-dev-1.rockspec"] = [[
+package = "testrock"
+version = "dev-1"
+source = {
+ url = "git://localhost:20000/testrock"
+}
+description = {
+ homepage = "https://localhost",
+ license = "MIT"
+}
+dependencies = {}
+build = {
+ type = "builtin",
+ modules = {
+ testrock = "testrock.lua"
+ }
+}
+]],
+----------------------------------------
+["testrock.lua"] = [[
+local testrock = {}
+
+function testrock.say()
+ return "Hello, world!"
+end
+
+return testrock
+]],
+----------------------------------------
+["foo.c"] = [[
+#include <lua.h>
+int luaopen_foo(lua_State* L) {
+ lua_pushnumber(L, 42);
+ return 1;
+}
+]],
+----------------------------------------
+["test.lua"] = [[
+print("this should be ignored!")
+]],
+}
+
+local function write_file(filename, contents)
+ local fd = assert(io.open(filename, "w"))
+ assert(fd:write(contents))
+ fd:close()
+end
+
+local function handling(args)
+ local pok, ret = pcall(args.try)
+ if not pok then
+ pok, ret = pcall(args.catch, ret)
+ end
+ args.finally()
+ if not pok then
+ error(ret)
+ end
+ return ret
+end
+
+function git_repo.start()
+ local dir = lfs.currentdir()
+ return handling {
+ try = function()
+ local pidfile = os.tmpname()
+ local basedir = test_env.testing_paths.testrun_dir .. "/git_repo"
+ local repodir = basedir .. "/testrock"
+ test_env.remove_dir(basedir)
+ lfs.mkdir(basedir)
+ lfs.mkdir(repodir)
+ lfs.chdir(repodir)
+ assert(test_env.execute("git init"))
+ for name, contents in pairs(files) do
+ write_file(name, contents)
+ test_env.execute("git add " .. name)
+ end
+ assert(test_env.execute("git commit -a -m 'initial commit'"))
+ assert(test_env.execute("git branch test-branch"))
+ print("git daemon --reuseaddr --pid-file="..pidfile.." --base-path="..basedir.." --export-all "..repodir.." &")
+ assert(test_env.execute("git daemon --reuseaddr --pid-file="..pidfile.." --base-path="..basedir.." --export-all "..repodir.." &"))
+ assert(test_env.execute("sleep 0.1; netstat -ln | grep '0.0.0.0:9418 .* LISTEN'"))
+ return {
+ stop = function()
+ local fd = io.open(pidfile)
+ local pid = fd:read("*a")
+ fd:close()
+ assert(test_env.execute("kill -HUP " .. pid))
+ test_env.remove_dir(basedir)
+ end
+ }
+ end,
+ catch = function(err)
+ error(err)
+ end,
+ finally = function()
+ lfs.chdir(dir)
+ end,
+ }
+end
+
+return git_repo
diff --git a/spec/util/mock-server.lua b/spec/util/mock-server.lua
new file mode 100644
index 0000000..1b09cd6
--- /dev/null
+++ b/spec/util/mock-server.lua
@@ -0,0 +1,98 @@
+#!/usr/bin/env lua
+
+--- A simple LuaRocks mock-server for testing.
+local restserver = require("restserver")
+local server = restserver:new():port(8080)
+
+server:add_resource("api/tool_version", {
+ {
+ method = "GET",
+ path = "/",
+ produces = "application/json",
+ handler = function(query)
+ local json = { version = query.current }
+ return restserver.response():status(200):entity(json)
+ end
+ }
+})
+
+server:add_resource("api/1/{id:[0-9]+}/status", {
+ {
+ method = "GET",
+ path = "/",
+ produces = "application/json",
+ handler = function(query)
+ local json = { user_id = "123", created_at = "29.1.1993" }
+ return restserver.response():status(200):entity(json)
+ end
+ }
+})
+
+server:add_resource("/api/1/{id:[0-9]+}/check_rockspec", {
+ {
+ method = "GET",
+ path = "/",
+ produces = "application/json",
+ handler = function(query)
+ local json = {}
+ return restserver.response():status(200):entity(json)
+ end
+ }
+})
+
+server:add_resource("/api/1/{id:[0-9]+}/upload", {
+ {
+ method = "POST",
+ path = "/",
+ produces = "application/json",
+ handler = function(query)
+ local json = {module = "luasocket", version = {id = "1.0"}, module_url = "http://localhost/luasocket", manifests = "root", is_new = "is_new"}
+ return restserver.response():status(200):entity(json)
+ end
+ }
+})
+
+server:add_resource("/api/1/{id:[0-9]+}/upload_rock/{id:[0-9]+}", {
+ {
+ method = "POST",
+ path = "/",
+ produces = "application/json",
+ handler = function(query)
+ local json = {"rock","module_url"}
+ return restserver.response():status(200):entity(json)
+ end
+ }
+})
+
+server:add_resource("/file/{name:[^/]+}", {
+ {
+ method = "GET",
+ path = "/",
+ produces = "text/plain",
+ handler = function(query, name)
+ local basedir = arg[1] or "./spec/fixtures"
+ local fd = io.open(basedir .. "/" .. name, "rb")
+ if not fd then
+ return restserver.response():status(404)
+ end
+ local data = fd:read("*a")
+ fd:close()
+ return restserver.response():status(200):entity(data)
+ end
+ }
+})
+
+-- SHUTDOWN this mock-server
+server:add_resource("/shutdown", {
+ {
+ method = "GET",
+ path = "/",
+ handler = function(query)
+ os.exit()
+ return restserver.response():status(200):entity()
+ end
+ }
+})
+
+-- This loads the restserver.xavante plugin
+server:enable("restserver.xavante"):start()
diff --git a/spec/util/quick.lua b/spec/util/quick.lua
new file mode 100644
index 0000000..a525aa9
--- /dev/null
+++ b/spec/util/quick.lua
@@ -0,0 +1,472 @@
+local quick = {}
+
+local dir_sep = package.config:sub(1, 1)
+
+local cfg, dir, fs, versions
+local initialized = false
+
+local function initialize()
+ if initialized then
+ return
+ end
+ initialized = true
+
+ cfg = require("luarocks.core.cfg")
+ dir = require("luarocks.dir")
+ fs = require("luarocks.fs")
+ versions = require("spec.util.versions")
+ cfg.init()
+ fs.init()
+end
+
+local function native_slash(pathname)
+ return (pathname:gsub("[/\\]", dir_sep))
+end
+
+local function parse_cmd(line)
+ local cmd, arg = line:match("^%s*([A-Z_]+):%s*(.*)%s*$")
+ return cmd, arg
+end
+
+local function is_blank(line)
+ return not not line:match("^%s*$")
+end
+
+local function is_hr(line)
+ return not not line:match("^%-%-%-%-%-")
+end
+
+local function parse(filename)
+ local fd = assert(io.open(filename, "rb"))
+ local input = assert(fd:read("*a"))
+ fd:close()
+
+ initialize()
+
+ local tests = {}
+
+ local cur_line = 0
+ local cur_suite = ""
+ local cur_test
+ local cur_op
+ local cur_block
+ local cur_block_name
+ local stack = { "start" }
+
+ local function start_test(arg)
+ cur_test = {
+ name = cur_suite .. arg,
+ ops = {},
+ }
+ cur_op = nil
+ table.insert(tests, cur_test)
+ table.insert(stack, "test")
+ end
+
+ local function fail(msg)
+ io.stderr:write("Error reading " .. filename .. ":" .. cur_line .. ": " .. msg .. "\n")
+ os.exit(1)
+ end
+
+ local function bool_arg(cmd, cur, field, arg)
+ if arg ~= "true" and arg ~= "false" then
+ fail(cmd .. " argument must be 'true' or 'false'")
+ end
+ cur[field] = (arg == "true")
+ end
+
+ local function block_start_arg(cmd, cur, field)
+ if not cur or cur.op ~= "RUN" then
+ fail(cmd .. " must be given in the context of a RUN")
+ end
+ if cur[field] then
+ fail(cmd .. " was already declared")
+ end
+
+ cur[field] = {
+ data = {}
+ }
+ cur_block = cur[field]
+ cur_block_name = cmd
+ table.insert(stack, "block start")
+ end
+
+ local test_env = require("spec.util.test_env")
+ local function expand_vars(line)
+ if not line then
+ return nil
+ end
+ return (line:gsub("%%%b{}", function(var)
+ var = var:sub(3, -2)
+ local fn, fnarg = var:match("^%s*([a-z_]+)%s*%(%s*([^)]+)%s*%)%s*$")
+
+ local value
+ if var == "tmpdir" then
+ value = "%{tmpdir}"
+ elseif var == "url(%{tmpdir})" then
+ value = "%{url(%{tmpdir})}"
+ elseif fn == "url" then
+ value = expand_vars(fnarg)
+ value = value:gsub("\\", "/")
+ elseif fn == "path" then
+ value = expand_vars(fnarg)
+ value = value:gsub("[/\\]", dir_sep)
+ elseif fn == "version" then
+ value = versions[fnarg:lower()] or ""
+ elseif fn == "version_" then
+ value = (versions[fnarg:lower()] or ""):gsub("[%.%-]", "_")
+ else
+ value = test_env.testing_paths[var]
+ or test_env.env_variables[var]
+ or test_env[var]
+ or ""
+ end
+
+ return value
+ end))
+ end
+
+ if input:sub(#input, #input) ~= "\n" then
+ input = input .. "\n"
+ end
+
+ for line in input:gmatch("([^\r\n]*)\r?\n?") do
+ cur_line = cur_line + 1
+
+ local state = stack[#stack]
+ if state == "start" then
+ local cmd, arg = parse_cmd(line)
+ if cmd == "TEST" then
+ start_test(arg)
+ elseif cmd == "SUITE" then
+ cur_suite = arg .. ": "
+ elseif cmd then
+ fail("expected TEST, got " .. cmd)
+ elseif is_blank(line) then
+ -- skip blank lines and arbitrary text,
+ -- which is interpreted as a comment
+ end
+ elseif state == "test" then
+ local cmd, arg = parse_cmd(line)
+ arg = expand_vars(arg)
+ if cmd == "FILE" then
+ cur_op = {
+ op = "FILE",
+ name = arg,
+ data = {},
+ }
+ table.insert(cur_test.ops, cur_op)
+ cur_block = cur_op
+ cur_block_name = "FILE"
+ table.insert(stack, "block start")
+ elseif cmd == "FILE_CONTENTS" then
+ cur_op = {
+ op = "FILE_CONTENTS",
+ name = arg,
+ data = {},
+ }
+ table.insert(cur_test.ops, cur_op)
+ cur_block = cur_op
+ cur_block_name = "FILE_CONTENTS"
+ table.insert(stack, "block start")
+ elseif cmd == "RUN" then
+ local program, args = arg:match("([^ ]+)%s*(.*)$")
+ if not program then
+ fail("expected a program argument in RUN")
+ end
+
+ cur_op = {
+ op = "RUN",
+ exit = 0,
+ exit_line = cur_line,
+ line = cur_line,
+ program = program,
+ args = args,
+ }
+ table.insert(cur_test.ops, cur_op)
+ elseif cmd == "EXISTS" then
+ cur_op = {
+ op = "EXISTS",
+ name = dir.normalize(arg),
+ line = cur_line,
+ }
+ table.insert(cur_test.ops, cur_op)
+ elseif cmd == "NOT_EXISTS" then
+ cur_op = {
+ op = "NOT_EXISTS",
+ name = dir.normalize(arg),
+ line = cur_line,
+ }
+ table.insert(cur_test.ops, cur_op)
+ elseif cmd == "MKDIR" then
+ cur_op = {
+ op = "MKDIR",
+ name = dir.normalize(arg),
+ line = cur_line,
+ }
+ table.insert(cur_test.ops, cur_op)
+ elseif cmd == "RMDIR" then
+ cur_op = {
+ op = "RMDIR",
+ name = dir.normalize(arg),
+ line = cur_line,
+ }
+ table.insert(cur_test.ops, cur_op)
+ elseif cmd == "RM" then
+ cur_op = {
+ op = "RM",
+ name = dir.normalize(arg),
+ line = cur_line,
+ }
+ table.insert(cur_test.ops, cur_op)
+ elseif cmd == "EXIT" then
+ if not cur_op or cur_op.op ~= "RUN" then
+ fail("EXIT must be given in the context of a RUN")
+ end
+
+ local code = tonumber(arg)
+ if not code and not (code >= 0 and code <= 128) then
+ fail("EXIT code must be a number in the range 0-128, got " .. arg)
+ end
+
+ cur_op.exit = code
+ cur_op.exit_line = cur_line
+ elseif cmd == "VERBOSE" then
+ if not cur_op or cur_op.op ~= "RUN" then
+ fail("VERBOSE must be given in the context of a RUN")
+ end
+
+ bool_arg("VERBOSE", cur_op, "verbose", arg)
+ elseif cmd == "STDERR" then
+ block_start_arg("STDERR", cur_op, "stderr")
+ elseif cmd == "NOT_STDERR" then
+ block_start_arg("NOT_STDERR", cur_op, "not_stderr")
+ elseif cmd == "STDOUT" then
+ block_start_arg("STDOUT", cur_op, "stdout")
+ elseif cmd == "NOT_STDOUT" then
+ block_start_arg("NOT_STDOUT", cur_op, "not_stdout")
+ elseif cmd == "PENDING" then
+ bool_arg("PENDING", cur_test, "pending", arg)
+ elseif cmd == "TEST" then
+ table.remove(stack)
+ start_test(arg)
+ elseif cmd then
+ fail("expected a command, got " .. cmd)
+ else
+ -- skip blank lines and arbitrary text,
+ -- which is interpreted as a comment
+ end
+ elseif state == "block start" then
+ local cmd, arg = parse_cmd(line)
+ if is_blank(line) then
+ -- skip
+ elseif is_hr(line) then
+ stack[#stack] = "block data"
+ cur_block.start = cur_line
+ elseif cmd == "PLAIN" then
+ bool_arg("PLAIN", cur_block, "plain", arg)
+ else
+ fail("expected '-----' to start " .. cur_block_name .. " block")
+ end
+ elseif state == "block data" then
+ if is_hr(line) then
+ cur_block = nil
+ table.remove(stack)
+ else
+ if not cur_block.plain then
+ line = expand_vars(line)
+ end
+ table.insert(cur_block.data, line)
+ end
+ end
+ end
+
+ return tests
+end
+
+local function check_output(write, block, block_name, data_var)
+ if block then
+ local is_positive = not block_name:match("NOT")
+ local err_msg = is_positive and "did not match" or "did match unwanted output"
+
+ write([=[ do ]=])
+ write([=[ local block_at = 1 ]=])
+ write([=[ local s, e, line, ok ]=])
+ for i, line in ipairs(block.data) do
+ write(([=[ line = %q ]=]):format(line))
+ write(([=[ s, e = string.find(%s, line, block_at, true) ]=]):format(data_var))
+ write(is_positive and ([=[ ok = s; if e then block_at = e + 1 end ]=]):format(i)
+ or ([=[ ok = not s ]=]))
+ write(([=[ assert(ok, error_message(%d, "%s %s: " .. line, %s)) ]=]):format(block.start + i, block_name, err_msg, data_var))
+ end
+ write([=[ end ]=])
+ end
+end
+
+function quick.compile(filename, env)
+ local tests = parse(filename)
+
+-- local dev_null = (package.config:sub(1, 1) == "/")
+-- and "/dev/null"
+-- or "NUL"
+
+ local cmd_helpers = {
+ ["luarocks"] = "luarocks_cmd",
+ ["luarocks-admin"] = "luarocks_admin_cmd",
+ }
+
+ for tn, t in ipairs(tests) do
+ local code = {}
+ local function write(...)
+ table.insert(code, table.concat({...}))
+ end
+
+ write(([=[ ]=]))
+ write(([=[ -- **************************************** ]=]))
+ write(([=[ -- %s ]=]):format(t.name))
+ write(([=[ -- **************************************** ]=]))
+ write(([=[ ]=]))
+
+ write([=[ local test_env = require("spec.util.test_env") ]=])
+ write([=[ local lfs = require("lfs") ]=])
+ write([=[ local fs = require("lfs") ]=])
+ write([=[ local dir_sep = package.config:sub(1, 1) ]=])
+ write([=[ local coverage = " -e \"require('luacov.runner')([[" .. test_env.testing_paths.testrun_dir .. dir_sep .. "luacov.config]])\" " ]=])
+ write([=[ local luarocks_cmd = test_env.execute_helper(test_env.Q(test_env.testing_paths.lua) .. coverage .. " " .. test_env.testing_paths.src_dir .. "/bin/luarocks", false, test_env.env_variables):sub(1, -5) ]=])
+ write([=[ local luarocks_admin_cmd = test_env.execute_helper(test_env.Q(test_env.testing_paths.lua) .. coverage .. " " .. test_env.testing_paths.src_dir .. "/bin/luarocks-admin", false, test_env.env_variables):sub(1, -5) ]=])
+
+ write([=[ local function make_dir(dirname) ]=])
+ write([=[ local bits = {} ]=])
+ write([=[ if dirname:sub(1, 1) == dir_sep then bits[1] = "" end ]=])
+ write([=[ local ok, err ]=])
+ write([=[ for p in dirname:gmatch("[^" .. dir_sep .. "]+") do ]=])
+ write([=[ table.insert(bits, p) ]=])
+ write([=[ ok, err = lfs.mkdir(table.concat(bits, dir_sep)) ]=])
+ write([=[ end ]=])
+ write([=[ local exists = (lfs.attributes(dirname) or {}).mode == "directory" ]=])
+ write([=[ return exists, (not exists) and err ]=])
+ write([=[ end ]=])
+
+ write(([=[ local function error_message(line, msg, input) ]=]))
+ write(([=[ local out = {"\n\n", %q, ":", line, ": ", msg} ]=]):format(filename))
+ write(([=[ if input then ]=]))
+ write(([=[ if input:match("\n") then ]=]))
+ write(([=[ table.insert(out, "\n") ]=]))
+ write(([=[ table.insert(out, ("-"):rep(40)) ]=]))
+ write(([=[ table.insert(out, "\n") ]=]))
+ write(([=[ table.insert(out, input) ]=]))
+ write(([=[ table.insert(out, ("-"):rep(40)) ]=]))
+ write(([=[ table.insert(out, "\n") ]=]))
+ write(([=[ else ]=]))
+ write(([=[ table.insert(out, ": ") ]=]))
+ write(([=[ table.insert(out, input) ]=]))
+ write(([=[ end ]=]))
+ write(([=[ end ]=]))
+ write(([=[ return table.concat(out) ]=]))
+ write(([=[ end ]=]))
+
+ write([=[ return function() ]=])
+ write([=[ test_env.run_in_tmp(function(tmpdir) ]=])
+ write([=[ local function handle_tmpdir(s) ]=])
+ write([=[ return (s:gsub("%%{url%(%%{tmpdir}%)}", (tmpdir:gsub("\\", "/"))) ]=])
+ write([=[ :gsub("%%{tmpdir}", (tmpdir:gsub("[\\/]", dir_sep)))) ]=])
+ write([=[ end ]=])
+ write([=[ local ok, err ]=])
+ for _, op in ipairs(t.ops) do
+ if op.name then
+ op.name = native_slash(op.name)
+ write(([=[ local name = handle_tmpdir(%q) ]=]):format(op.name))
+ end
+ if op.op == "FILE" then
+ if op.name:match("[\\/]") then
+ write(([=[ make_dir(handle_tmpdir(%q)) ]=]):format(dir.dir_name(op.name)))
+ end
+ write([=[ test_env.write_file(name, handle_tmpdir([=====[ ]=])
+ for _, line in ipairs(op.data) do
+ write(line)
+ end
+ write([=[ ]=====]), finally) ]=])
+ elseif op.op == "EXISTS" then
+ write(([=[ ok, err = lfs.attributes(name) ]=]))
+ write(([=[ assert.truthy(ok, error_message(%d, "EXISTS failed: " .. name .. " - " .. (err or "") )) ]=]):format(op.line))
+ elseif op.op == "NOT_EXISTS" then
+ write(([=[ assert.falsy(lfs.attributes(name), error_message(%d, "NOT_EXISTS failed: " .. name .. " exists" )) ]=]):format(op.line))
+ elseif op.op == "MKDIR" then
+ write(([=[ ok, err = make_dir(name) ]=]))
+ write(([=[ assert.truthy((lfs.attributes(name) or {}).mode == "directory", error_message(%d, "MKDIR failed: " .. name .. " - " .. (err or "") )) ]=]):format(op.line))
+ elseif op.op == "RMDIR" then
+ write(([=[ ok, err = test_env.remove_dir(name) ]=]))
+ write(([=[ assert.falsy((lfs.attributes(name) or {}).mode == "directory", error_message(%d, "MKDIR failed: " .. name .. " - " .. (err or "") )) ]=]):format(op.line))
+ elseif op.op == "RM" then
+ write(([=[ ok, err = os.remove(name) ]=]))
+ write(([=[ assert.falsy((lfs.attributes(name) or {}).mode == "file", error_message(%d, "RM failed: " .. name .. " - " .. (err or "") )) ]=]):format(op.line))
+ elseif op.op == "FILE_CONTENTS" then
+ write(([=[ do ]=]))
+ write(([=[ local fd_file = assert(io.open(name, "rb")) ]=]))
+ write(([=[ local file_data = fd_file:read("*a") ]=]))
+ write(([=[ fd_file:close() ]=]))
+ write([=[ local block_at = 1 ]=])
+ write([=[ local s, e, line ]=])
+ for i, line in ipairs(op.data) do
+ write(([=[ line = %q ]=]):format(line))
+ write(([=[ s, e = string.find(file_data, line, 1, true) ]=]))
+ write(([=[ assert(s, error_message(%d, "FILE_CONTENTS " .. name .. " did not match: " .. line, file_data)) ]=]):format(op.start + i))
+ write(([=[ block_at = e + 1 ]=]):format(i))
+ end
+ write([=[ end ]=])
+ elseif op.op == "RUN" then
+ local cmd_helper = cmd_helpers[op.program] or ("%q"):format(op.program)
+ local redirs = " 1>stdout.txt 2>stderr.txt "
+ write(([=[ local ok, _, code = os.execute(%s .. " " .. %q .. %q) ]=]):format(cmd_helper, op.args, redirs))
+ write([=[ if type(ok) == "number" then code = (ok >= 256 and ok / 256 or ok) end ]=])
+
+ write([=[ local fd_stderr = assert(io.open("stderr.txt", "rb")) ]=])
+ write([=[ local stderr_data = fd_stderr:read("*a") ]=])
+ write([=[ fd_stderr:close() ]=])
+
+ write([=[ if stderr_data:match("please report") then ]=])
+ write(([=[ assert(false, error_message(%d, "RUN crashed: ", stderr_data)) ]=]):format(op.line))
+ write([=[ end ]=])
+
+ if op.stdout or op.not_stdout or op.verbose then
+ write([=[ local fd_stdout = assert(io.open("stdout.txt", "rb")) ]=])
+ write([=[ local stdout_data = fd_stdout:read("*a") ]=])
+ write([=[ fd_stdout:close() ]=])
+ end
+
+ if op.verbose then
+ write([=[ print() ]=])
+ write([=[ print("STDOUT: --" .. ("-"):rep(70)) ]=])
+ write([=[ print(stdout_data) ]=])
+ write([=[ print("STDERR: --" .. ("-"):rep(70)) ]=])
+ write([=[ print(stderr_data) ]=])
+ write([=[ print(("-"):rep(80)) ]=])
+ write([=[ print() ]=])
+ end
+
+ check_output(write, op.stdout, "STDOUT", "stdout_data")
+ check_output(write, op.stderr, "STDERR", "stderr_data")
+
+ check_output(write, op.not_stdout, "NOT_STDOUT", "stdout_data")
+ check_output(write, op.not_stderr, "NOT_STDERR", "stderr_data")
+
+ if op.exit then
+ write(([=[ assert.same(%d, code, error_message(%d, "EXIT did not match: " .. %d, stderr_data)) ]=]):format(op.exit, op.exit_line, op.exit))
+ end
+ end
+ end
+ write([=[ end, finally) ]=])
+ write([=[ end ]=])
+
+ local program = table.concat(code, "\n")
+ local chunk = assert(load(program, "@" .. filename .. ":[TEST " .. tn .. "]", "t", env or _ENV))
+ if env and setfenv then
+ setfenv(chunk, env)
+ end
+ t.fn = chunk()
+ end
+
+ return tests
+end
+
+return quick
diff --git a/spec/util/test_env.lua b/spec/util/test_env.lua
new file mode 100644
index 0000000..92fb193
--- /dev/null
+++ b/spec/util/test_env.lua
@@ -0,0 +1,1187 @@
+local test_env = {}
+
+local lfs = require("lfs")
+local versions = require("spec.util.versions")
+
+local help_message = [[
+LuaRocks test-suite
+
+INFORMATION
+ New test-suite for LuaRocks project, using unit testing framework Busted.
+REQUIREMENTS
+ Be sure sshd is running on your system, or use '--exclude-tags=ssh',
+ to not execute tests which require sshd.
+USAGE
+ busted [-Xhelper <arguments>]
+ARGUMENTS
+ env=<type> Set type of environment to use ("minimal" or "full",
+ default: "minimal").
+ noreset Don't reset environment after each test
+ clean Remove existing testing environment.
+ ci Add if running on Unix CI.
+ appveyor Add if running on Appveyor.
+ os=<type> Set OS ("linux", "osx", or "windows").
+ lua_dir=<path> Path of Lua installation (default "/usr/local")
+ lua=<lua> Name of the interpreter, may be full path (default "lua")
+]]
+
+local function help()
+ print(help_message)
+ os.exit(1)
+end
+
+local function title(str)
+ print()
+ print(("-"):rep(#str))
+ print(str)
+ print(("-"):rep(#str))
+end
+
+local dir_sep = package.config:sub(1, 1)
+local function P(p)
+ return (p:gsub("/", dir_sep))
+end
+
+local function dir_path(...)
+ return P((table.concat({ ... }, "/"):gsub("\\", "/"):gsub("/+", "/")))
+end
+
+local function C(...)
+ return table.concat({...}, " ")
+end
+
+--- Quote argument for shell processing. Fixes paths on Windows.
+-- Adds double quotes and escapes. Based on function in fs/win32.lua.
+-- @param arg string: Unquoted argument.
+-- @return string: Quoted argument.
+local function Q(arg)
+ if test_env.TEST_TARGET_OS == "windows" then
+ local drive_letter = "[%.a-zA-Z]?:?[\\/]"
+ -- Quote DIR for Windows
+ if arg:match("^"..drive_letter) then
+ arg = P(arg)
+ end
+
+ if arg == "\\" then
+ return '\\' -- CHDIR needs special handling for root dir
+ end
+
+ return '"' .. arg .. '"'
+ else
+ return "'" .. arg:gsub("'", "'\\''") .. "'"
+ end
+end
+
+local function V(str)
+ return (str:gsub("${([^}]-)}", function(name)
+ name = name:lower()
+ local prefix, suffix = name:match("^(.*)_(.)$")
+ if suffix then
+ name = prefix
+ local d = assert(versions[name])
+ local v, r = d:match("^([^-]*)%-(%d*)$")
+ if suffix == "d" then
+ return d
+ elseif suffix == "v" then
+ return v
+ elseif suffix == "r" then
+ return r
+ else
+ print("Test error: invalid suffix " .. suffix .. " in variable " .. name)
+ os.exit(1)
+ end
+ else
+ if not versions[name] then
+ print("Test error: no version definition for " .. name)
+ os.exit(1)
+ end
+ return versions[name]
+ end
+ end))
+end
+
+local function tool(name)
+ if test_env.TEST_TARGET_OS == "windows" then
+ return Q(dir_path(test_env.testing_paths.win_tools, name .. ".exe"))
+ else
+ return name
+ end
+end
+
+local os_remove = os.remove
+os.remove = function(f) -- luacheck: ignore
+ return os_remove(V(f))
+end
+
+local os_rename = os.rename
+os.rename = function(a, b) -- luacheck: ignore
+ return os_rename(V(a), V(b))
+end
+
+-- Monkeypatch incorrect tmpname's on some Lua distributions for Windows
+local os_tmpname = os.tmpname
+os.tmpname = function() -- luacheck:ignore
+ local name = os_tmpname()
+ if name:sub(1, 1) == '\\' then
+ name = os.getenv "TEMP"..name
+ end
+ return name
+end
+
+local lfs_chdir = lfs.chdir
+lfs.chdir = function(d) -- luacheck: ignore
+ return lfs_chdir(V(d))
+end
+
+local lfs_attributes = lfs.attributes
+lfs.attributes = function(f, ...) -- luacheck: ignore
+ return lfs_attributes(V(f), ...)
+end
+
+local function exists(path)
+ return lfs.attributes(path, "mode") ~= nil
+end
+
+function test_env.file_if_exists(path)
+ return lfs.attributes(path, "mode") and path
+end
+
+function test_env.quiet(command)
+ if not test_env.VERBOSE then
+ if test_env.TEST_TARGET_OS == "windows" then
+ return command .. " 1> NUL 2> NUL"
+ else
+ return command .. " 1> /dev/null 2> /dev/null"
+ end
+ else
+ return command
+ end
+end
+
+function test_env.copy(source, destination)
+ source = V(source)
+ destination = V(destination)
+
+ local r_source, r_destination, err
+ r_source, err = io.open(source, "r")
+ if err then
+ print(debug.traceback())
+ os.exit(1)
+ end
+
+ r_destination, err = io.open(destination, "w")
+ if err then
+ print(debug.traceback())
+ os.exit(1)
+ end
+
+ while true do
+ local block = r_source:read(8192)
+ if not block then break end
+ r_destination:write(block)
+ end
+
+ r_source:close()
+ r_destination:close()
+end
+
+function test_env.get_tmp_path()
+ local path = os.tmpname()
+ if test_env.TEST_TARGET_OS == "windows" and not path:find(":") then
+ path = dir_path(os.getenv("TEMP"), path)
+ end
+ os.remove(path)
+ return path
+end
+
+--- Helper function that runs the given function inside
+-- a temporary directory, isolating it
+-- @param f function: the function to be run
+function test_env.run_in_tmp(f, finally)
+ local olddir = lfs.currentdir()
+ local tmpdir = test_env.get_tmp_path()
+ lfs.mkdir(tmpdir)
+ lfs.chdir(tmpdir)
+
+ if not finally then
+ error("run_in_tmp needs a finally argument")
+ end
+
+ -- for unit tests, so that current dir known by luarocks.fs (when running with non-lfs)
+ -- is synchronized with actual lfs (system) current dir
+ local fs = require("luarocks.fs")
+ if not fs.change_dir then
+ local cfg = require("luarocks.core.cfg")
+ cfg.init()
+ fs.init()
+ end
+ fs.change_dir(tmpdir)
+
+ local lr_config = test_env.env_variables.LUAROCKS_CONFIG
+
+ test_env.copy(lr_config, lr_config .. ".bak")
+
+ finally(function()
+ test_env.copy(lr_config .. ".bak", lr_config)
+ lfs.chdir(olddir)
+ lfs.rmdir(tmpdir)
+ fs.change_dir(olddir)
+ end)
+
+ f(tmpdir)
+end
+
+--- Helper function for execute_bool and execute_output
+-- @param command string: command to execute
+-- @param print_command boolean: print command if 'true'
+-- @param env_variables table: table of environment variables to export {FOO="bar", BAR="foo"}
+-- @return final_command string: concatenated command to execution
+function test_env.execute_helper(command, print_command, env_variables)
+ local final_command = ""
+
+ if print_command then
+ print("[EXECUTING]: " .. command)
+ end
+
+ local unset_variables = {
+ "LUA_PATH",
+ "LUA_CPATH",
+ "LUA_PATH_5_2",
+ "LUA_CPATH_5_2",
+ "LUA_PATH_5_3",
+ "LUA_CPATH_5_3",
+ "LUAROCKS_SYSCONFDIR",
+ }
+
+ if env_variables then
+ if test_env.TEST_TARGET_OS == "windows" then
+ for _, k in ipairs(unset_variables) do
+ final_command = final_command .. "set " .. k .. "=&"
+ end
+ for k,v in pairs(env_variables) do
+ final_command = final_command .. "set " .. k .. "=" .. v .. "&"
+ end
+ final_command = final_command:sub(1, -2) .. "&"
+ else
+ for _, k in ipairs(unset_variables) do
+ final_command = final_command .. "unset " .. k .. "; "
+ end
+ final_command = final_command .. "export "
+ for k,v in pairs(env_variables) do
+ final_command = final_command .. k .. "='" .. v .. "' "
+ end
+ -- remove last space and add ';' to separate exporting variables from command
+ final_command = final_command:sub(1, -2) .. "; "
+ end
+ end
+
+ final_command = final_command .. command .. " 2>&1"
+
+ return final_command
+end
+
+function test_env.execute(cmd)
+ local ok = os.execute(cmd)
+ return (ok == true or ok == 0) -- normalize Lua 5.1 output to boolean
+end
+
+--- Execute command and returns true/false
+-- @return true/false boolean: status of the command execution
+local function execute_bool(command, print_command, env_variables)
+ command = test_env.execute_helper(command, print_command, env_variables)
+
+ local redirect_filename
+ local redirect = ""
+ if print_command ~= nil then
+ redirect_filename = dir_path(test_env.testing_paths.luarocks_tmp, "output.txt")
+ redirect = " > " .. redirect_filename
+ os.remove(redirect_filename)
+ end
+ local ok = test_env.execute(command .. redirect)
+ if redirect ~= "" then
+ if not ok or test_env.VERBOSE then
+ local fd = io.open(redirect_filename, "r")
+ if fd then
+ print(fd:read("*a"))
+ fd:close()
+ end
+ end
+ os.remove(redirect_filename)
+ end
+ return ok
+end
+
+--- Execute command and returns output of command
+-- @return output string: output the command execution
+local function execute_output(command, print_command, env_variables)
+ command = test_env.execute_helper(command, print_command, env_variables)
+
+ local file = assert(io.popen(command))
+ local output = file:read('*all')
+ file:close()
+ return (output:gsub("\r\n", "\n"):gsub("\n$", "")) -- remove final newline
+end
+
+--- Set test_env.LUA_V or test_env.LUAJIT_V based
+-- on version of Lua used to run this script.
+function test_env.set_lua_version()
+ if _G.jit then
+ test_env.LUAJIT_V = _G.jit.version:match("(2%.%d)%.%d")
+ test_env.lua_version = "5.1"
+ else
+ test_env.LUA_V = _VERSION:match("5%.%d")
+ test_env.lua_version = test_env.LUA_V
+ end
+end
+
+--- Set all arguments from input into global variables
+function test_env.set_args()
+ -- if at least Lua/LuaJIT version argument was found on input start to parse other arguments to env. variables
+ test_env.TYPE_TEST_ENV = "minimal"
+ test_env.RESET_ENV = true
+
+ for _, argument in ipairs(arg) do
+ if argument:find("^env=") then
+ test_env.TYPE_TEST_ENV = argument:match("^env=(.*)$")
+ elseif argument == "noreset" then
+ test_env.RESET_ENV = false
+ elseif argument == "clean" then
+ test_env.TEST_ENV_CLEAN = true
+ elseif argument == "verbose" then
+ test_env.VERBOSE = true
+ elseif argument == "ci" then
+ test_env.CI = true
+ elseif argument == "appveyor" then
+ test_env.APPVEYOR = true
+ elseif argument:find("^os=") then
+ test_env.TEST_TARGET_OS = argument:match("^os=(.*)$")
+ elseif argument == "mingw" then
+ test_env.MINGW = true
+ elseif argument == "vs" then
+ test_env.MINGW = false
+ elseif argument:find("^lua_dir=") then
+ test_env.LUA_DIR = argument:match("^lua_dir=(.*)$")
+ elseif argument:find("^lua=") then
+ test_env.LUA = argument:match("^lua=(.*)$")
+ else
+ help()
+ end
+ end
+
+ if not test_env.TEST_TARGET_OS then
+ title("OS CHECK")
+
+ if dir_sep == "\\" then
+ test_env.TEST_TARGET_OS = "windows"
+ if test_env.APPVEYOR then
+ test_env.OPENSSL_INCDIR = "C:\\OpenSSL-v111-Win32\\include"
+ test_env.OPENSSL_LIBDIR = "C:\\OpenSSL-v111-Win32\\lib"
+ if test_env.MINGW then
+ test_env.OPENSSL_LIBDIR = "C:\\OpenSSL-v111-Win32\\bin"
+ end
+ end
+ else
+ local system = execute_output("uname -s")
+ if system == "Linux" then
+ test_env.TEST_TARGET_OS = "linux"
+ if test_env.CI then
+ test_env.OPENSSL_INCDIR = "/usr/include"
+ test_env.OPENSSL_LIBDIR = "/usr/lib/x86_64-linux-gnu"
+ end
+ elseif system == "Darwin" then
+ test_env.TEST_TARGET_OS = "osx"
+ if test_env.CI then
+ if exists("/opt/homebrew/opt/openssl@3/include") then
+ test_env.OPENSSL_INCDIR = "/opt/homebrew/opt/openssl@3/include"
+ test_env.OPENSSL_LIBDIR = "/opt/homebrew/opt/openssl@3/lib"
+ elseif exists("/opt/homebrew/opt/openssl@1.1/include") then
+ test_env.OPENSSL_INCDIR = "/opt/homebrew/opt/openssl@1.1/include"
+ test_env.OPENSSL_LIBDIR = "/opt/homebrew/opt/openssl@1.1/lib"
+ elseif exists("/opt/homebrew/opt/openssl/include") then
+ test_env.OPENSSL_INCDIR = "/opt/homebrew/opt/openssl/include"
+ test_env.OPENSSL_LIBDIR = "/opt/homebrew/opt/openssl/lib"
+ else
+ test_env.OPENSSL_INCDIR = "/usr/local/opt/openssl/include"
+ test_env.OPENSSL_LIBDIR = "/usr/local/opt/openssl/lib"
+ end
+ end
+ end
+ end
+ print(test_env.TEST_TARGET_OS)
+ end
+
+ if test_env.TEST_TARGET_OS == "windows" then
+ test_env.lib_extension = "dll"
+ else
+ test_env.lib_extension = "so"
+ end
+
+ test_env.openssl_dirs = ""
+ if test_env.OPENSSL_INCDIR then
+ test_env.openssl_dirs = C("OPENSSL_INCDIR=" .. test_env.OPENSSL_INCDIR,
+ "OPENSSL_LIBDIR=" .. test_env.OPENSSL_LIBDIR)
+ end
+
+ return true
+end
+
+function test_env.copy_dir(source_path, target_path)
+ source_path = V(source_path)
+ target_path = V(target_path)
+
+ local flag = test_env.TEST_TARGET_OS == "windows" and "-R" or "-a"
+ os.execute(C(tool("cp"), flag, dir_path(source_path, "."), target_path))
+end
+
+--- Remove directory recursively
+-- @param path string: directory path to delete
+function test_env.remove_dir(path)
+ path = V(path)
+
+ if exists(path) then
+ for file in lfs.dir(path) do
+ if file ~= "." and file ~= ".." then
+ local full_path = dir_path(path, file)
+
+ if lfs.attributes(full_path, "mode") == "directory" then
+ test_env.remove_dir(full_path)
+ else
+ os.remove(full_path)
+ end
+ end
+ end
+ end
+ lfs.rmdir(path)
+end
+
+--- Remove subdirectories of a directory that match a pattern
+-- @param path string: path to directory
+-- @param pattern string: pattern matching basenames of subdirectories to be removed
+function test_env.remove_subdirs(path, pattern)
+ path = V(path)
+
+ if exists(path) then
+ for file in lfs.dir(path) do
+ if file ~= "." and file ~= ".." then
+ local full_path = dir_path(path, file)
+
+ if lfs.attributes(full_path, "mode") == "directory" and file:find(pattern) then
+ test_env.remove_dir(full_path)
+ end
+ end
+ end
+ end
+end
+
+--- Remove files matching a pattern
+-- @param path string: directory where to delete files
+-- @param pattern string: pattern matching basenames of files to be deleted
+-- @return result_check boolean: true if one or more files deleted
+function test_env.remove_files(path, pattern)
+ path = V(path)
+
+ local result_check = false
+ if exists(path) then
+ for file in lfs.dir(path) do
+ if file ~= "." and file ~= ".." then
+ if file:find(pattern) then
+ if os.remove(dir_path(path, file)) then
+ result_check = true
+ end
+ end
+ end
+ end
+ end
+ return result_check
+end
+
+
+--- Function for downloading rocks and rockspecs
+-- @param urls table: array of full names of rocks/rockspecs to download
+-- @param save_path string: path to directory, where to download rocks/rockspecs
+-- @return make_manifest boolean: true if new rocks downloaded
+local function download_rocks(urls, save_path)
+ local luarocks_repo = "https://luarocks.org/"
+
+ local to_download = {}
+ local fixtures = {}
+ for _, url in ipairs(urls) do
+ url = V(url)
+
+ if url:match("^spec/fixtures") then
+ table.insert(fixtures, P(url:gsub("^spec/fixtures", test_env.testing_paths.fixtures_dir)))
+ else
+ -- check if already downloaded
+ if not exists(dir_path(save_path, url)) then
+ table.insert(to_download, ((luarocks_repo .. url):gsub("org//", "org/")))
+ end
+ end
+ end
+
+ if #fixtures > 0 then
+ os.execute(C(tool("cp"), table.concat(fixtures, " "), save_path))
+ end
+
+ if #to_download > 0 then
+ local ok = execute_bool(C(tool("wget"), "--no-check-certificate -cP", save_path, table.concat(to_download, " ")))
+ if not ok then
+ os.exit(1)
+ end
+ end
+
+ return (#fixtures > 0) or (#to_download > 0)
+end
+
+--- Create a file containing a string.
+-- @param pathname string: path to file.
+-- @param str string: content of the file.
+function test_env.write_file(pathname, str, finally)
+ pathname = V(pathname)
+
+ local file = assert(io.open(pathname, "wb"))
+ file:write(str)
+ file:close()
+ if finally then
+ finally(function()
+ os.remove(pathname)
+ end)
+ end
+end
+
+--- Create environment variables needed for tests
+-- @param testing_paths table: table with paths to testing directory
+-- @return env_variables table: table with created environment variables
+local function create_env(testing_paths)
+ local lua_v = _VERSION:gsub("Lua ", "")
+ local testrun_dir = test_env.testing_paths.testrun_dir
+ local lrprefix = testing_paths.testing_lrprefix
+ local tree = testing_paths.testing_tree
+ local sys_tree = testing_paths.testing_sys_tree
+ local deps_tree = testing_paths.testing_deps_tree
+
+ if test_env.LUAJIT_V then
+ lua_v="5.1"
+ end
+
+ local env_variables = {}
+ env_variables.GNUPGHOME = testing_paths.gpg_dir
+ env_variables.LUA_VERSION = lua_v
+ env_variables.LUAROCKS_CONFIG = dir_path(testrun_dir, "testing_config.lua")
+
+ local lua_path = {}
+ if test_env.TEST_TARGET_OS == "windows" then
+ table.insert(lua_path, dir_path(lrprefix, "lua", "?.lua"))
+ else
+ table.insert(lua_path, dir_path(lrprefix, "share", "lua", lua_v, "?.lua"))
+ end
+ table.insert(lua_path, dir_path(tree, "share", "lua", lua_v, "?.lua"))
+ table.insert(lua_path, dir_path(tree, "share", "lua", lua_v, "?", "init.lua"))
+ table.insert(lua_path, dir_path(sys_tree, "share", "lua", lua_v, "?.lua"))
+ table.insert(lua_path, dir_path(sys_tree, "share", "lua", lua_v, "?", "init.lua"))
+ table.insert(lua_path, dir_path(deps_tree, "share", "lua", lua_v, "?.lua"))
+ table.insert(lua_path, dir_path(deps_tree, "share", "lua", lua_v, "?", "init.lua"))
+ table.insert(lua_path, dir_path(testing_paths.src_dir, "?.lua"))
+ env_variables.LUA_PATH = table.concat(lua_path, ";") .. ";"
+
+ local lua_cpath = {}
+ local lib_pattern = "?." .. test_env.lib_extension
+ table.insert(lua_cpath, dir_path(tree, "lib", "lua", lua_v, lib_pattern))
+ table.insert(lua_cpath, dir_path(sys_tree, "lib", "lua", lua_v, lib_pattern))
+ table.insert(lua_cpath, dir_path(deps_tree, "lib", "lua", lua_v, lib_pattern))
+ env_variables.LUA_CPATH = table.concat(lua_cpath, ";") .. ";"
+
+ local path = { os.getenv("PATH") }
+ table.insert(path, dir_path(tree, "bin"))
+ table.insert(path, dir_path(sys_tree, "bin"))
+ table.insert(path, dir_path(deps_tree, "bin"))
+ env_variables.PATH = table.concat(path, test_env.TARGET_OS == "windows" and ";" or ":")
+
+ return env_variables
+end
+
+local function make_run_function(cmd_name, exec_function, with_coverage, do_print)
+ local cmd_prefix = Q(test_env.testing_paths.lua)
+ local testrun_dir = test_env.testing_paths.testrun_dir
+
+ if with_coverage then
+ cmd_prefix = C(cmd_prefix, "-e", "\"require('luacov.runner')([[" .. testrun_dir .. "/luacov.config]])\"")
+ end
+
+ if cmd_name then
+ cmd_prefix = C(cmd_prefix, dir_path(test_env.testing_paths.src_dir, "bin", cmd_name))
+ end
+
+ cmd_prefix = P(cmd_prefix)
+
+ return function(cmd, new_vars)
+ cmd = V(cmd)
+ local temp_vars = {}
+ for k, v in pairs(test_env.env_variables) do
+ temp_vars[k] = v
+ end
+ if new_vars then
+ for k, v in pairs(new_vars) do
+ temp_vars[k] = v
+ end
+ end
+ return exec_function(C(cmd_prefix, cmd), do_print, temp_vars)
+ end
+end
+
+local function make_run_functions()
+ local fns = {}
+
+ local cmds = {
+ ["lua"] = nil,
+ ["luarocks"] = "luarocks",
+ ["luarocks_admin"] = "luarocks-admin",
+ }
+
+ for _, name in ipairs({"lua", "luarocks", "luarocks_admin"}) do
+ fns[name] = make_run_function(cmds[name], execute_output, true, true)
+ fns[name .. "_bool"] = make_run_function(cmds[name], execute_bool, true, true)
+ fns[name .. "_nocov"] = make_run_function(cmds[name], execute_bool, false, true)
+ fns[name .. "_noprint_nocov"] = make_run_function(cmds[name], execute_bool, false, false)
+ end
+
+ return fns
+end
+
+local function move_file(src, dst)
+ local ok = execute_bool(C(tool("mv"), P(src), P(dst)))
+ if not ok then
+ print(debug.traceback())
+ os.exit(1)
+ end
+end
+
+--- Rebuild environment.
+-- Remove old installed rocks and install new ones,
+-- updating manifests and tree copies.
+local function build_environment(rocks, env_variables)
+ title("BUILDING ENVIRONMENT")
+ local testing_paths = test_env.testing_paths
+ test_env.remove_dir(testing_paths.testing_tree)
+ test_env.remove_dir(testing_paths.testing_sys_tree)
+
+ lfs.mkdir(testing_paths.testing_tree)
+ lfs.mkdir(testing_paths.testing_sys_tree)
+ lfs.mkdir(testing_paths.testing_deps_tree)
+
+ test_env.run.luarocks_admin_nocov(C("make_manifest", Q(testing_paths.testing_server)))
+ test_env.run.luarocks_admin_nocov(C("make_manifest", Q(testing_paths.testing_cache)))
+
+ for _, rock in ipairs(rocks) do
+ local only_server = "--only-server=" .. testing_paths.testing_cache
+ local tree = "--tree=" .. testing_paths.testing_deps_tree
+ if not test_env.run.luarocks_nocov(test_env.quiet(C("install", only_server, tree, Q(rock)), env_variables)) then
+ assert(test_env.run.luarocks_nocov(C("build", tree, Q(rock)), env_variables))
+ assert(test_env.run.luarocks_nocov(C("pack", tree, Q(rock)), env_variables))
+ move_file(rock .. "-*.rock", testing_paths.testing_cache)
+ end
+ end
+end
+
+local function find_lua()
+ -- (1) LUA is a full path
+ if test_env.LUA and test_env.LUA:match("[/\\]") then
+
+ local lua_bindir = test_env.LUA:match("^(.-)[/\\][^/\\]*$")
+ local luadir = test_env.LUA_DIR or lua_bindir:gsub("[/\\]bin$")
+ local lua = test_env.LUA
+
+ return lua_bindir, luadir, lua
+ end
+
+ -- (2) LUA is just the interpreter name
+ local lua_exe = test_env.LUA
+ or ((test_env.TEST_TARGET_OS == "windows") and "lua.exe")
+ or "lua"
+
+ -- (2.1) LUA_DIR was given
+ if test_env.LUA_DIR then
+
+ local luadir = test_env.LUA_DIR
+ local lua_bindir = exists(dir_path(luadir, "bin"))
+ and dir_path(luadir, "bin")
+ or luadir
+ local lua = dir_path(lua_bindir, lua_exe)
+
+ return lua_bindir, luadir, lua
+ end
+
+ -- (2.2) LUA_DIR was not given, try some default paths
+ local try_dirs = (test_env.TEST_TARGET_OS == "windows")
+ and { os.getenv("ProgramFiles(x86)").."\\LuaRocks" }
+ or { "/usr/local", "/usr" }
+
+ for _, luadir in ipairs(try_dirs) do
+ for _, lua_bindir in ipairs({ luadir, dir_path(luadir, "bin") }) do
+ local lua = dir_path(lua_bindir, lua_exe)
+ if exists(lua) then
+ return lua_bindir, luadir, lua
+ end
+ end
+ end
+end
+
+local function create_testing_paths(suffix)
+ local paths = {}
+
+ paths.lua_bindir, paths.luadir, paths.lua = find_lua()
+ if (not paths.lua) or (not exists(paths.lua)) then
+ error("Lua interpreter not found! Run `busted -Xhelper help` for options")
+ end
+
+ local base_dir = lfs.currentdir()
+ paths.src_dir = dir_path(base_dir, "src")
+ paths.spec_dir = dir_path(base_dir, "spec")
+ paths.util_dir = dir_path(base_dir, "spec", "util")
+ paths.fixtures_dir = dir_path(base_dir, "spec", "fixtures")
+ paths.fixtures_repo_dir = dir_path(base_dir, "spec", "fixtures", "a_repo")
+ paths.gpg_dir = dir_path(base_dir, "spec", "fixtures", "gpg")
+
+ local testrun_dir = dir_path(base_dir, "testrun")
+ paths.testrun_dir = testrun_dir
+ paths.testing_lrprefix = dir_path(testrun_dir, "testing_lrprefix-" .. suffix)
+ paths.testing_tree = dir_path(testrun_dir, "testing-" .. suffix)
+ paths.testing_sys_tree = dir_path(testrun_dir, "testing_sys-" .. suffix)
+ paths.testing_deps_tree = dir_path(testrun_dir, "testing_deps-" .. suffix)
+ paths.testing_cache = dir_path(testrun_dir, "testing_cache-" .. suffix)
+ paths.testing_server = dir_path(testrun_dir, "testing_server-" .. suffix)
+
+ local rocks_v = "rocks-" .. test_env.lua_version
+ paths.testing_rocks = dir_path(paths.testing_tree, "lib", "luarocks", rocks_v)
+ paths.testing_sys_rocks = dir_path(paths.testing_sys_tree, "lib", "luarocks", rocks_v)
+ paths.testing_deps_rocks = dir_path(paths.testing_deps_tree, "lib", "luarocks", rocks_v)
+
+ if test_env.TEST_TARGET_OS == "windows" then
+ paths.luarocks_tmp = os.getenv("TEMP")
+ else
+ paths.luarocks_tmp = "/tmp/luarocks_testing"
+ end
+
+ if test_env.TEST_TARGET_OS == "windows" then
+ paths.win_tools = dir_path(base_dir, "win32", "tools")
+ end
+
+ return paths
+end
+
+--- Helper function to unload luarocks modules from global table package.loaded
+-- Needed to load our local (testing) version of LuaRocks
+function test_env.unload_luarocks()
+ for modname, _ in pairs(package.loaded) do
+ if modname:match("^luarocks%.") then
+ package.loaded[modname] = nil
+ end
+ end
+ local src_pattern = dir_path(test_env.testing_paths.src_dir, "?.lua")
+ if not package.path:find(src_pattern, 1, true) then
+ package.path = src_pattern .. ";" .. package.path
+ end
+end
+
+local function get_luarocks_platform(variables)
+ local print_arch_script = "\"" ..
+ "cfg = require('luarocks.core.cfg');" ..
+ "cfg.init();" ..
+ "print(cfg.arch)" ..
+ "\""
+ local cmd = C(test_env.testing_paths.lua, "-e", print_arch_script)
+ return execute_output(cmd, false, variables)
+end
+
+--- Test if required rock is installed and if not, install it.
+-- Return `true` if the rock is already installed or has been installed successfully,
+-- `false` if installation failed.
+function test_env.need_rock(rock)
+ rock = V(rock)
+
+ print("Check if " .. rock .. " is installed")
+ if test_env.run.luarocks_noprint_nocov(test_env.quiet("show " .. rock)) then
+ return true
+ else
+ local ok = test_env.run.luarocks_noprint_nocov(test_env.quiet("install " .. rock))
+ if not ok then
+ print("WARNING: failed installing " .. rock)
+ end
+ return ok
+ end
+end
+
+--- For each key-value pair in replacements table
+-- replace %{key} in given string with value.
+local function substitute(str, replacements)
+ return (str:gsub("%%%b{}", function(marker)
+ local r = replacements[marker:sub(3, -2)]
+ if r then
+ r = r:gsub("\\", "\\\\")
+ end
+ return r
+ end))
+end
+
+
+--- Create configs for luacov and several versions of Luarocks
+-- configs needed for some tests.
+local function create_configs()
+ local testrun_dir = test_env.testing_paths.testrun_dir
+
+ -- testing_config.lua
+ -- testing_config_show_downloads.lua
+ -- testing_config_no_downloader.lua
+ local config_content = substitute([[
+ rocks_trees = {
+ { name = "user", root = "%{testing_tree}" },
+ { name = "deps", root = "%{testing_deps_tree}" },
+ { name = "system", root = "%{testing_sys_tree}" },
+ }
+ rocks_servers = {
+ "%{testing_server}"
+ }
+ local_cache = "%{testing_cache}"
+ upload_server = "testing"
+ upload_user = "%{user}"
+ upload_servers = {
+ testing = {
+ rsync = "localhost/tmp/luarocks_testing",
+ },
+ }
+ ]], {
+ user = "testuser",
+ testing_sys_tree = test_env.testing_paths.testing_sys_tree,
+ testing_deps_tree = test_env.testing_paths.testing_deps_tree,
+ testing_tree = test_env.testing_paths.testing_tree,
+ testing_server = test_env.testing_paths.testing_server,
+ testing_cache = test_env.testing_paths.testing_cache
+ })
+
+ test_env.write_file(dir_path(testrun_dir, "testing_config.lua"), config_content .. " \nweb_browser = \"true\"")
+ test_env.write_file(dir_path(testrun_dir, "testing_config_show_downloads.lua"), config_content
+ .. "show_downloads = true \n rocks_servers={\"http://luarocks.org/repositories/rocks\"}")
+ test_env.write_file(dir_path(testrun_dir, "testing_config_no_downloader.lua"), config_content
+ .. "variables = { WGET = 'invalid', CURL = 'invalid' }")
+
+ -- testing_config_sftp.lua
+ config_content = substitute([[
+ rocks_trees = {
+ "%{testing_tree}",
+ "%{testing_deps_tree}",
+ "%{testing_sys_tree}",
+ }
+ local_cache = "%{testing_cache}"
+ upload_server = "testing"
+ upload_user = "%{user}"
+ upload_servers = {
+ testing = {
+ sftp = "localhost/tmp/luarocks_testing",
+ },
+ }
+ ]], {
+ user = "testuser",
+ testing_sys_tree = test_env.testing_paths.testing_sys_tree,
+ testing_deps_tree = test_env.testing_paths.testing_deps_tree,
+ testing_tree = test_env.testing_paths.testing_tree,
+ testing_cache = test_env.testing_paths.testing_cache
+ })
+
+ test_env.write_file(dir_path(testrun_dir, "testing_config_sftp.lua"), config_content)
+
+ -- luacov.config
+ config_content = substitute([[
+ return {
+ statsfile = "%{statsfile}",
+ reportfile = "%{reportfile}",
+ exclude = {
+ "src%/luarocks%/vendor.+$",
+ },
+ modules = {
+ ["luarocks"] = "%{luarocks_path}",
+ ["luarocks-admin"] = "%{luarocks_admin_path}",
+ ["luarocks.*"] = "src",
+ ["luarocks.*.*"] = "src",
+ ["luarocks.*.*.*"] = "src"
+ }
+ }
+ ]], {
+ statsfile = dir_path(testrun_dir, "luacov.stats.out"),
+ reportfile = dir_path(testrun_dir, "luacov.report.out"),
+ luarocks_path = dir_path("src", "bin", "luarocks"),
+ luarocks_admin_path = dir_path("src", "bin", "luarocks-admin"),
+ })
+
+ test_env.write_file(dir_path(testrun_dir, "luacov.config"), config_content)
+
+ config_content = [[
+ -- Config file of mock LuaRocks.org site for tests
+ upload = {
+ server = "http://localhost:8080",
+ tool_version = "1.0.0",
+ api_version = "1",
+ }
+ ]]
+ test_env.write_file(dir_path(testrun_dir, "luarocks_site.lua"), config_content)
+end
+
+--- Remove testing directories.
+local function clean()
+ local testrun_dir = test_env.testing_paths.testrun_dir
+
+ print("Cleaning testing directory...")
+ test_env.remove_dir(test_env.testing_paths.luarocks_tmp)
+ test_env.remove_subdirs(testrun_dir, "testing[_%-]")
+ test_env.remove_files(testrun_dir, "testing_")
+ test_env.remove_files(testrun_dir, "luacov")
+ test_env.remove_files(testrun_dir, "upload_config")
+ test_env.remove_files(testrun_dir, "luarocks_site")
+ print("Cleaning done!")
+end
+
+--- Setup current checkout of luarocks to work with testing prefix.
+local function setup_luarocks()
+ local testing_paths = test_env.testing_paths
+ title("Setting up LuaRocks")
+
+ local lines = {
+ "return {",
+ ("SYSCONFDIR = %q,"):format(dir_path(testing_paths.testing_lrprefix, "etc/luarocks")),
+ ("LUA_DIR = %q,"):format(testing_paths.luadir),
+ ("LUA_BINDIR = %q,"):format(testing_paths.lua_bindir),
+ ("LUA = %q,"):format(testing_paths.lua),
+ }
+
+ if test_env.TEST_TARGET_OS == "windows" then
+ if test_env.MINGW then
+ table.insert(lines, [[SYSTEM = "mingw",]])
+ else
+ table.insert(lines, [[SYSTEM = "windows",]])
+ end
+ table.insert(lines, ("WIN_TOOLS = %q,"):format(testing_paths.win_tools))
+ end
+
+ table.insert(lines, "}")
+
+ test_env.write_file("src/luarocks/core/hardcoded.lua", table.concat(lines, "\n") .. "\n")
+
+ print("LuaRocks set up correctly!")
+end
+
+local function mock_api_call(path)
+ return test_env.execute(C(tool("wget"), "--timeout=0.1 --quiet --tries=10 http://localhost:8080" .. path))
+end
+
+function test_env.mock_server_init()
+ if not test_env.mock_prepared then
+ error("need to setup_specs with with_mock set to true")
+ end
+
+ local testing_paths = test_env.testing_paths
+ assert(test_env.need_rock("restserver-xavante"))
+
+ local lua = Q(testing_paths.lua)
+ local mock_server = Q(dir_path(testing_paths.util_dir, "mock-server.lua"))
+ local fixtures_dir = Q(testing_paths.fixtures_dir)
+
+ local cmd = C(lua, mock_server, fixtures_dir)
+
+ local bg_cmd = test_env.TEST_TARGET_OS == "windows"
+ and C("start", "/b", "\"\"", cmd)
+ or C(cmd, "&")
+
+ os.execute(test_env.execute_helper(bg_cmd, true, test_env.env_variables))
+
+ for _ = 1, 100 do
+ if mock_api_call("/api/tool_version") then
+ break
+ end
+ os.execute(test_env.TEST_TARGET_OS == "windows"
+ and "ping 192.0.2.0 -n 1 -w 250 > NUL"
+ or "sleep 0.1")
+ end
+
+end
+
+function test_env.mock_server_done()
+ mock_api_call("/shutdown")
+end
+
+local function find_binary_rock(src_rock, dirname)
+ local patt = src_rock:gsub("([.-])", "%%%1"):gsub("src", ".*[^s][^r][^c]")
+ for name in lfs.dir(dirname) do
+ if name:match(patt) then
+ return true
+ end
+ end
+ return false
+end
+
+local function prepare_mock_server_binary_rocks()
+ if test_env.mock_prepared then
+ return
+ end
+
+ local testing_paths = test_env.testing_paths
+
+ local rocks = {
+ -- rocks needed for mock-server
+ "luasocket-${LUASOCKET}.src.rock",
+ "coxpcall-1.16.0-1.src.rock",
+ "binaryheap-${BINARYHEAP}.src.rock",
+ "timerwheel-${TIMERWHEEL}.src.rock",
+ "copas-${COPAS}.src.rock",
+ "luafilesystem-${LUAFILESYSTEM}.src.rock",
+ "xavante-2.4.0-1.src.rock",
+ "wsapi-1.6.1-1.src.rock",
+ "rings-1.3.0-1.src.rock",
+ "wsapi-xavante-1.6.1-1.src.rock",
+ "dkjson-${DKJSON}.src.rock",
+ "restserver-0.1-1.src.rock",
+ "restserver-xavante-0.2-1.src.rock",
+ }
+ local make_manifest = download_rocks(rocks, testing_paths.testing_server)
+ for _, rock in ipairs(rocks) do
+ rock = V(rock)
+ local rockname = rock:gsub("%-[^-]+%-%d+%.[%a.]+$", "")
+ if not find_binary_rock(rock, testing_paths.testing_server) then
+ local rockpath = dir_path(testing_paths.testing_server, rock)
+ local tree = "--tree=" .. testing_paths.testing_cache
+
+ test_env.run.luarocks_nocov(C("build", Q(rockpath), tree))
+ test_env.run.luarocks_nocov(C("pack", rockname, tree))
+
+ move_file(rockname .. "-*.rock", testing_paths.testing_server)
+ make_manifest = true
+ end
+ end
+ if make_manifest then
+ test_env.run.luarocks_admin_nocov(C("make_manifest", Q(testing_paths.testing_server)))
+ end
+
+ test_env.mock_prepared = true
+end
+
+---
+-- Main function to create config files and testing environment
+function test_env.main()
+ local testing_paths = test_env.testing_paths
+ local testrun_dir = test_env.testing_paths.testrun_dir
+
+ if test_env.TEST_ENV_CLEAN then
+ clean()
+ end
+
+ lfs.mkdir(testrun_dir)
+ test_env.write_file(dir_path(testrun_dir, ".luarocks-no-project"), "")
+ lfs.mkdir(testing_paths.testing_cache)
+ lfs.mkdir(testing_paths.luarocks_tmp)
+
+ create_configs()
+
+ setup_luarocks()
+
+ -- Preparation of rocks for building environment
+ local rocks = {} -- names of rocks, required for building environment
+ local urls = {} -- names of rock and rockspec files to be downloaded
+
+ local env_vars = {
+ LUAROCKS_CONFIG = dir_path(testrun_dir, "testing_config.lua")
+ }
+
+ if test_env.TYPE_TEST_ENV == "full" then
+ table.insert(urls, "/luafilesystem-${LUAFILESYSTEM}.src.rock")
+ table.insert(urls, "/luasocket-${LUASOCKET}.src.rock")
+ table.insert(urls, "/luasec-${LUASEC}.src.rock")
+ table.insert(urls, "/md5-1.2-1.src.rock")
+ table.insert(urls, "/manifests/hisham/lua-zlib-1.2-0.src.rock")
+ table.insert(urls, "/manifests/hisham/lua-bz2-0.2.1.1-1.src.rock")
+ rocks = {"luafilesystem", "luasocket", "luasec", "md5", "lua-zlib", "lua-bz2"}
+ if test_env.TEST_TARGET_OS ~= "windows" then
+ if test_env.lua_version == "5.1" then
+ table.insert(urls, "/bit32-${BIT32}.src.rock")
+ table.insert(rocks, "bit32")
+ end
+ table.insert(urls, "/luaposix-${LUAPOSIX}.src.rock")
+ table.insert(rocks, "luaposix")
+ end
+ assert(test_env.run.luarocks_nocov(C("config", "variables.OPENSSL_INCDIR", Q(test_env.OPENSSL_INCDIR)), env_vars))
+ assert(test_env.run.luarocks_nocov(C("config", "variables.OPENSSL_LIBDIR", Q(test_env.OPENSSL_LIBDIR)), env_vars))
+ end
+
+ -- luacov is needed for both minimal or full environment
+ table.insert(urls, "/luacov-${LUACOV}.src.rock")
+ table.insert(urls, "/cluacov-${CLUACOV}.src.rock")
+ table.insert(rocks, "luacov")
+ table.insert(rocks, "cluacov")
+
+ -- Download rocks needed for LuaRocks testing environment
+ lfs.mkdir(testing_paths.testing_server)
+ download_rocks(urls, testing_paths.testing_server)
+
+ build_environment(rocks, env_vars)
+end
+
+--- Function for initial setup of environment and variables
+function test_env.setup_specs(extra_rocks, use_mock)
+ test_env.unload_luarocks()
+
+ local testrun_dir = test_env.testing_paths.testrun_dir
+ local variables = test_env.env_variables
+
+ -- if global variable about successful creation of testing environment doesn't exist, build environment
+ if not test_env.setup_done then
+ if test_env.CI then
+ if not exists(os.getenv("HOME"), ".ssh/id_rsa.pub") then
+ execute_bool("ssh-keygen -t rsa -P \"\" -f ~/.ssh/id_rsa")
+ execute_bool("cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys")
+ execute_bool("chmod og-wx ~/.ssh/authorized_keys")
+ execute_bool("ssh-keyscan localhost >> ~/.ssh/known_hosts")
+ end
+ end
+
+ test_env.main()
+
+ -- preload before meddling with package.path
+ require("spec.util.git_repo")
+ require("spec.util.quick")
+
+ package.path = variables.LUA_PATH
+ package.cpath = variables.LUA_CPATH
+
+ test_env.platform = get_luarocks_platform(test_env.env_variables)
+ test_env.wrapper_extension = test_env.TEST_TARGET_OS == "windows" and ".bat" or ""
+ test_env.setup_done = true
+ title("RUNNING TESTS")
+ end
+
+ if use_mock == "mock" then
+ prepare_mock_server_binary_rocks()
+ end
+
+ if extra_rocks then
+ local make_manifest = download_rocks(extra_rocks, test_env.testing_paths.testing_server)
+ if make_manifest then
+ test_env.run.luarocks_admin_nocov("make_manifest " .. test_env.testing_paths.testing_server)
+ end
+ end
+
+ if test_env.RESET_ENV then
+ test_env.remove_dir(test_env.testing_paths.testing_tree)
+ test_env.remove_dir(test_env.testing_paths.testing_sys_tree)
+ end
+
+ lfs.chdir(testrun_dir)
+end
+
+test_env.set_lua_version()
+test_env.set_args()
+test_env.testing_paths = create_testing_paths(test_env.LUA_V or test_env.LUAJIT_V)
+test_env.env_variables = create_env(test_env.testing_paths)
+test_env.run = make_run_functions()
+test_env.exists = exists
+test_env.V = V
+test_env.Q = Q
+test_env.P = P
+test_env.platform = get_luarocks_platform(test_env.env_variables)
+
+return test_env
diff --git a/spec/util/versions.lua b/spec/util/versions.lua
new file mode 100644
index 0000000..77c1129
--- /dev/null
+++ b/spec/util/versions.lua
@@ -0,0 +1,17 @@
+return {
+ binaryheap = "0.4-1", -- dependency for copas
+ bit32 = "5.3.5.1-1", -- dependency for luaposix on Lua 5.1
+ cluacov = "0.1.3-1",
+ copas = "3.0.0-2",
+ cprint = "0.2-1",
+ dkjson = "2.6-1",
+ lpeg = "1.0.0-1",
+ luacov = "0.15.0-1",
+ luafilesystem = "1.8.0-1",
+ luafilesystem_old = "1.6.3-2",
+ luaposix = "35.1-1",
+ luasocket = "3.0.0-1",
+ luasec = "1.3.2-1",
+ lxsh = "0.8.6-2",
+ timerwheel = "0.2.0-2", -- dependency for copas
+}