]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
CI coverage: handle Lua code coverage properly
authorPetr Špaček <petr.spacek@nic.cz>
Wed, 20 Dec 2017 11:47:10 +0000 (12:47 +0100)
committerPetr Špaček <petr.spacek@nic.cz>
Sat, 23 Dec 2017 22:48:48 +0000 (23:48 +0100)
Luacov statistics contained paths to installed files instead of source
files that it was a mess. The stats are now rewritten using hacky
mapping (created from install commands produced by make).

Also, branch and function coverage for Lua was always zero so now it is
turned off not to confuse users.

kresd config for respdiff now enables luacov as well.

Makefile
ci/respdiff/kresd.config
scripts/map_install_src.lua [new file with mode: 0755]
tests/config/coverage.lua [new file with mode: 0644]
tests/config/test.cfg
tests/deckard

index abb5bde67bbede15ec538bc1e8b74e30f495786a..6a22017c65c3df6ed0beda061270a183d6ea561e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -13,12 +13,16 @@ lint: $(patsubst %.lua.in,%.lua,$(wildcard */*/*.lua.in))
 coverage-c:
        @echo "# C coverage in $(COVERAGE_STAGE).c.info"
        @$(LCOV) --no-external --capture -d lib -d daemon -d modules -o $(COVERAGE_STAGE).c.info > /dev/null
-coverage-lua: $(wildcard */*/luacov.stats.out)
+coverage-lua: $(shell find -type f -name 'luacov.stats.out')
+       # map install paths to source paths
+       @$(MAKE) PREFIX=$(PREFIX) install --dry-run --always-make | scripts/map_install_src.lua --sed > .luacov_path_map
+       @find -type f -name 'luacov.stats.out' | xargs sed -i -f .luacov_path_map
+       @rm .luacov_path_map
        @echo "# Lua coverage in $(COVERAGE_STAGE).lua.info"
-       @if [ ! -z "$^" ]; then ./scripts/luacov_to_info.lua $^ > $(COVERAGE_STAGE).lua.info; fi
+       @scripts/luacov_to_info.lua $^ > $(COVERAGE_STAGE).lua.info
 coverage:
        @$(LCOV) $(addprefix --add-tracefile ,$(wildcard $(COVERAGE_STAGE)*.info)) --output-file coverage.info
-       @$(GENHTML) -q --ignore-errors source -o coverage -p $(realpath $(CURDIR)) -t "Knot DNS Resolver $(VERSION)-$(PLATFORM) coverage report" --legend coverage.info
+       @$(GENHTML) --no-function-coverage --no-branch-coverage -q -o coverage -p $(realpath $(CURDIR)) -t "Knot DNS Resolver $(VERSION)-$(PLATFORM) coverage report" --legend coverage.info
 
 .PHONY: all install check clean doc info coverage
 
index 2d6902f3b4b1695901a9a87365d0f3027e0b5d97..b804aad2a91de13a5ea64925ad168cc306a1af4e 100644 (file)
@@ -1,5 +1,7 @@
--- Refer to manual: https://knot-resolver.readthedocs.io/en/latest/daemon.html#configuration
+-- measure code coverage: kresd must run from $GIT_DIR
+require('tests.config.coverage')
 
+-- Refer to manual: https://knot-resolver.readthedocs.io/en/latest/daemon.html#configuration
 -- Listen on localhost and external interface
 net.listen('127.0.0.1', 5353)
 net.listen('::1', 5353)
diff --git a/scripts/map_install_src.lua b/scripts/map_install_src.lua
new file mode 100755 (executable)
index 0000000..f4f0ed3
--- /dev/null
@@ -0,0 +1,167 @@
+#!/usr/bin/env luajit
+
+-- parse install commands from stdin
+-- input: PREFIX=... make install --dry-run --always-make
+-- output: <install path> <source path>
+-- (or sed commands if --sed was specified)
+
+output = 'list'
+if #arg > 1 or arg[1] == '-h' or arg[1] == '--help' then
+       print(string.format([[
+Read install commands and map install paths to paths in source directory.
+
+Usage:
+$ PREFIX=... make install --dry-run --always-make | %s
+
+Example output:
+/kresd/git/.local/lib/kdns_modules/policy.lua  modules/policy/policy.lua
+
+Option --sed will produce output suitable as input suitable for sed.]],
+                               arg[0]))
+       os.exit(1)
+elseif #arg == 0 then
+       output = 'list'
+elseif arg[1] == '--sed' then
+       output = 'sed'
+else
+       print('Invalid arguments. See --help.')
+       os.exit(2)
+end
+
+-- remove double // from paths and remove trailing /
+function normalize_path(path)
+       assert(path)
+       repeat
+               path, changes = path:gsub('//', '/')
+       until changes == 0
+       return path:gsub('/$', '')
+end
+
+function is_opt(word)
+       return word:match('^-')
+end
+
+-- opts requiring additional argument to be skipped
+local ignored_opts_with_arg = {
+       ['--backup'] = true,
+       ['-g'] = true,
+       ['--group'] = true,
+       ['-m'] = true,
+       ['--mode'] = true,
+       ['-o'] = true,
+       ['--owner'] = true,
+       ['--strip-program'] = true,
+       ['--suffix'] = true,
+}
+
+-- state machine junctions caused by --opts
+-- returns: new state (expect, mode) and target name if any
+function parse_opts(word, expect, mode)
+       if word == '--' then
+               return 'names', mode, nil -- no options anymore
+       elseif word == '-d' or word == '--directory' then
+               return 'opt_or_name', 'newdir', nil
+       elseif word == '-t' or word == '--target-directory' then
+               return 'targetdir', mode, nil
+       elseif word:match('^--target-directory=') then
+               return 'opt_or_name', mode, string.sub(word, 20)
+       elseif ignored_opts_with_arg[word] then
+               return 'ignore', mode, nil -- ignore next word
+       else
+               return expect, mode, nil -- unhandled opt
+       end
+end
+
+
+-- cmd: complete install command line: install -m 0644 -t dest src1 src2
+-- dirs: names known to be directories: name => true
+-- returns: updated dirs
+function process_cmd(cmd, dirs)
+       -- print('# ' .. cmd)
+       sanity_check(cmd)
+       local expect = 'install'
+       local mode = 'copy' -- copy or newdir
+       local target -- last argument or argument for install -t
+       local names = {} -- non-option arguments
+
+       for word in cmd:gmatch('%S+') do
+               if expect == 'install' then -- parsing 'install'
+                       assert(word == 'install')
+                       expect = 'opt_or_name'
+               elseif expect == 'opt_or_name' then
+                       if is_opt(word) then
+                               expect, mode, newtarget = parse_opts(word, expect, mode)
+                               target = newtarget or target
+                       else
+                               if mode == 'copy' then
+                                       table.insert(names, word)
+                               elseif mode == 'newdir' then
+                                       local path = normalize_path(word)
+                                       dirs[path] = true
+                               else
+                                       assert(false, 'bad mode')
+                               end
+                       end
+               elseif expect == 'targetdir' then
+                       local path = normalize_path(word)
+                       dirs[path] = true
+                       target = word
+                       expect = 'opt_or_name'
+               elseif expect == 'names' then
+                       table.insert(names, word)
+               elseif expect == 'ignore' then
+                       expect = 'opt_or_name'
+               else
+                       assert(false, 'bad expect')
+               end
+       end
+       if mode == 'newdir' then
+               -- no mapping to print, this cmd just created directory
+               return dirs
+       end
+
+       if not target then -- last argument is the target
+               target = table.remove(names)
+       end
+       assert(target, 'fatal: no target in install cmd')
+       target = normalize_path(target)
+
+       for _, name in pairs(names) do
+               basename = string.gsub(name, "(.*/)(.*)", "%2")
+               if not dirs[target] then
+                       print('fatal: target directory "' .. target .. '" was not created yet!')
+                       os.exit(2)
+               end
+               -- mapping installed name -> source name
+               if output == 'list' then
+                       print(target .. '/' .. basename, name)
+               elseif output == 'sed' then
+                       print(string.format([[s`%s`%s`g]],
+                                           target .. '/' .. basename, name))
+               else
+                       assert(false, 'unsupported output')
+               end
+       end
+       return dirs
+end
+
+function sanity_check(cmd)
+       -- shell quotation is not supported
+       assert(not cmd:match('"'), 'quotes " are not supported')
+       assert(not cmd:match("'"), "quotes ' are not supported")
+       assert(not cmd:match('\\'), "escapes like \\ are not supported")
+       assert(cmd:match('^install%s'), 'not an install command')
+end
+
+-- remember directories created by install -d so we can expand relative paths
+local dirs = {}
+while true do
+       local cmd = io.read("*line")
+       if not cmd then
+               break
+       end
+       local isinstall = cmd:match('^install%s')
+       if isinstall then
+               dirs = process_cmd(cmd, dirs)
+       end
+end
diff --git a/tests/config/coverage.lua b/tests/config/coverage.lua
new file mode 100644 (file)
index 0000000..5af55d2
--- /dev/null
@@ -0,0 +1,12 @@
+-- optional code coverage
+-- include this file into config if you want to generate coverage data
+
+local ok, runner = pcall(require, 'luacov.runner')
+if ok then
+       runner.init({
+               savestepsize = 2, -- TODO
+               statsfile = 'luacov.stats.out',
+               exclude = {'test', 'tapered'},
+       })
+       jit.off()
+end
index a578aaca553eaeb89cde1de42edc8afe108727b4..cc8b0ff9b510a63b7c0e18250da69ae6fa5c8690 100644 (file)
@@ -1,16 +1,7 @@
 package.path = package.path .. ';' .. env.SOURCE_PATH .. '/?.lua'
 TEST_DIR = env.TEST_FILE:match('(.*/)')
 
--- optional code coverage
-local ok, runner = pcall(require, 'luacov.runner')
-if ok then
-       runner.init({
-               savestepsize = 2,
-               statsfile = TEST_DIR .. '/luacov.stats.out',
-               exclude = {'test', 'tapered'},
-       })
-       jit.off()
-end
+require('coverage')
 
 -- export testing module in globals
 local tapered = require('tapered.src.tapered')
index a5cd67c98836690e00ab76b7bd220023f7993ee9..8e533979fe667fdc0bf6975d643d9e98c37f3b41 160000 (submodule)
@@ -1 +1 @@
-Subproject commit a5cd67c98836690e00ab76b7bd220023f7993ee9
+Subproject commit 8e533979fe667fdc0bf6975d643d9e98c37f3b41