From: Alan T. DeKok Date: Thu, 11 Nov 2021 22:13:05 +0000 (-0500) Subject: add edit lists and tests X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eb9443ba9171e18c2c05c86238bcd0ffc3ce8def;p=thirdparty%2Ffreeradius-server.git add edit lists and tests so that we can edit lists "in place", and then undo the edits if anything goes wrong. --- diff --git a/src/lib/util/all.mk b/src/lib/util/all.mk index 9d3ca0e56c..56fdab7acd 100644 --- a/src/lib/util/all.mk +++ b/src/lib/util/all.mk @@ -4,6 +4,7 @@ SUBMAKEFILES := \ dbuff_tests.mk \ dcursor_tests.mk \ dlist_tests.mk \ + edit_tests.mk \ heap_tests.mk \ hmac_tests.mk \ libfreeradius-util.mk \ diff --git a/src/lib/util/edit.c b/src/lib/util/edit.c new file mode 100644 index 0000000000..fc9d88f832 --- /dev/null +++ b/src/lib/util/edit.c @@ -0,0 +1,434 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/lib/util/edit.c + * @brief Functions to edit pair lists, and track undo operations + * + * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) + */ + +RCSID("$Id$") + +#include +#include "edit.h" + +/** Track a series of edits. + * + */ +struct fr_edit_list_s { + /* + * List of edits to be made, in order. + */ + fr_dlist_head_t list; + + /* + * VPs which were inserted, and then over-written by a + * later edit. + */ + fr_pair_list_t deleted_pairs; +}; + +/** Track one particular edit. + */ +typedef struct { + fr_edit_op_t op; //!< edit operation to perform + fr_dlist_t entry; //!< linked list of edits + + fr_pair_t *vp; //!< pair edited, deleted, or inserted + + union { + fr_value_box_t data; //!< original data + fr_pair_list_t children; //!< original child list, + //!< for T_OP_SET of structural entries + + struct { + fr_pair_list_t *list; //!< parent list + fr_pair_t *ref; //!< reference pair for delete, insert before/after + }; + }; +} fr_edit_t; + + +/** Undo one particular edit. + */ +static int edit_undo(fr_edit_t *e) +{ + fr_pair_t *vp = e->vp; +#ifndef NDEBUG + int rcode; +#endif + + switch (e->op) { + case FR_EDIT_INVALID: + return -1; + + case FR_EDIT_VALUE: + if (fr_type_is_leaf(vp->vp_type)) { + if (!fr_type_is_fixed_size(vp->vp_type)) fr_value_box_clear(&vp->data); + fr_value_box_copy_shallow(NULL, &vp->data, &e->data); + } else { + fr_assert(fr_type_is_structural(vp->vp_type)); + + fr_pair_list_free(&vp->vp_group); + fr_pair_list_append(&vp->vp_group, &e->children); + } + break; + + case FR_EDIT_DELETE: +#ifndef NDEBUG + rcode = +#endif + fr_pair_insert_after(e->list, e->ref, vp); + fr_assert(rcode == 0); + break; + + case FR_EDIT_INSERT_BEFORE: + case FR_EDIT_INSERT_AFTER: + /* + * We can free the VP here, as any edits to its' + * children MUST come after the creation of the + * VP. And any deletion of VPs after this one + * must come after this VP was created. + */ + fr_pair_delete(e->list, vp); + talloc_free(vp); + break; + } + + return 0; +} + +/** Abort the entries in an edit list. + * + * After this call, the input list(s) are unchanged from before any + * edits were made. + * + * the caller still has to call talloc_free(el); + */ +void fr_edit_list_abort(fr_edit_list_t *el) +{ + fr_edit_t *e; + + if (!el) return; + + /* + * Undo edits in reverse order, as later edits depend on + * earlier ones. We don't have multiple edits of the + * same VP, but we can create a VP, and then later edit + * its children. + */ + while ((e = fr_dlist_pop_tail(&el->list)) != NULL) { + edit_undo(e); + }; +} + +/** Record one particular edit + * + * For INSERT / DELETE, this function will also insert / delete the + * VP. + * + * For VALUE changes, this function must be called BEFORE the value + * is changed. Once this function has been called, it is then safe + * to edit the value in place. + * + * Note that VALUE changes for structural types are allowed ONLY when + * using T_OP_SET, which over-writes previous values. For every + * other modification to structural types, we MUST instead call + * insert / delete on the vp_group. + */ +int fr_edit_list_record(fr_edit_list_t *el, fr_edit_op_t op, fr_pair_t *vp, fr_pair_list_t *list, fr_pair_t *ref) +{ + fr_edit_t *e; + + if (!el) return 0; + + /* + * Search for previous edits. + * + * @todo - if we're modifying values of a child VP, and + * it's parent is marked as INSERT, then we don't need to + * record FR_EDIT_VALUE changes to the children. It's + * not yet clear how best to track this. + */ + for (e = fr_dlist_head(&el->list); + e != NULL; + e = fr_dlist_next(&el->list, e)) { + fr_assert(e->vp != NULL); + + if (e->vp != vp) continue; + + switch (op) { + case FR_EDIT_INVALID: + return -1; + + /* + * We're editing a previous edit. + * There's no need to record anything + * new, as we've already recorded the + * original value. + * + * Note that we can insert a pair and + * then edit it. The undo list only + * saves the insert, as the later edit is + * irrelevant. If we're undoing, we + * simply delete the new attribute which + * was inserted. + */ + case FR_EDIT_VALUE: + /* + * If we delete a pair, we can't later + * edit it. That indicates serious + * issues with the code. + */ + fr_assert(e->op != FR_EDIT_DELETE); + + /* + * We're over-writing (again) the + * children of a structural type. + * + * Save the attributes we added in the + * previous T_OP_SET operation. Some + * other edit may be changing them, so we + * don't want to delete the attributes + * until we're done. + */ + if (fr_type_is_structural(vp->vp_type) && !fr_pair_list_empty(&vp->vp_group)) { + fr_pair_list_append(&el->deleted_pairs, &vp->vp_group); + } + return 0; + + /* + * We're inserting a new pair. + * + * We can't have previously edited this + * pair (inserted, deleted, or updated + * the value), as the pair is new! + */ + case FR_EDIT_INSERT_BEFORE: + case FR_EDIT_INSERT_AFTER: + fr_assert(0); + return -1; + + /* + * We're being asked to delete something + * we previously inserted, or previously + * edited. + */ + case FR_EDIT_DELETE: + /* + * We can't delete something which was + * already deleted. + */ + fr_assert(e->op != FR_EDIT_DELETE); + + /* + * We had previously inserted it. So + * just delete the insert operation, and + * delete the VP from the list. + * + * Other edits may refer to children of + * this pair. So we don't free the VP + * immediately, but instead reparent it + * to the edit list. So that when the + * edit list is freed, the VP will be + * freed. + */ + if ((e->op == FR_EDIT_INSERT_BEFORE) || + (e->op == FR_EDIT_INSERT_AFTER)) { + fr_assert(e->list == list); + + fr_pair_remove(list, vp); + fr_pair_append(&el->deleted_pairs, vp); + + fr_dlist_remove(&el->list, e); + talloc_free(e); + return 0; + } + + /* + * We had previously changed the value, + * but now we're going to delete it. + * + * Since it had previously existed, we + * have to reset its value to the + * original one, and then track the + * deletion. + */ + fr_assert(e->op == FR_EDIT_VALUE); + edit_undo(e); + e->op = FR_EDIT_DELETE; + goto delete; + } + } /* loop over existing edits */ + + /* + * No edit for this pair exists. Create a new edit entry. + */ + e = talloc_zero(el, fr_edit_t); + if (!e) return -1; + + e->op = op; + e->vp = vp; + + switch (op) { + case FR_EDIT_INVALID: + talloc_free(e); + return -1; + + case FR_EDIT_VALUE: + fr_assert(list == NULL); + fr_assert(ref == NULL); + + if (fr_type_is_leaf(vp->vp_type)) { + fr_value_box_copy_shallow(NULL, &e->data, &vp->data); + if (!fr_type_is_fixed_size(vp->vp_type)) fr_value_box_memdup_shallow(&vp->data, vp->data.enumv, + e->data.vb_octets, e->data.vb_length, + e->data.tainted); + } else { + fr_assert(fr_type_is_structural(vp->vp_type)); + + fr_pair_list_init(&e->children); + fr_pair_list_append(&e->children, &vp->vp_group); + } + break; + + case FR_EDIT_INSERT_BEFORE: + fr_assert(list != NULL); + + /* + * There's no need to record "prev". On undo, we + * just delete this pair from the list. + */ + e->list = list; + fr_pair_insert_before(list, ref, vp); + break; + + case FR_EDIT_INSERT_AFTER: + fr_assert(list != NULL); + + /* + * There's no need to record "prev". On undo, we + * just delete this pair from the list. + */ + e->list = list; + fr_pair_insert_after(list, ref, vp); + break; + + case FR_EDIT_DELETE: + delete: + fr_assert(list != NULL); + fr_assert(ref == NULL); + + e->list = list; + e->ref = fr_pair_list_prev(list, vp); + + fr_pair_remove(list, vp); + break; + } + + fr_dlist_insert_tail(&el->list, e); + return 0; +} + +/** Finalize the edits when we destroy the edit list. + * + * Which in large part means freeing the VPs which have been deleted, + * and then deleting the edit list. + */ +static int _edit_list_destructor(fr_edit_list_t *el) +{ + fr_edit_t *e; + + if (!el) return 0; + + for (e = fr_dlist_head(&el->list); + e != NULL; + e = fr_dlist_next(&el->list, e)) { + switch (e->op) { + case FR_EDIT_INVALID: + fr_assert(0); + break; + + case FR_EDIT_INSERT_BEFORE: + case FR_EDIT_INSERT_AFTER: + break; + + case FR_EDIT_DELETE: + fr_assert(e->vp != NULL); + talloc_free(e->vp); + break; + + case FR_EDIT_VALUE: + if (fr_type_is_leaf(e->vp->vp_type)) { + fr_value_box_clear(&e->data); + } else { + fr_assert(fr_type_is_structural(e->vp->vp_type)); + fr_pair_list_free(&e->children); + } + break; + } + } + + fr_pair_list_free(&el->deleted_pairs); + + talloc_free(el); + + return 0; +} + +fr_edit_list_t *fr_edit_list_alloc(TALLOC_CTX *ctx) +{ + fr_edit_list_t *el; + + el = talloc_zero(ctx, fr_edit_list_t); + if (!el) return NULL; + + fr_dlist_init(&el->list, fr_edit_t, entry); + + fr_pair_list_init(&el->deleted_pairs); + + talloc_set_destructor(el, _edit_list_destructor); + + return el; +} + +/** Notes + * + * Unlike "update" sections, edits are _not_ hierarchical. If we're + * editing values a list, then the list has to exist. If we're + * inserting pairs in a list, then we find the lowest existing pair, + * and add pairs there. + * + * The functions tmpl_extents_find() and tmpl_extents_build_to_leaf() + * should help us figure out where the VPs exist or not. + * + * The overall "update" algorithm is now: + * + * alloc(edit list) + * + * foreach entry in the things to do + * expand LHS if needed to local TMPL + * expand RHS if needed to local box / cursor / TMPL + * + * use LHS/RHS cursors to find VPs + * edit VPs, recording edits + * + * free temporary map + * talloc_free(edit list) + */ diff --git a/src/lib/util/edit.h b/src/lib/util/edit.h new file mode 100644 index 0000000000..afb79c3744 --- /dev/null +++ b/src/lib/util/edit.h @@ -0,0 +1,52 @@ +#pragma once +/* + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file lib/util/edit.h + * @brief Structures and prototypes for editing lists. + * + * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) + */ +RCSIDH(map_h, "$Id$") + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FR_EDIT_INVALID = 0, + FR_EDIT_DELETE, //!< delete a VP + FR_EDIT_VALUE, //!< edit a VP in place + FR_EDIT_INSERT_BEFORE, //!< insert a VP into a list, before another one + FR_EDIT_INSERT_AFTER, //!< insert a VP into a list, after another one. +} fr_edit_op_t; + +typedef struct fr_edit_list_s fr_edit_list_t; + +fr_edit_list_t *fr_edit_list_alloc(TALLOC_CTX *ctx); + +void fr_edit_list_abort(fr_edit_list_t *el); + +int fr_edit_list_record(fr_edit_list_t *el, fr_edit_op_t op, fr_pair_t *vp, fr_pair_list_t *list, fr_pair_t *prev); + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/util/edit_tests.c b/src/lib/util/edit_tests.c new file mode 100644 index 0000000000..0eb4f58f32 --- /dev/null +++ b/src/lib/util/edit_tests.c @@ -0,0 +1,459 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** Tests for a AVP manipulation and search API. + * + * @file src/lib/util/edit_tests.c + * @author Alan DeKok (aland@networkradius.com) + * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) + */ + +/** + * The 'TEST_INIT' macro provided by 'acutest.h' allowing to register a function to be called + * before call the unit tests. Therefore, It calls the function ALL THE TIME causing an overhead. + * That is why we are initializing test_init() by "__attribute__((constructor));" reducing the + * test execution by 50% of the time. + */ +#define USE_CONSTRUCTOR + +/* + * It should be declared before include the "acutest.h" + */ +#ifdef USE_CONSTRUCTOR +static void test_init(void) __attribute__((constructor)); +#else +static void test_init(void); +# define TEST_INIT test_init() +#endif + +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_GPERFTOOLS_PROFILER_H +# include +#endif + +static TALLOC_CTX *autofree; +static fr_pair_list_t test_pairs; +static fr_dict_t *test_dict; + +/** Global initialisation + */ +static void test_init(void) +{ + autofree = talloc_autofree_context(); + if (!autofree) { + error: + fr_perror("edit_tests"); + fr_exit_now(EXIT_FAILURE); + } + + /* + * Mismatch between the binary and the libraries it depends on + */ + if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) goto error; + + if (fr_dict_test_init(autofree, &test_dict, NULL) < 0) goto error; + + /* Initialize the "test_pairs" list */ + fr_pair_list_init(&test_pairs); + + if (fr_pair_test_list_alloc(autofree, &test_pairs, NULL) < 0) goto error; +} + +static void add_pairs(fr_pair_list_t *local_pairs) +{ + int count; + + fr_pair_list_init(local_pairs); + + fr_pair_append(local_pairs, fr_pair_afrom_da(autofree, fr_dict_attr_test_uint32)); + fr_pair_append(local_pairs, fr_pair_afrom_da(autofree, fr_dict_attr_test_octets)); + fr_pair_append(local_pairs, fr_pair_afrom_da(autofree, fr_dict_attr_test_tlv)); + + count = fr_pair_list_len(local_pairs); + TEST_CASE("Expected (count == 3)"); + TEST_CHECK(count == 3); +} + +static void expect3(fr_pair_list_t *local_pairs) +{ + int count; + fr_pair_t *vp; + + count = fr_pair_list_len(local_pairs); + TEST_CASE("Expected (count == 3) after undoing the edits"); + TEST_CHECK(count == 3); + + vp = fr_pair_list_head(local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + + vp = fr_pair_list_tail(local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_tlv); + + fr_pair_list_free(local_pairs); +} + +/* + * Tests functions + */ +static void test_pair_delete_head(void) +{ + fr_pair_t *vp; + fr_pair_list_t local_pairs; + size_t count; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and delete the first one"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); + TEST_CHECK(rcode == 0); + + talloc_free(el); + + count = fr_pair_list_len(&local_pairs); + TEST_CASE("Expected (count == 2) after deleting the head"); + TEST_CHECK(count == 2); + + vp = fr_pair_list_head(&local_pairs); + TEST_CASE("head is now what was the second pair"); + TEST_CHECK(vp->da == fr_dict_attr_test_octets); + + fr_pair_list_free(&local_pairs); +} + +static void test_pair_delete_head_abort(void) +{ + fr_pair_t *vp; + fr_pair_list_t local_pairs; + size_t count; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and delete the first one"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); + TEST_CHECK(rcode == 0); + + count = fr_pair_list_len(&local_pairs); + TEST_CASE("Expected (count == 2) after deleting the head"); + TEST_CHECK(count == 2); + + /* + * Abort the edit + */ + fr_edit_list_abort(el); + + talloc_free(el); + + expect3(&local_pairs); +} + +static void test_pair_delete_middle(void) +{ + fr_pair_t *vp; + fr_pair_list_t local_pairs; + size_t count; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and delete the middle one"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + vp = fr_pair_list_next(&local_pairs, vp); + TEST_CHECK(vp != NULL); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); + TEST_CHECK(rcode == 0); + + talloc_free(el); + + /* let's count */ + count = fr_pair_list_len(&local_pairs); + TEST_CASE("Expected (count == 2) after deleting the middle"); + TEST_CHECK(count == 2); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + + vp = fr_pair_list_tail(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_tlv); + + fr_pair_list_free(&local_pairs); +} + +static void test_pair_delete_middle_abort(void) +{ + fr_pair_t *vp, *middle; + fr_pair_list_t local_pairs; + size_t count; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and delete the middle one, then abort"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + middle = fr_pair_list_next(&local_pairs, vp); + TEST_CHECK(middle != NULL); + TEST_CHECK(middle->da == fr_dict_attr_test_octets); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, middle, &local_pairs, NULL); + TEST_CHECK(rcode == 0); + + count = fr_pair_list_len(&local_pairs); + TEST_CASE("Expected (count == 2) after deleting the middle"); + TEST_CHECK(count == 2); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + + vp = fr_pair_list_tail(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_tlv); + + /* + * Abort the edit + */ + fr_edit_list_abort(el); + + talloc_free(el); + + expect3(&local_pairs); +} + +static void test_pair_delete_multiple(void) +{ + fr_pair_t *vp; + fr_pair_list_t local_pairs; + size_t count; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and delete the last 2"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + vp = fr_pair_list_next(&local_pairs, vp); + TEST_CHECK(vp != NULL); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* middle */ + TEST_CHECK(rcode == 0); + + vp = fr_pair_list_tail(&local_pairs); + TEST_CHECK(vp != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* tail */ + TEST_CHECK(rcode == 0); + + talloc_free(el); + + count = fr_pair_list_len(&local_pairs); + TEST_CASE("Expected (count == 1) after deleting the last 2"); + TEST_CHECK(count == 1); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + + vp = fr_pair_list_tail(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); /* head == tail */ + + fr_pair_list_free(&local_pairs); +} + +static void test_pair_delete_multiple_abort(void) +{ + fr_pair_t *vp; + fr_pair_list_t local_pairs; + size_t count; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and delete the last two, then abort"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + vp = fr_pair_list_next(&local_pairs, vp); + TEST_CHECK(vp->da == fr_dict_attr_test_octets); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* middle */ + TEST_CHECK(rcode == 0); + + vp = fr_pair_list_tail(&local_pairs); + TEST_CHECK(vp != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* tail */ + TEST_CHECK(rcode == 0); + + count = fr_pair_list_len(&local_pairs); + TEST_CASE("Expected (count == 1) after deleting the last 2"); + TEST_CHECK(count == 1); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + + vp = fr_pair_list_tail(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + + /* + * Abort the edit + */ + fr_edit_list_abort(el); + + talloc_free(el); + + expect3(&local_pairs); +} + + +static void test_pair_edit_value(void) +{ + fr_pair_t *vp; + fr_pair_list_t local_pairs; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and change the value of the first one"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp != NULL); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_VALUE, vp, NULL, NULL); + TEST_CHECK(rcode == 0); + + TEST_CHECK(vp->vp_uint32 == 0); + + vp->vp_uint32 = 1; + TEST_CHECK(vp->vp_uint32 == 1); + + talloc_free(el); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + TEST_CHECK(vp->vp_uint32 == 1); + + expect3(&local_pairs); +} + +static void test_pair_edit_value_abort(void) +{ + fr_pair_t *vp; + fr_pair_list_t local_pairs; + fr_edit_list_t *el; + int rcode; + + TEST_CASE("Add 3 pairs and change the value of the first one, then abort"); + + add_pairs(&local_pairs); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp != NULL); + + el = fr_edit_list_alloc(NULL); + TEST_CHECK(el != NULL); + + rcode = fr_edit_list_record(el, FR_EDIT_VALUE, vp, NULL, NULL); + TEST_CHECK(rcode == 0); + + TEST_CHECK(vp->vp_uint32 == 0); + + vp->vp_uint32 = 1; + TEST_CHECK(vp->vp_uint32 == 1); + + /* + * Abort the edit + */ + fr_edit_list_abort(el); + + talloc_free(el); + + vp = fr_pair_list_head(&local_pairs); + TEST_CHECK(vp->da == fr_dict_attr_test_uint32); + TEST_CHECK(vp->vp_uint32 == 0); + + expect3(&local_pairs); +} + +TEST_LIST = { + /* + * Deletion. + */ + { "pair_delete_head", test_pair_delete_head }, + { "pair_delete_head_abort", test_pair_delete_head_abort }, + + { "pair_delete_middle", test_pair_delete_middle }, + { "pair_delete_middle_abort", test_pair_delete_middle_abort }, + + { "pair_delete_multiple", test_pair_delete_multiple }, + { "pair_delete_multiple_abort", test_pair_delete_multiple_abort }, + + /* + * Insert before + */ + + /* + * Insert after + */ + + /* + * Value modification + */ + { "pair_edit_value", test_pair_edit_value }, + { "pair_edit_value_abort", test_pair_edit_value_abort }, + + { NULL } +}; diff --git a/src/lib/util/edit_tests.mk b/src/lib/util/edit_tests.mk new file mode 100644 index 0000000000..be5d4e49c4 --- /dev/null +++ b/src/lib/util/edit_tests.mk @@ -0,0 +1,7 @@ +TARGET := edit_tests + +SOURCES := edit_tests.c + +TGT_LDLIBS := $(LIBS) $(GPERFTOOLS_LIBS) +TGT_LDFLAGS := $(LDFLAGS) $(GPERFTOOLS_LDFLAGS) +TGT_PREREQS := libfreeradius-util.a diff --git a/src/lib/util/libfreeradius-util.mk b/src/lib/util/libfreeradius-util.mk index e7cf935fcb..b2ca3303da 100644 --- a/src/lib/util/libfreeradius-util.mk +++ b/src/lib/util/libfreeradius-util.mk @@ -25,6 +25,7 @@ SOURCES := \ dict_validate.c \ dl.c \ dns.c \ + edit.c \ event.c \ ext.c \ fifo.c \