diff options
Diffstat (limited to 'extras/luacov/src')
-rwxr-xr-x | extras/luacov/src/bin/luacov | 142 | ||||
-rw-r--r-- | extras/luacov/src/luacov.lua | 95 | ||||
-rw-r--r-- | extras/luacov/src/luacov/stats.lua | 73 | ||||
-rw-r--r-- | extras/luacov/src/luacov/tick.lua | 7 |
4 files changed, 317 insertions, 0 deletions
diff --git a/extras/luacov/src/bin/luacov b/extras/luacov/src/bin/luacov new file mode 100755 index 0000000..6a24bc6 --- /dev/null +++ b/extras/luacov/src/bin/luacov @@ -0,0 +1,142 @@ +#!/usr/bin/env lua + +local luacov = require("luacov.stats") + +local data, most_hits = luacov.load() + +if not data then + print("Could not load stats file "..luacov.statsfile..".") + print("Run your Lua program with -lluacov and then rerun luacov.") + os.exit(1) +end + +local report = io.open("luacov.report.out", "w") + +-- only report on files specified on the command line +local patterns = {} +local exclude_patterns = {} +local exclude_next = false +for i = 1, #arg do + if arg[i] == "-X" then + exclude_next = true + else + if exclude_next then + exclude_patterns[#exclude_patterns+1] = arg[i] + exclude_next = false + else + patterns[#patterns+1] = arg[i] + end + end +end + +local names = {} +for filename, _ in pairs(data) do + if not patterns[1] then + table.insert(names, filename) + else + local path = filename:gsub("/", "."):gsub("%.lua$", "") + local include = false + for _, p in ipairs(patterns) do + if path:match(p) then + include = true + break + end + end + if include then + for _, p in ipairs(exclude_patterns) do + if path:match(p) then + include = false + break + end + end + end + if include then + table.insert(names, filename) + end + end +end + +table.sort(names) + +local most_hits_length = ("%d"):format(most_hits):len() +local empty_format = (" "):rep(most_hits_length+1) +local false_negative_format = ("!%% %dd"):format(most_hits_length) +local zero_format = ("*"):rep(most_hits_length).."0" +local count_format = ("%% %dd"):format(most_hits_length+1) + +local exclusions = +{ + { false, "^#!" }, -- Unix hash-bang magic line + { true, "" }, -- Empty line + { true, "}" }, -- Just a close-brace + { true, "end,?" }, -- Single "end" + { true, "else" }, -- Single "else" + { true, "repeat" }, -- Single "repeat" + { true, "do" }, -- Single "do" + { true, "local%s+[%w_,%s]+" }, -- "local var1, ..., varN" + { true, "local%s+[%w_,%s]+%s*=" }, -- "local var1, ..., varN =" + { true, "local%s+function%s*%([%w_,%.%s]*%)" }, -- "local function(arg1, ..., argN)" + { true, "local%s+function%s+[%w_]*%s*%([%w_,%.%s]*%)" }, -- "local function f (arg1, ..., argN)" +} + +local function excluded(line) + for _, e in ipairs(exclusions) do + if e[1] then + if line:match("^%s*"..e[2].."%s*$") or line:match("^%s*"..e[2].."%s*%-%-") then return true end + else + if line:match(e[2]) then return true end + end + end + return false +end + +for _, filename in ipairs(names) do + local filedata = data[filename] + local file = io.open(filename, "r") + if file then + report:write("\n") + report:write("==============================================================================\n") + report:write(filename, "\n") + report:write("==============================================================================\n") + local line_nr = 1 + block_comment, equals = false, "" + while true do + local line = file:read("*l") + if not line then break end + local true_line = line + + local new_block_comment = false + if not block_comment then + local l, equals = line:match("^(.*)%-%-%[(=*)%[") + if l then + line = l + new_block_comment = true + end + else + local l = line:match("%]"..equals.."%](.*)$") + if l then + line = l + block_comment = false + end + end + + local hits = filedata[line_nr] or 0 + if block_comment or excluded(line) then + if hits > 0 then + report:write(false_negative_format:format(hits)) + else + report:write(empty_format) + end + else + if hits == 0 then + report:write(zero_format) + else + report:write(count_format:format(hits)) + end + end + report:write("\t", true_line, "\n") + if new_block_comment then block_comment = true end + line_nr = line_nr + 1 + end + end +end diff --git a/extras/luacov/src/luacov.lua b/extras/luacov/src/luacov.lua new file mode 100644 index 0000000..a44333a --- /dev/null +++ b/extras/luacov/src/luacov.lua @@ -0,0 +1,95 @@ + +local M = {} + +local stats = require("luacov.stats") +local data = stats.load() +local statsfile = stats.start() +M.statsfile = statsfile + +local tick = package.loaded["luacov.tick"] +local ctr = 0 +local luacovlock = os.tmpname() + +local booting = true +local skip = {} +M.skip = skip + +local function on_line(_, line_nr) + if tick then + ctr = ctr + 1 + if ctr == 100 then + ctr = 0 + stats.save(data, statsfile) + end + end + + -- get name of processed file; ignore Lua code loaded from raw strings + local name = debug.getinfo(2, "S").source + if not name:match("^@") then + return + end + name = name:sub(2) + + -- skip 'luacov.lua' in coverage report + if booting then + skip[name] = true + booting = false + end + + if skip[name] then + return + end + + local file = data[name] + if not file then + file = {max=0} + data[name] = file + end + if line_nr > file.max then + file.max = line_nr + end + file[line_nr] = (file[line_nr] or 0) + 1 +end + +local function on_exit() + os.remove(luacovlock) + stats.save(data, statsfile) + stats.stop(statsfile) +end + +local function init() + if not tick then + M.on_exit_trick = io.open(luacovlock, "w") + debug.setmetatable(M.on_exit_trick, { __gc = on_exit } ) + end + + debug.sethook(on_line, "l") + + local rawcoroutinecreate = coroutine.create + coroutine.create = function(...) + local co = rawcoroutinecreate(...) + debug.sethook(co, on_line, "l") + return co + end + coroutine.wrap = function(...) + local co = rawcoroutinecreate(...) + debug.sethook(co, on_line, "l") + return function() + local r = { coroutine.resume(co) } + if not r[1] then + error(r[2]) + end + return unpack(r, 2) + end + end + + local rawexit = os.exit + os.exit = function(...) + on_exit() + rawexit(...) + end +end + +init() + +return M
diff --git a/extras/luacov/src/luacov/stats.lua b/extras/luacov/src/luacov/stats.lua new file mode 100644 index 0000000..5390c75 --- /dev/null +++ b/extras/luacov/src/luacov/stats.lua @@ -0,0 +1,73 @@ + +local M = {} + +local statsfile = "luacov.stats.out" +local stats + +function M.load() + local data, most_hits = {}, 0 + stats = io.open(statsfile, "r") + if not stats then + return data + end + while true do + local nlines = stats:read("*n") + if not nlines then + break + end + local skip = stats:read(1) + if skip ~= ":" then + break + end + local filename = stats:read("*l") + if not filename then + break + end + data[filename] = { + max=nlines + } + for i = 1, nlines do + local hits = stats:read("*n") + if not hits then + break + end + local skip = stats:read(1) + if skip ~= " " then + break + end + if hits > 0 then + data[filename][i] = hits + most_hits = math.max(most_hits, hits) + end + end + end + stats:close() + return data, most_hits +end + +function M.start() + return io.open(statsfile, "w") +end + +function M.stop(stats) + stats:close() +end + +function M.save(data, stats) + stats:seek("set") + for filename, filedata in pairs(data) do + local max = filedata.max + stats:write(max, ":", filename, "\n") + for i = 1, max do + local hits = filedata[i] + if not hits then + hits = 0 + end + stats:write(hits, " ") + end + stats:write("\n") + end + stats:flush() +end + +return M diff --git a/extras/luacov/src/luacov/tick.lua b/extras/luacov/src/luacov/tick.lua new file mode 100644 index 0000000..624cae3 --- /dev/null +++ b/extras/luacov/src/luacov/tick.lua @@ -0,0 +1,7 @@ + +--- Load luacov using this if you want it to periodically +-- save the stats file. This is useful if your script is +-- a daemon (ie, does not properly terminate.) +module("luacov.tick", package.seeall) + +require("luacov") |