From e83b0e7a3d62b14a14c57ab6ce5996efcde38af3 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
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

