LCOV - code coverage report
Current view: top level - src/test/modules/test_radixtree - test_radixtree.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 126 127 99.2 %
Date: 2024-11-21 10:14:43 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*--------------------------------------------------------------------------
       2             :  *
       3             :  * test_radixtree.c
       4             :  *      Test module for adaptive radix tree.
       5             :  *
       6             :  * Copyright (c) 2024, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *      src/test/modules/test_radixtree/test_radixtree.c
      10             :  *
      11             :  * -------------------------------------------------------------------------
      12             :  */
      13             : #include "postgres.h"
      14             : 
      15             : #include "common/int.h"
      16             : #include "common/pg_prng.h"
      17             : #include "fmgr.h"
      18             : #include "utils/memutils.h"
      19             : #include "utils/timestamp.h"
      20             : 
      21             : /* uncomment to use shared memory for the tree */
      22             : /* #define TEST_SHARED_RT */
      23             : 
      24             : #define UINT64_HEX_FORMAT "%" INT64_MODIFIER "X"
      25             : 
      26             : /* Convenient macros to test results */
      27             : #define EXPECT_TRUE(expr)   \
      28             :     do { \
      29             :         if (!(expr)) \
      30             :             elog(ERROR, \
      31             :                  "%s was unexpectedly false in file \"%s\" line %u", \
      32             :                  #expr, __FILE__, __LINE__); \
      33             :     } while (0)
      34             : 
      35             : #define EXPECT_FALSE(expr)  \
      36             :     do { \
      37             :         if (expr) \
      38             :             elog(ERROR, \
      39             :                  "%s was unexpectedly true in file \"%s\" line %u", \
      40             :                  #expr, __FILE__, __LINE__); \
      41             :     } while (0)
      42             : 
      43             : #define EXPECT_EQ_U64(result_expr, expected_expr)   \
      44             :     do { \
      45             :         uint64      _result = (result_expr); \
      46             :         uint64      _expected = (expected_expr); \
      47             :         if (_result != _expected) \
      48             :             elog(ERROR, \
      49             :                  "%s yielded " UINT64_HEX_FORMAT ", expected " UINT64_HEX_FORMAT " (%s) in file \"%s\" line %u", \
      50             :                  #result_expr, _result, _expected, #expected_expr, __FILE__, __LINE__); \
      51             :     } while (0)
      52             : 
      53             : /*
      54             :  * With uint64, 64-bit platforms store the value in the last-level child
      55             :  * pointer, and 32-bit platforms store this in a single-value leaf.
      56             :  * This gives us buildfarm coverage for both paths in this module.
      57             :  */
      58             : typedef uint64 TestValueType;
      59             : 
      60             : /*
      61             :  * The node class name and the number of keys big enough to grow nodes
      62             :  * into each size class.
      63             :  */
      64             : typedef struct rt_node_class_test_elem
      65             : {
      66             :     char       *class_name;
      67             :     int         nkeys;
      68             : } rt_node_class_test_elem;
      69             : 
      70             : static rt_node_class_test_elem rt_node_class_tests[] =
      71             : {
      72             :     {
      73             :         .class_name = "node-4", /* RT_CLASS_4 */
      74             :         .nkeys = 2,
      75             :     },
      76             :     {
      77             :         .class_name = "node-16-lo", /* RT_CLASS_16_LO */
      78             :         .nkeys = 15,
      79             :     },
      80             :     {
      81             :         .class_name = "node-16-hi", /* RT_CLASS_16_HI */
      82             :         .nkeys = 30,
      83             :     },
      84             :     {
      85             :         .class_name = "node-48",  /* RT_CLASS_48 */
      86             :         .nkeys = 60,
      87             :     },
      88             :     {
      89             :         .class_name = "node-256", /* RT_CLASS_256 */
      90             :         .nkeys = 256,
      91             :     },
      92             : };
      93             : 
      94             : 
      95             : /* define the radix tree implementation to test */
      96             : #define RT_PREFIX rt
      97             : #define RT_SCOPE
      98             : #define RT_DECLARE
      99             : #define RT_DEFINE
     100             : #define RT_USE_DELETE
     101             : #define RT_VALUE_TYPE TestValueType
     102             : #ifdef TEST_SHARED_RT
     103             : #define RT_SHMEM
     104             : #endif
     105             : #define RT_DEBUG
     106             : #include "lib/radixtree.h"
     107             : 
     108             : 
     109             : /*
     110             :  * Return the number of keys in the radix tree.
     111             :  */
     112             : static uint64
     113           4 : rt_num_entries(rt_radix_tree *tree)
     114             : {
     115           4 :     return tree->ctl->num_keys;
     116             : }
     117             : 
     118           2 : PG_MODULE_MAGIC;
     119             : 
     120           4 : PG_FUNCTION_INFO_V1(test_radixtree);
     121             : 
     122             : static void
     123           2 : test_empty(void)
     124             : {
     125             :     MemoryContext radixtree_ctx;
     126             :     rt_radix_tree *radixtree;
     127             :     rt_iter    *iter;
     128             :     uint64      key;
     129             : #ifdef TEST_SHARED_RT
     130             :     int         tranche_id = LWLockNewTrancheId();
     131             :     dsa_area   *dsa;
     132             : #endif
     133             : 
     134           2 :     radixtree_ctx = AllocSetContextCreate(CurrentMemoryContext,
     135             :                                           "test_radix_tree",
     136             :                                           ALLOCSET_SMALL_SIZES);
     137             : 
     138             : #ifdef TEST_SHARED_RT
     139             :     LWLockRegisterTranche(tranche_id, "test_radix_tree");
     140             :     dsa = dsa_create(tranche_id);
     141             : 
     142             :     radixtree = rt_create(radixtree_ctx, dsa, tranche_id);
     143             : #else
     144           2 :     radixtree = rt_create(radixtree_ctx);
     145             : #endif
     146             : 
     147             :     /* Should not find anything in an empty tree */
     148           2 :     EXPECT_TRUE(rt_find(radixtree, 0) == NULL);
     149           2 :     EXPECT_TRUE(rt_find(radixtree, 1) == NULL);
     150           2 :     EXPECT_TRUE(rt_find(radixtree, PG_UINT64_MAX) == NULL);
     151           2 :     EXPECT_FALSE(rt_delete(radixtree, 0));
     152           2 :     EXPECT_TRUE(rt_num_entries(radixtree) == 0);
     153             : 
     154             :     /* Iterating on an empty tree should not return anything */
     155           2 :     iter = rt_begin_iterate(radixtree);
     156           2 :     EXPECT_TRUE(rt_iterate_next(iter, &key) == NULL);
     157           2 :     rt_end_iterate(iter);
     158             : 
     159           2 :     rt_free(radixtree);
     160             : 
     161             : #ifdef TEST_SHARED_RT
     162             :     dsa_detach(dsa);
     163             : #endif
     164           2 : }
     165             : 
     166             : /* Basic set, find, and delete tests */
     167             : static void
     168          60 : test_basic(rt_node_class_test_elem *test_info, int shift, bool asc)
     169             : {
     170             :     MemoryContext radixtree_ctx;
     171             :     rt_radix_tree *radixtree;
     172             :     rt_iter    *iter;
     173             :     uint64     *keys;
     174          60 :     int         children = test_info->nkeys;
     175             : #ifdef TEST_SHARED_RT
     176             :     int         tranche_id = LWLockNewTrancheId();
     177             :     dsa_area   *dsa;
     178             : #endif
     179             : 
     180          60 :     radixtree_ctx = AllocSetContextCreate(CurrentMemoryContext,
     181             :                                           "test_radix_tree",
     182             :                                           ALLOCSET_SMALL_SIZES);
     183             : 
     184             : #ifdef TEST_SHARED_RT
     185             :     LWLockRegisterTranche(tranche_id, "test_radix_tree");
     186             :     dsa = dsa_create(tranche_id);
     187             : 
     188             :     radixtree = rt_create(radixtree_ctx, dsa, tranche_id);
     189             : #else
     190          60 :     radixtree = rt_create(radixtree_ctx);
     191             : #endif
     192             : 
     193          60 :     elog(NOTICE, "testing node %s with shift %d and %s keys",
     194             :          test_info->class_name, shift, asc ? "ascending" : "descending");
     195             : 
     196          60 :     keys = palloc(sizeof(uint64) * children);
     197        4416 :     for (int i = 0; i < children; i++)
     198             :     {
     199        4356 :         if (asc)
     200        2178 :             keys[i] = (uint64) i << shift;
     201             :         else
     202        2178 :             keys[i] = (uint64) (children - 1 - i) << shift;
     203             :     }
     204             : 
     205             :     /*
     206             :      * Insert keys. Since the tree was just created, rt_set should return
     207             :      * false.
     208             :      */
     209        4416 :     for (int i = 0; i < children; i++)
     210        4356 :         EXPECT_FALSE(rt_set(radixtree, keys[i], (TestValueType *) &keys[i]));
     211             : 
     212          60 :     rt_stats(radixtree);
     213             : 
     214             :     /* look up keys */
     215        4416 :     for (int i = 0; i < children; i++)
     216             :     {
     217             :         TestValueType *value;
     218             : 
     219        4356 :         value = rt_find(radixtree, keys[i]);
     220             : 
     221             :         /* Test rt_find returns the expected value */
     222        4356 :         EXPECT_TRUE(value != NULL);
     223        4356 :         EXPECT_EQ_U64(*value, (TestValueType) keys[i]);
     224             :     }
     225             : 
     226             :     /* update keys */
     227        4416 :     for (int i = 0; i < children; i++)
     228             :     {
     229        4356 :         TestValueType update = keys[i] + 1;
     230             : 
     231             :         /* rt_set should report the key found */
     232        4356 :         EXPECT_TRUE(rt_set(radixtree, keys[i], (TestValueType *) &update));
     233             :     }
     234             : 
     235             :     /* delete and re-insert keys */
     236        4416 :     for (int i = 0; i < children; i++)
     237             :     {
     238        4356 :         EXPECT_TRUE(rt_delete(radixtree, keys[i]));
     239        4356 :         EXPECT_FALSE(rt_set(radixtree, keys[i], (TestValueType *) &keys[i]));
     240             :     }
     241             : 
     242             :     /* look up keys after deleting and re-inserting */
     243        4416 :     for (int i = 0; i < children; i++)
     244             :     {
     245             :         TestValueType *value;
     246             : 
     247        4356 :         value = rt_find(radixtree, keys[i]);
     248             : 
     249             :         /* Test that rt_find returns the expected value */
     250        4356 :         EXPECT_TRUE(value != NULL);
     251        4356 :         EXPECT_EQ_U64(*value, (TestValueType) keys[i]);
     252             :     }
     253             : 
     254             :     /* test that iteration returns the expected keys and values */
     255          60 :     iter = rt_begin_iterate(radixtree);
     256             : 
     257        4416 :     for (int i = 0; i < children; i++)
     258             :     {
     259             :         uint64      expected;
     260             :         uint64      iterkey;
     261             :         TestValueType *iterval;
     262             : 
     263             :         /* iteration is ordered by key, so adjust expected value accordingly */
     264        4356 :         if (asc)
     265        2178 :             expected = keys[i];
     266             :         else
     267        2178 :             expected = keys[children - 1 - i];
     268             : 
     269        4356 :         iterval = rt_iterate_next(iter, &iterkey);
     270             : 
     271        4356 :         EXPECT_TRUE(iterval != NULL);
     272        4356 :         EXPECT_EQ_U64(iterkey, expected);
     273        4356 :         EXPECT_EQ_U64(*iterval, expected);
     274             :     }
     275             : 
     276          60 :     rt_end_iterate(iter);
     277             : 
     278             :     /* delete all keys again */
     279        4416 :     for (int i = 0; i < children; i++)
     280        4356 :         EXPECT_TRUE(rt_delete(radixtree, keys[i]));
     281             : 
     282             :     /* test that all keys were deleted */
     283        4416 :     for (int i = 0; i < children; i++)
     284        4356 :         EXPECT_TRUE(rt_find(radixtree, keys[i]) == NULL);
     285             : 
     286          60 :     rt_stats(radixtree);
     287             : 
     288          60 :     pfree(keys);
     289          60 :     rt_free(radixtree);
     290             : 
     291             : #ifdef TEST_SHARED_RT
     292             :     dsa_detach(dsa);
     293             : #endif
     294          60 : }
     295             : 
     296             : static int
     297     3467244 : key_cmp(const void *a, const void *b)
     298             : {
     299     3467244 :     return pg_cmp_u64(*(const uint64 *) a, *(const uint64 *) b);
     300             : }
     301             : 
     302             : static void
     303           2 : test_random(void)
     304             : {
     305             :     MemoryContext radixtree_ctx;
     306             :     rt_radix_tree *radixtree;
     307             :     rt_iter    *iter;
     308             :     pg_prng_state state;
     309             : 
     310             :     /* limit memory usage by limiting the key space */
     311           2 :     uint64      filter = ((uint64) (0x07 << 24) | (0xFF << 16) | 0xFF);
     312           2 :     uint64      seed = GetCurrentTimestamp();
     313           2 :     int         num_keys = 100000;
     314             :     uint64     *keys;
     315             : #ifdef TEST_SHARED_RT
     316             :     int         tranche_id = LWLockNewTrancheId();
     317             :     dsa_area   *dsa;
     318             : #endif
     319             : 
     320           2 :     radixtree_ctx = AllocSetContextCreate(CurrentMemoryContext,
     321             :                                           "test_radix_tree",
     322             :                                           ALLOCSET_SMALL_SIZES);
     323             : 
     324             : #ifdef TEST_SHARED_RT
     325             :     LWLockRegisterTranche(tranche_id, "test_radix_tree");
     326             :     dsa = dsa_create(tranche_id);
     327             : 
     328             :     radixtree = rt_create(radixtree_ctx, dsa, tranche_id);
     329             : #else
     330           2 :     radixtree = rt_create(radixtree_ctx);
     331             : #endif
     332             : 
     333             :     /* add some random values */
     334           2 :     pg_prng_seed(&state, seed);
     335           2 :     keys = (TestValueType *) palloc(sizeof(uint64) * num_keys);
     336      200002 :     for (uint64 i = 0; i < num_keys; i++)
     337             :     {
     338      200000 :         uint64      key = pg_prng_uint64(&state) & filter;
     339      200000 :         TestValueType val = (TestValueType) key;
     340             : 
     341             :         /* save in an array */
     342      200000 :         keys[i] = key;
     343             : 
     344      200000 :         rt_set(radixtree, key, &val);
     345             :     }
     346             : 
     347           2 :     rt_stats(radixtree);
     348             : 
     349      200002 :     for (uint64 i = 0; i < num_keys; i++)
     350             :     {
     351             :         TestValueType *value;
     352             : 
     353      200000 :         value = rt_find(radixtree, keys[i]);
     354             : 
     355             :         /* Test rt_find for values just inserted */
     356      200000 :         EXPECT_TRUE(value != NULL);
     357      200000 :         EXPECT_EQ_U64(*value, keys[i]);
     358             :     }
     359             : 
     360             :     /* sort keys for iteration and absence tests */
     361           2 :     qsort(keys, num_keys, sizeof(uint64), key_cmp);
     362             : 
     363             :     /* should not find numbers in between the keys */
     364      200000 :     for (uint64 i = 0; i < num_keys - 1; i++)
     365             :     {
     366             :         TestValueType *value;
     367             : 
     368             :         /* skip duplicate and adjacent keys */
     369      199998 :         if (keys[i + 1] == keys[i] || keys[i + 1] == keys[i] + 1)
     370       49524 :             continue;
     371             : 
     372             :         /* should not find the number right after key */
     373      150474 :         value = rt_find(radixtree, keys[i] + 1);
     374      150474 :         EXPECT_TRUE(value == NULL);
     375             :     }
     376             : 
     377             :     /* should not find numbers lower than lowest key */
     378          40 :     for (uint64 key = 0; key < keys[0]; key++)
     379             :     {
     380             :         TestValueType *value;
     381             : 
     382             :         /* arbitrary stopping point */
     383          38 :         if (key > 10000)
     384           0 :             break;
     385             : 
     386          38 :         value = rt_find(radixtree, key);
     387          38 :         EXPECT_TRUE(value == NULL);
     388             :     }
     389             : 
     390             :     /* should not find numbers higher than highest key */
     391       20000 :     for (uint64 i = 1; i < 10000; i++)
     392             :     {
     393             :         TestValueType *value;
     394             : 
     395       19998 :         value = rt_find(radixtree, keys[num_keys - 1] + i);
     396       19998 :         EXPECT_TRUE(value == NULL);
     397             :     }
     398             : 
     399             :     /* test that iteration returns the expected keys and values */
     400           2 :     iter = rt_begin_iterate(radixtree);
     401             : 
     402      200002 :     for (int i = 0; i < num_keys; i++)
     403             :     {
     404             :         uint64      expected;
     405             :         uint64      iterkey;
     406             :         TestValueType *iterval;
     407             : 
     408             :         /* skip duplicate keys */
     409      200000 :         if (i < num_keys - 1 && keys[i + 1] == keys[i])
     410       18002 :             continue;
     411             : 
     412      181998 :         expected = keys[i];
     413      181998 :         iterval = rt_iterate_next(iter, &iterkey);
     414             : 
     415      181998 :         EXPECT_TRUE(iterval != NULL);
     416      181998 :         EXPECT_EQ_U64(iterkey, expected);
     417      181998 :         EXPECT_EQ_U64(*iterval, expected);
     418             :     }
     419             : 
     420           2 :     rt_end_iterate(iter);
     421             : 
     422             :     /* reset random number generator for deletion */
     423           2 :     pg_prng_seed(&state, seed);
     424             : 
     425             :     /* delete in original random order */
     426      200002 :     for (uint64 i = 0; i < num_keys; i++)
     427             :     {
     428      200000 :         uint64      key = pg_prng_uint64(&state) & filter;
     429             : 
     430      200000 :         rt_delete(radixtree, key);
     431             :     }
     432             : 
     433           2 :     EXPECT_TRUE(rt_num_entries(radixtree) == 0);
     434             : 
     435           2 :     pfree(keys);
     436           2 :     rt_free(radixtree);
     437             : 
     438             : #ifdef TEST_SHARED_RT
     439             :     dsa_detach(dsa);
     440             : #endif
     441           2 : }
     442             : 
     443             : Datum
     444           2 : test_radixtree(PG_FUNCTION_ARGS)
     445             : {
     446             :     /* borrowed from RT_MAX_SHIFT */
     447           2 :     const int   max_shift = (sizeof(uint64) - 1) * BITS_PER_BYTE;
     448             : 
     449           2 :     test_empty();
     450             : 
     451          12 :     for (int i = 0; i < lengthof(rt_node_class_tests); i++)
     452             :     {
     453          10 :         rt_node_class_test_elem *test_info = &(rt_node_class_tests[i]);
     454             : 
     455             :         /* a tree with one level, i.e. a single node under the root node */
     456          10 :         test_basic(test_info, 0, true);
     457          10 :         test_basic(test_info, 0, false);
     458             : 
     459             :         /* a tree with two levels */
     460          10 :         test_basic(test_info, 8, true);
     461          10 :         test_basic(test_info, 8, false);
     462             : 
     463             :         /* a tree with the maximum number of levels */
     464          10 :         test_basic(test_info, max_shift, true);
     465          10 :         test_basic(test_info, max_shift, false);
     466             :     }
     467             : 
     468           2 :     test_random();
     469             : 
     470           2 :     PG_RETURN_VOID();
     471             : }

Generated by: LCOV version 1.14