summaryrefslogtreecommitdiffstats
path: root/tests/config
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:26:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:26:00 +0000
commit830407e88f9d40d954356c3754f2647f91d5c06a (patch)
treed6a0ece6feea91f3c656166dbaa884ef8a29740e /tests/config
parentInitial commit. (diff)
downloadknot-resolver-upstream/5.6.0.tar.xz
knot-resolver-upstream/5.6.0.zip
Adding upstream version 5.6.0.upstream/5.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/config')
-rw-r--r--tests/config/basic.test.lua211
-rw-r--r--tests/config/cache.test.lua67
-rw-r--r--tests/config/doh2.test.lua524
-rw-r--r--tests/config/lru.test.lua84
-rw-r--r--tests/config/meson.build41
-rw-r--r--tests/config/net.test.lua67
-rw-r--r--tests/config/tapered/.travis.yml40
-rw-r--r--tests/config/tapered/.travis/platform.sh15
-rw-r--r--tests/config/tapered/.travis/setenv_lua.sh3
-rw-r--r--tests/config/tapered/.travis/setup_lua.sh122
-rw-r--r--tests/config/tapered/CHANGES.md70
-rw-r--r--tests/config/tapered/LICENSE.md27
-rw-r--r--tests/config/tapered/README.md116
-rw-r--r--tests/config/tapered/doc/changes.html104
-rw-r--r--tests/config/tapered/doc/index.html111
-rw-r--r--tests/config/tapered/doc/license.html40
-rw-r--r--tests/config/tapered/doc/normalize.css439
-rw-r--r--tests/config/tapered/doc/screen.css109
-rwxr-xr-xtests/config/tapered/make-doc.bash55
-rw-r--r--tests/config/tapered/src/tapered.lua205
-rw-r--r--tests/config/tapered/tapered-1.1-0.rockspec21
-rw-r--r--tests/config/tapered/tapered-1.2.0-1.rockspec21
-rw-r--r--tests/config/tapered/tapered-1.2.1-1.rockspec21
-rw-r--r--tests/config/tapered/tapered-2.0.0-1.rockspec21
-rw-r--r--tests/config/tapered/tapered-2.0.1-1.rockspec21
-rw-r--r--tests/config/tapered/tapered-2.1.0-1.rockspec21
-rw-r--r--tests/config/tapered/tapered-2.2.0-1.rockspec22
-rw-r--r--tests/config/tapered/tapered-2.3.0-1.rockspec22
-rw-r--r--tests/config/tapered/test/.luacov55
-rw-r--r--tests/config/tapered/test/boom-result.txt7
-rwxr-xr-xtests/config/tapered/test/boom-test.lua19
-rw-r--r--tests/config/tapered/test/done-failure-result.txt2
-rw-r--r--tests/config/tapered/test/done-failure-test.lua5
-rw-r--r--tests/config/tapered/test/done-success-result.txt2
-rw-r--r--tests/config/tapered/test/done-success-test.lua5
-rw-r--r--tests/config/tapered/test/dynamic-setup-teardown-result.txt10
-rwxr-xr-xtests/config/tapered/test/dynamic-setup-teardown-test.lua27
-rwxr-xr-xtests/config/tapered/test/exit-failure-test.lua6
-rwxr-xr-xtests/config/tapered/test/exit-success-test.lua6
-rw-r--r--tests/config/tapered/test/informational-fields-result.txt4
-rwxr-xr-xtests/config/tapered/test/informational-fields-test.lua8
-rw-r--r--tests/config/tapered/test/is-isnt-result.txt43
-rwxr-xr-xtests/config/tapered/test/is-isnt-test.lua36
-rw-r--r--tests/config/tapered/test/like-unlike-result.txt13
-rwxr-xr-xtests/config/tapered/test/like-unlike-test.lua16
-rw-r--r--tests/config/tapered/test/ok-nok-result.txt7
-rwxr-xr-xtests/config/tapered/test/ok-nok-test.lua11
-rw-r--r--tests/config/tapered/test/pass-fail-result.txt4
-rwxr-xr-xtests/config/tapered/test/pass-fail-test.lua11
-rw-r--r--tests/config/tapered/test/result_test03.txt55
-rwxr-xr-xtests/config/tapered/test/runner.bash71
-rw-r--r--tests/config/tapered/test/same-result.txt31
-rw-r--r--tests/config/tapered/test/same-test.lua74
-rw-r--r--tests/config/tapered/test/setup-teardown-result.txt4
-rwxr-xr-xtests/config/tapered/test/setup-teardown-test.lua14
-rw-r--r--tests/config/test.cfg46
-rw-r--r--tests/config/test_dns_generators.lua134
-rw-r--r--tests/config/test_utils.lua121
-rw-r--r--tests/config/tls.test.lua29
-rw-r--r--tests/config/worker.test.lua65
60 files changed, 3561 insertions, 0 deletions
diff --git a/tests/config/basic.test.lua b/tests/config/basic.test.lua
new file mode 100644
index 0000000..73356a4
--- /dev/null
+++ b/tests/config/basic.test.lua
@@ -0,0 +1,211 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+
+-- 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.section.ANSWER, 0, 'section constants work')
+ same(kres.rcode.SERVFAIL, 2, 'rcode constants work')
+ same(kres.opcode.UPDATE, 5, 'opcode constants work')
+ -- Test inverse tables to convert constants to text
+ same(kres.tostring.class[1], 'IN', 'text class constants work')
+ same(kres.tostring.type[2], 'NS', 'text record type constants work')
+ same(kres.tostring.type[65535], 'TYPE65535', 'text record type undefined constants work')
+ same(kres.tostring.section[0], 'ANSWER', 'text section constants work')
+ same(kres.tostring.rcode[2], 'SERVFAIL', 'text rcode constants work')
+ same(kres.tostring.opcode[5], 'UPDATE', 'text opcode 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'", 'table print works')
+ same(table_print({fakepizza=1}), "{\n ['fakepizza'] = 1,\n}", 'table print works on tables')
+end
+
+-- test global API functions
+local function test_global_functions()
+ boom(kres.parse_rdata, {'A 127.0.0.1'}, 'parse_rdata with non-table argument')
+ boom(kres.parse_rdata, {{1}}, 'parse_rdata with non-string data in arg table')
+ same(kres.parse_rdata({'A 127.0.0.1'}), {string.char(127, 0, 0, 1)}, 'parse_rdata with single record')
+ same(kres.parse_rdata({'A 127.0.0.1', 'A 127.0.0.2'}),
+ {string.char(127, 0, 0, 1), string.char(127, 0, 0, 2)}, 'parse_rdata with multiple records')
+end
+
+-- test if dns library functions work
+local function test_rrset_functions()
+ local rr = {owner = '\3com\0', 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')
+ -- test creating rrset
+ rr = kres.rrset(todname('com.'), kres.type.A, kres.class.IN, 66)
+ ok(ffi.istype(kres.rrset, rr), 'created an empty RR')
+ same(rr:owner(), '\3com\0', 'created RR has correct owner')
+ same(rr:class(), kres.class.IN, 'created RR has correct class')
+ same(rr:class(kres.class.CH), kres.class.CH, 'can set a different class')
+ same(rr:class(kres.class.IN), kres.class.IN, 'can restore a class')
+ same(rr.type, kres.type.A, 'created RR has correct type')
+ -- test adding rdata
+ same(rr:wire_size(), 0, 'empty RR wire size is zero')
+ ok(rr:add_rdata('\1\2\3\4', 4), 'adding RDATA works')
+ same(rr:wire_size(), 5 + 4 + 4 + 2 + 4, 'RR wire size works after adding RDATA')
+ -- test conversion to text
+ local expect = 'com. 66 A 1.2.3.4\n'
+ same(rr:txt_dump(), expect, 'RR to text works')
+ -- create a dummy rrsig
+ local rrsig = kres.rrset(todname('com.'), kres.type.RRSIG, kres.class.IN, 0)
+ rrsig:add_rdata('\0\1', 2)
+ same(rr:rdcount(), 1, 'add_rdata really added RDATA')
+ -- check rrsig matching
+ same(rr.type, rrsig:type_covered(), 'rrsig type covered matches covered RR type')
+ ok(rr:is_covered_by(rrsig), 'rrsig is covering a record')
+ -- test rrset merging
+ local copy = kres.rrset(rr:owner(), rr.type, kres.class.IN, 66)
+ ok(copy:add_rdata('\4\3\2\1', 4), 'adding second RDATA works')
+ ok(rr:merge_rdata(copy), 'merge_rdata works')
+ same(rr:rdcount(), 2, 'RDATA count is correct after merge_rdata')
+ expect = 'com. 66 A 1.2.3.4\n' ..
+ 'com. 66 A 4.3.2.1\n'
+ same(rr:txt_dump(), expect, 'merge_rdata actually merged RDATA')
+end
+
+-- test dns library packet interface
+local function test_packet_functions()
+ local pkt = kres.packet(512)
+ isnt(pkt, nil, 'creating packets works')
+ -- Test manipulating header
+ ok(pkt:rcode(kres.rcode.NOERROR), 'setting rcode works')
+ same(pkt:rcode(), 0, 'getting rcode works')
+ same(pkt:opcode(), 0, 'getting opcode works')
+ is(pkt:aa(), false, 'packet is created without AA')
+ is(pkt:ra(), false, 'packet is created without RA')
+ is(pkt:ad(), false, 'packet is created without AD')
+ ok(pkt:rd(true), 'setting RD bit works')
+ is(pkt:rd(), true, 'getting RD bit works')
+ ok(pkt:tc(true), 'setting TC bit works')
+ is(pkt:tc(), true, 'getting TC bit works')
+ ok(pkt:tc(false), 'disabling TC bit works')
+ is(pkt:tc(), false, 'getting TC bit after disable works')
+ is(pkt:cd(), false, 'getting CD bit works')
+ is(pkt:id(1234), 1234, 'setting MSGID works')
+ is(pkt:id(), 1234, 'getting MSGID works')
+ -- Test manipulating question
+ is(pkt:qname(), nil, 'reading name from empty question')
+ is(pkt:qtype(), 0, 'reading type from empty question')
+ is(pkt:qclass(), 0, 'reading class from empty question')
+ ok(pkt:question(todname('hello'), kres.class.IN, kres.type.A), 'setting question section works')
+ same(pkt:qname(), todname('hello'), 'reading QNAME works')
+ same(pkt:qtype(), kres.type.A, 'reading QTYPE works')
+ same(pkt:qclass(), kres.class.IN, 'reading QCLASS works')
+ -- Test manipulating sections
+ ok(pkt:begin(kres.section.ANSWER), 'switching sections works')
+ local res, err = pkt:put(nil, 0, 0, 0, '')
+ isnt(res, true, 'inserting nil entry doesnt work')
+ isnt(err.code, 0, 'error code is non-zero')
+ isnt(tostring(res), '', 'inserting nil returns invalid parameter')
+ ok(pkt:put(pkt:qname(), 900, pkt:qclass(), kres.type.A, '\1\2\3\4'), 'adding rrsets works')
+ boom(pkt.begin, {pkt, 10}, 'switching to invalid section doesnt work')
+ ok(pkt:begin(kres.section.ADDITIONAL), 'switching to different section works')
+ boom(pkt.begin, {pkt, 0}, 'rewinding sections doesnt work')
+ local before_insert = pkt:remaining_bytes()
+ ok(pkt:put(pkt:qname(), 900, pkt:qclass(), kres.type.A, '\4\3\2\1'), 'adding rrsets to different section works')
+ same(pkt:remaining_bytes(), before_insert - (2 + 4 + 4 + 2 + 4), 'remaining bytes count goes down with insertions')
+ -- Test conversions to text
+ like(pkt:tostring(), '->>HEADER<<-', 'packet to text works')
+ -- Test deserialization
+ local wire = pkt:towire()
+ same(#wire, 55, 'packet serialization works')
+ local parsed = kres.packet(#wire, wire)
+ isnt(parsed, nil, 'creating packet from wire works')
+ ok(parsed:parse(), 'parsing packet from wire works')
+ same(parsed:qname(), pkt:qname(), 'parsed packet has same QNAME')
+ same(parsed:qtype(), pkt:qtype(), 'parsed packet has same QTYPE')
+ same(parsed:qclass(), pkt:qclass(), 'parsed packet has same QCLASS')
+ same(parsed:opcode(), pkt:opcode(), 'parsed packet has same opcode')
+ same(parsed:rcode(), pkt:rcode(), 'parsed packet has same rcode')
+ same(parsed:rd(), pkt:rd(), 'parsed packet has same RD')
+ same(parsed:id(), pkt:id(), 'parsed packet has same MSGID')
+ same(parsed:qdcount(), pkt:qdcount(), 'parsed packet has same question count')
+ same(parsed:ancount(), pkt:ancount(), 'parsed packet has same answer count')
+ same(parsed:nscount(), pkt:nscount(), 'parsed packet has same authority count')
+ same(parsed:arcount(), pkt:arcount(), 'parsed packet has same additional count')
+ same(parsed:tostring(), pkt:tostring(), 'parsed packet is equal to source packet')
+
+ -- Test adding RR sets directly
+ local copy = kres.packet(512)
+ copy:question(todname('hello'), kres.class.IN, kres.type.A)
+ copy:begin(kres.section.ANSWER)
+ local rr = kres.rrset(pkt:qname(), kres.type.A, kres.class.IN, 66)
+ rr:add_rdata('\4\3\2\1', 4)
+ ok(copy:put_rr(rr), 'adding RR sets directly works')
+ ok(copy:recycle(), 'recycling packet works')
+
+ -- Test recycling of packets
+ -- Clear_payload keeps header + question intact
+ local cleared = kres.packet(#wire, wire) -- same as "parsed" above
+ ok(cleared:parse(), 'parsing packet from wire works')
+ ok(cleared:clear_payload(), 'clear_payload works')
+ same(cleared:id(), pkt:id(), 'cleared packet has same MSGID')
+ same(cleared:qr(), pkt:qr(), 'cleared packet has same QR')
+ same(cleared:opcode(), pkt:opcode(), 'cleared packet has same OPCODE')
+ same(cleared:aa(), pkt:aa(), 'cleared packet has same AA')
+ same(cleared:tc(), pkt:tc(), 'cleared packet has same TC')
+ same(cleared:rd(), pkt:rd(), 'cleared packet has same RD')
+ same(cleared:ra(), pkt:ra(), 'cleared packet has same RA')
+ same(cleared:ad(), pkt:ad(), 'cleared packet has same AD')
+ same(cleared:cd(), pkt:cd(), 'cleared packet has same CD')
+ same(cleared:rcode(), pkt:rcode(), 'cleared packet has same RCODE')
+ same(cleared:qdcount(), pkt:qdcount(), 'cleared packet has same question count')
+ same(cleared:ancount(), 0, 'cleared packet has no answers')
+ same(cleared:nscount(), 0, 'cleared packet has no authority')
+ same(cleared:arcount(), 0, 'cleared packet has no additional')
+ same(cleared:qname(), pkt:qname(), 'cleared packet has same QNAME')
+ same(cleared:qtype(), pkt:qtype(), 'cleared packet has same QTYPE')
+ same(cleared:qclass(), pkt:qclass(), 'cleared packet has same QCLASS')
+
+ -- Recycle clears question as well
+ ok(pkt:recycle(), 'recycle() works')
+ is(pkt:ancount(), 0, 'recycle() clears records')
+ is(pkt:qname(), nil, 'recycle() clears question')
+ is(#pkt:towire(), 12, 'recycle() clears the packet wireformat')
+end
+
+-- test JSON encode/decode functions
+local function test_json_functions()
+ for msg, obj in pairs({
+ ['number'] = 0,
+ ['string'] = 'ok',
+ ['list'] = {1, 2, 3},
+ ['map'] = {foo='bar'},
+ ['nest structure'] = {foo='bar', baz={1,2,3}},
+ }) do
+ same(fromjson(tojson(obj)), obj, 'json test: ' .. msg)
+ end
+
+ for _, str in ipairs({
+ '{', '}',
+ '[', ']',
+ 'x,',
+ '[1,2,3,]',
+ }) do
+ boom(fromjson, {'{'}, 'json test: invalid \'' .. str .. '\'')
+ end
+end
+
+return {
+ test_constants,
+ test_globals,
+ test_global_functions,
+ test_rrset_functions,
+ test_packet_functions,
+ test_json_functions,
+}
diff --git a/tests/config/cache.test.lua b/tests/config/cache.test.lua
new file mode 100644
index 0000000..33b87db
--- /dev/null
+++ b/tests/config/cache.test.lua
@@ -0,0 +1,67 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- test if cache module properties work
+local function test_properties()
+ is(type(cache), 'table', 'cache module is loaded')
+ is(cache.count(), 0, 'cache is empty on startup')
+ local backends = cache.backends()
+ is(type(backends), 'table', 'cache provides a list of backends')
+ ok(backends['lmdb'], 'cache provides built-in lmdb backend')
+ is(cache.current_storage, 'lmdb://', 'cache starts with lmdb backend')
+ is(cache.current_size, 100 * MB, 'cache starts with default size limit')
+ is(cache.max_ttl(10), 10, 'allows setting maximum TTL')
+ is(cache.max_ttl(), 10, 'stored maximum TTL')
+ is(cache.min_ttl(1), 1, 'allows setting minimum TTL')
+ is(cache.min_ttl(), 1, 'stored minimum TTL')
+end
+
+-- test if the stats work with reopening the cache and operations fail with closed cache
+local function test_stats()
+ ok(cache.close(), 'cache can be closed')
+ boom(cache.open, {100 * MB, 'invalid://'}, 'cache cannot be opened with invalid backend')
+
+ boom(cache.clear, {}, '.clear() does not work on closed cache')
+ boom(cache.count, {}, '.count() does not work on closed cache')
+ boom(cache.get, { 'key' }, '.get(...) does not work on closed cache')
+
+ ok(cache.open(100 * MB), 'cache can be reopened')
+ local s = cache.stats()
+ is(type(s), 'table', 'stats returns a table')
+ -- Just checking the most useful fields
+ isnt(s.read and s.read_miss and s.write, nil, 'stats returns correct fields')
+end
+
+-- test if cache can be resized or shrunk
+local function test_resize()
+ ok(cache.open(200 * MB, 'lmdb://'), 'cache can be resized')
+ is(cache.current_size, 200 * MB, 'cache was resized')
+ ok(cache.open(50 * MB), 'cache can be shrunk')
+ is(cache.current_size, 50 * MB, 'cache was shrunk')
+end
+
+-- test access to cache through context
+local function test_context_cache()
+ local c = kres.context().cache
+ is(type(c), 'cdata', 'context has a cache object')
+ local s = c.stats
+ isnt(s.read and s.read_miss and s.write, 'context cache stats works')
+ -- insert A record into cache
+ local rdata = '\1\2\3\4'
+ local rr = kres.rrset('\3com\0', kres.type.A, kres.class.IN, 66)
+ rr:add_rdata(rdata, #rdata)
+ local s_write = s.write
+ ok(c:insert(rr, nil, 0, 0), 'cache insertion works (A)')
+ ok(c:commit(), 'cache commit works')
+ isnt(s.write, s_write, 'cache insertion increments counters')
+ -- insert NS record into cache
+ local rr_ns = kres.rrset('\3com\0', kres.type.NS, kres.class.IN, 66)
+ local rdata_ns = todname('c.gtld-servers.net')
+ ok(rr_ns:add_rdata(rdata_ns, #rdata_ns), 'adding rdata works')
+ ok(c:insert(rr_ns, nil, 0), 'cache insertion works (NS)')
+end
+
+return {
+ test_properties,
+ test_stats,
+ test_resize,
+ test_context_cache,
+}
diff --git a/tests/config/doh2.test.lua b/tests/config/doh2.test.lua
new file mode 100644
index 0000000..2360e7f
--- /dev/null
+++ b/tests/config/doh2.test.lua
@@ -0,0 +1,524 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local basexx = require('basexx')
+local ffi = require('ffi')
+
+local function gen_huge_answer(_, req)
+ local answer = req:ensure_answer()
+ ffi.C.kr_pkt_make_auth_header(answer)
+
+ answer:rcode(kres.rcode.NOERROR)
+
+ -- 64k answer
+ answer:begin(kres.section.ANSWER)
+ answer:put('\4test\0', 300, answer:qclass(), kres.type.URI,
+ '\0\0\0\0' .. string.rep('0', 65000))
+ answer:put('\4test\0', 300, answer:qclass(), kres.type.URI,
+ '\0\0\0\0' .. 'done')
+ return kres.DONE
+end
+
+local function gen_varying_ttls(_, req)
+ local qry = req:current()
+ local answer = req:ensure_answer()
+ ffi.C.kr_pkt_make_auth_header(answer)
+
+ answer:rcode(kres.rcode.NOERROR)
+
+ -- varying TTLs in ANSWER section
+ answer:begin(kres.section.ANSWER)
+ answer:put(qry.sname, 1800, answer:qclass(), kres.type.AAAA,
+ '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
+ answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\127\0\0\1')
+ answer:put(qry.sname, 20000, answer:qclass(), kres.type.NS, '\2ns\4test\0')
+
+ -- shorter TTL than all other RRs
+ answer:begin(kres.section.AUTHORITY)
+ answer:put('\4test\0', 300, answer:qclass(), kres.type.SOA,
+ -- ns.test. nobody.invalid. 1 3600 1200 604800 10800
+ '\2ns\4test\0\6nobody\7invalid\0\0\0\0\1\0\0\14\16\0\0\4\176\0\9\58\128\0\0\42\48')
+ return kres.DONE
+end
+
+function parse_pkt(input, desc)
+ local wire = ffi.cast("void *", input)
+ local pkt = ffi.C.knot_pkt_new(wire, #input, nil);
+ assert(pkt, desc .. ': failed to create new packet')
+
+ local result = ffi.C.knot_pkt_parse(pkt, 0)
+ ok(result == 0, desc .. ': knot_pkt_parse works on received answer')
+ return pkt
+end
+
+local function check_ok(req, desc)
+ local headers, stream, errno = req:go(16)
+ if errno then
+ local errmsg = stream
+ nok(errmsg, desc .. ': ' .. errmsg)
+ return
+ end
+ same(tonumber(headers:get(':status')), 200, desc .. ': status 200')
+ same(headers:get('content-type'), 'application/dns-message', desc .. ': content-type')
+ local body = assert(stream:get_body_as_string())
+ local pkt = parse_pkt(body, desc)
+ return headers, pkt
+end
+
+local function check_err(req, exp_status, desc)
+ local headers, errmsg, errno = req:go(16)
+ if errno then
+ nok(errmsg, desc .. ': ' .. errmsg)
+ return
+ end
+ local got_status = headers:get(':status')
+ same(got_status, exp_status, desc)
+end
+
+-- check prerequisites
+local bound, port
+local host = '127.0.0.1'
+for _ = 1,10 do
+ port = math.random(30000, 39999)
+ bound = pcall(net.listen, host, port, { kind = 'doh2'})
+ if bound then
+ break
+ end
+end
+
+if not bound then
+ -- skipping doh2 tests (failure to bind may be caused by missing support during build)
+ os.exit(77)
+else
+ policy.add(policy.suffix(policy.DROP, policy.todnames({'servfail.test.'})))
+ policy.add(policy.suffix(policy.DENY, policy.todnames({'nxdomain.test.'})))
+ policy.add(policy.suffix(gen_varying_ttls, policy.todnames({'noerror.test.'})))
+
+ local req_templ, uri_templ
+ local function start_server()
+ local request = require('http.request')
+ local ssl_ctx = require('openssl.ssl.context')
+ uri_templ = string.format('https://%s:%d/dns-query', host, port)
+ req_templ = assert(request.new_from_uri(uri_templ))
+ req_templ.headers:upsert('content-type', 'application/dns-message')
+ req_templ.ctx = ssl_ctx.new()
+ req_templ.ctx:setVerify(ssl_ctx.VERIFY_NONE)
+ end
+
+
+ -- test a valid DNS query using POST
+ local function test_post_servfail()
+ local desc = 'valid POST query which ends with SERVFAIL'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- servfail.test. A
+ 'FZUBAAABAAAAAAAACHNlcnZmYWlsBHRlc3QAAAEAAQ=='))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- uncacheable
+ same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+ same(headers:get('access-control-allow-origin'), '*', desc .. ': CORS headers')
+ same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
+ end
+
+ local function test_post_noerror()
+ local desc = 'valid POST query which ends with NOERROR'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- noerror.test. A
+ 'vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB'))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- HTTP TTL is minimum from all RRs in the answer
+ same(headers:get('cache-control'), 'max-age=300', desc .. ': TTL 900')
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode matches')
+ same(pkt:ancount(), 3, desc .. ': ANSWER is present')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ same(pkt:arcount(), 0, desc .. ': ADDITIONAL is empty')
+ end
+
+ local function test_post_ignore_params_1()
+ local desc = 'valid POST ignores parameters (without ?dns)'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req.headers:upsert(':path', '/doh?something=asdf&aaa=bbb')
+ req:set_body(basexx.from_base64( -- noerror.test. A
+ 'vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB'))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- HTTP TTL is minimum from all RRs in the answer
+ same(headers:get('cache-control'), 'max-age=300', desc .. ': TTL 900')
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode matches')
+ same(pkt:ancount(), 3, desc .. ': ANSWER is present')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ same(pkt:arcount(), 0, desc .. ': ADDITIONAL is empty')
+ end
+
+ local function test_post_ignore_params_2()
+ local desc = 'valid POST ignores parameters (with ?dns)'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req.headers:upsert(':path', '/dns-query?dns=' -- servfail.test. A
+ .. 'FZUBAAABAAAAAAAACHNlcnZmYWlsBHRlc3QAAAEAAQ')
+ req:set_body(basexx.from_base64( -- noerror.test. A
+ 'vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB'))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- HTTP TTL is minimum from all RRs in the answer
+ same(headers:get('cache-control'), 'max-age=300', desc .. ': TTL 900')
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode matches')
+ same(pkt:ancount(), 3, desc .. ': ANSWER is present')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ same(pkt:arcount(), 0, desc .. ': ADDITIONAL is empty')
+ end
+
+ local function test_post_nxdomain()
+ local desc = 'valid POST query which ends with NXDOMAIN'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- nxdomain.test. A
+ 'viABAAABAAAAAAAACG54ZG9tYWluBHRlc3QAAAEAAQ=='))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ same(headers:get('cache-control'), 'max-age=10800', desc .. ': TTL 10800')
+ same(pkt:rcode(), kres.rcode.NXDOMAIN, desc .. ': rcode matches')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ end
+
+ -- RFC 8484 section 6 explicitly allows huge answers over HTTP
+ local function test_huge_answer()
+ policy.add(policy.suffix(gen_huge_answer, policy.todnames({'huge.test'})))
+ local desc = 'POST query for a huge answer'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- huge.test. URI, no EDNS
+ 'HHwBAAABAAAAAAAABGh1Z2UEdGVzdAABAAAB'))
+ local _, pkt = check_ok(req, desc)
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode NOERROR')
+ same(pkt:tc(), false, desc .. ': no TC bit')
+ same(pkt:ancount(), 2, desc .. ': ANSWER contains both RRs')
+ end
+
+ -- test an invalid DNS query using POST
+ local function test_post_short_input()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'POST')
+ req:set_body(string.rep('0', 11)) -- 11 bytes < DNS msg header
+ check_err(req, '400', 'too short POST finishes with 400')
+ end
+
+-- local function test_post_long_input()
+-- -- FIXME: This test is broken in Lua. The connection times out
+-- -- for some reason, but sending a request like this with `curl`
+-- -- or PowerShell's `Invoke-RestMethod` provides correct results.
+--
+-- local req = assert(req_templ:clone())
+-- req.headers:upsert(':method', 'POST')
+-- req:set_body(string.rep('s', 1025)) -- > DNS msg over UDP
+-- check_err(req, '400', 'too long POST finishes with 400')
+-- end
+
+ local function test_post_unparseable_input()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'POST')
+ req:set_body(string.rep('\0', 1024)) -- garbage
+ check_err(req, '400', 'unparseable DNS message finishes with 400')
+ end
+
+ local function test_post_unsupp_type()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'POST')
+ req.headers:upsert('content-type', 'application/dns+json')
+ req:set_body(string.rep('\0', 12)) -- valid message
+ check_err(req, '415', 'unsupported request content type finishes with 415')
+ end
+
+ -- test a valid DNS query using GET
+ local function test_get_servfail()
+ local desc = 'valid GET query which ends with SERVFAIL'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- servfail.test. A
+ .. 'FZUBAAABAAAAAAAACHNlcnZmYWlsBHRlc3QAAAEAAQ')
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- uncacheable
+ same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+ same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
+ end
+
+ local function test_get_noerror()
+ local desc = 'valid GET query which ends with NOERROR'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- noerror.test. A
+ .. 'vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB')
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- HTTP TTL is minimum from all RRs in the answer
+ same(headers:get('cache-control'), 'max-age=300', desc .. ': TTL 900')
+ same(headers:get('access-control-allow-origin'), '*', desc .. ': CORS headers')
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode matches')
+ same(pkt:ancount(), 3, desc .. ': ANSWER is present')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ same(pkt:arcount(), 0, desc .. ': ADDITIONAL is empty')
+ end
+
+ local function test_get_nxdomain()
+ local desc = 'valid GET query which ends with NXDOMAIN'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- nxdomain.test. A
+ .. 'viABAAABAAAAAAAACG54ZG9tYWluBHRlc3QAAAEAAQ')
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ same(headers:get('cache-control'), 'max-age=10800', desc .. ': TTL 10800')
+ same(pkt:rcode(), kres.rcode.NXDOMAIN, desc .. ': rcode matches')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ end
+
+ local function test_get_multiple_amps()
+ local desc = 'GET query with consecutive ampersands'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path',
+ '/doh?other=something&another=something&&&&dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB')
+ check_ok(req, desc)
+ end
+
+ local function test_get_other_params_before_dns()
+ local desc = 'GET query with other parameters before dns is valid'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path',
+ '/doh?other=something&another=something&dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB')
+ check_ok(req, desc)
+ end
+
+ local function test_get_other_params_after_dns()
+ local desc = 'GET query with other parameters after dns is valid'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path',
+ '/doh?dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB&other=something&another=something')
+ check_ok(req, desc)
+ end
+
+ local function test_get_other_params()
+ local desc = 'GET query with other parameters than dns on both sides is valid'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path',
+ '/doh?other=something&dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB&another=something')
+ check_ok(req, desc)
+ end
+
+ -- test an invalid DNS query using GET
+ local function test_get_long_input()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' .. basexx.to_url64(string.rep('\0', 1030)))
+ check_err(req, '400', 'too long GET finishes with 400')
+ end
+
+ local function test_get_no_dns_param()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?notdns=' .. basexx.to_url64(string.rep('\0', 1024)))
+ check_err(req, '400', 'GET without dns parameter finishes with 400')
+ end
+
+ local function test_get_unparseable()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh??dns=' .. basexx.to_url64(string.rep('\0', 1024)))
+ check_err(req, '400', 'unparseable GET finishes with 400')
+ end
+
+ local function test_get_invalid_b64()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=thisisnotb64')
+ check_err(req, '400', 'GET with invalid base64 finishes with 400')
+ end
+
+ local function test_get_invalid_chars()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' .. basexx.to_url64(string.rep('\0', 200)) .. '@#$%?!')
+ check_err(req, '400', 'GET with invalid characters in b64 finishes with 400')
+ end
+
+ local function test_unsupp_method()
+ local req = assert(req_templ:clone())
+ req.headers:upsert(':method', 'PUT')
+ check_err(req, '501', 'unsupported method finishes with 501')
+ end
+
+ local function test_dstaddr()
+ local triggered = false
+ local exp_dstaddr = ffi.gc(ffi.C.kr_straddr_socket(host, port, nil), ffi.C.free)
+ local function check_dstaddr(state, req)
+ triggered = true
+ same(ffi.C.kr_sockaddr_cmp(req.qsource.dst_addr, exp_dstaddr), 0,
+ 'request has correct server address')
+ return state
+ end
+ policy.add(policy.suffix(check_dstaddr, policy.todnames({'dstaddr.test'})))
+ local desc = 'valid POST query has server address available in request'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- dstaddr.test. A
+ 'FnkBAAABAAAAAAAAB2RzdGFkZHIEdGVzdAAAAQAB'))
+ check_ok(req, desc)
+ ok(triggered, 'dstaddr policy was triggered')
+ end
+
+ local function test_srcaddr()
+ modules.load('view')
+ assert(view)
+ local policy_refuse = policy.suffix(policy.REFUSE, policy.todnames({'srcaddr.test.knot-resolver.cz'}))
+ -- these netmasks would not work if the request did not contain IP addresses
+ view:addr('0.0.0.0/0', policy_refuse)
+ view:addr('::/0', policy_refuse)
+
+ local desc = 'valid POST query has source address available in request'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- srcaddr.test.knot-resolver.cz TXT
+ 'QNQBAAABAAAAAAAAB3NyY2FkZHIEdGVzdA1rbm90LXJlc29sdmVyAmN6AAAQAAE'))
+ local _, pkt = check_ok(req, desc)
+ same(pkt:rcode(), kres.rcode.REFUSED, desc .. ': view module caught it')
+
+ modules.unload('view')
+ end
+
+ local function test_headers()
+ local get_desc = nil
+
+ local function check_headers_zero_len(state, req)
+ same(tonumber(req.qsource.headers.len), 0, get_desc())
+ return state
+ end
+
+ local function check_headers_value(state, req)
+ local value = ffi.string(req.qsource.headers.at[0].value)
+ same(value, 'lua test config.doh2', get_desc())
+ return state
+ end
+
+ local function check_headers_more_values(state, req)
+ local checked = 0
+ for i = 1, tonumber(req.qsource.headers.len) do
+ local name = ffi.string(req.qsource.headers.at[i - 1].name)
+ local value = ffi.string(req.qsource.headers.at[i - 1].value)
+ if (name == 'user-agent') then
+ same(value, 'lua test config.doh2', get_desc() .. ' - user-agent')
+ checked = checked + 1
+ elseif (name == ':scheme') then
+ same(value, 'https', get_desc() .. ' - :scheme')
+ checked = checked + 1
+ end
+ end
+
+ same(checked, 2, get_desc() .. ' - two checked')
+ return state
+ end
+
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- headers.test. A
+ .. 'AAABAAABAAAAAAAAB2hlYWRlcnMEdGVzdAAAAQAB')
+ req.headers:upsert('user-agent', 'lua test config.doh2')
+ req.headers:upsert(':scheme', 'https')
+
+ get_desc = function() return 'exposed HTTP headers: no headers' end
+ rule_desc = policy.add(policy.all(check_headers_zero_len))
+ check_ok(req, get_desc())
+ get_desc = function() return 'exposed HTTP headers: empty string' end
+ net.doh_headers('')
+ check_ok(req, get_desc())
+ get_desc = function() return 'exposed HTTP headers: empty table' end
+ net.doh_headers({''})
+ check_ok(req, get_desc())
+ policy.del(rule_desc.id)
+
+ get_desc = function() return 'exposed HTTP headers: take just one string parameter' end
+ boom(net.doh_headers, {'user-agent', ':method'}, get_desc())
+ get_desc = function() return 'exposed HTTP headers: take just one table parameter' end
+ boom(net.doh_headers, {{'user-agent'}, {':method'}}, get_desc())
+
+ get_desc = function() return 'exposed HTTP headers: take one header - as string' end
+ net.doh_headers('user-agent')
+ rule_desc = policy.add(policy.all(check_headers_value))
+ check_ok(req, get_desc())
+ get_desc = function() return 'exposed HTTP headers: take one header - as table' end
+ net.doh_headers({ 'user-agent' })
+ check_ok(req, get_desc())
+ policy.del(rule_desc.id)
+
+ get_desc = function() return 'exposed HTTP headers: take more headers' end
+ net.doh_headers({ ':method', 'user-agent', ':path', ':scheme' })
+ rule_desc = policy.add(policy.all(check_headers_more_values))
+ check_ok(req, get_desc())
+ policy.del(rule_desc.id)
+
+
+ end
+
+-- not implemented
+-- local function test_post_unsupp_accept()
+-- local req = assert(req_templ:clone())
+-- req.headers:upsert(':method', 'POST')
+-- req.headers:upsert('accept', 'application/dns+json')
+-- req:set_body(string.rep('\0', 12)) -- valid message
+-- check_err(req, '406', 'unsupported Accept type finishes with 406')
+-- end
+
+ -- plan tests
+ local tests = {
+ start_server,
+ test_post_servfail,
+ test_post_noerror,
+ test_post_ignore_params_1,
+ test_post_ignore_params_2,
+ test_post_nxdomain,
+ test_huge_answer,
+ test_post_short_input,
+-- test_post_long_input, -- FIXME see the test function
+ test_post_unparseable_input,
+ test_post_unsupp_type,
+ test_get_servfail,
+ test_get_noerror,
+ test_get_nxdomain,
+ test_get_multiple_amps,
+ test_get_other_params_before_dns,
+ test_get_other_params_after_dns,
+ test_get_other_params,
+ test_get_long_input,
+ test_get_no_dns_param,
+ test_get_unparseable,
+ test_get_invalid_b64,
+ test_get_invalid_chars,
+ test_unsupp_method,
+ test_dstaddr,
+ test_srcaddr,
+ test_headers
+ }
+
+ return tests
+end
diff --git a/tests/config/lru.test.lua b/tests/config/lru.test.lua
new file mode 100644
index 0000000..324d636
--- /dev/null
+++ b/tests/config/lru.test.lua
@@ -0,0 +1,84 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+
+-- Test LRU interface
+local function test_lru()
+ local capacity = 1024
+ local lru = kres.lru(capacity)
+ local dict = {
+ "catagmatic", "prevaricator", "statoscope", "workhand", "benzamide",
+ "alluvia", "fanciful", "bladish", "Tarsius", "unfast", "appropriative",
+ "seraphically", "monkeypod", "deflectometer", "tanglesome", "zodiacal",
+ "physiologically", "economizer", "forcepslike", "betrumpet",
+ "Danization", "broadthroat", "randir", "usherette", "nephropyosis",
+ "hematocyanin", "chrysohermidin", "uncave", "mirksome", "podophyllum",
+ "siphonognathous", "indoor", "featheriness", "forwardation",
+ "archruler", "soricoid", "Dailamite", "carmoisin", "controllability",
+ "unpragmatical", "childless", "transumpt", "productive",
+ "thyreotoxicosis", "oversorrow", "disshadow", "osse", "roar",
+ "pantomnesia", "talcer", "hydrorrhoea", "Satyridae", "undetesting",
+ "smoothbored", "widower", "sivathere", "pendle", "saltation",
+ "autopelagic", "campfight", "unexplained", "Macrorhamphosus",
+ "absconsa", "counterflory", "interdependent", "triact", "reconcentration",
+ "oversharpness", "sarcoenchondroma", "superstimulate", "assessory",
+ "pseudepiscopacy", "telescopically", "ventriloque", "politicaster",
+ "Caesalpiniaceae", "inopportunity", "Helion", "uncompatible",
+ "cephaloclasia", "oversearch", "Mahayanistic", "quarterspace",
+ "bacillogenic", "hamartite", "polytheistical", "unescapableness",
+ "Pterophorus", "cradlemaking", "Hippoboscidae", "overindustrialize",
+ "perishless", "cupidity", "semilichen", "gadge", "detrimental",
+ "misencourage", "toparchia", "lurchingly", "apocatastasis"
+ }
+
+ -- Check that key insertion works
+ local inserted = 0
+ for i, word in ipairs(dict) do
+ if lru:set(word, i) then
+ if lru:get(word) == i then
+ inserted = inserted + 1
+ end
+ end
+ end
+
+ is(inserted, #dict, 'all inserted keys can be retrieved')
+
+ -- Check that using binary data as keys works
+ local badinserts = 0
+ for i, word in ipairs(dict) do
+ local word_len = #word
+ word = ffi.cast('char *', word)
+ if lru:set(word, i, word_len) then
+ if lru:get(word, word_len) ~= i then
+ badinserts = badinserts + 1
+ end
+ end
+ end
+
+ is(badinserts, 0, 'insertion works for binary keys')
+
+ -- Sanity check that non-existent keys cannot be retrieved
+ local missing = "not in lru"
+ is(lru:get(missing, #missing, false), nil, 'key that wasnt inserted cannot be retrieved')
+
+ -- Test whether key eviction works and LRU is able to insert past the capacity
+ badinserts = 0
+ for i = 0, capacity do
+ local word = dict[1] .. tostring(i)
+ if lru:set(word, i) then
+ if lru:get(word) ~= i then
+ badinserts = badinserts + 1
+ end
+ end
+ end
+
+ is(badinserts, 0, 'insertion works for more keys than LRU capacity')
+
+ -- Delete and GC
+ lru = nil -- luacheck: ignore 311
+ collectgarbage()
+ collectgarbage()
+end
+
+return {
+ test_lru,
+}
diff --git a/tests/config/meson.build b/tests/config/meson.build
new file mode 100644
index 0000000..a739222
--- /dev/null
+++ b/tests/config/meson.build
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+config_tests += [
+ ['basic', files('basic.test.lua'), ['skip_asan']],
+ ['cache', files('cache.test.lua'), ['skip_asan']],
+ ['net', files('net.test.lua'), ['config_net']],
+ ['doh2', files('doh2.test.lua')],
+ ['lru', files('lru.test.lua')],
+ ['tls', files('tls.test.lua')],
+ ['worker', files('worker.test.lua')],
+]
+
+
+run_configtest = find_program('../../scripts/test-config.sh')
+
+
+foreach config_test : config_tests
+ # additional suites
+ extra_suites = config_test.length() >= 3 ? config_test[2] : []
+
+ # environment variables for test
+ conftest_env = environment()
+ conftest_env.prepend('PATH', sbin_dir)
+ conftest_env.set('KRESD_NO_LISTEN', '1')
+ conftest_env.set('SOURCE_PATH', meson.current_source_dir())
+ conftest_env.set(
+ 'TEST_FILE', '@0@/@1@'.format(meson.source_root(), config_test[1][0]))
+
+ test(
+ 'config.' + config_test[0],
+ run_configtest,
+ args: [
+ '-c', files('test.cfg'),
+ '-n'
+ ],
+ env: conftest_env,
+ suite: [
+ 'postinstall',
+ 'config',
+ ] + extra_suites,
+ )
+endforeach
diff --git a/tests/config/net.test.lua b/tests/config/net.test.lua
new file mode 100644
index 0000000..589bf61
--- /dev/null
+++ b/tests/config/net.test.lua
@@ -0,0 +1,67 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local kr_table_len = require('kluautil').kr_table_len
+
+local function test_env_no_listen()
+ -- config tests are executed with env variable KRESD_NO_LISTEN=1
+ -- so net.list() should be an empty table
+ same(kr_table_len(net.list()), 0,
+ "env 'KRESD_NO_LISTEN=1' prevents kresd from listening")
+end
+
+local function test_freebind()
+ if require('jit').os == 'OSX' then
+ return 77 -- freebind is not supported on macOS
+ end
+ boom(net.listen, {'192.0.2.1', 50049},
+ 'net.listen() without freebind should fail')
+ -- TODO: same(kr_table_len(net.list()), 0,
+ -- "net.listen() failure does not modify output from net.list()")
+ ok(net.listen('192.0.2.1', 50049, { freebind=true }),
+ 'net.listen() with freebind succeeds')
+ local net_list = net.list()
+ -- same(list length == 2)
+ same(net_list[1].transport.protocol, 'udp',
+ 'net.listen({freebind = true}) without kind starts UDP listener')
+ same(net_list[2].transport.protocol, 'tcp',
+ 'net.listen({freebind = true}) without kind starts TCP listener')
+ same(net_list[1].transport.freebind, true,
+ 'net.listen({freebind = true}) enables FREEBIND for UDP listener')
+ same(net_list[2].transport.freebind, true,
+ 'net.listen({freebind = true}) enables FREEBIND for TCP listener')
+end
+
+local function test_proxy_allowed()
+ same(net.proxy_allowed(), {}, 'net.proxy_allowed() empty by default')
+ net.proxy_allowed('172.22.0.1')
+ same(net.proxy_allowed(), {'172.22.0.1/32'}, 'net.proxy_allowed() single IPv4 host')
+ net.proxy_allowed({'172.22.0.1'})
+ same(net.proxy_allowed(), {'172.22.0.1/32'}, 'net.proxy_allowed() single IPv4 host (as table)')
+ net.proxy_allowed('172.18.1.0/24')
+ same(net.proxy_allowed(), {'172.18.1.0/24'}, 'net.proxy_allowed() IPv4 net')
+ net.proxy_allowed({'172.22.0.1', '172.18.1.0/24'})
+ same(net.proxy_allowed(), {'172.18.1.0/24', '172.22.0.1/32'}, 'net.proxy_allowed() multiple IPv4 args as table')
+ net.proxy_allowed({})
+ same(net.proxy_allowed(), {}, 'net.proxy_allowed() clear table')
+ net.proxy_allowed({'::1'})
+ same(net.proxy_allowed(), {'::1/128'}, 'net.proxy_allowed() single IPv6 host')
+ net.proxy_allowed({'2001:db8:cafe:beef::/64'})
+ same(net.proxy_allowed(), {'2001:db8:cafe:beef::/64'}, 'net.proxy_allowed() IPv6 net')
+ net.proxy_allowed({'0.0.0.0/0', '::/0'})
+ same(net.proxy_allowed(), {'0.0.0.0/0', '::/0'}, 'net.proxy_allowed() allow all IPv4 and IPv6')
+ net.proxy_allowed({'::1'})
+ same(net.proxy_allowed(), {'::1/128'}, 'net.proxy_allowed() single IPv6 host after all (proper reset)')
+ boom(net.proxy_allowed, {'a'}, 'net.proxy_allowed() invalid string arg')
+ boom(net.proxy_allowed, {'127.0.0.'}, 'net.proxy_allowed() incomplete IPv4')
+ boom(net.proxy_allowed, {'256.0.0.0'}, 'net.proxy_allowed() invalid IPv4')
+ boom(net.proxy_allowed, {'xx::'}, 'net.proxy_allowed() invalid IPv6')
+ boom(net.proxy_allowed, {'127.0.0.1/33'}, 'net.proxy_allowed() IPv4 invalid netmask')
+ boom(net.proxy_allowed, {'127.0.0.1/-1'}, 'net.proxy_allowed() IPv4 negative netmask')
+ boom(net.proxy_allowed, {'fd::/132'}, 'net.proxy_allowed() IPv6 invalid netmask')
+ boom(net.proxy_allowed, {{'127.0.0.0/8', '::1/129'}}, 'net.proxy_allowed() single param invalid')
+end
+
+return {
+ test_env_no_listen,
+ test_freebind,
+ test_proxy_allowed,
+}
diff --git a/tests/config/tapered/.travis.yml b/tests/config/tapered/.travis.yml
new file mode 100644
index 0000000..77135af
--- /dev/null
+++ b/tests/config/tapered/.travis.yml
@@ -0,0 +1,40 @@
+language: c
+
+sudo: false
+
+env:
+ global:
+ - LUAROCKS=2.4.3
+ matrix:
+ - LUA=lua5.1
+ - LUA=lua5.2
+ - LUA=lua5.3
+ - LUA=luajit # latest stable version (2.0.4)
+ - LUA=luajit2.0 # current head of 2.0 branch
+ - LUA=luajit2.1 # current head of 2.1 branch
+
+branches:
+ only:
+ - bugfix
+ - master
+
+before_install:
+ - source .travis/setenv_lua.sh
+ - lua -v
+ - luarocks install luacov
+
+script:
+ - cd test
+ - git clone https://github.com/sstephenson/bats.git
+ - ./bats/bin/bats runner.bash
+
+after_success:
+ - luacov
+ - cp -v luacov.report.out ../
+ - cd ..
+ - bash <(curl -s https://codecov.io/bash)
+
+notifications:
+ email:
+ on_success: change
+ on_failure: always
diff --git a/tests/config/tapered/.travis/platform.sh b/tests/config/tapered/.travis/platform.sh
new file mode 100644
index 0000000..7259a7d
--- /dev/null
+++ b/tests/config/tapered/.travis/platform.sh
@@ -0,0 +1,15 @@
+if [ -z "${PLATFORM:-}" ]; then
+ PLATFORM=$TRAVIS_OS_NAME;
+fi
+
+if [ "$PLATFORM" == "osx" ]; then
+ PLATFORM="macosx";
+fi
+
+if [ -z "$PLATFORM" ]; then
+ if [ "$(uname)" == "Linux" ]; then
+ PLATFORM="linux";
+ else
+ PLATFORM="macosx";
+ fi;
+fi
diff --git a/tests/config/tapered/.travis/setenv_lua.sh b/tests/config/tapered/.travis/setenv_lua.sh
new file mode 100644
index 0000000..8d8c825
--- /dev/null
+++ b/tests/config/tapered/.travis/setenv_lua.sh
@@ -0,0 +1,3 @@
+export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/luarocks/bin
+bash .travis/setup_lua.sh
+eval `$HOME/.lua/luarocks path`
diff --git a/tests/config/tapered/.travis/setup_lua.sh b/tests/config/tapered/.travis/setup_lua.sh
new file mode 100644
index 0000000..a72b6d2
--- /dev/null
+++ b/tests/config/tapered/.travis/setup_lua.sh
@@ -0,0 +1,122 @@
+#! /bin/bash
+
+# A script for setting up environment for travis-ci testing.
+# Sets up Lua and Luarocks.
+# LUA must be "lua5.1", "lua5.2" or "luajit".
+# luajit2.0 - master v2.0
+# luajit2.1 - master v2.1
+
+set -eufo pipefail
+
+LUAJIT_VERSION="2.0.4"
+LUAJIT_BASE="LuaJIT-$LUAJIT_VERSION"
+
+source .travis/platform.sh
+
+LUA_HOME_DIR=$TRAVIS_BUILD_DIR/install/lua
+
+LR_HOME_DIR=$TRAVIS_BUILD_DIR/install/luarocks
+
+mkdir $HOME/.lua
+
+LUAJIT="no"
+
+if [ "$PLATFORM" == "macosx" ]; then
+ if [ "$LUA" == "luajit" ]; then
+ LUAJIT="yes";
+ fi
+ if [ "$LUA" == "luajit2.0" ]; then
+ LUAJIT="yes";
+ fi
+ if [ "$LUA" == "luajit2.1" ]; then
+ LUAJIT="yes";
+ fi;
+elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then
+ LUAJIT="yes";
+fi
+
+mkdir -p "$LUA_HOME_DIR"
+
+if [ "$LUAJIT" == "yes" ]; then
+
+ if [ "$LUA" == "luajit" ]; then
+ curl --location https://github.com/LuaJIT/LuaJIT/archive/v$LUAJIT_VERSION.tar.gz | tar xz;
+ else
+ git clone https://github.com/LuaJIT/LuaJIT.git $LUAJIT_BASE;
+ fi
+
+ cd $LUAJIT_BASE
+
+ if [ "$LUA" == "luajit2.1" ]; then
+ git checkout v2.1;
+ # force the INSTALL_TNAME to be luajit
+ perl -i -pe 's/INSTALL_TNAME=.+/INSTALL_TNAME= luajit/' Makefile
+ fi
+
+ make && make install PREFIX="$LUA_HOME_DIR"
+
+ ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/luajit
+ ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/lua;
+
+else
+
+ if [ "$LUA" == "lua5.1" ]; then
+ curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz
+ cd lua-5.1.5;
+ elif [ "$LUA" == "lua5.2" ]; then
+ curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz
+ cd lua-5.2.4;
+ elif [ "$LUA" == "lua5.3" ]; then
+ curl http://www.lua.org/ftp/lua-5.3.4.tar.gz | tar xz
+ cd lua-5.3.4;
+ fi
+
+ # Build Lua without backwards compatibility for testing
+ perl -i -pe 's/-DLUA_COMPAT_(ALL|5_2)//' src/Makefile
+ make $PLATFORM
+ make INSTALL_TOP="$LUA_HOME_DIR" install;
+
+ ln -s $LUA_HOME_DIR/bin/lua $HOME/.lua/lua
+ ln -s $LUA_HOME_DIR/bin/luac $HOME/.lua/luac;
+
+fi
+
+cd $TRAVIS_BUILD_DIR
+
+lua -v
+
+LUAROCKS_BASE=luarocks-$LUAROCKS
+
+curl --location http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz
+
+cd $LUAROCKS_BASE
+
+if [ "$LUA" == "luajit" ]; then
+ ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR";
+elif [ "$LUA" == "luajit2.0" ]; then
+ ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR";
+elif [ "$LUA" == "luajit2.1" ]; then
+ ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.1" --prefix="$LR_HOME_DIR";
+else
+ ./configure --with-lua="$LUA_HOME_DIR" --prefix="$LR_HOME_DIR"
+fi
+
+make build && make install
+
+ln -s $LR_HOME_DIR/bin/luarocks $HOME/.lua/luarocks
+
+cd $TRAVIS_BUILD_DIR
+
+luarocks --version
+
+rm -rf $LUAROCKS_BASE
+
+if [ "$LUAJIT" == "yes" ]; then
+ rm -rf $LUAJIT_BASE;
+elif [ "$LUA" == "lua5.1" ]; then
+ rm -rf lua-5.1.5;
+elif [ "$LUA" == "lua5.2" ]; then
+ rm -rf lua-5.2.4;
+elif [ "$LUA" == "lua5.3" ]; then
+ rm -rf lua-5.3.4;
+fi
diff --git a/tests/config/tapered/CHANGES.md b/tests/config/tapered/CHANGES.md
new file mode 100644
index 0000000..63b19b0
--- /dev/null
+++ b/tests/config/tapered/CHANGES.md
@@ -0,0 +1,70 @@
+# tapered version history
+
+## *1.0-0* (July 10, 2015)
+
++ Initial public release
+
+## *1.0-1* (July 10, 2015)
+
++ Fix rockspec: the URL for Bitbucket was wrong.
+
+## *1.1-0* (July 13, 2015)
+
++ Improve organization and coverage of tests
++ Refactor the `same` method
++ Remove `same_mt`
++ Meaningful exit statuses via `done`
++ Add CI via [drone.io][dio]
+
+[dio]: https://drone.io/bitbucket.org/telemachus/tapered/latest
+
+## *1.2.0-1* (July 19, 2015)
+
++ Clean up code using luacheck and luacov
++ Small tweaks to README and CHANGES
++ Fix version number: the previous two digit number was a mistake, based on
+ a misunderstanding of LuaRocks conventions. This is an annoying switch, but
+ better now than later. And better to do it than to live with a versioning
+ pattern I dislike.
+
+## *1.2.1-1* (December 5, 2015)
+
++ Test coverage stats are now thanks to [codecov][codecov].
++ Latest stable Lua in the 5.3 series is 5.3.2, so we test against that now.
+
+[codecov]: https://codecov.io
+
+## *2.0.0-1* (May 1, 2016)
+
++ The informational fields are now functions that return strings. This is to prevent them from violating Lua recommendations about variables such as `_VERSION`. (I've bumped the major version number since this is technically an API change, though for most users it will not require any changes on their end.)
+
+## *2.0.1-1* (May 2, 2016)
+
++ Fix a typo in the documentation.
++ Adjust the `version()` return value to show only software version, not the rockspec version as well.
+
+## *2.1.0-1* (July 21, 2016)
+
++ Update to test against Lua 5.3.3
+
+## *2.2.0-1* (February 11, 2017)
+
++ Update to test against Lua 5.3.4
++ The repo is now housed on [Github](https://github.com/telemachus/tapered)
++ CI is now provided by [Travis.ci](https://travis-ci.org/telemachus/tapered)
+
+## *2.3.0-1* (October 15, 2017)
+
++ Remove `setup()` and `teardown()`
++ Update to use luarocks 2.4.3 for testing on Travis
+
+Would you rather view the [documentation][d]?
+
+[d]: /README.md
+
+---
+
+(c) 2012-2017 Peter Aronoff. BSD 3-Clause license; see [LICENSE.md][l] for
+details.
+
+[l]: /LICENSE.md
diff --git a/tests/config/tapered/LICENSE.md b/tests/config/tapered/LICENSE.md
new file mode 100644
index 0000000..bcaacde
--- /dev/null
+++ b/tests/config/tapered/LICENSE.md
@@ -0,0 +1,27 @@
+Copyright (c) 2012-2017, Peter Aronoff All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/tests/config/tapered/README.md b/tests/config/tapered/README.md
new file mode 100644
index 0000000..bafb2e7
--- /dev/null
+++ b/tests/config/tapered/README.md
@@ -0,0 +1,116 @@
+# tapered [![Build Status](https://travis-ci.org/telemachus/tapered.svg?branch=master)](https://travis-ci.org/telemachus/tapered) [![Coverage](https://codecov.io/gh/telemachus/tapered/branch/master/graph/badge.svg)](https://codecov.io/gh/telemachus/tapered)
+
+## Synopsis
+
+Very minimal tap testing for Lua. Arguably too minimal.
+
+## Assertions
+
+The `message` parameter is always optional. Brief messages help make test output
+clearer to readers, but are not needed if the output goes straight to another
+program for parsing.
+
++ `ok(expression, [message])` Tests whether `expression` returns a truthy
+ value.
+
++ `nok(expression, [message])` Tests whether `expression` returns a falsy
+ value.
+
++ `is(actual, expected, [message])` Tests whether `actual` is equal to
+ `expected`. The test uses `==` internally.
+
++ `isnt(actual, expected, [message])` Tests whether `actual` is not equal to
+ `actual`. The test uses `~=` internally.
+
++ `same(actual, expected, [message])` Tests whether `actual` is a deep copy
+ of `expected`. The test uses an `__eq` metamethod if one is found. Useful
+ for comparing tables.
+
++ `like(string, pattern, [message])` Tests whether `string` matches the given
+ `pattern`.
+
++ `unlike(string, pattern, [message])` Tests whether `string` does not match
+ the given `pattern`.
+
++ `pass([message])` A test that always passes. Useful as a quasi-skip with a
+ message.
+
++ `fail([message])` A test that always fails. Useful as a quasi-TODO with a
+ message.
+
++ `boom(function, args, [message])` Calls `function` with `args` as
+ parameters and checks to see if an exception is raised. Passes if an
+ exception is raised; fails otherwise. (The exception is swallowed.) The
+ `args` parameter expects a table. The table can be empty but not `nil`.
+
+## Helper method
+
+A method is available to show how many tests were run. (This output
+is required for [TAP compliance][tap], which may matter in some cases.)
+
+[tap]: http://testanything.org/tap-specification.html
+
++ `done([number])` Call this function (optionally) at the end of your test file.
+ It will print out a line in the form `1..n` where `n` is the total number
+ of tests run. This secures TAP compliance when needed. The call to `done`
+ is not otherwise required. If you don't care about TAP compliance, neither does
+ the library. If you pass the optional parameter to the method, it will check
+ whether the number of tests you expected matches the number of actual tests.
+ Thus, if can function like a traditional `plan` method. However, this method
+ should always be called *last* in your tap file, unlike `plan` methods which
+ normally start the test file.
+
+ Another reason to use `done` is if you care about the exit status of the
+ tests. Many continuous integration tools rely on tests signalling success or
+ failure via their exit status. After `done` is called, the script will exit
+ with a status of 0, indicating success, if all tests passed. If some tests
+ failed, the script will exit with a status equal to the number of failed
+ tests, indicating failure. A script will also exit with an error status if
+ there is a mismatch between the actual number of tests run and the number
+ passed to `done` as a parameter.
+
+## Varia
+
+The module provides four informational functions that return strings. They
+should be self-explanatory.
+
++ `version() -- 2.3.0`
+
++ `author() -- Peter Aronoff`
+
++ `url() -- https://github.com/telemachus/tapered.git`
+
++ `license() -- BSD 3-Clause`
+
+## Credits
+
+For the `same` method I took ideas and code from [Penlight][p], [Underscore][u],
+[luassert][l], and [cwtest][cw]. I thank all the people who worked on those.
+
+Indirect inspirations include [knock][k], [Test::More][tm], and [bats][b]—not so
+much for code as for ideas about testing and simplicity.
+
+Thanks in particular to [Pierre Chapuis][pchapuis] for help with ideas and
+getting continuous integration for tapered.
+
+An anonymous email showed me that my setup and teardown methods had a logical
+flaw. As a result, I've removed those methods. I appreciate the report.
+
+All the mistakes are mine. See [version history][c] for release details.
+
+[p]: https://github.com/stevedonovan/Penlight
+[u]: https://github.com/mirven/underscore.lua
+[l]: https://github.com/Olivine-Labs/luassert
+[cw]: https://github.com/catwell/cwtest
+[k]: https://github.com/chneukirchen/knock
+[tm]: http://search.cpan.org/perldoc?Test::More
+[b]: https://github.com/sstephenson/bats
+[c]: /CHANGES.md
+[pchapuis]: https://twitter.com/pchapuis
+
+---
+
+(c) 2012-2017 Peter Aronoff. BSD 3-Clause license; see [LICENSE.md][li] for
+details.
+
+[li]: /LICENSE.md
diff --git a/tests/config/tapered/doc/changes.html b/tests/config/tapered/doc/changes.html
new file mode 100644
index 0000000..a4f7034
--- /dev/null
+++ b/tests/config/tapered/doc/changes.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="author" content="Peter Aronoff">
+<title>tapered version history</title>
+<link rel="stylesheet" href="normalize.css" media="screen,projection">
+<link rel="stylesheet" href="screen.css" media="screen,projection">
+</head>
+<body>
+<h1>tapered version history</h1>
+
+<h2><em>1.0-0</em> (July 10, 2015)</h2>
+
+<ul>
+<li>Initial public release</li>
+</ul>
+
+
+<h2><em>1.0-1</em> (July 10, 2015)</h2>
+
+<ul>
+<li>Fix rockspec: the URL for Bitbucket was wrong.</li>
+</ul>
+
+
+<h2><em>1.1-0</em> (July 13, 2015)</h2>
+
+<ul>
+<li>Improve organization and coverage of tests</li>
+<li>Refactor the <code>same</code> method</li>
+<li>Remove <code>same_mt</code></li>
+<li>Meaningful exit statuses via <code>done</code></li>
+<li>Add CI via <a href="https://drone.io/bitbucket.org/telemachus/tapered/latest">drone.io</a></li>
+</ul>
+
+
+<h2><em>1.2.0-1</em> (July 19, 2015)</h2>
+
+<ul>
+<li>Clean up code using luacheck and luacov</li>
+<li>Small tweaks to README and CHANGES</li>
+<li>Fix version number: the previous two digit number was a mistake, based on
+a misunderstanding of LuaRocks conventions. This is an annoying switch, but
+better now than later. And better to do it than to live with a versioning
+pattern I dislike.</li>
+</ul>
+
+
+<h2><em>1.2.1-1</em> (December 5, 2015)</h2>
+
+<ul>
+<li>Test coverage stats are now thanks to <a href="https://codecov.io">codecov</a>.</li>
+<li>Latest stable Lua in the 5.3 series is 5.3.2, so we test against that now.</li>
+</ul>
+
+
+<h2><em>2.0.0-1</em> (May 1, 2016)</h2>
+
+<ul>
+<li>The informational fields are now functions that return strings. This is to prevent them from violating Lua recommendations about variables such as <code>_VERSION</code>. (I&rsquo;ve bumped the major version number since this is technically an API change, though for most users it will not require any changes on their end.)</li>
+</ul>
+
+
+<h2><em>2.0.1-1</em> (May 2, 2016)</h2>
+
+<ul>
+<li>Fix a typo in the documentation.</li>
+<li>Adjust the <code>version()</code> return value to show only software version, not the rockspec version as well.</li>
+</ul>
+
+
+<h2><em>2.1.0-1</em> (July 21, 2016)</h2>
+
+<ul>
+<li>Update to test against Lua 5.3.3</li>
+</ul>
+
+
+<h2><em>2.2.0-1</em> (February 11, 2017)</h2>
+
+<ul>
+<li>Update to test against Lua 5.3.4</li>
+<li>The repo is now housed on <a href="https://github.com/telemachus/tapered">Github</a></li>
+<li>CI is now provided by <a href="https://travis-ci.org/telemachus/tapered">Travis.ci</a></li>
+</ul>
+
+
+<h2><em>2.3.0-1</em> (October 15, 2017)</h2>
+
+<ul>
+<li>Remove <code>setup()</code> and <code>teardown()</code></li>
+<li>Update to use luarocks 2.4.3 for testing on Travis</li>
+</ul>
+
+
+<p>Would you rather view the <a href="index.html">documentation</a>?</p>
+
+<hr />
+
+<p>&copy; 2012-2017 Peter Aronoff. BSD 3-Clause license; see <a href="license.html">the license</a> for
+details.</p>
+</body>
+</html>
diff --git a/tests/config/tapered/doc/index.html b/tests/config/tapered/doc/index.html
new file mode 100644
index 0000000..69d6b24
--- /dev/null
+++ b/tests/config/tapered/doc/index.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="author" content="Peter Aronoff">
+<title>tapered documentation</title>
+<link rel="stylesheet" href="normalize.css" media="screen,projection">
+<link rel="stylesheet" href="screen.css" media="screen,projection">
+</head>
+<body>
+<h1>tapered <a href="https://travis-ci.org/telemachus/tapered"><img src="https://travis-ci.org/telemachus/tapered.svg?branch=master" alt="Build Status" /></a> <a href="https://codecov.io/gh/telemachus/tapered"><img src="https://codecov.io/gh/telemachus/tapered/branch/master/graph/badge.svg" alt="Coverage" /></a></h1>
+
+<h2>Synopsis</h2>
+
+<p>Very minimal tap testing for Lua. Arguably too minimal.</p>
+
+<h2>Assertions</h2>
+
+<p>The <code>message</code> parameter is always optional. Brief messages help make test output
+clearer to readers, but are not needed if the output goes straight to another
+program for parsing.</p>
+
+<ul>
+<li><p><code>ok(expression, [message])</code> Tests whether <code>expression</code> returns a truthy
+value.</p></li>
+<li><p><code>nok(expression, [message])</code> Tests whether <code>expression</code> returns a falsy
+value.</p></li>
+<li><p><code>is(actual, expected, [message])</code> Tests whether <code>actual</code> is equal to
+<code>expected</code>. The test uses <code>==</code> internally.</p></li>
+<li><p><code>isnt(actual, expected, [message])</code> Tests whether <code>actual</code> is not equal to
+<code>actual</code>. The test uses <code>~=</code> internally.</p></li>
+<li><p><code>same(actual, expected, [message])</code> Tests whether <code>actual</code> is a deep copy
+of <code>expected</code>. The test uses an <code>__eq</code> metamethod if one is found. Useful
+for comparing tables.</p></li>
+<li><p><code>like(string, pattern, [message])</code> Tests whether <code>string</code> matches the given
+<code>pattern</code>.</p></li>
+<li><p><code>unlike(string, pattern, [message])</code> Tests whether <code>string</code> does not match
+the given <code>pattern</code>.</p></li>
+<li><p><code>pass([message])</code> A test that always passes. Useful as a quasi-skip with a
+message.</p></li>
+<li><p><code>fail([message])</code> A test that always fails. Useful as a quasi-TODO with a
+message.</p></li>
+<li><p><code>boom(function, args, [message])</code> Calls <code>function</code> with <code>args</code> as
+parameters and checks to see if an exception is raised. Passes if an
+exception is raised; fails otherwise. (The exception is swallowed.) The
+<code>args</code> parameter expects a table. The table can be empty but not <code>nil</code>.</p></li>
+</ul>
+
+
+<h2>Helper method</h2>
+
+<p>A method is available to show how many tests were run. (This output
+is required for <a href="http://testanything.org/tap-specification.html">TAP compliance</a>, which may matter in some cases.)</p>
+
+<ul>
+<li><p><code>done([number])</code> Call this function (optionally) at the end of your test file.
+It will print out a line in the form <code>1..n</code> where <code>n</code> is the total number
+of tests run. This secures TAP compliance when needed. The call to <code>done</code>
+is not otherwise required. If you don&rsquo;t care about TAP compliance, neither does
+the library. If you pass the optional parameter to the method, it will check
+whether the number of tests you expected matches the number of actual tests.
+Thus, if can function like a traditional <code>plan</code> method. However, this method
+should always be called <em>last</em> in your tap file, unlike <code>plan</code> methods which
+normally start the test file.</p>
+
+<p>Another reason to use <code>done</code> is if you care about the exit status of the
+tests. Many continuous integration tools rely on tests signalling success or
+failure via their exit status. After <code>done</code> is called, the script will exit
+with a status of 0, indicating success, if all tests passed. If some tests
+failed, the script will exit with a status equal to the number of failed
+tests, indicating failure. A script will also exit with an error status if
+there is a mismatch between the actual number of tests run and the number
+passed to <code>done</code> as a parameter.</p></li>
+</ul>
+
+
+<h2>Varia</h2>
+
+<p>The module provides four informational functions that return strings. They
+should be self-explanatory.</p>
+
+<ul>
+<li><p><code>version() -- 2.3.0</code></p></li>
+<li><p><code>author() -- Peter Aronoff</code></p></li>
+<li><p><code>url() -- https://github.com/telemachus/tapered.git</code></p></li>
+<li><p><code>license() -- BSD 3-Clause</code></p></li>
+</ul>
+
+
+<h2>Credits</h2>
+
+<p>For the <code>same</code> method I took ideas and code from <a href="https://github.com/stevedonovan/Penlight">Penlight</a>, <a href="https://github.com/mirven/underscore.lua">Underscore</a>,
+<a href="https://github.com/Olivine-Labs/luassert">luassert</a>, and <a href="https://github.com/catwell/cwtest">cwtest</a>. I thank all the people who worked on those.</p>
+
+<p>Indirect inspirations include <a href="https://github.com/chneukirchen/knock">knock</a>, <a href="http://search.cpan.org/perldoc?Test::More">Test::More</a>, and <a href="https://github.com/sstephenson/bats">bats</a>—not so
+much for code as for ideas about testing and simplicity.</p>
+
+<p>Thanks in particular to <a href="https://twitter.com/pchapuis">Pierre Chapuis</a> for help with ideas and
+getting continuous integration for tapered.</p>
+
+<p>An anonymous email showed me that my setup and teardown methods had a logical
+flaw. As a result, I&rsquo;ve removed those methods. I appreciate the report.</p>
+
+<p>All the mistakes are mine. See <a href="changes.html">version history</a> for release details.</p>
+
+<hr />
+
+<p>&copy; 2012-2017 Peter Aronoff. BSD 3-Clause license; see <a href="license.html">the license</a> for
+details.</p>
+</body>
+</html>
diff --git a/tests/config/tapered/doc/license.html b/tests/config/tapered/doc/license.html
new file mode 100644
index 0000000..7c8ae8c
--- /dev/null
+++ b/tests/config/tapered/doc/license.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="author" content="Peter Aronoff">
+<title>tapered license</title>
+<link rel="stylesheet" href="normalize.css" media="screen,projection">
+<link rel="stylesheet" href="screen.css" media="screen,projection">
+</head>
+<body>
+<p>Copyright &copy; 2012-2017, Peter Aronoff All rights reserved.</p>
+
+<p>Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:</p>
+
+<ol>
+<li><p>Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.</p></li>
+<li><p>Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.</p></li>
+<li><p>Neither the name of the copyright holder nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.</p></li>
+</ol>
+
+
+<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &ldquo;AS IS&rdquo;
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.</p>
+</body>
+</html>
diff --git a/tests/config/tapered/doc/normalize.css b/tests/config/tapered/doc/normalize.css
new file mode 100644
index 0000000..9fc7ae4
--- /dev/null
+++ b/tests/config/tapered/doc/normalize.css
@@ -0,0 +1,439 @@
+/*! normalize.css 2011-08-12T17:28 UTC · http://github.com/necolas/normalize.css */
+
+/* =============================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/*
+ * Corrects block display not defined in IE6/7/8/9 & FF3
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+
+/*
+ * Corrects inline-block display not defined in IE6/7/8/9 & FF3
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+/*
+ * Prevents modern browsers from displaying 'audio' without controls
+ */
+
+audio:not([controls]) {
+ display: none;
+}
+
+/*
+ * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4
+ * Known issue: no IE6 support
+ */
+
+[hidden] {
+ display: none;
+}
+
+
+/* =============================================================================
+ Base
+ ========================================================================== */
+
+/*
+ * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units
+ * http://clagnut.com/blog/348/#c790
+ * 2. Keeps page centred in all browsers regardless of content height
+ * 3. Prevents iOS text size adjust after orientation change, without disabling user zoom
+ * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
+ */
+
+html {
+ font-size: 100%; /* 1 */
+ overflow-y: scroll; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 3 */
+ -ms-text-size-adjust: 100%; /* 3 */
+}
+
+/*
+ * Addresses margins handled incorrectly in IE6/7
+ */
+
+body {
+ margin: 0;
+}
+
+/*
+ * Addresses font-family inconsistency between 'textarea' and other form elements.
+ */
+
+body,
+button,
+input,
+select,
+textarea {
+ font-family: sans-serif;
+}
+
+
+/* =============================================================================
+ Links
+ ========================================================================== */
+
+a {
+ color: #00e;
+}
+
+a:visited {
+ color: #551a8b;
+}
+
+/*
+ * Addresses outline displayed oddly in Chrome
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/*
+ * Improves readability when focused and also mouse hovered in all browsers
+ * people.opera.com/patrickl/experiments/keyboard/test
+ */
+
+a:hover,
+a:active {
+ outline: 0;
+}
+
+
+/* =============================================================================
+ Typography
+ ========================================================================== */
+
+/*
+ * Addresses styling not present in IE7/8/9, S5, Chrome
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/*
+ * Addresses style set to 'bolder' in FF3/4, S4/5, Chrome
+*/
+
+b,
+strong {
+ font-weight: bold;
+}
+
+blockquote {
+ margin: 1em 40px;
+}
+
+/*
+ * Addresses styling not present in S5, Chrome
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/*
+ * Addresses styling not present in IE6/7/8/9
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/*
+ * Corrects font family set oddly in IE6, S4/5, Chrome
+ * en.wikipedia.org/wiki/User:Davidgothberg/Test59
+ */
+
+pre,
+code,
+kbd,
+samp {
+ font-family: monospace, serif;
+ _font-family: 'courier new', monospace;
+ font-size: 1em;
+}
+
+/*
+ * Improves readability of pre-formatted text in all browsers
+ */
+
+pre {
+ white-space: pre;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+/*
+ * 1. Addresses CSS quotes not supported in IE6/7
+ * 2. Addresses quote property not supported in S4
+ */
+
+/* 1 */
+
+q {
+ quotes: none;
+}
+
+/* 2 */
+
+q:before,
+q:after {
+ content: '';
+ content: none;
+}
+
+small {
+ font-size: 75%;
+}
+
+/*
+ * Prevents sub and sup affecting line-height in all browsers
+ * gist.github.com/413930
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+
+/* =============================================================================
+ Lists
+ ========================================================================== */
+
+ul,
+ol {
+ margin: 1em 0;
+ padding: 0 0 0 40px;
+}
+
+dd {
+ margin: 0 0 0 40px;
+}
+
+nav ul,
+nav ol {
+ list-style: none;
+ list-style-image: none;
+}
+
+
+/* =============================================================================
+ Embedded content
+ ========================================================================== */
+
+/*
+ * 1. Removes border when inside 'a' element in IE6/7/8/9
+ * 2. Improves image quality when scaled in IE7
+ * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
+ */
+
+img {
+ border: 0; /* 1 */
+ -ms-interpolation-mode: bicubic; /* 2 */
+}
+
+/*
+ * Corrects overflow displayed oddly in IE9
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+
+/* =============================================================================
+ Figures
+ ========================================================================== */
+
+/*
+ * Addresses margin not present in IE6/7/8/9, S5, O11
+ */
+
+figure {
+ margin: 0;
+}
+
+
+/* =============================================================================
+ Forms
+ ========================================================================== */
+
+/*
+ * Corrects margin displayed oddly in IE6/7
+ */
+
+form {
+ margin: 0;
+}
+
+/*
+ * Define consistent margin and padding
+ */
+
+fieldset {
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/*
+ * 1. Corrects color not being inherited in IE6/7/8/9
+ * 2. Corrects alignment displayed oddly in IE6/7
+ */
+
+legend {
+ border: 0; /* 1 */
+ *margin-left: -7px; /* 2 */
+}
+
+/*
+ * 1. Corrects font size not being inherited in all browsers
+ * 2. Addresses margins set differently in IE6/7, F3/4, S5, Chrome
+ * 3. Improves appearance and consistency in all browsers
+ */
+
+button,
+input,
+select,
+textarea {
+ font-size: 100%; /* 1 */
+ margin: 0; /* 2 */
+ vertical-align: baseline; /* 3 */
+ *vertical-align: middle; /* 3 */
+}
+
+/*
+ * 1. Addresses FF3/4 setting line-height using !important in the UA stylesheet
+ * 2. Corrects inner spacing displayed oddly in IE6/7
+ */
+
+button,
+input {
+ line-height: normal; /* 1 */
+ *overflow: visible; /* 2 */
+}
+
+/*
+ * Corrects overlap and whitespace issue for buttons and inputs in IE6/7
+ * Known issue: reintroduces inner spacing
+ */
+
+table button,
+table input {
+ *overflow: auto;
+}
+
+/*
+ * 1. Improves usability and consistency of cursor style between image-type 'input' and others
+ * 2. Corrects inability to style clickable 'input' types in iOS
+ */
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer; /* 1 */
+ -webkit-appearance: button; /* 2 */
+}
+
+/*
+ * 1. Addresses box sizing set to content-box in IE8/9
+ * 2. Addresses excess padding in IE8/9
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/*
+ * 1. Addresses appearance set to searchfield in S5, Chrome
+ * 2. Addresses box sizing set to border-box in S5, Chrome (include -moz to future-proof)
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/*
+ * Corrects inner padding displayed oddly in S5, Chrome on OSX
+ */
+
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+ * Corrects inner padding and border displayed oddly in FF3/4
+ * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/*
+ * 1. Removes default vertical scrollbar in IE6/7/8/9
+ * 2. Improves readability and alignment in all browsers
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+
+/* =============================================================================
+ Tables
+ ========================================================================== */
+
+/*
+ * Remove most spacing between table cells
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/tests/config/tapered/doc/screen.css b/tests/config/tapered/doc/screen.css
new file mode 100644
index 0000000..2583183
--- /dev/null
+++ b/tests/config/tapered/doc/screen.css
@@ -0,0 +1,109 @@
+body {
+ margin: 10px auto;
+ width: 820px;
+ font-size: 18px;
+ font-family: "DejaVuSerifBook", "Palatino", "Palatino Linotype", serif;
+ color: #333;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-family: "DejaVuSansBook", "Lucida Grande", "Helvetica Neue", sans-serif;
+ font-weight: bold;
+ line-height: 1.6;
+ margin-bottom: 5px;
+ color: #111;
+}
+
+h1 {
+ margin-top: 10px;
+}
+
+h2, h3, h4, h5, h6 { margin-top: 20px; }
+
+h1 { font-size: 28px; }
+
+h2 { font-size: 24px; border-bottom: solid 4px; }
+h3 { font-size: 20px; }
+
+p {
+ line-height: 1.3;
+ margin: 12px auto;
+
+}
+
+code {
+ font-family: "DejaVuSansMono", Inconsolata, Consolas, Menlo, monospace;
+}
+
+blockquote {
+ margin: 10px 30px 10px 20px;
+}
+
+blockquote p {
+ line-height: 1.2;
+}
+
+ul, ol, ul p, ol p {
+ margin-left: 0;
+ line-height: 1.3;
+}
+
+ul ul, ol ol {
+ font-size: 100%;
+}
+
+ul {
+ list-style-type: disc;
+}
+
+ul ul {
+ list-style-type: circle;
+}
+
+ul ul ul {
+ list-style-type: square;
+}
+
+ul ul ul ul {
+ list-style-type: disc;
+}
+
+h2 + p {
+ margin-top: 15px;
+}
+
+a:link, a:visited {
+ text-decoration: underline;
+ color: #336891;
+}
+
+a:hover, a:focus {
+ text-decoration: none;
+ color: #336891;
+}
+
+pre {
+ margin: 30px 15px;
+ padding: 10px 5px 10px 15px;
+ border: solid black 5px;
+ border-left: solid black 30px;
+ font-size: 14px;
+ line-height: 1.3;
+ background: #FFF;
+ color: #000;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+}
+
+ins {
+ text-decoration: none;
+}
+
+ins:before {
+ content: "〈";
+}
+
+ins:after {
+ content: "〉";
+}
diff --git a/tests/config/tapered/make-doc.bash b/tests/config/tapered/make-doc.bash
new file mode 100755
index 0000000..4ceb65d
--- /dev/null
+++ b/tests/config/tapered/make-doc.bash
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+
+{
+ printf "<!DOCTYPE html>\n"
+ printf "<html lang=\"en\">\n"
+ printf "<head>\n"
+ printf "<meta charset=\"utf-8\">\n"
+ printf "<meta name=\"author\" content=\"Peter Aronoff\">\n"
+ printf "<title>tapered documentation</title>\n"
+ printf "<link rel=\"stylesheet\" href=\"normalize.css\" "
+ printf "media=\"screen,projection\">\n"
+ printf "<link rel=\"stylesheet\" href=\"screen.css\" "
+ printf "media=\"screen,projection\">\n"
+ printf "</head>\n"
+ printf "<body>\n"
+ markdown README.md | sed -e 's/\/CHANGES.md/changes.html/' -e 's/\/LICENSE.md/license.html/' -e 's/LICENSE.md/the license/'
+ printf "</body>\n"
+ printf "</html>\n"
+} > doc/index.html
+
+{
+ printf "<!DOCTYPE html>\n"
+ printf "<html lang=\"en\">\n"
+ printf "<head>\n"
+ printf "<meta charset=\"utf-8\">\n"
+ printf "<meta name=\"author\" content=\"Peter Aronoff\">\n"
+ printf "<title>tapered version history</title>\n"
+ printf "<link rel=\"stylesheet\" href=\"normalize.css\" "
+ printf "media=\"screen,projection\">\n"
+ printf "<link rel=\"stylesheet\" href=\"screen.css\" "
+ printf "media=\"screen,projection\">\n"
+ printf "</head>\n"
+ printf "<body>\n"
+ markdown CHANGES.md | sed -e 's/\/README.md/index.html/' -e 's/\/LICENSE.md/license.html/' -e 's/LICENSE.md/the license/'
+ printf "</body>\n"
+ printf "</html>\n"
+} > doc/changes.html
+
+{
+ printf "<!DOCTYPE html>\n"
+ printf "<html lang=\"en\">\n"
+ printf "<head>\n"
+ printf "<meta charset=\"utf-8\">\n"
+ printf "<meta name=\"author\" content=\"Peter Aronoff\">\n"
+ printf "<title>tapered license</title>\n"
+ printf "<link rel=\"stylesheet\" href=\"normalize.css\" "
+ printf "media=\"screen,projection\">\n"
+ printf "<link rel=\"stylesheet\" href=\"screen.css\" "
+ printf "media=\"screen,projection\">\n"
+ printf "</head>\n"
+ printf "<body>\n"
+ markdown LICENSE.md
+ printf "</body>\n"
+ printf "</html>\n"
+} > doc/license.html
diff --git a/tests/config/tapered/src/tapered.lua b/tests/config/tapered/src/tapered.lua
new file mode 100644
index 0000000..de648c2
--- /dev/null
+++ b/tests/config/tapered/src/tapered.lua
@@ -0,0 +1,205 @@
+-- vim: et:ts=2:sw=2
+-- Helper variables and functions
+local get_info = debug.getinfo
+local pcall = pcall
+local slice = string.sub
+local sprintf = string.format
+local str_find = string.find
+local tonumber = tonumber
+-- Lua 5.3 moved unpack to table.unpack
+local unpack = unpack or table.unpack
+local write = io.write
+local rawget = rawget
+local getmetatable = getmetatable
+local exit = os.exit
+local krprint = require("krprint")
+
+---- Helper methods
+
+--- C-like printf method
+local printf = function(fmt, ...)
+ write(sprintf(fmt, ...))
+end
+
+local printdiff = function(func_name, actual, expected)
+ printf("Assertion %s() failed for test below (marked 'not ok'):\n", func_name)
+ printf("Expected: %s\n", krprint.pprint(expected))
+ printf("Got: %s\n", krprint.pprint(actual))
+end
+
+--- Compare potentially complex tables or objects
+--
+-- Ideas here are taken from [Penlight][p], [Underscore][u], [cwtest][cw], and
+-- [luassert][l].
+-- [p]: https://github.com/stevedonovan/Penlight
+-- [u]: https://github.com/mirven/underscore.lua
+-- [cw]: https://github.com/catwell/cwtest
+-- [l]: https://github.com/Olivine-Labs/luassert
+--
+-- Predeclare both function names
+local keyvaluesame
+local deepsame
+--
+--- keyvaluesame(table, table) => true or false
+-- Helper method to compare all the keys and values of a table
+keyvaluesame = function (t1, t2)
+ for k1, v1 in pairs(t1) do
+ local v2 = t2[k1]
+ if v2 == nil or not deepsame(v1, v2) then return false end
+ end
+
+ -- Check for any keys present in t2 but not t1
+ for k2, _ in pairs(t2) do
+ if t1[k2] == nil then return false end
+ end
+
+ return true
+end
+--
+--- deepsame(item, item) => true or false
+-- Compare two items of any type for identity
+deepsame = function (t1, t2)
+ local ty1, ty2 = type(t1), type(t2)
+ if ty1 ~= ty2 then return false end
+ if ty1 ~= 'table' then return t1 == t2 end
+
+ -- If ._eq is found, use == and end quickly.
+ -- As of Lua 5.3 == only cares if **one** of the two items has a __eq
+ -- metamethod. Penlight, underscore and cwtest take the same approach,
+ -- so I will as well.
+ local eq = rawget(getmetatable(t1) or {}, '__eq')
+ if (type(eq) == 'function') then
+ return not not eq(t1, t2)
+ else
+ return keyvaluesame(t1, t2)
+ end
+end
+
+---- tapered test suite
+
+local exit_status = 0
+local test_count = 0
+local debug_level = 3
+
+-- All other tests are defined in terms of this primitive, which is
+-- kept private.
+local _test = function (exp, msg)
+ test_count = test_count + 1
+
+ if msg then
+ msg = sprintf(" - %s", msg)
+ else
+ msg = ''
+ end
+
+ if exp then
+ printf("ok %s%s\n", test_count, msg)
+ else
+ exit_status = 1 + exit_status
+ printf("not ok %s%s\n", test_count, msg)
+ local info = get_info(debug_level)
+ printf("# Trouble in %s around line %s\n",
+ slice(info.source, 2), info.currentline)
+ end
+end
+
+local ok = function (expression, msg)
+ _test(expression, msg)
+end
+
+local nok = function (expression, msg)
+ _test(not expression, msg)
+end
+
+local is = function (actual, expected, msg)
+ local result = actual == expected;
+ if not result then
+ printdiff("is", actual, expected)
+ end
+ _test(result, msg)
+end
+
+local isnt = function (actual, expected, msg)
+ _test(actual ~= expected, msg)
+end
+
+local same = function (actual, expected, msg)
+ local result = deepsame(actual, expected)
+ if not result then
+ printdiff("same", actual, expected)
+ end
+ _test(result, msg)
+end
+
+local like = function (str, pattern, msg)
+ _test(str_find(str, pattern), msg)
+end
+
+local unlike = function (str, pattern, msg)
+ _test(not str_find(str, pattern), msg)
+end
+
+local pass = function (msg)
+ _test(true, msg)
+end
+
+local fail = function (msg)
+ _test(false, msg)
+end
+
+local boom = function (func, args, msg)
+ local err, errmsg
+ err, errmsg = pcall(func, unpack(args))
+ _test(not err, msg)
+ if not err and type(errmsg) == 'string' then
+ printf('# Got this error: "%s"\n', errmsg)
+ end
+end
+
+local done = function (n)
+ local expected = tonumber(n)
+ if not expected or test_count == expected then
+ printf('1..%d\n', test_count)
+ elseif expected ~= test_count then
+ exit_status = 1 + exit_status
+ local s
+ if expected == 1 then s = '' else s = 's' end
+ printf("# Bad plan. You planned %d test%s but ran %d\n",
+ expected, s, test_count)
+ end
+ exit(exit_status)
+end
+
+local version = function ()
+ return "2.3.0"
+end
+
+local author = function ()
+ return "Peter Aronoff"
+end
+
+local url = function ()
+ return "https://github.com/telemachus/tapered"
+end
+
+local license = function ()
+ return "BSD 3-Clause"
+end
+
+return {
+ ok = ok,
+ nok = nok,
+ is = is,
+ isnt = isnt,
+ same = same,
+ like = like,
+ unlike = unlike,
+ pass = pass,
+ fail = fail,
+ boom = boom,
+ done = done,
+ version = version,
+ author = author,
+ url = url,
+ license = license
+}
diff --git a/tests/config/tapered/tapered-1.1-0.rockspec b/tests/config/tapered/tapered-1.1-0.rockspec
new file mode 100644
index 0000000..d6dea7d
--- /dev/null
+++ b/tests/config/tapered/tapered-1.1-0.rockspec
@@ -0,0 +1,21 @@
+package = 'tapered'
+version = '1.1-0'
+source = {
+ url = 'https://bitbucket.org/telemachus/tapered/downloads/tapered-v1.1-0.tar.gz',
+ dir = 'tapered',
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/tapered-1.2.0-1.rockspec b/tests/config/tapered/tapered-1.2.0-1.rockspec
new file mode 100644
index 0000000..5e6c364
--- /dev/null
+++ b/tests/config/tapered/tapered-1.2.0-1.rockspec
@@ -0,0 +1,21 @@
+package = 'tapered'
+version = '1.2.0-1'
+source = {
+ url = 'https://bitbucket.org/telemachus/tapered/downloads/tapered-v1.2.0-1.tar.gz',
+ dir = 'tapered',
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/tapered-1.2.1-1.rockspec b/tests/config/tapered/tapered-1.2.1-1.rockspec
new file mode 100644
index 0000000..673e1af
--- /dev/null
+++ b/tests/config/tapered/tapered-1.2.1-1.rockspec
@@ -0,0 +1,21 @@
+package = 'tapered'
+version = '1.2.1-1'
+source = {
+ url = 'https://bitbucket.org/telemachus/tapered/downloads/tapered-v1.2.1-1.tar.gz',
+ dir = 'tapered',
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/tapered-2.0.0-1.rockspec b/tests/config/tapered/tapered-2.0.0-1.rockspec
new file mode 100644
index 0000000..5e5484a
--- /dev/null
+++ b/tests/config/tapered/tapered-2.0.0-1.rockspec
@@ -0,0 +1,21 @@
+package = 'tapered'
+version = '2.0.0-1'
+source = {
+ url = 'https://bitbucket.org/telemachus/tapered/downloads/tapered-v2.0.0-1.tar.gz',
+ dir = 'tapered',
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/tapered-2.0.1-1.rockspec b/tests/config/tapered/tapered-2.0.1-1.rockspec
new file mode 100644
index 0000000..1127375
--- /dev/null
+++ b/tests/config/tapered/tapered-2.0.1-1.rockspec
@@ -0,0 +1,21 @@
+package = 'tapered'
+version = '2.0.1-1'
+source = {
+ url = 'https://bitbucket.org/telemachus/tapered/downloads/tapered-v2.0.1-1.tar.gz',
+ dir = 'tapered',
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/tapered-2.1.0-1.rockspec b/tests/config/tapered/tapered-2.1.0-1.rockspec
new file mode 100644
index 0000000..f14c756
--- /dev/null
+++ b/tests/config/tapered/tapered-2.1.0-1.rockspec
@@ -0,0 +1,21 @@
+package = 'tapered'
+version = '2.1.0-1'
+source = {
+ url = 'https://bitbucket.org/telemachus/tapered/downloads/tapered-v2.1.0-1.tar.gz',
+ dir = 'tapered',
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/tapered-2.2.0-1.rockspec b/tests/config/tapered/tapered-2.2.0-1.rockspec
new file mode 100644
index 0000000..cfb8b51
--- /dev/null
+++ b/tests/config/tapered/tapered-2.2.0-1.rockspec
@@ -0,0 +1,22 @@
+package = 'tapered'
+version = '2.2.0-1'
+source = {
+ url = 'git://github.com/telemachus/tapered.git',
+ branch = 'master',
+ tag = 'v2.2.0-1'
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/tapered-2.3.0-1.rockspec b/tests/config/tapered/tapered-2.3.0-1.rockspec
new file mode 100644
index 0000000..b3060fb
--- /dev/null
+++ b/tests/config/tapered/tapered-2.3.0-1.rockspec
@@ -0,0 +1,22 @@
+package = 'tapered'
+version = '2.3.0-1'
+source = {
+ url = 'git://github.com/telemachus/tapered.git',
+ branch = 'master',
+ tag = 'v2.3.0-1'
+}
+description = {
+ summary = 'Very minimal tap testing for Lua.',
+ detailed = [[
+ Very minimal tap testing for Lua. Arguably too minimal.
+ ]],
+ license = 'BSD 3-Clause'
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = { tapered = 'src/tapered.lua' },
+ copy_directories = { 'doc' }
+}
diff --git a/tests/config/tapered/test/.luacov b/tests/config/tapered/test/.luacov
new file mode 100644
index 0000000..7d3205e
--- /dev/null
+++ b/tests/config/tapered/test/.luacov
@@ -0,0 +1,55 @@
+--- Global configuration file. Copy, customize and store in your
+-- project folder as '.luacov' for project specific configuration.
+-- If some options are missing, their default values from this file
+-- will be used.
+-- @class module
+-- @name luacov.defaults
+return {
+
+ -- default filename to load for config options if not provided
+ -- only has effect in 'luacov.defaults.lua'
+ ["configfile"] = ".luacov",
+
+ -- filename to store stats collected
+ ["statsfile"] = "luacov.stats.out",
+
+ -- filename to store report
+ ["reportfile"] = "luacov.report.out",
+
+ -- luacov.stats file updating frequency.
+ -- The lower this value - the more frequenty results will be written out to luacov.stats
+ -- You may want to reduce this value for short lived scripts (to for example 2) to avoid losing coverage data.
+ ["savestepsize"] = 100,
+
+ -- Run reporter on completion? (won't work for ticks)
+ runreport = false,
+
+ -- Delete stats file after reporting?
+ deletestats = false,
+
+ -- Process Lua code loaded from raw strings
+ -- (that is, when the 'source' field in the debug info
+ -- does not start with '@')
+ codefromstrings = false,
+
+ -- Patterns for files to include when reporting
+ -- all will be included if nothing is listed
+ -- (exclude overrules include, do not include
+ -- the .lua extension, path separator is always '/')
+ ["include"] = { 'src/tapered' },
+
+ -- Patterns for files to exclude when reporting
+ -- all will be included if nothing is listed
+ -- (exclude overrules include, do not include
+ -- the .lua extension, path separator is always '/')
+ ["exclude"] = {
+ "luacov$",
+ "luacov/reporter$",
+ "luacov/defaults$",
+ "luacov/runner$",
+ "luacov/stats$",
+ "luacov/tick$",
+ },
+
+
+}
diff --git a/tests/config/tapered/test/boom-result.txt b/tests/config/tapered/test/boom-result.txt
new file mode 100644
index 0000000..871b509
--- /dev/null
+++ b/tests/config/tapered/test/boom-result.txt
@@ -0,0 +1,7 @@
+ok 1 - ok - return nada.__eq
+# Got this error: "boom-test.lua:13: attempt to index local 'x' (a nil value)"
+not ok 2 - not ok - Test boom with a method that throws no exception
+# Trouble in boom-test.lua around line 16
+ok 3 - ok - Test boom with a method that throws an exception "Kaboom!"
+# Got this error: "Kaboom!"
+1..3
diff --git a/tests/config/tapered/test/boom-test.lua b/tests/config/tapered/test/boom-test.lua
new file mode 100755
index 0000000..f2b585a
--- /dev/null
+++ b/tests/config/tapered/test/boom-test.lua
@@ -0,0 +1,19 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+local error = error
+
+--- boom
+-- boom(method, arguments, [msg]) tests if the function throws an exception when
+-- given the arguments supplied. The arguments parameter must be a table. It can
+-- be empty if the given method takes no arguments, but it cannot be nil.
+--
+-- Note that boom expects an exception. It passes if an exception is throw, and
+-- fails if one is not. The exception, however, will be swallowed by boom so
+-- that test execution continues.
+local broken = function (x) return x.__eq end
+local add = function (x, y) return x+y end
+tap.boom(broken, {}, 'ok - return nada.__eq')
+tap.boom(add, {2, 3}, 'not ok - Test boom with a method that throws no exception')
+tap.boom(error, {'Kaboom!'},
+ 'ok - Test boom with a method that throws an exception "Kaboom!"')
+tap.done()
diff --git a/tests/config/tapered/test/done-failure-result.txt b/tests/config/tapered/test/done-failure-result.txt
new file mode 100644
index 0000000..ce29e24
--- /dev/null
+++ b/tests/config/tapered/test/done-failure-result.txt
@@ -0,0 +1,2 @@
+ok 1
+# Bad plan. You planned 10 tests but ran 1
diff --git a/tests/config/tapered/test/done-failure-test.lua b/tests/config/tapered/test/done-failure-test.lua
new file mode 100644
index 0000000..40603d4
--- /dev/null
+++ b/tests/config/tapered/test/done-failure-test.lua
@@ -0,0 +1,5 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+tap.ok(true)
+tap.done(10)
diff --git a/tests/config/tapered/test/done-success-result.txt b/tests/config/tapered/test/done-success-result.txt
new file mode 100644
index 0000000..45541bc
--- /dev/null
+++ b/tests/config/tapered/test/done-success-result.txt
@@ -0,0 +1,2 @@
+ok 1
+1..1
diff --git a/tests/config/tapered/test/done-success-test.lua b/tests/config/tapered/test/done-success-test.lua
new file mode 100644
index 0000000..9e58ddd
--- /dev/null
+++ b/tests/config/tapered/test/done-success-test.lua
@@ -0,0 +1,5 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+tap.ok(true)
+tap.done(1)
diff --git a/tests/config/tapered/test/dynamic-setup-teardown-result.txt b/tests/config/tapered/test/dynamic-setup-teardown-result.txt
new file mode 100644
index 0000000..7130f99
--- /dev/null
+++ b/tests/config/tapered/test/dynamic-setup-teardown-result.txt
@@ -0,0 +1,10 @@
+# I'm a little teapot.
+ok 1 - setup() only with '# I'm a little teapot.'
+# This is my handle and this is my spout.
+ok 2 - setup() handle and spout, teardown() cleanup on aisle 9
+# Cleanup on aisle 9!
+# This is my handle and this is my spout.
+ok 3 - setup() again handle and spout, teardown() changed
+# I changed this.
+ok 4 - Both setup and teardown should be gone now: redefined as nil.
+1..4
diff --git a/tests/config/tapered/test/dynamic-setup-teardown-test.lua b/tests/config/tapered/test/dynamic-setup-teardown-test.lua
new file mode 100755
index 0000000..c7abb9d
--- /dev/null
+++ b/tests/config/tapered/test/dynamic-setup-teardown-test.lua
@@ -0,0 +1,27 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+-- luacheck: globals setup teardown
+function setup()
+ print("# I'm a little teapot.")
+end
+tap.ok(true, "setup() only with '# I'm a little teapot.'")
+
+function setup()
+ print('# This is my handle and this is my spout.')
+end
+function teardown()
+ print('# Cleanup on aisle 9!')
+end
+tap.ok(true, 'setup() handle and spout, teardown() cleanup on aisle 9')
+
+function teardown()
+ print('# I changed this.')
+end
+tap.ok(true, 'setup() again handle and spout, teardown() changed')
+
+setup = nil
+teardown = nil
+tap.ok(true, 'Both setup and teardown should be gone now: redefined as nil.')
+
+tap.done()
diff --git a/tests/config/tapered/test/exit-failure-test.lua b/tests/config/tapered/test/exit-failure-test.lua
new file mode 100755
index 0000000..18339c2
--- /dev/null
+++ b/tests/config/tapered/test/exit-failure-test.lua
@@ -0,0 +1,6 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+-- Test exit status with a failed test. Call `fail` and we're done!
+tap.fail('not ok - No test: just fail')
+tap.done()
diff --git a/tests/config/tapered/test/exit-success-test.lua b/tests/config/tapered/test/exit-success-test.lua
new file mode 100755
index 0000000..b588b64
--- /dev/null
+++ b/tests/config/tapered/test/exit-success-test.lua
@@ -0,0 +1,6 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+-- Testing exit status. Call `pass` and then we're out.
+tap.pass('ok - No test: just pass')
+tap.done()
diff --git a/tests/config/tapered/test/informational-fields-result.txt b/tests/config/tapered/test/informational-fields-result.txt
new file mode 100644
index 0000000..4d0ebc1
--- /dev/null
+++ b/tests/config/tapered/test/informational-fields-result.txt
@@ -0,0 +1,4 @@
+ok 1 - author() should be 'Peter Aronoff'
+ok 2 - version() should be '2.3.0'
+ok 3 - license() should be 'BSD 3-Clause'
+ok 4 - url() should be 'https://github.com/telemachus/tapered'
diff --git a/tests/config/tapered/test/informational-fields-test.lua b/tests/config/tapered/test/informational-fields-test.lua
new file mode 100755
index 0000000..35ef7d8
--- /dev/null
+++ b/tests/config/tapered/test/informational-fields-test.lua
@@ -0,0 +1,8 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+tap.is(tap.author(), 'Peter Aronoff', "author() should be 'Peter Aronoff'")
+tap.is(tap.version(), '2.3.0', "version() should be '2.3.0'")
+tap.is(tap.license(), 'BSD 3-Clause', "license() should be 'BSD 3-Clause'")
+tap.is(tap.url(), "https://github.com/telemachus/tapered",
+ "url() should be 'https://github.com/telemachus/tapered'")
diff --git a/tests/config/tapered/test/is-isnt-result.txt b/tests/config/tapered/test/is-isnt-result.txt
new file mode 100644
index 0000000..583a9a7
--- /dev/null
+++ b/tests/config/tapered/test/is-isnt-result.txt
@@ -0,0 +1,43 @@
+ok 1 - ok - is(2+1, 3)
+not ok 2 - not ok - is(2+2, 3)
+# Trouble in is-isnt-test.lua around line 8
+ok 3 - ok - is(print, print)
+not ok 4 - not ok - is(print, 3)
+# Trouble in is-isnt-test.lua around line 10
+ok 5 - ok - is("hello", "hello")
+not ok 6 - not ok - is("goodbye", "hello")
+# Trouble in is-isnt-test.lua around line 12
+ok 7 - ok - is(nil, nil)
+not ok 8 - not ok - is(nil, false)
+# Trouble in is-isnt-test.lua around line 14
+ok 9 - ok - is(false, false)
+ok 10 - ok - is(true, true)
+not ok 11 - not ok - is(true, false)
+# Trouble in is-isnt-test.lua around line 17
+not ok 12 - not ok - is(false, true)
+# Trouble in is-isnt-test.lua around line 18
+ok 13 - ok - isnt(2+2, 3)
+not ok 14 - not ok - isnt(2+2, 4)
+# Trouble in is-isnt-test.lua around line 20
+ok 15 - ok - isnt(3, print)
+ok 16 - ok - isnt(print, os.exit)
+ok 17 - ok - isnt("hello", "goodbye")
+not ok 18 - not ok - isnt("hello", "hello")
+# Trouble in is-isnt-test.lua around line 24
+not ok 19 - not ok - isnt(nil, nil)
+# Trouble in is-isnt-test.lua around line 25
+ok 20 - ok - isnt(nil, false)
+not ok 21 - not ok - isnt(false, false)
+# Trouble in is-isnt-test.lua around line 27
+not ok 22 - not ok - isnt(true, true)
+# Trouble in is-isnt-test.lua around line 28
+ok 23 - ok - isnt(true, false)
+ok 24 - ok - isnt(false, true)
+ok 25 - ok - isnt(nil, false)
+not ok 26 - not ok - isnt(false, false)
+# Trouble in is-isnt-test.lua around line 32
+not ok 27 - not ok - isnt(true, true)
+# Trouble in is-isnt-test.lua around line 33
+ok 28 - ok - isnt(true, false)
+ok 29 - ok - isnt(false, true)
+1..29
diff --git a/tests/config/tapered/test/is-isnt-test.lua b/tests/config/tapered/test/is-isnt-test.lua
new file mode 100755
index 0000000..d1c7636
--- /dev/null
+++ b/tests/config/tapered/test/is-isnt-test.lua
@@ -0,0 +1,36 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+--- is and isnt
+-- is(actual, expected, [msg]) tests if actual == expected
+-- isnt(actual, expected, [msg]) tests if actual ~= expected
+tap.is(2+1, 3, 'ok - is(2+1, 3)')
+tap.is(2+2, 3, 'not ok - is(2+2, 3)')
+tap.is(print, print, 'ok - is(print, print)')
+tap.is(print, 3, 'not ok - is(print, 3)')
+tap.is('hello', 'hello', 'ok - is("hello", "hello")')
+tap.is('goodbye', 'hello', 'not ok - is("goodbye", "hello")')
+tap.is(nil, nil, 'ok - is(nil, nil)')
+tap.is(nil, false, 'not ok - is(nil, false)')
+tap.is(false, false, 'ok - is(false, false)')
+tap.is(true, true, 'ok - is(true, true)')
+tap.is(true, false, 'not ok - is(true, false)')
+tap.is(false, true, 'not ok - is(false, true)')
+tap.isnt(2+2, 3, 'ok - isnt(2+2, 3)')
+tap.isnt(2+2, 4, 'not ok - isnt(2+2, 4)')
+tap.isnt(3, print, 'ok - isnt(3, print)')
+tap.isnt(print, os.exit, 'ok - isnt(print, os.exit)')
+tap.isnt('hello', 'goodbye', 'ok - isnt("hello", "goodbye")')
+tap.isnt('hello', 'hello', 'not ok - isnt("hello", "hello")')
+tap.isnt(nil, nil, 'not ok - isnt(nil, nil)')
+tap.isnt(nil, false, 'ok - isnt(nil, false)')
+tap.isnt(false, false, 'not ok - isnt(false, false)')
+tap.isnt(true, true, 'not ok - isnt(true, true)')
+tap.isnt(true, false, 'ok - isnt(true, false)')
+tap.isnt(false, true, 'ok - isnt(false, true)')
+tap.isnt(nil, false, 'ok - isnt(nil, false)')
+tap.isnt(false, false, 'not ok - isnt(false, false)')
+tap.isnt(true, true, 'not ok - isnt(true, true)')
+tap.isnt(true, false, 'ok - isnt(true, false)')
+tap.isnt(false, true, 'ok - isnt(false, true)')
+tap.done()
diff --git a/tests/config/tapered/test/like-unlike-result.txt b/tests/config/tapered/test/like-unlike-result.txt
new file mode 100644
index 0000000..f61aac2
--- /dev/null
+++ b/tests/config/tapered/test/like-unlike-result.txt
@@ -0,0 +1,13 @@
+ok 1 - ok - like('sat', 'sat')
+not ok 2 - not ok - like('sat', 'bbb')
+# Trouble in like-unlike-test.lua around line 8
+ok 3 - ok - unlike('sat', 'q')
+not ok 4 - not ok - unlike('q', 'q')
+# Trouble in like-unlike-test.lua around line 10
+ok 5 - ok - like(' sat', '%s+sat')
+ok 6 - ok - like('934', '%d%d%d')
+ok 7 - ok - like('934', '%d%d')
+not ok 8 - not ok - like('934', '%d%s')
+# Trouble in like-unlike-test.lua around line 14
+ok 9 - ok - unlike('934', '%d%s')
+1..9
diff --git a/tests/config/tapered/test/like-unlike-test.lua b/tests/config/tapered/test/like-unlike-test.lua
new file mode 100755
index 0000000..ab48ca7
--- /dev/null
+++ b/tests/config/tapered/test/like-unlike-test.lua
@@ -0,0 +1,16 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+--- like and unlike
+-- like(string, pattern, [msg]) tests if the pattern matches the string
+-- unlike(string, pattern, [msg]) tests if the pattern does not match the string
+tap.like('sat', 'sat', "ok - like('sat', 'sat')")
+tap.like('sat', 'bbb', "not ok - like('sat', 'bbb')")
+tap.unlike('sat', 'q', "ok - unlike('sat', 'q')")
+tap.unlike('q', 'q', "not ok - unlike('q', 'q')")
+tap.like(' sat', '%s+sat', "ok - like(' sat', '%s+sat')")
+tap.like('934', '%d%d%d', "ok - like('934', '%d%d%d')")
+tap.like('934', '%d%d', "ok - like('934', '%d%d')")
+tap.like('934', '%d%s', "not ok - like('934', '%d%s')")
+tap.unlike('934', '%d%s', "ok - unlike('934', '%d%s')")
+tap.done()
diff --git a/tests/config/tapered/test/ok-nok-result.txt b/tests/config/tapered/test/ok-nok-result.txt
new file mode 100644
index 0000000..ef45c6a
--- /dev/null
+++ b/tests/config/tapered/test/ok-nok-result.txt
@@ -0,0 +1,7 @@
+ok 1 - ok - ok(true)
+not ok 2 - not ok - ok(false)
+# Trouble in ok-nok-test.lua around line 8
+ok 3 - ok - nok(false)
+not ok 4 - not ok - nok(true)
+# Trouble in ok-nok-test.lua around line 10
+1..4
diff --git a/tests/config/tapered/test/ok-nok-test.lua b/tests/config/tapered/test/ok-nok-test.lua
new file mode 100755
index 0000000..c2a0b4d
--- /dev/null
+++ b/tests/config/tapered/test/ok-nok-test.lua
@@ -0,0 +1,11 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+--- ok and nok
+-- ok(exp, [msg]) tests if exp returns (or is) a truthy value.
+-- nok(exp, [msg]) tests if exp returns (or is) a falsey value.
+tap.ok(true, 'ok - ok(true)')
+tap.ok(false, 'not ok - ok(false)')
+tap.nok(false, 'ok - nok(false)')
+tap.nok(true, 'not ok - nok(true)')
+tap.done()
diff --git a/tests/config/tapered/test/pass-fail-result.txt b/tests/config/tapered/test/pass-fail-result.txt
new file mode 100644
index 0000000..8c29e66
--- /dev/null
+++ b/tests/config/tapered/test/pass-fail-result.txt
@@ -0,0 +1,4 @@
+ok 1 - ok - No test: just pass
+not ok 2 - not ok - No test: just fail
+# Trouble in pass-fail-test.lua around line 10
+1..2
diff --git a/tests/config/tapered/test/pass-fail-test.lua b/tests/config/tapered/test/pass-fail-test.lua
new file mode 100755
index 0000000..0f2418a
--- /dev/null
+++ b/tests/config/tapered/test/pass-fail-test.lua
@@ -0,0 +1,11 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+--- pass and fail
+-- pass([msg]) is not a test. It always passes.
+-- fail([msg]) is not a test. It always fails.
+-- These two methods are useful for checking the basic setup of a test suite.
+-- Also, pass can be used as a quasi-skip and fail as a quasi-TODO.
+tap.pass('ok - No test: just pass')
+tap.fail('not ok - No test: just fail')
+tap.done()
diff --git a/tests/config/tapered/test/result_test03.txt b/tests/config/tapered/test/result_test03.txt
new file mode 100644
index 0000000..58c066c
--- /dev/null
+++ b/tests/config/tapered/test/result_test03.txt
@@ -0,0 +1,55 @@
+ok 1 - Test ok on true
+not ok 2 - Test ok on false
+# Trouble in test03-allfuncs.lua around line 9
+ok 3 - Test nok on false
+not ok 4 - Test nok on true
+# Trouble in test03-allfuncs.lua around line 11
+ok 5 - Test is on 2 + 1 = 3
+not ok 6 - Test is on 2 + 2 = 3
+# Trouble in test03-allfuncs.lua around line 17
+ok 7 - Test isnt on 2 + 2 = 3
+not ok 8 - Test isnt on 2 + 2 = 4
+# Trouble in test03-allfuncs.lua around line 19
+ok 9 - Test same on two empty tables
+ok 10 - Test same on two identical, simple tables
+not ok 11 - Test same on two simple non-identical tables
+# Trouble in test03-allfuncs.lua around line 36
+ok 12 - Test same on two identical, nested tables
+ok 13 - Test same on identical, array-like tables
+not ok 14 - Test same on two non-identical, array-like tables
+# Trouble in test03-allfuncs.lua around line 43
+ok 15 - Test same on two identical, hash-like tables
+not ok 16 - Test same on two non-identical, hash-like tables
+# Trouble in test03-allfuncs.lua around line 65
+ok 17 - Test same on two identical tables with functions as values
+not ok 18 - Test same on two non-identical tables with functions as values
+# Trouble in test03-allfuncs.lua around line 74
+ok 19 - Test same on two dissimilar tables that share .__eq => true
+not ok 20 - Test same: first table .__eq => true, second => false
+# Trouble in test03-allfuncs.lua around line 89
+not ok 21 - Test same: first table .__eq => false, second => true
+# Trouble in test03-allfuncs.lua around line 90
+not ok 22 - Test same on two similar tables where first .__eq => true
+# Trouble in test03-allfuncs.lua around line 91
+not ok 23 - Test same on two similar tables where second .__eq => true
+# Trouble in test03-allfuncs.lua around line 92
+ok 24 - Test like with string 'sat' and pattern 'sat'
+not ok 25 - Test like with string 'sat' and pattern 'bbb'
+# Trouble in test03-allfuncs.lua around line 98
+ok 26 - Test unlike with string 'sat' and pattern 'q'
+not ok 27 - Test unlike with string 'q' and pattern 'q'
+# Trouble in test03-allfuncs.lua around line 100
+ok 28 - Test like with string ' sat' and pattern '%s+sat'
+ok 29 - Test like with string '934' and pattern '%d%d%d'
+ok 30 - Test like with string '934' and pattern '%d%d'
+ok 31 - Test unlike with string '934' and pattern '%d%s'
+ok 32 - No test: just pass
+not ok 33 - No test: just fail
+# Trouble in test03-allfuncs.lua around line 112
+not ok 34 - Test boom with a method that throws no exception
+# Trouble in test03-allfuncs.lua around line 123
+ok 35 - Test boom with a method that throws an exception "Kaboom!"
+# Got this error:
+# Kaboom!
+1..35
+# Bad plan. You planned 43 tests but ran 35
diff --git a/tests/config/tapered/test/runner.bash b/tests/config/tapered/test/runner.bash
new file mode 100755
index 0000000..f7f38fc
--- /dev/null
+++ b/tests/config/tapered/test/runner.bash
@@ -0,0 +1,71 @@
+#!/usr/bin/env bats
+
+@test "ignition!" {
+ run true
+ [ "$status" -eq 0 ]
+ [ "$output" = "" ]
+}
+
+@test "ok and nok" {
+ run lua -lluacov ok-nok-test.lua
+ [ "$status" -eq 2 ]
+ [ "$output" = "$(cat ok-nok-result.txt)" ]
+}
+
+@test "is and isnt" {
+ run lua -lluacov is-isnt-test.lua
+ [ "$status" -eq 13 ]
+ [ "$output" = "$(cat is-isnt-result.txt)" ]
+}
+
+@test "same" {
+ run lua -lluacov same-test.lua
+ [ "$status" -eq 10 ]
+ [ "$output" = "$(cat same-result.txt)" ]
+}
+
+@test "like and unlike" {
+ run lua -lluacov like-unlike-test.lua
+ [ "$status" -eq 3 ]
+ [ "$output" = "$(cat like-unlike-result.txt)" ]
+}
+
+@test "pass and fail" {
+ run lua -lluacov pass-fail-test.lua
+ [ "$status" -eq 1 ]
+ [ "$output" = "$(cat pass-fail-result.txt)" ]
+}
+
+@test "boom" {
+ run lua -lluacov boom-test.lua
+ [ "$status" -eq 1 ]
+ # This is foul, but choices are limited. Error messages for Lua 5.3
+ # are different, so I need to look at the output but NOT the errors.
+ [ "$(grep -v '^#' <<< "$output")" = "$(cat boom-result.txt | grep -v '^#')" ]
+}
+
+@test "done success" {
+ run lua -lluacov done-success-test.lua
+ [ "$status" -eq 0 ]
+ [ "$output" = "$(cat done-success-result.txt)" ]
+}
+
+@test "done failure" {
+ run lua -lluacov done-failure-test.lua
+ [ "$status" -eq 1 ]
+ [ "$output" = "$(cat done-failure-result.txt)" ]
+}
+
+@test "exit status" {
+ run lua -lluacov exit-success-test.lua
+ [ "$status" -eq 0 ]
+
+ run lua -lluacov exit-failure-test.lua
+ [ "$status" -eq 1 ]
+}
+
+@test "informational fields" {
+ run lua -lluacov informational-fields-test.lua
+ [ "$status" -eq 0 ]
+ [ "$output" = "$(cat informational-fields-result.txt)" ]
+}
diff --git a/tests/config/tapered/test/same-result.txt b/tests/config/tapered/test/same-result.txt
new file mode 100644
index 0000000..c77a5a4
--- /dev/null
+++ b/tests/config/tapered/test/same-result.txt
@@ -0,0 +1,31 @@
+ok 1 - ok - same({}, {}
+ok 2 - ok - same({1,2,3}, {1,2,3})
+not ok 3 - not ok - same({1}, {})
+# Trouble in same-test.lua around line 7
+ok 4 - ok - same({{1}, 2, {3,4}}, {{1}, 2, {3,4})
+ok 5 - ok - same({"Monday", "Tuesday"}, {"Monday", "Tuesday"})
+not ok 6 - not ok - same({"Monday", "Tuesday"}, {"Monday", "Tuesday", "Wednesday"})
+# Trouble in same-test.lua around line 15
+ok 7 - ok - same({Monday = 1}, {Monday = 1})
+not ok 8 - not ok - same({Monday = 1}, {Monday = 1, Tuesday = 2})
+# Trouble in same-test.lua around line 29
+ok 9 - ok - same({m = {1,2}, n = {1,2}}, {m = {1,2}, n = {1,2}})
+not ok 10 - not ok - same({m = {1,2}, n = {1,2}}, {m = {1,2}, n = {1,2,3}})
+# Trouble in same-test.lua around line 36
+not ok 11 - not ok - same({m = {1,2}, n = {1,2,3}}, {m = {1,2}, n = {1,2}})
+# Trouble in same-test.lua around line 37
+ok 12 - ok - same({p = print, a = assert}, {p = print, a = assert})
+not ok 13 - not ok - same({p = print, a = assert}, {p = print, a = assert, e = error})
+# Trouble in same-test.lua around line 44
+ok 14 - ok - same({4, s=4}, {6, s=4},__eq => x[s] and y[s] are even)
+ok 15 - ok - same({6, s=8}, {4, s=4},__eq => x[s] and y[s] are even)
+not ok 16 - not ok - same({4, s=4}, {4, s=3},__eq => x[s] and y[s] are even)
+# Trouble in same-test.lua around line 61
+not ok 17 - not ok - same({4, s=4}, {4, s=3},__eq => x[s] and y[s] are even)
+# Trouble in same-test.lua around line 63
+not ok 18 - not ok - same({4, s=4}, {6, s=4},__eq => x[s] is even, y[s] odd)
+# Trouble in same-test.lua around line 68
+ok 19 - ok - same({4, s=4}, {4, s=3},__eq => x[s] is even, y[s] odd)
+not ok 20 - not ok - same({4, s=3}, {4, s=4},__eq => x[s] is even, y[s] odd)
+# Trouble in same-test.lua around line 72
+1..20
diff --git a/tests/config/tapered/test/same-test.lua b/tests/config/tapered/test/same-test.lua
new file mode 100644
index 0000000..68c3574
--- /dev/null
+++ b/tests/config/tapered/test/same-test.lua
@@ -0,0 +1,74 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+-- luacheck: compat
+
+tap.same({},{}, 'ok - same({}, {}')
+tap.same({1,2,3}, {1,2,3}, 'ok - same({1,2,3}, {1,2,3})')
+tap.same({1},{}, 'not ok - same({1}, {})')
+tap.same({{1}, 2, {3,4}},{{1}, 2, {3,4}},
+ 'ok - same({{1}, 2, {3,4}}, {{1}, 2, {3,4})')
+
+local days1 = { 'Monday', 'Tuesday' }
+local days2 = { 'Monday', 'Tuesday' }
+tap.same(days1, days2, 'ok - same({"Monday", "Tuesday"}, {"Monday", "Tuesday"})')
+local days3 = { 'Monday', 'Tuesday', 'Wednesday', }
+tap.same(days2, days3,
+ 'not ok - same({"Monday", "Tuesday"}, {"Monday", "Tuesday", "Wednesday"})')
+
+local hash1 = {
+ Monday = 1,
+}
+local hash2 = {
+ Monday = 1,
+}
+local hash3 = {
+ Monday = 1,
+ Tuesday = 2,
+}
+tap.same(hash1, hash2, 'ok - same({Monday = 1}, {Monday = 1})')
+tap.same(hash1, hash3,
+ 'not ok - same({Monday = 1}, {Monday = 1, Tuesday = 2})')
+
+local n1 = { m = { 1, 2 }, n = { 1, 2 } }
+local n2 = { m = { 1, 2 }, n = { 1, 2 } }
+local n3 = { m = { 1, 2 }, n = { 1, 2, 3 } }
+tap.same(n1, n2, 'ok - same({m = {1,2}, n = {1,2}}, {m = {1,2}, n = {1,2}})')
+tap.same(n1, n3, 'not ok - same({m = {1,2}, n = {1,2}}, {m = {1,2}, n = {1,2,3}})')
+tap.same(n3, n1, 'not ok - same({m = {1,2}, n = {1,2,3}}, {m = {1,2}, n = {1,2}})')
+
+local method_table1 = { p = print, a = assert }
+local method_table2 = { p = print, a = assert }
+local method_table3 = { p = print, a = assert, e = error }
+tap.same(method_table1, method_table2,
+ 'ok - same({p = print, a = assert}, {p = print, a = assert})')
+tap.same(method_table1, method_table3,
+ 'not ok - same({p = print, a = assert}, {p = print, a = assert, e = error})')
+
+local foo = {4, s = 4}
+local bar = {6, s = 8}
+local oof = {4, s = 3}
+local mt1 = {}
+local mt2 = {}
+local evens = function (x, y) return x['s'] % 2 == 0 and y['s'] % 2 == 0 end
+local even_odd = function (x, y) return x['s'] % 2 == 0 and y['s'] % 2 ~= 0 end
+mt1.__eq = evens
+mt2.__eq = even_odd
+setmetatable(foo, mt1)
+setmetatable(bar, mt1)
+setmetatable(oof, mt1)
+tap.same(foo, bar, 'ok - same({4, s=4}, {6, s=4},__eq => x[s] and y[s] are even)')
+tap.same(bar, foo, 'ok - same({6, s=8}, {4, s=4},__eq => x[s] and y[s] are even)')
+tap.same(foo, oof,
+ 'not ok - same({4, s=4}, {4, s=3},__eq => x[s] and y[s] are even)')
+tap.same(oof, foo,
+ 'not ok - same({4, s=4}, {4, s=3},__eq => x[s] and y[s] are even)')
+setmetatable(foo, mt2)
+setmetatable(bar, mt2)
+setmetatable(oof, mt2)
+tap.same(foo, bar,
+ 'not ok - same({4, s=4}, {6, s=4},__eq => x[s] is even, y[s] odd)')
+tap.same(foo, oof,
+ 'ok - same({4, s=4}, {4, s=3},__eq => x[s] is even, y[s] odd)')
+tap.same(oof, foo,
+ 'not ok - same({4, s=3}, {4, s=4},__eq => x[s] is even, y[s] odd)')
+tap.done()
diff --git a/tests/config/tapered/test/setup-teardown-result.txt b/tests/config/tapered/test/setup-teardown-result.txt
new file mode 100644
index 0000000..c5d5668
--- /dev/null
+++ b/tests/config/tapered/test/setup-teardown-result.txt
@@ -0,0 +1,4 @@
+# I'm a little teapot.
+ok 1 - Short and stout.
+# Here is my handle, and here is my spout.
+1..1
diff --git a/tests/config/tapered/test/setup-teardown-test.lua b/tests/config/tapered/test/setup-teardown-test.lua
new file mode 100755
index 0000000..974a4aa
--- /dev/null
+++ b/tests/config/tapered/test/setup-teardown-test.lua
@@ -0,0 +1,14 @@
+package.path = '../src/?.lua;' .. package.path
+local tap = require 'tapered'
+
+-- luacheck: globals setup teardown
+function setup()
+ print("# I'm a little teapot.")
+end
+
+function teardown()
+ print("# Here is my handle, and here is my spout.")
+end
+
+tap.ok(true, "Short and stout.")
+tap.done()
diff --git a/tests/config/test.cfg b/tests/config/test.cfg
new file mode 100644
index 0000000..b45226c
--- /dev/null
+++ b/tests/config/test.cfg
@@ -0,0 +1,46 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- disable output buffering to crashes do not leave us without log
+io.stdout:setvbuf('no')
+io.stderr:setvbuf('no')
+
+-- modify path to be able to load testing modules
+package.path = package.path .. ';' .. env.SOURCE_PATH .. '/?.lua'
+
+-- export testing module in globals
+local tapered = require('tapered.src.tapered')
+for k, v in pairs(tapered) do
+ _G[k] = v
+end
+
+-- don't send priming queries etc.
+modules.unload 'detect_time_skew'
+modules.unload 'priming'
+modules.unload 'ta_signal_query'
+modules.unload 'ta_update'
+
+-- load test
+local ffi = require('ffi')
+assert(type(env.TEST_FILE) == 'string')
+log_info(ffi.C.LOG_GRP_TESTS, 'processing test file %s', env.TEST_FILE)
+local tests = dofile(env.TEST_FILE)
+
+-- run test after processed config file
+-- default config will be used and we can test it.
+assert(type(tests) == 'table',
+ string.format('file %s did not return a table of test'
+ .. ' functions, did you forget return?',
+ env.TEST_FILE))
+
+local runtest = require('test_utils').test
+worker.coroutine(function ()
+ local at_least_one_test = false
+ for idx, t in ipairs(tests) do
+ assert(type(t) == 'function',
+ string.format('test table idx %d in file %s'
+ .. ' is not a function', idx, env.TEST_FILE))
+ at_least_one_test = true
+ runtest(t)
+ end
+ assert(at_least_one_test, 'test set is empty?! a typo in function name?')
+ done()
+end)
diff --git a/tests/config/test_dns_generators.lua b/tests/config/test_dns_generators.lua
new file mode 100644
index 0000000..4a7cc0a
--- /dev/null
+++ b/tests/config/test_dns_generators.lua
@@ -0,0 +1,134 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+local kr_cach = kres.context().cache
+
+
+local charset = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'}
+
+local function gen_bytes(len)
+ local bytes = {}
+ for _ = 1,len do
+ table.insert(bytes, charset[math.random(1, #charset)])
+ end
+ local result = table.concat(bytes)
+ assert(#result == len)
+ return result
+end
+
+local function gen_ttl()
+ return math.random(0, 2^31-1)
+end
+
+local function gen_rrtype()
+ return math.random(1024, 61000)
+end
+
+local function gen_rdata(len)
+ assert(len >= 1 and len <= 65535)
+ return gen_bytes(len)
+end
+
+local function gen_label(len) -- in bytes including the length byte
+ assert(len >= 2 and len <= 64)
+ local bytes = {string.char(len - 1), gen_bytes(len - 1)}
+ return table.concat(bytes)
+end
+
+local function gen_dname()
+ local target_len -- length 2 bytes does not make sense
+ while target_len == nil or target_len == 2 do
+ target_len = math.random(1, 255)
+ end
+
+ local labels = {string.char(0)}
+ local cur_len = 1
+ while target_len > cur_len do
+ local new_len = math.random(
+ 2,
+ math.min(target_len - cur_len,
+ 64))
+ if (target_len - cur_len - new_len) == 1 then
+ -- it is a trap, single-byte label is allowed only at the end
+ -- we cannot leave room for single-byte label in the next round
+ if new_len == 64 then
+ goto continue -- we are at max label length, try again
+ end
+ new_len = new_len + 1
+ end
+ table.insert(labels, 1, gen_label(new_len))
+ cur_len = cur_len + new_len
+ ::continue::
+ end
+ assert(target_len == cur_len)
+ local dname = table.concat(labels)
+ assert(#dname >= 1 and #dname <= 255)
+ assert(string.byte(dname, #dname) == 0)
+ return dname
+end
+
+
+local function gen_rrset()
+ local rrs = {}
+ local maxsize = 300 -- RR data size in bytes per RR set, does not include owner etc.
+ local target_len = math.random(1, maxsize)
+ local cur_len = 0
+ while target_len > cur_len do
+ local new_len = math.random(1, target_len - cur_len)
+ local new_rr = gen_rdata(new_len)
+ cur_len = cur_len + #new_rr
+ table.insert(rrs, new_rr)
+ end
+ assert(target_len == cur_len)
+ return rrs, cur_len
+end
+
+
+local function add_random_rrset()
+ local owner = gen_dname()
+ local ttl = gen_ttl()
+ local rr_type = gen_rrtype()
+ local rdata_set = gen_rrset()
+
+ local kr_rrset = kres.rrset(owner, rr_type, kres.class.IN, ttl)
+ for _, rr in ipairs(rdata_set) do
+ assert(kr_rrset:add_rdata(rr, #rr))
+ end
+ assert(kr_cach:insert(kr_rrset, nil, ffi.C.KR_RANK_SECURE))
+end
+
+ffi.cdef('int usleep(uint32_t usec);') -- at least in current glibc it's always 32-bit
+
+local rr_count = 0
+local function gen_batch()
+ for _ = 1,math.random(1,10) do
+ add_random_rrset()
+ rr_count = rr_count + 1
+ if rr_count % 100 == 0 then
+ print('cache usage ', cache.stats()['usage_percent'], '%')
+ end
+ end
+ kr_cach:commit()
+ ffi.C.usleep(15) -- stop *whole process* to give better chance to GC executing
+ local delay
+ if math.random(1,4) == 1 then
+ delay = 1 -- give a chance to DNS resolving
+ else
+ delay = 0
+ end
+ event.after(delay, gen_batch)
+end
+
+return {
+ add_random_rrset=add_random_rrset,
+ gen_batch=gen_batch,
+ gen_bytes=gen_bytes,
+ gen_dname=gen_dname,
+ gen_label=gen_label,
+ gen_rdata=gen_rdata,
+ gen_rrset=gen_rrset,
+ gen_rrtype=gen_rrtype,
+ gen_ttl=gen_ttl
+}
diff --git a/tests/config/test_utils.lua b/tests/config/test_utils.lua
new file mode 100644
index 0000000..4389293
--- /dev/null
+++ b/tests/config/test_utils.lua
@@ -0,0 +1,121 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local M = {}
+
+function M.test(f, ...)
+ local res, exception = xpcall(f, debug.traceback, ...)
+ if not res then
+ io.stderr:write(string.format('%s\n', exception))
+ os.exit(2)
+ end
+ return res
+end
+
+function M.table_keys_to_lower(table)
+ local res = {}
+ for k, v in pairs(table) do
+ res[k:lower()] = v
+ end
+ return res
+end
+
+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
+ pass(message)
+ return
+ end
+ end
+ 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
+
+local function answer2table(pkt)
+ local got_answers = {}
+ local ans_rrs = pkt:rrsets(kres.section.ANSWER)
+ for i = 1, #ans_rrs do
+ rrs = ans_rrs[i]
+ for rri = 0, rrs:rdcount() - 1 do
+ local rr = ans_rrs[i]:txt_fields(rri)
+ got_answers[rr.owner] = got_answers[rr.owner] or {}
+ got_answers[rr.owner][rr.type] = got_answers[rr.owner][rr.type] or {}
+ table.insert(got_answers[rr.owner][rr.type], rr.rdata)
+ table.sort(got_answers[rr.owner][rr.type])
+ end
+ end
+ return got_answers
+end
+
+M.NODATA = -1
+-- Resolve a name and check the answer. Do *not* return until finished.
+-- expected_rdata is one string or a table of strings in presentation format
+function M.check_answer(desc, qname, qtype, expected_rcode, expected_rdata)
+ assert(type(qtype) == 'number')
+ local qtype_str = kres.tostring.type[qtype]
+ qname = string.lower(qname)
+
+ local expected_answer = {}
+ if expected_rdata ~= nil then
+ if type(expected_rdata) ~= 'table' then
+ expected_rdata = { expected_rdata }
+ end
+ if #expected_rdata > 0 then
+ table.sort(expected_rdata)
+ expected_answer = {
+ [qname] = {
+ [qtype_str] =
+ expected_rdata
+ }
+ }
+ end
+ end
+
+ local wire_rcode = expected_rcode
+ if expected_rcode == kres.rcode.NOERROR and type(expected_rdata) == 'table'
+ and #expected_rdata == 0 then
+ expected_rcode = M.NODATA
+ end
+ if expected_rcode == M.NODATA then wire_rcode = kres.rcode.NOERROR end
+
+ local done = false
+ local callback = function(pkt)
+ ok(pkt, 'answer not dropped')
+ same(pkt:rcode(), wire_rcode,
+ desc .. ': expecting answer for query ' .. qname .. ' ' .. qtype_str
+ .. ' with rcode ' .. kres.tostring.rcode[wire_rcode])
+
+ ok((pkt:ancount() > 0) == (expected_rcode == kres.rcode.NOERROR),
+ desc ..': checking number of answers for ' .. qname .. ' ' .. qtype_str)
+
+ if expected_rdata then
+ same(expected_answer, answer2table(pkt), 'ANSWER section matches')
+ end
+ done = true
+ end
+ resolve(qname, qtype, kres.class.IN, {},
+ function(...)
+ local ok, err = xpcall(callback, debug.traceback, ...)
+ if not ok then
+ fail('error in check_answer callback function')
+ io.stderr:write(string.format('%s\n', err))
+ os.exit(2)
+ end
+ end
+ )
+
+ for delay = 0.1, 5, 0.5 do -- total max 23.5s in 9 steps
+ if done then return end
+ worker.sleep(delay)
+ end
+ if not done then fail('check_answer() timed out') end
+end
+
+return M
diff --git a/tests/config/tls.test.lua b/tests/config/tls.test.lua
new file mode 100644
index 0000000..ef811bb
--- /dev/null
+++ b/tests/config/tls.test.lua
@@ -0,0 +1,29 @@
+local ffi = require('ffi')
+ffi.cdef([[ const char * gnutls_check_version (const char * req_version); ]])
+
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local function test_session_config()
+ ok(net.tls_sticket_secret(),
+ 'net.tls_sticket_secret() to trigger key regeneration')
+ if ffi.C.gnutls_check_version("3.6.3") ~= nil then
+ ok(net.tls_sticket_secret('0123456789ABCDEF0123456789ABCDEF'),
+ 'net.tls_sticket_secret with valid key')
+ end
+ boom(net.tls_sticket_secret, {{}},
+ 'net.tls_sticket_secret({}) is invalid')
+ boom(net.tls_sticket_secret, {'0123456789ABCDEF0123456789ABCDE'},
+ 'net.tls_sticket_secret with too short key')
+
+ boom(net.tls_sticket_secret_file, {},
+ 'net.tls_sticket_secret_file without filename')
+ boom(net.tls_sticket_secret_file, {{}},
+ 'net.tls_sticket_secret_file with non-string filename')
+ boom(net.tls_sticket_secret_file, {'/tmp/a_non_existent_file_REALLY_1528898130'},
+ 'net.tls_sticket_secret_file with non-existent filename')
+ boom(net.tls_sticket_secret_file, {'/dev/null'},
+ 'net.tls_sticket_secret_file with empty file')
+end
+
+return {
+ test_session_config
+}
diff --git a/tests/config/worker.test.lua b/tests/config/worker.test.lua
new file mode 100644
index 0000000..756bb5f
--- /dev/null
+++ b/tests/config/worker.test.lua
@@ -0,0 +1,65 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- check prerequisites
+if not worker.bg_worker then
+ -- skipping worker test because it doesnt support background worker
+ os.exit(77)
+else
+ -- import primitives for synchronisation
+ local monotime = require('cqueues').monotime
+
+ -- test whether sleeping works
+ local function test_worker_sleep()
+ local now = monotime()
+ ok(pcall(worker.sleep, 0.1), 'sleep works')
+ local elapsed = monotime() - now
+ ok(elapsed > 0, 'sleep takes non-zero time')
+ end
+
+ -- helper to track number of executions
+ local cv = require('cqueues.condition').new()
+ local tasks = 0
+ local function work ()
+ worker.sleep(0.1)
+ tasks = tasks - 1
+ if tasks == 0 then
+ cv:signal()
+ elseif tasks < 0 then
+ error('too many executions')
+ end
+ end
+
+ -- test whether coroutines work
+ local function test_worker_coroutine()
+ tasks = 2
+ worker.coroutine(work)
+ worker.coroutine(work)
+ -- Check if coroutines finish
+ local status = cv:wait(1)
+ same(tasks, 0, 'all coroutines finish')
+ ok(status, 'coroutines finish successfully')
+ -- Check if nesting coroutines works
+ local now = monotime()
+ tasks = 100
+ worker.coroutine(function ()
+ for _ = 1, 100 do
+ worker.coroutine(work)
+ end
+ end)
+ status = cv:wait(1)
+ local elapsed = monotime() - now
+ same(tasks, 0, 'all nested coroutines finish')
+ ok(status, 'nested coroutines finish successfully')
+ -- Test if 100 coroutines didnt execute synchronously
+ -- (the wait time would be 100 * 0.1 = 10s sleep time)
+ -- Concurrent sleep time should still be ~0.1s (added some safe margin)
+ ok(elapsed < 0.5, 'coroutines didnt block while sleeping')
+ end
+
+ -- plan tests
+ local tests = {
+ test_worker_sleep,
+ test_worker_coroutine
+ }
+
+ return tests
+end