Verified Commit f41676d2 authored by Marek Vavruša's avatar Marek Vavruša Committed by Petr Špaček

tests/config: added a TAP-based test environment for modules/configs

I moved the test files to module directories because it allows
vendoring of whole modules including tests etc.

The test environment provides convenience functions and produces
test output in TAP format. Ideally all tests should use a common
format, so that CI can parse it provide better test output on PRs.
It seems like Gitlab CI doesn't support anything yet, but there
are two sort-of standards supported in CI tools - TAP and JUnit.
I chose TAP because it's easier to read for humans, cmocka supports it,
and it should be easier to adapt Deckard. There are also tools to
convert TAP into JUnit XML file.

Also added more tests for global functions and variables, and the
test tool now also tracks coverage (if `luacov` is installed).
parent 5830586e
......@@ -18,6 +18,8 @@
*.gcno
*.gcda
*.gcov
*.info
luacov.*.out
.dirstamp
.libs
.deps
......
......@@ -4,3 +4,6 @@
[submodule "modules/policy/lua-aho-corasick"]
path = modules/policy/lua-aho-corasick
url = git://github.com/cloudflare/lua-aho-corasick.git
[submodule "tests/config/tapered"]
path = tests/config/tapered
url = https://github.com/telemachus/tapered.git
......@@ -61,6 +61,7 @@ end
-- Ignore test files
exclude_files = {
'modules/policy/lua-aho-corasick', -- Vendored
'tests/config/tapered',
}
-- Ignore some pedantic checks
......@@ -75,4 +76,4 @@ files['daemon/lua/kres-gen.lua'].ignore = {'631'} -- Allow overly long lines
-- Tests and scripts can use global variables
files['scripts'].ignore = {'111', '112', '113'}
files['tests'].ignore = {'111', '112', '113'}
files['tests/config/test_utils.lua'].ignore = {'121'}
\ No newline at end of file
files['modules/*/*_test.lua'].ignore = {'111', '112', '113'}
\ No newline at end of file
local utils = require('test_utils')
-- setup resolver
modules = { 'hints' }
-- test for default configuration
local function test_default()
-- get loaded root hints and change names to lowercase
hints_data = utils.table_keys_to_lower(hints.root())
-- root hints loaded from default location
-- check correct ip address of a.root-server.net
utils.contains(hints_data['a.root-servers.net.'], '198.41.0.4', 'has IP address for a.root-servers.net.')
end
-- test loading from config file
local function test_custom()
-- load custom root hints file with fake ip address for a.root-server.net
err_msg = hints.root_file(TEST_DIR .. 'hints_test.zone')
same(err_msg, '', 'load root hints from file')
-- get loaded root hints and change names to lowercase
hints_data = utils.table_keys_to_lower(hints.root())
isnt(hints_data['a.root-servers.net.'], nil, 'can retrieve root hints')
-- check loaded ip address of a.root-server.net
utils.not_contains(hints_data['a.root-servers.net.'], '198.41.0.4', 'real IP address for a.root-servers.net. is replaced')
utils.contains(hints_data['a.root-servers.net.'], '10.0.0.1', 'real IP address for a.root-servers.net. is correct')
end
return {
test_default,
test_custom
}
\ No newline at end of file
dofile('./test_utils.lua') -- load test utilities
-- setup resolver
modules = { 'predict' }
-- mock global functions
local resolve_count = 0
local current_epoch = 0
worker.resolve = function ()
resolve_count = resolve_count + 1
end
stats.frequent = function ()
return {
{name = 'example.com', type = 'TYPE65535'},
{name = 'example.com', type = 'SOA'},
}
end
local current_epoch = 0
predict.epoch = function ()
return current_epoch % predict.period + 1
end
-- test if draining of prefetch queue works
function test_predict_drain()
local function test_predict_drain()
predict.queue_len = 2
predict.queue['TYPE65535 example.com'] = 1
predict.queue['SOA example.com'] = 1
predict.drain()
-- test that it attempted to prefetch
assert.same(2, resolve_count)
assert.same(0, predict.queue_len)
same(resolve_count, 2, 'attempted to prefetch on drain')
same(predict.queue_len, 0, 'prefetch queue empty after drain')
end
-- test if prediction process works
function test_predict_process()
local function test_predict_process()
-- start new epoch
predict.process()
assert.same(0, predict.queue_len)
same(predict.queue_len, 0, 'first epoch, empty prefetch queue')
-- next epoch, still no period for frequent queries
current_epoch = current_epoch + 1
predict.process()
assert.same(0, predict.queue_len)
same(predict.queue_len, 0, 'second epoch, empty prefetch queue')
-- next epoch, found period
current_epoch = current_epoch + 1
predict.process()
assert.same(2, predict.queue_len)
same(predict.queue_len, 2, 'third epoch, prefetching')
-- drain works with scheduled prefetches (two batches)
resolve_count = 0
predict.drain()
predict.drain()
assert.same(2, resolve_count)
assert.same(0, predict.queue_len)
same(resolve_count, 2, 'attempted to resolve queries in queue')
same(predict.queue_len, 0, 'prefetch queue is empty')
end
-- run test after processed config file
-- default config will be used and we can test it.
event.after(0, function (ev)
test(test_predict_drain)
test(test_predict_process)
quit()
end)
-- return test set
return {
test_predict_drain,
test_predict_process
}
\ No newline at end of file
dofile('./test_utils.lua') -- load test utilities
-- test if constants work properly
function test_constants()
assert.same(1, kres.class.IN)
assert.same(1, kres.class['IN'])
assert.same(2, kres.type.NS)
assert.same(2, kres.type.TYPE2)
assert.same(nil, kres.type.BADTYPE)
assert.same(2, kres.rcode.SERVFAIL)
end
-- test if rrsets interfaces work
function test_rrsets()
local rr = {owner = '\3com', ttl = 1, type = kres.type.TXT, rdata = '\5hello'}
local rr_text = tostring(kres.rr2str(rr))
assert.same('com. 1 TXT "hello"', rr_text:gsub('%s+', ' '))
end
-- run test after processed config file
-- default config will be used and we can test it.
event.after(0, function (ev)
test(test_constants)
test(test_rrsets)
quit()
end)
-- test if constants work properly
local function test_constants()
same(kres.class.IN, 1, 'class constants work')
same(kres.type.NS, 2, 'record type constants work')
same(kres.type.TYPE2, 2, 'unnamed record type constants work')
same(kres.type.BADTYPE, nil, 'non-existent type constants are checked')
same(kres.rcode.SERVFAIL, 2, 'rcode constants work')
end
-- test globals
local function test_globals()
ok(mode('strict'), 'changing strictness mode')
boom(mode, {'badmode'}, 'changing to non-existent strictness mode')
same(reorder_RR(true), true, 'answer section reordering')
same(option('REORDER_RR', false), false, 'generic option call')
boom(option, {'REORDER_RR', 'potato'}, 'generic option call argument check')
boom(option, {'MARS_VACATION', false}, 'generic option check name')
same(table_print('crabdiary'), 'crabdiary\n', 'table print works')
same(table_print({fakepizza=1}), '[fakepizza] => 1\n', 'table print works on tables')
end
-- test if dns library functions work
local function test_kres_functions()
local rr = {owner = '\3com', ttl = 1, type = kres.type.TXT, rdata = '\5hello'}
local rr_text = tostring(kres.rr2str(rr))
same(rr_text:gsub('%s+', ' '), 'com. 1 TXT "hello"', 'rrset to text works')
same(kres.dname2str(todname('com.')), 'com.', 'domain name conversion works')
end
return {
test_constants,
test_globals,
test_kres_functions,
}
\ No newline at end of file
dofile('./test_utils.lua') -- load test utilities
-- setup resolver
modules = { 'hints' }
-- test for default configuration
function test_default()
-- get loaded root hints and change names to lowercase
hints_data = table_keys_to_lower(hints.root())
-- root hints loaded from default location
-- check correct ip address of a.root-server.net
if not contains(hints_data['a.root-servers.net.'], '198.41.0.4') then
fail("Real IP address for a.root-servers.net. not found.")
end
end
-- test loading from config file
function test_custom()
-- load custom root hints file with fake ip address for a.root-server.net
err_msg = hints.root_file('hints.zone')
if err_msg ~= '' then
fail("hints.root_file error: %s", err_msg)
end
-- get loaded root hints and change names to lowercase
hints_data = table_keys_to_lower(hints.root())
-- check loaded ip address of a.root-server.net
if contains(hints_data['a.root-servers.net.'], '198.41.0.4') then
fail("Real IP address for a.root-servers.net. not removed")
end
if not contains(hints_data['a.root-servers.net.'], '10.0.0.1') then
fail("Fake IP address for a.root-servers.net. not found.")
end
end
-- run test after processed config file
-- default config will be used and we can test it.
ev = event.after(0, function (ev)
test_default()
test_custom()
quit()
end)
#!/bin/bash -e
export SOURCE_PATH=$(cd "$(dirname "$0")" && pwd -P)
export TEST_FILE=${2}
export TMP_RUNDIR="$(mktemp -d)"
export KRESD_NO_LISTEN=1
function finish {
rm -rf "${TMP_RUNDIR}"
rm -rf "${TMP_RUNDIR}"
}
trap finish EXIT
echo "config-test: ${2}"
cp "tests/config/${2}/"* "${TMP_RUNDIR}/"
cp tests/config/test_utils.lua "${TMP_RUNDIR}/"
KRESD_NO_LISTEN=1 ${DEBUGGER} ${1} -f 1 -c test.cfg "${TMP_RUNDIR}"
echo "# $(basename ${TEST_FILE})"
${DEBUGGER} ${1} -f 1 -c ${SOURCE_PATH}/test.cfg "${TMP_RUNDIR}"
\ No newline at end of file
Subproject commit be84b64d18293a29ca0acdf0431b0345084afe33
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
-- export testing module in globals
local tapered = require('tapered.src.tapered')
for k, v in pairs(tapered) do
_G[k] = v
end
-- load test
local tests = dofile(env.TEST_FILE) or {}
-- run test after processed config file
-- default config will be used and we can test it.
local runtest = require('test_utils').test
event.after(0, function ()
for _, t in ipairs(tests) do
runtest(t)
end
done()
end)
......@@ -6,17 +6,18 @@
# Check return code of kresd. Passed test have to call quit().
tests_config := \
basic \
hints \
predict
$(wildcard modules/*/*_test.lua) \
$(wildcard tests/config/*_test.lua)
define make_config_test
test-config-$(1): tests/config/$(1)/test.cfg check-install-precond
@$(preload_syms) ./tests/config/runtest.sh $(abspath $(SBINDIR)/kresd) $(1)
.PHONY: test-$(1)
$(1): check-install-precond
@$(preload_syms) ./tests/config/runtest.sh $(abspath $(SBINDIR)/kresd) $(abspath $(1))
$(1)-clean:
@$(RM) $(dir $(1))/luacov.stats.out
.PHONY: $(1)
endef
$(foreach test,$(tests_config),$(eval $(call make_config_test,$(test))))
check-config: $(foreach test,$(tests_config),test-config-$(test))
check-config: $(tests_config)
check-config-clean: $(foreach test,$(tests_config),$(test)-clean)
.PHONY: check-config
function fail(fmt, ...)
io.stderr:write(string.format(fmt..'\n', ...))
os.exit(2)
end
local M = {}
function test(f, ...)
function M.test(f, ...)
local res, exception = pcall(f, ...)
if not res then
local trace = debug.getinfo(2)
fail('%s:%d %s', trace.source, trace.currentline, exception)
io.stderr:write(string.format('%s:%d %s\n', trace.source, trace.currentline, exception))
os.exit(2)
end
return res
end
function table_keys_to_lower(table)
function M.table_keys_to_lower(table)
local res = {}
for k, v in pairs(table) do
res[k:lower()] = v
......@@ -20,32 +18,24 @@ function table_keys_to_lower(table)
return res
end
function contains(table, value)
local function contains(pass, fail, table, value, message)
message = message or string.format('table contains "%s"', value)
for _, v in pairs(table) do
if v == value then
return true
pass(message)
return
end
end
return false
fail(message)
return
end
function M.contains(table, value, message)
return contains(pass, fail, table, value, message)
end
function M.not_contains(table, value, message)
return contains(fail, pass, table, value, message)
end
-- Emulate busted testing interface
local assert_builtin = assert
assert = setmetatable({}, {
__call = function (_, ...)
return assert_builtin(...)
end,
__index = {
truthy = function (expr)
assert_builtin(expr)
end,
falsy = function (expr)
assert_builtin(not expr)
end,
same = function (a, b)
if a ~= b then
assert_builtin(false, string.format('expected: %s got: %s', a, b))
end
end,
}
})
\ No newline at end of file
return M
\ No newline at end of file
......@@ -48,6 +48,6 @@ tests: check-unit
# installcheck requires kresd to be installed in its final destination
# (DESTDIR is not supported right now because module path gets hardcoded)
installcheck: check-config
tests-clean: $(foreach test,$(tests_BIN),$(test)-clean) mock_cmodule-clean $(CLEAN_DNSTAP)
tests-clean: $(foreach test,$(tests_BIN),$(test)-clean) mock_cmodule-clean $(CLEAN_DNSTAP) check-config-clean
.PHONY: check-integration deckard installcheck tests tests-clean
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment