LCOV - code coverage report
Current view: top level - src/test/modules/test_rbtree - test_rbtree.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 245 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 16 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*--------------------------------------------------------------------------
       2              :  *
       3              :  * test_rbtree.c
       4              :  *              Test correctness of red-black tree operations.
       5              :  *
       6              :  * Copyright (c) 2009-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  * IDENTIFICATION
       9              :  *              src/test/modules/test_rbtree/test_rbtree.c
      10              :  *
      11              :  * -------------------------------------------------------------------------
      12              :  */
      13              : 
      14              : #include "postgres.h"
      15              : 
      16              : #include "common/pg_prng.h"
      17              : #include "fmgr.h"
      18              : #include "lib/rbtree.h"
      19              : #include "utils/memutils.h"
      20              : 
      21            0 : PG_MODULE_MAGIC;
      22              : 
      23              : 
      24              : /*
      25              :  * Our test trees store an integer key, and nothing else.
      26              :  */
      27              : typedef struct IntRBTreeNode
      28              : {
      29              :         RBTNode         rbtnode;
      30              :         int                     key;
      31              : } IntRBTreeNode;
      32              : 
      33              : 
      34              : /*
      35              :  * Node comparator.  We don't worry about overflow in the subtraction,
      36              :  * since none of our test keys are negative.
      37              :  */
      38              : static int
      39            0 : irbt_cmp(const RBTNode *a, const RBTNode *b, void *arg)
      40              : {
      41            0 :         const IntRBTreeNode *ea = (const IntRBTreeNode *) a;
      42            0 :         const IntRBTreeNode *eb = (const IntRBTreeNode *) b;
      43              : 
      44            0 :         return ea->key - eb->key;
      45            0 : }
      46              : 
      47              : /*
      48              :  * Node combiner.  For testing purposes, just check that library doesn't
      49              :  * try to combine unequal keys.
      50              :  */
      51              : static void
      52            0 : irbt_combine(RBTNode *existing, const RBTNode *newdata, void *arg)
      53              : {
      54            0 :         const IntRBTreeNode *eexist = (const IntRBTreeNode *) existing;
      55            0 :         const IntRBTreeNode *enew = (const IntRBTreeNode *) newdata;
      56              : 
      57            0 :         if (eexist->key != enew->key)
      58            0 :                 elog(ERROR, "red-black tree combines %d into %d",
      59              :                          enew->key, eexist->key);
      60            0 : }
      61              : 
      62              : /* Node allocator */
      63              : static RBTNode *
      64            0 : irbt_alloc(void *arg)
      65              : {
      66            0 :         return (RBTNode *) palloc(sizeof(IntRBTreeNode));
      67              : }
      68              : 
      69              : /* Node freer */
      70              : static void
      71            0 : irbt_free(RBTNode *node, void *arg)
      72              : {
      73            0 :         pfree(node);
      74            0 : }
      75              : 
      76              : /*
      77              :  * Create a red-black tree using our support functions
      78              :  */
      79              : static RBTree *
      80            0 : create_int_rbtree(void)
      81              : {
      82            0 :         return rbt_create(sizeof(IntRBTreeNode),
      83              :                                           irbt_cmp,
      84              :                                           irbt_combine,
      85              :                                           irbt_alloc,
      86              :                                           irbt_free,
      87              :                                           NULL);
      88              : }
      89              : 
      90              : /*
      91              :  * Generate a random permutation of the integers 0..size-1
      92              :  */
      93              : static int *
      94            0 : GetPermutation(int size)
      95              : {
      96            0 :         int                *permutation;
      97            0 :         int                     i;
      98              : 
      99            0 :         permutation = (int *) palloc(size * sizeof(int));
     100              : 
     101            0 :         permutation[0] = 0;
     102              : 
     103              :         /*
     104              :          * This is the "inside-out" variant of the Fisher-Yates shuffle algorithm.
     105              :          * Notionally, we append each new value to the array and then swap it with
     106              :          * a randomly-chosen array element (possibly including itself, else we
     107              :          * fail to generate permutations with the last integer last).  The swap
     108              :          * step can be optimized by combining it with the insertion.
     109              :          */
     110            0 :         for (i = 1; i < size; i++)
     111              :         {
     112            0 :                 int                     j = pg_prng_uint64_range(&pg_global_prng_state, 0, i);
     113              : 
     114            0 :                 if (j < i)                           /* avoid fetching undefined data if j=i */
     115            0 :                         permutation[i] = permutation[j];
     116            0 :                 permutation[j] = i;
     117            0 :         }
     118              : 
     119            0 :         return permutation;
     120            0 : }
     121              : 
     122              : /*
     123              :  * Populate an empty RBTree with "size" integers having the values
     124              :  * 0, step, 2*step, 3*step, ..., inserting them in random order
     125              :  */
     126              : static void
     127            0 : rbt_populate(RBTree *tree, int size, int step)
     128              : {
     129            0 :         int                *permutation = GetPermutation(size);
     130            0 :         IntRBTreeNode node;
     131            0 :         bool            isNew;
     132            0 :         int                     i;
     133              : 
     134              :         /* Insert values.  We don't expect any collisions. */
     135            0 :         for (i = 0; i < size; i++)
     136              :         {
     137            0 :                 node.key = step * permutation[i];
     138            0 :                 rbt_insert(tree, (RBTNode *) &node, &isNew);
     139            0 :                 if (!isNew)
     140            0 :                         elog(ERROR, "unexpected !isNew result from rbt_insert");
     141            0 :         }
     142              : 
     143              :         /*
     144              :          * Re-insert the first value to make sure collisions work right.  It's
     145              :          * probably not useful to test that case over again for all the values.
     146              :          */
     147            0 :         if (size > 0)
     148              :         {
     149            0 :                 node.key = step * permutation[0];
     150            0 :                 rbt_insert(tree, (RBTNode *) &node, &isNew);
     151            0 :                 if (isNew)
     152            0 :                         elog(ERROR, "unexpected isNew result from rbt_insert");
     153            0 :         }
     154              : 
     155            0 :         pfree(permutation);
     156            0 : }
     157              : 
     158              : /*
     159              :  * Check the correctness of left-right traversal.
     160              :  * Left-right traversal is correct if all elements are
     161              :  * visited in increasing order.
     162              :  */
     163              : static void
     164            0 : testleftright(int size)
     165              : {
     166            0 :         RBTree     *tree = create_int_rbtree();
     167            0 :         IntRBTreeNode *node;
     168            0 :         RBTreeIterator iter;
     169            0 :         int                     lastKey = -1;
     170            0 :         int                     count = 0;
     171              : 
     172              :         /* check iteration over empty tree */
     173            0 :         rbt_begin_iterate(tree, LeftRightWalk, &iter);
     174            0 :         if (rbt_iterate(&iter) != NULL)
     175            0 :                 elog(ERROR, "left-right walk over empty tree produced an element");
     176              : 
     177              :         /* fill tree with consecutive natural numbers */
     178            0 :         rbt_populate(tree, size, 1);
     179              : 
     180              :         /* iterate over the tree */
     181            0 :         rbt_begin_iterate(tree, LeftRightWalk, &iter);
     182              : 
     183            0 :         while ((node = (IntRBTreeNode *) rbt_iterate(&iter)) != NULL)
     184              :         {
     185              :                 /* check that order is increasing */
     186            0 :                 if (node->key <= lastKey)
     187            0 :                         elog(ERROR, "left-right walk gives elements not in sorted order");
     188            0 :                 lastKey = node->key;
     189            0 :                 count++;
     190              :         }
     191              : 
     192            0 :         if (lastKey != size - 1)
     193            0 :                 elog(ERROR, "left-right walk did not reach end");
     194            0 :         if (count != size)
     195            0 :                 elog(ERROR, "left-right walk missed some elements");
     196            0 : }
     197              : 
     198              : /*
     199              :  * Check the correctness of right-left traversal.
     200              :  * Right-left traversal is correct if all elements are
     201              :  * visited in decreasing order.
     202              :  */
     203              : static void
     204            0 : testrightleft(int size)
     205              : {
     206            0 :         RBTree     *tree = create_int_rbtree();
     207            0 :         IntRBTreeNode *node;
     208            0 :         RBTreeIterator iter;
     209            0 :         int                     lastKey = size;
     210            0 :         int                     count = 0;
     211              : 
     212              :         /* check iteration over empty tree */
     213            0 :         rbt_begin_iterate(tree, RightLeftWalk, &iter);
     214            0 :         if (rbt_iterate(&iter) != NULL)
     215            0 :                 elog(ERROR, "right-left walk over empty tree produced an element");
     216              : 
     217              :         /* fill tree with consecutive natural numbers */
     218            0 :         rbt_populate(tree, size, 1);
     219              : 
     220              :         /* iterate over the tree */
     221            0 :         rbt_begin_iterate(tree, RightLeftWalk, &iter);
     222              : 
     223            0 :         while ((node = (IntRBTreeNode *) rbt_iterate(&iter)) != NULL)
     224              :         {
     225              :                 /* check that order is decreasing */
     226            0 :                 if (node->key >= lastKey)
     227            0 :                         elog(ERROR, "right-left walk gives elements not in sorted order");
     228            0 :                 lastKey = node->key;
     229            0 :                 count++;
     230              :         }
     231              : 
     232            0 :         if (lastKey != 0)
     233            0 :                 elog(ERROR, "right-left walk did not reach end");
     234            0 :         if (count != size)
     235            0 :                 elog(ERROR, "right-left walk missed some elements");
     236            0 : }
     237              : 
     238              : /*
     239              :  * Check the correctness of the rbt_find operation by searching for
     240              :  * both elements we inserted and elements we didn't.
     241              :  */
     242              : static void
     243            0 : testfind(int size)
     244              : {
     245            0 :         RBTree     *tree = create_int_rbtree();
     246            0 :         int                     i;
     247              : 
     248              :         /* Insert even integers from 0 to 2 * (size-1) */
     249            0 :         rbt_populate(tree, size, 2);
     250              : 
     251              :         /* Check that all inserted elements can be found */
     252            0 :         for (i = 0; i < size; i++)
     253              :         {
     254            0 :                 IntRBTreeNode node;
     255            0 :                 IntRBTreeNode *resultNode;
     256              : 
     257            0 :                 node.key = 2 * i;
     258            0 :                 resultNode = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
     259            0 :                 if (resultNode == NULL)
     260            0 :                         elog(ERROR, "inserted element was not found");
     261            0 :                 if (node.key != resultNode->key)
     262            0 :                         elog(ERROR, "find operation in rbtree gave wrong result");
     263            0 :         }
     264              : 
     265              :         /*
     266              :          * Check that not-inserted elements can not be found, being sure to try
     267              :          * values before the first and after the last element.
     268              :          */
     269            0 :         for (i = -1; i <= 2 * size; i += 2)
     270              :         {
     271            0 :                 IntRBTreeNode node;
     272            0 :                 IntRBTreeNode *resultNode;
     273              : 
     274            0 :                 node.key = i;
     275            0 :                 resultNode = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
     276            0 :                 if (resultNode != NULL)
     277            0 :                         elog(ERROR, "not-inserted element was found");
     278            0 :         }
     279            0 : }
     280              : 
     281              : /*
     282              :  * Check the correctness of the rbt_find_less() and rbt_find_great() functions
     283              :  * by searching for an equal key and iterating the lesser keys then the greater
     284              :  * keys.
     285              :  */
     286              : static void
     287            0 : testfindltgt(int size)
     288              : {
     289            0 :         RBTree     *tree = create_int_rbtree();
     290              : 
     291              :         /*
     292              :          * Using the size as the random key to search wouldn't allow us to get at
     293              :          * least one greater match, so we do size - 1
     294              :          */
     295            0 :         int                     randomKey = pg_prng_uint64_range(&pg_global_prng_state, 0, size - 1);
     296            0 :         bool            keyDeleted;
     297            0 :         IntRBTreeNode searchNode = {.key = randomKey};
     298            0 :         IntRBTreeNode *lteNode;
     299            0 :         IntRBTreeNode *gteNode;
     300            0 :         IntRBTreeNode *node;
     301              : 
     302              :         /* Insert natural numbers */
     303            0 :         rbt_populate(tree, size, 1);
     304              : 
     305              :         /*
     306              :          * Since the search key is included in the naturals of the tree, we're
     307              :          * sure to find an equal match
     308              :          */
     309            0 :         lteNode = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode, true);
     310            0 :         gteNode = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode, true);
     311              : 
     312            0 :         if (lteNode == NULL || lteNode->key != searchNode.key)
     313            0 :                 elog(ERROR, "rbt_find_less() didn't find the equal key");
     314              : 
     315            0 :         if (gteNode == NULL || gteNode->key != searchNode.key)
     316            0 :                 elog(ERROR, "rbt_find_great() didn't find the equal key");
     317              : 
     318            0 :         if (lteNode != gteNode)
     319            0 :                 elog(ERROR, "rbt_find_less() and rbt_find_great() found different equal keys");
     320              : 
     321              :         /* Find the rest of the naturals lesser than the search key */
     322            0 :         keyDeleted = false;
     323            0 :         for (; searchNode.key > 0; searchNode.key--)
     324              :         {
     325              :                 /*
     326              :                  * Find the next key.  If the current key is deleted, we can pass
     327              :                  * equal_match == true and still find the next one.
     328              :                  */
     329            0 :                 node = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode,
     330            0 :                                                                                            keyDeleted);
     331              : 
     332              :                 /* ensure we find a lesser match */
     333            0 :                 if (!node || !(node->key < searchNode.key))
     334            0 :                         elog(ERROR, "rbt_find_less() didn't find a lesser key");
     335              : 
     336              :                 /* randomly delete the found key or leave it */
     337            0 :                 keyDeleted = (pg_prng_uint64_range(&pg_global_prng_state, 0, 1) == 1);
     338            0 :                 if (keyDeleted)
     339            0 :                         rbt_delete(tree, (RBTNode *) node);
     340            0 :         }
     341              : 
     342              :         /* Find the rest of the naturals greater than the search key */
     343            0 :         keyDeleted = false;
     344            0 :         for (searchNode.key = randomKey; searchNode.key < size - 1; searchNode.key++)
     345              :         {
     346              :                 /*
     347              :                  * Find the next key.  If the current key is deleted, we can pass
     348              :                  * equal_match == true and still find the next one.
     349              :                  */
     350            0 :                 node = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode,
     351            0 :                                                                                                 keyDeleted);
     352              : 
     353              :                 /* ensure we find a greater match */
     354            0 :                 if (!node || !(node->key > searchNode.key))
     355            0 :                         elog(ERROR, "rbt_find_great() didn't find a greater key");
     356              : 
     357              :                 /* randomly delete the found key or leave it */
     358            0 :                 keyDeleted = (pg_prng_uint64_range(&pg_global_prng_state, 0, 1) == 1);
     359            0 :                 if (keyDeleted)
     360            0 :                         rbt_delete(tree, (RBTNode *) node);
     361            0 :         }
     362              : 
     363              :         /* Check out of bounds searches find nothing */
     364            0 :         searchNode.key = -1;
     365            0 :         node = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode, true);
     366            0 :         if (node != NULL)
     367            0 :                 elog(ERROR, "rbt_find_less() found non-inserted element");
     368            0 :         searchNode.key = 0;
     369            0 :         node = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode, false);
     370            0 :         if (node != NULL)
     371            0 :                 elog(ERROR, "rbt_find_less() found non-inserted element");
     372            0 :         searchNode.key = size;
     373            0 :         node = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode, true);
     374            0 :         if (node != NULL)
     375            0 :                 elog(ERROR, "rbt_find_great() found non-inserted element");
     376            0 :         searchNode.key = size - 1;
     377            0 :         node = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode, false);
     378            0 :         if (node != NULL)
     379            0 :                 elog(ERROR, "rbt_find_great() found non-inserted element");
     380            0 : }
     381              : 
     382              : /*
     383              :  * Check the correctness of the rbt_leftmost operation.
     384              :  * This operation should always return the smallest element of the tree.
     385              :  */
     386              : static void
     387            0 : testleftmost(int size)
     388              : {
     389            0 :         RBTree     *tree = create_int_rbtree();
     390            0 :         IntRBTreeNode *result;
     391              : 
     392              :         /* Check that empty tree has no leftmost element */
     393            0 :         if (rbt_leftmost(tree) != NULL)
     394            0 :                 elog(ERROR, "leftmost node of empty tree is not NULL");
     395              : 
     396              :         /* fill tree with consecutive natural numbers */
     397            0 :         rbt_populate(tree, size, 1);
     398              : 
     399              :         /* Check that leftmost element is the smallest one */
     400            0 :         result = (IntRBTreeNode *) rbt_leftmost(tree);
     401            0 :         if (result == NULL || result->key != 0)
     402            0 :                 elog(ERROR, "rbt_leftmost gave wrong result");
     403            0 : }
     404              : 
     405              : /*
     406              :  * Check the correctness of the rbt_delete operation.
     407              :  */
     408              : static void
     409            0 : testdelete(int size, int delsize)
     410              : {
     411            0 :         RBTree     *tree = create_int_rbtree();
     412            0 :         int                *deleteIds;
     413            0 :         bool       *chosen;
     414            0 :         int                     i;
     415              : 
     416              :         /* fill tree with consecutive natural numbers */
     417            0 :         rbt_populate(tree, size, 1);
     418              : 
     419              :         /* Choose unique ids to delete */
     420            0 :         deleteIds = (int *) palloc(delsize * sizeof(int));
     421            0 :         chosen = (bool *) palloc0(size * sizeof(bool));
     422              : 
     423            0 :         for (i = 0; i < delsize; i++)
     424              :         {
     425            0 :                 int                     k = pg_prng_uint64_range(&pg_global_prng_state, 0, size - 1);
     426              : 
     427            0 :                 while (chosen[k])
     428            0 :                         k = (k + 1) % size;
     429            0 :                 deleteIds[i] = k;
     430            0 :                 chosen[k] = true;
     431            0 :         }
     432              : 
     433              :         /* Delete elements */
     434            0 :         for (i = 0; i < delsize; i++)
     435              :         {
     436            0 :                 IntRBTreeNode find;
     437            0 :                 IntRBTreeNode *node;
     438              : 
     439            0 :                 find.key = deleteIds[i];
     440              :                 /* Locate the node to be deleted */
     441            0 :                 node = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &find);
     442            0 :                 if (node == NULL || node->key != deleteIds[i])
     443            0 :                         elog(ERROR, "expected element was not found during deleting");
     444              :                 /* Delete it */
     445            0 :                 rbt_delete(tree, (RBTNode *) node);
     446            0 :         }
     447              : 
     448              :         /* Check that deleted elements are deleted */
     449            0 :         for (i = 0; i < size; i++)
     450              :         {
     451            0 :                 IntRBTreeNode node;
     452            0 :                 IntRBTreeNode *result;
     453              : 
     454            0 :                 node.key = i;
     455            0 :                 result = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
     456            0 :                 if (chosen[i])
     457              :                 {
     458              :                         /* Deleted element should be absent */
     459            0 :                         if (result != NULL)
     460            0 :                                 elog(ERROR, "deleted element still present in the rbtree");
     461            0 :                 }
     462              :                 else
     463              :                 {
     464              :                         /* Else it should be present */
     465            0 :                         if (result == NULL || result->key != i)
     466            0 :                                 elog(ERROR, "delete operation removed wrong rbtree value");
     467              :                 }
     468            0 :         }
     469              : 
     470              :         /* Delete remaining elements, so as to exercise reducing tree to empty */
     471            0 :         for (i = 0; i < size; i++)
     472              :         {
     473            0 :                 IntRBTreeNode find;
     474            0 :                 IntRBTreeNode *node;
     475              : 
     476            0 :                 if (chosen[i])
     477            0 :                         continue;
     478            0 :                 find.key = i;
     479              :                 /* Locate the node to be deleted */
     480            0 :                 node = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &find);
     481            0 :                 if (node == NULL || node->key != i)
     482            0 :                         elog(ERROR, "expected element was not found during deleting");
     483              :                 /* Delete it */
     484            0 :                 rbt_delete(tree, (RBTNode *) node);
     485            0 :         }
     486              : 
     487              :         /* Tree should now be empty */
     488            0 :         if (rbt_leftmost(tree) != NULL)
     489            0 :                 elog(ERROR, "deleting all elements failed");
     490              : 
     491            0 :         pfree(deleteIds);
     492            0 :         pfree(chosen);
     493            0 : }
     494              : 
     495              : /*
     496              :  * SQL-callable entry point to perform all tests
     497              :  *
     498              :  * Argument is the number of entries to put in the trees
     499              :  */
     500            0 : PG_FUNCTION_INFO_V1(test_rb_tree);
     501              : 
     502              : Datum
     503            0 : test_rb_tree(PG_FUNCTION_ARGS)
     504              : {
     505            0 :         int                     size = PG_GETARG_INT32(0);
     506              : 
     507            0 :         if (size <= 0 || size > MaxAllocSize / sizeof(int))
     508            0 :                 elog(ERROR, "invalid size for test_rb_tree: %d", size);
     509            0 :         testleftright(size);
     510            0 :         testrightleft(size);
     511            0 :         testfind(size);
     512            0 :         testfindltgt(size);
     513            0 :         testleftmost(size);
     514            0 :         testdelete(size, Max(size / 10, 1));
     515            0 :         PG_RETURN_VOID();
     516            0 : }
        

Generated by: LCOV version 2.3.2-1