introduce isc_cfgmgr_foreachnodes

isc_cfgmgr_foreachnodes/foreachproperties enable to list each direct
sub-nodes/properties form within a node.

This is needed in general, and will make it easier to have a dump API
which also will be needed to make materialization testing easier.

The implementation is not great because it's likely slow (lot of LMDB
lookups and string manipulation). But at least it enables to expose the
API so we can built up on top of it, and come back to a better
implementation later.
This commit is contained in:
Colin Vidal
2025-02-24 10:35:39 +01:00
parent 80f5981de0
commit 148a6abbc2
3 changed files with 461 additions and 78 deletions
+167 -77
View File
@@ -51,6 +51,9 @@ typedef struct {
MDB_cursor *builtincursor;
MDB_cursor *usercursor;
MDB_cursor *runningcursor;
MDB_dbi builtindbi;
MDB_dbi userdbi;
MDB_dbi runningdbi;
MDB_txn *txn;
bool readonly;
isc_cfgmgr_mode_t mode;
@@ -75,17 +78,16 @@ static thread_local char isc__cfgmgr_lasterror[LASTERRORLEN];
static bool
isc__cfgmgr_openlmdbcursor(const char *dbname, MDB_cursor **cursor,
bool readonly) {
MDB_dbi dbi;
MDB_dbi *dbi, bool readonly) {
int result;
result = mdb_dbi_open(isc__cfgmgr_ctx.txn, dbname,
readonly ? 0 : MDB_CREATE, &dbi);
readonly ? 0 : MDB_CREATE, dbi);
if (result != 0) {
return false;
}
result = mdb_cursor_open(isc__cfgmgr_ctx.txn, dbi, cursor);
result = mdb_cursor_open(isc__cfgmgr_ctx.txn, *dbi, cursor);
if (result != 0) {
return false;
}
@@ -117,11 +119,13 @@ isc__cfgmgr_startlmdbtransaction(bool readonly) {
INSIST(isc__cfgmgr_ctx.txn != NULL);
if (!isc__cfgmgr_openlmdbcursor(
"builtin", &isc__cfgmgr_ctx.builtincursor, readonly) ||
"builtin", &isc__cfgmgr_ctx.builtincursor,
&isc__cfgmgr_ctx.builtindbi, readonly) ||
!isc__cfgmgr_openlmdbcursor("user", &isc__cfgmgr_ctx.usercursor,
readonly) ||
!isc__cfgmgr_openlmdbcursor(
"running", &isc__cfgmgr_ctx.runningcursor, readonly))
&isc__cfgmgr_ctx.userdbi, readonly) ||
!isc__cfgmgr_openlmdbcursor("running",
&isc__cfgmgr_ctx.runningcursor,
&isc__cfgmgr_ctx.runningdbi, readonly))
{
isc__cfgmgr_closelmdbcursors();
mdb_txn_abort(isc__cfgmgr_ctx.txn);
@@ -268,6 +272,22 @@ isc__cfgmgr_lmdbcursor(void) {
UNREACHABLE();
}
static MDB_dbi
isc__cfgmgr_lmdbdbi(void) {
switch (isc__cfgmgr_ctx.mode) {
case ISC_CFGMGR_MODEUNKNOWN:
UNREACHABLE();
case ISC_CFGMGR_MODEBUILTIN:
return isc__cfgmgr_ctx.builtindbi;
case ISC_CFGMGR_MODEUSER:
return isc__cfgmgr_ctx.userdbi;
case ISC_CFGMGR_MODERUNNING:
return isc__cfgmgr_ctx.runningdbi;
}
UNREACHABLE();
}
static void
isc__cfgmgr_setlasterror(const char *fmt, ...) {
va_list ap;
@@ -391,18 +411,34 @@ isc_cfgmgr_rollback(void) {
isc__cfgmgr_checkofftransaction();
}
static void
isc__cfgmgr_buildkey(const char *name, bool addleading, char *b, size_t len) {
const char *prefix = "/";
const char *leading = addleading ? "/" : "";
REQUIRE(name != NULL);
if (ISC_LIST_EMPTY(isc__cfgmgr_ctx.openednodes) == false) {
prefix = ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes)->prefix;
static isc__cfgmgr_node_t *
isc__cfgmgr_curnode(void) {
if (ISC_LIST_EMPTY(isc__cfgmgr_ctx.openednodes)) {
return NULL;
}
REQUIRE(snprintf(b, len, "%s%s%s", prefix, name, leading) <= (int)len);
return ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes);
}
static const char *
isc__cfgmgr_curprefix(void) {
const isc__cfgmgr_node_t *node = isc__cfgmgr_curnode();
if (node) {
return node->prefix;
}
return "/";
}
static void
isc__cfgmgr_buildkey(const char *name, const char *term, char *b, size_t len) {
REQUIRE(name != NULL);
REQUIRE(term != NULL);
REQUIRE(b != NULL);
REQUIRE(snprintf(b, len, "%s%s%s", isc__cfgmgr_curprefix(), name,
term) <= (int)len);
}
void
@@ -421,7 +457,7 @@ isc_cfgmgr_open(const char *name) {
};
strcpy(node->name, name);
isc__cfgmgr_buildkey(name, true, node->prefix, BUFLEN);
isc__cfgmgr_buildkey(name, "/", node->prefix, BUFLEN);
ISC_LIST_APPEND(isc__cfgmgr_ctx.openednodes, node, link);
}
@@ -431,7 +467,7 @@ isc_cfgmgr_close(void) {
isc__cfgmgr_checkopenednodes();
node = ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes);
node = isc__cfgmgr_curnode();
ISC_LIST_UNLINK(isc__cfgmgr_ctx.openednodes, node, link);
isc_mem_free(isc__cfgmgr_mctx, node->name);
isc_mem_put(isc__cfgmgr_mctx, node->prefix, BUFLEN);
@@ -440,19 +476,19 @@ isc_cfgmgr_close(void) {
void
isc_cfgmgr_delnode(void) {
MDB_val dbkey;
char *prefix;
const char *prefix;
isc__cfgmgr_checkopenednodes();
REQUIRE(isc__cfgmgr_ctx.readonly == false);
prefix = ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes)->prefix;
dbkey = (MDB_val){ .mv_size = strlen(prefix) + 1, .mv_data = prefix };
prefix = isc__cfgmgr_curprefix();
do {
/*
* Recursively delete nested nodes and properties.
*/
MDB_val dbkey = { .mv_size = strlen(prefix) + 1,
.mv_data = (void *)prefix };
int result = mdb_cursor_get(isc__cfgmgr_lmdbcursor(), &dbkey,
NULL, MDB_SET_RANGE);
@@ -481,7 +517,7 @@ isc_cfgmgr_getval(const char *name, isc_cfgmgr_val_t *value) {
REQUIRE(name != NULL);
REQUIRE(value != NULL);
isc__cfgmgr_buildkey(name, false, key, sizeof(key));
isc__cfgmgr_buildkey(name, "", key, sizeof(key));
dbkey = (MDB_val){ .mv_size = strlen(key) + 1, .mv_data = key };
if (mdb_cursor_get(isc__cfgmgr_lmdbcursor(), &dbkey, &dbval, MDB_SET) !=
@@ -519,7 +555,7 @@ isc_cfgmgr_setval(const char *name, const isc_cfgmgr_val_t *value) {
REQUIRE((value != NULL && value->type != ISC_CFGMGR_UNDEFINED) ||
value == NULL);
isc__cfgmgr_buildkey(name, false, key, sizeof(key));
isc__cfgmgr_buildkey(name, "", key, sizeof(key));
dbkey = (MDB_val){ .mv_size = strlen(key) + 1, .mv_data = key };
if (value == NULL) {
@@ -568,61 +604,115 @@ out:
return result;
}
void
isc_cfgmgr_foreachnodes(void (*action)(void *state, const char *name),
void *state) {
UNUSED(state);
UNUSED(action);
static void
isc__cfgmgr_foreach(void (*actionnode)(void *state, const char *name),
void (*actionproperty)(void *state, const char *name,
const isc_cfgmgr_val_t *val),
void *state) {
MDB_cursor *cursor;
MDB_val dbkey;
const char *prefix = isc__cfgmgr_curprefix();
size_t prefixlen = strlen(prefix);
size_t i;
char name[BUFLEN];
int result;
bool foundnode;
isc__cfgmgr_checkontransaction();
REQUIRE(mdb_cursor_open(isc__cfgmgr_ctx.txn, isc__cfgmgr_lmdbdbi(),
&cursor) == 0);
name[0] = 0;
dbkey.mv_data = (void *)prefix;
dbkey.mv_size = prefixlen;
result = mdb_cursor_get(cursor, &dbkey, NULL, MDB_SET_RANGE);
/*
* Not implemented yet, but here's the plan. Let's consider the
* following keys:
* The current implementation is slow: first, it looked up the first key
* which has the same prefix than the current one. Then, the code find
* the first "/" after the prefix to find the next sub-node name. If
* there none, the current entry is a property and it retry on the next
* one.
*
* /foo/bar/gee/f
* /foo/bar/gff/g
* /foo/bar/gdd
* /foo/prop
* /foo/zz/a
* /foo/zz/c
* When a sub-node name is found, it checks if it matches the previous
* found name, if it does, it moves on to the next entry (so this is
* typically _lot_ of LMDB lookup to go to the next sub-node,
* especially with zone with lot of properties).
*
* Then action will be called once, with "bar" as name, then another
* time with "zz" as name. To be fast, the implementation consist to
* have a internal sub-node in "foo" (or any node with sub-nodes) which
* contains its direct sub-nodes. So here, we would actually also have
* the following key,values:
* When a sub-node name is found and does not match the previous one,
* the action function is called, passing the sub-node name as "name".
*
* __internal_idx__/foo/bar/,bar
* __internal_idx__/foo/zz/,zz
* This also enable to iterate over direct properties of the currently
* opened node. This seems particularly useful as base-API to build
* custom configuration printer. (Other approach I attempted was to
* provide a "configuration format" that cfgmgr was reading to validate
* or call user-provided function to build configuration blocks (when a
* node is found) or properties. I might come back to this, but in
* meantime this seems handy to write tests/validate approaches).
*
* What _foreachnodes is doing is:
*
* - create a new (local) cursor
* - make a search for "__internal_idx__/foo/" (so it takes the
* current prefix and prefix "__internal_idx__" on it)
* - then, for each entry until we reach the end of entries starting
* by "__internal_idex__/foo/", we call _foreachnodes, passing the
* value as "name".
*
* The caller is free to open the sub-node, or do whathever needed, i.e.
* also call _foreachnodes on its own sub-nodes, because _foreachnodes
* call has its own cursor, it won't mess it up.
*
* In order to maintains this internal __internal__idx__ node, every
* time a property is added to a node "bar", its prefix is taken, then
* "__internal_idx__" is appened to it, making a key. This key is added
* with the value "bar" (current node name). Then this key,val is added
* to current LMDB DB.
*
* The similar logic is applied if the node is removed (taking the node
* prefix, prefixing it with "__internal_idx__" and delete all entries
* starting with this key. However, it is a little bit more subtle: it
* also needs to verify, anytime a property is removed, if there are
* remaining properties for the current node. If not, then it also need
* to remove the "__internal_idx__/nodeprefix" as well.
*
* Because the API enforces the fact that a regular key is always
* starting with "/", the "__internal_idx__" key,values are indeed
* internal and can't never be accessed/overridden by API misuse, nor
* clash with view/zones/any user provided names.
* I tried various alternatives and tricks (i.e. adding "indexes" to
* have a list of sub-nodes), but this is tricky and very time consuming
* to get right, so I leave this problem on the side for now, at least
* the API work and make it possible to move on/experiment with it.
*/
again:
if (result != MDB_SUCCESS ||
strncmp(prefix, dbkey.mv_data, prefixlen) != 0)
{
goto out;
}
i = prefixlen;
foundnode = false;
while (i < dbkey.mv_size) {
if (((char *)dbkey.mv_data)[i] == '/') {
foundnode = true;
break;
}
i++;
}
if (foundnode && actionnode) {
char newname[BUFLEN];
size_t namelen = i - prefixlen;
INSIST(namelen < sizeof(newname));
strncpy(newname, (char *)dbkey.mv_data + prefixlen, namelen);
newname[namelen] = 0;
if (strcmp(name, newname) != 0) {
actionnode(state, newname);
strcpy(name, newname);
}
} else if (!foundnode && actionproperty) {
char propname[BUFLEN];
size_t namelen = i - prefixlen;
isc_cfgmgr_val_t val;
INSIST(namelen < sizeof(propname));
strncpy(propname, (char *)dbkey.mv_data + prefixlen, namelen);
propname[namelen] = 0;
REQUIRE(isc_cfgmgr_getval(propname, &val) == ISC_R_SUCCESS);
actionproperty(state, propname, &val);
}
result = mdb_cursor_get(cursor, &dbkey, NULL, MDB_NEXT);
goto again;
out:
mdb_cursor_close(cursor);
}
void
isc_cfgmgr_foreachnodes(void(*action)(void *state, const char *name),
void *state) {
isc__cfgmgr_foreach(action, NULL, state);
}
void
isc_cfgmgr_foreachproperties(void (*action)(void *state, const char *name,
const isc_cfgmgr_val_t *val),
void *state) {
isc__cfgmgr_foreach(NULL, action, state);
}
+9
View File
@@ -153,6 +153,15 @@ void
isc_cfgmgr_foreachnodes(void(*action)(void *state, const char *name),
void *state);
/*
* Calls function "action" for each direct property of the currently opened
* node. Must be called under a transaction.
*/
void
isc_cfgmgr_foreachproperties(void (*action)(void *state, const char *name,
const isc_cfgmgr_val_t *val),
void *state);
/*
* Starts a read-only transaction and returns ISC_R_SUCCESS. If there is an
* issue creating a transaction, ISC_R_FAILURE is returned. Prior calling this
+285 -1
View File
@@ -772,7 +772,7 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_delete) {
isc_cfgmgr_open("foo");
assert_int_equal(result, ISC_R_SUCCESS);
result = isc_cfgmgr_getval("prop1", &val);
result = isc_cfgmgr_getval("prop1", &val);
assert_int_equal(result, ISC_R_SUCCESS);
result = isc_cfgmgr_getval("prop2", &val);
@@ -1319,6 +1319,288 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_opentests) {
isc_cfgmgr_deinit();
}
static void
isc__cfgmgr_foreachnodesaction(void *state, const char *name) {
isc_buffer_t *b = state;
isc_buffer_printf(b, "%s ", name);
}
static void
isc__cfgmgr_foreachpropertiesaction(void *state, const char *name,
const isc_cfgmgr_val_t *val) {
isc_buffer_t *b = state;
isc_buffer_printf(b, "%s=", name);
switch (val->type) {
case ISC_CFGMGR_UINT32:
isc_buffer_printf(b, "%u ", val->uint32);
break;
case ISC_CFGMGR_STRING:
isc_buffer_printf(b, "%s ", val->string);
break;
default:
UNREACHABLE();
}
}
static void
isc__cfgmgr_foreachnodesactionreentrant(void *state, const char *name) {
isc_buffer_t *b = state;
isc_buffer_printf(b, "%s[", name);
isc_cfgmgr_open(name);
isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesactionreentrant, state);
isc_cfgmgr_close();
isc_buffer_printf(b, "] ");
}
ISC_RUN_TEST_IMPL(isc_cfgmgr_foreachnodes) {
isc_result_t result;
isc_cfgmgr_val_t val;
result = isc_cfgmgr_init(mctx, TEST_DBPATH);
assert_int_equal(result, ISC_R_SUCCESS);
result = isc_cfgmgr_rwtransaction();
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_open("bar");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 42 };
result = isc_cfgmgr_setval("propbar1", &val);
assert_int_equal(result, ISC_R_SUCCESS);
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "barstr" };
result = isc_cfgmgr_setval("another-stuff", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo");
isc_cfgmgr_open("foo2");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "foo2str" };
result = isc_cfgmgr_setval("another-foo-stuff", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo1");
isc_cfgmgr_open("foo11");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 };
result = isc_cfgmgr_setval("fooprop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo12");
result = isc_cfgmgr_setval("prop", &val);
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 456 };
result = isc_cfgmgr_setval("fooprop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo13");
result = isc_cfgmgr_setval("prop", &val);
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 789 };
result = isc_cfgmgr_setval("fooprop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_close(); /* foo1 */
isc_cfgmgr_close(); /* foo */
isc_buffer_t b;
char data[512];
data[0] = 0;
/*
* bar doesn't have direct sub-nodes
*/
isc_cfgmgr_open("bar");
isc_buffer_init(&b, data, sizeof(data));
isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesaction, &b);
assert_string_equal(b.base, "");
isc_cfgmgr_close();
/*
* foo1 has 3 direct sub-nodes
*/
isc_cfgmgr_open("foo");
isc_cfgmgr_open("foo1");
isc_buffer_init(&b, data, sizeof(data));
isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesaction, &b);
assert_string_equal(b.base, "foo11 foo12 foo13 ");
isc_cfgmgr_close();
isc_cfgmgr_close();
/*
* isc_cfgmgr_foreachnodes is re-entrant
*/
isc_cfgmgr_open("foo");
isc_buffer_init(&b, data, sizeof(data));
isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesactionreentrant, &b);
assert_string_equal(b.base, "foo1[foo11[] foo12[] foo13[] ] foo2[] ");
isc_cfgmgr_close();
/*
* foreachproperties
*/
isc_cfgmgr_open("bar");
isc_buffer_init(&b, data, sizeof(data));
isc_cfgmgr_foreachproperties(isc__cfgmgr_foreachpropertiesaction, &b);
assert_string_equal(b.base, "another-stuff=barstr propbar1=42 ");
isc_cfgmgr_close();
isc_cfgmgr_rollback();
isc_cfgmgr_deinit();
}
typedef struct isc__cfgmgr_dump isc__cfgmgr_dump_t;
struct isc__cfgmgr_dump {
size_t indent;
size_t lvl;
isc_buffer_t buffer;
char data[512];
};
static void
isc__cfgmgr_dumptestsprop(void *state, const char *name,
const isc_cfgmgr_val_t *val) {
isc__cfgmgr_dump_t *dump = state;
for (size_t i = 0; i < dump->lvl * dump->indent; i++) {
isc_buffer_putstr(&dump->buffer, " ");
}
isc_buffer_printf(&dump->buffer, "%s ", name);
switch (val->type) {
case ISC_CFGMGR_UINT32:
isc_buffer_printf(&dump->buffer, "%u", val->uint32);
break;
case ISC_CFGMGR_STRING:
isc_buffer_printf(&dump->buffer, "%s", val->string);
break;
default:
UNREACHABLE();
}
isc_buffer_putstr(&dump->buffer, ";\n");
}
static void
isc__cfgmgr_dumptestnode(void *state, const char *name) {
isc__cfgmgr_dump_t *dump = state;
for (size_t i = 0; i < dump->lvl * dump->indent; i++) {
isc_buffer_putstr(&dump->buffer, " ");
}
isc_buffer_printf(&dump->buffer, "%s {\n", name);
dump->lvl++;
isc_cfgmgr_open(name);
isc_cfgmgr_foreachproperties(isc__cfgmgr_dumptestsprop, state);
isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, state);
isc_cfgmgr_close();
dump->lvl--;
for (size_t i = 0; i < dump->lvl * dump->indent; i++) {
isc_buffer_putstr(&dump->buffer, " ");
}
isc_buffer_printf(&dump->buffer, "};\n");
}
ISC_RUN_TEST_IMPL(isc_cfgmgr_dumptests) {
isc_result_t result;
isc_cfgmgr_val_t val;
result = isc_cfgmgr_init(mctx, TEST_DBPATH);
assert_int_equal(result, ISC_R_SUCCESS);
result = isc_cfgmgr_rwtransaction();
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_open("bar");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 42 };
result = isc_cfgmgr_setval("propbar1", &val);
assert_int_equal(result, ISC_R_SUCCESS);
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "barstr" };
result = isc_cfgmgr_setval("another-stuff", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo");
isc_cfgmgr_open("foo2");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "foo2str" };
result = isc_cfgmgr_setval("another-foo-stuff", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo1");
isc_cfgmgr_open("foo11");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 };
result = isc_cfgmgr_setval("fooprop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo12");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 };
result = isc_cfgmgr_setval("prop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 456 };
result = isc_cfgmgr_setval("fooprop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_open("foo13");
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 };
result = isc_cfgmgr_setval("prop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 789 };
result = isc_cfgmgr_setval("fooprop", &val);
assert_int_equal(result, ISC_R_SUCCESS);
isc_cfgmgr_close();
isc_cfgmgr_close(); /* foo1 */
isc_cfgmgr_close(); /* foo */
isc__cfgmgr_dump_t dump = {
.lvl = 0,
.indent = 8,
};
isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data));
dump.data[0] = 0;
/* TO BE FIXED... */
isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump);
const char expected[] = "bar {\n"
" another-stuff barstr;\n"
" propbar1 42;\n"
"};\n"
"foo {\n"
" foo1 {\n"
" foo11 {\n"
" fooprop 123;\n"
" };\n"
" foo12 {\n"
" fooprop 456;\n"
" prop 123;\n"
" };\n"
" foo13 {\n"
" fooprop 789;\n"
" prop 123;\n"
" };\n"
" };\n"
" foo2 {\n"
" another-foo-stuff foo2str;\n"
" };\n"
"};\n";
assert_string_equal(dump.buffer.base, expected);
isc_cfgmgr_rollback();
isc_cfgmgr_deinit();
}
// ISC_RUN_TEST_IMPL(isc_cfgmgr_builtintests) {
// isc_result_t result;
// isc_cfgmgr_val_t val;
@@ -1786,6 +2068,8 @@ ISC_TEST_ENTRY(isc_cfgmgr_delete)
ISC_TEST_ENTRY(isc_cfgmgr_nested_nodes)
ISC_TEST_ENTRY(isc_cfgmgr_threads)
ISC_TEST_ENTRY(isc_cfgmgr_opentests)
ISC_TEST_ENTRY(isc_cfgmgr_foreachnodes)
ISC_TEST_ENTRY(isc_cfgmgr_dumptests)
// ISC_TEST_ENTRY(isc_cfgmgr_validationtest)
// ISC_TEST_ENTRY(isc_cfgmgr_getparent)