summaryrefslogtreecommitdiffstats
path: root/tests/knot/test_journal.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/knot/test_journal.c746
1 files changed, 746 insertions, 0 deletions
diff --git a/tests/knot/test_journal.c b/tests/knot/test_journal.c
new file mode 100644
index 0000000..e12cc84
--- /dev/null
+++ b/tests/knot/test_journal.c
@@ -0,0 +1,746 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <tap/basic.h>
+#include <tap/files.h>
+
+#include "libknot/libknot.h"
+#include "knot/journal/journal.c"
+#include "knot/zone/zone.h"
+#include "knot/zone/zone-diff.h"
+#include "libknot/rrtype/soa.h"
+#include "test_conf.h"
+
+#define RAND_RR_LABEL 16
+#define RAND_RR_PAYLOAD 64
+#define MIN_SOA_SIZE 22
+
+char *test_dir_name;
+journal_db_t *db;
+journal_t *j;
+const knot_dname_t *apex = (const uint8_t *)"\4test";
+
+static void set_conf(int zonefile_sync, size_t journal_usage)
+{
+ char conf_str[512];
+ snprintf(conf_str, sizeof(conf_str),
+ "zone:\n"
+ " - domain: %s\n"
+ " zonefile-sync: %d\n"
+ " max-journal-usage: %zu\n"
+ " max-journal-depth: 1000\n",
+ (const char *)(apex + 1), zonefile_sync, journal_usage);
+ int ret = test_conf(conf_str, NULL);
+ (void)ret;
+ assert(ret == KNOT_EOK);
+}
+
+static void unset_conf(void)
+{
+ conf_update(NULL, CONF_UPD_FNONE);
+}
+
+/*! \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 Init RRSet with type SOA and given serial. */
+static void init_soa(knot_rrset_t *rr, const uint32_t serial, const knot_dname_t *apex)
+{
+ knot_rrset_init(rr, knot_dname_copy(apex, NULL), KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 3600);
+
+ uint8_t soa_data[MIN_SOA_SIZE] = { 0 };
+ int ret = knot_rrset_add_rdata(rr, soa_data, sizeof(soa_data), NULL);
+ knot_soa_serial_set(rr->rrs.rdata, serial);
+ (void)ret;
+ assert(ret == KNOT_EOK);
+}
+
+/*! \brief Init RRSet with type TXT, random owner and random payload. */
+static void init_random_rr(knot_rrset_t *rr , const knot_dname_t *apex)
+{
+ /* Create random label. */
+ char owner[RAND_RR_LABEL + knot_dname_size(apex)];
+ owner[0] = RAND_RR_LABEL - 1;
+ randstr(owner + 1, RAND_RR_LABEL);
+
+ /* Append zone apex. */
+ memcpy(owner + RAND_RR_LABEL, apex, knot_dname_size(apex));
+ knot_rrset_init(rr, knot_dname_copy((knot_dname_t *)owner, NULL),
+ KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 3600);
+
+ /* Create random RDATA. */
+ uint8_t txt[RAND_RR_PAYLOAD + 1];
+ txt[0] = RAND_RR_PAYLOAD - 1;
+ randstr((char *)(txt + 1), RAND_RR_PAYLOAD);
+
+ int ret = knot_rrset_add_rdata(rr, txt, RAND_RR_PAYLOAD, NULL);
+ (void)ret;
+ assert(ret == KNOT_EOK);
+}
+
+/*! \brief Init changeset with random changes. */
+static void init_random_changeset(changeset_t *ch, const uint32_t from, const uint32_t to,
+ const size_t size, const knot_dname_t *apex, bool is_bootstrap)
+{
+ // Add SOAs
+ knot_rrset_t soa;
+
+ if (is_bootstrap) {
+ ch->soa_from = NULL;
+ } else {
+ init_soa(&soa, from, apex);
+ ch->soa_from = knot_rrset_copy(&soa, NULL);
+ assert(ch->soa_from);
+ knot_rrset_clear(&soa, NULL);
+ }
+
+ init_soa(&soa, to, apex);
+ ch->soa_to = knot_rrset_copy(&soa, NULL);
+ assert(ch->soa_to);
+ knot_rrset_clear(&soa, NULL);
+
+ // Add RRs to add section
+ for (size_t i = 0; i < size / 2; ++i) {
+ knot_rrset_t rr;
+ init_random_rr(&rr, apex);
+ int ret = changeset_add_addition(ch, &rr, 0);
+ (void)ret;
+ assert(ret == KNOT_EOK);
+ knot_rrset_clear(&rr, NULL);
+ }
+
+ // Add RRs to remove section
+ for (size_t i = 0; i < size / 2 && !is_bootstrap; ++i) {
+ knot_rrset_t rr;
+ init_random_rr(&rr, apex);
+ int ret = changeset_add_removal(ch, &rr, 0);
+ (void)ret;
+ assert(ret == KNOT_EOK);
+ knot_rrset_clear(&rr, NULL);
+ }
+}
+
+static void changeset_set_soa_serials(changeset_t *ch, uint32_t from, uint32_t to,
+ const knot_dname_t *apex)
+{
+ knot_rrset_t soa;
+
+ init_soa(&soa, from, apex);
+ knot_rrset_free(ch->soa_from, NULL);
+ ch->soa_from = knot_rrset_copy(&soa, NULL);
+ assert(ch->soa_from);
+ knot_rrset_clear(&soa, NULL);
+
+ init_soa(&soa, to, apex);
+ knot_rrset_free(ch->soa_to, NULL);
+ ch->soa_to = knot_rrset_copy(&soa, NULL);
+ assert(ch->soa_to);
+ knot_rrset_clear(&soa, NULL);
+}
+
+/*! \brief Compare two changesets for equality. */
+static bool changesets_eq(const changeset_t *ch1, changeset_t *ch2)
+{
+ if (changeset_size(ch1) != changeset_size(ch2)) {
+ return false;
+ }
+
+ changeset_iter_t it1;
+ changeset_iter_all(&it1, ch1);
+ changeset_iter_t it2;
+ changeset_iter_all(&it2, ch2);
+
+ knot_rrset_t rr1 = changeset_iter_next(&it1);
+ knot_rrset_t rr2 = changeset_iter_next(&it2);
+ bool ret = true;
+ while (!knot_rrset_empty(&rr1)) {
+ if (!knot_rrset_equal(&rr1, &rr2, KNOT_RRSET_COMPARE_WHOLE)) {
+ ret = false;
+ break;
+ }
+ rr1 = changeset_iter_next(&it1);
+ rr2 = changeset_iter_next(&it2);
+ }
+
+ changeset_iter_clear(&it1);
+ changeset_iter_clear(&it2);
+
+ return ret;
+}
+
+static bool changesets_list_eq(list_t *l1, list_t *l2)
+{
+ node_t *n = NULL;
+ node_t *k = HEAD(*l2);
+ WALK_LIST(n, *l1) {
+ if (k == NULL) {
+ return false;
+ }
+
+ changeset_t *ch1 = (changeset_t *) n;
+ changeset_t *ch2 = (changeset_t *) k;
+ if (!changesets_eq(ch1, ch2)) {
+ return false;
+ }
+
+ k = k->next;
+ }
+
+ if (k->next != NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/*! \brief Test a list of changesets for continuity. */
+static bool test_continuity(list_t *l)
+{
+ node_t *n = NULL;
+ uint32_t key1, key2;
+ WALK_LIST(n, *l) {
+ if (n == TAIL(*l)) {
+ break;
+ }
+ changeset_t *ch1 = (changeset_t *) n;
+ changeset_t *ch2 = (changeset_t *) n->next;
+ key1 = knot_soa_serial(ch1->soa_to->rrs.rdata);
+ key2 = knot_soa_serial(ch2->soa_from->rrs.rdata);
+ if (key1 != key2) {
+ return KNOT_EINVAL;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static void test_journal_db(void)
+{
+ int ret, ret2 = KNOT_EOK;
+
+ ret = journal_db_init(&db, test_dir_name, 2 * 1024 * 1024, JOURNAL_MODE_ASYNC);
+ is_int(KNOT_EOK, ret, "journal: init db (%d)", ret);
+
+ ret = journal_open_db(&db);
+ is_int(KNOT_EOK, ret, "journal: open db (%d)", ret);
+
+ journal_db_close(&db);
+ ok(db == NULL, "journal: close and destroy db");
+
+ ret = journal_db_init(&db, test_dir_name, 4 * 1024 * 1024, JOURNAL_MODE_ASYNC);
+ if (ret == KNOT_EOK) ret2 = journal_open_db(&db);
+ ok(ret == KNOT_EOK && ret2 == KNOT_EOK, "journal: open with bigger mapsize (%d, %d)", ret, ret2);
+ journal_db_close(&db);
+
+ ret = journal_db_init(&db, test_dir_name, 1024 * 1024, JOURNAL_MODE_ASYNC);
+ if (ret == KNOT_EOK) ret2 = journal_open_db(&db);
+ ok(ret == KNOT_EOK && ret2 == KNOT_EOK, "journal: open with smaller mapsize (%d, %d)", ret, ret2);
+ journal_db_close(&db);
+}
+
+/*! \brief Test behavior with real changesets. */
+static void test_store_load(void)
+{
+ int ret, ret2 = KNOT_EOK;
+
+ set_conf(1000, 512 * 1024);
+
+ j = journal_new();
+ ok(j != NULL, "journal: new");
+
+ ret = journal_db_init(&db, test_dir_name, (512 + 1024) * 1024, JOURNAL_MODE_ASYNC);
+ if (ret == KNOT_EOK) ret2 = journal_open(j, &db, apex);
+ is_int(KNOT_EOK, ret, "journal: open (%d, %d)", ret, ret2);
+
+ /* Save and load changeset. */
+ changeset_t *m_ch = changeset_new(apex);
+ init_random_changeset(m_ch, 0, 1, 128, apex, false);
+ ret = journal_store_changeset(j, m_ch);
+ is_int(KNOT_EOK, ret, "journal: store changeset (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_INFO);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+ list_t l, k;
+ init_list(&l);
+ init_list(&k);
+ ret = journal_load_changesets(j, &l, 0);
+ add_tail(&k, &m_ch->n);
+ ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load changeset (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+
+ /* Load ctx's. */
+ chgset_ctx_list_t cl = { { 0 }, 0 };
+ ret = journal_load_chgset_ctx(j, &cl, 0);
+ ok(ret == KNOT_EOK, "journal: chgset_ctx: load (%s)", knot_strerror(ret));
+ chgset_ctx_list_close(&cl);
+
+ changesets_free(&l);
+ changesets_free(&k);
+
+ /* Flush the journal. */
+ ret = journal_flush(j);
+ is_int(KNOT_EOK, ret, "journal: first and simple flush (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+ init_list(&l);
+ init_list(&k);
+
+ /* Fill the journal. */
+ ret = KNOT_EOK;
+ uint32_t serial = 1;
+ for (; ret == KNOT_EOK && serial < 40000; ++serial) {
+ changeset_t *m_ch2 = changeset_new(apex);
+ init_random_changeset(m_ch2, serial, serial + 1, 128, apex, false);
+ ret = journal_store_changeset(j, m_ch2);
+ if (ret != KNOT_EOK) {
+ changeset_free(m_ch2);
+ break;
+ }
+ add_tail(&k, &m_ch2->n);
+ }
+ is_int(KNOT_EBUSY, ret, "journal: overfill with changesets (%d inserted) (%d should= %d)",
+ serial, ret, KNOT_EBUSY);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+
+ /* Load all changesets stored until now. */
+ ret = journal_load_changesets(j, &l, 1);
+ ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load changesets (%d)", ret);
+
+ changesets_free(&l);
+ init_list(&l);
+ ret = journal_load_changesets(j, &l, 1);
+ ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: re-load changesets (%d)", ret);
+
+ ret = journal_load_chgset_ctx(j, &cl, 1);
+ ok(ret == KNOT_EOK, "journal: chgset_ctx: load 2 (%s)", knot_strerror(ret));
+ ok(list_size(&cl.l) == list_size(&l), "journal: chgset_ctx: load size %zu ?== %zu", list_size(&cl.l), list_size(&l));
+ chgset_ctx_list_close(&cl);
+
+ changesets_free(&l);
+ init_list(&l);
+
+ /* Flush the journal. */
+ ret = journal_flush(j);
+ is_int(KNOT_EOK, ret, "journal: second flush (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+
+ /* Test whether the journal kept changesets after flush. */
+ ret = journal_load_changesets(j, &l, 1);
+ ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load right after flush (%d)", ret);
+
+ changesets_free(&k);
+ changesets_free(&l);
+ init_list(&k);
+ init_list(&l);
+
+ /* Store next changeset. */
+ changeset_t ch;
+ ret = changeset_init(&ch, apex);
+ ok(ret == KNOT_EOK, "journal: changeset init (%d)", ret);
+ init_random_changeset(&ch, serial, serial + 1, 128, apex, false);
+ ret = journal_store_changeset(j, &ch);
+ changeset_clear(&ch);
+ is_int(KNOT_EOK, ret, "journal: store after flush (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+
+ /* Load last changesets. */
+ init_list(&l);
+ ret = journal_load_changesets(j, &l, serial);
+ changesets_free(&l);
+ is_int(KNOT_EOK, ret, "journal: load changesets after flush (%d)", ret);
+
+ /* Flush the journal again. */
+ ret = journal_flush(j);
+ is_int(KNOT_EOK, ret, "journal: flush again (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+
+ /* Fill the journal using a list. */
+ uint32_t m_serial = 1;
+ for (; m_serial < serial / 2; ++m_serial) {
+ changeset_t *m_ch7 = changeset_new(apex);
+ init_random_changeset(m_ch7, m_serial, m_serial + 1, 128, apex, false);
+ add_tail(&l, &m_ch7->n);
+ }
+ ret = journal_store_changesets(j, &l);
+ is_int(KNOT_EOK, ret, "journal: fill with changesets using a list (%d inserted)", m_serial);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+
+ /* Cleanup. */
+ changesets_free(&l);
+ init_list(&l);
+
+ /* Load all previous changesets. */
+ ret = journal_load_changesets(j, &l, 1);
+ ok(ret == KNOT_EOK && knot_soa_serial(((changeset_t *)TAIL(l))->soa_to->rrs.rdata) == m_serial,
+ "journal: load all changesets");
+
+ /* Check for changeset ordering. */
+ ok(test_continuity(&l) == KNOT_EOK, "journal: changesets are in order");
+
+ /* Cleanup. */
+ changesets_free(&l);
+ init_list(&l);
+ ret = journal_flush(j);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_flush 0");
+ ret = journal_drop_changesets(j); /* Clear the journal for the collision test */
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_drop_changesets");
+
+ /* Test for serial number collision handling. We insert changesets
+ * with valid serial sequence that overflows and then collides with itself.
+ * The sequence is 0 -> 1 -> 2 -> 2147483647 -> 4294967294 -> 1 which should
+ * remove changesets 0->1 and 1->2. */
+ ok(EMPTY_LIST(k), "journal: empty list k");
+ ok(EMPTY_LIST(l), "journal: empty list l");
+ changeset_t *m_ch3 = changeset_new(apex);
+ init_random_changeset(m_ch3, 0, 1, 128, apex, false);
+ ret = journal_store_changeset(j, m_ch3);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 1");
+ changeset_set_soa_serials(m_ch3, 1, 2, apex);
+ ret = journal_store_changeset(j, m_ch3);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 2");
+ changeset_set_soa_serials(m_ch3, 2, 2147483647, apex);
+ add_tail(&k, &m_ch3->n);
+ ret = journal_store_changeset(j, m_ch3);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 3");
+ changeset_t *m_ch4 = changeset_new(apex);
+ init_random_changeset(m_ch4, 2147483647, 4294967294, 128, apex, false);
+ add_tail(&k, &m_ch4->n);
+ ret = journal_store_changeset(j, m_ch4);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 4");
+ changeset_t *m_ch5 = changeset_new(apex);
+ init_random_changeset(m_ch5, 4294967294, 1, 128, apex, false);
+ add_tail(&k, &m_ch5->n);
+ ret = journal_store_changeset(j, m_ch5);
+ is_int(KNOT_EBUSY, ret, "journal: allways ok journal_store_changeset 5");
+ ret = journal_flush(j);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_flush 1");
+ ret = journal_store_changeset(j, m_ch5);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_store_changeset 6");
+ ret = journal_flush(j);
+ is_int(KNOT_EOK, ret, "journal: allways ok journal_flush 2");
+ ret = journal_load_changesets(j, &l, 0);
+ ret2 = journal_load_changesets(j, &l, 1);
+ int ret3 = journal_load_changesets(j, &l, 2);
+ fprintf(stderr, "ret=%d ret2=%d ret3=%d\n", ret, ret2, ret3);
+ ok(ret == KNOT_ENOENT && ret2 == KNOT_ENOENT && ret3 == KNOT_EOK &&
+ changesets_list_eq(&l, &k), "journal: serial collision");
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ is_int(KNOT_EOK, ret, "journal check (%d)", ret);
+
+ /* Cleanup. */
+ changesets_free(&l);
+ changesets_free(&k);
+
+ init_list(&l);
+ init_list(&k);
+
+ /* Check bootstrap changeset */
+ ret = journal_drop_changesets(j);
+ ok(ret == KNOT_EOK, "journal: journal_drop_changesets must be ok");
+
+ changeset_t *m_ch6 = changeset_new(apex);
+ init_random_changeset(m_ch6, 0, 1, 128, apex, true);
+ ret = journal_store_changeset(j, m_ch6);
+ ok(ret == KNOT_EOK, "journal: store bootstrap (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ ok(ret == KNOT_EOK, "journal check (%d)", ret);
+ changeset_t *m_ch7 = changeset_new(apex);
+ init_random_changeset(m_ch7, 1, 2, 128, apex, false);
+ ret = journal_store_changeset(j, m_ch7);
+ ok(ret == KNOT_EOK, "journal: store after bootstrap (%d)", ret);
+ add_tail(&k, &m_ch6->n);
+ add_tail(&k, &m_ch7->n);
+ ret = journal_load_bootstrap(j, &l);
+ ok(ret == KNOT_EOK && changesets_list_eq(&l, &k), "journal: load boostrap (%d)", ret);
+ ret = journal_check(j, JOURNAL_CHECK_STDERR);
+ ok(ret == KNOT_EOK, "journal check (%d)", ret);
+
+ changesets_free(&l);
+ changesets_free(&k);
+
+ init_list(&l);
+ init_list(&k);
+
+ ret = journal_scrape(j);
+ ok(ret == KNOT_EOK, "journal: scrape must be ok");
+
+ unset_conf();
+}
+
+const uint8_t *rdA = (const uint8_t *) "\x01\x02\x03\x04";
+const uint8_t *rdB = (const uint8_t *) "\x01\x02\x03\x05";
+const uint8_t *rdC = (const uint8_t *) "\x01\x02\x03\x06";
+
+// frees owner
+static knot_rrset_t * tm_rrset(knot_dname_t * owner, const uint8_t * rdata)
+{
+ knot_rrset_t * rrs = knot_rrset_new(owner, KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600, NULL);
+ knot_rrset_add_rdata(rrs, rdata, 4, NULL);
+ free(owner);
+ return rrs;
+}
+
+static knot_dname_t * tm_owner(const char * prefix, const knot_dname_t *apex)
+{
+ knot_dname_t * ret = malloc(strlen(prefix) + knot_dname_size(apex) + 2);
+ ret[0] = strlen(prefix);
+ strcpy((char *) (ret + 1), prefix);
+ memcpy(ret + ret[0] + 1, apex, knot_dname_size(apex));
+ return ret;
+}
+
+static knot_rrset_t * tm_rrs(const knot_dname_t * apex, int x)
+{
+ static knot_rrset_t * rrsA = NULL;
+ static knot_rrset_t * rrsB = NULL;
+ static knot_rrset_t * rrsC = NULL;
+
+ if (apex == NULL) {
+ knot_rrset_free(rrsA, NULL);
+ knot_rrset_free(rrsB, NULL);
+ knot_rrset_free(rrsC, NULL);
+ rrsA = rrsB = rrsC = NULL;
+ return NULL;
+ }
+
+ if (rrsA == NULL) rrsA = tm_rrset(tm_owner("aaaaaaaaaaaaaaaaa", apex), rdA);
+ if (rrsB == NULL) rrsB = tm_rrset(tm_owner("bbbbbbbbbbbbbbbbb", apex), rdB);
+ if (rrsC == NULL) rrsC = tm_rrset(tm_owner("ccccccccccccccccc", apex), rdC);
+ switch ((x % 3 + 3) % 3) {
+ case 0: return rrsA;
+ case 1: return rrsB;
+ case 2: return rrsC;
+ }
+ assert(0); return NULL;
+}
+
+int tm_rrcnt(const changeset_t * ch, int flg)
+{
+ changeset_iter_t it;
+ int i = 0;
+ if (flg >= 0) changeset_iter_add(&it, ch);
+ else changeset_iter_rem(&it, ch);
+
+ knot_rrset_t rri;
+ while (rri = changeset_iter_next(&it), !knot_rrset_empty(&rri)) i++;
+
+ changeset_iter_clear(&it);
+ return i;
+}
+
+static changeset_t * tm_chs(const knot_dname_t * apex, int x)
+{
+ static changeset_t * chsI = NULL, * chsX = NULL, * chsY = NULL;
+ static uint32_t serial = 0;
+
+ if (apex == NULL) {
+ changeset_free(chsI);
+ changeset_free(chsX);
+ changeset_free(chsY);
+ chsI = chsX = chsY = NULL;
+ return NULL;
+ }
+
+ if (chsI == NULL) {
+ chsI = changeset_new(apex);
+ assert(chsI != NULL);
+ changeset_add_addition(chsI, tm_rrs(apex, 0), 0);
+ changeset_add_addition(chsI, tm_rrs(apex, 1), 0);
+ }
+ if (chsX == NULL) {
+ chsX = changeset_new(apex);
+ assert(chsX != NULL);
+ changeset_add_removal(chsX, tm_rrs(apex, 1), 0);
+ changeset_add_addition(chsX, tm_rrs(apex, 2), 0);
+ }
+ if (chsY == NULL) {
+ chsY = changeset_new(apex);
+ assert(chsY != NULL);
+ changeset_add_removal(chsY, tm_rrs(apex, 2), 0);
+ changeset_add_addition(chsY, tm_rrs(apex, 1), 0);
+ }
+ assert(x >= 0);
+ changeset_t * ret;
+ if (x == 0) ret = chsI;
+ else if (x % 2 == 1) ret = chsX;
+ else ret = chsY;
+
+ changeset_set_soa_serials(ret, serial, serial + 1, apex);
+ serial++;
+
+ return ret;
+}
+
+static int merged_present(void)
+{
+ local_txn_t(txn, j);
+ txn_begin(txn, 0);
+ int res = md_flag(txn, MERGED_SERIAL_VALID);
+ txn_abort(txn);
+ return res;
+}
+
+static void test_merge(void)
+{
+ int i, ret;
+ list_t l;
+
+ // allow merge
+ set_conf(-1, 512 * 1024);
+ ok(journal_merge_allowed(j), "journal: merge allowed");
+
+ ret = journal_drop_changesets(j);
+ is_int(KNOT_EOK, ret, "journal: journal_drop_changesets must be ok");
+
+ // insert stuff and check the merge
+ for (i = 0; !merged_present() && i < 40000; i++) {
+ ret = journal_store_changeset(j, tm_chs(apex, i));
+ is_int(KNOT_EOK, ret, "journal: journal_store_changeset must be ok");
+ }
+ init_list(&l);
+ ret = journal_load_changesets(j, &l, 0);
+ is_int(KNOT_EOK, ret, "journal: journal_load_changesets must be ok");
+ ok(list_size(&l) == 2, "journal: read the merged and one following");
+ changeset_t * mch = (changeset_t *)HEAD(l);
+ ok(list_size(&l) >= 1 && tm_rrcnt(mch, 1) == 2, "journal: merged additions # = 2");
+ ok(list_size(&l) >= 1 && tm_rrcnt(mch, -1) == 1, "journal: merged removals # = 1");
+ changesets_free(&l);
+
+ // insert one more and check the #s of results
+ journal_store_changeset(j, tm_chs(apex, i));
+ init_list(&l);
+ ret = journal_load_changesets(j, &l, 0);
+ is_int(KNOT_EOK, ret, "journal: journal_load_changesets2 must be ok");
+ ok(list_size(&l) == 3, "journal: read merged together with new changeset");
+ changesets_free(&l);
+ init_list(&l);
+ ret = journal_load_changesets(j, &l, (uint32_t) (i - 3));
+ is_int(KNOT_EOK, ret, "journal: journal_load_changesets3 must be ok");
+ ok(list_size(&l) == 4, "journal: read short history of merged/unmerged changesets");
+ changesets_free(&l);
+
+ ret = journal_drop_changesets(j);
+ assert(ret == KNOT_EOK);
+
+ // disallow merge
+ unset_conf();
+ set_conf(1000, 512 * 1024);
+ ok(!journal_merge_allowed(j), "journal: merge disallowed");
+
+ tm_rrs(NULL, 0);
+ tm_chs(NULL, 0);
+ unset_conf();
+}
+
+static void test_stress_base(journal_t *j, size_t update_size, size_t file_size)
+{
+ int ret;
+ uint32_t serial = 0;
+
+ journal_close(j);
+ journal_db_close(&db);
+ db = NULL;
+ ret = journal_db_init(&db, test_dir_name, file_size, JOURNAL_MODE_ASYNC);
+ assert(ret == KNOT_EOK);
+ ret = journal_open_db(&db);
+ assert(ret == KNOT_EOK);
+ ret = journal_open(j, &db, apex);
+ assert(ret == KNOT_EOK);
+
+ set_conf(1000, file_size / 2);
+
+ changeset_t ch;
+ ret = changeset_init(&ch, apex);
+ ok(ret == KNOT_EOK, "journal: changeset init (%d)", ret);
+ init_random_changeset(&ch, serial, serial + 1, update_size, apex, false);
+
+ for (int i = 1; i <= 6; ++i) {
+ serial = 0;
+ while (true) {
+ changeset_set_soa_serials(&ch, serial, serial + 1, apex);
+ ret = journal_store_changeset(j, &ch);
+ if (ret == KNOT_EOK) {
+ serial++;
+ } else {
+ break;
+ }
+ }
+
+ int ret = journal_flush(j);
+ ok(serial > 0 && ret == KNOT_EOK, "journal: pass #%d fillup run (%d inserts)", i, serial);
+ }
+
+ changeset_clear(&ch);
+
+ unset_conf();
+}
+
+/*! \brief Test behavior when writing to jurnal and flushing it. */
+static void test_stress(journal_t *j)
+{
+ diag("stress test: small data");
+ test_stress_base(j, 40, (1024 + 512) * 1024);
+
+ diag("stress test: medium data");
+ test_stress_base(j, 400, 3 * 1024 * 1024);
+
+ diag("stress test: large data");
+ test_stress_base(j, 4000, 10 * 1024 * 1024);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ test_dir_name = test_mkdtemp();
+
+ test_journal_db();
+
+ test_store_load();
+
+ test_merge();
+
+ test_stress(j);
+
+ journal_close(j);
+ journal_free(&j);
+ journal_db_close(&db);
+
+ test_rm_rf(test_dir_name);
+ free(test_dir_name);
+
+ return 0;
+}