From e83b0e7a3d62b14a14c57ab6ce5996efcde38af3 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Wed, 13 Aug 2025 15:40:31 -0400 Subject: [PATCH v3] Add a module that tests Bitmapset Basic tests for Bitmapset to ensure functionality and help prevent unintentional breaking changes in the future. --- src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + src/test/modules/test_bitmapset/.gitignore | 4 + src/test/modules/test_bitmapset/Makefile | 23 + .../expected/test_bitmapset.out | 46 ++ src/test/modules/test_bitmapset/meson.build | 33 + .../test_bitmapset/sql/test_bitmapset.sql | 32 + .../test_bitmapset/test_bitmapset--1.0.sql | 38 + .../modules/test_bitmapset/test_bitmapset.c | 728 ++++++++++++++++++ .../test_bitmapset/test_bitmapset.control | 4 + 10 files changed, 910 insertions(+) create mode 100644 src/test/modules/test_bitmapset/.gitignore create mode 100644 src/test/modules/test_bitmapset/Makefile create mode 100644 src/test/modules/test_bitmapset/expected/test_bitmapset.out create mode 100644 src/test/modules/test_bitmapset/meson.build create mode 100644 src/test/modules/test_bitmapset/sql/test_bitmapset.sql create mode 100644 src/test/modules/test_bitmapset/test_bitmapset--1.0.sql create mode 100644 src/test/modules/test_bitmapset/test_bitmapset.c create mode 100644 src/test/modules/test_bitmapset/test_bitmapset.control diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 903a8ac151a..94071ec0e16 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -16,6 +16,7 @@ SUBDIRS = \ spgist_name_ops \ test_aio \ test_binaryheap \ + test_bitmapset \ test_bloomfilter \ test_copy_callbacks \ test_custom_rmgrs \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 93be0f57289..d8f5c9c7494 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -15,6 +15,7 @@ subdir('spgist_name_ops') subdir('ssl_passphrase_callback') subdir('test_aio') subdir('test_binaryheap') +subdir('test_bitmapset') subdir('test_bloomfilter') subdir('test_copy_callbacks') subdir('test_custom_rmgrs') diff --git a/src/test/modules/test_bitmapset/.gitignore b/src/test/modules/test_bitmapset/.gitignore new file mode 100644 index 00000000000..5dcb3ff9723 --- /dev/null +++ b/src/test/modules/test_bitmapset/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_bitmapset/Makefile b/src/test/modules/test_bitmapset/Makefile new file mode 100644 index 00000000000..67199d40d73 --- /dev/null +++ b/src/test/modules/test_bitmapset/Makefile @@ -0,0 +1,23 @@ +# src/test/modules/test_bitmapset/Makefile + +MODULE_big = test_bitmapset +OBJS = \ + $(WIN32RES) \ + test_bitmapset.o +PGFILEDESC = "test_bitmapset - test code for src/include/nodes/bitmapset.h" + +EXTENSION = test_bitmapset +DATA = test_bitmapset--1.0.sql + +REGRESS = test_bitmapset + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_bitmapset +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_bitmapset/expected/test_bitmapset.out b/src/test/modules/test_bitmapset/expected/test_bitmapset.out new file mode 100644 index 00000000000..9cc2f9f9552 --- /dev/null +++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out @@ -0,0 +1,46 @@ +CREATE EXTENSION test_bitmapset; +-- +-- All the logic is in the test_bitmapset() function. It will throw +-- an error if something fails. +-- +-- Run the main comprehensive tests +SELECT test_bitmapset(); +NOTICE: starting bitmapset tests +NOTICE: testing empty bitmapset operations +NOTICE: testing single element bitmapset operations +NOTICE: testing multiple elements and set operations +NOTICE: testing prev_member edge cases +NOTICE: testing edge cases and boundary conditions +NOTICE: testing iteration functions +NOTICE: testing comprehensive set operations +NOTICE: testing randomized operations +NOTICE: randomized testing completed successfully +NOTICE: testing advanced functions +NOTICE: all bitmapset tests passed + test_bitmapset +---------------- + +(1 row) + +-- Test error conditions - these should all fail with specific error messages +-- Test bms_make_singleton with negative member +SELECT test_bms_make_singleton_negative(); -- This should ERROR +ERROR: negative bitmapset member not allowed +-- Test bms_is_member with negative member +SELECT test_bms_is_member_negative(); -- This should ERROR +ERROR: negative bitmapset member not allowed +-- Test bms_add_member with negative member +SELECT test_bms_add_member_negative(); -- This should ERROR +ERROR: negative bitmapset member not allowed +-- Test bms_del_member with negative member +SELECT test_bms_del_member_negative(); -- This should ERROR +ERROR: negative bitmapset member not allowed +-- Test bms_add_range with negative lower bound +SELECT test_bms_add_range_negative(); -- This should ERROR +ERROR: negative bitmapset member not allowed +-- Test bms_singleton_member on empty set +SELECT test_bms_singleton_member_empty(); -- This should ERROR +ERROR: bitmapset is empty +-- Test bms_singleton_member on multiple member set +SELECT test_bms_singleton_member_multiple(); -- This should ERROR +ERROR: bitmapset has multiple members diff --git a/src/test/modules/test_bitmapset/meson.build b/src/test/modules/test_bitmapset/meson.build new file mode 100644 index 00000000000..848f7a44eb9 --- /dev/null +++ b/src/test/modules/test_bitmapset/meson.build @@ -0,0 +1,33 @@ +# Copyright (c) 2024-2025, PostgreSQL Global Development Group + +test_bitmapset_sources = files( + 'test_bitmapset.c', +) + +if host_system == 'windows' + test_bitmapset_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_bitmapset', + '--FILEDESC', 'test_bitmapset - test code for src/include/nodes/bitmapset.h',]) +endif + +test_bitmapset = shared_module('test_bitmapset', + test_bitmapset_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_bitmapset + +test_install_data += files( + 'test_bitmapset.control', + 'test_bitmapset--1.0.sql', +) + +tests += { + 'name': 'test_bitmapset', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_bitmapset', + ], + }, +} diff --git a/src/test/modules/test_bitmapset/sql/test_bitmapset.sql b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql new file mode 100644 index 00000000000..15b946b1661 --- /dev/null +++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql @@ -0,0 +1,32 @@ +CREATE EXTENSION test_bitmapset; + +-- +-- All the logic is in the test_bitmapset() function. It will throw +-- an error if something fails. +-- + +-- Run the main comprehensive tests +SELECT test_bitmapset(); + +-- Test error conditions - these should all fail with specific error messages + +-- Test bms_make_singleton with negative member +SELECT test_bms_make_singleton_negative(); -- This should ERROR + +-- Test bms_is_member with negative member +SELECT test_bms_is_member_negative(); -- This should ERROR + +-- Test bms_add_member with negative member +SELECT test_bms_add_member_negative(); -- This should ERROR + +-- Test bms_del_member with negative member +SELECT test_bms_del_member_negative(); -- This should ERROR + +-- Test bms_add_range with negative lower bound +SELECT test_bms_add_range_negative(); -- This should ERROR + +-- Test bms_singleton_member on empty set +SELECT test_bms_singleton_member_empty(); -- This should ERROR + +-- Test bms_singleton_member on multiple member set +SELECT test_bms_singleton_member_multiple(); -- This should ERROR diff --git a/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql new file mode 100644 index 00000000000..9f72c1ce2d8 --- /dev/null +++ b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql @@ -0,0 +1,38 @@ +/* src/test/modules/test_bitmapset/test_bitmapset--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_bitmapset" to load this file. \quit + +-- Main test function +CREATE FUNCTION test_bitmapset() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +-- Error condition testing functions +CREATE FUNCTION test_bms_make_singleton_negative() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_is_member_negative() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_add_member_negative() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_del_member_negative() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_add_range_negative() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_singleton_member_empty() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_singleton_member_multiple() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_bitmapset/test_bitmapset.c b/src/test/modules/test_bitmapset/test_bitmapset.c new file mode 100644 index 00000000000..3a5469bc6ae --- /dev/null +++ b/src/test/modules/test_bitmapset/test_bitmapset.c @@ -0,0 +1,728 @@ +/* + * test_bitmapset.c + * Test module for bitmapset data structure + * + * This module tests the bitmapset implementation in PostgreSQL, + * covering all public API functions, edge cases, and memory usage. + * + * src/test/modules/test_bitmapset/test_bitmapset.c + */ + +#include "postgres.h" + +#include "common/pg_prng.h" +#include "utils/timestamp.h" +#include "fmgr.h" +#include "nodes/pg_list.h" +#include "nodes/bitmapset.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(test_bitmapset); +PG_FUNCTION_INFO_V1(test_bms_make_singleton_negative); +PG_FUNCTION_INFO_V1(test_bms_is_member_negative); +PG_FUNCTION_INFO_V1(test_bms_add_member_negative); +PG_FUNCTION_INFO_V1(test_bms_del_member_negative); +PG_FUNCTION_INFO_V1(test_bms_add_range_negative); +PG_FUNCTION_INFO_V1(test_bms_singleton_member_empty); +PG_FUNCTION_INFO_V1(test_bms_singleton_member_multiple); + +#define EXPECT_TRUE(expr) \ + do { \ + if (!(expr)) \ + elog(ERROR, \ + "%s was unexpectedly false in file \"%s\" line %u", \ + #expr, __FILE__, __LINE__); \ + } while (0) + +#define EXPECT_FALSE(expr) \ + do { \ + if (expr) \ + elog(ERROR, \ + "%s was unexpectedly true in file \"%s\" line %u", \ + #expr, __FILE__, __LINE__); \ + } while (0) + +#define EXPECT_EQ_INT(result_expr, expected_expr) \ + do { \ + int _result = (result_expr); \ + int _expected = (expected_expr); \ + if (_result != _expected) \ + elog(ERROR, \ + "%s yielded %d, expected %d (%s) in file \"%s\" line %u", \ + #result_expr, _result, _expected, #expected_expr, __FILE__, __LINE__); \ + } while (0) + +#define EXPECT_NULL(expr) \ + do { \ + if ((expr) != NULL) \ + elog(ERROR, \ + "%s was unexpectedly non-NULL in file \"%s\" line %u", \ + #expr, __FILE__, __LINE__); \ + } while (0) + +#define EXPECT_NOT_NULL(expr) \ + do { \ + if ((expr) == NULL) \ + elog(ERROR, \ + "%s was unexpectedly NULL in file \"%s\" line %u", \ + #expr, __FILE__, __LINE__); \ + } while (0) + +#ifdef DEBUG + +static void +elog_bitmapset(int elevel, const char *label, const Bitmapset *bms) +{ + StringInfoData buf; + int member; + bool first = true; + + initStringInfo(&buf); + + if (label) + appendStringInfo(&buf, "%s: ", label); + + if (!bms_is_empty(bms)) + { + appendStringInfoChar(&buf, '{'); + member = -1; + while ((member = bms_next_member(bms, member)) >= 0) + { + if (!first) + appendStringInfoString(&buf, ", "); + appendStringInfo(&buf, "%d", member); + first = false; + } + appendStringInfoChar(&buf, '}'); + } + else + appendStringInfoString(&buf, "EMPTY"); + + elog(elevel, "%s", buf.data); + pfree(buf.data); +} + +#endif + +Datum +test_bms_make_singleton_negative(PG_FUNCTION_ARGS) +{ + /* This should throw an error for negative member */ + bms_make_singleton(-1); + PG_RETURN_VOID(); +} + +Datum +test_bms_is_member_negative(PG_FUNCTION_ARGS) +{ + /* This should throw an error for negative member */ + bms_is_member(-5, NULL); + PG_RETURN_VOID(); +} + +Datum +test_bms_add_member_negative(PG_FUNCTION_ARGS) +{ + /* This should throw an error for negative member */ + bms_add_member(NULL, -10); + PG_RETURN_VOID(); +} + +Datum +test_bms_del_member_negative(PG_FUNCTION_ARGS) +{ + /* This should throw an error for negative member */ + bms_del_member(NULL, -20); + PG_RETURN_VOID(); +} + +Datum +test_bms_add_range_negative(PG_FUNCTION_ARGS) +{ + /* This should throw an error for negative lower bound */ + bms_add_range(NULL, -5, 10); + PG_RETURN_VOID(); +} + +Datum +test_bms_singleton_member_empty(PG_FUNCTION_ARGS) +{ + /* This should throw an error for empty set */ + bms_singleton_member(NULL); + PG_RETURN_VOID(); +} + +Datum +test_bms_singleton_member_multiple(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + + /* Create a set with multiple members */ + bms = bms_add_member(bms, 1); + bms = bms_add_member(bms, 2); + + /* This should throw an error for multiple members */ + bms_singleton_member(bms); + + bms_free(bms); + PG_RETURN_VOID(); +} + + +/* Test hash functions and advanced operations */ +static void +test_advanced_functions(void) +{ + Bitmapset *bms1 = NULL; + Bitmapset *bms2 = NULL; + Bitmapset *result = NULL; + List *int_list = NIL; + uint32 hash1, + hash2; + Bitmapset *ptr1, + *ptr2; + + elog(NOTICE, "testing advanced functions"); + + /* Test hash functions */ + bms1 = bms_add_member(bms1, 10); + bms1 = bms_add_member(bms1, 20); + bms2 = bms_add_member(bms2, 10); + bms2 = bms_add_member(bms2, 20); + + hash1 = bms_hash_value(bms1); + hash2 = bms_hash_value(bms2); + EXPECT_EQ_INT(hash1, hash2); + + /* Test bitmap_hash and bitmap_match */ + ptr1 = bms1; + ptr2 = bms2; + EXPECT_EQ_INT(bitmap_hash(&ptr1, sizeof(Bitmapset *)), + bitmap_hash(&ptr2, sizeof(Bitmapset *))); + EXPECT_EQ_INT(bitmap_match(&ptr1, &ptr2, sizeof(Bitmapset *)), 0); + + /* Test bms_overlap_list */ + int_list = lappend_int(int_list, 5); + int_list = lappend_int(int_list, 10); + int_list = lappend_int(int_list, 30); + + EXPECT_TRUE(bms_overlap_list(bms1, int_list)); + EXPECT_FALSE(bms_overlap_list(NULL, int_list)); + EXPECT_FALSE(bms_overlap_list(bms1, NIL)); + + /* Test bms_add_range */ + result = bms_add_range(NULL, 5, 8); + EXPECT_EQ_INT(bms_num_members(result), 4); /* 5, 6, 7, 8 */ + for (int i = 5; i <= 8; i++) + EXPECT_TRUE(bms_is_member(i, result)); + bms_free(result); + + /* Test empty range */ + result = bms_add_range(NULL, 10, 5); + EXPECT_NULL(result); + + /* Test recycling operations */ + result = bms_add_members(bms1, bms2); + EXPECT_TRUE(bms1 == result); /* bms1 is recycled */ + EXPECT_EQ_INT(bms_num_members(result), 2); + + result = bms_replace_members(result, bms2); + EXPECT_TRUE(bms_equal(result, bms2)); + + bms_free(result); /* which is bms1 */ + bms_free(bms2); + list_free(int_list); +} + +/* Test prev_member edge cases */ +static void +test_prev_member_edge_cases(void) +{ + Bitmapset *bms = NULL; + + elog(NOTICE, "testing prev_member edge cases"); + + /* Test prev_member with -1 on empty set */ + EXPECT_EQ_INT(bms_prev_member(bms, -1), -2); + + /* Test prev_member with -1 on non-empty set (find highest) */ + bms = bms_add_member(bms, 100); + EXPECT_EQ_INT(bms_prev_member(bms, -1), 100); + + bms_free(bms); +} + +/* Randomized testing similar to test_radixtree.c */ +static void +test_random_operations(void) +{ + Bitmapset *bms1 = NULL; + Bitmapset *bms2 = NULL; + Bitmapset *result = NULL; + pg_prng_state state; + uint64 seed = GetCurrentTimestamp(); + int num_ops = 5000; + int max_range = 2000; + int *members; + int num_members = 0; + + elog(NOTICE, "testing randomized operations"); + + pg_prng_seed(&state, seed); + members = palloc(sizeof(int) * num_ops); + + /* Phase 1: Random insertions */ + for (int i = 0; i < num_ops / 2; i++) + { + int member = pg_prng_uint32(&state) % max_range; + + if (!bms_is_member(member, bms1)) + { + members[num_members++] = member; + bms1 = bms_add_member(bms1, member); + } + } + + /* Phase 2: Random set operations */ + for (int i = 0; i < num_ops / 4; i++) + { + int member = pg_prng_uint32(&state) % max_range; + + bms2 = bms_add_member(bms2, member); + } + + /* Test union */ + result = bms_union(bms1, bms2); + EXPECT_NOT_NULL(result); + + /* Verify union contains all members from first set */ + for (int i = 0; i < num_members; i++) + { + if (!bms_is_member(members[i], result)) + elog(ERROR, "union missing member %d", members[i]); + } + bms_free(result); + + /* Test intersection */ + result = bms_intersect(bms1, bms2); + if (result != NULL) + { + int member = -1; + + while ((member = bms_next_member(result, member)) >= 0) + { + if (!bms_is_member(member, bms1) || !bms_is_member(member, bms2)) + elog(ERROR, "intersection contains invalid member %d", member); + } + bms_free(result); + } + + /* Phase 3: Test range operations */ + result = NULL; + for (int i = 0; i < 5; i++) + { + int lower = pg_prng_uint32(&state) % 100; + int upper = lower + (pg_prng_uint32(&state) % 20); + + result = bms_add_range(result, lower, upper); + } + if (result != NULL) + { + EXPECT_TRUE(bms_num_members(result) > 0); + bms_free(result); + } + + /* Cleanup */ + pfree(members); + bms_free(bms1); + bms_free(bms2); + + elog(NOTICE, "randomized testing completed successfully"); +} + +/* Test empty bitmapset operations */ +static void +test_empty_bitmapset(void) +{ + Bitmapset *result, + *bms = NULL; + + elog(NOTICE, "testing empty bitmapset operations"); + + /* Test operations on NULL bitmapset */ + EXPECT_TRUE(bms_is_empty(bms)); + EXPECT_EQ_INT(bms_num_members(bms), 0); + EXPECT_FALSE(bms_is_member(0, bms)); + EXPECT_FALSE(bms_is_member(1, bms)); + EXPECT_FALSE(bms_is_member(100, bms)); + EXPECT_EQ_INT(bms_next_member(bms, -1), -2); + EXPECT_EQ_INT(bms_prev_member(bms, -1), -2); + + /* Test hash of empty set */ + EXPECT_EQ_INT(bms_hash_value(bms), 0); + + /* Test comparison with empty sets */ + EXPECT_EQ_INT(bms_compare(bms, NULL), 0); + + /* Test copy of empty set */ + result = bms_copy(bms); + EXPECT_NULL(result); + + /* Test union with empty set */ + result = bms_union(bms, NULL); + EXPECT_NULL(result); + + /* Test intersection with empty set */ + result = bms_intersect(bms, NULL); + EXPECT_NULL(result); + + /* Test difference with empty set */ + result = bms_difference(bms, NULL); + EXPECT_NULL(result); + + /* Test equal comparison */ + EXPECT_TRUE(bms_equal(bms, NULL)); + EXPECT_TRUE(bms_equal(NULL, bms)); + + /* Test subset operations */ + EXPECT_TRUE(bms_is_subset(bms, NULL)); + EXPECT_TRUE(bms_is_subset(NULL, bms)); + + /* Test overlap */ + EXPECT_FALSE(bms_overlap(bms, NULL)); + EXPECT_FALSE(bms_overlap(NULL, bms)); +} + +/* Test single element bitmapset operations */ +static void +test_single_element(void) +{ + Bitmapset *result, + *bms = NULL; + int test_member = 42; + + elog(NOTICE, "testing single element bitmapset operations"); + + /* Add single element */ + bms = bms_add_member(bms, test_member); + EXPECT_NOT_NULL(bms); + EXPECT_FALSE(bms_is_empty(bms)); + EXPECT_EQ_INT(bms_num_members(bms), 1); + EXPECT_TRUE(bms_is_member(test_member, bms)); + EXPECT_FALSE(bms_is_member(test_member - 1, bms)); + EXPECT_FALSE(bms_is_member(test_member + 1, bms)); + + /* Test iteration */ + EXPECT_EQ_INT(bms_next_member(bms, -1), test_member); + EXPECT_EQ_INT(bms_next_member(bms, test_member), -2); + EXPECT_EQ_INT(bms_prev_member(bms, test_member + 1), test_member); + EXPECT_EQ_INT(bms_prev_member(bms, test_member), -2); + + /* Test copy */ + result = bms_copy(bms); + EXPECT_NOT_NULL(result); + EXPECT_TRUE(bms_equal(bms, result)); + EXPECT_TRUE(bms_is_member(test_member, result)); + + /* Test member index */ + EXPECT_EQ_INT(bms_member_index(bms, 42), 0); + EXPECT_EQ_INT(bms_member_index(bms, 43), -1); + + /* Test comparison */ + EXPECT_EQ_INT(bms_compare(bms, result), 0); + + /* Test remove member */ + result = bms_del_member(result, test_member); + EXPECT_NULL(result); + + /* Test comparison again */ + EXPECT_EQ_INT(bms_compare(bms, result), 1); + + /* Test remove non-existent member */ + result = bms_copy(bms); + EXPECT_NOT_NULL(result); + result = bms_del_member(result, test_member + 1); + EXPECT_NOT_NULL(result); + EXPECT_TRUE(bms_equal(bms, result)); + + bms_free(bms); + bms_free(result); +} + +/* Test multiple elements and set operations */ +static void +test_multiple_elements(void) +{ + Bitmapset *bms1 = NULL; + Bitmapset *bms2 = NULL; + Bitmapset *result = NULL; + int elements1[] = {1, 5, 10, 15, 20, 100}; + int elements2[] = {3, 5, 12, 15, 25, 200}; + int num_elements1 = sizeof(elements1) / sizeof(elements1[0]); + int num_elements2 = sizeof(elements2) / sizeof(elements2[0]); + + elog(NOTICE, "testing multiple elements and set operations"); + + /* Build first set */ + for (int i = 0; i < num_elements1; i++) + bms1 = bms_add_member(bms1, elements1[i]); + + EXPECT_EQ_INT(bms_num_members(bms1), num_elements1); + + /* Build second set */ + for (int i = 0; i < num_elements2; i++) + bms2 = bms_add_member(bms2, elements2[i]); + + EXPECT_EQ_INT(bms_num_members(bms2), num_elements2); + + /* Test membership */ + for (int i = 0; i < num_elements1; i++) + EXPECT_TRUE(bms_is_member(elements1[i], bms1)); + + for (int i = 0; i < num_elements2; i++) + EXPECT_TRUE(bms_is_member(elements2[i], bms2)); + + /* Test union */ + result = bms_union(bms1, bms2); + EXPECT_NOT_NULL(result); + for (int i = 0; i < num_elements1; i++) + EXPECT_TRUE(bms_is_member(elements1[i], result)); + for (int i = 0; i < num_elements2; i++) + EXPECT_TRUE(bms_is_member(elements2[i], result)); + EXPECT_EQ_INT(bms_num_members(result), 10); /* 1,3,5,10,12,15,20,25,100,200 */ + bms_free(result); + + /* Test intersection */ + result = bms_intersect(bms1, bms2); + EXPECT_NOT_NULL(result); + EXPECT_TRUE(bms_is_member(5, result)); + EXPECT_TRUE(bms_is_member(15, result)); + EXPECT_EQ_INT(bms_num_members(result), 2); /* 5, 15 */ + bms_free(result); + + /* Test difference */ + result = bms_difference(bms1, bms2); + EXPECT_NOT_NULL(result); + EXPECT_TRUE(bms_is_member(1, result)); + EXPECT_TRUE(bms_is_member(10, result)); + EXPECT_TRUE(bms_is_member(20, result)); + EXPECT_TRUE(bms_is_member(100, result)); + EXPECT_FALSE(bms_is_member(5, result)); + EXPECT_FALSE(bms_is_member(15, result)); + EXPECT_EQ_INT(bms_num_members(result), 4); /* 1, 10, 20, 100 */ + bms_free(result); + + /* Test overlap */ + EXPECT_TRUE(bms_overlap(bms1, bms2)); + + /* Test subset operations */ + result = NULL; + result = bms_add_member(result, 5); + result = bms_add_member(result, 15); + EXPECT_TRUE(bms_is_subset(result, bms1)); + EXPECT_TRUE(bms_is_subset(result, bms2)); + EXPECT_FALSE(bms_is_subset(bms1, result)); + bms_free(result); + + /* Test subset comparison */ + bms2 = bms_add_member(NULL, 5); + EXPECT_EQ_INT(bms_subset_compare(bms2, bms1), BMS_SUBSET1); + EXPECT_EQ_INT(bms_subset_compare(bms1, bms2), BMS_SUBSET2); + + result = bms_add_member(NULL, 999); + EXPECT_EQ_INT(bms_subset_compare(bms2, result), BMS_DIFFERENT); + bms_free(result); + + bms_free(bms1); + bms_free(bms2); +} + +/* Test edge cases and boundary conditions */ +static void +test_edge_cases(void) +{ + int large_element = 10000; + int count; + int member; + Bitmapset *result; + Bitmapset *bms = NULL; + + elog(NOTICE, "testing edge cases and boundary conditions"); + + /* Test element 0 */ + bms = bms_add_member(bms, 0); + EXPECT_TRUE(bms_is_member(0, bms)); + EXPECT_EQ_INT(bms_num_members(bms), 1); + EXPECT_EQ_INT(bms_next_member(bms, -1), 0); + bms_free(bms); + bms = NULL; + + /* Test large element numbers */ + bms = bms_add_member(bms, large_element); + EXPECT_TRUE(bms_is_member(large_element, bms)); + EXPECT_EQ_INT(bms_num_members(bms), 1); + bms_free(bms); + bms = NULL; + + /* Test adding same element multiple times */ + bms = bms_add_member(bms, 42); + bms = bms_add_member(bms, 42); + EXPECT_EQ_INT(bms_num_members(bms), 1); + EXPECT_TRUE(bms_is_member(42, bms)); + bms_free(bms); + bms = NULL; + + /* Test removing from single-element set */ + bms = bms_add_member(bms, 99); + result = bms_del_member(bms, 99); + EXPECT_NULL(result); + bms_free(bms); + bms = NULL; + + /* Test dense range */ + for (int i = 0; i < 64; i++) + bms = bms_add_member(bms, i); + EXPECT_EQ_INT(bms_num_members(bms), 64); + + /* Test iteration over dense range */ + count = 0; + member = -1; + while ((member = bms_next_member(bms, member)) >= 0) + { + EXPECT_EQ_INT(member, count); + count++; + } + EXPECT_EQ_INT(count, 64); + + bms_free(bms); +} + +/* Test iterator functions */ +static void +test_iteration(void) +{ + Bitmapset *bms = NULL; + int member; + int index; + int elements[] = {2, 7, 15, 31, 63, 127, 255, 511, 1023}; + int num_elements = sizeof(elements) / sizeof(elements[0]); + + elog(NOTICE, "testing iteration functions"); + + /* Build test set */ + for (int i = 0; i < num_elements; i++) + bms = bms_add_member(bms, elements[i]); + + /* Test forward iteration */ + member = -1; + index = 0; + while ((member = bms_next_member(bms, member)) >= 0) + { + EXPECT_EQ_INT(member, elements[index]); + index++; + } + EXPECT_EQ_INT(index, num_elements); + + /* Test backward iteration */ + member = bms->nwords * BITS_PER_BITMAPWORD; + index = num_elements - 1; + while ((member = bms_prev_member(bms, member)) >= 0) + { + EXPECT_EQ_INT(member, elements[index]); + index--; + } + EXPECT_EQ_INT(index, -1); + + /* Test iteration bounds */ + EXPECT_EQ_INT(bms_next_member(bms, 1023), -2); + EXPECT_EQ_INT(bms_prev_member(bms, 2), -2); + + bms_free(bms); +} + +/* Test set operations with various combinations */ +static void +test_set_operations(void) +{ + Bitmapset *empty = NULL; + Bitmapset *single = NULL; + Bitmapset *multi = NULL; + Bitmapset *result = NULL; + Bitmapset *single_copy = NULL; + + elog(NOTICE, "testing comprehensive set operations"); + + single = bms_make_singleton(10); + multi = bms_add_range(multi, 9, 11); + + result = bms_union(single, multi); + EXPECT_EQ_INT(bms_num_members(single), 1); + EXPECT_EQ_INT(bms_num_members(multi), 3); + EXPECT_EQ_INT(bms_num_members(result), 3); + EXPECT_TRUE(bms_is_member(9, result)); + EXPECT_TRUE(bms_is_member(10, result)); + EXPECT_TRUE(bms_is_member(11, result)); + bms_free(result); + + /* Union operations */ + result = bms_union(empty, single); + EXPECT_TRUE(bms_equal(result, single)); + bms_free(result); + + result = bms_union(single, empty); + EXPECT_TRUE(bms_equal(result, single)); + bms_free(result); + + /* Intersection operations */ + result = bms_intersect(empty, single); + EXPECT_NULL(result); + + result = bms_intersect(single, multi); + EXPECT_EQ_INT(bms_num_members(result), 1); + EXPECT_TRUE(bms_is_member(10, result)); + bms_free(result); + + /* Difference operations */ + result = bms_difference(single, multi); + EXPECT_NULL(result); + + result = bms_difference(multi, single); + EXPECT_EQ_INT(bms_num_members(result), 2); + EXPECT_TRUE(bms_is_member(9, result)); + EXPECT_FALSE(bms_is_member(10, result)); + EXPECT_TRUE(bms_is_member(11, result)); + bms_free(result); + + /* Equality tests */ + EXPECT_FALSE(bms_equal(single, multi)); + EXPECT_TRUE(bms_equal(empty, NULL)); + + single_copy = bms_copy(single); + EXPECT_TRUE(bms_equal(single, single_copy)); + bms_free(single_copy); + + bms_free(single); + bms_free(multi); +} + +/* Main test function */ +Datum +test_bitmapset(PG_FUNCTION_ARGS) +{ + elog(NOTICE, "starting bitmapset tests"); + + test_empty_bitmapset(); + test_single_element(); + test_multiple_elements(); + test_prev_member_edge_cases(); + test_edge_cases(); + test_iteration(); + test_set_operations(); + test_random_operations(); + test_advanced_functions(); + + elog(NOTICE, "all bitmapset tests passed"); + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_bitmapset/test_bitmapset.control b/src/test/modules/test_bitmapset/test_bitmapset.control new file mode 100644 index 00000000000..8d02ec8bf0a --- /dev/null +++ b/src/test/modules/test_bitmapset/test_bitmapset.control @@ -0,0 +1,4 @@ +comment = 'Test code for Bitmapset' +default_version = '1.0' +module_pathname = '$libdir/test_bitmapset' +relocatable = true -- 2.49.0