diff options
| author | Mike Vink <mike@pionative.com> | 2025-02-03 21:29:42 +0100 |
|---|---|---|
| committer | Mike Vink <mike@pionative.com> | 2025-02-03 21:29:42 +0100 |
| commit | 5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch) | |
| tree | deca28ea15e79f6f804c3d90d2ba757881638af5 /spec/util/test_env.lua | |
Diffstat (limited to 'spec/util/test_env.lua')
| -rw-r--r-- | spec/util/test_env.lua | 1187 |
1 files changed, 1187 insertions, 0 deletions
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 |
