summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorAnton <anton@picapica>2026-04-30 03:57:10 +0400
committerAnton <anton@picapica>2026-04-30 03:57:10 +0400
commit62b3a87abf4343e5c0cd1358dc958f06277a09d0 (patch)
treee5790e240f880fd3969daff927343c8c2b98414a /tests
parentdc8dc3d49d4b98e28cee3e68cf408bc8af9633b9 (diff)
downloadpica-pica-62b3a87abf4343e5c0cd1358dc958f06277a09d0.tar.gz
pica-pica-62b3a87abf4343e5c0cd1358dc958f06277a09d0.zip
pica-node: fix bug in client tree node removal
Diffstat (limited to 'tests')
-rw-r--r--tests/test_client_tree.c436
1 files changed, 436 insertions, 0 deletions
diff --git a/tests/test_client_tree.c b/tests/test_client_tree.c
new file mode 100644
index 0000000..81076f8
--- /dev/null
+++ b/tests/test_client_tree.c
@@ -0,0 +1,436 @@
+/*
+ (c) Copyright 2012 - 2026 Anton Sviridenko
+ https://picapica.im
+
+ 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, version 3.
+
+ 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 <https://www.gnu.org/licenses/>.
+*/
+
+/* Co-authored with Claude Sonnet 4.6 */
+
+#include "../PICA_node.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+//extern int client_tree_add(struct client *ci);
+//extern struct client *client_tree_search(const unsigned char *id);
+//extern void client_tree_remove(struct client *ci);
+//extern struct client *client_tree_root;
+struct client *client_tree_root = NULL;
+
+int client_tree_add(struct client *ci)
+{
+ struct client *i;
+ if (client_tree_root)
+ {
+ i = client_tree_root;
+
+ do
+ {
+ int cmp;
+
+ cmp = memcmp(ci->id, i->id, PICA_ID_SIZE);
+
+ if (cmp == 0)
+ return 0;
+
+ if (cmp < 0) // ci->id < i->id
+ {
+ if (i->left)
+ i = i->left;
+ else
+ {
+ i->left = ci;
+ ci->up = i;
+ return 1;
+ }
+ }
+ else
+ {
+ if (i->right)
+ i = i->right;
+ else
+ {
+ i->right = ci;
+ ci->up = i;
+ return 1;
+ }
+ }
+ }
+ while(1);
+ }
+ else
+ client_tree_root = ci;
+
+ return 1;
+}
+
+struct client* client_tree_search(const unsigned char *id)
+{
+ struct client* i_ptr;
+ i_ptr = client_tree_root;
+
+
+ while(i_ptr)
+ {
+ int cmp = memcmp(id, i_ptr->id, PICA_ID_SIZE);
+
+ if (cmp == 0)
+ return i_ptr;
+
+ if (cmp < 0) //id < i_ptr->id
+ i_ptr = i_ptr->left;
+ else
+ i_ptr = i_ptr->right;
+ }
+
+ return 0;
+}
+
+void client_tree_print(struct client *c)
+{
+ char id_buf[2 * PICA_ID_SIZE];
+ if (!c)
+ {
+ return;
+ }
+
+
+ if (c->next_multi)
+ client_tree_print(c->next_multi);
+
+ if (c->left)
+ client_tree_print(c->left);
+
+ if (c->right)
+ client_tree_print(c->right);
+}
+
+
+void client_tree_remove(struct client* ci)
+{
+ struct client** p_link;//указывает на left или right в родительском узле
+
+ if (!ci->up)
+ {
+ p_link = &client_tree_root;
+ }
+ else
+ {
+ if (ci == ci->up->left)
+ p_link = &(ci->up->left);
+ else
+ p_link = &(ci->up->right);
+ }
+
+
+ if (!ci->left && !ci->right)
+ {
+ *p_link = 0;
+ return;
+ }
+
+ if (ci->left && !ci->right)
+ {
+ ci->left->up = ci->up;
+ *p_link = ci->left;
+ return;
+ }
+
+ if (!ci->left && ci->right)
+ {
+ ci->right->up = ci->up;
+ *p_link = ci->right;
+ return;
+ }
+
+
+ {
+ struct client *lm;
+ lm = ci->right;
+ while(lm->left) lm = lm->left; //поиск самого левого узла правого поддерева
+
+ if (lm != ci->right)
+ {
+ lm->up->left = lm->right;
+ if (lm->right)
+ lm->right->up = lm->up; // FIX 1
+ lm->right = ci->right;
+ ci->right->up = lm; // FIX 2
+ }
+ lm->left = ci->left;
+ if (ci->left)
+ ci->left->up = lm; // FIX 3
+ lm->up = ci->up;
+ *p_link = lm;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Helpers */
+/* ------------------------------------------------------------------ */
+
+/* Allocate a zeroed node with a single-byte key padded to PICA_ID_SIZE */
+static struct client *make_node(unsigned char key)
+{
+ struct client *n = calloc(1, sizeof(struct client));
+ n->id[0] = key; /* remaining bytes are 0 – still unique for key != 0 */
+ return n;
+}
+
+/* Reset the global tree root */
+static void reset_tree(void) { client_tree_root = NULL; }
+
+/* Walk every node in the tree and verify ->up consistency */
+static int check_up_pointers(struct client *node, struct client *expected_parent)
+{
+ if (!node) return 1;
+ if (node->up != expected_parent) return 0;
+ return check_up_pointers(node->left, node) &&
+ check_up_pointers(node->right, node);
+}
+
+/* Simple test accounting */
+static int passed = 0, failed = 0;
+static void report(const char *name, int ok)
+{
+ if (ok) { printf("[PASS] %s\n", name); passed++; }
+ else { printf("[FAIL] %s\n", name); failed++; }
+}
+
+
+/* ------------------------------------------------------------------ */
+/* Tests */
+/* ------------------------------------------------------------------ */
+
+/*
+ * TEST for BUG 1
+ * --------------
+ * lm->right->up must be updated when lm is detached from its parent.
+ *
+ * Tree shape built:
+ *
+ * 50 (root)
+ * / \
+ * 20 80 <- ci = 50 will be removed
+ * \
+ * 70 <- right child of 50
+ * / \
+ * 60 75 <- lm = 60 (leftmost of right subtree)
+ * \
+ * 73 <- lm->right; its ->up must point to lm->up (70) after removal
+ *
+ * After removing 50, lm (60) replaces it.
+ * lm->right (73) must have ->up == 70, not the stale lm.
+ *
+ * Note: to keep the shape simple we use keys so that BST ordering holds.
+ * (all keys are single bytes compared with memcmp over 32 bytes, so
+ * byte[0] alone determines order when all other bytes are 0.)
+ */
+static void test_fix1_lm_right_up(void)
+{
+ reset_tree();
+
+ struct client *n50 = make_node(50);
+ struct client *n20 = make_node(20);
+ struct client *n80 = make_node(80); /* unused but keeps shape symmetric */
+ struct client *n70 = make_node(70);
+ struct client *n60 = make_node(60);
+ struct client *n75 = make_node(75);
+ struct client *n73 = make_node(73);
+
+ client_tree_add(n50);
+ client_tree_add(n20);
+ client_tree_add(n80);
+ client_tree_add(n70);
+ client_tree_add(n60);
+ client_tree_add(n75);
+ client_tree_add(n73);
+
+ /*
+ * Remove n50 (two children).
+ * lm = leftmost of right subtree of n50 = n60 (n50->right=n80... wait,
+ * we need lm to be deep enough to have a right child.
+ *
+ * Re-think: use a simpler layout where n50's right child IS the node
+ * whose leftmost descendant has a right child.
+ *
+ * Rebuild with clearer shape:
+ * root = 50
+ * 50->left = 20
+ * 50->right = 70 (ci->right)
+ * 70->left = 60 (lm – leftmost of right subtree)
+ * 60->right = 65 (lm->right – the node whose ->up is tested)
+ */
+ reset_tree();
+ free(n80); free(n75); free(n73);
+
+ n50 = make_node(50);
+ n20 = make_node(20);
+ n70 = make_node(70);
+ n60 = make_node(60);
+ struct client *n65 = make_node(65); /* lm->right */
+
+ client_tree_add(n50); /* root */
+ client_tree_add(n20); /* 50->left */
+ client_tree_add(n70); /* 50->right */
+ client_tree_add(n60); /* 70->left = lm */
+ client_tree_add(n65); /* 60->right = lm->right */
+
+ /* Sanity: verify shape before removal */
+ /* n50 is root, n70 is ci->right, n60 is lm, n65 is lm->right */
+
+ client_tree_remove(n50);
+
+ /*
+ * After removal, lm (n60) is the new root (n50->up was NULL).
+ * lm->right should now be n70 (ci->right).
+ * n65 (old lm->right) was detached from n60 and re-hung under n70.
+ * Specifically: n70->left = n65, so n65->up must == n70.
+ */
+ int ok = (n65->up == n70);
+ report("FIX1: lm->right->up updated after lm detached from parent", ok);
+
+ free(n50); free(n20); free(n70); free(n60); free(n65);
+}
+
+/*
+ * TEST for BUG 2
+ * --------------
+ * ci->right->up must be updated to lm when lm != ci->right.
+ *
+ * Same tree shape as above. After removing n50:
+ * lm (n60) takes n50's position as root.
+ * lm->right is set to ci->right (n70).
+ * Therefore n70->up must == n60 (lm).
+ */
+static void test_fix2_ci_right_up(void)
+{
+ reset_tree();
+
+ struct client *n50 = make_node(50);
+ struct client *n20 = make_node(20);
+ struct client *n70 = make_node(70);
+ struct client *n60 = make_node(60);
+ struct client *n65 = make_node(65);
+
+ client_tree_add(n50);
+ client_tree_add(n20);
+ client_tree_add(n70);
+ client_tree_add(n60);
+ client_tree_add(n65);
+
+ client_tree_remove(n50);
+
+ /*
+ * lm = n60 (leftmost of n50->right = n70's subtree).
+ * lm != ci->right (n60 != n70), so the "lm != ci->right" branch fires.
+ * lm->right is then set to n70, so n70->up must be n60.
+ */
+ int ok = (n70->up == n60);
+ report("FIX2: ci->right->up updated when lm replaces ci", ok);
+
+ free(n50); free(n20); free(n70); free(n60); free(n65);
+}
+
+/*
+ * TEST for BUG 3
+ * --------------
+ * ci->left->up must be updated to lm after lm takes ci's position.
+ *
+ * Minimal two-children removal where lm == ci->right (the simplest case –
+ * ci->right has no left child), to isolate Bug 3 independently of Bug 1/2.
+ *
+ * Tree:
+ * 50 (root, ci)
+ * / \
+ * 20 70 lm == ci->right == 70 (no left child)
+ *
+ * After removing 50, n70 becomes root.
+ * n70->left = n20, so n20->up must == n70.
+ */
+static void test_fix3_ci_left_up(void)
+{
+ reset_tree();
+
+ struct client *n50 = make_node(50);
+ struct client *n20 = make_node(20);
+ struct client *n70 = make_node(70);
+
+ client_tree_add(n50);
+ client_tree_add(n20);
+ client_tree_add(n70);
+
+ client_tree_remove(n50);
+
+ /*
+ * lm == n70 (== ci->right, no left child under 70).
+ * Code goes to the shared part: lm->left = ci->left (n20).
+ * ci->left->up (n20->up) must be updated to lm (n70).
+ */
+ int ok = (n20->up == n70);
+ report("FIX3: ci->left->up updated when lm replaces ci", ok);
+
+ free(n50); free(n20); free(n70);
+}
+
+/*
+ * INTEGRITY TEST
+ * --------------
+ * After a sequence of insertions and removals, verify that every ->up
+ * pointer in the remaining tree is consistent with the actual parent.
+ * This catches all three bugs simultaneously via a post-condition check.
+ *
+ * Sequence:
+ * Insert: 50, 30, 70, 20, 40, 60, 80, 35, 45, 65
+ * Remove: 50 (two children, lm = 60, lm != 70, lm has no right child)
+ * 30 (two children, lm = 35)
+ * Expected: fully consistent ->up pointers throughout.
+ */
+static void test_full_up_consistency(void)
+{
+ reset_tree();
+
+ unsigned char keys[] = {50, 30, 70, 20, 40, 60, 80, 35, 45, 65};
+ int n = sizeof(keys) / sizeof(keys[0]);
+ struct client *nodes[10];
+
+ for (int i = 0; i < n; i++) {
+ nodes[i] = make_node(keys[i]);
+ client_tree_add(nodes[i]);
+ }
+
+ /* Remove node with key 50 (two children; lm=60 which has right child 65) */
+ client_tree_remove(nodes[0]); /* n50 */
+
+ /* Remove node with key 30 (two children; lm=35 which has no right child) */
+ client_tree_remove(nodes[1]); /* n30 */
+
+ int ok = check_up_pointers(client_tree_root, NULL);
+ report("INTEGRITY: all ->up pointers consistent after multiple removals", ok);
+
+ for (int i = 0; i < n; i++) free(nodes[i]);
+}
+
+
+int main(void)
+{
+ test_fix1_lm_right_up();
+ test_fix2_ci_right_up();
+ test_fix3_ci_left_up();
+ test_full_up_consistency();
+
+ printf("\n%d passed, %d failed.\n", passed, failed);
+ return failed ? 1 : 0;
+}