From 3ec7bcfa595c22d55b564da9213affc9aee78b0e Mon Sep 17 00:00:00 2001 From: Peter Melnichenko Date: Wed, 29 Jun 2016 12:51:27 +0300 Subject: Improve config loading error handling --- luacov-scm-1.rockspec | 1 + src/luacov/runner.lua | 52 +++++++++++++++---------- src/luacov/util.lua | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 src/luacov/util.lua diff --git a/luacov-scm-1.rockspec b/luacov-scm-1.rockspec index 5df14bc..3e4d79b 100644 --- a/luacov-scm-1.rockspec +++ b/luacov-scm-1.rockspec @@ -30,6 +30,7 @@ build = { ["luacov.stats"] = "src/luacov/stats.lua", ["luacov.tick"] = "src/luacov/tick.lua", ["luacov.hook"] = "src/luacov/hook.lua", + ["luacov.util"] = "src/luacov/util.lua" }, install = { bin = { diff --git a/src/luacov/runner.lua b/src/luacov/runner.lua index d7f150b..160cc95 100644 --- a/src/luacov/runner.lua +++ b/src/luacov/runner.lua @@ -9,9 +9,11 @@ local runner = {} runner.version = "0.11.0" local stats = require("luacov.stats") +local util = require("luacov.util") runner.defaults = require("luacov.defaults") local debug = require("debug") +local raw_os_exit = os.exit local new_anchor = newproxy or function() return {} end -- luacheck: compat @@ -146,16 +148,6 @@ local function on_exit() end end --- Returns true if the given filename exists. -local function file_exists(fname) - local f = io.open(fname) - - if f then - f:close() - return true - end -end - local dir_sep = package.config:sub(1, 1) local wildcard_expansion = "[^/]+" @@ -315,6 +307,31 @@ local function set_config(configuration) runner.tick = runner.tick or runner.configuration.tick end +local function die(error_msg) + io.stderr:write(("Error: %s\n"):format(error_msg)) + raw_os_exit(1) +end + +local function load_config_file(name, is_default) + local ok, conf, error_msg = util.load_config(name, _G) + + if ok then + if type(conf) ~= "table" then + die("config is not a table") + end + + return conf + end + + local error_type = conf + + if error_type == "read" and is_default then + return nil + end + + die(("couldn't %s config file %s: %s"):format(error_type, name, error_msg)) +end + local default_config_file = ".luacov" ------------------------------------------------------ @@ -327,14 +344,10 @@ local default_config_file = ".luacov" function runner.load_config(configuration) if not runner.configuration then if not configuration then - -- nothing provided, load from default location if possible - if file_exists(default_config_file) then - set_config(dofile(default_config_file)) - else - set_config(runner.defaults) - end + -- Nothing provided, load from default location if possible. + set_config(load_config_file(default_config_file, true) or runner.defaults) elseif type(configuration) == "string" then - set_config(dofile(configuration)) + set_config(load_config_file(configuration)) elseif type(configuration) == "table" then set_config(configuration) else @@ -406,10 +419,9 @@ function runner.init(configuration) -- metatable trick on filehandle won't work if Lua exits through -- os.exit() hence wrap that with exit code as well - local rawexit = os.exit os.exit = function(...) -- luacheck: no global on_exit() - rawexit(...) + raw_os_exit(...) end debug.sethook(runner.debug_hook, "l") @@ -518,7 +530,7 @@ local function getfilename(name) error("Bad argument: " .. tostring(name)) end - if file_exists(name) then + if util.file_exists(name) then return name end diff --git a/src/luacov/util.lua b/src/luacov/util.lua new file mode 100644 index 0000000..9f8e6f9 --- /dev/null +++ b/src/luacov/util.lua @@ -0,0 +1,103 @@ +--------------------------------------------------- +-- Utility module. +-- @class module +-- @name luacov.util +local util = {} + +--- Removes a prefix from a string if it's present. +-- @param str a string. +-- @param prefix a prefix string. +-- @return original string if does not start with prefix +-- or string without prefix. +function util.unprefix(str, prefix) + if str:sub(1, #prefix) == prefix then + return str:sub(#prefix + 1) + else + return str + end +end + +-- Returns contents of a file or nil + error message. +local function read_file(name) + local f, open_err = io.open(name, "rb") + + if not f then + return nil, util.unprefix(open_err, name .. ": ") + end + + local contents, read_err = f:read("*a") + f:close() + + if contents then + return contents + else + return nil, read_err + end +end + +--- Loads a string. +-- @param str a string. +-- @param[opt] env environment table. +-- @param[opt] chunkname chunk name. +function util.load_string(str, env, chunkname) + if _VERSION:find("5%.1") then + local func, err = loadstring(str, chunkname) + + if not func then + return nil, err + end + + if env then + setfenv(func, env) + end + + return func + else + return load(str, chunkname, "bt", env or _ENV) + end +end + +--- Load a config file. +-- Reads, loads and runs a Lua file in an environment. +-- @param name file name. +-- @param env environment table. +-- @return true and the first return value of config on success, +-- nil + error type + error message on failure, where error type +-- can be "read", "load" or "run". +function util.load_config(name, env) + local src, read_err = read_file(name) + + if not src then + return nil, "read", read_err + end + + local func, load_err = util.load_string(src, env, "@config") + + if not func then + return nil, "load", "line " .. util.unprefix(load_err, "config:") + end + + local ok, ret = pcall(func) + + if not ok then + return nil, "run", "line " .. util.unprefix(ret, "config:") + end + + return true, ret +end + +--- Checks if a file exists. +-- @param name file name. +-- @return true if file can be opened, false otherwise. +function util.file_exists(name) + local f = io.open(name) + + if f then + f:close() + return true + else + return false + end +end + +return util -- cgit v1.2.1