"Fossies" - the Fresh Open Source Software Archive

Member "knot-2.9.2/tests/knot/test_journal.c" (12 Dec 2019, 21404 Bytes) of package /linux/misc/dns/knot-2.9.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "test_journal.c": 2.9.1_vs_2.9.2.

    1 /*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
    2 
    3     This program is free software: you can redistribute it and/or modify
    4     it under the terms of the GNU General Public License as published by
    5     the Free Software Foundation, either version 3 of the License, or
    6     (at your option) any later version.
    7 
    8     This program is distributed in the hope that it will be useful,
    9     but WITHOUT ANY WARRANTY; without even the implied warranty of
   10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   11     GNU General Public License for more details.
   12 
   13     You should have received a copy of the GNU General Public License
   14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
   15  */
   16 
   17 #include <assert.h>
   18 #include <string.h>
   19 #include <stdlib.h>
   20 #include <stdio.h>
   21 #include <limits.h>
   22 #include <unistd.h>
   23 #include <sys/stat.h>
   24 #include <tap/basic.h>
   25 #include <tap/files.h>
   26 
   27 #include "knot/journal/journal_read.h"
   28 #include "knot/journal/journal_write.h"
   29 
   30 #include "libknot/libknot.h"
   31 #include "knot/zone/zone.h"
   32 #include "knot/zone/zone-diff.h"
   33 #include "libknot/rrtype/soa.h"
   34 #include "test_conf.h"
   35 
   36 #define RAND_RR_LABEL 16
   37 #define RAND_RR_PAYLOAD 64
   38 #define MIN_SOA_SIZE 22
   39 
   40 char *test_dir_name;
   41 
   42 knot_lmdb_db_t jdb;
   43 zone_journal_t jj;
   44 
   45 unsigned env_flag;
   46 
   47 static void set_conf(int zonefile_sync, size_t journal_usage, const knot_dname_t *apex)
   48 {
   49     char conf_str[512];
   50     snprintf(conf_str, sizeof(conf_str),
   51              "zone:\n"
   52              " - domain: %s\n"
   53              "   zonefile-sync: %d\n"
   54              "   max-journal-usage: %zu\n"
   55              "   max-journal-depth: 1000\n",
   56              (const char *)(apex + 1), zonefile_sync, journal_usage);
   57     int ret = test_conf(conf_str, NULL);
   58     (void)ret;
   59     assert(ret == KNOT_EOK);
   60 }
   61 
   62 static void unset_conf(void)
   63 {
   64     conf_update(NULL, CONF_UPD_FNONE);
   65 }
   66 
   67 /*! \brief Generate random string with given length. */
   68 static int randstr(char* dst, size_t len)
   69 {
   70     for (int i = 0; i < len - 1; ++i) {
   71         dst[i] = '0' + (int) (('Z'-'0') * (rand() / (RAND_MAX + 1.0)));
   72     }
   73     dst[len - 1] = '\0';
   74 
   75     return 0;
   76 }
   77 
   78 /*! \brief Init RRSet with type SOA and given serial. */
   79 static void init_soa(knot_rrset_t *rr, const uint32_t serial, const knot_dname_t *apex)
   80 {
   81     knot_rrset_init(rr, knot_dname_copy(apex, NULL), KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 3600);
   82 
   83     uint8_t soa_data[MIN_SOA_SIZE] = { 0 };
   84     int ret = knot_rrset_add_rdata(rr, soa_data, sizeof(soa_data), NULL);
   85     knot_soa_serial_set(rr->rrs.rdata, serial);
   86     (void)ret;
   87     assert(ret == KNOT_EOK);
   88 }
   89 
   90 /*! \brief Init RRSet with type TXT, random owner and random payload. */
   91 static void init_random_rr(knot_rrset_t *rr , const knot_dname_t *apex)
   92 {
   93     /* Create random label. */
   94     char owner[RAND_RR_LABEL + knot_dname_size(apex)];
   95     owner[0] = RAND_RR_LABEL - 1;
   96     randstr(owner + 1, RAND_RR_LABEL);
   97 
   98     /* Append zone apex. */
   99     memcpy(owner + RAND_RR_LABEL, apex, knot_dname_size(apex));
  100     knot_rrset_init(rr, knot_dname_copy((knot_dname_t *)owner, NULL),
  101             KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 3600);
  102 
  103     /* Create random RDATA. */
  104     uint8_t txt[RAND_RR_PAYLOAD + 1];
  105     txt[0] = RAND_RR_PAYLOAD - 1;
  106     randstr((char *)(txt + 1), RAND_RR_PAYLOAD);
  107 
  108     int ret = knot_rrset_add_rdata(rr, txt, RAND_RR_PAYLOAD, NULL);
  109     (void)ret;
  110     assert(ret == KNOT_EOK);
  111 }
  112 
  113 /*! \brief Init changeset with random changes. */
  114 static void init_random_changeset(changeset_t *ch, const uint32_t from, const uint32_t to,
  115                                   const size_t size, const knot_dname_t *apex, bool is_bootstrap)
  116 {
  117     // Add SOAs
  118     knot_rrset_t soa;
  119 
  120     if (is_bootstrap) {
  121         ch->soa_from = NULL;
  122         zone_contents_deep_free(ch->remove);
  123         ch->remove = NULL;
  124     } else {
  125         init_soa(&soa, from, apex);
  126         ch->soa_from = knot_rrset_copy(&soa, NULL);
  127         assert(ch->soa_from);
  128         knot_rrset_clear(&soa, NULL);
  129     }
  130 
  131     init_soa(&soa, to, apex);
  132     ch->soa_to = knot_rrset_copy(&soa, NULL);
  133     assert(ch->soa_to);
  134     knot_rrset_clear(&soa, NULL);
  135 
  136     // Add RRs to add section
  137     for (size_t i = 0; i < size / 2; ++i) {
  138         knot_rrset_t rr;
  139         init_random_rr(&rr, apex);
  140         int ret = changeset_add_addition(ch, &rr, 0);
  141         (void)ret;
  142         assert(ret == KNOT_EOK);
  143         knot_rrset_clear(&rr, NULL);
  144     }
  145 
  146     // Add RRs to remove section
  147     for (size_t i = 0; i < size / 2 && !is_bootstrap; ++i) {
  148         knot_rrset_t rr;
  149         init_random_rr(&rr, apex);
  150         int ret = changeset_add_removal(ch, &rr, 0);
  151         (void)ret;
  152         assert(ret == KNOT_EOK);
  153         knot_rrset_clear(&rr, NULL);
  154     }
  155 }
  156 
  157 static void changeset_set_soa_serials(changeset_t *ch, uint32_t from, uint32_t to,
  158                       const knot_dname_t *apex)
  159 {
  160     knot_rrset_t soa;
  161 
  162     init_soa(&soa, from, apex);
  163     knot_rrset_free(ch->soa_from, NULL);
  164     ch->soa_from = knot_rrset_copy(&soa, NULL);
  165     assert(ch->soa_from);
  166     knot_rrset_clear(&soa, NULL);
  167 
  168     init_soa(&soa, to, apex);
  169     knot_rrset_free(ch->soa_to, NULL);
  170     ch->soa_to = knot_rrset_copy(&soa, NULL);
  171     assert(ch->soa_to);
  172     knot_rrset_clear(&soa, NULL);
  173 }
  174 
  175 /*! \brief Compare two changesets for equality. */
  176 static bool changesets_eq(const changeset_t *ch1, changeset_t *ch2)
  177 {
  178     bool bootstrap = (ch1->remove == NULL);
  179 
  180     if (!bootstrap && changeset_size(ch1) != changeset_size(ch2)) {
  181         return false;
  182     }
  183 
  184     if ((bootstrap && ch2->remove != NULL) ||
  185         (!bootstrap && ch2->remove == NULL)) {
  186         return false;
  187     }
  188 
  189     changeset_iter_t it1;
  190     changeset_iter_t it2;
  191     if (bootstrap) {
  192         changeset_iter_add(&it1, ch1);
  193         changeset_iter_add(&it2, ch2);
  194     } else {
  195         changeset_iter_all(&it1, ch1);
  196         changeset_iter_all(&it2, ch2);
  197     }
  198 
  199     knot_rrset_t rr1 = changeset_iter_next(&it1);
  200     knot_rrset_t rr2 = changeset_iter_next(&it2);
  201     bool ret = true;
  202     while (!knot_rrset_empty(&rr1)) {
  203         if (!knot_rrset_equal(&rr1, &rr2, true)) {
  204             ret = false;
  205             break;
  206         }
  207         rr1 = changeset_iter_next(&it1);
  208         rr2 = changeset_iter_next(&it2);
  209     }
  210 
  211     changeset_iter_clear(&it1);
  212     changeset_iter_clear(&it2);
  213 
  214     return ret;
  215 }
  216 
  217 static bool changesets_list_eq(list_t *l1, list_t *l2)
  218 {
  219     node_t *n = NULL;
  220     node_t *k = HEAD(*l2);
  221     WALK_LIST(n, *l1) {
  222         if (k == NULL) {
  223             return false;
  224         }
  225 
  226         changeset_t *ch1 = (changeset_t *) n;
  227         changeset_t *ch2 = (changeset_t *) k;
  228         if (!changesets_eq(ch1, ch2)) {
  229             return false;
  230         }
  231 
  232         k = k->next;
  233     }
  234 
  235     if (k->next != NULL) {
  236         return false;
  237     }
  238 
  239     return true;
  240 }
  241 
  242 /*! \brief Test a list of changesets for continuity. */
  243 static bool test_continuity(list_t *l)
  244 {
  245     node_t *n = NULL;
  246     uint32_t key1, key2;
  247     WALK_LIST(n, *l) {
  248         if (n == TAIL(*l)) {
  249             break;
  250         }
  251         changeset_t *ch1 = (changeset_t *) n;
  252         changeset_t *ch2 = (changeset_t *) n->next;
  253         key1 = knot_soa_serial(ch1->soa_to->rrs.rdata);
  254         key2 = knot_soa_serial(ch2->soa_from->rrs.rdata);
  255         if (key1 != key2) {
  256             return KNOT_EINVAL;
  257         }
  258     }
  259 
  260     return KNOT_EOK;
  261 }
  262 
  263 static void test_journal_db(void)
  264 {
  265     env_flag = journal_env_flags(JOURNAL_MODE_ASYNC);
  266     knot_lmdb_init(&jdb, test_dir_name, 2048 * 1024, env_flag, NULL);
  267 
  268     int ret = knot_lmdb_open(&jdb);
  269     is_int(KNOT_EOK, ret, "journal: open db (%s)", knot_strerror(ret));
  270 
  271     ret = knot_lmdb_reconfigure(&jdb, test_dir_name, 4096 * 1024, env_flag);
  272     is_int(KNOT_EOK, ret, "journal: re-open with bigger mapsize (%s)", knot_strerror(ret));
  273 
  274     ret = knot_lmdb_reconfigure(&jdb, test_dir_name, 1024 * 1024, env_flag);
  275     is_int(KNOT_EOK, ret, "journal: re-open with smaller mapsize (%s)", knot_strerror(ret));
  276 
  277     knot_lmdb_deinit(&jdb);
  278 }
  279 
  280 static int load_j_list(zone_journal_t *zj, bool zij, uint32_t serial,
  281                        journal_read_t **read, list_t *list)
  282 {
  283     changeset_t *ch;
  284     init_list(list);
  285     int ret = journal_read_begin(*zj, zij, serial, read);
  286     if (ret == KNOT_EOK) {
  287         while ((ch = calloc(1, sizeof(*ch))) != NULL &&
  288                journal_read_changeset(*read, ch)) {
  289             add_tail(list, &ch->n);
  290         }
  291         free(ch);
  292         ret = journal_read_get_error(*read, KNOT_EOK);
  293     }
  294     return ret;
  295 }
  296 
  297 /*! \brief Test behavior with real changesets. */
  298 static void test_store_load(const knot_dname_t *apex)
  299 {
  300     set_conf(1000, 512 * 1024, apex);
  301 
  302     knot_lmdb_init(&jdb, test_dir_name, 1536 * 1024, env_flag, NULL);
  303     assert(knot_lmdb_open(&jdb) == KNOT_EOK);
  304 
  305     jj.db = &jdb;
  306     jj.zone = apex;
  307     list_t l, k;
  308 
  309     changeset_t *m_ch = changeset_new(apex), r_ch, e_ch;
  310     init_random_changeset(m_ch, 0, 1, 128, apex, false);
  311     int ret = journal_insert(jj, m_ch, NULL);
  312     is_int(KNOT_EOK, ret, "journal: store changeset (%s)", knot_strerror(ret));
  313     ret = journal_sem_check(jj);
  314     is_int(KNOT_EOK, ret, "journal: check after store changeset (%s)", knot_strerror(ret));
  315     journal_read_t *read = NULL;
  316 
  317     ret = load_j_list(&jj, false, changeset_from(m_ch), &read, &l);
  318     is_int(KNOT_EOK, ret, "journal: read single changeset (%s)", knot_strerror(ret));
  319     ok(1 == list_size(&l), "journal: read exactly one changeset");
  320     ok(changesets_eq(m_ch, HEAD(l)), "journal: changeset equal after read");
  321     changesets_free(&l);
  322 
  323     journal_read_end(read);
  324     ret = journal_set_flushed(jj);
  325     is_int(KNOT_EOK, ret, "journal: first simple flush (%s)", knot_strerror(ret));
  326 
  327     init_list(&k);
  328     uint32_t serial = 1;
  329     for (; ret == KNOT_EOK && serial < 40000; ++serial) {
  330         changeset_t *m_ch2 = changeset_new(apex);
  331         init_random_changeset(m_ch2, serial, serial + 1, 128, apex, false);
  332         ret = journal_insert(jj, m_ch2, NULL);
  333         if (ret != KNOT_EOK) {
  334             changeset_free(m_ch2);
  335             break;
  336         }
  337         add_tail(&k, &m_ch2->n);
  338     }
  339     is_int(KNOT_EBUSY, ret, "journal: overfill with changesets (%d inserted) (%d should= %d)",
  340            serial, ret, KNOT_EBUSY);
  341     ret = journal_sem_check(jj);
  342     is_int(KNOT_EOK, ret, "journal check (%s)", knot_strerror(ret));
  343 
  344     ret = load_j_list(&jj, false, 1, &read, &l);
  345     is_int(KNOT_EOK, ret, "journal: load list (%s)", knot_strerror(ret));
  346     ok(changesets_list_eq(&l, &k), "journal: changeset lists equal after read");
  347     ok(test_continuity(&l) == KNOT_EOK, "journal: changesets are in order");
  348     changesets_free(&l);
  349     journal_read_end(read);
  350 
  351     ret = load_j_list(&jj, false, 1, &read, &l);
  352     is_int(KNOT_EOK, ret, "journal: load list 2nd (%s)", knot_strerror(ret));
  353     ok(changesets_list_eq(&l, &k), "journal: changeset lists equal after 2nd read");
  354     changesets_free(&l);
  355     journal_read_end(read);
  356 
  357     ret = journal_set_flushed(jj);
  358     is_int(KNOT_EOK, ret, "journal: flush after overfill (%s)", knot_strerror(ret));
  359     ret = journal_sem_check(jj);
  360     is_int(KNOT_EOK, ret, "journal check (%s)", knot_strerror(ret));
  361 
  362     ret = load_j_list(&jj, false, 1, &read, &l);
  363     is_int(KNOT_EOK, ret, "journal: load list (%s)", knot_strerror(ret));
  364     ok(changesets_list_eq(&l, &k), "journal: changeset lists equal after flush");
  365     changesets_free(&l);
  366     journal_read_end(read);
  367 
  368     changesets_free(&k);
  369 
  370     changeset_t ch;
  371     ret = changeset_init(&ch, apex);
  372     ok(ret == KNOT_EOK, "journal: changeset init (%d)", ret);
  373     init_random_changeset(&ch, serial, serial + 1, 555, apex, false);
  374     ret = journal_insert(jj, &ch, NULL);
  375     is_int(KNOT_EOK, ret, "journal: store after flush (%d)", ret);
  376     ret = journal_sem_check(jj);
  377     is_int(KNOT_EOK, ret, "journal check (%s)", knot_strerror(ret));
  378     ret = load_j_list(&jj, false, serial, &read, &l);
  379     is_int(KNOT_EOK, ret, "journal: load after store after flush after overfill (%s)", knot_strerror(ret));
  380     is_int(1, list_size(&l), "journal: single changeset in list");
  381     ok(changesets_eq(&ch, HEAD(l)), "journal: changeset unmalformed after overfill");
  382     changesets_free(&l);
  383     journal_read_end(read);
  384 
  385     changeset_clear(&ch);
  386 
  387     changeset_init(&ch, apex);
  388     init_random_changeset(&ch, 2, 3, 100, apex, false);
  389     ret = journal_insert(jj, &ch, NULL);
  390     is_int(KNOT_EOK, ret, "journal: insert discontinuous changeset (%s)", knot_strerror(ret));
  391     ret = journal_sem_check(jj);
  392     is_int(KNOT_EOK, ret, "journal check (%s)", knot_strerror(ret));
  393     ret = load_j_list(&jj, false, 2, &read, &l);
  394     is_int(KNOT_EOK, ret, "journal: read after discontinuity (%s)", knot_strerror(ret));
  395     is_int(1, list_size(&l), "journal: dicontinuity caused journal to drop");
  396     changesets_free(&l);
  397     journal_read_end(read);
  398 
  399     // Test for serial number collision handling. We insert changesets
  400     // with valid serial sequence that overflows and then collides with itself.
  401     // The sequence is 0 -> 1 -> 2 -> 2147483647 -> 4294967294 -> 1 which should
  402     // remove changesets 0->1 and 1->2. *
  403     uint32_t serials[6] = { 0, 1, 2, 2147483647, 4294967294, 1 };
  404     for (int i = 0; i < 5; i++) {
  405         changeset_clear(&ch);
  406         changeset_init(&ch, apex);
  407         init_random_changeset(&ch, serials[i], serials[i + 1], 100, apex, false);
  408         ret = journal_insert(jj, &ch, NULL);
  409         is_int(i == 4 ? KNOT_EBUSY : KNOT_EOK, ret, "journal: inserting cycle (%s)", knot_strerror(ret));
  410         ret = journal_sem_check(jj);
  411         is_int(KNOT_EOK, ret, "journal check (%s)", knot_strerror(ret));
  412     }
  413     ret = journal_set_flushed(jj);
  414     is_int(KNOT_EOK, ret, "journal: flush in cycle (%s)", knot_strerror(ret));
  415     ret = journal_insert(jj, &ch, NULL);
  416     is_int(KNOT_EOK, ret, "journal: inserted cycle (%s)", knot_strerror(ret));
  417     ret = journal_sem_check(jj);
  418     is_int(KNOT_EOK, ret, "journal check (%s)", knot_strerror(ret));
  419     ret = journal_read_begin(jj, false, 0, &read);
  420     is_int(KNOT_ENOENT, ret, "journal: cycle removed first changeset (%d should= %d)", ret, KNOT_ENOENT);
  421     ret = journal_read_begin(jj, false, 1, &read);
  422     is_int(KNOT_ENOENT, ret, "journal: cycle removed second changeset (%d should= %d)", ret, KNOT_ENOENT);
  423     ret = load_j_list(&jj, false, 4294967294, &read, &l);
  424     is_int(KNOT_EOK, ret, "journal: read after cycle (%s)", knot_strerror(ret));
  425     ok(3 >= list_size(&l), "journal: cycle caused journal to partly drop");
  426     ok(changesets_eq(&ch, HEAD(l)), "journal: changeset unmalformed after cycle");
  427     changesets_free(&l);
  428     journal_read_end(read);
  429     changeset_clear(&ch);
  430     changeset_free(m_ch);
  431 
  432     changeset_init(&e_ch, apex);
  433     init_random_changeset(&e_ch, 0, 1, 200, apex, true);
  434     zone_node_t *n = NULL;
  435     zone_contents_add_rr(e_ch.add, e_ch.soa_to, &n);
  436     ret = journal_insert_zone(jj, e_ch.add);
  437     zone_contents_remove_rr(e_ch.add, e_ch.soa_to, &n);
  438     is_int(KNOT_EOK, ret, "journal: insert zone-in-journal (%s)", knot_strerror(ret));
  439     changeset_init(&r_ch, apex);
  440     init_random_changeset(&r_ch, 1, 2, 200, apex, false);
  441     ret = journal_insert(jj, &r_ch, NULL);
  442     is_int(KNOT_EOK, ret, "journal: insert after zone-in-journal (%s)", knot_strerror(ret));
  443     ret = load_j_list(&jj, true, 0, &read, &l);
  444     is_int(KNOT_EOK, ret, "journal: load zone-in-journal (%s)", knot_strerror(ret));
  445     is_int(2, list_size(&l), "journal: read two changesets from zone-in-journal");
  446     ok(changesets_eq(&e_ch, HEAD(l)), "journal: zone-in-journal unmalformed");
  447     ok(changesets_eq(&r_ch, TAIL(l)), "journal: after zone-in-journal unmalformed");
  448     changesets_free(&l);
  449     journal_read_end(read);
  450     changeset_clear(&e_ch);
  451     changeset_clear(&r_ch);
  452 
  453     ret = journal_scrape_with_md(jj);
  454     is_int(KNOT_EOK, ret, "journal: scrape with md (%s)", knot_strerror(ret));
  455 
  456     unset_conf();
  457 }
  458 
  459 const uint8_t *rdA = (const uint8_t *) "\x01\x02\x03\x04";
  460 const uint8_t *rdB = (const uint8_t *) "\x01\x02\x03\x05";
  461 const uint8_t *rdC = (const uint8_t *) "\x01\x02\x03\x06";
  462 
  463 // frees owner
  464 static knot_rrset_t * tm_rrset(knot_dname_t * owner, const uint8_t * rdata)
  465 {
  466     knot_rrset_t * rrs = knot_rrset_new(owner, KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600, NULL);
  467     knot_rrset_add_rdata(rrs, rdata, 4, NULL);
  468     free(owner);
  469     return rrs;
  470 }
  471 
  472 static knot_dname_t *tm_owner(const char *prefix, const knot_dname_t *apex)
  473 {
  474     size_t prefix_len = strlen(prefix);
  475     size_t apex_len = knot_dname_size(apex);
  476 
  477     knot_dname_t *out = malloc(prefix_len + apex_len + 2);
  478     out[0] = prefix_len;
  479     memcpy(out + 1, prefix, prefix_len);
  480     memcpy(out + 1 + prefix_len, apex, apex_len);
  481     return out;
  482 }
  483 
  484 static knot_rrset_t * tm_rrs(const knot_dname_t * apex, int x)
  485 {
  486     static knot_rrset_t * rrsA = NULL;
  487     static knot_rrset_t * rrsB = NULL;
  488     static knot_rrset_t * rrsC = NULL;
  489 
  490     if (apex == NULL) {
  491         knot_rrset_free(rrsA, NULL);
  492         knot_rrset_free(rrsB, NULL);
  493         knot_rrset_free(rrsC, NULL);
  494         rrsA = rrsB = rrsC = NULL;
  495         return NULL;
  496     }
  497 
  498     if (rrsA == NULL) rrsA = tm_rrset(tm_owner("aaaaaaaaaaaaaaaaa", apex), rdA);
  499     if (rrsB == NULL) rrsB = tm_rrset(tm_owner("bbbbbbbbbbbbbbbbb", apex), rdB);
  500     if (rrsC == NULL) rrsC = tm_rrset(tm_owner("ccccccccccccccccc", apex), rdC);
  501     switch ((x % 3 + 3) % 3) {
  502     case 0: return rrsA;
  503     case 1: return rrsB;
  504     case 2: return rrsC;
  505     }
  506     assert(0); return NULL;
  507 }
  508 
  509 int tm_rrcnt(const changeset_t * ch, int flg)
  510 {
  511     changeset_iter_t it;
  512     int i = 0;
  513     if (flg >= 0) changeset_iter_add(&it, ch);
  514     else changeset_iter_rem(&it, ch);
  515 
  516     knot_rrset_t rri;
  517     while (rri = changeset_iter_next(&it), !knot_rrset_empty(&rri)) i++;
  518 
  519     changeset_iter_clear(&it);
  520     return i;
  521 }
  522 
  523 static changeset_t * tm_chs(const knot_dname_t * apex, int x)
  524 {
  525     static changeset_t * chsI = NULL, * chsX = NULL, * chsY = NULL;
  526     static uint32_t serial = 0;
  527 
  528     if (apex == NULL) {
  529         changeset_free(chsI);
  530         changeset_free(chsX);
  531         changeset_free(chsY);
  532         chsI = chsX = chsY = NULL;
  533         return NULL;
  534     }
  535 
  536     if (chsI == NULL) {
  537         chsI = changeset_new(apex);
  538         assert(chsI != NULL);
  539         changeset_add_addition(chsI, tm_rrs(apex, 0), 0);
  540         changeset_add_addition(chsI, tm_rrs(apex, 1), 0);
  541     }
  542     if (chsX == NULL) {
  543         chsX = changeset_new(apex);
  544         assert(chsX != NULL);
  545         changeset_add_removal(chsX, tm_rrs(apex, 1), 0);
  546         changeset_add_addition(chsX, tm_rrs(apex, 2), 0);
  547     }
  548     if (chsY == NULL) {
  549         chsY = changeset_new(apex);
  550         assert(chsY != NULL);
  551         changeset_add_removal(chsY, tm_rrs(apex, 2), 0);
  552         changeset_add_addition(chsY, tm_rrs(apex, 1), 0);
  553     }
  554     assert(x >= 0);
  555     changeset_t * ret;
  556     if (x == 0) ret = chsI;
  557     else if (x % 2 == 1) ret = chsX;
  558     else ret = chsY;
  559 
  560     changeset_set_soa_serials(ret, serial, serial + 1, apex);
  561     serial++;
  562 
  563     return ret;
  564 }
  565 
  566 static int merged_present(void)
  567 {
  568     bool exists, has_merged;
  569     return journal_info(jj, &exists, NULL, NULL, NULL, &has_merged, NULL, NULL, NULL) == KNOT_EOK && exists && has_merged;
  570 }
  571 
  572 static void test_merge(const knot_dname_t *apex)
  573 {
  574     int i, ret;
  575     list_t l;
  576 
  577     // allow merge
  578     set_conf(-1, 100 * 1024, apex);
  579     ok(!journal_allow_flush(jj), "journal: merge allowed");
  580 
  581     ret = journal_scrape_with_md(jj);
  582     is_int(KNOT_EOK, ret, "journal: journal_drop_changesets must be ok");
  583 
  584     // insert stuff and check the merge
  585     for (i = 0; !merged_present() && i < 40000; i++) {
  586         ret = journal_insert(jj, tm_chs(apex, i), NULL);
  587         is_int(KNOT_EOK, ret, "journal: journal_store_changeset must be ok");
  588     }
  589     ret = journal_sem_check(jj);
  590     is_int(KNOT_EOK, ret, "journal: sem check (%s)", knot_strerror(ret));
  591     journal_read_t *read = NULL;
  592     ret = load_j_list(&jj, false, 0, &read, &l);
  593     is_int(KNOT_EOK, ret, "journal: journal_load_changesets must be ok");
  594     assert(ret == KNOT_EOK);
  595     ok(list_size(&l) == 2, "journal: read the merged and one following");
  596     changeset_t * mch = (changeset_t *)HEAD(l);
  597     ok(list_size(&l) >= 1 && tm_rrcnt(mch, 1) == 2, "journal: merged additions # = 2");
  598     ok(list_size(&l) >= 1 && tm_rrcnt(mch, -1) == 0, "journal: merged removals # = 0");
  599     changesets_free(&l);
  600     journal_read_end(read);
  601 
  602     // insert one more and check the #s of results
  603     ret = journal_insert(jj, tm_chs(apex, i), NULL);
  604     is_int(KNOT_EOK, ret, "journal: insert one more (%s)", knot_strerror(ret));
  605     ret = load_j_list(&jj, false, 0, &read, &l);
  606     is_int(KNOT_EOK, ret, "journal: journal_load_changesets2 must be ok");
  607     ok(list_size(&l) == 3, "journal: read merged together with new changeset");
  608     changesets_free(&l);
  609     journal_read_end(read);
  610     ret = load_j_list(&jj, false, i - 3, &read, &l);
  611     is_int(KNOT_EOK, ret, "journal: journal_load_changesets3 must be ok");
  612     ok(list_size(&l) == 4, "journal: read short history of merged/unmerged changesets");
  613     changesets_free(&l);
  614     journal_read_end(read);
  615 
  616 
  617     ret = journal_scrape_with_md(jj);
  618     assert(ret == KNOT_EOK);
  619 
  620     // disallow merge
  621     unset_conf();
  622     set_conf(1000, 512 * 1024, apex);
  623     ok(journal_allow_flush(jj), "journal: merge disallowed");
  624 
  625     tm_rrs(NULL, 0);
  626     tm_chs(NULL, 0);
  627     unset_conf();
  628 }
  629 
  630 static void test_stress_base(const knot_dname_t *apex,
  631                              size_t update_size, size_t file_size)
  632 {
  633     int ret;
  634     uint32_t serial = 0;
  635 
  636 
  637     ret = knot_lmdb_reconfigure(&jdb, test_dir_name, file_size, journal_env_flags(JOURNAL_MODE_ASYNC));
  638     is_int(KNOT_EOK, ret, "journal: recofigure to mapsize %zu (%s)", file_size, knot_strerror(ret));
  639 
  640     set_conf(1000, file_size / 2, apex);
  641 
  642     changeset_t ch;
  643     ret = changeset_init(&ch, apex);
  644     ok(ret == KNOT_EOK, "journal: changeset init (%d)", ret);
  645     init_random_changeset(&ch, serial, serial + 1, update_size, apex, false);
  646 
  647     for (int i = 1; i <= 6; ++i) {
  648         serial = 0;
  649         while (true) {
  650             changeset_set_soa_serials(&ch, serial, serial + 1, apex);
  651             ret = journal_insert(jj, &ch, NULL);
  652             if (ret == KNOT_EOK) {
  653                 serial++;
  654             } else {
  655                 break;
  656             }
  657         }
  658 
  659         ret = journal_set_flushed(jj);
  660         if (ret == KNOT_EOK) {
  661             ret = journal_sem_check(jj);
  662         }
  663         ok(serial > 0 && ret == KNOT_EOK, "journal: pass #%d fillup run (%d inserts) (%s)", i, serial, knot_strerror(ret));
  664     }
  665 
  666     changeset_clear(&ch);
  667 
  668     unset_conf();
  669 }
  670 
  671 /*! \brief Test behavior when writing to jurnal and flushing it. */
  672 static void test_stress(const knot_dname_t *apex)
  673 {
  674     diag("stress test: small data");
  675     test_stress_base(apex, 40, (1024 + 512) * 1024);
  676 
  677     diag("stress test: medium data");
  678     test_stress_base(apex, 400, 3 * 1024 * 1024);
  679 
  680     diag("stress test: large data");
  681     test_stress_base(apex, 4000, 10 * 1024 * 1024);
  682 }
  683 
  684 int main(int argc, char *argv[])
  685 {
  686     plan_lazy();
  687 
  688     const knot_dname_t *apex = (const uint8_t *)"\4test";
  689 
  690     test_dir_name = test_mkdtemp();
  691 
  692     test_journal_db();
  693 
  694     test_store_load(apex);
  695 
  696     test_merge(apex);
  697 
  698     test_stress(apex);
  699 
  700     knot_lmdb_deinit(&jdb);
  701 
  702     test_rm_rf(test_dir_name);
  703     free(test_dir_name);
  704 
  705     return 0;
  706 }