diff --git a/bin/moon b/bin/moon index 0818d23..8ce4982 100755 --- a/bin/moon +++ b/bin/moon @@ -15,6 +15,7 @@ local help = [=[Usage: %s [options] [script [args]] -h Print this message -d Disable stack trace rewriting + -c Collect and print code coverage -v Print version ]=] @@ -78,6 +79,13 @@ end if not opts.d then local err, trace + local cov + + if opts.c then + local coverage = require "moonscript.cmd.coverage" + cov = coverage.CodeCoverage() + cov:start() + end xpcall(run_chunk, function(_err) err = _err @@ -97,6 +105,10 @@ if not opts.d then util.trim(trace) }, "\n")) end + else + if cov then + cov:stop() + end end else run_chunk() diff --git a/moonscript/cmd/coverage.lua b/moonscript/cmd/coverage.lua new file mode 100644 index 0000000..be4923d --- /dev/null +++ b/moonscript/cmd/coverage.lua @@ -0,0 +1,138 @@ +local moon = require("moon") +local log +log = function(str) + if str == nil then + str = "" + end + return io.stderr:write(str .. "\n") +end +local create_counter +create_counter = function() + return setmetatable({ }, { + __index = function(self, name) + do + local tbl = setmetatable({ }, { + __index = function(self) + return 0 + end + }) + self[name] = tbl + return tbl + end + end + }) +end +local position_to_lines +position_to_lines = function(file_content, positions) + local lines = { } + local current_pos = 0 + local line_no = 1 + for char in file_content:gmatch(".") do + do + local count = rawget(positions, current_pos) + if count then + lines[line_no] = count + end + end + if char == "\n" then + line_no = line_no + 1 + end + current_pos = current_pos + 1 + end + return lines +end +local format_file +format_file = function(fname, positions) + local file = assert(io.open(fname)) + local content = file:read("*a") + file:close() + local lines = position_to_lines(content, positions) + log("------| @" .. tostring(fname)) + local line_no = 1 + for line in (content .. "\n"):gmatch("(.-)\n") do + local foramtted_no = ("% 5d"):format(line_no) + local sym = lines[line_no] and "*" or " " + log(tostring(sym) .. tostring(foramtted_no) .. "| " .. tostring(line)) + line_no = line_no + 1 + end + return log() +end +local CodeCoverage +do + local _base_0 = { + start = function(self) + self.line_counts = create_counter() + return debug.sethook((function() + local _base_1 = self + local _fn_0 = _base_1.process_line + return function(...) + return _fn_0(_base_1, ...) + end + end)(), "l") + end, + stop = function(self) + debug.sethook() + local line_table = require("moonscript.line_tables") + return self:format_results() + end, + process_line = function(self, _, line_no) + local debug_data = debug.getinfo(2, "S") + local source = debug_data.source + self.line_counts[source][line_no] = self.line_counts[source][line_no] + 1 + end, + format_results = function(self) + local line_table = require("moonscript.line_tables") + local positions = create_counter() + for file, lines in pairs(self.line_counts) do + local _continue_0 = false + repeat + local file_table = line_table[file] + if not (file_table) then + _continue_0 = true + break + end + for line, count in pairs(lines) do + local _continue_1 = false + repeat + local position = file_table[line] + if not (position) then + _continue_1 = true + break + end + positions[file][position] = positions[file][position] + count + _continue_1 = true + until true + if not _continue_1 then + break + end + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + for file, ps in pairs(positions) do + format_file(file, ps) + end + end + } + _base_0.__index = _base_0 + local _class_0 = setmetatable({ + __init = function() end, + __base = _base_0, + __name = "CodeCoverage" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + CodeCoverage = _class_0 +end +return { + CodeCoverage = CodeCoverage +} diff --git a/moonscript/cmd/coverage.moon b/moonscript/cmd/coverage.moon new file mode 100644 index 0000000..5066406 --- /dev/null +++ b/moonscript/cmd/coverage.moon @@ -0,0 +1,76 @@ + +moon = require "moon" + +log = (str="") -> + io.stderr\write str .. "\n" + +create_counter = -> + setmetatable {}, __index: (name) => + with tbl = setmetatable {}, __index: => 0 + @[name] = tbl + +position_to_lines = (file_content, positions) -> + lines = {} + current_pos = 0 + line_no = 1 + for char in file_content\gmatch "." + if count = rawget positions, current_pos + lines[line_no] = count + + if char == "\n" + line_no += 1 + + current_pos += 1 + + lines + +format_file = (fname, positions) -> + file = assert io.open fname + content = file\read "*a" + file\close! + + lines = position_to_lines content, positions + log "------| @#{fname}" + line_no = 1 + for line in (content .. "\n")\gmatch "(.-)\n" + foramtted_no = "% 5d"\format(line_no) + sym = lines[line_no] and "*" or " " + log "#{sym}#{foramtted_no}| #{line}" + line_no += 1 + + log! + + +class CodeCoverage + start: => + @line_counts = create_counter! + debug.sethook @\process_line, "l" + + stop: => + debug.sethook! + line_table = require "moonscript.line_tables" + + @format_results! + + process_line: (_, line_no) => + debug_data = debug.getinfo 2, "S" + source = debug_data.source + @line_counts[source][line_no] += 1 + + format_results: => + line_table = require "moonscript.line_tables" + positions = create_counter! + + for file, lines in pairs @line_counts + file_table = line_table[file] + continue unless file_table + + for line, count in pairs lines + position = file_table[line] + continue unless position + positions[file][position] += count + + for file, ps in pairs positions + format_file file, ps + +{ :CodeCoverage }