summaryrefslogtreecommitdiff
path: root/extras/luacov/src
diff options
context:
space:
mode:
Diffstat (limited to 'extras/luacov/src')
-rwxr-xr-xextras/luacov/src/bin/luacov142
-rw-r--r--extras/luacov/src/luacov.lua95
-rw-r--r--extras/luacov/src/luacov/stats.lua73
-rw-r--r--extras/luacov/src/luacov/tick.lua7
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")