From 5fd83cabfd04cfcf82905029a278c341d2aadb2b Mon Sep 17 00:00:00 2001 From: Ondřej Surý Date: Mon, 6 Jan 2014 10:48:17 +0100 Subject: New upstream version 1.4.0 --- tests/Makefile.inc | 63 ++ tests/TESTS | 23 + tests/acl.c | 155 +++++ tests/base32hex.c | 211 +++++++ tests/base64.c | 199 +++++++ tests/conf.c | 132 +++++ tests/data/sample_conf | 59 ++ tests/descriptor.c | 256 ++++++++ tests/dname.c | 178 ++++++ tests/dnssec_keys.c | 248 ++++++++ tests/dnssec_nsec3.c | 102 ++++ tests/dnssec_sign.c | 153 +++++ tests/dnssec_zone_nsec.c | 45 ++ tests/dthreads.c | 309 ++++++++++ tests/events.c | 183 ++++++ tests/fdset.c | 154 +++++ tests/hattrie.c | 198 +++++++ tests/hhash.c | 173 ++++++ tests/journal.c | 280 +++++++++ tests/resource.sh | 30 + tests/rrl.c | 208 +++++++ tests/rrset.c | 1458 ++++++++++++++++++++++++++++++++++++++++++++++ tests/runtests.c | 1385 +++++++++++++++++++++++++++++++++++++++++++ tests/sample_conf.h | 2 + tests/server.c | 87 +++ tests/slab.c | 101 ++++ tests/tap/basic.c | 631 ++++++++++++++++++++ tests/tap/basic.h | 135 +++++ tests/tap/float.c | 67 +++ tests/tap/float.h | 42 ++ tests/tap/macros.h | 88 +++ tests/wire.c | 47 ++ tests/zonedb.c | 117 ++++ tests/ztree.c | 121 ++++ 34 files changed, 7640 insertions(+) create mode 100644 tests/Makefile.inc create mode 100644 tests/TESTS create mode 100644 tests/acl.c create mode 100644 tests/base32hex.c create mode 100644 tests/base64.c create mode 100644 tests/conf.c create mode 100644 tests/data/sample_conf create mode 100644 tests/descriptor.c create mode 100644 tests/dname.c create mode 100644 tests/dnssec_keys.c create mode 100644 tests/dnssec_nsec3.c create mode 100644 tests/dnssec_sign.c create mode 100644 tests/dnssec_zone_nsec.c create mode 100644 tests/dthreads.c create mode 100644 tests/events.c create mode 100644 tests/fdset.c create mode 100644 tests/hattrie.c create mode 100644 tests/hhash.c create mode 100644 tests/journal.c create mode 100755 tests/resource.sh create mode 100644 tests/rrl.c create mode 100644 tests/rrset.c create mode 100644 tests/runtests.c create mode 100644 tests/sample_conf.h create mode 100644 tests/server.c create mode 100644 tests/slab.c create mode 100644 tests/tap/basic.c create mode 100644 tests/tap/basic.h create mode 100644 tests/tap/float.c create mode 100644 tests/tap/float.h create mode 100644 tests/tap/macros.h create mode 100644 tests/wire.c create mode 100644 tests/zonedb.c create mode 100644 tests/ztree.c (limited to 'tests') 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. + + 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 . + */ + +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include + +#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. + + 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 . + */ + +#include +#include + +#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", ¶ms.public_exponent); + + result = knot_free_key_params(¶ms); + ok(result == KNOT_EOK + && memcmp(¶ms, &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(¶ms, &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(¶ms, &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(¶ms, &tsig_key); + ok(result == KNOT_EINVAL, + "knot_tsig_key_from_params(), no shared secret"); + + params.name = name; + knot_binary_from_base64("Ok6NmA==", ¶ms.secret); + uint8_t decoded_secret[] = { 0x3a, 0x4e, 0x8d, 0x98 }; + result = knot_tsig_key_from_params(¶ms, &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(¶ms); + 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. + + 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 . + */ + +#include +#include +#include +#include + +#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(¶ms, 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(¶ms); + + // 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(¶ms, 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. + + 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 . + */ + +#include +#include +#include +#include + +#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. + + 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 . +*/ + +#include +#include + +#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, ¶ms); + 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. + + 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include +#include + +#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 ? "" : *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. + + 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 . + */ + +#include +#include +#include +#include + +#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. + + 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 +# Resource file is printed to stdout. +# Usable variables ( is stripped from path and extension): +# const char *_rc; // File content in binary format +# const unsigned /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. + + 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 . + */ + +#include +#include +#include +#include + +#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. + + 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 . + */ +#include +#include +#include +#include +#include + +#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 ] [-s ] + * runtests -o [-b ] [-s ] + * + * 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 + * not ok + * ok # skip + * not ok # todo + * + * where 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 . + * + * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011 + * Russ Allbery + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* sys/time.h must be included before sys/resource.h on some platforms. */ +#include + +/* 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 ] [-s ] ...\n\ + %s [-b ] [-s ] -l \n\ + %s -o [-b ] [-s ] \n\ +\n%s"; +static const char usage_extra[] = "\ +Options:\n\ + -b Set the build directory to \n\ + -l Take the list of tests to run from \n\ + -o Run a single test rather than a list of tests\n\ + -s Set the source directory to \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. + + 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 . + */ + +#include +#include +#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. + + 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 . + */ + +#include +#include +#include +#include +#include +#include + +#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 . + * + * Copyright 2009, 2010, 2011, 2012 Russ Allbery + * 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 +#include +#include +#include +#include +#ifdef _WIN32 +# include +#else +# include +#endif +#include +#include + +#include + +/* 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 . + * + * Copyright 2009, 2010, 2011, 2012 Russ Allbery + * 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 +#include /* va_list */ +#include /* 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 . + * + * Copyright 2008, 2010, 2012 Russ Allbery + * + * 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 +#include +#include + +#include +#include + +/* + * 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 . + * + * Copyright 2008, 2010, 2012 Russ Allbery + * + * 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 + +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 . + * + * Copyright 2008, 2012 Russ Allbery + * + * 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. + + 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 . + */ + +#include +#include + +#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. + + 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 . + */ + +#include +#include + +#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. + + 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 . + */ + +#include +#include + +#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; +} -- cgit v1.2.3