summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2014-01-06 10:48:17 +0100
committerOndřej Surý <ondrej@sury.org>2014-01-06 10:48:17 +0100
commit5fd83cabfd04cfcf82905029a278c341d2aadb2b (patch)
tree7635e6fe801d8a7288bcb7f631fd82bb6e838a03 /tests
parent3a0c81652b9ca314b2c116993006c32ec669ec0f (diff)
downloadknot-5fd83cabfd04cfcf82905029a278c341d2aadb2b.tar.gz
New upstream version 1.4.0
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile.inc63
-rw-r--r--tests/TESTS23
-rw-r--r--tests/acl.c155
-rw-r--r--tests/base32hex.c211
-rw-r--r--tests/base64.c199
-rw-r--r--tests/conf.c132
-rw-r--r--tests/data/sample_conf59
-rw-r--r--tests/descriptor.c256
-rw-r--r--tests/dname.c178
-rw-r--r--tests/dnssec_keys.c248
-rw-r--r--tests/dnssec_nsec3.c102
-rw-r--r--tests/dnssec_sign.c153
-rw-r--r--tests/dnssec_zone_nsec.c45
-rw-r--r--tests/dthreads.c309
-rw-r--r--tests/events.c183
-rw-r--r--tests/fdset.c154
-rw-r--r--tests/hattrie.c198
-rw-r--r--tests/hhash.c173
-rw-r--r--tests/journal.c280
-rwxr-xr-xtests/resource.sh30
-rw-r--r--tests/rrl.c208
-rw-r--r--tests/rrset.c1458
-rw-r--r--tests/runtests.c1385
-rw-r--r--tests/sample_conf.h2
-rw-r--r--tests/server.c87
-rw-r--r--tests/slab.c101
-rw-r--r--tests/tap/basic.c631
-rw-r--r--tests/tap/basic.h135
-rw-r--r--tests/tap/float.c67
-rw-r--r--tests/tap/float.h42
-rw-r--r--tests/tap/macros.h88
-rw-r--r--tests/wire.c47
-rw-r--r--tests/zonedb.c117
-rw-r--r--tests/ztree.c121
34 files changed, 7640 insertions, 0 deletions
diff --git a/tests/Makefile.inc b/tests/Makefile.inc
new file mode 100644
index 0000000..5a75ad1
--- /dev/null
+++ b/tests/Makefile.inc
@@ -0,0 +1,63 @@
+# -*- mode: makefile; -*-
+check_PROGRAMS = \
+ tests/runtests \
+ tests/journal \
+ tests/slab \
+ tests/hattrie \
+ tests/hhash \
+ tests/dthreads \
+ tests/events \
+ tests/acl \
+ tests/fdset \
+ tests/base64 \
+ tests/base32hex \
+ tests/descriptor \
+ tests/server \
+ tests/conf \
+ tests/rrl \
+ tests/wire \
+ tests/dname \
+ tests/ztree \
+ tests/zonedb \
+ tests/dnssec_keys \
+ tests/dnssec_nsec3 \
+ tests/dnssec_sign \
+ tests/dnssec_zone_nsec \
+ tests/rrset
+
+check_LIBRARIES = tests/tap/libtap.a
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/tests
+
+tests_runtests_CPPFLAGS = \
+ -DSOURCE='"$(abs_top_srcdir)/tests"' \
+ -DBUILD='"$(abs_top_builddir)/tests"'
+
+tests_tap_libtap_a_CPPFLAGS = -I$(abs_top_srcdir)/tests
+tests_tap_libtap_a_SOURCES = \
+ tests/tap/basic.c tests/tap/basic.h \
+ tests/tap/float.c tests/tap/float.h \
+ tests/tap/macros.h
+
+check-compile-only: $(check_PROGRAMS)
+
+check-local: $(check_PROGRAMS)
+ cd tests && ./runtests -l $(abs_top_srcdir)/tests/TESTS
+
+LDADD = \
+ tests/tap/libtap.a \
+ src/libknotd.la src/libknots.la \
+ @LIBOBJS@
+
+tests_conf_SOURCES = tests/conf.c tests/sample_conf.h
+nodist_tests_conf_SOURCES = tests/sample_conf.c
+CLEANFILES = tests/sample_conf.c
+
+EXTRA_DIST = tests/data tests/TESTS
+
+dist_check_SCRIPTS = tests/resource.sh
+
+tests/sample_conf.c: tests/data/sample_conf
+ $(abs_top_srcdir)/tests/resource.sh $(abs_top_srcdir)/tests/data/sample_conf >$@
diff --git a/tests/TESTS b/tests/TESTS
new file mode 100644
index 0000000..c77c6c9
--- /dev/null
+++ b/tests/TESTS
@@ -0,0 +1,23 @@
+journal
+slab
+hattrie
+hhash
+dthreads
+events
+acl
+fdset
+base64
+base32hex
+descriptor
+server
+conf
+rrl
+wire
+dname
+ztree
+zonedb
+dnssec_keys
+dnssec_nsec3
+dnssec_sign
+dnssec_zone_nsec
+rrset
diff --git a/tests/acl.c b/tests/acl.c
new file mode 100644
index 0000000..832a9fe
--- /dev/null
+++ b/tests/acl.c
@@ -0,0 +1,155 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <tap/basic.h>
+
+#include "common/errcode.h"
+#include "common/sockaddr.h"
+#include "common/acl.h"
+
+
+int main(int argc, char *argv[])
+{
+ plan(19);
+
+ // 1. Create an ACL
+ acl_match_t *match = NULL;
+ acl_t *acl = acl_new();
+ ok(acl != 0, "acl: new");
+
+ // 2. Create IPv4 address
+ sockaddr_t test_v4;
+ int ret = sockaddr_set(&test_v4, AF_INET, "127.0.0.1", 12345);
+ ok(ret > 0, "acl: new IPv4 address");
+
+ // 3. Create IPv6 address
+ sockaddr_t test_v6;
+ ret = sockaddr_set(&test_v6, AF_INET6, "::1", 54321);
+ ok(ret > 0, "acl: new IPv6 address");
+
+ // 4. Create simple IPv4 rule
+ ret = acl_insert(acl, &test_v4, NULL);
+ ok(ret == KNOT_EOK, "acl: inserted IPv4 rule");
+
+ // 5. Create simple IPv6 rule
+ ret = acl_insert(acl, &test_v6, NULL);
+ ok(ret == KNOT_EOK, "acl: inserted IPv6 rule");
+
+ // 6. Create simple IPv4 'any port' rule
+ sockaddr_t test_v4a;
+ sockaddr_set(&test_v4a, AF_INET, "20.20.20.20", 0);
+ ret = acl_insert(acl, &test_v4a, NULL);
+ ok(ret == KNOT_EOK, "acl: inserted IPv4 'any port' rule");
+
+ // 7. Attempt to match unmatching address
+ sockaddr_t unmatch_v4;
+ sockaddr_set(&unmatch_v4, AF_INET, "10.10.10.10", 24424);
+ match = acl_find(acl, &unmatch_v4);
+ ok(match == NULL, "acl: matching non-existing address");
+
+ // 8. Attempt to match unmatching IPv6 address
+ sockaddr_t unmatch_v6;
+ sockaddr_set(&unmatch_v6, AF_INET6, "2001:db8::1428:57ab", 24424);
+ match = acl_find(acl, &unmatch_v6);
+ ok(match == NULL, "acl: matching non-existing IPv6 address");
+
+ // 9. Attempt to match matching address
+ match = acl_find(acl, &test_v4);
+ ok(match != NULL, "acl: matching existing address");
+
+ // 10. Attempt to match matching address
+ match = acl_find(acl, &test_v6);
+ ok(match != NULL, "acl: matching existing IPv6 address");
+
+ // 11. Attempt to match matching 'any port' address
+ sockaddr_t match_v4a;
+ sockaddr_set(&match_v4a, AF_INET, "20.20.20.20", 24424);
+ match = acl_find(acl, &match_v4a);
+ ok(match != NULL, "acl: matching existing IPv4 'any port' address");
+
+ // 12. Attempt to match matching address without matching port
+ // FIXME
+ skip("acl: matching address without matching port");
+/* sockaddr_set(&unmatch_v4, AF_INET, "127.0.0.1", 54321);
+ match = acl_find(acl, &unmatch_v4);
+ ok(match == NULL, "acl: matching address without matching port"); */
+
+ // 13. Invalid parameters
+// lives_ok({
+ acl_delete(0);
+ acl_insert(0, 0, NULL);
+ acl_find(0, 0);
+ acl_truncate(0);
+// }, "acl: won't crash with NULL parameters");
+ ok(1, "acl: won't crash with NULL parameters");
+
+ // 14. Attempt to match subnet
+ sockaddr_t match_pf4, test_pf4;
+ sockaddr_set(&match_pf4, AF_INET, "192.168.1.0", 0);
+ sockaddr_setprefix(&match_pf4, 24);
+ acl_insert(acl, &match_pf4, NULL);
+ sockaddr_set(&test_pf4, AF_INET, "192.168.1.20", 0);
+ match = acl_find(acl, &test_pf4);
+ ok(match != NULL, "acl: searching address in matching prefix /24");
+
+ // 15. Attempt to search non-matching subnet
+ sockaddr_set(&test_pf4, AF_INET, "192.168.2.20", 0);
+ match = acl_find(acl, &test_pf4);
+ ok(match == NULL, "acl: searching address in non-matching prefix /24");
+
+ // 16. Attempt to match v6 subnet
+ sockaddr_t match_pf6, test_pf6;
+ sockaddr_set(&match_pf6, AF_INET6, "2001:0DB8:0400:000e:0:0:0:AB00", 0);
+ sockaddr_setprefix(&match_pf6, 120);
+ acl_insert(acl, &match_pf6, NULL);
+ sockaddr_set(&test_pf6, AF_INET6, "2001:0DB8:0400:000e:0:0:0:AB03", 0);
+ match = acl_find(acl, &test_pf6);
+ ok(match != NULL, "acl: searching v6 address in matching prefix /120");
+
+ // 17. Attempt to search non-matching subnet
+ sockaddr_set(&test_pf6, AF_INET6, "2001:0DB8:0400:000e:0:0:0:CCCC", 0);
+ match = acl_find(acl, &test_pf6);
+ ok(match == NULL, "acl: searching v6 address in non-matching prefix /120");
+
+ // 18. Add preferred node
+ sockaddr_set(&test_pf4, AF_INET, "192.168.0.0", 0);
+ sockaddr_setprefix(&test_pf4, 16);
+ acl_insert(acl, &test_pf4, NULL);
+ sockaddr_set(&match_pf4, AF_INET, "192.168.1.20", 0);
+ void *sval = (void*)0x1234;
+ acl_insert(acl, &match_pf4, sval);
+ match = acl_find(acl, &match_pf4);
+ ok(match && match->val == sval, "acl: search for preferred node");
+
+ // 19. Scenario after truncating
+ acl_truncate(acl);
+ sockaddr_set(&test_pf6, AF_INET6, "2001:a1b0:e11e:50d1::3:300", 0);
+ acl_insert(acl, &test_pf6, NULL);
+ sockaddr_set(&test_pf4, AF_INET, "231.17.67.223", 0);
+ acl_insert(acl, &test_pf4, NULL);
+ sockaddr_set(&test_pf4, AF_INET, "82.87.48.136", 0);
+ acl_insert(acl, &test_pf4, NULL);
+ sockaddr_set(&match_pf4, AF_INET, "82.87.48.136", 12345);
+ match = acl_find(acl, &match_pf4);
+ ok(match != NULL, "acl: scenario after truncating");
+ acl_delete(&acl);
+
+ // Return
+ return 0;
+}
diff --git a/tests/base32hex.c b/tests/base32hex.c
new file mode 100644
index 0000000..0736186
--- /dev/null
+++ b/tests/base32hex.c
@@ -0,0 +1,211 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "common/errcode.h"
+#include "common/base32hex.h"
+
+#define BUF_LEN 256
+
+int main(int argc, char *argv[])
+{
+ plan(42);
+
+ int32_t ret;
+ uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN];
+ uint32_t in_len, ref_len;
+
+ // 1. test vector -> ENC -> DEC
+ strcpy((char *)in, "");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "1. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content");
+ }
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "1. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content");
+ }
+
+ // 2. test vector -> ENC -> DEC
+ strcpy((char *)in, "f");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CO======");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "2. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content");
+ }
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "2. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content");
+ }
+
+ // 3. test vector -> ENC -> DEC
+ strcpy((char *)in, "fo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNG====");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "3. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content");
+ }
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "3. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content");
+ }
+
+ // 4. test vector -> ENC -> DEC
+ strcpy((char *)in, "foo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMU===");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "4. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content");
+ }
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "4. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content");
+ }
+
+ // 5. test vector -> ENC -> DEC
+ strcpy((char *)in, "foob");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMUOG=");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "5. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content");
+ }
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "5. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content");
+ }
+
+ // 6. test vector -> ENC -> DEC
+ strcpy((char *)in, "fooba");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMUOJ1");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "6. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content");
+ }
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "6. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content");
+ }
+
+ // 7. test vector -> ENC -> DEC
+ strcpy((char *)in, "foobar");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMUOJ1E8======");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "7. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content");
+ }
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "7. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content");
+ }
+
+ // Bad paddings
+ ret = base32hex_decode((uint8_t *)"AAAAAA==", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 2");
+ ret = base32hex_decode((uint8_t *)"AAA=====", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 5");
+ ret = base32hex_decode((uint8_t *)"A======", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 7");
+ ret = base32hex_decode((uint8_t *)"=======", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 8");
+
+ // Bad data length
+ ret = base32hex_decode((uint8_t *)"A", 1, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 1");
+ ret = base32hex_decode((uint8_t *)"AA", 2, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 2");
+ ret = base32hex_decode((uint8_t *)"AAA", 3, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 3");
+ ret = base32hex_decode((uint8_t *)"AAAA", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 4");
+ ret = base32hex_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 5");
+ ret = base32hex_decode((uint8_t *)"AAAAAA", 6, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 6");
+ ret = base32hex_decode((uint8_t *)"AAAAAAA", 7, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 7");
+ ret = base32hex_decode((uint8_t *)"AAAAAAAAA", 9, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 9");
+
+ // Bad data character
+ ret = base32hex_decode((uint8_t *)"AAAAAAA$", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar");
+ ret = base32hex_decode((uint8_t *)"AAAAAAA ", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character space");
+
+ return 0;
+}
diff --git a/tests/base64.c b/tests/base64.c
new file mode 100644
index 0000000..a5e13d2
--- /dev/null
+++ b/tests/base64.c
@@ -0,0 +1,199 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "common/errcode.h"
+#include "common/base64.h"
+
+#define BUF_LEN 256
+
+int main(int argc, char *argv[])
+{
+ plan(36);
+
+ int32_t ret;
+ uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN];
+ uint32_t in_len, ref_len;
+
+ // 1. test vector -> ENC -> DEC
+ strcpy((char *)in, "");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "1. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content");
+ }
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "1. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content");
+ }
+
+ // 2. test vector -> ENC -> DEC
+ strcpy((char *)in, "f");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zg==");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "2. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content");
+ }
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "2. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content");
+ }
+
+ // 3. test vector -> ENC -> DEC
+ strcpy((char *)in, "fo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm8=");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "3. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content");
+ }
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "3. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content");
+ }
+
+ // 4. test vector -> ENC -> DEC
+ strcpy((char *)in, "foo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9v");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "4. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content");
+ }
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "4. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content");
+ }
+
+ // 5. test vector -> ENC -> DEC
+ strcpy((char *)in, "foob");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9vYg==");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "5. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content");
+ }
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "5. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content");
+ }
+
+ // 6. test vector -> ENC -> DEC
+ strcpy((char *)in, "fooba");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9vYmE=");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "6. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content");
+ }
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "6. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content");
+ }
+
+ // 7. test vector -> ENC -> DEC
+ strcpy((char *)in, "foobar");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9vYmFy");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "7. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content");
+ }
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "7. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content");
+ }
+
+ // Bad paddings
+ ret = base64_decode((uint8_t *)"A===", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 3");
+ ret = base64_decode((uint8_t *)"====", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 4");
+
+ // Bad data length
+ ret = base64_decode((uint8_t *)"A", 1, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 1");
+ ret = base64_decode((uint8_t *)"AA", 2, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 2");
+ ret = base64_decode((uint8_t *)"AAA", 3, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 3");
+ ret = base64_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 5");
+
+ // Bad data character
+ ret = base64_decode((uint8_t *)"AAA$", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad data character dollar");
+ ret = base64_decode((uint8_t *)"AAA ", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad data character space");
+
+ return 0;
+}
diff --git a/tests/conf.c b/tests/conf.c
new file mode 100644
index 0000000..b6e22be
--- /dev/null
+++ b/tests/conf.c
@@ -0,0 +1,132 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <tap/basic.h>
+
+#include "knot/conf/conf.h"
+
+/* Resources. */
+#include "tests/sample_conf.h"
+
+/*! Run all scheduled tests for given parameters.
+ */
+int main(int argc, char *argv[])
+{
+ plan(21);
+
+ // Test 1: Allocate new config
+ const char *config_fn = "rc:/sample_conf";
+ conf_t *conf = conf_new(config_fn);
+ ok(conf != 0, "config_new()");
+
+ // Test 2: Parse config
+ int ret = conf_parse_str(conf, sample_conf_rc);
+ is_int(0, ret, "parsing configuration file %s", config_fn);
+ if (ret != 0) {
+ skip_block(19, "Parse err");
+ goto skip_all;
+ }
+
+ // Test 3: Test server version (0-level depth)
+ is_string("Infinitesimal", conf->version, "server version loaded ok");
+
+ // Test 4: Test interfaces (1-level depth)
+ ok(!EMPTY_LIST(conf->ifaces), "configured interfaces exist");
+
+ // Test 5,6,7,8: Interfaces content (2-level depth)
+ struct node *n = HEAD(conf->ifaces);
+ conf_iface_t *iface = (conf_iface_t*)n;
+ is_string("10.10.1.1", iface->address, "interface0 address check");
+ is_int(53531, iface->port, "interface0 port check");
+ n = n->next;
+ iface = (conf_iface_t*)n;
+ is_string("::0", iface->address, "interface1 address check");
+ is_int(53, iface->port, "interface1 default port check");
+
+ // Test 9,10: Check server key
+ if(conf->key_count <= 0) {
+ ok(0, "TSIG key algorithm check - NO KEY FOUND");
+ ok(0, "TSIG key secret check - NO KEY FOUND");
+ } else {
+ knot_tsig_key_t *k = &((conf_key_t *)HEAD(conf->keys))->k;
+ uint8_t decoded_secret[] = { 0x5a };
+
+ ok(k->algorithm == KNOT_TSIG_ALG_HMAC_MD5,
+ "TSIG key algorithm check");
+ ok(k->secret.size == sizeof(decoded_secret)
+ && memcmp(k->secret.data, decoded_secret,
+ sizeof(decoded_secret)) == 0,
+ "TSIG key secret check");
+ }
+
+ // Test 11,12,13,14,15,16,17,18: Check logging facilities
+ ok(conf->logs_count == 4, "log facilites count check");
+ n = HEAD(conf->logs);
+ ok(!EMPTY_LIST(conf->logs), "log facilities not empty");
+
+ conf_log_t *log = (conf_log_t*)n;
+ node_t *nm = HEAD(log->map);
+ conf_log_map_t *m = (conf_log_map_t*)nm;
+ ok(log->type == LOGT_SYSLOG, "log0 is syslog");
+
+ if (EMPTY_LIST(log->map)) {
+ skip_block(5, "Empty list");
+ } else {
+ ok(m->source == LOG_ANY, "syslog first rule is ANY");
+ int mask = LOG_MASK(LOG_NOTICE)|LOG_MASK(LOG_WARNING)|LOG_MASK(LOG_ERR);
+ ok(m->prios == mask, "syslog mask is equal");
+ nm = nm->next;
+ m = (conf_log_map_t*)nm;
+ ok(m != 0, "syslog has more than 1 rule");
+ if (m == 0) {
+ skip_block(2, "No mapping");
+ } else {
+ ok(m->source == LOG_ZONE, "syslog next rule is for zone");
+ ok(m->prios == 0xff, "rule for zone is: any level");
+ }
+ }
+
+ // Test 19,20: File facility checks
+ n = n->next;
+ log = (conf_log_t*)n;
+ ok(n != 0, "log has next facility");
+ if (n == 0) {
+ skip("No mapping");
+ } else {
+ is_string("/var/log/knot/server.err", log->file, "log file matches");
+ }
+
+ // Test 21: Load key dname
+ const char *sample_str = "key0.example.net";
+ knot_dname_t *sample = knot_dname_from_str(sample_str);
+ if (conf->key_count > 0) {
+ knot_tsig_key_t *k = &((conf_key_t *)HEAD(conf->keys))->k;
+ ok(knot_dname_cmp(sample, k->name) == 0,
+ "TSIG key dname check");
+ } else {
+ ok(0, "TSIG key dname check - NO KEY FOUND");
+ }
+ knot_dname_free(&sample);
+
+skip_all:
+
+ // Deallocating config
+ conf_free(conf);
+
+ return 0;
+}
diff --git a/tests/data/sample_conf b/tests/data/sample_conf
new file mode 100644
index 0000000..2a3704f
--- /dev/null
+++ b/tests/data/sample_conf
@@ -0,0 +1,59 @@
+# configuration file will follow bird (and juniper) type of configuration file
+# i.e. curly brackets will be used;
+
+# what to do with };
+# a) ignore ; if it follows }
+
+system {
+
+ identity "I have no mouth and must scream";
+ version "Infinitesimal";
+ storage ".";
+}
+
+keys {
+ key0.example.net hmac-md5 "Wg=="; # key special for one remote
+ key1.example.net hmac-md5 "ZGFuCg=="; # implicit key for whole zone
+}
+
+remotes {
+ remote0 { address 1.2.3.4; }
+}
+
+zones {
+ example.net {
+ file "/var/lib/knot/example.net";
+ xfr-out remote0;
+ }
+}
+
+interfaces {
+ interface0 {
+ address 10.10.1.1;
+ port 53531;
+ }
+
+ interface1 {
+ address ::0;
+ # port 53;
+ }
+}
+
+log {
+ syslog {
+ any notice, warning, error;
+ zone all;
+ }
+
+ file "/var/log/knot/server.err" {
+ server error;
+ }
+
+ stderr {
+ any warning, error;
+ }
+
+ stdout {
+ any info;
+ }
+}
diff --git a/tests/descriptor.c b/tests/descriptor.c
new file mode 100644
index 0000000..1fd576b
--- /dev/null
+++ b/tests/descriptor.c
@@ -0,0 +1,256 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "common/descriptor.h"
+
+#define BUF_LEN 256
+
+int main(int argc, char *argv[])
+{
+ plan(81);
+
+ const rdata_descriptor_t *descr;
+ char name[BUF_LEN];
+ int ret;
+ uint16_t num;
+
+ // Get descriptor, type num to string:
+ // 1. TYPE0
+ descr = get_rdata_descriptor(0);
+ ok(descr->type_name == 0, "get TYPE0 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE0 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE0 descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(0, name, BUF_LEN);
+ ok(ret != -1, "get TYPE0 ret");
+ ok(strcmp(name, "TYPE0") == 0, "get TYPE0 name");
+
+ // 2. A
+ descr = get_rdata_descriptor(1);
+ ok(strcmp(descr->type_name, "A") == 0, "get A descriptor name");
+ ok(descr->block_types[0] == 4,
+ "get A descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get A descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(1, name, BUF_LEN);
+ ok(ret != -1, "get A ret");
+ ok(strcmp(name, "A") == 0, "get A name");
+
+ // 3. CNAME
+ descr = get_rdata_descriptor(5);
+ ok(strcmp(descr->type_name, "CNAME") == 0, "get CNAME descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_COMPRESSED_DNAME,
+ "get CNAME descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get CNAME descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(5, name, BUF_LEN);
+ ok(ret != -1, "get CNAME ret");
+ ok(strcmp(name, "CNAME") == 0, "get CNAME name");
+
+ // 4. TYPE38 (A6)
+ descr = get_rdata_descriptor(38);
+ ok(descr->type_name == 0, "get TYPE38 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE38 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE38 descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(38, name, BUF_LEN);
+ ok(ret != -1, "get TYPE38 ret");
+ ok(strcmp(name, "TYPE38") == 0, "get TYPE38 name");
+
+ // 5. ANY
+ descr = get_rdata_descriptor(255);
+ ok(strcmp(descr->type_name, "ANY") == 0, "get ANY descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get ANY descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get ANY descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(255, name, BUF_LEN);
+ ok(ret != -1, "get ANY ret");
+ ok(strcmp(name, "ANY") == 0, "get ANY name");
+
+ // 6. TYPE256
+ descr = get_rdata_descriptor(256);
+ ok(descr->type_name == 0, "get TYPE256 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE256 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE256 descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(256, name, BUF_LEN);
+ ok(ret != -1, "get TYPE256 ret");
+ ok(strcmp(name, "TYPE256") == 0, "get TYPE256 name");
+
+
+ // Class num to string:
+ // 7. CLASS0
+ ret = knot_rrclass_to_string(0, name, BUF_LEN);
+ ok(ret != -1, "get CLASS0 ret");
+ ok(strcmp(name, "CLASS0") == 0, "get CLASS0 name");
+
+ // 8. IN
+ ret = knot_rrclass_to_string(1, name, BUF_LEN);
+ ok(ret != -1, "get IN ret");
+ ok(strcmp(name, "IN") == 0, "get IN name");
+
+ // 9. ANY
+ ret = knot_rrclass_to_string(255, name, BUF_LEN);
+ ok(ret != -1, "get ANY ret");
+ ok(strcmp(name, "ANY") == 0, "get ANY name");
+
+ // 10. CLASS65535
+ ret = knot_rrclass_to_string(65535, name, BUF_LEN);
+ ok(ret != -1, "get CLASS65535 ret");
+ ok(strcmp(name, "CLASS65535") == 0, "get CLASS65535 name");
+
+ // String to type num:
+ // 11. A
+ ret = knot_rrtype_from_string("A", &num);
+ ok(ret != -1, "get A num ret");
+ ok(num == 1, "get A num");
+
+ // 12. a
+ ret = knot_rrtype_from_string("a", &num);
+ ok(ret != -1, "get a num ret");
+ ok(num == 1, "get a num");
+
+ // 13. AaAa
+ ret = knot_rrtype_from_string("AaAa", &num);
+ ok(ret != -1, "get AaAa num ret");
+ ok(num == 28, "get AaAa num");
+
+ // 14. ""
+ ret = knot_rrtype_from_string("", &num);
+ ok(ret == -1, "get "" num ret");
+
+ // 15. DUMMY
+ ret = knot_rrtype_from_string("DUMMY", &num);
+ ok(ret == -1, "get DUMMY num ret");
+
+ // 16. TypE33
+ ret = knot_rrtype_from_string("TypE33", &num);
+ ok(ret != -1, "get TypE33 num ret");
+ ok(num == 33, "get TypE33 num");
+
+ // 17. TYPE
+ ret = knot_rrtype_from_string("TYPE", &num);
+ ok(ret == -1, "get TYPE num ret");
+
+ // 18. TYPE0
+ ret = knot_rrtype_from_string("TYPE0", &num);
+ ok(ret != -1, "get TYPE0 num ret");
+ ok(num == 0, "get TYPE0 num");
+
+ // 19. TYPE65535
+ ret = knot_rrtype_from_string("TYPE65535", &num);
+ ok(ret != -1, "get TYPE65535 num ret");
+ ok(num == 65535, "get TYPE65535 num");
+
+ // 20. TYPE65536
+ ret = knot_rrtype_from_string("TYPE65536", &num);
+ ok(ret == -1, "get TYPE65536 num ret");
+
+ // String to class num:
+ // 21. In
+ ret = knot_rrclass_from_string("In", &num);
+ ok(ret != -1, "get In num ret");
+ ok(num == 1, "get In num");
+
+ // 22. ANY
+ ret = knot_rrclass_from_string("ANY", &num);
+ ok(ret != -1, "get ANY num ret");
+ ok(num == 255, "get ANY num");
+
+ // 23. ""
+ ret = knot_rrclass_from_string("", &num);
+ ok(ret == -1, "get "" num ret");
+
+ // 24. DUMMY
+ ret = knot_rrclass_from_string("DUMMY", &num);
+ ok(ret == -1, "get DUMMY num ret");
+
+ // 25. CLass33
+ ret = knot_rrclass_from_string("CLass33", &num);
+ ok(ret != -1, "get CLass33 num ret");
+ ok(num == 33, "get CLass33 num");
+
+ // 26. CLASS
+ ret = knot_rrclass_from_string("CLASS", &num);
+ ok(ret == -1, "get CLASS num ret");
+
+ // 27. CLASS0
+ ret = knot_rrclass_from_string("CLASS0", &num);
+ ok(ret != -1, "get CLASS0 num ret");
+ ok(num == 0, "get CLASS0 num");
+
+ // 28. CLASS65535
+ ret = knot_rrclass_from_string("CLASS65535", &num);
+ ok(ret != -1, "get CLASS65535 num ret");
+ ok(num == 65535, "get CLASS65535 num");
+
+ // 29. CLASS65536
+ ret = knot_rrclass_from_string("CLASS65536", &num);
+ ok(ret == -1, "get CLASS65536 num ret");
+
+ // Get obsolete descriptor:
+ // 30. TYPE0
+ descr = get_obsolete_rdata_descriptor(0);
+ ok(descr->type_name == 0, "get TYPE0 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE0 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE0 descriptor 2. item type");
+
+ // 31. MD
+ descr = get_obsolete_rdata_descriptor(3);
+ ok(strcmp(descr->type_name, "MD") == 0, "get MD descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_COMPRESSED_DNAME,
+ "get A descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get A descriptor 2. item type");
+
+ // 32. NXT
+ descr = get_obsolete_rdata_descriptor(30);
+ ok(strcmp(descr->type_name, "NXT") == 0, "get NXT descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_COMPRESSED_DNAME,
+ "get CNAME descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_REMAINDER,
+ "get CNAME descriptor 2. item type");
+ ok(descr->block_types[2] == KNOT_RDATA_WF_END,
+ "get CNAME descriptor 3. item type");
+
+ // 33. TYPE38 (A6)
+ descr = get_obsolete_rdata_descriptor(38);
+ ok(descr->type_name == 0, "get TYPE38 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE38 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE38 descriptor 2. item type");
+
+ return 0;
+}
diff --git a/tests/dname.c b/tests/dname.c
new file mode 100644
index 0000000..24bea65
--- /dev/null
+++ b/tests/dname.c
@@ -0,0 +1,178 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <tap/basic.h>
+
+#include "libknot/dname.h"
+
+/* Test dname_parse_from_wire */
+static int test_fw(size_t l, const char *w) {
+ const uint8_t *np = (const uint8_t *)w + l;
+ return knot_dname_wire_check((const uint8_t *)w, np, NULL) > 0;
+}
+
+int main(int argc, char *argv[])
+{
+ plan(29);
+
+ knot_dname_t *d = NULL, *d2 = NULL;
+ const char *w = NULL, *t = NULL;
+ unsigned len = 0;
+ size_t pos = 0;
+
+ /* 1. NULL wire */
+ ok(!test_fw(0, NULL), "parsing NULL dname");
+
+ /* 2. empty label */
+ ok(test_fw(1, ""), "parsing empty dname");
+
+ /* 3. incomplete dname */
+ ok(!test_fw(5, "\x08""dddd"), "parsing incomplete wire");
+
+ /* 4. non-fqdn */
+ ok(!test_fw(3, "\x02""ab"), "parsing non-fqdn name");
+
+ /* 5. label > 63b */
+ w = "\x40""dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
+ ok(!test_fw(65, w), "parsing label > 63b");
+
+ /* 6. label count == 126 */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+ ok(test_fw(253, w), "parsing label count == 127");
+
+ /* 7. label count == 127 */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+ ok(test_fw(255, w), "parsing label count == 127");
+
+ /* 8. label count > 127 */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+ ok(!test_fw(257, w), "parsing label count > 127");
+
+ /* 9. dname length > 255 */
+ w = "\xff""ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
+ ok(!test_fw(257, w), "parsing dname len > 255");
+
+ /* 10. special case - invalid label */
+ w = "\x20\x68\x6d\x6e\x63\x62\x67\x61\x61\x61\x61\x65\x72\x6b\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x67\x6e\x69\x64\x68\x62\x61\x61\x61\x61\x65\x6c\x64\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x61\x63\x6f\x63\x64\x62\x61\x61\x61\x61\x65\x6b\x72\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x69\x62\x63\x6d\x6a\x6f\x61\x61\x61\x61\x65\x72\x6a\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x6f\x6c\x6e\x6c\x67\x68\x61\x61\x61\x61\x65\x73\x72\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x6a\x6b\x64\x66\x66\x67\x61\x61\x61\x61\x65\x6c\x68\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x67\x67\x6c\x70\x70\x61\x61\x61\x61\x61\x65\x73\x72\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x65\x6b\x6c\x67\x70\x66\x61\x61\x61\x61\x65\x6c\x68\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x0\x21\x42\x63\x84\xa5\xc6\xe7\x8\xa\xd\x11\x73\x3\x6e\x69\x63\x2\x43\x5a";
+ ok(!test_fw(277, w), "parsing invalid label (spec. case 1)");
+
+ /* 11. parse from string (correct) .*/
+ len = 10;
+ w = "\x04""abcd""\x03""efg";
+ t = "abcd.efg";
+ d = knot_dname_from_str(t);
+ ok(d && knot_dname_size(d) == len && memcmp(d, w, len) == 0,
+ "dname_fromstr: parsed correct non-FQDN name");
+ knot_dname_free(&d);
+
+ /* 12. parse FQDN from string (correct) .*/
+ t = "abcd.efg.";
+ d = knot_dname_from_str(t);
+ ok(d && knot_dname_size(d) == len && memcmp(d, w, len) == 0,
+ "dname_fromstr: parsed correct FQDN name");
+ knot_dname_free(&d);
+
+ /* 13. parse name from string (incorrect) .*/
+ t = "..";
+ d = knot_dname_from_str(t);
+ ok(d == NULL, "dname_fromstr: parsed incorrect name");
+
+ /* 14. equal name is subdomain */
+ t = "ab.cd.ef";
+ d2 = knot_dname_from_str(t);
+ t = "ab.cd.ef";
+ d = knot_dname_from_str(t);
+ ok(!knot_dname_is_sub(d, d2), "dname_subdomain: equal name");
+ knot_dname_free(&d);
+
+ /* 15. true subdomain */
+ t = "0.ab.cd.ef";
+ d = knot_dname_from_str(t);
+ ok(knot_dname_is_sub(d, d2), "dname_subdomain: true subdomain");
+ knot_dname_free(&d);
+
+ /* 16. not subdomain */
+ t = "cd.ef";
+ d = knot_dname_from_str(t);
+ ok(!knot_dname_is_sub(d, d2), "dname_subdomain: not subdomain");
+ knot_dname_free(&d);
+
+ /* 17. root subdomain */
+ t = ".";
+ d = knot_dname_from_str(t);
+ ok(knot_dname_is_sub(d2, d), "dname_subdomain: root subdomain");
+ knot_dname_free(&d);
+ knot_dname_free(&d2);
+
+ /* 18-19. dname cat (valid) */
+ w = "\x03""cat";
+ len = 5;
+ d = knot_dname_copy((const uint8_t *)w);
+ t = "*";
+ d2 = knot_dname_from_str(t);
+ d2 = knot_dname_cat(d2, d);
+ t = "\x01""*""\x03""cat";
+ len = 2 + 4 + 1;
+ ok (d2 && len == knot_dname_size(d2), "dname_cat: valid concatenation size");
+ ok(memcmp(d2, t, len) == 0, "dname_cat: valid concatenation");
+ knot_dname_free(&d);
+ knot_dname_free(&d2);
+
+ /* 20-21. parse from wire (valid) */
+ t = "\x04""abcd""\x03""efg";
+ len = 10;
+ pos = 0;
+ d = knot_dname_parse((const uint8_t *)t, &pos, len);
+ ok(d != NULL, "dname_parse: valid name");
+ ok(pos == len, "dname_parse: valid name (parsed length)");
+ knot_dname_free(&d);
+
+ /* 22-23. parse from wire (invalid) */
+ t = "\x08""dddd";
+ len = 5;
+ pos = 0;
+ d = knot_dname_parse((const uint8_t *)t, &pos, len);
+ ok(d == NULL, "dname_parse: bad name");
+ ok(pos == 0, "dname_parse: bad name (parsed length)");
+
+ /* name equality checks */
+ t = "ab.cd.ef";
+ d = knot_dname_from_str(t);
+ ok(knot_dname_is_equal(d, d), "dname_is_equal: equal names");
+ t = "ab.cd.fe";
+ d2 = knot_dname_from_str(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: same label count");
+ knot_dname_free(&d2);
+ t = "ab.cd";
+ d2 = knot_dname_from_str(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) < len(d2)");
+ knot_dname_free(&d2);
+ t = "ab.cd.ef.gh";
+ d2 = knot_dname_from_str(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) > len(d2)");
+ knot_dname_free(&d2);
+ t = "ab.cd.efe";
+ d2 = knot_dname_from_str(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label longer");
+ knot_dname_free(&d2);
+ t = "ab.cd.e";
+ d2 = knot_dname_from_str(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label shorter");
+ knot_dname_free(&d2);
+
+ return 0;
+}
diff --git a/tests/dnssec_keys.c b/tests/dnssec_keys.c
new file mode 100644
index 0000000..0482305
--- /dev/null
+++ b/tests/dnssec_keys.c
@@ -0,0 +1,248 @@
+/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <tap/basic.h>
+
+#include "libknot/dnssec/key.h"
+#include "libknot/dnssec/key.c" // testing static functions
+
+int main(int argc, char *argv[])
+{
+ plan(26);
+
+ // 1-3. - strndup_with_suffix()
+ {
+ char *result;
+
+ result = strndup_with_suffix("begin", 5, "end");
+ ok(result && strcmp(result, "beginend") == 0,
+ "strndup_with_suffix(), matching length");
+ free(result);
+
+ result = strndup_with_suffix("begin", 3, "end");
+ ok(result && strcmp(result, "begend") == 0,
+ "strndup_with_suffix(), shorter length");
+ free(result);
+
+ result = strndup_with_suffix("", 0, "end");
+ ok(result && strcmp(result, "end") == 0,
+ "strndup_with_suffix(), empty base string");
+ free(result);
+ }
+
+ // 4.-9. - get_key_filenames()
+ {
+ char *public, *private;
+ int result;
+
+ result = get_key_filenames("Kexample.com.+1.+2.private",
+ &public, &private);
+ ok(result == KNOT_EOK &&
+ strcmp(public, "Kexample.com.+1.+2.key") == 0 &&
+ strcmp(private, "Kexample.com.+1.+2.private") == 0,
+ "get_key_filenames(), from private key");
+ free(public);
+ free(private);
+
+ result = get_key_filenames("Kexample.com.+4.+8.key",
+ &public, &private);
+ ok(result == KNOT_EOK &&
+ strcmp(public, "Kexample.com.+4.+8.key") == 0 &&
+ strcmp(private, "Kexample.com.+4.+8.private") == 0,
+ "get_key_filenames(), from public key");
+ free(public);
+ free(private);
+
+ result = get_key_filenames("nic.cz.+4.+8",
+ &public, &private);
+ ok(result == KNOT_EOK &&
+ strcmp(public, "nic.cz.+4.+8.key") == 0 &&
+ strcmp(private, "nic.cz.+4.+8.private") == 0,
+ "get_key_filenames(), without extension");
+ free(public);
+ free(private);
+
+ result = get_key_filenames("nic.cz.+0.+1.",
+ &public, &private);
+ ok(result == KNOT_EOK &&
+ strcmp(public, "nic.cz.+0.+1.key") == 0 &&
+ strcmp(private, "nic.cz.+0.+1.private") == 0,
+ "get_key_filenames(), empty extension");
+ free(public);
+ free(private);
+
+ result = get_key_filenames("../keys/Kfoo.bar.+5.+10.private",
+ &public, &private);
+ ok(result == KNOT_EOK &&
+ strcmp(public, "../keys/Kfoo.bar.+5.+10.key") == 0 &&
+ strcmp(private, "../keys/Kfoo.bar.+5.+10.private") == 0,
+ "get_key_filenames(), with path");
+ free(public);
+ free(private);
+
+ result = get_key_filenames("keys/something.txt",
+ &public, &private);
+ ok(result == KNOT_EOK &&
+ strcmp(public, "keys/something.txt.key") == 0 &&
+ strcmp(private, "keys/something.txt.private") == 0,
+ "get_key_filenames(), nonstandard name");
+ free(public);
+ free(private);
+ }
+
+ // 10. - key_param_base64()
+ {
+ knot_binary_t output = { 0 };
+ int result;
+
+ result = key_param_base64(&output, "aGVsbG8gRE5TIHdvcmxk");
+
+ ok(result == KNOT_EOK && output.size == 15
+ && memcmp((char *)output.data, "hello DNS world", 15) == 0,
+ "key_param_base64(), correct usage");
+
+ knot_binary_free(&output);
+ }
+
+ // 11-16. - key_param_int()
+ {
+ int output = 0;
+ int result;
+
+ result = key_param_int(&output, "12345");
+ ok(result == KNOT_EOK && output == 12345,
+ "key_param_int(), correct number");
+
+ result = key_param_int(&output, "6789 whatever");
+ ok(result == KNOT_EOK && output == 6789,
+ "key_param_int(), number, space, and text");
+
+ result = key_param_int(&output, "24680\n");
+ ok(result == KNOT_EOK && output == 24680,
+ "key_param_int(), number and new line");
+
+ result = key_param_int(&output, "0");
+ ok(result == KNOT_EOK && output == 0,
+ "key_param_int(), zero");
+
+ result = key_param_int(&output, "");
+ ok(result == KNOT_EINVAL,
+ "key_param_int(), empty string");
+
+ result = key_param_int(&output, "\t \n");
+ ok(result == KNOT_EINVAL,
+ "key_param_int(), only white spaces");
+
+ result = key_param_int(&output, "4444abc");
+ ok(result == KNOT_EINVAL,
+ "key_param_int(), number and text");
+ }
+
+ // 17-21. - parse_keyfile_line()
+ {
+ knot_key_params_t key = { 0 };
+ int result;
+ char *line;
+
+ line = strdup("Algorithm: 123 ABC");
+ result = parse_keyfile_line(&key, line, strlen(line));
+ ok(result == KNOT_EOK && key.algorithm == 123,
+ "parse_keyfile_line(), simple line with algorithm");
+ free(line);
+
+ line = strdup("Key: c2VjcmV0\n");
+ result = parse_keyfile_line(&key, line, strlen(line));
+ ok(result == KNOT_EOK && key.secret.size == 6
+ && memcmp((char *)key.secret.data, "secret", 6) == 0,
+ "parse_keyfile_line(), new line terminated line with key");
+ knot_binary_free(&key.secret);
+ free(line);
+
+ line = strdup("Cool: S25vdCBETlM=");
+ result = parse_keyfile_line(&key, line, strlen(line));
+ ok(result == KNOT_EOK,
+ "parse_keyfile_line(), unknown parameter");
+ free(line);
+
+ line = strdup("Activate: 20130521144259\n");
+ result = parse_keyfile_line(&key, line, strlen(line));
+ ok(result == KNOT_EOK && key.time_activate == 1369147379,
+ "parse_keyfile_line(), timestamp parsing");
+ free(line);
+ }
+
+ // 22. - knot_free_key_params()
+ {
+ int result;
+ knot_key_params_t params = { 0 };
+ knot_key_params_t empty_params = { 0 };
+
+ params.algorithm = 42;
+ knot_binary_from_base64("AQAB", &params.public_exponent);
+
+ result = knot_free_key_params(&params);
+ ok(result == KNOT_EOK
+ && memcmp(&params, &empty_params, sizeof(params)) == 0,
+ "knot_free_key_params(), regular free");
+ }
+
+ // 23-25. - knot_tsig_key_from_params()
+ {
+ int result;
+ knot_key_params_t params = { 0 };
+ knot_tsig_key_t tsig_key = { 0 };
+ const char *owner = "shared.example.com.";
+ knot_dname_t *name = knot_dname_from_str(owner);
+
+ result = knot_tsig_key_from_params(&params, &tsig_key);
+ ok(result == KNOT_EINVAL,
+ "knot_tsig_key_from_params(), empty parameters");
+
+ params.secret.data = (uint8_t *)"test";
+ params.secret.size = 4;
+ result = knot_tsig_key_from_params(&params, &tsig_key);
+ ok(result == KNOT_EINVAL,
+ "knot_tsig_key_from_params(), no key name");
+
+ params.name = name;
+ params.secret.data = NULL;
+ params.secret.size = 0;
+ result = knot_tsig_key_from_params(&params, &tsig_key);
+ ok(result == KNOT_EINVAL,
+ "knot_tsig_key_from_params(), no shared secret");
+
+ params.name = name;
+ knot_binary_from_base64("Ok6NmA==", &params.secret);
+ uint8_t decoded_secret[] = { 0x3a, 0x4e, 0x8d, 0x98 };
+ result = knot_tsig_key_from_params(&params, &tsig_key);
+ ok(result == KNOT_EOK
+ && tsig_key.secret.size == sizeof(decoded_secret)
+ && memcmp(tsig_key.secret.data, decoded_secret,
+ sizeof(decoded_secret)) == 0,
+ "knot_tsig_key_from_params(), secret set properly");
+
+ knot_free_key_params(&params);
+ knot_tsig_key_free(&tsig_key);
+ }
+
+ //! \todo knot_keytag()
+ //! \todo get_key_info_from_public_key() -- working with files required
+ //! \todo knot_load_key_params() -- working with files required
+ //! \todo knot_get_key_type()
+
+ return 0;
+}
diff --git a/tests/dnssec_nsec3.c b/tests/dnssec_nsec3.c
new file mode 100644
index 0000000..c36d014
--- /dev/null
+++ b/tests/dnssec_nsec3.c
@@ -0,0 +1,102 @@
+/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "common/descriptor.h"
+#include "common/errcode.h"
+#include "libknot/dname.h"
+#include "libknot/consts.h"
+#include "libknot/dnssec/nsec3.h"
+#include "libknot/rrset.h"
+
+int main(int argc, char *argv[])
+{
+ plan(10);
+
+ int result = KNOT_EOK;
+
+ // lengths of different hashes
+
+ is_int(20, knot_nsec3_hash_length(1),
+ "raw hash length for SHA1");
+ is_int(0, knot_nsec3_hash_length(42),
+ "raw hash length for unknown algorithm");
+ is_int(32, knot_nsec3_hash_b32_length(1),
+ "B32 hash length for SHA1");
+ is_int(0, knot_nsec3_hash_b32_length(42),
+ "B32 hash length for unknown algorithm");
+
+ // parsing NSEC3PARAMs from wire
+
+ knot_nsec3_params_t params = { 0 };
+ knot_rrset_t *rrset = NULL;
+ uint8_t rdata[] = {
+ 0x01, // hash algorithm
+ 0x00, // flags
+ 0x00, 0x0a, // iterations
+ 0x04, // salt length
+ 'a', 'b', 'c', 'd' // salt
+ };
+
+ rrset = knot_rrset_new(NULL, KNOT_RRTYPE_NSEC3PARAM, KNOT_CLASS_IN, 0);
+ result = knot_rrset_add_rdata(rrset, rdata, sizeof(rdata));
+ if (result == KNOT_EOK) {
+ result = knot_nsec3_params_from_wire(&params, rrset);
+ }
+
+ is_int(1, params.algorithm, "parse algorithm from wire");
+ is_int(0, params.flags, "parse flags from wire");
+ is_int(10, params.iterations, "parse iterations from wire");
+ is_int(4, params.salt_length, "parse salt length from wire");
+ is_int(0, memcmp(params.salt, "abcd", 4), "parse salt from wire");
+
+ knot_rrset_deep_free(&rrset, 1);
+ knot_nsec3_params_free(&params);
+
+ // hash computation
+
+ params.algorithm = 1;
+ params.flags = 0;
+ params.iterations = 7;
+ params.salt_length = 14;
+ params.salt = (uint8_t *)strdup("happywithnsec3");
+
+ const char *dname_str = "knot-dns.cz.";
+ knot_dname_t *dname = knot_dname_from_str(dname_str);
+
+ size_t digest_size = 0;
+ uint8_t *digest = NULL;
+ result = knot_nsec3_hash(&params, dname, knot_dname_size(dname),
+ &digest, &digest_size);
+
+ uint8_t expected[] = {
+ 0x72, 0x40, 0x55, 0x83, 0x92, 0x93, 0x95, 0x28, 0xee, 0xa2,
+ 0xcc, 0xe1, 0x13, 0xbe, 0xcd, 0x41, 0xee, 0x8a, 0x71, 0xfd
+ };
+
+ ok(result == KNOT_EOK && digest_size == sizeof(expected) &&
+ memcmp(digest, expected, sizeof(expected)) == 0, "compute hash");
+
+ free(digest);
+ free(params.salt);
+ knot_dname_free(&dname);
+
+ return 0;
+}
diff --git a/tests/dnssec_sign.c b/tests/dnssec_sign.c
new file mode 100644
index 0000000..c53976e
--- /dev/null
+++ b/tests/dnssec_sign.c
@@ -0,0 +1,153 @@
+/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <assert.h>
+#include <openssl/opensslconf.h>
+#include <tap/basic.h>
+
+#include "common/errcode.h"
+#include "libknot/dnssec/config.h"
+#include "libknot/dnssec/crypto.h"
+#include "libknot/dnssec/sign.h"
+
+static void test_algorithm(const char *alg, const knot_key_params_t *kp)
+{
+ int result;
+
+ knot_dnssec_key_t key = { 0 };
+ result = knot_dnssec_key_from_params(kp, &key);
+ is_int(KNOT_EOK, result, "%s: create key from params", alg);
+
+ knot_dnssec_sign_context_t *ctx;
+ ctx = knot_dnssec_sign_init(&key);
+ ok(ctx != NULL, "%s: create signing context", alg);
+
+ if (ctx == NULL) {
+ skip_block(12, "%s: required test failed", alg);
+ } else {
+
+ size_t sig_size = knot_dnssec_sign_size(&key);
+ ok(sig_size > 0, "%s: get signature size", alg);
+
+ uint8_t *sig = malloc(sig_size);
+ assert(sig != NULL);
+
+ result = knot_dnssec_sign_add(ctx, (uint8_t *)"test", 4);
+ is_int(KNOT_EOK, result, "%s: add data A", alg);
+
+ result = knot_dnssec_sign_new(ctx);
+ is_int(KNOT_EOK, result, "%s: restart context", alg);
+
+ result = knot_dnssec_sign_add(ctx, (uint8_t *)"hello", 5);
+ is_int(KNOT_EOK, result, "%s: add data B", alg);
+
+ result = knot_dnssec_sign_add(ctx, (uint8_t *)"dns", 3);
+ is_int(KNOT_EOK, result, "%s: add data C", alg);
+
+ result = knot_dnssec_sign_write(ctx, sig, sig_size);
+ is_int(KNOT_EOK, result, "%s: write signature", alg);
+
+ result = knot_dnssec_sign_new(ctx);
+ is_int(KNOT_EOK, result, "%s: restart context", alg);
+
+ result = knot_dnssec_sign_add(ctx, (uint8_t *)"wrong", 5);
+ is_int(KNOT_EOK, result, "%s: add data D", alg);
+
+ result = knot_dnssec_sign_verify(ctx, sig, sig_size);
+ ok(result == KNOT_DNSSEC_EINVALID_SIGNATURE, "%s: verify invalid signature", alg);
+
+ result = knot_dnssec_sign_new(ctx);
+ is_int(KNOT_EOK, result, "%s: restart context", alg);
+
+ result = knot_dnssec_sign_add(ctx, (uint8_t *)"hellodns", 8);
+ is_int(KNOT_EOK, result, "%s: add data B + C", alg);
+
+ result = knot_dnssec_sign_verify(ctx, sig, sig_size);
+ is_int(KNOT_EOK, result, "%s: verify valid signature", alg);
+
+ free(sig);
+ }
+
+ knot_dnssec_sign_free(ctx);
+ knot_dnssec_key_free(&key);
+}
+
+int main(int argc, char *argv[])
+{
+ plan(4 * 14);
+
+ knot_key_params_t kp = { 0 };
+
+ // RSA
+
+ kp.name = knot_dname_from_str("example.com.");
+ kp.algorithm = 5;
+ knot_binary_from_base64("pSxiFXG8wB1SSHdok+OdaAp6QdvqjpZ17ucNge21iYVfv+DZq52l21KdmmyEqoG9wG/87O7XG8XVLNyYPue8Mw==", &kp.modulus);
+ knot_binary_from_base64("AQAB", &kp.public_exponent);
+ knot_binary_from_base64("UuNK9Wf2SJJuUF9b45s9ypA3egVaV+O5mwHoDWO0ziWJxFXNMMsobDdusEDjCw64xnlLmrbzNJ3+ClrOnV04gQ==", &kp.private_exponent);
+ knot_binary_from_base64("0/wjqkgVZxqrFi5OMzq2qQYpxKn3HgS87Io9UG6iqis=", &kp.prime_one);
+ knot_binary_from_base64("x3gFCPpaJ4etPEM1hRd6WMAcmx5FBMjvuuzID6SWWhk=", &kp.prime_two);
+ knot_binary_from_base64("Z8qUS9NvZ0QPcJTLhRnCRY/W84ukivYW6lnlG3SQAHE=", &kp.exponent_one);
+ knot_binary_from_base64("C0kjH8rqZuoqRwqWcJ1Pcs4L0Er6JLcpuS3Ec/4f86E=", &kp.exponent_two);
+ knot_binary_from_base64("VYc62FQX0Vnd27VxkX6hsBcl7Oh00wVCeh3WTDutndg=", &kp.coefficient);
+
+ test_algorithm("RSA", &kp);
+ knot_free_key_params(&kp);
+
+ // DSA
+
+ kp.name = knot_dname_from_str("example.com.");
+ kp.algorithm = 6;
+ knot_binary_from_base64("u7tr4jc7CH0+r2muVEZyjYu7hpMrQ1dHGAMv7hr5dBFYzkutfdBmDSW4C+qxaXWo14gi+jJ8XqFqQ7rQn23DdQ==", &kp.prime);
+ knot_binary_from_base64("tgZ5X6pFoCOM2NzfiAYVG1434Mk=", &kp.subprime);
+ knot_binary_from_base64("bHidtFIFYAHXp7ZxTFd6poJJG8brqO9eyJygvYSFCej/FGDqhF2TsboVvS/evW/qTaSvhkd/aiDg5eAfu1HvrQ==", &kp.base);
+ knot_binary_from_base64("FiTBDsbFDNTw7IrhPeVbzM0DMmI=", &kp.private_value);
+ knot_binary_from_base64("G1pX04Bcew8wyHsmno4Q0tNdmBLlaEdbqvQ03W5XVXUM6MPrtzxgc6jdOogqZsvGK4c+FbThBu42Z1t/ioQr8A==", &kp.public_value);
+
+ test_algorithm("DSA", &kp);
+ knot_free_key_params(&kp);
+
+ // ECDSA
+
+#ifdef KNOT_ENABLE_ECDSA
+ kp.name = knot_dname_from_str("example.com");
+ kp.algorithm = 13;
+ knot_binary_from_base64("1N/PvpB8jZcvv+zr3Q987RKK1cBxDKULzEc5F/nnpSg=", &kp.private_key);
+ knot_binary_from_base64("AAAAAH3t6EfkvHK5fQMGslhWcCfMF6Q3oNbol2f19DGAb8r49ZX7iQ12sFIyrs2CiwDxFR9Y7fF2zOZ005VV1LA3m1Q=", &kp.rdata);
+
+ test_algorithm("ECDSA", &kp);
+ knot_free_key_params(&kp);
+#else
+ skip_block(14, "ECDSA: not supported on this system");
+#endif
+
+#if KNOT_ENABLE_GOST
+ kp.name = knot_dname_from_str("example.com");
+ kp.algorithm = 12;
+ knot_binary_from_base64("MEUCAQAwHAYGKoUDAgITMBIGByqFAwICIwEGByqFAwICHgEEIgIgN2CMRL538HmFM9+GHYM54rEDYO+tLDV3q7AtK1nZ4iA=", &kp.private_key);
+ knot_binary_from_base64("eHh4eOJ4YHvlasoDRc4ZnvRzldoTUgwWSW0bPv7r9xJ074Dn8KzM4yU9fJgTwIT1TsaHmejYopDnVdjxZyrKNra8Keo=", &kp.rdata);
+
+ test_algorithm("GOST", &kp);
+ knot_free_key_params(&kp);
+#else
+ skip_block(14, "GOST: not supported on this system");
+#endif
+
+ knot_crypto_cleanup();
+
+ return 0;
+}
diff --git a/tests/dnssec_zone_nsec.c b/tests/dnssec_zone_nsec.c
new file mode 100644
index 0000000..559312c
--- /dev/null
+++ b/tests/dnssec_zone_nsec.c
@@ -0,0 +1,45 @@
+/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+#include <tap/basic.h>
+
+#include "libknot/dname.h"
+#include "libknot/dnssec/zone-nsec.h"
+
+int main(int argc, char *argv[])
+{
+ plan(1);
+
+ knot_dname_t *owner = knot_dname_from_str("name.example.com");
+ knot_dname_t *apex = knot_dname_from_str("example.com");
+ knot_dname_t *expect = knot_dname_from_str("sv9o5lv8kgv6lm1t9dkst43b3c0aagbj.example.com");
+
+ knot_nsec3_params_t params = {
+ .algorithm = 1, .flags = 0, .iterations = 10,
+ .salt = (uint8_t *)"\xc0\x01", .salt_length = 2
+ };
+
+ knot_dname_t *result = create_nsec3_owner(owner, apex, &params);
+ is_int(0, knot_dname_cmp(result, expect), "create_nsec3_owner()");
+
+ knot_dname_free(&result);
+ knot_dname_free(&owner);
+ knot_dname_free(&apex);
+ knot_dname_free(&expect);
+
+ return 0;
+}
diff --git a/tests/dthreads.c b/tests/dthreads.c
new file mode 100644
index 0000000..f33d2bf
--- /dev/null
+++ b/tests/dthreads.c
@@ -0,0 +1,309 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <pthread.h>
+#include <sched.h>
+#include <sys/select.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <tap/basic.h>
+
+#include "knot/server/dthreads.h"
+
+/* Unit runnable data. */
+static pthread_mutex_t _runnable_mx;
+static volatile int _runnable_i = 0;
+static const int _runnable_cycles = 10000;
+
+/*! \brief Unit runnable. */
+int runnable(struct dthread_t *thread)
+{
+ for (int i = 0; i < _runnable_cycles; ++i) {
+
+ // Increase counter
+ pthread_mutex_lock(&_runnable_mx);
+ ++_runnable_i;
+ pthread_mutex_unlock(&_runnable_mx);
+
+ // Cancellation point
+ if (dt_is_cancelled(thread)) {
+ break;
+ }
+
+ // Yield
+ sched_yield();
+ }
+
+ return 0;
+}
+
+/* Destructor data. */
+static volatile int _destructor_data;
+static pthread_mutex_t _destructor_mx;
+
+/*! \brief Thread destructor. */
+int destruct(struct dthread_t *thread)
+{
+ pthread_mutex_lock(&_destructor_mx);
+ _destructor_data += 1;
+ pthread_mutex_unlock(&_destructor_mx);
+
+ return 0;
+}
+
+/*! \brief Unit blocking runnable. */
+int runnable_simio(struct dthread_t *thread)
+{
+ // Infinite blocking, must be interrupted
+ select(0, 0, 0, 0, 0);
+ return 0;
+}
+
+/*! \brief Create unit. */
+static inline dt_unit_t *dt_test_create(int size)
+{
+ return dt_create(size);
+}
+
+/*! \brief Assign a task. */
+static inline int dt_test_single(dt_unit_t *unit)
+{
+ return dt_repurpose(unit->threads[0], &runnable, NULL) == 0;
+}
+
+/*! \brief Assign task to all unit threads. */
+static inline int dt_test_coherent(dt_unit_t *unit)
+{
+ int ret = 0;
+ for (int i = 0; i < unit->size; ++i) {
+ ret += dt_repurpose(unit->threads[i], &runnable, NULL);
+ }
+
+ return ret == 0;
+}
+
+/*! \brief Repurpose single thread. */
+static inline int dt_test_repurpose(dt_unit_t *unit, int id)
+{
+ return dt_repurpose(unit->threads[id], &runnable_simio, NULL) == 0;
+}
+
+/*! \brief Cancel single thread. */
+static inline int dt_test_cancel(dt_unit_t *unit, int id)
+{
+ int ret = dt_cancel(unit->threads[id]);
+ ret |= dt_signalize(unit->threads[id], SIGALRM);
+ return ret == 0; /* Both succeeded. */
+}
+
+/*! \brief Reanimate dead threads. */
+static inline int dt_test_reanimate(dt_unit_t *unit)
+{
+ // Compact all threads
+ int ret = 0;
+ ret += dt_compact(unit);
+
+ // Remove purpose from all
+ for (int i = 0; i < unit->size; ++i) {
+ ret += dt_repurpose(unit->threads[i], 0, 0);
+ }
+
+ // Set single thread to purpose
+ ret += dt_repurpose(unit->threads[0], &runnable, 0);
+
+ // Restart
+ _runnable_i = 0;
+ ret += dt_start(unit);
+
+ // Wait for finish
+ ret += dt_join(unit);
+
+ // Verify
+ int expected = 1 * _runnable_cycles;
+ if (_runnable_i != expected) {
+ return 0;
+ }
+
+ // Check return codes
+ return ret == 0;
+}
+
+/*! \brief Start unit. */
+static inline int dt_test_start(dt_unit_t *unit)
+{
+ return dt_start(unit) == 0;
+}
+
+/*! \brief Stop unit. */
+static inline int dt_test_stop(dt_unit_t *unit)
+{
+ return dt_stop(unit);
+}
+
+/*! \brief Join unit. */
+static inline int dt_test_join(dt_unit_t *unit)
+{
+ return dt_join(unit) == 0;
+}
+
+// Signal handler
+static void interrupt_handle(int s)
+{
+}
+
+/*! API: run tests. */
+int main(int argc, char *argv[])
+{
+ plan(20);
+
+ // Register service and signal handler
+ struct sigaction sa;
+ sa.sa_handler = interrupt_handle;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGALRM, &sa, NULL); // Interrupt
+
+ /* Initialize */
+ srand(time(NULL));
+ struct timeval tv;
+ pthread_mutex_init(&_runnable_mx, NULL);
+ pthread_mutex_init(&_destructor_mx, NULL);
+
+ /* Test 1: Create unit */
+ dt_unit_t *unit = dt_test_create(2);
+ ok(unit != 0, "dthreads: create unit (optimal size %d)", unit->size);
+ if (unit == 0) {
+ skip_block(17, "No dthreads unit");
+ goto skip_all;
+ }
+
+ /* Test 2: Assign a single task. */
+ ok(dt_test_single(unit), "dthreads: assign single task");
+
+ /* Test 3: Start tasks. */
+ _runnable_i = 0;
+ ok(dt_test_start(unit), "dthreads: start single task");
+
+ /* Test 4: Wait for tasks. */
+ ok(dt_test_join(unit), "dthreads: join threads");
+
+ /* Test 5: Compare counter. */
+ int expected = _runnable_cycles * 1;
+ is_int(expected, _runnable_i, "dthreads: result ok");
+
+ /* Test 6: Repurpose threads. */
+ _runnable_i = 0;
+ ok(dt_test_coherent(unit), "dthreads: repurpose to coherent");
+
+ /* Test 7: Restart threads. */
+ ok(dt_test_start(unit), "dthreads: start coherent unit");
+
+ /* Test 8: Repurpose single thread. */
+ tv.tv_sec = 0;
+ tv.tv_usec = 4000 + rand() % 1000; // 4-5ms
+ diag("waiting for %dus to let thread do some work ...",
+ (int)tv.tv_usec);
+ select(0, 0, 0, 0, &tv);
+ ok(dt_test_repurpose(unit, 0), "dthreads: repurpose on-the-fly");
+
+ /* Test 9: Cancel blocking thread. */
+ tv.tv_sec = 0;
+ tv.tv_usec = (250 + rand() % 500) * 1000; // 250-750ms
+ diag("waiting for %dms to let thread pretend blocking I/O ...",
+ (int)(tv.tv_usec / 1000));
+ select(0, 0, 0, 0, &tv);
+ ok(dt_test_cancel(unit, 0), "dthreads: cancel blocking thread");
+
+ /* Test 10: Wait for tasks. */
+ ok(dt_test_join(unit), "dthreads: join threads");
+
+ /* Test 11: Compare counter. */
+ int expected_lo = _runnable_cycles * (unit->size - 1);
+ ok(_runnable_i >= expected_lo,
+ "dthreads: result %d is => %d", _runnable_i, expected_lo);
+
+ /* Test 12: Compare counter #2. */
+ /*! \note repurpose could trigger next run of the unit if both finished */
+ int expected_hi = _runnable_cycles * (unit->size + unit->size - 1);
+ ok(_runnable_i <= expected_hi,
+ "dthreads: result %d is <= %d", _runnable_i, expected_hi);
+
+ /* Test 13: Reanimate dead threads. */
+ ok(dt_test_reanimate(unit), "dthreads: reanimate dead threads");
+
+ /* Test 14: Deinitialize */
+ dt_delete(&unit);
+ ok(unit == NULL, "dthreads: delete unit");
+
+ /* Test 15: Wrong values. */
+ unit = dt_create(-1);
+ ok(unit == NULL, "dthreads: create with negative count");
+ unit = dt_create_coherent(dt_optimal_size(), 0, 0, 0);
+
+ /* Test 16: NULL runnable. */
+ is_int(0, dt_start(unit), "dthreads: start with NULL runnable");
+
+ /* Test 17: NULL operations crashing. */
+ int op_count = 14;
+ int expected_min = op_count * -1;
+ // All functions must return -1 at least
+ int ret = 0;
+ ret += dt_activate(0); // -1
+ ret += dt_cancel(0); // -1
+ ret += dt_compact(0); // -1
+ dt_delete(0); //
+ ret += dt_is_cancelled(0); // 0
+ ret += dt_join(0); // -1
+ ret += dt_repurpose(0, 0, 0); // -1
+ ret += dt_signalize(0, SIGALRM); // -1
+ ret += dt_start(0); // -1
+ ret += dt_start_id(0); // -1
+ ret += dt_stop(0); // -1
+ ret += dt_stop_id(0); // -1
+ ret += dt_unit_lock(0); // -1
+ ret += dt_unit_unlock(0); // -1
+ is_int(-1464, ret, "dthreads: not crashed while executing functions on NULL context");
+
+ /* Test 18: expected results. */
+ ok(ret <= expected_min,
+ "dthreads: correct values when passed NULL context "
+ "(%d, min: %d)", ret, expected_min);
+
+ /* Test 19: Thread destructor. */
+ _destructor_data = 0;
+ unit = dt_create_coherent(2, 0, destruct, 0);
+ dt_start(unit);
+ dt_stop(unit);
+ dt_join(unit);
+ is_int(2, _destructor_data, "dthreads: destructor with dt_create_coherent()");
+ dt_delete(&unit);
+
+ /* Test 20: Thread destructor setter. */
+ unit = dt_create(1);
+ dt_set_desctructor(unit->threads[0], destruct);
+ dt_start(unit);
+ dt_stop(unit);
+ dt_join(unit);
+ is_int(3, _destructor_data, "dthreads: destructor with dt_set_desctructor()");
+ dt_delete(&unit);
+
+skip_all:
+
+ pthread_mutex_destroy(&_runnable_mx);
+ pthread_mutex_destroy(&_destructor_mx);
+ return 0;
+}
diff --git a/tests/events.c b/tests/events.c
new file mode 100644
index 0000000..2ba8177
--- /dev/null
+++ b/tests/events.c
@@ -0,0 +1,183 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <tap/basic.h>
+
+#include "common/evqueue.h"
+#include "common/evsched.h"
+#include "common/errcode.h"
+
+void* term_thr(void *arg)
+{
+ evsched_t *s = (evsched_t *)arg;
+
+ /* Sleep for 100ms. */
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 100 * 1000; // 100ms
+ select(0, 0, 0, 0, &tv);
+
+ /* Issue termination event. */
+ evsched_schedule_term(s, 0);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ plan(20);
+
+ /*
+ * Event queue tests.
+ */
+
+ // 1. Construct an event queue
+ evqueue_t *q = evqueue_new();
+ ok(q != 0, "evqueue: new");
+
+ // 2. Send integer through event queue
+ int ret = 0;
+ uint8_t sent = 0xaf, rcvd = 0;
+ ret = evqueue_write(q, &sent, sizeof(uint8_t));
+ is_int(sizeof(uint8_t), ret, "evqueue: send byte through");
+
+ // 3. Receive byte from event queue
+ ret = evqueue_read(q, &rcvd, sizeof(uint8_t));
+ is_int(sizeof(uint8_t), ret, "evqueue: received byte");
+
+ // 4. Received match
+ ok(sent == rcvd, "evqueue: received byte match");
+
+ // 5. Sending event
+ event_t ev, rev;
+ memset(&ev, 0, sizeof(event_t));
+ memset(&rev, 0, sizeof(event_t));
+ ev.type = 0xfa11;
+ ev.data = (void*)0xceed;
+ ret = evqueue_add(q, &ev);
+ is_int(0, ret, "evqueue: sent event to queue");
+
+ // 6. Poll for new events
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100 * 1000 * 1000; // 100ms
+ ret = evqueue_poll(q, &ts, 0);
+ ok(ret > 0, "evqueue: polling queue for events");
+
+ // 7. Compare received event
+ ret = evqueue_get(q, &rev);
+ /* Compare useful data, as event owner was changed in evqueue_get(). */
+ if (ev.type == rev.type && ev.data == rev.data) {
+ ret = 0;
+ }
+ is_int(0, ret, "evqueue: received event matches sent");
+
+ // 8. Invalid parameters
+ evqueue_free(0);
+ evqueue_poll(0,0,0);
+ evqueue_read(0, 0, 0);
+ evqueue_write(0, 0, 0);
+ evqueue_read(0, 0, 0);
+ evqueue_get(0, 0);
+ evqueue_add(0, 0);
+ ok(1, "evqueue: won't crash with NULL parameters");
+
+ // 9. Free event queue
+ evqueue_free(&q);
+ ok(1, "evqueue: delete");
+
+ /*
+ * Event scheduler tests.
+ */
+
+ // 1. Construct event scheduler
+ event_t *e = 0;
+ evsched_t *s = evsched_new();
+ ok(s != 0, "evsched: new");
+
+ // 2. Schedule event to happen after N ms
+ int msecs = 200;
+ struct timeval st, rt;
+ gettimeofday(&st, 0);
+ e = evsched_schedule_cb(s, 0, (void*)0xcafe, msecs);
+ ok(e != 0, "evsched: scheduled empty event after %dms", msecs);
+
+ // 3. Wait for next event
+ e = evsched_next(s);
+ evsched_event_finished(s);
+ gettimeofday(&rt, 0);
+ ok(e != 0, "evsched: received valid event");
+
+#ifdef ENABLE_TIMED_TESTS
+ // 4. Check receive time
+ double passed = (rt.tv_sec - st.tv_sec) * 1000;
+ passed += (rt.tv_usec - st.tv_usec) / 1000;
+ double margin = msecs * 0.4;
+ double lb = msecs - margin, ub = msecs + margin;
+ int in_bounds = (passed >= lb) && (passed <= ub);
+ ok(in_bounds, "evsched: receive time %.1lfms is in <%.1lf,%.1lf>",
+ passed, lb, ub);
+#else
+ skip("Timed tests not enabled");
+#endif
+
+ // 5. Check data
+ ok(e->data == (void*)0xcafe, "evsched: received data is valid");
+
+ // 6. Delete event
+ evsched_event_free(s, e);
+ ok(1, "evsched: deleted event");
+
+ // 7. Insert and immediately cancel an event
+ e = evsched_schedule_cb(s, 0, (void*)0xdead, 1000);
+ ret = evsched_cancel(s, e);
+ ok(ret == KNOT_EOK, "evsched: inserted and cancelled an event");
+ if (e) {
+ evsched_event_free(s, e);
+ }
+
+ // 8. Start listener thread and block
+ pthread_t t;
+ pthread_create(&t, 0, term_thr, s);
+ e = evsched_next(s);
+ evsched_event_finished(s);
+ ok(e != 0, "evsched: received termination event");
+
+ // 9. Termination event is valid
+ ok(e->type == EVSCHED_TERM, "evsched: termination event is valid");
+ evsched_event_free(s, e);
+ pthread_join(t, 0);
+
+ // 10. Invalid parameters
+ evsched_delete(0);
+ evsched_event_new(0, 0);
+ evsched_event_free(0, 0);
+ evsched_next(0);
+ evsched_schedule(0, 0, 0);
+ evsched_schedule_cb(0, 0, 0, 0);
+ evsched_schedule_term(0, 0);
+ evsched_cancel(0, 0);
+ ok(1, "evsched: won't crash with NULL parameters");
+
+ // 11. Delete event scheduler
+ evsched_delete(&s);
+ ok(1, "evsched: delete");
+
+ return 0;
+}
diff --git a/tests/fdset.c b/tests/fdset.c
new file mode 100644
index 0000000..f42843f
--- /dev/null
+++ b/tests/fdset.c
@@ -0,0 +1,154 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <tap/basic.h>
+
+#include "common/fdset.h"
+
+#define WRITE_PATTERN ((char) 0xde)
+#define WRITE_PATTERN_LEN sizeof(char)
+
+
+/* Subtract the `struct timeval' values X and Y,
+ storing the result in RESULT.
+ Return 1 if the difference is negative, otherwise 0.
+ Copyright http://www.delorie.com/gnu/docs/glibc/libc_428.html
+*/
+static int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval* y)
+{
+ /* Perform the carry for the later subtraction by updating y. */
+ if (x->tv_usec < y->tv_usec) {
+ int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+ y->tv_usec -= 1000000 * nsec;
+ y->tv_sec += nsec;
+ }
+ if (x->tv_usec - y->tv_usec > 1000000) {
+ int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+ y->tv_usec += 1000000 * nsec;
+ y->tv_sec -= nsec;
+ }
+
+ /* Compute the time remaining to wait.
+ tv_usec is certainly positive. */
+ result->tv_sec = x->tv_sec - y->tv_sec;
+ result->tv_usec = x->tv_usec - y->tv_usec;
+
+ /* Return 1 if result is negative. */
+ return x->tv_sec < y->tv_sec;
+}
+
+static size_t timeval_diff(struct timeval *from, struct timeval *to) {
+ struct timeval res;
+ timeval_subtract(&res, to, from);
+ return res.tv_sec*1000 + res.tv_usec/1000;
+}
+
+void* thr_action(void *arg)
+{
+ int *fd = (int *)arg;
+
+ /* Sleep for 100ms. */
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 100 * 1000; // 100ms
+ select(0, 0, 0, 0, &tv);
+
+ /* Write pattern. */
+ char pattern = WRITE_PATTERN;
+ if (write(*fd, &pattern, WRITE_PATTERN_LEN) == -1) {
+ // Error.
+ }
+
+ return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+ plan(11);
+
+ /* 1. Create fdset. */
+ fdset_t set;
+ int ret = fdset_init(&set, 32);
+ is_int(0, ret, "fdset: init");
+
+ /* 2. Create pipe. */
+ int fds[2], tmpfds[2];
+ ret = pipe(fds);
+ ok(ret >= 0, "fdset: pipe() works");
+ ret = pipe(tmpfds);
+
+ /* 3. Add fd to set. */
+ ret = fdset_add(&set, fds[0], POLLIN, NULL);
+ is_int(0, ret, "fdset: add to set works");
+ fdset_add(&set, tmpfds[0], POLLIN, NULL);
+
+ /* Schedule write. */
+ struct timeval ts, te;
+ gettimeofday(&ts, 0);
+ pthread_t t;
+ pthread_create(&t, 0, thr_action, &fds[1]);
+
+ /* 4. Watch fdset. */
+ int nfds = poll(set.pfd, set.n, 60 * 1000);
+ gettimeofday(&te, 0);
+ size_t diff = timeval_diff(&ts, &te);
+
+ ok(nfds > 0, "fdset: poll returned %d events in %zu ms", nfds, diff);
+
+ /* 5. Prepare event set. */
+ ok(set.pfd[0].revents & POLLIN, "fdset: pipe is active");
+
+ /* 6. Receive data. */
+ char buf = 0x00;
+ ret = read(set.pfd[0].fd, &buf, WRITE_PATTERN_LEN);
+ ok(ret >= 0 && buf == WRITE_PATTERN, "fdset: contains valid data");
+
+ /* 7-9. Remove from event set. */
+ ret = fdset_remove(&set, 0);
+ is_int(0, ret, "fdset: remove from fdset works");
+ close(fds[0]);
+ close(fds[1]);
+ ret = fdset_remove(&set, 0);
+ close(tmpfds[1]);
+ close(tmpfds[1]);
+ is_int(0, ret, "fdset: remove from fdset works (2)");
+ ret = fdset_remove(&set, 0);
+ ok(ret != 0, "fdset: removing nonexistent item");
+
+ /* 10. Crash test. */
+ fdset_init(0, 0);
+ fdset_add(0, 1, 1, 0);
+ fdset_add(0, 0, 1, 0);
+ fdset_remove(0, 1);
+ fdset_remove(0, 0);
+ ok(1, "fdset: crash test successful");
+
+ /* 11. Destroy fdset. */
+ ret = fdset_clear(&set);
+ is_int(0, ret, "fdset: destroyed");
+
+ /* Cleanup. */
+ pthread_join(t, 0);
+
+ return 0;
+}
diff --git a/tests/hattrie.c b/tests/hattrie.c
new file mode 100644
index 0000000..7efee4b
--- /dev/null
+++ b/tests/hattrie.c
@@ -0,0 +1,198 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <time.h>
+#include <tap/basic.h>
+
+#include "common/mempattern.h"
+#include "common/hattrie/hat-trie.h"
+
+static const char *alphabet = "abcdefghijklmn.0123456789-";
+static char *randstr() {
+ unsigned len = (1 + rand() % 64) + 1; /* (1-64) + '\0' */
+ char *s = xmalloc(len * sizeof(char));
+ for (unsigned i = 0; i < len - 1; ++i) {
+ s[i] = alphabet[rand() % strlen(alphabet)];
+ }
+ s[len - 1] = '\0';
+ return s;
+}
+static bool str_check_sort(const char *prev, const char *cur, size_t l1, size_t l2)
+{
+ if (prev == NULL) {
+ return true;
+ }
+ int res = memcmp(prev, cur, MIN(l1, l2));
+ if (res == 0) { /* Keys may be equal. */
+ if (l1 > l2) { /* 'prev' is longer, breaks ordering. */
+ return false;
+ }
+ } else if (res > 0){
+ return false; /* Broken lexicographical order */
+ }
+ return true;
+}
+
+
+int main(int argc, char *argv[])
+{
+ plan(9);
+
+ /* Interesting intems. */
+ unsigned count = 10;
+ const char *items[] = {
+ "abcd",
+ "abc",
+ "ab",
+ "a",
+ "abcdefghijklmnopqrstuvw",
+ "abAcd",
+ "abcA",
+ "abA",
+ "Aab",
+ "A"
+ };
+
+ /* Dummy items. */
+ srand(time(NULL));
+ unsigned dummy_count = 65535;
+ char **dummy = xmalloc(sizeof(char*) * dummy_count);
+ for (unsigned i = 0; i < dummy_count; ++i) {
+ dummy[i] = randstr();
+ }
+
+ /* Test 1: Create */
+ unsigned passed = 1;
+ value_t *v = NULL;
+ hattrie_t *t = hattrie_create();
+ ok(t != NULL, "hattrie: create");
+
+ /* Test 2: Insert */
+ unsigned really_inserted = 0;
+ passed = 1;
+ for (unsigned i = 0; i < count; ++i) {
+ v = hattrie_get(t, items[i], strlen(items[i]));
+ if (!v) {
+ passed = 0;
+ break;
+ }
+ if (*v == NULL) {
+ ++really_inserted;
+ }
+ *v = (value_t)items[i];
+ }
+ ok(passed, "hattrie: insert");
+
+ /* Test 3: Insert dummy. */
+ passed = 1;
+ for (unsigned i = 0; i < dummy_count; ++i) {
+ v = hattrie_get(t, dummy[i], strlen(dummy[i]));
+ if (!v) {
+ passed = 0;
+ break;
+ }
+ if (*v == NULL) {
+ *v = dummy[i];
+ ++really_inserted;
+ }
+ }
+ ok(passed, "hattrie: dummy insert");
+
+ /* Test 4: Lookup */
+ passed = 1;
+ for (unsigned i = 0; i < count; ++i) {
+ v = hattrie_tryget(t, items[i], strlen(items[i]));
+ if (!v || *v != items[i]) {
+ diag("hattrie: mismatch on element '%u'", i);
+ passed = 0;
+ break;
+ }
+ }
+ ok(passed, "hattrie: lookup");
+
+ /* Test 5: LPR lookup */
+ unsigned lpr_count = 5;
+ const char *lpr[] = {
+ "abcdZ",
+ "abcZ",
+ "abZ",
+ "aZ",
+ "abcdefghijklmnopqrstuvw"
+ };
+ passed = 1;
+ for (unsigned i = 0; i < lpr_count; ++i) {
+ int ret = hattrie_find_lpr(t, lpr[i], strlen(lpr[i]), &v);
+ if (!v || ret != 0 || *v != items[i]) {
+ diag("hattrie: lpr='%s' mismatch lpr(%s) != %s",
+ (char *)(!v ? "<NULL>" : *v), lpr[i], items[i]);
+ passed = 0;
+ break;
+ }
+ }
+ ok(passed, "hattrie: longest prefix match");
+
+ /* Test 6: false LPR lookup */
+ const char *false_lpr = "Z";
+ int ret = hattrie_find_lpr(t, false_lpr, strlen(false_lpr), &v);
+ ok(ret != 0 && v == NULL, "hattrie: non-existent prefix lookup");
+
+ /* Check total insertions against trie weight. */
+ is_int(hattrie_weight(t), really_inserted, "hattrie: trie weight matches insertions");
+
+ /* Unsorted iteration */
+ unsigned counted = 0;
+ hattrie_iter_t *it = hattrie_iter_begin(t, false);
+ while (!hattrie_iter_finished(it)) {
+ ++counted;
+ hattrie_iter_next(it);
+ }
+ is_int(really_inserted, counted, "hattrie: unsorted iteration");
+ hattrie_iter_free(it);
+
+ /* Sorted iteration. */
+ size_t len = 0, prev_len = 0;
+ const char *cur = NULL;
+ char *prev = NULL;
+ counted = 0;
+ hattrie_build_index(t);
+ it = hattrie_iter_begin(t, true);
+ while (!hattrie_iter_finished(it)) {
+ cur = hattrie_iter_key(it, &len);
+ if (!str_check_sort(prev, cur, prev_len, len)) {
+ diag("(%zu)'%s' < (%zu)'%s' FAIL\n",
+ prev_len, prev, len, cur);
+ break;
+ }
+ ++counted;
+ free(prev);
+ prev = xmalloc(len);
+ memcpy(prev, cur, len);
+ prev_len = len;
+ hattrie_iter_next(it);
+ }
+ free(prev);
+ is_int(really_inserted, counted, "hattrie: sorted iteration");
+ hattrie_iter_free(it);
+
+ for (unsigned i = 0; i < dummy_count; ++i) {
+ free(dummy[i]);
+ }
+ free(dummy);
+ hattrie_free(t);
+ return 0;
+}
diff --git a/tests/hhash.c b/tests/hhash.c
new file mode 100644
index 0000000..1e1c5ea
--- /dev/null
+++ b/tests/hhash.c
@@ -0,0 +1,173 @@
+/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <assert.h>
+#include <tap/basic.h>
+
+#include "common/hhash.h"
+#include "common/mempattern.h"
+#include "common/mempool.h"
+#include "libknot/common.h"
+
+/* Test defines. */
+#define ELEM_COUNT 65535
+#define KEY_LEN(x) (strlen(x)+1)
+
+/* Random key string generator for tests. */
+static const char *alphabet = "0123abcdABCDwxyzWXYZ.-_";
+char *test_randstr_mm(struct mm_ctx *mm)
+{
+ unsigned len = (5 + rand() % 251) + 1;
+ char *s = mm->alloc(mm->ctx, len * sizeof(char));
+ for (unsigned i = 0; i < len - 1; ++i) {
+ s[i] = alphabet[rand() % strlen(alphabet)];
+ }
+ s[len - 1] = '\0';
+ return s;
+}
+
+/*! \brief Return true if 'cur' comes after 'prev'. */
+static bool str_check_sort(const char *prev, const char *cur)
+{
+ if (prev == NULL) {
+ return true;
+ }
+
+ int l1 = strlen(prev);
+ int l2 = strlen(cur);
+ int res = memcmp(prev, cur, MIN(l1, l2));
+ if (res == 0) { /* Keys may be equal. */
+ if (l1 > l2) { /* 'prev' is longer, breaks ordering. */
+ return false;
+ }
+ } else if (res > 0){
+ return false; /* Broken lexicographical order */
+ }
+
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ plan(11);
+
+ /* Create memory pool context. */
+ struct mempool *pool = mp_new(64 * 1024);
+ mm_ctx_t mm;
+ mm.ctx = pool;
+ mm.alloc = (mm_alloc_t)mp_alloc;
+ mm.free = NULL;
+
+ /* Create hashtable */
+ int ret = KNOT_EOK;
+ uint16_t len = 0;
+ const char *key = "mykey", *cur = NULL, *prev = NULL;
+ value_t val = (void*)0xdeadbeef, *rval = NULL;
+ hhash_iter_t it;
+ hhash_t *tbl = hhash_create_mm(ELEM_COUNT, &mm);
+ ok(tbl != NULL, "hhash: create");
+ if (tbl == NULL) {
+ return KNOT_ERROR; /* No point in testing further on. */
+ }
+
+ /* Generate random keys. */
+ char *keys[ELEM_COUNT];
+ unsigned nfilled = 0;
+ for (unsigned i = 0; i < ELEM_COUNT; ++i) {
+ keys[i] = test_randstr_mm(&mm);
+ }
+
+ /* Insert single element. */
+ ret = hhash_insert(tbl, key, KEY_LEN(key), val);
+ ok(ret == KNOT_EOK, "hhash: insert single element");
+
+ /* Retrieve nonexistent element. */
+ cur = "nokey";
+ rval = hhash_find(tbl, cur, KEY_LEN(cur));
+ ok(rval == NULL, "hhash: find non-existent element");
+
+ /* Retrieve single element. */
+ rval = hhash_find(tbl, key, KEY_LEN(key));
+ ok(rval != NULL, "hhash: find existing element");
+
+ /* Fill the table. */
+ for (unsigned i = 0; i < ELEM_COUNT; ++i) {
+ ret = hhash_insert(tbl, keys[i], KEY_LEN(keys[i]), keys[i]);
+ if (ret != KNOT_EOK) {
+ nfilled = i;
+ ret = KNOT_EOK;
+ break;
+ }
+ }
+
+ /* Check all keys integrity. */
+ unsigned nfound = 0;
+ for (unsigned i = 0; i < nfilled; ++i) {
+ rval = hhash_find(tbl, keys[i], KEY_LEN(keys[i]));
+ if (!rval || memcmp(*rval, keys[i], KEY_LEN(keys[i])) != 0) {
+ break; /* Mismatch */
+ }
+ ++nfound;
+ }
+ is_int(nfilled, nfound, "hhash: found all inserted keys");
+
+ /* Test keys order index. */
+ hhash_build_index(tbl);
+ hhash_iter_begin(tbl, &it, true);
+ while (!hhash_iter_finished(&it)) {
+ cur = hhash_iter_key(&it, &len);
+ if (!str_check_sort(prev, cur)) {
+ break;
+ }
+ prev = cur;
+ int strl = strlen(cur);
+ assert(strl + 1 == len);
+ hhash_iter_next(&it);
+ }
+ ok(hhash_iter_finished(&it), "hhash: passed order index checks");
+
+ /* Retrieve all keys. */
+ nfound = 0;
+ hhash_iter_begin(tbl, &it, false);
+ while (!hhash_iter_finished(&it)) {
+ cur = hhash_iter_key(&it, &len);
+ if (hhash_find(tbl, cur, len) == NULL) {
+ break;
+ } else {
+ ++nfound;
+ }
+ hhash_iter_next(&it);
+ }
+ ok(hhash_iter_finished(&it), "hhash: found all iterated keys");
+ is_int(tbl->weight, nfound, "hhash: all iterated keys found");
+
+ /* Test find less or equal. */
+ prev = "mykey0"; /* mykey should precede it */
+ hhash_find_leq(tbl, prev, KEY_LEN(prev), &rval);
+ ok(rval && *rval == val, "hhash: find less or equal");
+
+ /* Delete key and retrieve it. */
+ ret = hhash_del(tbl, key, KEY_LEN(key));
+ ok(ret == KNOT_EOK, "hhash: remove key");
+ rval = hhash_find(tbl, key, KEY_LEN(key));
+ ok(rval == NULL, "hhash: find removed element");
+
+ /* Free all memory. */
+ mp_delete(mm.ctx);
+ return KNOT_EOK;
+}
diff --git a/tests/journal.c b/tests/journal.c
new file mode 100644
index 0000000..81be326
--- /dev/null
+++ b/tests/journal.c
@@ -0,0 +1,280 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <unistd.h>
+#include <tap/basic.h>
+
+#include "knot/server/journal.h"
+#include "knot/knot.h"
+
+static unsigned JOURNAL_TEST_COUNT = 24;
+
+/*! \brief Generate random string with given length. */
+static int randstr(char* dst, size_t len)
+{
+ for (int i = 0; i < len - 1; ++i) {
+ dst[i] = '0' + (int) (('Z'-'0') * (rand() / (RAND_MAX + 1.0)));
+ }
+ dst[len - 1] = '\0';
+
+ return 0;
+}
+
+/*! \brief Walk journal of chars into buffer. */
+static int _wbi = 0;
+static char _walkbuf[7];
+static int walkchars_cmp(uint64_t k1, uint64_t k2) {
+ return k1 - k2;
+}
+
+static int walkchars(journal_t *j, journal_node_t *n) {
+ journal_read(j, n->id, walkchars_cmp, _walkbuf + _wbi);
+ ++_wbi;
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ plan(JOURNAL_TEST_COUNT);
+
+ /* Create tmpdir */
+ int fsize = 8092;
+ int jsize = 6;
+ char *tmpdir = test_tmpdir();
+ char jfn_buf[4096];
+ snprintf(jfn_buf, 4096 - 1, "%s/%s", tmpdir, "journal.XXXXXX");
+
+ /* Create tmpfile. */
+ int tmp_fd = mkstemp(jfn_buf);
+ ok(tmp_fd >= 0, "journal: create temporary file");
+ if (tmp_fd < 0) {
+ skip_block(JOURNAL_TEST_COUNT - 1, "No temporary file");
+ goto skip_all;
+ }
+ close(tmp_fd);
+
+ /* Create journal. */
+ const char *jfilename = jfn_buf;
+ int ret = journal_create(jfilename, jsize);
+ is_int(KNOT_EOK, ret, "journal: create journal '%s'", jfilename);
+
+ /* Open journal. */
+ journal_t *journal = journal_open(jfilename, fsize, 0);
+ ok(journal != NULL, "journal: open journal '%s'", jfilename);
+
+ /* Retain journal. */
+ ret = journal_retain(journal);
+ is_int(KNOT_EOK, ret, "journal: retain");
+
+ /* Write entry to log. */
+ const char *sample = "deadbeef";
+ ret = journal_write(journal, 0x0a, sample, strlen(sample));
+ is_int(KNOT_EOK, ret, "journal: write");
+
+ /* Read entry from log. */
+ char tmpbuf[64] = {'\0'};
+ ret = journal_read(journal, 0x0a, 0, tmpbuf);
+ is_int(KNOT_EOK, ret, "journal: read entry");
+
+ /* Compare read data. */
+ ret = strncmp(sample, tmpbuf, strlen(sample));
+ is_int(KNOT_EOK, ret, "journal: read data integrity check");
+
+ /* Append several characters. */
+ journal_write(journal, 0, "X", 1); /* Dummy */
+ char word[7] = { 'w', 'o', 'r', 'd', '0', '\0', '\0' };
+ for (int i = 0; i < strlen(word); ++i) {
+ journal_write(journal, i, word+i, 1);
+ }
+
+ /* Compare journal_walk() result. */
+ _wbi = 0;
+ journal_walk(journal, walkchars);
+ _walkbuf[_wbi] = '\0';
+ ret = strcmp(word, _walkbuf);
+ is_int(0, ret, "journal: read data integrity check 2 '%s'", _walkbuf);
+ _wbi = 0;
+
+ /* Change single letter and compare. */
+ word[5] = 'X';
+ journal_write(journal, 5, word+5, 1); /* append 'X', shifts out 'w' */
+ journal_walk(journal, walkchars);
+ _walkbuf[_wbi] = '\0';
+ ret = strcmp(word + 1, _walkbuf);
+ is_int(0, ret, "journal: read data integrity check 3 '%s'", _walkbuf);
+ _wbi = 0;
+
+ /* Release journal. */
+ journal_release(journal);
+
+ /* Close journal. */
+ journal_close(journal);
+
+ /* Recreate journal = NORMAL mode. */
+ if (remove(jfilename) < 0) {
+ diag("journal: couldn't remove filename");
+ }
+ fsize = 8092;
+ jsize = 512;
+ ret = journal_create(jfilename, jsize);
+ is_int(KNOT_EOK, ret, "journal: create journal '%s'", jfilename);
+
+ journal = journal_open(jfilename, fsize, 0);
+ ok(journal != NULL, "journal: open journal '%s'", jfilename);
+ journal_retain(journal);
+
+ /* Write random data. */
+ int chk_key = 0;
+ char chk_buf[64] = {'\0'};
+ ret = 0;
+ const int itcount = jsize * 5 + 5;
+ for (int i = 0; i < itcount; ++i) {
+ int key = rand() % 65535;
+ randstr(tmpbuf, sizeof(tmpbuf));
+ if (journal_write(journal, key, tmpbuf, sizeof(tmpbuf)) != KNOT_EOK) {
+ ret = -1;
+ break;
+ }
+
+ /* Store some key on the end. */
+ if (i == itcount - 2) {
+ chk_key = key;
+ memcpy(chk_buf, tmpbuf, sizeof(chk_buf));
+ }
+ }
+ is_int(0, ret, "journal: sustained looped writes");
+
+ /* Check data integrity. */
+ memset(tmpbuf, 0, sizeof(tmpbuf));
+ journal_read(journal, chk_key, 0, tmpbuf);
+ ret = strncmp(chk_buf, tmpbuf, sizeof(chk_buf));
+ is_int(0, ret, "journal: read data integrity check");
+
+ /* Reopen log and re-read value. */
+ memset(tmpbuf, 0, sizeof(tmpbuf));
+ journal_release(journal);
+ journal_close(journal);
+ journal = journal_open(jfilename, fsize, 0);
+ ok(journal != NULL, "journal: open journal '%s'", jfilename);
+ journal_retain(journal);
+
+ journal_read(journal, chk_key, 0, tmpbuf);
+ ret = strncmp(chk_buf, tmpbuf, sizeof(chk_buf));
+ is_int(0, ret, "journal: read data integrity check after close/open");
+
+ /* Map journal entry. */
+ char *mptr = NULL;
+ memset(chk_buf, 0xde, sizeof(chk_buf));
+ ret = journal_map(journal, 0x12345, &mptr, sizeof(chk_buf));
+ ok(mptr && ret == 0, "journal: mapped journal entry");
+ if (ret != 0) {
+ skip_block(2, "No mapped journal");
+ } else {
+ /* Write to mmaped entry and unmap. */
+ memcpy(mptr, chk_buf, sizeof(chk_buf));
+ ret = journal_unmap(journal, 0x12345, mptr, 1);
+ ok(mptr && ret == 0, "journal: written to mapped entry and finished");
+
+ /* Compare mmaped entry. */
+ memset(tmpbuf, 0, sizeof(tmpbuf));
+ journal_read(journal, 0x12345, NULL, tmpbuf);
+ ret = strncmp(chk_buf, tmpbuf, sizeof(chk_buf));
+ ok(ret == 0, "journal: mapped entry data integrity check");
+
+ } /* end skip */
+
+ /* Make a transaction. */
+ uint64_t tskey = 0x75750000;
+ ret = journal_trans_begin(journal);
+ is_int(0, ret, "journal: TRANS begin");
+ for (int i = 0; i < 16; ++i) {
+ memset(tmpbuf, i, sizeof(tmpbuf));
+ journal_write(journal, tskey + i, tmpbuf, sizeof(tmpbuf));
+ }
+
+ /* Check if uncommited node exists. */
+ ret = journal_read(journal, tskey + rand() % 16, NULL, chk_buf);
+ ok(ret != 0, "journal: check for uncommited node");
+
+ /* Commit transaction. */
+ ret = journal_trans_commit(journal);
+ int read_ret = journal_read(journal, tskey + rand() % 16, NULL, chk_buf);
+ ok(ret == 0 && read_ret == 0, "journal: transaction commit");
+
+ /* Rollback transaction. */
+ tskey = 0x6B6B0000;
+ journal_trans_begin(journal);
+ for (int i = 0; i < 16; ++i) {
+ memset(tmpbuf, i, sizeof(tmpbuf));
+ journal_write(journal, tskey + i, tmpbuf, sizeof(tmpbuf));
+ }
+ ret = journal_trans_rollback(journal);
+ read_ret = journal_read(journal, tskey + rand() % 16, NULL, chk_buf);
+ ok(ret == 0 && read_ret != 0, "journal: transaction rollback");
+
+ /* Write random data. */
+ ret = 0;
+ for (int i = 0; i < 512; ++i) {
+ int key = i;
+ randstr(tmpbuf, sizeof(tmpbuf));
+ ret = journal_map(journal, key, &mptr, sizeof(tmpbuf));
+ if (ret != KNOT_EOK) {
+ diag("journal_map failed: %s", knot_strerror(ret));
+ break;
+ }
+ memcpy(mptr, tmpbuf, sizeof(tmpbuf));
+ if ((ret = journal_unmap(journal, key, mptr, 1)) != KNOT_EOK) {
+ diag("journal_unmap failed: %s", knot_strerror(ret));
+ break;
+ }
+
+ /* Store some key on the end. */
+ memset(chk_buf, 0, sizeof(chk_buf));
+ ret = journal_read(journal, key, 0, chk_buf);
+ if (ret != 0) {
+ diag("journal_map integrity check failed %s",
+ knot_strerror(ret));
+ break;
+ }
+ ret = strncmp(chk_buf, tmpbuf, sizeof(chk_buf));
+ if (ret != 0) {
+ diag("journal_map integrity check failed");
+ break;
+ }
+ }
+ is_int(0, ret, "journal: sustained mmap r/w");
+
+ /* Open + create journal. */
+ journal_release(journal);
+ journal_close(journal);
+ remove(jfilename);
+ journal = journal_open(jfilename, fsize, 0);
+ ok(journal != NULL, "journal: open+create from scratch '%s'", jfilename);
+
+ /* Close journal. */
+ journal_close(journal);
+
+ /* Delete journal. */
+ remove(jfilename);
+
+skip_all:
+ return 0;
+}
diff --git a/tests/resource.sh b/tests/resource.sh
new file mode 100755
index 0000000..497dfb8
--- /dev/null
+++ b/tests/resource.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# This script generates resource files.
+# Usage: ./resource.sh <file>
+# Resource file is printed to stdout.
+# Usable variables (<file> is stripped from path and extension):
+# const char *<file>_rc; // File content in binary format
+# const unsigned <file_rc_size; // File size
+# Examples:
+# (file: dumps/test.out content: "ahoj")
+# ./resource.sh dumps/test.out
+# static const unsigned test_rc_size = 4;
+# static const char test_rc[] = { 'a', 'h', 'o', 'j', '\0' };
+
+hd="hexdump -v -e"
+fmt="\"0\" \"x\" 1/1 \"%02X\" \", \""
+
+# Preparse source file name
+# header="${1%.*}_rc"
+header=$(basename ${1}_rc)
+
+# Get file size and dump content
+size=$(wc -c "${1}" | awk '{print $1}' 2>/dev/null)
+dump=$(${hd} "${fmt}" "${1}" 2>/dev/null)
+
+# Format file size variable
+echo "const unsigned ${header}_size = ${size};"
+
+# Format file content dump
+echo "const char ${header}[] = { "
+echo "${dump}0x00 };"
diff --git a/tests/rrl.c b/tests/rrl.c
new file mode 100644
index 0000000..1caa7c1
--- /dev/null
+++ b/tests/rrl.c
@@ -0,0 +1,208 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <tap/basic.h>
+
+#include "knot/server/rrl.h"
+#include "knot/server/dthreads.h"
+#include "knot/knot.h"
+#include "libknot/packet/response.h"
+#include "libknot/packet/query.h"
+#include "libknot/nameserver/name-server.h"
+#include "common/descriptor.h"
+#include "libknot/dnssec/random.h"
+
+/* Enable time-dependent tests. */
+//#define ENABLE_TIMED_TESTS
+#define RRL_SIZE 196613
+#define RRL_THREADS 8
+#define RRL_INSERTS (RRL_SIZE/(5*RRL_THREADS)) /* lf = 1/5 */
+#define RRL_LOCKS 64
+
+/* Disabled as default as it depends on random input.
+ * Table may be consistent even if some collision occur (and they may occur).
+ */
+#ifdef ENABLE_TIMED_TESTS
+struct bucketmap_t {
+ unsigned i;
+ uint64_t x;
+};
+
+/*! \brief Unit runnable. */
+struct runnable_data {
+ int passed;
+ rrl_table_t *rrl;
+ sockaddr_t *addr;
+ rrl_req_t *rq;
+ knot_zone_t *zone;
+};
+
+static void* rrl_runnable(void *arg)
+{
+ struct runnable_data* d = (struct runnable_data*)arg;
+ sockaddr_t addr;
+ memcpy(&addr, d->addr, sizeof(sockaddr_t));
+ int lock = -1;
+ uint32_t now = time(NULL);
+ struct bucketmap_t *m = malloc(RRL_INSERTS * sizeof(struct bucketmap_t));
+ for (unsigned i = 0; i < RRL_INSERTS; ++i) {
+ m[i].i = knot_random_uint32_t(UINT32_MAX);
+ addr.addr4.sin_addr.s_addr = m[i].i;
+ rrl_item_t *b = rrl_hash(d->rrl, &addr, d->rq, d->zone, now, &lock);
+ rrl_unlock(d->rrl, lock);
+ m[i].x = b->netblk;
+ }
+ for (unsigned i = 0; i < RRL_INSERTS; ++i) {
+ addr.addr4.sin_addr.s_addr = m[i].i;
+ rrl_item_t *b = rrl_hash(d->rrl, &addr, d->rq, d->zone, now, &lock);
+ rrl_unlock(d->rrl, lock);
+ if (b->netblk != m[i].x) {
+ d->passed = 0;
+ }
+ }
+ free(m);
+ return NULL;
+}
+
+static void rrl_hopscotch(struct runnable_data* rd)
+{
+ rd->passed = 1;
+ pthread_t thr[RRL_THREADS];
+ for (unsigned i = 0; i < RRL_THREADS; ++i) {
+ pthread_create(thr + i, NULL, &rrl_runnable, rd);
+ }
+ for (unsigned i = 0; i < RRL_THREADS; ++i) {
+ pthread_join(thr[i], NULL);
+ }
+}
+#endif
+
+int main(int argc, char *argv[])
+{
+ plan(10);
+
+ /* Prepare query. */
+ knot_packet_t *query = knot_packet_new();
+ if (knot_packet_set_max_size(query, 512) < 0) {
+ knot_packet_free(&query);
+ return KNOT_ERROR; /* Fatal */
+ }
+ knot_query_init(query);
+
+ knot_dname_t *qname = knot_dname_from_str("beef.");
+ int ret = knot_query_set_question(query, qname, KNOT_CLASS_IN, KNOT_RRTYPE_A);
+ knot_dname_free(&qname);
+ if (ret != KNOT_EOK) {
+ knot_packet_free(&query);
+ return KNOT_ERROR; /* Fatal */
+ }
+
+
+ /* Prepare response */
+ knot_nameserver_t *ns = knot_ns_create();
+ uint8_t rbuf[65535];
+ size_t rlen = sizeof(rbuf);
+ memset(rbuf, 0, sizeof(rbuf));
+ knot_ns_error_response_from_query(ns, query, KNOT_RCODE_NOERROR, rbuf, &rlen);
+
+ rrl_req_t rq;
+ rq.w = rbuf;
+ rq.len = rlen;
+ rq.query = query;
+ rq.flags = 0;
+
+ /* 1. create rrl table */
+ rrl_table_t *rrl = rrl_create(RRL_SIZE);
+ ok(rrl != NULL, "rrl: create");
+
+ /* 2. set rate limit */
+ uint32_t rate = 10;
+ rrl_setrate(rrl, rate);
+ is_int(rate, rrl_rate(rrl), "rrl: setrate");
+
+ /* 3. setlocks */
+ ret = rrl_setlocks(rrl, RRL_LOCKS);
+ is_int(KNOT_EOK, ret, "rrl: setlocks");
+
+ /* 4. N unlimited requests. */
+ knot_dname_t *apex = knot_dname_from_str("rrl.");
+ knot_zone_t *zone = knot_zone_new(knot_node_new(apex, NULL, 0));
+ sockaddr_t addr;
+ sockaddr_t addr6;
+ sockaddr_set(&addr, AF_INET, "1.2.3.4", 0);
+ sockaddr_set(&addr6, AF_INET6, "1122:3344:5566:7788::aabb", 0);
+ ret = 0;
+ for (unsigned i = 0; i < rate; ++i) {
+ if (rrl_query(rrl, &addr, &rq, zone) != KNOT_EOK ||
+ rrl_query(rrl, &addr6, &rq, zone) != KNOT_EOK) {
+ ret = KNOT_ELIMIT;
+ break;
+ }
+ }
+ is_int(0, ret, "rrl: unlimited IPv4/v6 requests");
+
+#ifdef ENABLE_TIMED_TESTS
+ /* 5. limited request */
+ ret = rrl_query(rrl, &addr, &rq, zone);
+ is_int(0, ret, "rrl: throttled IPv4 request");
+
+ /* 6. limited IPv6 request */
+ ret = rrl_query(rrl, &addr6, &rq, zone);
+ is_int(0, ret, "rrl: throttled IPv6 request");
+#else
+ skip_block(2, "Timed tests not enabled");
+#endif
+
+ /* 7. invalid values. */
+ ret = 0;
+ rrl_create(0); // NULL
+ ret += rrl_setrate(0, 0); // 0
+ ret += rrl_rate(0); // 0
+ ret += rrl_setlocks(0,0); // -1
+ ret += rrl_query(0, 0, 0, 0); // -1
+ ret += rrl_query(rrl, 0, 0, 0); // -1
+ ret += rrl_query(rrl, (void*)0x1, 0, 0); // -1
+ ret += rrl_destroy(0); // -1
+ is_int(-488, ret, "rrl: not crashed while executing functions on NULL context");
+
+#ifdef ENABLE_TIMED_TESTS
+ /* 8. hopscotch test */
+ struct runnable_data rd = {
+ 1, rrl, &addr, &rq, zone
+ };
+ rrl_hopscotch(&rd);
+ ok(rd.passed, "rrl: hashtable is ~ consistent");
+
+ /* 9. reseed */
+ is_int(0, rrl_reseed(rrl), "rrl: reseed");
+
+ /* 10. hopscotch after reseed. */
+ rrl_hopscotch(&rd);
+ ok(rd.passed, "rrl: hashtable is ~ consistent");
+#else
+ skip_block(3, "Timed tests not enabled");
+#endif
+
+ knot_dname_free(&apex);
+ knot_zone_deep_free(&zone);
+ knot_ns_destroy(&ns);
+ knot_packet_free(&query);
+ rrl_destroy(rrl);
+ return 0;
+}
diff --git a/tests/rrset.c b/tests/rrset.c
new file mode 100644
index 0000000..38d0f1d
--- /dev/null
+++ b/tests/rrset.c
@@ -0,0 +1,1458 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <config.h>
+#include <assert.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <tap/basic.h>
+
+#include "common/descriptor.h"
+#include "common/errcode.h"
+#include "common/print.h"
+#include "libknot/rrset.h"
+#include "libknot/util/wire.h"
+#include "common/mempattern.h"
+
+#if 0
+/*
+ * Unit implementation.
+ */
+
+enum rrset_test_const {
+ TEST_RRSET_COUNT = 14,
+ TEST_RDATA_COUNT = 10,
+ TEST_DNAME_COUNT = 11,
+
+ TEST_RDATA_A_LESS = 0,
+ TEST_RDATA_A_GT = 1,
+ TEST_RDATA_NS_LESS = 2,
+ TEST_RDATA_NS_GT = 3,
+ TEST_RDATA_MX_DNAME_LESS = 4,
+ TEST_RDATA_MX_DNAME_GT = 5,
+ TEST_RDATA_MX_BIN_LESS = 6,
+ TEST_RDATA_MX_BIN_GT = 7,
+ TEST_RDATA_MINFO1 = 8,
+ TEST_RDATA_MINFO2 = 9,
+
+ TEST_RRSET_A_LESS = 0,
+ TEST_RRSET_A_GT = 1,
+ TEST_RRSET_MERGE_UNIQUE1 = 0,
+ TEST_RRSET_MERGE_UNIQUE2 = 1,
+ TEST_RRSET_MERGE_RESULT1 = 10,
+ TEST_RRSET_NS_LESS = 2,
+ TEST_RRSET_NS_GT = 3,
+ TEST_RRSET_MX_BIN_LESS = 4,
+ TEST_RRSET_MX_BIN_GT = 5,
+ TEST_RRSET_MX_DNAME_LESS = 6,
+ TEST_RRSET_MX_DNAME_GT = 7,
+ TEST_RRSET_MINFO = 8,
+ TEST_RRSET_MINFO_MULTIPLE1 = 9,
+ TEST_RRSET_MINFO_MULTIPLE2 = 13,
+ TEST_RRSET_OWNER_LESS = 11,
+ TEST_RRSET_OWNER_GT = 12,
+
+ CHECK_LAST_INDEX = 0,
+ OMMIT_LAST_INDEX = 1,
+ TEST_DNAME_GENERIC = 0,
+ TEST_DNAME_LESS = 1,
+ TEST_DNAME_GREATER = 2
+};
+
+static char *test_dname_strings[TEST_DNAME_COUNT] = {
+ "a.dname.com.",
+ "b.dname.com.",
+ "c.dname.com.",
+ "d.dname.com.",
+ "e.dname.com.",
+ "f.dname.com.",
+ "ns1.nic.cz.",
+ "ns2.nic.cz.",
+ "ns3.nic.cz.",
+ "ns4.nic.cz.",
+ "ns5.nic.cz."
+};
+
+static knot_dname_t *test_dnames[TEST_DNAME_COUNT];
+
+struct test_rdata {
+ uint8_t *rdata; // RDATA in knot internal format
+ uint8_t *wire; // RDATA in wireformat
+ uint16_t size; // RDATA internal size
+ uint16_t wire_size; // Wireformat size
+};
+
+typedef struct test_rdata test_rdata_t;
+
+struct test_rrset {
+ int owner_id; // Owners have to be dynamically allocated, IDs used to connect.
+ knot_rrset_t rrset; // Dynamically created knot_rrset_t structure.
+ uint8_t *header_wire; // Owner, class, TTL.
+ size_t header_wire_size; // Header size.
+ size_t rr_count; // RR count.
+ int test_rdata_ids[16]; // RDATA IDs - will be used to assign RDATA.
+ test_rdata_t **test_rdata; // Dynamically created test RDATA.
+};
+
+typedef struct test_rrset test_rrset_t;
+
+/* Artificial RDATA definitions: */
+static test_rdata_t test_rdata_array[TEST_RDATA_COUNT] = {
+ [TEST_RDATA_A_LESS] = {(uint8_t *)"\x1\x1\x1\0", (uint8_t *)"\x1\x1\x1\0", 4, 4},
+ [TEST_RDATA_A_GT] = {(uint8_t *)"\x1\x1\x1\1", (uint8_t *)"\x1\x1\x1\1", 4, 4},
+ [TEST_RDATA_NS_LESS] = {NULL, NULL, sizeof(knot_dname_t *), 0},
+ [TEST_RDATA_NS_GT] = {NULL, NULL, sizeof(knot_dname_t *), 0},
+ [TEST_RDATA_MX_DNAME_LESS] = {NULL, NULL, sizeof(knot_dname_t *) + 2, 0},
+ [TEST_RDATA_MX_DNAME_GT] = {NULL, NULL, sizeof(knot_dname_t *) + 2, 0},
+ [TEST_RDATA_MX_BIN_LESS] = {NULL, NULL, sizeof(knot_dname_t *) + 2, 0},
+ [TEST_RDATA_MX_BIN_GT] = {NULL, NULL, sizeof(knot_dname_t *) + 2, 0},
+ [TEST_RDATA_MINFO1] = {NULL, NULL, sizeof(knot_dname_t *) * 2, 0},
+ [TEST_RDATA_MINFO2] = {NULL, NULL, sizeof(knot_dname_t *) * 2, 0},
+};
+
+
+test_rrset_t test_rrset_array[TEST_RRSET_COUNT] = {
+ [TEST_RRSET_A_LESS] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_A_LESS}, NULL},
+ [TEST_RRSET_A_GT] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_A_GT}, NULL},
+ [TEST_RRSET_NS_LESS] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_NS, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_NS_LESS}, NULL},
+ [TEST_RRSET_NS_GT] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_NS, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_NS_GT}, NULL},
+ [TEST_RRSET_MX_DNAME_LESS] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_MX, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_MX_DNAME_LESS}, NULL},
+ [TEST_RRSET_MX_DNAME_GT] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_MX, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_MX_DNAME_GT}, NULL},
+ [TEST_RRSET_MX_BIN_LESS] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_MX, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_MX_BIN_LESS}, NULL},
+ [TEST_RRSET_MX_BIN_GT] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_MX, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_MX_BIN_GT}, NULL},
+ [TEST_RRSET_MINFO] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_MINFO, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_MINFO1}, NULL},
+ [TEST_RRSET_MINFO_MULTIPLE1] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_MINFO, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 2, {TEST_RDATA_MINFO1, TEST_RDATA_MINFO2}, NULL},
+ [TEST_RRSET_MINFO_MULTIPLE2] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_MINFO, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 2, {TEST_RDATA_MINFO2, TEST_RDATA_MINFO1}, NULL},
+ [TEST_RRSET_MERGE_RESULT1] = {TEST_DNAME_GENERIC, {NULL, KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 2, {TEST_RDATA_A_LESS, TEST_RDATA_A_GT}, NULL},
+ [TEST_RRSET_OWNER_LESS] = {TEST_DNAME_LESS, {NULL, KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_A_LESS}, NULL},
+ [TEST_RRSET_OWNER_GT] = {TEST_DNAME_GREATER, {NULL, KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600, NULL, NULL, 0, NULL},
+ NULL, 0, 1, {TEST_RDATA_A_LESS}, NULL}
+};
+
+static void create_test_dnames()
+{
+ for (int i = 0; i < TEST_DNAME_COUNT; i++) {
+ test_dnames[i] =
+ knot_dname_from_str(test_dname_strings[i],
+ strlen(test_dname_strings[i]));
+ }
+}
+
+static void init_test_rdata_with_dname(uint8_t **rdata, uint16_t *rdata_size,
+ uint8_t **wire, uint16_t *wire_size,
+ size_t pos, size_t wire_pos,
+ size_t alloc_size,
+ size_t wire_alloc_size,
+ knot_dname_t *dname)
+{
+ if (pos == 0) {
+ *rdata = xmalloc(alloc_size);
+ *rdata_size = 0;
+ *wire = xmalloc(wire_alloc_size);
+ *wire_size = 0;
+ }
+ memcpy(*rdata + pos, &dname, sizeof(knot_dname_t *));
+ *rdata_size += sizeof(knot_dname_t *);
+ memcpy(*wire + wire_pos, dname, knot_dname_size(dname));
+ *wire_size += knot_dname_size(dname);
+}
+
+static void init_test_rdata_with_binary(uint8_t **rdata, uint16_t *rdata_size,
+ uint8_t **wire, uint16_t *wire_size,
+ size_t pos, size_t wire_pos,
+ size_t alloc_size,
+ size_t wire_alloc_size,
+ const void *data, size_t data_size)
+{
+ if (pos == 0) {
+ // New structure, allocate.
+ *rdata = xmalloc(alloc_size);
+ *rdata_size = 0;
+ *wire = xmalloc(wire_alloc_size);
+ *wire_size = 0;
+ }
+ memcpy(*rdata + pos, data, data_size);
+ *rdata_size += data_size;
+ memcpy(*wire + wire_pos, data, data_size);
+ *wire_size += data_size;
+}
+
+static void create_test_rdata()
+{
+ /* NS, MX and MINFO types need an init. */
+
+ /* NS RDATA DNAME = a.dname.com. */
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_NS_LESS].rdata,
+ &test_rdata_array[TEST_RDATA_NS_LESS].size,
+ &test_rdata_array[TEST_RDATA_NS_LESS].wire,
+ &test_rdata_array[TEST_RDATA_NS_LESS].wire_size,
+ 0, 0, sizeof(knot_dname_t *),
+ knot_dname_size(test_dnames[0]), test_dnames[0]);
+
+ /* NS RDATA DNAME = b.dname.com. */
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_NS_GT].rdata,
+ &test_rdata_array[TEST_RDATA_NS_GT].size,
+ &test_rdata_array[TEST_RDATA_NS_GT].wire,
+ &test_rdata_array[TEST_RDATA_NS_GT].wire_size,
+ 0, 0, sizeof(knot_dname_t *),
+ knot_dname_size(test_dnames[1]), test_dnames[1]);
+
+ /* MX RDATA - short = 10 DNAME = a.dname.com. */
+ uint16_t id = htobe16(10);
+ init_test_rdata_with_binary(&test_rdata_array[TEST_RDATA_MX_DNAME_LESS].rdata,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_LESS].size,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_LESS].wire,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_LESS].wire_size,
+ 0, 0, sizeof(knot_dname_t *) + 2,
+ knot_dname_size(test_dnames[1]) + 2, &id, 2);
+
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_MX_DNAME_LESS].rdata,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_LESS].size,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_LESS].wire,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_LESS].wire_size,
+ 2, 2, sizeof(knot_dname_t *),
+ knot_dname_size(test_dnames[1]), test_dnames[0]);
+
+ /* MX RDATA - short = 10 DNAME = b.dname.com. */
+ init_test_rdata_with_binary(&test_rdata_array[TEST_RDATA_MX_DNAME_GT].rdata,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_GT].size,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_GT].wire,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_GT].wire_size,
+ 0, 0, sizeof(knot_dname_t *) + 2,
+ knot_dname_size(test_dnames[1]) + 2, &id, 2);
+
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_MX_DNAME_GT].rdata,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_GT].size,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_GT].wire,
+ &test_rdata_array[TEST_RDATA_MX_DNAME_GT].wire_size,
+ 2, 2, sizeof(knot_dname_t *),
+ knot_dname_size(test_dnames[1]), test_dnames[1]);
+
+ test_rdata_array[TEST_RDATA_MX_BIN_LESS] = test_rdata_array[TEST_RDATA_MX_DNAME_LESS];
+
+ /* MX RDATA - short = 20 DNAME = b.dname.com. */
+ id = htobe16(20);
+ init_test_rdata_with_binary(&test_rdata_array[TEST_RDATA_MX_BIN_GT].rdata,
+ &test_rdata_array[TEST_RDATA_MX_BIN_GT].size,
+ &test_rdata_array[TEST_RDATA_MX_BIN_GT].wire,
+ &test_rdata_array[TEST_RDATA_MX_BIN_GT].wire_size,
+ 0, 0, sizeof(knot_dname_t *) + 2,
+ knot_dname_size(test_dnames[1]) + 2, &id, 2);
+
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_MX_BIN_GT].rdata,
+ &test_rdata_array[TEST_RDATA_MX_BIN_GT].size,
+ &test_rdata_array[TEST_RDATA_MX_BIN_GT].wire,
+ &test_rdata_array[TEST_RDATA_MX_BIN_GT].wire_size,
+ 2, 2, sizeof(knot_dname_t *),
+ knot_dname_size(test_dnames[1]), test_dnames[1]);
+
+ /* MINFO RRs. */
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_MINFO1].rdata,
+ &test_rdata_array[TEST_RDATA_MINFO1].size,
+ &test_rdata_array[TEST_RDATA_MINFO1].wire,
+ &test_rdata_array[TEST_RDATA_MINFO1].wire_size,
+ 0, 0, sizeof(knot_dname_t *) * 2,
+ knot_dname_size(test_dnames[0]) + knot_dname_size(test_dnames[1]),
+ test_dnames[0]);
+
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_MINFO1].rdata,
+ &test_rdata_array[TEST_RDATA_MINFO1].size,
+ &test_rdata_array[TEST_RDATA_MINFO1].wire,
+ &test_rdata_array[TEST_RDATA_MINFO1].wire_size,
+ sizeof(knot_dname_t *), knot_dname_size(test_dnames[0]),
+ sizeof(knot_dname_t *),
+ knot_dname_size(test_dnames[1]), test_dnames[1]);
+
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_MINFO2].rdata,
+ &test_rdata_array[TEST_RDATA_MINFO2].size,
+ &test_rdata_array[TEST_RDATA_MINFO2].wire,
+ &test_rdata_array[TEST_RDATA_MINFO2].wire_size,
+ 0, 0, sizeof(knot_dname_t *) * 2,
+ knot_dname_size(test_dnames[2]) + knot_dname_size(test_dnames[3]),
+ test_dnames[2]);
+
+ init_test_rdata_with_dname(&test_rdata_array[TEST_RDATA_MINFO2].rdata,
+ &test_rdata_array[TEST_RDATA_MINFO2].size,
+ &test_rdata_array[TEST_RDATA_MINFO2].wire,
+ &test_rdata_array[TEST_RDATA_MINFO2].wire_size,
+ sizeof(knot_dname_t *), knot_dname_size(test_dnames[2]),
+ sizeof(knot_dname_t *),
+ knot_dname_size(test_dnames[3]), test_dnames[3]);
+}
+
+static void create_test_rrsets()
+{
+ for (int i = 0; i < TEST_RRSET_COUNT; i++) {
+ /* Create memory representation. */
+ test_rrset_t *test_rrset = &test_rrset_array[i];
+ /* Assign owner. */
+ test_rrset->rrset.owner = test_dnames[test_rrset->owner_id];
+
+ /* Create wire representation. */
+
+ /* Create header wire. */
+ test_rrset->header_wire_size = knot_dname_size(test_rrset->rrset.owner) + 8;
+ test_rrset->header_wire =
+ xmalloc(test_rrset->header_wire_size);
+ /* Copy owner wire to header wire. */
+ memcpy(test_rrset->header_wire, test_rrset->rrset.owner,
+ knot_dname_size(test_rrset->rrset.owner));
+ /* Copy type to wire. */
+ size_t offset = knot_dname_size(test_rrset->rrset.owner);
+ knot_wire_write_u16(test_rrset->header_wire + offset,
+ test_rrset->rrset.type);
+ offset += sizeof(uint16_t);
+ /* Copy class to wire. */
+ knot_wire_write_u16(test_rrset->header_wire + offset,
+ test_rrset->rrset.rclass);
+ offset += sizeof(uint16_t);
+ /* Copy TTL to wire. */
+ knot_wire_write_u32(test_rrset->header_wire + offset,
+ test_rrset->rrset.ttl);
+
+ /* Create RDATA. */
+ test_rrset->test_rdata =
+ xmalloc(sizeof(void *) * test_rrset->rr_count);
+ size_t actual_length = 0;
+ size_t rdlength = 0;
+ test_rrset->rrset.rdata_count = test_rrset->rr_count;
+ for (int j = 0; j < test_rrset->rr_count; j++) {
+ test_rrset->test_rdata[j] =
+ &test_rdata_array[test_rrset->test_rdata_ids[j]];
+ rdlength += test_rrset->test_rdata[j]->wire_size;
+ actual_length += test_rrset->test_rdata[j]->size;
+ }
+ /* Assign RDATA (including indices). */
+ offset = 0;
+ test_rrset->rrset.rdata = xmalloc(actual_length);
+ test_rrset->rrset.rdata_indices =
+ xmalloc(sizeof(uint32_t) * test_rrset->rr_count);
+ for (int j = 0; j < test_rrset->rr_count; j++) {
+ if (j > 0) {
+ test_rrset->rrset.rdata_indices[j - 1] =
+ test_rrset->test_rdata[j]->size;
+ }
+
+ memcpy(test_rrset->rrset.rdata + offset,
+ test_rrset->test_rdata[j]->rdata,
+ test_rrset->test_rdata[j]->size);
+ offset += test_rrset->test_rdata[j]->size;
+ }
+ /* Store sum of indices to the last index. */
+ test_rrset->rrset.rdata_indices[test_rrset->rr_count - 1] =
+ offset;
+ }
+}
+
+static int check_rrset_values(const knot_rrset_t *rrset,
+ knot_dname_t *dname, uint16_t type,
+ uint16_t rclass, uint16_t ttl, uint16_t rr_count)
+{
+ int errors = 0;
+
+ if (!knot_dname_is_equal(rrset->owner, dname)) {
+ diag("Wrong DNAME in the created RRSet.\n");
+ ++errors;
+ }
+
+ if (rrset->type != type) {
+ diag("Wrong type in the created RRSet.\n");
+ ++errors;
+ }
+
+ if (rrset->rclass != rclass) {
+ diag("Wrong class in the created RRSet.\n");
+ ++errors;
+ }
+
+ if (rrset->ttl != ttl) {
+ diag("Wrong TTL in the created RRSet.\n");
+ ++errors;
+ }
+
+ if (rrset->rdata_count!= rr_count) {
+ diag("Wrong RR count in the created RRSet.\n");
+ ++errors;
+ }
+
+ return errors;
+}
+
+static int test_rrset_new()
+{
+ /* Actual values don't matter in this case. */
+ knot_dname_t *dname = test_dnames[0];
+ uint16_t type = 1;
+ uint16_t rclass = 1;
+ uint32_t ttl = 1;
+
+ knot_rrset_t *rrset = knot_rrset_new(dname, type, rclass, ttl);
+ if (rrset == NULL) {
+ diag("Failed to create new RRSet.\n");
+ return 0;
+ }
+
+ int check_errors = check_rrset_values(rrset, dname, type, rclass, ttl,
+ 0);
+ free(rrset);
+
+ return (check_errors == 0);
+}
+
+static int test_rrset_create_rdata()
+{
+ /* Two cases need to be tested - empty RRSet and non-empty RRSet. */
+
+
+ knot_rrset_t *rrset = knot_rrset_new(test_dnames[0], 0, 0, 0);
+ assert(rrset);
+
+ /*
+ * Again, actual data are not crutial, we need to see if indices
+ * are changed accordingly and so on, but the data are not important.
+ */
+ uint8_t *write_pointer =
+ knot_rrset_create_rdata(rrset,
+ test_rdata_array[0].size);
+ if (write_pointer == NULL) {
+ diag("Could not create data of size %d\n",
+ test_rdata_array[0].size);
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Write dummy data. */
+ memcpy(write_pointer, test_rdata_array[0].rdata,
+ test_rdata_array[0].size);
+
+ /* Check that indices are set right. */
+ if (rrset->rdata_indices[0] != test_rdata_array[0].size) {
+ diag("Wrong RDATA index after inserting RDATA to RRSet.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Rdata count must be equal to one. */
+ if (rrset->rdata_count != 1) {
+ diag("Wrong RDATA count after inserting RDATA to RRSet.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Make sure that the data in the RRSet are the same. */
+ int ret = memcmp(rrset->rdata, test_rdata_array[0].rdata,
+ test_rdata_array[0].size);
+ if (ret) {
+ diag("Wrong data inserted into RRSet.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Insert second item - all other inserts will do the same thing. */
+ write_pointer = knot_rrset_create_rdata(rrset,
+ test_rdata_array[1].size);
+ if (write_pointer == NULL) {
+ diag("Could not create data of size %d\n",
+ test_rdata_array[1].size);
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Write dummy data. */
+ memcpy(write_pointer, test_rdata_array[1].rdata,
+ test_rdata_array[1].size);
+
+ /* Check that indices are set right. */
+ if (rrset->rdata_indices[0] != test_rdata_array[1].size) {
+ diag("Wrong RDATA first index after "
+ "inserting RDATA to RRSet.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ if (rrset->rdata_indices[1] !=
+ test_rdata_array[0].size + test_rdata_array[1].size) {
+ diag("Wrong RDATA last index after "
+ "inserting RDATA to RRSet.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Rdata count must be equal to two. */
+ if (rrset->rdata_count != 2) {
+ diag("Wrong RDATA count after inserting second "
+ "RDATA to RRSet.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Make sure that the data in the RRSet are the same. */
+ ret = memcmp(rrset->rdata + test_rdata_array[0].size,
+ test_rdata_array[1].rdata, test_rdata_array[1].size);
+ if (ret) {
+ diag("Wrong data inserted into RRSet.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ /* Test that data of length 0 are not inserted. */
+ void *ret_ptr = knot_rrset_create_rdata(rrset, 0);
+ if (ret_ptr != NULL) {
+ diag("Empty RDATA inserted.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 1;
+}
+
+static int test_rrset_rdata_item_size()
+{
+ /* Test that types containing DNAMEs only return OK values. */
+ knot_rrset_t *rrset =
+ &test_rrset_array[TEST_RRSET_MINFO_MULTIPLE1].rrset;
+ if (rrset_rdata_item_size(rrset, 0) != sizeof(knot_dname_t *) * 2) {
+ diag("Wrong item length read from RRSet (first item).\n");
+ return 0;
+ }
+
+ if (rrset_rdata_item_size(rrset, 1) != sizeof(knot_dname_t *) * 2) {
+ diag("Wrong item length read from RRSet (last item).\n");
+ return 0;
+ }
+
+ if (rrset_rdata_size_total(rrset) != sizeof(knot_dname_t *) * 4) {
+ diag("Wrong total size returned (MINFO RRSet)\n");
+ return 0;
+ }
+
+ rrset = &test_rrset_array[TEST_RRSET_A_GT].rrset;
+ if (rrset_rdata_item_size(rrset, 0) != 4) {
+ diag("Wrong item length read from A RRSet.\n");
+ return 0;
+ }
+
+ rrset = &test_rrset_array[TEST_RRSET_MX_BIN_GT].rrset;
+ if (rrset_rdata_item_size(rrset, 0) != 2 + sizeof(knot_dname_t *)) {
+ diag("Wrong item length read from A RRSet.\n");
+ return 0;
+ }
+
+ knot_rrset_t *rrset1 = knot_rrset_new(rrset->owner,
+ KNOT_RRTYPE_TXT, KNOT_CLASS_IN,
+ 3600);
+
+ knot_rrset_create_rdata(rrset1, 16);
+ knot_rrset_add_rdata(rrset1,
+ (uint8_t *)"thesearesomedatathatdonotmatter", 25);
+ knot_rrset_create_rdata(rrset1, 38);
+
+ if (rrset_rdata_item_size(rrset1, 0) != 16) {
+ diag("Wrong item lenght in read (first).\n");
+ knot_rrset_deep_free(&rrset1, 1, 1);
+ return 0;
+ }
+
+ if (rrset_rdata_item_size(rrset1, 1) != 25) {
+ diag("Wrong item lenght in read (middle).\n");
+ knot_rrset_deep_free(&rrset1, 1, 1);
+ return 0;
+ }
+
+ if (rrset_rdata_item_size(rrset1, 2) != 38) {
+ diag("Wrong item lenght in read (last).\n");
+ knot_rrset_deep_free(&rrset1, 1, 1);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&rrset1, 1, 1);
+ return 1;
+}
+
+static int test_rrset_get_rdata()
+{
+ knot_rrset_t *rrset = knot_rrset_new(test_dnames[0],
+ KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 3600);
+ assert(rrset);
+ uint8_t *ref_pointer = knot_rrset_create_rdata(rrset, 16);
+ memcpy(ref_pointer, "badcafecafebabee", 16);
+ uint8_t *pointer = knot_rrset_get_rdata(rrset, 0);
+ if (pointer != ref_pointer) {
+ diag("Could not get RDATA from RRSet (%p vs %p).\n",
+ pointer, ref_pointer);
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ int ret = memcmp(pointer, ref_pointer, 16);
+ if (ret) {
+ diag("Got bad RDATA from RRSet (comparison failed).\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ uint8_t *ref_pointer2 = knot_rrset_create_rdata(rrset, 16);
+ memcpy(ref_pointer2, "foobarfoobarfoob", 16);
+ pointer = knot_rrset_get_rdata(rrset, 1);
+ if (pointer != ref_pointer2) {
+ diag("Could not ger RDATA from RRSet (%p vs %p).\n",
+ pointer, ref_pointer2);
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ ret = memcmp(pointer, ref_pointer2, 16);
+ if (ret) {
+ diag("Got bad RDATA from RRSet (comparison failed).\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 1;
+}
+
+static int test_rrset_shallow_copy()
+{
+ for (int i = 0; i < TEST_RRSET_COUNT; i++) {
+ knot_rrset_t *rrset_copy = NULL;
+ knot_rrset_t *rrset = &test_rrset_array[i].rrset;
+ int ret = knot_rrset_shallow_copy(rrset,
+ &rrset_copy);
+ if (ret != KNOT_EOK) {
+ knot_rrset_free(&rrset_copy);
+ diag("Could not copy RRSet.\n");
+ return 0;
+ }
+
+ /* Check that created RRSet has the same as the old one. */
+ int errors = check_rrset_values(rrset_copy, rrset->owner, rrset->type,
+ rrset->rclass, rrset->ttl,
+ rrset->rdata_count);
+ if (errors) {
+ knot_rrset_free(&rrset_copy);
+ return 0;
+ }
+
+ /* Check that created RRSet has the same RDATA. */
+ if (rrset->rdata != rrset_copy->rdata) {
+ diag("RDATA in the new RRSet do not match.\n");
+ knot_rrset_free(&rrset_copy);
+ return 0;
+ }
+
+ /* Check that RDATA indices are the same. */
+ if (rrset->rdata_indices != rrset_copy->rdata_indices) {
+ diag("RDATA indices in the new RRSet do not match.\n");
+ knot_rrset_free(&rrset_copy);
+ return 0;
+ }
+
+ knot_rrset_free(&rrset_copy);
+ }
+
+ return 1;
+}
+
+static int test_rrset_deep_copy()
+{
+ for (int i = 0; i < TEST_RRSET_COUNT; i++) {
+ knot_rrset_t *rrset_copy = NULL;
+ knot_rrset_t *rrset = &test_rrset_array[i].rrset;
+ int ret = knot_rrset_deep_copy(rrset, &rrset_copy);
+ if (ret != KNOT_EOK) {
+ diag("Could not copy RRSet.\n");
+ return 0;
+ }
+
+ /* Check that created RRSet has the same as the old one. */
+ int errors = check_rrset_values(rrset_copy, rrset->owner, rrset->type,
+ rrset->rclass, rrset->ttl,
+ rrset->rdata_count);
+ if (errors) {
+ knot_rrset_deep_free(&rrset_copy, 1, 1);
+ return 0;
+ }
+
+ /* Check that RDATA indices contain the same data. */
+ ret = memcmp(rrset->rdata_indices, rrset_copy->rdata_indices,
+ rrset->rdata_count);
+ if (ret) {
+ diag("Copied RRSet has different RDATA indices.\n");
+ knot_rrset_deep_free(&rrset_copy, 1, 1);
+ return 0;
+ }
+
+ /*
+ * Go through RDATA and compare blocks. Cannot compare the whole thing
+ * since DNAMEs are copied as well and will have different address.
+ */
+ ret = knot_rrset_rdata_equal(rrset, rrset_copy);
+ if (!ret) {
+ diag("Copied RRSet has different RDATA.\n");
+ knot_rrset_deep_free(&rrset_copy, 1, 1);
+ return 0;
+ }
+ knot_rrset_deep_free(&rrset_copy, 1, 1);
+ }
+
+ return 1;
+}
+
+static int test_rrset_to_wire()
+{
+ size_t wire_size = 65535;
+ uint8_t wire[wire_size];
+ uint16_t rr_count = 0;
+
+ /* Test correct conversions. */
+ for (int i = 0; i < TEST_RRSET_COUNT; i++) {
+ memset(wire, 0, wire_size);
+ wire_size = 65535;
+ /* Convert to wire. */
+ int ret = knot_rrset_to_wire(&test_rrset_array[i].rrset, wire,
+ &wire_size, 65535, &rr_count, NULL);
+ if (ret != KNOT_EOK) {
+ diag("Could not convert RRSet to wire (%s).\n",
+ knot_strerror(ret));
+ return 0;
+ }
+
+ if (rr_count != test_rrset_array[i].rrset.rdata_count) {
+ diag("Wrong number of RRs converted.\n");
+ return 0;
+ }
+
+ size_t offset = 0;
+ for (int j = 0; j < rr_count; ++j) {
+ /* Check that header is OK. */
+ ret = memcmp(wire + offset,
+ test_rrset_array[i].header_wire,
+ test_rrset_array[i].header_wire_size);
+ if (ret) {
+ diag("Header of RRSet %d, RR %d is wrongly converted.\n",
+ i, j);
+ return 0;
+ }
+
+ offset += test_rrset_array[i].header_wire_size;
+ /* Check RDLENGTH. */
+ uint16_t rdlength = knot_wire_read_u16(wire + offset);
+ if (rdlength != test_rrset_array[i].test_rdata[j]->wire_size) {
+ diag("Wrong RDLENGTH\n");
+ return 0;
+ }
+ offset += sizeof(uint16_t);
+
+ /* Check that the RDATA are OK. */
+ ret = memcmp(wire + offset,
+ test_rrset_array[i].test_rdata[j]->wire,
+ rdlength);
+ if (ret) {
+ diag("RDATA of RRSet %d, RR %d, "
+ "are wrongly converted. Type=%"PRIu16"\n",
+ i, j, test_rrset_array[i].rrset.type);
+ return 0;
+ }
+ offset += rdlength;
+ }
+
+ if (offset != wire_size) {
+ diag("Wrong wire size, in RRSet=%d "
+ "(should be=%d, is=%d).\n", i,
+ offset, wire_size);
+ return 0;
+ }
+ }
+
+ /* Check that function does not crash if given small wire. */
+ int ret = knot_rrset_to_wire(&test_rrset_array[0].rrset, wire,
+ &wire_size, 5, &rr_count, NULL);
+ if (ret != KNOT_ESPACE) {
+ diag("RRSet was converted to wire even though twe wire was"
+ " not big enough.\n");
+ return 0;
+ }
+ /* RDATA do not fit. */
+ ret = knot_rrset_to_wire(&test_rrset_array[0].rrset, wire,
+ &wire_size, 25, &rr_count, NULL);
+ if (ret != KNOT_ESPACE) {
+ diag("RRSet was converted to wire even though twe wire was"
+ " not big enough.\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int test_rrset_merge()
+{
+ knot_rrset_t *merge_to;
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset,
+ &merge_to);
+ knot_rrset_t *merge_from;
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE2].rrset,
+ &merge_from);
+ assert(merge_to);
+ assert(merge_from);
+ int ret = knot_rrset_merge(merge_to, merge_from);
+ if (ret != KNOT_EOK) {
+ diag("Could not merge RRSets.\n");
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+ return 0;
+ }
+
+ //TODO check that merge operation does not change second rr
+ //TODO check that two RRSet that are not mergable will not merge
+ if (!knot_rrset_equal(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE2].rrset,
+ merge_from,
+ KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Merge corrupted second RRSet.\n");
+ return 0;
+ }
+
+ if (merge_to->rdata_count !=
+ test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset.rdata_count +
+ merge_from->rdata_count) {
+ diag("Not all RDATA were merged.\n");
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+ return 0;
+ }
+
+ /* Check that the first RRSet now contains RDATA from the second. */
+ /* Indices first. */
+ ret = memcmp(merge_to->rdata_indices,
+ test_rrset_array[TEST_RRSET_MERGE_RESULT1].rrset.rdata_indices,
+ merge_to->rdata_count);
+ if (ret) {
+ diag("Merge operation corrupted the first RRSet's indices.\n");
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+ return 0;
+ }
+
+ /* Check actual RDATA. */
+ ret = knot_rrset_rdata_equal(merge_to,
+ &test_rrset_array[TEST_RRSET_MERGE_RESULT1].rrset);
+ if (!ret) {
+ diag("Merged RDATA are wrong.\n");
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+
+ return 1;
+}
+
+static int test_rrset_merge_sort()
+{
+ /* Test that merge of two identical RRSets results in no-op. */
+ knot_rrset_t *merge_to = NULL;
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset,
+ &merge_to);
+ knot_rrset_t *merge_from = NULL;
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset,
+ &merge_from);
+ int merged, removed_rrs;
+ int ret = knot_rrset_merge_sort(merge_to, merge_from, &merged, &removed_rrs);
+ if (ret != KNOT_EOK) {
+ diag("Merge of identical RRSets failed.\n");
+ return 0;
+ }
+
+ if (!knot_rrset_equal(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset,
+ merge_to, KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Merge corrupted first RRSet.\n");
+ return 0;
+ }
+
+ if (!knot_rrset_equal(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset,
+ merge_from, KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Merge corrupted second RRSet.\n");
+ return 0;
+ }
+
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+
+ /* Merge normal, non-duplicated RRSets. */
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset,
+ &merge_to);
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE2].rrset,
+ &merge_from);
+ assert(merge_to);
+ assert(merge_from);
+
+ ret = knot_rrset_merge_sort(merge_to, merge_from, &merged,
+ &removed_rrs);
+ if (ret != KNOT_EOK) {
+ diag("Merge of identical RRSets failed.\n");
+ return 0;
+ }
+
+ if (!knot_rrset_equal(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE2].rrset,
+ merge_from,
+ KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Merge corrupted second RRSet.\n");
+ return 0;
+ }
+
+ if (!knot_rrset_equal(&test_rrset_array[TEST_RRSET_MERGE_RESULT1].rrset,
+ merge_to,
+ KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Merge did not create correct RDATA.\n");
+ return 0;
+ }
+
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+
+ /* Merge RRSets with both duplicated and unique RDATAs. */
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_UNIQUE1].rrset,
+ &merge_to);
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MERGE_RESULT1].rrset,
+ &merge_from);
+ assert(merge_to);
+ assert(merge_from);
+
+ ret = knot_rrset_merge_sort(merge_to, merge_from, &merged,
+ &removed_rrs);
+ if (ret != KNOT_EOK) {
+ diag("Merge of identical RRSets failed.\n");
+ return 0;
+ }
+
+ if (!knot_rrset_equal(&test_rrset_array[TEST_RRSET_MERGE_RESULT1].rrset,
+ merge_from,
+ KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Merge corrupted second RRSet.\n");
+ return 0;
+ }
+
+ if (!knot_rrset_equal(&test_rrset_array[TEST_RRSET_MERGE_RESULT1].rrset,
+ merge_to,
+ KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Merge did not create correct RDATA.\n");
+ return 0;
+ }
+
+ knot_rrset_deep_free(&merge_to, 1, 1);
+ knot_rrset_deep_free(&merge_from, 1, 1);
+
+ return 1;
+}
+
+static int test_rrset_equal()
+{
+ /* Test pointer comparison. */
+ int ret = knot_rrset_equal((knot_rrset_t *)0xdeadbeef,
+ (knot_rrset_t *)0xdeadbeef,
+ KNOT_RRSET_COMPARE_PTR);
+ if (!ret) {
+ diag("Pointer comparison failed (1).\n");
+ return 0;
+ }
+
+ ret = knot_rrset_equal((knot_rrset_t *)0xdeadbeef,
+ (knot_rrset_t *)0xcafebabe,
+ KNOT_RRSET_COMPARE_PTR);
+ if (ret) {
+ diag("Pointer comparison failed (0).\n");
+ return 0;
+ }
+
+ /* Create equal RRSets. */
+ knot_rrset_t *rrs1 = NULL;
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_A_GT].rrset,
+ &rrs1);
+ knot_rrset_t *rrs2 = &test_rrset_array[TEST_RRSET_A_GT].rrset;
+ /* Test header comparison. */
+ ret = knot_rrset_equal(rrs1, rrs2, KNOT_RRSET_COMPARE_HEADER);
+ if (!ret) {
+ diag("Header comparison failed (Header equal).\n");
+ knot_rrset_deep_free(&rrs1, 1, 1);
+ return 0;
+ }
+ /* Change DNAME. */
+ knot_rrset_set_owner(rrs1, test_dnames[4]);
+ ret = knot_rrset_equal(rrs1, rrs2, KNOT_RRSET_COMPARE_HEADER);
+ if (ret) {
+ char *owner1 = knot_dname_to_str(rrs1->owner);
+ char *owner2 = knot_dname_to_str(rrs2->owner);
+ diag("Header comparison failed "
+ "(DNAMEs different (%s %s), but ret=%d).\n", owner1,
+ owner2, ret);
+ rrs1->owner = test_dnames[0];
+ knot_rrset_deep_free(&rrs1, 1, 1);
+ free(owner1);
+ free(owner2);
+ return 0;
+ }
+ rrs1->owner = test_dnames[0];
+ /* Change CLASS. */
+ rrs1->rclass = KNOT_CLASS_CH;
+ ret = knot_rrset_equal(rrs1, rrs2, KNOT_RRSET_COMPARE_HEADER);
+ if (ret) {
+ diag("Header comparison failed (CLASSEs different).\n");
+ knot_rrset_deep_free(&rrs1, 1, 1);
+ return 0;
+ }
+ rrs1->rclass = KNOT_CLASS_IN;
+
+ /* Test whole comparison. */
+ ret = knot_rrset_equal(rrs1, rrs2, KNOT_RRSET_COMPARE_WHOLE);
+ if (!ret) {
+ diag("Whole comparison failed (Same RRSets).\n");
+ knot_rrset_deep_free(&rrs1, 1, 1);
+ return 0;
+ }
+
+ rrs2 = &test_rrset_array[TEST_RRSET_A_LESS].rrset;
+ ret = knot_rrset_equal(rrs1, rrs2, KNOT_RRSET_COMPARE_WHOLE);
+ if (ret) {
+ diag("Whole comparison failed (Different RRSets).\n");
+ knot_rrset_deep_free(&rrs1, 1, 1);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&rrs1, 1, 1);
+
+ return 1;
+}
+
+static int test_rrset_rdata_equal()
+{
+ /* Equal - raw data only. */
+ knot_rrset_t *rrset1 = &test_rrset_array[TEST_RRSET_A_LESS].rrset;
+ knot_rrset_t *rrset2 = &test_rrset_array[TEST_RRSET_A_LESS].rrset;
+ if (!knot_rrset_rdata_equal(rrset1, rrset2)) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 1. (raw data) %d %d\n",
+ rrset1->type, rrset2->type);
+ return 0;
+ }
+
+ /* Equal - DNAME only. */
+ rrset1 = &test_rrset_array[TEST_RRSET_NS_LESS].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_NS_LESS].rrset;
+ if (!knot_rrset_rdata_equal(rrset1, rrset2)) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 1. (DNAME only)\n");
+ return 0;
+ }
+
+ /* Equal - combination. */
+ rrset1 = &test_rrset_array[TEST_RRSET_MX_BIN_LESS].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_MX_BIN_LESS].rrset;
+ if (!knot_rrset_rdata_equal(rrset1, rrset2)) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 1. (MX combination)\n");
+ return 0;
+ }
+
+ /* Equal - combination, different order. */
+ rrset1 = &test_rrset_array[TEST_RRSET_MINFO_MULTIPLE1].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_MINFO_MULTIPLE2].rrset;
+ if (!knot_rrset_rdata_equal(rrset1, rrset2)) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 1. (MINFO - order, combination)\n");
+ return 0;
+ }
+
+ /* Not equal - second item missing. */
+ rrset1 = &test_rrset_array[TEST_RRSET_MINFO_MULTIPLE1].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_MINFO].rrset;
+ if (knot_rrset_rdata_equal(rrset1, rrset2)) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (MINFO - combination)\n");
+ return 0;
+ }
+
+ /* Other way around. */
+ if (knot_rrset_rdata_equal(rrset2, rrset1)) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (combination)\n");
+ return 0;
+ }
+
+ /* Not equal - second item different. */
+
+ /* Other way around. */
+
+ /* Not equal - raw data only. */
+ rrset1 = &test_rrset_array[TEST_RRSET_A_LESS].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_A_GT].rrset;
+ if (knot_rrset_rdata_equal(rrset1, rrset2) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (raw data only)\n");
+ return 0;
+ }
+
+ /* Not equal - raw data only. */
+ if (knot_rrset_rdata_equal(rrset2, rrset1) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (raw data only)\n");
+ return 0;
+ }
+
+ /* Not equal - DNAME only. */
+ rrset1 = &test_rrset_array[TEST_RRSET_NS_LESS].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_NS_GT].rrset;
+ if (knot_rrset_rdata_equal(rrset1, rrset2) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (DNAME only)\n");
+ return 0;
+ }
+
+ /* Not equal - DNAME only. */
+ if (knot_rrset_rdata_equal(rrset2, rrset1) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (DNAME only)\n");
+ return 0;
+ }
+
+ /* Not equal - combination, difference in binary part. */
+ rrset1 = &test_rrset_array[TEST_RRSET_MX_BIN_LESS].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_MX_BIN_GT].rrset;
+ if (knot_rrset_rdata_equal(rrset1, rrset2) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (combination)\n");
+ return 0;
+ }
+
+ /* Not equal - combination, difference in binary part. */
+ if (knot_rrset_rdata_equal(rrset2, rrset1) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (combination)\n");
+ return 0;
+ }
+
+ /* Not equal - combination, difference in DNAME part. */
+ rrset1 = &test_rrset_array[TEST_RRSET_MX_DNAME_LESS].rrset;
+ rrset2 = &test_rrset_array[TEST_RRSET_MX_DNAME_GT].rrset;
+ if (knot_rrset_rdata_equal(rrset1, rrset2) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0. (combination)\n");
+ return 0;
+ }
+
+ /* Not equal - combination, difference in DNAME part. */
+ if (knot_rrset_rdata_equal(rrset2, rrset1) == 1) {
+ diag("rrset_rdata_equal() returned wrong "
+ "value, should be 0 (combination)\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int test_rrset_next_dname()
+{
+ /* Same test as in above, but we'll use multiple RRs within one SET. */
+ knot_rrset_t *rrset = &test_rrset_array[TEST_RRSET_MINFO_MULTIPLE1].rrset;
+ knot_dname_t *extracted_dnames[4];
+ extracted_dnames[0] = test_dnames[0];
+ extracted_dnames[1] = test_dnames[1];
+ extracted_dnames[2] = test_dnames[2];
+ extracted_dnames[3] = test_dnames[3];
+ knot_dname_t **dname = NULL;
+ int i = 0;
+ while ((dname = knot_rrset_get_next_dname(rrset, dname))) {
+ if (!knot_dname_is_equal(extracted_dnames[i], *dname)) {
+ diag("Got wrong DNAME from RDATA. on index %d\n", i);
+ char *ext_name = knot_dname_to_str(extracted_dnames[i]);
+ char *act_name = knot_dname_to_str(*dname);
+ diag("DNAME should be %s, but was %s (%p - %p)\n",
+ ext_name, act_name, extracted_dnames[i], *dname);
+ free(ext_name);
+ free(act_name);
+ return 0;
+ }
+ i++;
+ }
+
+ if (i != 4) {
+ diag("Not all DNAMEs were extracted (%d out of 4).\n",
+ i);
+ return 0;
+ }
+
+ /* Now try NS. */
+ rrset = &test_rrset_array[TEST_RRSET_NS_LESS].rrset;
+ dname = NULL;
+ dname = knot_rrset_get_next_dname(rrset, dname);
+ if (dname == NULL || !knot_dname_is_equal(*dname, test_dnames[TEST_DNAME_GENERIC])) {
+ diag("Got wrong DNAME from NS RDATA. Was %p, should be %p \n",
+ dname ? *dname: NULL, test_dnames[TEST_DNAME_GENERIC]);
+ return 0;
+ }
+ dname = knot_rrset_get_next_dname(rrset, dname);
+ if (dname != NULL) {
+ diag("Got DNAME from RRSet even though all had been extracted previously. (NS)\n");
+ return 0;
+ }
+ /* Now try MX. */
+ rrset = &test_rrset_array[TEST_RRSET_MX_BIN_GT].rrset;
+ dname = NULL;
+ dname = knot_rrset_get_next_dname(rrset, dname);
+ if (dname == NULL || !knot_dname_is_equal(*dname, test_dnames[1])) {
+ diag("Got wrong DNAME from MX RDATA.\n");
+ return 0;
+ }
+ dname = knot_rrset_get_next_dname(rrset, dname);
+ if (dname != NULL) {
+ diag("Got DNAME from RRSet even though all had been extracted previously. (MX)\n");
+ return 0;
+ }
+
+ /* Try writes into DNAMEs you've gotten. */
+ rrset = NULL;
+ knot_rrset_deep_copy(&test_rrset_array[TEST_RRSET_MINFO_MULTIPLE1].rrset,
+ &rrset);
+ dname = NULL;
+ i = 4;
+ while ((dname = knot_rrset_get_next_dname(rrset, dname))) {
+ knot_dname_free(dname);
+ memcpy(dname, &test_dnames[i], sizeof(knot_dname_t *));
+ i++;
+ }
+
+ if (i != 8) {
+ diag("Not all DNAMEs were traversed (%d).\n", i);
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ knot_dname_t **dname_read = NULL;
+ i = 4;
+ while ((dname_read = knot_rrset_get_next_dname(rrset,
+ dname_read))) {
+ if (*dname_read != test_dnames[i]) {
+ diag("Rewriting of DNAMEs in RDATA was "
+ "not successful.\n");
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+ i++;
+ }
+
+ if (i != 8) {
+ diag("Not all DNAMEs were traversed (%d).\n", i);
+ knot_rrset_deep_free(&rrset, 1, 1);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&rrset, 1, 1);
+
+ return 1;
+}
+
+static int test_rrset_find_pos()
+{
+ /* Create some mockup TXT RRSets. */
+ knot_rrset_t *rrset_source = knot_rrset_new(test_dnames[0], KNOT_RRTYPE_TXT,
+ KNOT_CLASS_IN, 3600);
+ uint8_t *mock_data = (uint8_t *)"cafebabebadcafecafecafecafe";
+ /* Test removal of two exactly same items. */
+ uint8_t *rdata = knot_rrset_create_rdata(rrset_source,
+ strlen((char *)mock_data));
+ memcpy(rdata, mock_data, strlen((char *)mock_data));
+ knot_rrset_t *rrset_find_in = NULL;
+ knot_rrset_deep_copy(rrset_source, &rrset_find_in);
+ rdata = knot_rrset_create_rdata(rrset_source, 10);
+ memcpy(rdata, mock_data ,10);
+ size_t rr_pos = 0;
+ int ret = knot_rrset_find_rr_pos(rrset_source, rrset_find_in, 0, &rr_pos);
+ if (ret != KNOT_EOK) {
+ knot_rrset_deep_free(&rrset_source, 1, 1);
+ knot_rrset_deep_free(&rrset_find_in, 1, 1);
+ diag("RR was not found, even though it should have been.");
+ return 0;
+ }
+ if (rr_pos != 0) {
+ knot_rrset_deep_free(&rrset_source, 1, 1);
+ knot_rrset_deep_free(&rrset_find_in, 1, 1);
+ diag("Wrong index returned. Should be 0, was %zu", rr_pos);
+ return 0;
+ }
+
+ /* Add second RR. */
+ knot_rrset_deep_free(&rrset_find_in, 1, 1);
+ knot_rrset_shallow_copy(rrset_source, &rrset_find_in);
+ /* Reset RRSet. */
+ rrset_find_in->rdata = NULL;
+ rrset_find_in->rdata_indices = NULL;
+ rrset_find_in->rdata_count = 0;
+
+ rdata = knot_rrset_create_rdata(rrset_find_in, 10);
+ memcpy(rdata, mock_data ,10);
+ ret = knot_rrset_find_rr_pos(rrset_source, rrset_find_in, 0, &rr_pos);
+ if (ret != KNOT_EOK) {
+ diag("RR was not found, even though it should have been.");
+ return 0;
+ }
+ if (rr_pos != 1) {
+ diag("Wrong index returned. Should be 1, was %zu", rr_pos);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&rrset_source, 1, 1);
+ knot_rrset_deep_free(&rrset_find_in, 1, 1);
+
+ return 1;
+}
+
+static int test_rrset_remove_rr()
+{
+ /* Remove RR and test that the returned data were OK. */
+
+ /* Create some mockup TXT RRSets. */
+ knot_rrset_t *rrset_source = knot_rrset_new(test_dnames[0], KNOT_RRTYPE_TXT,
+ KNOT_CLASS_IN, 3600);
+ uint8_t *mock_data = (uint8_t *)"cafebabebadcafecafecafecafe";
+ /* Test removal of two exactly same items. */
+ uint8_t *rdata = knot_rrset_create_rdata(rrset_source,
+ strlen((char *)mock_data));
+ memcpy(rdata, mock_data, strlen((char *)mock_data));
+ rdata = knot_rrset_create_rdata(rrset_source, 10);
+ memcpy(rdata, mock_data ,10);
+ knot_rrset_t *rrset_dest = NULL;
+ /* Create copy. */
+ knot_rrset_deep_copy(rrset_source, &rrset_dest);
+ rdata = knot_rrset_create_rdata(rrset_dest, 16);
+ memcpy(rdata, "foobarfoobarfoo", 16);
+ knot_rrset_t *returned_rr = NULL;
+ int ret = knot_rrset_remove_rr_using_rrset(rrset_dest, rrset_source, &returned_rr, 0);
+ if (ret != KNOT_EOK) {
+ diag("Could not remove");
+ knot_rrset_deep_free(&rrset_source, 1, 1);
+ knot_rrset_deep_free(&returned_rr, 1, 1);
+ return 0;
+ }
+
+// diag("Returned\n");
+// knot_rrset_dump(returned_rr);
+// diag("Source\n");
+// knot_rrset_dump(rrset_source);
+// diag("Destinantion\n");
+// knot_rrset_dump(rrset_dest);
+
+ /* Only one RR within RRSet, needs to be the same. */
+ if (!knot_rrset_equal(rrset_source, returned_rr,
+ KNOT_RRSET_COMPARE_WHOLE)) {
+ diag("Got wrong data in return rrset.");
+ knot_rrset_deep_free(&rrset_source, 1, 1);
+ knot_rrset_deep_free(&returned_rr, 1, 1);
+ return 0;
+ }
+
+ knot_rrset_deep_free(&rrset_source, 1, 1);
+ knot_rrset_deep_free(&rrset_dest, 1, 1);
+ knot_rrset_deep_free(&returned_rr, 1, 1);
+
+ return 1;
+}
+
+static int knot_rrset_tests_run(int argc, char *argv[])
+{
+ plan(14);
+ int res = 0,
+ res_final = 1;
+
+ create_test_dnames();
+ create_test_rdata();
+ create_test_rrsets();
+
+ res = test_rrset_new();
+ ok(res, "rrset: create");
+ res_final *= res;
+
+ res = test_rrset_create_rdata();
+ ok(res, "rrset: create_rdata");
+ res_final *= res;
+
+ res = test_rrset_get_rdata();
+ ok(res, "rrset: get rdata");
+ res_final *= res;
+
+ res = test_rrset_equal();
+ ok(res, "rrset: rrset_equal");
+ res_final *= res;
+
+ res = test_rrset_rdata_equal();
+ ok(res, "rrset: rrset_rdata_equal");
+
+ res = test_rrset_shallow_copy();
+ ok(res, "rrset: shallow copy");
+ res_final *= res;
+
+ res = test_rrset_deep_copy();
+ ok(res, "rrset: deep copy");
+ res_final *= res;
+
+ res = test_rrset_to_wire();
+ ok(res, "rrset: to wire");
+ res_final *= res;
+
+ res = test_rrset_rdata_item_size();
+ ok(res, "rrset: rdata_item_size");
+ res_final *= res;
+
+ res = test_rrset_merge();
+ ok(res, "rrset: merge");
+ res_final *= res;
+
+ res = test_rrset_merge_sort();
+ ok(res, "rrset: merge + sort");
+ res_final *= res;
+
+ res = test_rrset_next_dname();
+ ok(res, "rrset: next dname");
+ res_final *= res;
+
+ res = test_rrset_remove_rr();
+ ok(res, "rrset: remove rr");
+
+ res = test_rrset_find_pos();
+ ok(res, "rrset: find pos");
+ res_final *= res;
+
+ return res_final;
+}
+#else
+int main(void) {
+ plan(14);
+ skip_block(14, "the implementation is not done yet");
+}
+#endif
diff --git a/tests/runtests.c b/tests/runtests.c
new file mode 100644
index 0000000..a5cb77a
--- /dev/null
+++ b/tests/runtests.c
@@ -0,0 +1,1385 @@
+/*
+ * Run a set of tests, reporting results.
+ *
+ * Usage:
+ *
+ * runtests [-b <build-dir>] [-s <source-dir>] <test-list>
+ * runtests -o [-b <build-dir>] [-s <source-dir>] <test>
+ *
+ * In the first case, expects a list of executables located in the given file,
+ * one line per executable. For each one, runs it as part of a test suite,
+ * reporting results. Test output should start with a line containing the
+ * number of tests (numbered from 1 to this number), optionally preceded by
+ * "1..", although that line may be given anywhere in the output. Each
+ * additional line should be in the following format:
+ *
+ * ok <number>
+ * not ok <number>
+ * ok <number> # skip
+ * not ok <number> # todo
+ *
+ * where <number> is the number of the test. An optional comment is permitted
+ * after the number if preceded by whitespace. ok indicates success, not ok
+ * indicates failure. "# skip" and "# todo" are a special cases of a comment,
+ * and must start with exactly that formatting. They indicate the test was
+ * skipped for some reason (maybe because it doesn't apply to this platform)
+ * or is testing something known to currently fail. The text following either
+ * "# skip" or "# todo" and whitespace is the reason.
+ *
+ * As a special case, the first line of the output may be in the form:
+ *
+ * 1..0 # skip some reason
+ *
+ * which indicates that this entire test case should be skipped and gives a
+ * reason.
+ *
+ * Any other lines are ignored, although for compliance with the TAP protocol
+ * all lines other than the ones in the above format should be sent to
+ * standard error rather than standard output and start with #.
+ *
+ * This is a subset of TAP as documented in Test::Harness::TAP or
+ * TAP::Parser::Grammar, which comes with Perl.
+ *
+ * If the -o option is given, instead run a single test and display all of its
+ * output. This is intended for use with failing tests so that the person
+ * running the test suite can get more details about what failed.
+ *
+ * If built with the C preprocessor symbols SOURCE and BUILD defined, C TAP
+ * Harness will export those values in the environment so that tests can find
+ * the source and build directory and will look for tests under both
+ * directories. These paths can also be set with the -b and -s command-line
+ * options, which will override anything set at build time.
+ *
+ * Any bug reports, bug fixes, and improvements are very much welcome and
+ * should be sent to the e-mail address below. This program is part of C TAP
+ * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011
+ * Russ Allbery <rra@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+*/
+
+/* Required for fdopen(), getopt(), and putenv(). */
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 500
+# endif
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+/* sys/time.h must be included before sys/resource.h on some platforms. */
+#include <sys/resource.h>
+
+/* AIX doesn't have WCOREDUMP. */
+#ifndef WCOREDUMP
+# define WCOREDUMP(status) ((unsigned)(status) & 0x80)
+#endif
+
+/*
+ * Used for iterating through arrays. Returns the number of elements in the
+ * array (useful for a < upper bound in a for loop).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+/*
+ * The source and build versions of the tests directory. This is used to set
+ * the SOURCE and BUILD environment variables and find test programs, if set.
+ * Normally, this should be set as part of the build process to the test
+ * subdirectories of $(abs_top_srcdir) and $(abs_top_builddir) respectively.
+ */
+#ifndef SOURCE
+# define SOURCE NULL
+#endif
+#ifndef BUILD
+# define BUILD NULL
+#endif
+
+/* Test status codes. */
+enum test_status {
+ TEST_FAIL,
+ TEST_PASS,
+ TEST_SKIP,
+ TEST_INVALID
+};
+
+/* Indicates the state of our plan. */
+enum plan_status {
+ PLAN_INIT, /* Nothing seen yet. */
+ PLAN_FIRST, /* Plan seen before any tests. */
+ PLAN_PENDING, /* Test seen and no plan yet. */
+ PLAN_FINAL /* Plan seen after some tests. */
+};
+
+/* Error exit statuses for test processes. */
+#define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */
+#define CHILDERR_EXEC 101 /* Couldn't exec child process. */
+#define CHILDERR_STDERR 102 /* Couldn't open stderr file. */
+
+/* Structure to hold data for a set of tests. */
+struct testset {
+ char *file; /* The file name of the test. */
+ char *path; /* The path to the test program. */
+ enum plan_status plan; /* The status of our plan. */
+ unsigned long count; /* Expected count of tests. */
+ unsigned long current; /* The last seen test number. */
+ unsigned int length; /* The length of the last status message. */
+ unsigned long passed; /* Count of passing tests. */
+ unsigned long failed; /* Count of failing lists. */
+ unsigned long skipped; /* Count of skipped tests (passed). */
+ unsigned long allocated; /* The size of the results table. */
+ enum test_status *results; /* Table of results by test number. */
+ unsigned int aborted; /* Whether the set was aborted. */
+ int reported; /* Whether the results were reported. */
+ int status; /* The exit status of the test. */
+ unsigned int all_skipped; /* Whether all tests were skipped. */
+ char *reason; /* Why all tests were skipped. */
+};
+
+/* Structure to hold a linked list of test sets. */
+struct testlist {
+ struct testset *ts;
+ struct testlist *next;
+};
+
+/*
+ * Usage message. Should be used as a printf format with four arguments: the
+ * path to runtests, given three times, and the usage_description. This is
+ * split into variables to satisfy the pedantic ISO C90 limit on strings.
+ */
+static const char usage_message[] = "\
+Usage: %s [-b <build-dir>] [-s <source-dir>] <test> ...\n\
+ %s [-b <build-dir>] [-s <source-dir>] -l <test-list>\n\
+ %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
+\n%s";
+static const char usage_extra[] = "\
+Options:\n\
+ -b <build-dir> Set the build directory to <build-dir>\n\
+ -l <list> Take the list of tests to run from <test-list>\n\
+ -o Run a single test rather than a list of tests\n\
+ -s <source-dir> Set the source directory to <source-dir>\n\
+\n\
+runtests normally runs each test listed on the command line. With the -l\n\
+option, it instead runs every test listed in a file. With the -o option,\n\
+it instead runs a single test and shows its complete output.\n";
+
+/*
+ * Header used for test output. %s is replaced by the file name of the list
+ * of tests.
+ */
+static const char banner[] = "\n\
+Running all tests listed in %s. If any tests fail, run the failing\n\
+test program with runtests -o to see more details.\n\n";
+
+/* Header for reports of failed tests. */
+static const char header[] = "\n\
+Failed Set Fail/Total (%) Skip Stat Failing Tests\n\
+-------------------------- -------------- ---- ---- ------------------------";
+
+/* Include the file name and line number in malloc failures. */
+#define xcalloc(n, size) x_calloc((n), (size), __FILE__, __LINE__)
+#define xmalloc(size) x_malloc((size), __FILE__, __LINE__)
+#define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__)
+#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)
+
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros).
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __attribute__(spec) /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC. Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+# define __alloc_size__(spec, args...) /* empty */
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does. For them, suppress warnings about unknown
+ * attributes on declarations. This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Declare internal functions that benefit from compiler attributes. */
+static void sysdie(const char *, ...)
+ __attribute__((__nonnull__, __noreturn__, __format__(printf, 1, 2)));
+static void *x_calloc(size_t, size_t, const char *, int)
+ __attribute__((__alloc_size__(1, 2), __malloc__, __nonnull__));
+static void *x_malloc(size_t, const char *, int)
+ __attribute__((__alloc_size__(1), __malloc__, __nonnull__));
+static void *x_realloc(void *, size_t, const char *, int)
+ __attribute__((__alloc_size__(2), __malloc__, __nonnull__(3)));
+static char *x_strdup(const char *, const char *, int)
+ __attribute__((__malloc__, __nonnull__));
+
+
+/*
+ * Report a fatal error, including the results of strerror, and exit.
+ */
+static void
+sysdie(const char *format, ...)
+{
+ int oerrno;
+ va_list args;
+
+ oerrno = errno;
+ fflush(stdout);
+ fprintf(stderr, "runtests: ");
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, ": %s\n", strerror(oerrno));
+ exit(1);
+}
+
+
+/*
+ * Allocate zeroed memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_calloc(size_t n, size_t size, const char *file, int line)
+{
+ void *p;
+
+ n = (n > 0) ? n : 1;
+ size = (size > 0) ? size : 1;
+ p = calloc(n, size);
+ if (p == NULL)
+ sysdie("failed to calloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
+ * Allocate memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_malloc(size_t size, const char *file, int line)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL)
+ sysdie("failed to malloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
+ * Reallocate memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_realloc(void *p, size_t size, const char *file, int line)
+{
+ p = realloc(p, size);
+ if (p == NULL)
+ sysdie("failed to realloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
+ * Copy a string, reporting a fatal error and exiting on failure.
+ */
+static char *
+x_strdup(const char *s, const char *file, int line)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ if (p == NULL)
+ sysdie("failed to strdup %lu bytes at %s line %d",
+ (unsigned long) len, file, line);
+ memcpy(p, s, len);
+ return p;
+}
+
+
+/*
+ * Given a struct timeval, return the number of seconds it represents as a
+ * double. Use difftime() to convert a time_t to a double.
+ */
+static double
+tv_seconds(const struct timeval *tv)
+{
+ return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6;
+}
+
+
+/*
+ * Given two struct timevals, return the difference in seconds.
+ */
+static double
+tv_diff(const struct timeval *tv1, const struct timeval *tv0)
+{
+ return tv_seconds(tv1) - tv_seconds(tv0);
+}
+
+
+/*
+ * Given two struct timevals, return the sum in seconds as a double.
+ */
+static double
+tv_sum(const struct timeval *tv1, const struct timeval *tv2)
+{
+ return tv_seconds(tv1) + tv_seconds(tv2);
+}
+
+
+/*
+ * Given a pointer to a string, skip any leading whitespace and return a
+ * pointer to the first non-whitespace character.
+ */
+static const char *
+skip_whitespace(const char *p)
+{
+ while (isspace((unsigned char)(*p)))
+ p++;
+ return p;
+}
+
+
+/*
+ * Start a program, connecting its stdout to a pipe on our end and its stderr
+ * to /dev/null, and storing the file descriptor to read from in the two
+ * argument. Returns the PID of the new process. Errors are fatal.
+ */
+static pid_t
+test_start(const char *path, int *fd)
+{
+ int fds[2], errfd;
+ pid_t child;
+
+ if (pipe(fds) == -1) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("can't create pipe");
+ }
+ child = fork();
+ if (child == (pid_t) -1) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("can't fork");
+ } else if (child == 0) {
+ /* In child. Set up our stdout and stderr. */
+ errfd = open("/dev/null", O_WRONLY);
+ if (errfd < 0)
+ _exit(CHILDERR_STDERR);
+ if (dup2(errfd, 2) == -1)
+ _exit(CHILDERR_DUP);
+ close(fds[0]);
+ if (dup2(fds[1], 1) == -1)
+ _exit(CHILDERR_DUP);
+
+ /* Now, exec our process. */
+ if (execl(path, path, (char *) 0) == -1)
+ _exit(CHILDERR_EXEC);
+ } else {
+ /* In parent. Close the extra file descriptor. */
+ close(fds[1]);
+ }
+ *fd = fds[0];
+ return child;
+}
+
+
+/*
+ * Back up over the output saying what test we were executing.
+ */
+static void
+test_backspace(struct testset *ts)
+{
+ unsigned int i;
+
+ if (!isatty(STDOUT_FILENO))
+ return;
+ for (i = 0; i < ts->length; i++)
+ putchar('\b');
+ for (i = 0; i < ts->length; i++)
+ putchar(' ');
+ for (i = 0; i < ts->length; i++)
+ putchar('\b');
+ ts->length = 0;
+}
+
+
+/*
+ * Read the plan line of test output, which should contain the range of test
+ * numbers. We may initialize the testset structure here if we haven't yet
+ * seen a test. Return true if initialization succeeded and the test should
+ * continue, false otherwise.
+ */
+static int
+test_plan(const char *line, struct testset *ts)
+{
+ unsigned long i;
+ long n;
+
+ /*
+ * Accept a plan without the leading 1.. for compatibility with older
+ * versions of runtests. This will only be allowed if we've not yet seen
+ * a test result.
+ */
+ line = skip_whitespace(line);
+ if (strncmp(line, "1..", 3) == 0)
+ line += 3;
+
+ /*
+ * Get the count, check it for validity, and initialize the struct. If we
+ * have something of the form "1..0 # skip foo", the whole file was
+ * skipped; record that. If we do skip the whole file, zero out all of
+ * our statistics, since they're no longer relevant. strtol is called
+ * with a second argument to advance the line pointer past the count to
+ * make it simpler to detect the # skip case.
+ */
+ n = strtol(line, (char **) &line, 10);
+ if (n == 0) {
+ line = skip_whitespace(line);
+ if (*line == '#') {
+ line = skip_whitespace(line + 1);
+ if (strncasecmp(line, "skip", 4) == 0) {
+ line = skip_whitespace(line + 4);
+ if (*line != '\0') {
+ ts->reason = xstrdup(line);
+ ts->reason[strlen(ts->reason) - 1] = '\0';
+ }
+ ts->all_skipped = 1;
+ ts->aborted = 1;
+ ts->count = 0;
+ ts->passed = 0;
+ ts->skipped = 0;
+ ts->failed = 0;
+ return 0;
+ }
+ }
+ }
+ if (n <= 0) {
+ puts("ABORTED (invalid test count)");
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+ if (ts->plan == PLAN_INIT && ts->allocated == 0) {
+ ts->count = n;
+ ts->allocated = n;
+ ts->plan = PLAN_FIRST;
+ ts->results = xmalloc(ts->count * sizeof(enum test_status));
+ for (i = 0; i < ts->count; i++)
+ ts->results[i] = TEST_INVALID;
+ } else if (ts->plan == PLAN_PENDING) {
+ if ((unsigned long) n < ts->count) {
+ test_backspace(ts);
+ printf("ABORTED (invalid test number %lu)\n", ts->count);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+ ts->count = n;
+ if ((unsigned long) n > ts->allocated) {
+ ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
+ for (i = ts->allocated; i < ts->count; i++)
+ ts->results[i] = TEST_INVALID;
+ ts->allocated = n;
+ }
+ ts->plan = PLAN_FINAL;
+ }
+ return 1;
+}
+
+
+/*
+ * Given a single line of output from a test, parse it and return the success
+ * status of that test. Anything printed to stdout not matching the form
+ * /^(not )?ok \d+/ is ignored. Sets ts->current to the test number that just
+ * reported status.
+ */
+static void
+test_checkline(const char *line, struct testset *ts)
+{
+ enum test_status status = TEST_PASS;
+ const char *bail;
+ char *end;
+ long number;
+ unsigned long i, current;
+ int outlen;
+
+ /* Before anything, check for a test abort. */
+ bail = strstr(line, "Bail out!");
+ if (bail != NULL) {
+ bail = skip_whitespace(bail + strlen("Bail out!"));
+ if (*bail != '\0') {
+ size_t length;
+
+ length = strlen(bail);
+ if (bail[length - 1] == '\n')
+ length--;
+ test_backspace(ts);
+ printf("ABORTED (%.*s)\n", (int) length, bail);
+ ts->reported = 1;
+ }
+ ts->aborted = 1;
+ return;
+ }
+
+ /*
+ * If the given line isn't newline-terminated, it was too big for an
+ * fgets(), which means ignore it.
+ */
+ if (line[strlen(line) - 1] != '\n')
+ return;
+
+ /* If the line begins with a hash mark, ignore it. */
+ if (line[0] == '#')
+ return;
+
+ /* If we haven't yet seen a plan, look for one. */
+ if (ts->plan == PLAN_INIT && isdigit((unsigned char)(*line))) {
+ if (!test_plan(line, ts))
+ return;
+ } else if (strncmp(line, "1..", 3) == 0) {
+ if (ts->plan == PLAN_PENDING) {
+ if (!test_plan(line, ts))
+ return;
+ } else {
+ test_backspace(ts);
+ puts("ABORTED (multiple plans)");
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+ }
+
+ /* Parse the line, ignoring something we can't parse. */
+ if (strncmp(line, "not ", 4) == 0) {
+ status = TEST_FAIL;
+ line += 4;
+ }
+ if (strncmp(line, "ok", 2) != 0)
+ return;
+ line = skip_whitespace(line + 2);
+ errno = 0;
+ number = strtol(line, &end, 10);
+ if (errno != 0 || end == line)
+ number = ts->current + 1;
+ current = number;
+ if (number <= 0 || (current > ts->count && ts->plan == PLAN_FIRST)) {
+ test_backspace(ts);
+ printf("ABORTED (invalid test number %lu)\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+
+ /* We have a valid test result. Tweak the results array if needed. */
+ if (ts->plan == PLAN_INIT || ts->plan == PLAN_PENDING) {
+ ts->plan = PLAN_PENDING;
+ if (current > ts->count)
+ ts->count = current;
+ if (current > ts->allocated) {
+ unsigned long n;
+
+ n = (ts->allocated == 0) ? 32 : ts->allocated * 2;
+ if (n < current)
+ n = current;
+ ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
+ for (i = ts->allocated; i < n; i++)
+ ts->results[i] = TEST_INVALID;
+ ts->allocated = n;
+ }
+ }
+
+ /*
+ * Handle directives. We should probably do something more interesting
+ * with unexpected passes of todo tests.
+ */
+ while (isdigit((unsigned char)(*line)))
+ line++;
+ line = skip_whitespace(line);
+ if (*line == '#') {
+ line = skip_whitespace(line + 1);
+ if (strncasecmp(line, "skip", 4) == 0)
+ status = TEST_SKIP;
+ if (strncasecmp(line, "todo", 4) == 0)
+ status = (status == TEST_FAIL) ? TEST_SKIP : TEST_FAIL;
+ }
+
+ /* Make sure that the test number is in range and not a duplicate. */
+ if (ts->results[current - 1] != TEST_INVALID) {
+ test_backspace(ts);
+ printf("ABORTED (duplicate test number %lu)\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+
+ /* Good results. Increment our various counters. */
+ switch (status) {
+ case TEST_PASS: ts->passed++; break;
+ case TEST_FAIL: ts->failed++; break;
+ case TEST_SKIP: ts->skipped++; break;
+ case TEST_INVALID: break;
+ }
+ ts->current = current;
+ ts->results[current - 1] = status;
+ if (isatty(STDOUT_FILENO)) {
+ test_backspace(ts);
+ if (ts->plan == PLAN_PENDING)
+ outlen = printf("%lu/?", current);
+ else
+ outlen = printf("%lu/%lu", current, ts->count);
+ ts->length = (outlen >= 0) ? outlen : 0;
+ fflush(stdout);
+ }
+}
+
+
+/*
+ * Print out a range of test numbers, returning the number of characters it
+ * took up. Takes the first number, the last number, the number of characters
+ * already printed on the line, and the limit of number of characters the line
+ * can hold. Add a comma and a space before the range if chars indicates that
+ * something has already been printed on the line, and print ... instead if
+ * chars plus the space needed would go over the limit (use a limit of 0 to
+ * disable this).
+ */
+static unsigned int
+test_print_range(unsigned long first, unsigned long last, unsigned int chars,
+ unsigned int limit)
+{
+ unsigned int needed = 0;
+ unsigned long n;
+
+ for (n = first; n > 0; n /= 10)
+ needed++;
+ if (last > first) {
+ for (n = last; n > 0; n /= 10)
+ needed++;
+ needed++;
+ }
+ if (chars > 0)
+ needed += 2;
+ if (limit > 0 && chars + needed > limit) {
+ needed = 0;
+ if (chars <= limit) {
+ if (chars > 0) {
+ printf(", ");
+ needed += 2;
+ }
+ printf("...");
+ needed += 3;
+ }
+ } else {
+ if (chars > 0)
+ printf(", ");
+ if (last > first)
+ printf("%lu-", first);
+ printf("%lu", last);
+ }
+ return needed;
+}
+
+
+/*
+ * Summarize a single test set. The second argument is 0 if the set exited
+ * cleanly, a positive integer representing the exit status if it exited
+ * with a non-zero status, and a negative integer representing the signal
+ * that terminated it if it was killed by a signal.
+ */
+static void
+test_summarize(struct testset *ts, int status)
+{
+ unsigned long i;
+ unsigned long missing = 0;
+ unsigned long failed = 0;
+ unsigned long first = 0;
+ unsigned long last = 0;
+
+ if (ts->aborted) {
+ fputs("ABORTED", stdout);
+ if (ts->count > 0)
+ printf(" (passed %lu/%lu)", ts->passed, ts->count - ts->skipped);
+ } else {
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ if (missing == 0)
+ fputs("MISSED ", stdout);
+ if (first && i == last)
+ last = i + 1;
+ else {
+ if (first)
+ test_print_range(first, last, missing - 1, 0);
+ missing++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first)
+ test_print_range(first, last, missing - 1, 0);
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (missing && !failed)
+ fputs("; ", stdout);
+ if (failed == 0)
+ fputs("FAILED ", stdout);
+ if (first && i == last)
+ last = i + 1;
+ else {
+ if (first)
+ test_print_range(first, last, failed - 1, 0);
+ failed++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first)
+ test_print_range(first, last, failed - 1, 0);
+ if (!missing && !failed) {
+ fputs(!status ? "ok" : "dubious", stdout);
+ if (ts->skipped > 0) {
+ if (ts->skipped == 1)
+ printf(" (skipped %lu test)", ts->skipped);
+ else
+ printf(" (skipped %lu tests)", ts->skipped);
+ }
+ }
+ }
+ if (status > 0)
+ printf(" (exit status %d)", status);
+ else if (status < 0)
+ printf(" (killed by signal %d%s)", -status,
+ WCOREDUMP(ts->status) ? ", core dumped" : "");
+ putchar('\n');
+}
+
+
+/*
+ * Given a test set, analyze the results, classify the exit status, handle a
+ * few special error messages, and then pass it along to test_summarize() for
+ * the regular output. Returns true if the test set ran successfully and all
+ * tests passed or were skipped, false otherwise.
+ */
+static int
+test_analyze(struct testset *ts)
+{
+ if (ts->reported)
+ return 0;
+ if (ts->all_skipped) {
+ if (ts->reason == NULL)
+ puts("skipped");
+ else
+ printf("skipped (%s)\n", ts->reason);
+ return 1;
+ } else if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) {
+ switch (WEXITSTATUS(ts->status)) {
+ case CHILDERR_DUP:
+ if (!ts->reported)
+ puts("ABORTED (can't dup file descriptors)");
+ break;
+ case CHILDERR_EXEC:
+ if (!ts->reported)
+ puts("ABORTED (execution failed -- not found?)");
+ break;
+ case CHILDERR_STDERR:
+ if (!ts->reported)
+ puts("ABORTED (can't open /dev/null)");
+ break;
+ default:
+ test_summarize(ts, WEXITSTATUS(ts->status));
+ break;
+ }
+ return 0;
+ } else if (WIFSIGNALED(ts->status)) {
+ test_summarize(ts, -WTERMSIG(ts->status));
+ return 0;
+ } else if (ts->plan != PLAN_FIRST && ts->plan != PLAN_FINAL) {
+ puts("ABORTED (no valid test plan)");
+ ts->aborted = 1;
+ return 0;
+ } else {
+ test_summarize(ts, 0);
+ return (ts->failed == 0);
+ }
+}
+
+
+/*
+ * Runs a single test set, accumulating and then reporting the results.
+ * Returns true if the test set was successfully run and all tests passed,
+ * false otherwise.
+ */
+static int
+test_run(struct testset *ts)
+{
+ pid_t testpid, child;
+ int outfd, status;
+ unsigned long i;
+ FILE *output;
+ char buffer[BUFSIZ];
+
+ /* Run the test program. */
+ testpid = test_start(ts->path, &outfd);
+ output = fdopen(outfd, "r");
+ if (!output) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("fdopen failed");
+ }
+
+ /* Pass each line of output to test_checkline(). */
+ while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
+ test_checkline(buffer, ts);
+ if (ferror(output) || ts->plan == PLAN_INIT)
+ ts->aborted = 1;
+ test_backspace(ts);
+
+ /*
+ * Consume the rest of the test output, close the output descriptor,
+ * retrieve the exit status, and pass that information to test_analyze()
+ * for eventual output.
+ */
+ while (fgets(buffer, sizeof(buffer), output))
+ ;
+ fclose(output);
+ child = waitpid(testpid, &ts->status, 0);
+ if (child == (pid_t) -1) {
+ if (!ts->reported) {
+ puts("ABORTED");
+ fflush(stdout);
+ }
+ sysdie("waitpid for %u failed", (unsigned int) testpid);
+ }
+ if (ts->all_skipped)
+ ts->aborted = 0;
+ status = test_analyze(ts);
+
+ /* Convert missing tests to failed tests. */
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ ts->failed++;
+ ts->results[i] = TEST_FAIL;
+ status = 0;
+ }
+ }
+ return status;
+}
+
+
+/* Summarize a list of test failures. */
+static void
+test_fail_summary(const struct testlist *fails)
+{
+ struct testset *ts;
+ unsigned int chars;
+ unsigned long i, first, last, total;
+
+ puts(header);
+
+ /* Failed Set Fail/Total (%) Skip Stat Failing (25)
+ -------------------------- -------------- ---- ---- -------------- */
+ for (; fails; fails = fails->next) {
+ ts = fails->ts;
+ total = ts->count - ts->skipped;
+ printf("%-26.26s %4lu/%-4lu %3.0f%% %4lu ", ts->file, ts->failed,
+ total, total ? (ts->failed * 100.0) / total : 0,
+ ts->skipped);
+ if (WIFEXITED(ts->status))
+ printf("%4d ", WEXITSTATUS(ts->status));
+ else
+ printf(" -- ");
+ if (ts->aborted) {
+ puts("aborted");
+ continue;
+ }
+ chars = 0;
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (first != 0 && i == last)
+ last = i + 1;
+ else {
+ if (first != 0)
+ chars += test_print_range(first, last, chars, 19);
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first != 0)
+ test_print_range(first, last, chars, 19);
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Check whether a given file path is a valid test. Currently, this checks
+ * whether it is executable and is a regular file. Returns true or false.
+ */
+static int
+is_valid_test(const char *path)
+{
+ struct stat st;
+
+ if (access(path, X_OK) < 0)
+ return 0;
+ if (stat(path, &st) < 0)
+ return 0;
+ if (!S_ISREG(st.st_mode))
+ return 0;
+ return 1;
+}
+
+
+/*
+ * Given the name of a test, a pointer to the testset struct, and the source
+ * and build directories, find the test. We try first relative to the current
+ * directory, then in the build directory (if not NULL), then in the source
+ * directory. In each of those directories, we first try a "-t" extension and
+ * then a ".t" extension. When we find an executable program, we return the
+ * path to that program. If none of those paths are executable, just fill in
+ * the name of the test as is.
+ *
+ * The caller is responsible for freeing the path member of the testset
+ * struct.
+ */
+static char *
+find_test(const char *name, const char *source, const char *build)
+{
+ char *path;
+ const char *bases[3], *suffix, *base;
+ unsigned int i, j;
+ const char *suffixes[3] = { "-t", ".t", "" };
+
+ /* Possible base directories. */
+ bases[0] = ".";
+ bases[1] = build;
+ bases[2] = source;
+
+ /* Try each suffix with each base. */
+ for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
+ suffix = suffixes[i];
+ for (j = 0; j < ARRAY_SIZE(bases); j++) {
+ unsigned int path_len;
+
+ base = bases[j];
+ if (base == NULL)
+ continue;
+ path_len = strlen(base) + strlen(name) + strlen(suffix) + 2;
+ path = xmalloc(path_len);
+ snprintf(path, path_len, "%s/%s%s", base, name, suffix);
+ if (is_valid_test(path))
+ return path;
+ free(path);
+ path = NULL;
+ }
+ }
+ if (path == NULL)
+ path = xstrdup(name);
+ return path;
+}
+
+
+/*
+ * Read a list of tests from a file, returning the list of tests as a struct
+ * testlist. Reports an error to standard error and exits if the list of
+ * tests cannot be read.
+ */
+static struct testlist *
+read_test_list(const char *filename)
+{
+ FILE *file;
+ unsigned int line;
+ size_t length;
+ char buffer[BUFSIZ];
+ struct testlist *listhead, *current;
+
+ /* Create the initial container list that will hold our results. */
+ listhead = xmalloc(sizeof(struct testlist));
+ listhead->ts = NULL;
+ listhead->next = NULL;
+ current = NULL;
+
+ /*
+ * Open our file of tests to run and read it line by line, creating a new
+ * struct testlist and struct testset for each line.
+ */
+ file = fopen(filename, "r");
+ if (file == NULL)
+ sysdie("can't open %s", filename);
+ line = 0;
+ while (fgets(buffer, sizeof(buffer), file)) {
+ line++;
+ length = strlen(buffer) - 1;
+ if (buffer[length] != '\n') {
+ fprintf(stderr, "%s:%u: line too long\n", filename, line);
+ exit(1);
+ }
+ buffer[length] = '\0';
+ if (current == NULL)
+ current = listhead;
+ else {
+ current->next = xmalloc(sizeof(struct testlist));
+ current = current->next;
+ current->next = NULL;
+ }
+ current->ts = xcalloc(1, sizeof(struct testset));
+ current->ts->plan = PLAN_INIT;
+ current->ts->file = xstrdup(buffer);
+ current->ts->reason = NULL;
+ }
+ fclose(file);
+
+ /* Return the results. */
+ return listhead;
+}
+
+
+/*
+ * Build a list of tests from command line arguments. Takes the argv and argc
+ * representing the command line arguments and returns a newly allocated test
+ * list. The caller is responsible for freeing.
+ */
+static struct testlist *
+build_test_list(char *argv[], int argc)
+{
+ int i;
+ struct testlist *listhead, *current;
+
+ /* Create the initial container list that will hold our results. */
+ listhead = xmalloc(sizeof(struct testlist));
+ listhead->ts = NULL;
+ listhead->next = NULL;
+ current = NULL;
+
+ /* Walk the list of arguments and create test sets for them. */
+ for (i = 0; i < argc; i++) {
+ if (current == NULL)
+ current = listhead;
+ else {
+ current->next = xmalloc(sizeof(struct testlist));
+ current = current->next;
+ current->next = NULL;
+ }
+ current->ts = xcalloc(1, sizeof(struct testset));
+ current->ts->plan = PLAN_INIT;
+ current->ts->file = xstrdup(argv[i]);
+ current->ts->reason = NULL;
+ }
+
+ /* Return the results. */
+ return listhead;
+}
+
+
+/* Free a struct testset. */
+static void
+free_testset(struct testset *ts)
+{
+ free(ts->file);
+ free(ts->path);
+ free(ts->results);
+ if (ts->reason != NULL)
+ free(ts->reason);
+ free(ts);
+}
+
+
+/*
+ * Run a batch of tests. Takes two additional parameters: the root of the
+ * source directory and the root of the build directory. Test programs will
+ * be first searched for in the current directory, then the build directory,
+ * then the source directory. Returns true iff all tests passed, and always
+ * frees the test list that's passed in.
+ */
+static int
+test_batch(struct testlist *tests, const char *source, const char *build)
+{
+ size_t length;
+ unsigned int i;
+ unsigned int longest = 0;
+ unsigned int count = 0;
+ struct testset *ts;
+ struct timeval start, end;
+ struct rusage stats;
+ struct testlist *failhead = NULL;
+ struct testlist *failtail = NULL;
+ struct testlist *current, *next;
+ int succeeded;
+ unsigned long total = 0;
+ unsigned long passed = 0;
+ unsigned long skipped = 0;
+ unsigned long failed = 0;
+ unsigned long aborted = 0;
+
+ /* Walk the list of tests to find the longest name. */
+ for (current = tests; current != NULL; current = current->next) {
+ length = strlen(current->ts->file);
+ if (length > longest)
+ longest = length;
+ }
+
+ /*
+ * Add two to longest and round up to the nearest tab stop. This is how
+ * wide the column for printing the current test name will be.
+ */
+ longest += 2;
+ if (longest % 8)
+ longest += 8 - (longest % 8);
+
+ /* Start the wall clock timer. */
+ gettimeofday(&start, NULL);
+
+ /* Now, plow through our tests again, running each one. */
+ for (current = tests; current != NULL; current = current->next) {
+ ts = current->ts;
+
+ /* Print out the name of the test file. */
+ fputs(ts->file, stdout);
+ for (i = strlen(ts->file); i < longest; i++)
+ putchar('.');
+ if (isatty(STDOUT_FILENO))
+ fflush(stdout);
+
+ /* Run the test. */
+ ts->path = find_test(ts->file, source, build);
+ succeeded = test_run(ts);
+ fflush(stdout);
+
+ /* Record cumulative statistics. */
+ aborted += ts->aborted;
+ total += ts->count + ts->all_skipped;
+ passed += ts->passed;
+ skipped += ts->skipped + ts->all_skipped;
+ failed += ts->failed;
+ count++;
+
+ /* If the test fails, we shuffle it over to the fail list. */
+ if (!succeeded) {
+ if (failhead == NULL) {
+ failhead = xmalloc(sizeof(struct testset));
+ failtail = failhead;
+ } else {
+ failtail->next = xmalloc(sizeof(struct testset));
+ failtail = failtail->next;
+ }
+ failtail->ts = ts;
+ failtail->next = NULL;
+ }
+ }
+ total -= skipped;
+
+ /* Stop the timer and get our child resource statistics. */
+ gettimeofday(&end, NULL);
+ getrusage(RUSAGE_CHILDREN, &stats);
+
+ /* Summarize the failures and free the failure list. */
+ if (failhead != NULL) {
+ test_fail_summary(failhead);
+ while (failhead != NULL) {
+ next = failhead->next;
+ free(failhead);
+ failhead = next;
+ }
+ }
+
+ /* Free the memory used by the test lists. */
+ while (tests != NULL) {
+ next = tests->next;
+ free_testset(tests->ts);
+ free(tests);
+ tests = next;
+ }
+
+ /* Print out the final test summary. */
+ putchar('\n');
+ if (aborted != 0) {
+ if (aborted == 1)
+ printf("Aborted %lu test set", aborted);
+ else
+ printf("Aborted %lu test sets", aborted);
+ printf(", passed %lu/%lu tests", passed, total);
+ }
+ else if (failed == 0)
+ fputs("All tests successful", stdout);
+ else
+ printf("Failed %lu/%lu tests, %.2f%% okay", failed, total,
+ (total - failed) * 100.0 / total);
+ if (skipped != 0) {
+ if (skipped == 1)
+ printf(", %lu test skipped", skipped);
+ else
+ printf(", %lu tests skipped", skipped);
+ }
+ puts(".");
+ printf("Files=%u, Tests=%lu", count, total);
+ printf(", %.2f seconds", tv_diff(&end, &start));
+ printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
+ tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
+ tv_sum(&stats.ru_utime, &stats.ru_stime));
+ return (failed == 0 && aborted == 0);
+}
+
+
+/*
+ * Run a single test case. This involves just running the test program after
+ * having done the environment setup and finding the test program.
+ */
+static void
+test_single(const char *program, const char *source, const char *build)
+{
+ char *path;
+
+ path = find_test(program, source, build);
+ if (execl(path, path, (char *) 0) == -1)
+ sysdie("cannot exec %s", path);
+}
+
+
+/*
+ * Main routine. Set the SOURCE and BUILD environment variables and then,
+ * given a file listing tests, run each test listed.
+ */
+int
+main(int argc, char *argv[])
+{
+ int option;
+ int status = 0;
+ int single = 0;
+ char *source_env = NULL;
+ char *build_env = NULL;
+ const char *shortlist;
+ const char *list = NULL;
+ const char *source = SOURCE;
+ const char *build = BUILD;
+ struct testlist *tests;
+
+ while ((option = getopt(argc, argv, "b:hl:os:")) != EOF) {
+ switch (option) {
+ case 'b':
+ build = optarg;
+ break;
+ case 'h':
+ printf(usage_message, argv[0], argv[0], argv[0], usage_extra);
+ exit(0);
+ break;
+ case 'l':
+ list = optarg;
+ break;
+ case 'o':
+ single = 1;
+ break;
+ case 's':
+ source = optarg;
+ break;
+ default:
+ exit(1);
+ }
+ }
+ argv += optind;
+ argc -= optind;
+ if ((list == NULL && argc < 1) || (list != NULL && argc > 0)) {
+ fprintf(stderr, usage_message, argv[0], argv[0], argv[0], usage_extra);
+ exit(1);
+ }
+
+ /* Set SOURCE and BUILD environment variables. */
+ if (source != NULL) {
+ unsigned int len = strlen("SOURCE=") + strlen(source) + 1;
+ source_env = xmalloc(len);
+ snprintf(source_env, len, "SOURCE=%s", source);
+ if (putenv(source_env) != 0)
+ sysdie("cannot set SOURCE in the environment");
+ }
+ if (build != NULL) {
+ unsigned int len = strlen("BUILD=") + strlen(build) + 1;
+ build_env = xmalloc(len);
+ snprintf(build_env, len, "BUILD=%s", build);
+ if (putenv(build_env) != 0)
+ sysdie("cannot set BUILD in the environment");
+ }
+
+ /* Run the tests as instructed. */
+ if (single)
+ test_single(argv[0], source, build);
+ else if (list != NULL) {
+ shortlist = strrchr(list, '/');
+ if (shortlist == NULL)
+ shortlist = list;
+ else
+ shortlist++;
+ printf(banner, shortlist);
+ tests = read_test_list(list);
+ status = test_batch(tests, source, build) ? 0 : 1;
+ } else {
+ tests = build_test_list(argv, argc);
+ status = test_batch(tests, source, build) ? 0 : 1;
+ }
+
+ /* For valgrind cleanliness, free all our memory. */
+ if (source_env != NULL) {
+ putenv((char *) "SOURCE=");
+ free(source_env);
+ }
+ if (build_env != NULL) {
+ putenv((char *) "BUILD=");
+ free(build_env);
+ }
+ exit(status);
+}
diff --git a/tests/sample_conf.h b/tests/sample_conf.h
new file mode 100644
index 0000000..4630ced
--- /dev/null
+++ b/tests/sample_conf.h
@@ -0,0 +1,2 @@
+extern const unsigned sample_conf_rc_size;
+extern const char sample_conf_rc[];
diff --git a/tests/server.c b/tests/server.c
new file mode 100644
index 0000000..19060cc
--- /dev/null
+++ b/tests/server.c
@@ -0,0 +1,87 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <tap/basic.h>
+#include "knot/server/server.h"
+
+/*! Test: create server. */
+server_t *test_server_create()
+{
+ return server_create();
+}
+
+/*! Test: start server. */
+int test_server_start(server_t *s)
+{
+ return server_start(s) == 0;
+}
+
+/*! Test: finish server. */
+int test_server_finish(server_t *s)
+{
+ return server_wait(s) == 0;
+}
+
+/*! Test: stop server. */
+int test_server_destroy(server_t *s)
+{
+ server_destroy(&s);
+ return s == 0;
+}
+
+// Signal handler
+static void interrupt_handle(int s)
+{
+}
+
+/*! API: run tests. */
+int main(int argc, char *argv[])
+{
+ plan(4);
+
+ server_t *server = 0;
+ int ret = 0;
+
+ // Register service and signal handler
+ struct sigaction sa;
+ sa.sa_handler = interrupt_handle;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGALRM, &sa, NULL); // Interrupt
+
+ //! Test server for correct initialization
+ server = test_server_create();
+ ok(server != 0, "server: initialized");
+
+ //! Test server startup
+ ret = test_server_start(server);
+ ok(ret, "server: started ok");
+
+ if (!ret) {
+ skip_block(2, "server crashed, skipping deinit and destroy tests");
+ } else {
+ server_stop(server);
+
+ //! Test server waiting for finish
+ ok(test_server_finish(server), "server: waiting for finish");
+
+ //! Test server for correct deinitialization
+ ok(test_server_destroy(server), "server: deinit");
+ }
+
+ return 0;
+}
diff --git a/tests/slab.c b/tests/slab.c
new file mode 100644
index 0000000..bde7ae8
--- /dev/null
+++ b/tests/slab.c
@@ -0,0 +1,101 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdbool.h>
+#include <tap/basic.h>
+
+#include "common/slab/slab.h"
+
+/*! \brief Type-safe maximum macro. */
+#define SLAB_MAX(a, b) \
+ ({ typeof (a) _a = (a); typeof (b) _b = (b); _a > _b ? _a : _b; })
+
+
+/* Explicitly ask for symbols,
+ * as the constructor and destructor
+ * aren't created for test modules.
+ */
+extern void slab_init();
+extern void slab_deinit();
+
+int main(int argc, char *argv[])
+{
+ plan(4);
+
+ // 1. Create slab cache
+ srand(time(0));
+ const unsigned pattern = 0xdeadbeef;
+ slab_cache_t cache;
+ int ret = slab_cache_init(&cache, sizeof(int));
+ is_int(0, ret, "slab: created empty cache");
+
+ // 2. Couple alloc/free
+ bool valid_free = true;
+ for(int i = 0; i < 100; ++i) {
+ int* data = (int*)slab_cache_alloc(&cache);
+ *data = pattern;
+ slab_free(data);
+ if (*data == pattern)
+ valid_free = false;
+ }
+
+ // 5. Verify freed block
+ ok(valid_free, "slab: freed memory is correctly invalidated");
+
+ // 4. Reap memory
+ slab_t* slab = cache.slabs_free;
+ int free_count = 0;
+ while (slab) {
+ slab_t* next = slab->next;
+ if (slab_isempty(slab)) {
+ ++free_count;
+ }
+ slab = next;
+ }
+
+ int reaped = slab_cache_reap(&cache);
+ is_int(reaped, free_count, "slab: cache reaping works");
+
+ // Stress cache
+ int alloc_count = 73521;
+ void** ptrs = alloca(alloc_count * sizeof(void*));
+ int ptrs_i = 0;
+ for(int i = 0; i < alloc_count; ++i) {
+ double roll = rand() / (double) RAND_MAX;
+ if ((ptrs_i == 0) || (roll < 0.6)) {
+ int id = ptrs_i++;
+ ptrs[id] = slab_cache_alloc(&cache);
+ if (ptrs[id] == 0) {
+ ptrs_i--;
+ } else {
+ int* data = (int*)ptrs[id];
+ *data = pattern;
+ }
+ } else {
+ slab_free(ptrs[--ptrs_i]);
+ }
+ }
+
+ // 5. Delete cache
+ slab_cache_destroy(&cache);
+ is_int(0, cache.bufsize, "slab: freed cache");
+
+ return 0;
+}
diff --git a/tests/tap/basic.c b/tests/tap/basic.c
new file mode 100644
index 0000000..2d19d19
--- /dev/null
+++ b/tests/tap/basic.c
@@ -0,0 +1,631 @@
+/*
+ * Some utility routines for writing tests.
+ *
+ * Here are a variety of utility routines for writing tests compatible with
+ * the TAP protocol. All routines of the form ok() or is*() take a test
+ * number and some number of appropriate arguments, check to be sure the
+ * results match the expected output using the arguments, and print out
+ * something appropriate for that test number. Other utility routines help in
+ * constructing more complex tests, skipping tests, reporting errors, setting
+ * up the TAP output format, or finding things in the test environment.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2009, 2010, 2011, 2012 Russ Allbery <rra@stanford.edu>
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+# include <direct.h>
+#else
+# include <sys/stat.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <tests/tap/basic.h>
+
+/* Windows provides mkdir and rmdir under different names. */
+#ifdef _WIN32
+# define mkdir(p, m) _mkdir(p)
+# define rmdir(p) _rmdir(p)
+#endif
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+unsigned long testnum = 1;
+
+/*
+ * Status information stored so that we can give a test summary at the end of
+ * the test case. We store the planned final test and the count of failures.
+ * We can get the highest test count from testnum.
+ *
+ * We also store the PID of the process that called plan() and only summarize
+ * results when that process exits, so as to not misreport results in forked
+ * processes.
+ *
+ * If _lazy is true, we're doing lazy planning and will print out the plan
+ * based on the last test number at the end of testing.
+ */
+static unsigned long _planned = 0;
+static unsigned long _failed = 0;
+static pid_t _process = 0;
+static int _lazy = 0;
+
+
+/*
+ * Our exit handler. Called on completion of the test to report a summary of
+ * results provided we're still in the original process. This also handles
+ * printing out the plan if we used plan_lazy(), although that's suppressed if
+ * we never ran a test (due to an early bail, for example).
+ */
+static void
+finish(void)
+{
+ unsigned long highest = testnum - 1;
+
+ if (_planned == 0 && !_lazy)
+ return;
+ fflush(stderr);
+ if (_process != 0 && getpid() == _process) {
+ if (_lazy && highest > 0) {
+ printf("1..%lu\n", highest);
+ _planned = highest;
+ }
+ if (_planned > highest)
+ printf("# Looks like you planned %lu test%s but only ran %lu\n",
+ _planned, (_planned > 1 ? "s" : ""), highest);
+ else if (_planned < highest)
+ printf("# Looks like you planned %lu test%s but ran %lu extra\n",
+ _planned, (_planned > 1 ? "s" : ""), highest - _planned);
+ else if (_failed > 0)
+ printf("# Looks like you failed %lu test%s of %lu\n", _failed,
+ (_failed > 1 ? "s" : ""), _planned);
+ else if (_planned > 1)
+ printf("# All %lu tests successful or skipped\n", _planned);
+ else
+ printf("# %lu test successful or skipped\n", _planned);
+ }
+}
+
+
+/*
+ * Initialize things. Turns on line buffering on stdout and then prints out
+ * the number of tests in the test suite.
+ */
+void
+plan(unsigned long count)
+{
+ if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
+ fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
+ strerror(errno));
+ fflush(stderr);
+ printf("1..%lu\n", count);
+ testnum = 1;
+ _planned = count;
+ _process = getpid();
+ atexit(finish);
+}
+
+
+/*
+ * Initialize things for lazy planning, where we'll automatically print out a
+ * plan at the end of the program. Turns on line buffering on stdout as well.
+ */
+void
+plan_lazy(void)
+{
+ if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
+ fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
+ strerror(errno));
+ testnum = 1;
+ _process = getpid();
+ _lazy = 1;
+ atexit(finish);
+}
+
+
+/*
+ * Skip the entire test suite and exits. Should be called instead of plan(),
+ * not after it, since it prints out a special plan line.
+ */
+void
+skip_all(const char *format, ...)
+{
+ fflush(stderr);
+ printf("1..0 # skip");
+ if (format != NULL) {
+ va_list args;
+
+ putchar(' ');
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+ exit(0);
+}
+
+
+/*
+ * Print the test description.
+ */
+static void
+print_desc(const char *format, va_list args)
+{
+ printf(" - ");
+ vprintf(format, args);
+}
+
+
+/*
+ * Takes a boolean success value and assumes the test passes if that value
+ * is true and fails if that value is false.
+ */
+void
+ok(int success, const char *format, ...)
+{
+ fflush(stderr);
+ printf("%sok %lu", success ? "" : "not ", testnum++);
+ if (!success)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Same as ok(), but takes the format arguments as a va_list.
+ */
+void
+okv(int success, const char *format, va_list args)
+{
+ fflush(stderr);
+ printf("%sok %lu", success ? "" : "not ", testnum++);
+ if (!success)
+ _failed++;
+ if (format != NULL)
+ print_desc(format, args);
+ putchar('\n');
+}
+
+
+/*
+ * Skip a test.
+ */
+void
+skip(const char *reason, ...)
+{
+ fflush(stderr);
+ printf("ok %lu # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Report the same status on the next count tests.
+ */
+void
+ok_block(unsigned long count, int status, const char *format, ...)
+{
+ unsigned long i;
+
+ fflush(stderr);
+ for (i = 0; i < count; i++) {
+ printf("%sok %lu", status ? "" : "not ", testnum++);
+ if (!status)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Skip the next count tests.
+ */
+void
+skip_block(unsigned long count, const char *reason, ...)
+{
+ unsigned long i;
+
+ fflush(stderr);
+ for (i = 0; i < count; i++) {
+ printf("ok %lu # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Takes an expected integer and a seen integer and assumes the test passes
+ * if those two numbers match.
+ */
+void
+is_int(long long wanted, long long seen, const char *format, ...)
+{
+ fflush(stderr);
+ if (wanted == seen)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %lld\n# seen: %lld\n", wanted, seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes a string and what the string should be, and assumes the test passes
+ * if those strings match (using strcmp).
+ */
+void
+is_string(const char *wanted, const char *seen, const char *format, ...)
+{
+ if (wanted == NULL)
+ wanted = "(null)";
+ if (seen == NULL)
+ seen = "(null)";
+ fflush(stderr);
+ if (strcmp(wanted, seen) == 0)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %s\n# seen: %s\n", wanted, seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes an expected unsigned long and a seen unsigned long and assumes the
+ * test passes if the two numbers match. Otherwise, reports them in hex.
+ */
+void
+is_hex(unsigned long long wanted, unsigned long long seen,
+ const char *format, ...)
+{
+ fflush(stderr);
+ if (wanted == seen)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %llx\n# seen: %llx\n",
+ (unsigned long long) wanted,
+ (unsigned long long) seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Bail out with an error.
+ */
+void
+bail(const char *format, ...)
+{
+ va_list args;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+ exit(255);
+}
+
+
+/*
+ * Bail out with an error, appending strerror(errno).
+ */
+void
+sysbail(const char *format, ...)
+{
+ va_list args;
+ int oerrno = errno;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf(": %s\n", strerror(oerrno));
+ exit(255);
+}
+
+
+/*
+ * Report a diagnostic to stderr.
+ */
+void
+diag(const char *format, ...)
+{
+ va_list args;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("# ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+}
+
+
+/*
+ * Report a diagnostic to stderr, appending strerror(errno).
+ */
+void
+sysdiag(const char *format, ...)
+{
+ va_list args;
+ int oerrno = errno;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("# ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf(": %s\n", strerror(oerrno));
+}
+
+
+/*
+ * Allocate cleared memory, reporting a fatal error with bail on failure.
+ */
+void *
+bcalloc(size_t n, size_t size)
+{
+ void *p;
+
+ p = calloc(n, size);
+ if (p == NULL)
+ sysbail("failed to calloc %lu", (unsigned long)(n * size));
+ return p;
+}
+
+
+/*
+ * Allocate memory, reporting a fatal error with bail on failure.
+ */
+void *
+bmalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL)
+ sysbail("failed to malloc %lu", (unsigned long) size);
+ return p;
+}
+
+
+/*
+ * Reallocate memory, reporting a fatal error with bail on failure.
+ */
+void *
+brealloc(void *p, size_t size)
+{
+ p = realloc(p, size);
+ if (p == NULL)
+ sysbail("failed to realloc %lu bytes", (unsigned long) size);
+ return p;
+}
+
+
+/*
+ * Copy a string, reporting a fatal error with bail on failure.
+ */
+char *
+bstrdup(const char *s)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ if (p == NULL)
+ sysbail("failed to strdup %lu bytes", (unsigned long) len);
+ memcpy(p, s, len);
+ return p;
+}
+
+
+/*
+ * Copy up to n characters of a string, reporting a fatal error with bail on
+ * failure. Don't use the system strndup function, since it may not exist and
+ * the TAP library doesn't assume any portability support.
+ */
+char *
+bstrndup(const char *s, size_t n)
+{
+ const char *p;
+ char *copy;
+ size_t length;
+
+ /* Don't assume that the source string is nul-terminated. */
+ for (p = s; (size_t) (p - s) < n && *p != '\0'; p++)
+ ;
+ length = p - s;
+ copy = malloc(length + 1);
+ if (p == NULL)
+ sysbail("failed to strndup %lu bytes", (unsigned long) length);
+ memcpy(copy, s, length);
+ copy[length] = '\0';
+ return copy;
+}
+
+
+/*
+ * Locate a test file. Given the partial path to a file, look under BUILD and
+ * then SOURCE for the file and return the full path to the file. Returns
+ * NULL if the file doesn't exist. A non-NULL return should be freed with
+ * test_file_path_free().
+ *
+ * This function uses sprintf because it attempts to be independent of all
+ * other portability layers. The use immediately after a memory allocation
+ * should be safe without using snprintf or strlcpy/strlcat.
+ */
+char *
+test_file_path(const char *file)
+{
+ char *base;
+ char *path = NULL;
+ size_t length;
+ const char *envs[] = { "BUILD", "SOURCE", NULL };
+ int i;
+
+ for (i = 0; envs[i] != NULL; i++) {
+ base = getenv(envs[i]);
+ if (base == NULL)
+ continue;
+ length = strlen(base) + 1 + strlen(file) + 1;
+ path = bmalloc(length);
+ snprintf(path, length, "%s/%s", base, file);
+ if (access(path, R_OK) == 0)
+ break;
+ free(path);
+ path = NULL;
+ }
+ return path;
+}
+
+
+/*
+ * Free a path returned from test_file_path(). This function exists primarily
+ * for Windows, where memory must be freed from the same library domain that
+ * it was allocated from.
+ */
+void
+test_file_path_free(char *path)
+{
+ if (path != NULL)
+ free(path);
+}
+
+
+/*
+ * Create a temporary directory, tmp, under BUILD if set and the current
+ * directory if it does not. Returns the path to the temporary directory in
+ * newly allocated memory, and calls bail on any failure. The return value
+ * should be freed with test_tmpdir_free.
+ *
+ * This function uses sprintf because it attempts to be independent of all
+ * other portability layers. The use immediately after a memory allocation
+ * should be safe without using snprintf or strlcpy/strlcat.
+ */
+char *
+test_tmpdir(void)
+{
+ const char *build;
+ char *path = NULL;
+ size_t length;
+
+ build = getenv("BUILD");
+ if (build == NULL)
+ build = ".";
+ length = strlen(build) + strlen("/tmp") + 1;
+ path = bmalloc(length);
+ snprintf(path, length, "%s/tmp", build);
+ if (access(path, X_OK) < 0)
+ if (mkdir(path, 0777) < 0)
+ sysbail("error creating temporary directory %s", path);
+ return path;
+}
+
+
+/*
+ * Free a path returned from test_tmpdir() and attempt to remove the
+ * directory. If we can't delete the directory, don't worry; something else
+ * that hasn't yet cleaned up may still be using it.
+ */
+void
+test_tmpdir_free(char *path)
+{
+ rmdir(path);
+ if (path != NULL)
+ free(path);
+}
diff --git a/tests/tap/basic.h b/tests/tap/basic.h
new file mode 100644
index 0000000..544de51
--- /dev/null
+++ b/tests/tap/basic.h
@@ -0,0 +1,135 @@
+/*
+ * Basic utility routines for the TAP protocol.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2009, 2010, 2011, 2012 Russ Allbery <rra@stanford.edu>
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_BASIC_H
+#define TAP_BASIC_H 1
+
+#include <tests/tap/macros.h>
+#include <stdarg.h> /* va_list */
+#include <sys/types.h> /* size_t */
+
+/*
+ * Used for iterating through arrays. ARRAY_SIZE returns the number of
+ * elements in the array (useful for a < upper bound in a for loop) and
+ * ARRAY_END returns a pointer to the element past the end (ISO C99 makes it
+ * legal to refer to such a pointer as long as it's never dereferenced).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#define ARRAY_END(array) (&(array)[ARRAY_SIZE(array)])
+
+BEGIN_DECLS
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+extern unsigned long testnum;
+
+/* Print out the number of tests and set standard output to line buffered. */
+void plan(unsigned long count);
+
+/*
+ * Prepare for lazy planning, in which the plan will be printed automatically
+ * at the end of the test program.
+ */
+void plan_lazy(void);
+
+/* Skip the entire test suite. Call instead of plan. */
+void skip_all(const char *format, ...)
+ __attribute__((__noreturn__, __format__(printf, 1, 2)));
+
+/*
+ * Basic reporting functions. The okv() function is the same as ok() but
+ * takes the test description as a va_list to make it easier to reuse the
+ * reporting infrastructure when writing new tests.
+ */
+void ok(int success, const char *format, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void okv(int success, const char *format, va_list args);
+void skip(const char *reason, ...)
+ __attribute__((__format__(printf, 1, 2)));
+
+/* Report the same status on, or skip, the next count tests. */
+void ok_block(unsigned long count, int success, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void skip_block(unsigned long count, const char *reason, ...)
+ __attribute__((__format__(printf, 2, 3)));
+
+/* Check an expected value against a seen value. */
+void is_int(long long wanted, long long seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_string(const char *wanted, const char *seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_hex(unsigned long long wanted, unsigned long long seen,
+ const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+
+/* Bail out with an error. sysbail appends strerror(errno). */
+void bail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+void sysbail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+
+/* Report a diagnostic to stderr prefixed with #. */
+void diag(const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 1, 2)));
+void sysdiag(const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 1, 2)));
+
+/* Allocate memory, reporting a fatal error with bail on failure. */
+void *bcalloc(size_t, size_t)
+ __attribute__((__alloc_size__(1, 2), __malloc__));
+void *bmalloc(size_t)
+ __attribute__((__alloc_size__(1), __malloc__));
+void *brealloc(void *, size_t)
+ __attribute__((__alloc_size__(2), __malloc__));
+char *bstrdup(const char *)
+ __attribute__((__malloc__, __nonnull__));
+char *bstrndup(const char *, size_t)
+ __attribute__((__malloc__, __nonnull__));
+
+/*
+ * Find a test file under BUILD or SOURCE, returning the full path. The
+ * returned path should be freed with test_file_path_free().
+ */
+char *test_file_path(const char *file)
+ __attribute__((__malloc__, __nonnull__));
+void test_file_path_free(char *path);
+
+/*
+ * Create a temporary directory relative to BUILD and return the path. The
+ * returned path should be freed with test_tmpdir_free.
+ */
+char *test_tmpdir(void)
+ __attribute__((__malloc__));
+void test_tmpdir_free(char *path);
+
+END_DECLS
+
+#endif /* TAP_BASIC_H */
diff --git a/tests/tap/float.c b/tests/tap/float.c
new file mode 100644
index 0000000..67dd555
--- /dev/null
+++ b/tests/tap/float.c
@@ -0,0 +1,67 @@
+/*
+ * Utility routines for writing floating point tests.
+ *
+ * Currently provides only one function, which checks whether a double is
+ * equal to an expected value within a given epsilon. This is broken into a
+ * separate source file from the rest of the basic C TAP library because it
+ * may require linking with -lm on some platforms, and the package may not
+ * otherwise care about floating point.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2010, 2012 Russ Allbery <rra@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/* Required for isnan() and isinf(). */
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 600
+# endif
+#endif
+
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <tests/tap/basic.h>
+#include <tests/tap/float.h>
+
+/*
+ * Takes an expected double and a seen double and assumes the test passes if
+ * those two numbers are within delta of each other.
+ */
+void
+is_double(double wanted, double seen, double epsilon, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ fflush(stderr);
+ if ((isnan(wanted) && isnan(seen))
+ || (isinf(wanted) && isinf(seen) && wanted == seen)
+ || fabs(wanted - seen) <= epsilon)
+ okv(1, format, args);
+ else {
+ printf("# wanted: %g\n# seen: %g\n", wanted, seen);
+ okv(0, format, args);
+ }
+}
diff --git a/tests/tap/float.h b/tests/tap/float.h
new file mode 100644
index 0000000..7464535
--- /dev/null
+++ b/tests/tap/float.h
@@ -0,0 +1,42 @@
+/*
+ * Floating point check function for the TAP protocol.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2010, 2012 Russ Allbery <rra@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_FLOAT_H
+#define TAP_FLOAT_H 1
+
+#include <tests/tap/macros.h>
+
+BEGIN_DECLS
+
+/* Check an expected value against a seen value within epsilon. */
+void is_double(double wanted, double seen, double epsilon,
+ const char *format, ...)
+ __attribute__((__format__(printf, 4, 5)));
+
+END_DECLS
+
+#endif /* TAP_FLOAT_H */
diff --git a/tests/tap/macros.h b/tests/tap/macros.h
new file mode 100644
index 0000000..e19624a
--- /dev/null
+++ b/tests/tap/macros.h
@@ -0,0 +1,88 @@
+/*
+ * Helpful macros for TAP header files.
+ *
+ * This is not, strictly speaking, related to TAP, but any TAP add-on is
+ * probably going to need these macros, so define them in one place so that
+ * everyone can pull them in.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2012 Russ Allbery <rra@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_MACROS_H
+#define TAP_MACROS_H 1
+
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros), and only with gcc 2.96 can you use
+ * the attribute __malloc__. 2.96 is very old, so don't bother trying to get
+ * the other attributes to work with GCC versions between 2.7 and 2.96.
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+# define __attribute__(spec) /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC. Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+# define __alloc_size__(spec, args...) /* empty */
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does. For them, suppress warnings about unknown
+ * attributes on declarations. This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Used for unused parameters to silence gcc warnings. */
+/* #define UNUSED __attribute__((__unused__)) */
+
+/*
+ * BEGIN_DECLS is used at the beginning of declarations so that C++
+ * compilers don't mangle their names. END_DECLS is used at the end.
+ */
+#undef BEGIN_DECLS
+#undef END_DECLS
+#ifdef __cplusplus
+# define BEGIN_DECLS extern "C" {
+# define END_DECLS }
+#else
+# define BEGIN_DECLS /* empty */
+# define END_DECLS /* empty */
+#endif
+
+#endif /* TAP_MACROS_H */
diff --git a/tests/wire.c b/tests/wire.c
new file mode 100644
index 0000000..3667a31
--- /dev/null
+++ b/tests/wire.c
@@ -0,0 +1,47 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <tap/basic.h>
+
+#include "libknot/util/utils.h"
+
+#define write_test(size, value, ...) { \
+ const uint8_t expect[] = { __VA_ARGS__ }; \
+ uint8_t wdata[sizeof(expect)] = { 0x00 }; \
+ knot_wire_write_u ## size(wdata, value); \
+ ok(memcmp(wdata, expect, sizeof(expect)) == 0, "%d-bit write", size); \
+}
+
+int main(int argc, char *argv[])
+{
+ plan(8);
+
+ const uint8_t rdata[] = { 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
+
+ is_hex( 0x8899, knot_wire_read_u16(rdata), "16-bit read");
+ is_hex( 0x8899aabb, knot_wire_read_u32(rdata), "32-bit read");
+ is_hex( 0x8899aabbccdd, knot_wire_read_u48(rdata), "48-bit read");
+ is_hex(0x8899aabbccddeeff, knot_wire_read_u64(rdata), "64-bit read");
+
+ write_test(16, 0x1122, 0x11, 0x22);
+ write_test(32, 0x66778899, 0x66, 0x77, 0x88, 0x99);
+ write_test(48, 0xbbccdd778899, 0xbb, 0xcc, 0xdd, 0x77, 0x88, 0x99);
+ write_test(64, 0xbbccddee66778899, 0xbb, 0xcc, 0xdd, 0xee,
+ 0x66, 0x77, 0x88, 0x99);
+
+ return 0;
+}
diff --git a/tests/zonedb.c b/tests/zonedb.c
new file mode 100644
index 0000000..bdb6f57
--- /dev/null
+++ b/tests/zonedb.c
@@ -0,0 +1,117 @@
+/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <tap/basic.h>
+
+#include "libknot/zone/zonedb.h"
+#include "libknot/zone/zone.h"
+
+#define ZONE_COUNT 10
+static const char *zone_list[ZONE_COUNT] = {
+ ".",
+ "com",
+ "net",
+ "c.com",
+ "a.com",
+ "a.net",
+ "b.net",
+ "c.a.com",
+ "b.b.b.com",
+ "b.b.b.b.net",
+};
+
+int main(int argc, char *argv[])
+{
+ plan(6);
+
+ /* Create database. */
+ char buf[KNOT_DNAME_MAX_LENGTH];
+ const char *prefix = "zzz.";
+ size_t nr_passed = 0;
+ knot_dname_t *dname = NULL;
+ knot_zone_t *zone = NULL;
+ knot_zone_t *zones[ZONE_COUNT] = {0};
+ knot_zonedb_t *db = knot_zonedb_new(ZONE_COUNT);
+ ok(db != NULL, "zonedb: new");
+
+ /* Populate. */
+ for (unsigned i = 0; i < ZONE_COUNT; ++i) {
+ dname = knot_dname_from_str(zone_list[i]);
+ zones[i] = knot_zone_new_empty(dname);
+ if (zones[i] == NULL) {
+ knot_dname_free(&dname);
+ goto cleanup;
+ }
+ if (knot_zonedb_add_zone(db, zones[i]) == KNOT_EOK) {
+ ++nr_passed;
+ } else {
+ diag("knot_zonedb_add_zone(%s) failed", zone_list[i]);
+ }
+ }
+ ok(nr_passed == ZONE_COUNT, "zonedb: add zones");
+
+ /* Build search index. */
+ ok(knot_zonedb_build_index(db) == KNOT_EOK, "zonedb: build search index");
+
+ /* Lookup of exact names. */
+ nr_passed = 0;
+ for (unsigned i = 0; i < ZONE_COUNT; ++i) {
+ dname = knot_dname_from_str(zone_list[i]);
+ if (knot_zonedb_find_zone(db, dname) == zones[i]) {
+ ++nr_passed;
+ } else {
+ diag("knot_zonedb_find_zone(%s) failed", zone_list[i]);
+ }
+ knot_dname_free(&dname);
+ }
+ ok(nr_passed == ZONE_COUNT, "zonedb: find exact zones");
+
+ /* Lookup of sub-names. */
+ nr_passed = 0;
+ for (unsigned i = 0; i < ZONE_COUNT; ++i) {
+ strcpy(buf, prefix);
+ if (strcmp(zone_list[i], ".") != 0) {
+ strncat(buf, zone_list[i], strlen(zone_list[i]));
+ }
+ dname = knot_dname_from_str(buf);
+ if (knot_zonedb_find_zone_for_name(db, dname) == zones[i]) {
+ ++nr_passed;
+ } else {
+ diag("knot_zonedb_find_zone(%s) failed", buf);
+ }
+ knot_dname_free(&dname);
+ }
+ ok(nr_passed == ZONE_COUNT, "zonedb: find zones for subnames");
+
+ /* Remove all zones. */
+ nr_passed = 0;
+ for (unsigned i = 0; i < ZONE_COUNT; ++i) {
+ dname = knot_dname_from_str(zone_list[i]);
+ zone = knot_zonedb_remove_zone(db, dname);
+ if (zone == zones[i]) {
+ knot_zone_free(&zone);
+ ++nr_passed;
+ } else {
+ diag("knot_zonedb_remove_zone(%s) failed", zone_list[i]);
+ }
+ }
+ ok(nr_passed == ZONE_COUNT, "zonedb: removed all zones");
+
+cleanup:
+ knot_zonedb_deep_free(&db);
+ return 0;
+}
diff --git a/tests/ztree.c b/tests/ztree.c
new file mode 100644
index 0000000..f4c583e
--- /dev/null
+++ b/tests/ztree.c
@@ -0,0 +1,121 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <tap/basic.h>
+
+#include "libknot/zone/zone-tree.h"
+
+#define NCOUNT 4
+static knot_dname_t* NAME[NCOUNT];
+static knot_node_t NODE[NCOUNT];
+static knot_dname_t* ORDER[NCOUNT];
+static void ztree_init_data()
+{
+ NAME[0] = knot_dname_from_str(".");
+ NAME[1] = knot_dname_from_str("master.ac.");
+ NAME[2] = knot_dname_from_str("ac.");
+ NAME[3] = knot_dname_from_str("ns.");
+
+ knot_dname_t *order[NCOUNT] = {
+ NAME[0], NAME[2], NAME[1], NAME[3]
+ };
+ memcpy(ORDER, order, NCOUNT * sizeof(knot_dname_t*));
+
+ for (unsigned i = 0; i < NCOUNT; ++i) {
+ memset(NODE + i, 0, sizeof(knot_node_t));
+ NODE[i].owner = NAME[i];
+ NODE[i].prev = NODE + ((NCOUNT + i - 1) % NCOUNT);
+ NODE[i].rrset_count = 1; /* required for ordered search */
+ }
+}
+
+static void ztree_free_data()
+{
+ for (unsigned i = 0; i < NCOUNT; ++i)
+ knot_dname_free(NAME + i);
+}
+
+static int ztree_iter_data(knot_node_t **node, void *data)
+{
+ unsigned *i = (unsigned *)data;
+ knot_dname_t *owner = (*node)->owner;
+ int result = KNOT_EOK;
+ if (owner != ORDER[*i]) {
+ result = KNOT_ERROR;
+ char *exp_s = knot_dname_to_str(ORDER[*i]);
+ char *owner_s = knot_dname_to_str(owner);
+ diag("ztree: at index: %u expected '%s' got '%s'\n", *i, exp_s, owner_s);
+ free(exp_s);
+ free(owner_s);
+ }
+ ++(*i);
+ return result;
+}
+
+int main(int argc, char *argv[])
+{
+ plan(5);
+
+ ztree_init_data();
+
+ /* 1. create test */
+ knot_zone_tree_t* t = knot_zone_tree_create();
+ ok(t != NULL, "ztree: created");
+
+ /* 2. insert test */
+ unsigned passed = 1;
+ for (unsigned i = 0; i < NCOUNT; ++i) {
+ if (knot_zone_tree_insert(t, NODE + i) != KNOT_EOK) {
+ passed = 0;
+ break;
+ }
+ }
+ ok(passed, "ztree: insertion");
+
+ /* 3. check data test */
+ passed = 1;
+ const knot_node_t *node = NULL;
+ for (unsigned i = 0; i < NCOUNT; ++i) {
+ int r = knot_zone_tree_find(t, NAME[i], &node);
+ if (r != KNOT_EOK || node != NODE + i) {
+ passed = 0;
+ break;
+ }
+ }
+ ok(passed, "ztree: lookup");
+
+ /* heal index for ordered lookup */
+ hattrie_build_index(t);
+
+ /* 4. ordered lookup */
+ passed = 1;
+ node = NULL;
+ const knot_node_t *prev = NULL;
+ knot_dname_t *tmp_dn = knot_dname_from_str("z.ac.");
+ knot_zone_tree_find_less_or_equal(t, tmp_dn, &node, &prev);
+ knot_dname_free(&tmp_dn);
+ ok(prev == NODE + 1, "ztree: ordered lookup");
+
+ /* 5. ordered traversal */
+ unsigned i = 0;
+ int ret = knot_zone_tree_apply_inorder(t, ztree_iter_data, &i);
+ ok (ret == KNOT_EOK, "ztree: ordered traversal");
+
+ knot_zone_tree_free(&t);
+ ztree_free_data();
+ return 0;
+}