mirror of
https://github.com/postgres/postgres.git
synced 2025-07-27 12:41:57 +03:00
Add a test harness for the binary heap code.
binaryheap is heavily used and already has decent test coverage, but it lacks dedicated tests for its correctness. This commit changes that. Author: Aleksander Alekseev <aleksander@tigerdata.com> Discussion: https://postgr.es/m/CAJ7c6TMwp%2Bmb8MMoi%3DSMVMso2hYecoVu2Pwf2EOkesq0MiSKxw%40mail.gmail.com
This commit is contained in:
@ -15,6 +15,7 @@ SUBDIRS = \
|
|||||||
plsample \
|
plsample \
|
||||||
spgist_name_ops \
|
spgist_name_ops \
|
||||||
test_aio \
|
test_aio \
|
||||||
|
test_binaryheap \
|
||||||
test_bloomfilter \
|
test_bloomfilter \
|
||||||
test_copy_callbacks \
|
test_copy_callbacks \
|
||||||
test_custom_rmgrs \
|
test_custom_rmgrs \
|
||||||
|
@ -14,6 +14,7 @@ subdir('plsample')
|
|||||||
subdir('spgist_name_ops')
|
subdir('spgist_name_ops')
|
||||||
subdir('ssl_passphrase_callback')
|
subdir('ssl_passphrase_callback')
|
||||||
subdir('test_aio')
|
subdir('test_aio')
|
||||||
|
subdir('test_binaryheap')
|
||||||
subdir('test_bloomfilter')
|
subdir('test_bloomfilter')
|
||||||
subdir('test_copy_callbacks')
|
subdir('test_copy_callbacks')
|
||||||
subdir('test_custom_rmgrs')
|
subdir('test_custom_rmgrs')
|
||||||
|
4
src/test/modules/test_binaryheap/.gitignore
vendored
Normal file
4
src/test/modules/test_binaryheap/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Generated subdirectories
|
||||||
|
/log/
|
||||||
|
/results/
|
||||||
|
/tmp_check/
|
24
src/test/modules/test_binaryheap/Makefile
Normal file
24
src/test/modules/test_binaryheap/Makefile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# src/test/modules/test_binaryheap/Makefile
|
||||||
|
|
||||||
|
MODULE_big = test_binaryheap
|
||||||
|
OBJS = \
|
||||||
|
$(WIN32RES) \
|
||||||
|
test_binaryheap.o
|
||||||
|
|
||||||
|
PGFILEDESC = "test_binaryheap - test code for binaryheap"
|
||||||
|
|
||||||
|
EXTENSION = test_binaryheap
|
||||||
|
DATA = test_binaryheap--1.0.sql
|
||||||
|
|
||||||
|
REGRESS = test_binaryheap
|
||||||
|
|
||||||
|
ifdef USE_PGXS
|
||||||
|
PG_CONFIG = pg_config
|
||||||
|
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||||
|
include $(PGXS)
|
||||||
|
else
|
||||||
|
subdir = src/test/modules/test_binaryheap
|
||||||
|
top_builddir = ../../../..
|
||||||
|
include $(top_builddir)/src/Makefile.global
|
||||||
|
include $(top_srcdir)/contrib/contrib-global.mk
|
||||||
|
endif
|
@ -0,0 +1,12 @@
|
|||||||
|
CREATE EXTENSION test_binaryheap;
|
||||||
|
--
|
||||||
|
-- These tests don't produce any interesting output. We're checking that
|
||||||
|
-- the operations complete without crashing or hanging and that none of their
|
||||||
|
-- internal sanity tests fail.
|
||||||
|
--
|
||||||
|
SELECT test_binaryheap();
|
||||||
|
test_binaryheap
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
33
src/test/modules/test_binaryheap/meson.build
Normal file
33
src/test/modules/test_binaryheap/meson.build
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (c) 2025, PostgreSQL Global Development Group
|
||||||
|
|
||||||
|
test_binaryheap_sources = files(
|
||||||
|
'test_binaryheap.c',
|
||||||
|
)
|
||||||
|
|
||||||
|
if host_system == 'windows'
|
||||||
|
test_binaryheap_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
|
||||||
|
'--NAME', 'test_binaryheap',
|
||||||
|
'--FILEDESC', 'test_binaryheap - test code for binaryheap',])
|
||||||
|
endif
|
||||||
|
|
||||||
|
test_binaryheap = shared_module('test_binaryheap',
|
||||||
|
test_binaryheap_sources,
|
||||||
|
kwargs: pg_test_mod_args,
|
||||||
|
)
|
||||||
|
test_install_libs += test_binaryheap
|
||||||
|
|
||||||
|
test_install_data += files(
|
||||||
|
'test_binaryheap.control',
|
||||||
|
'test_binaryheap--1.0.sql',
|
||||||
|
)
|
||||||
|
|
||||||
|
tests += {
|
||||||
|
'name': 'test_binaryheap',
|
||||||
|
'sd': meson.current_source_dir(),
|
||||||
|
'bd': meson.current_build_dir(),
|
||||||
|
'regress': {
|
||||||
|
'sql': [
|
||||||
|
'test_binaryheap',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
8
src/test/modules/test_binaryheap/sql/test_binaryheap.sql
Normal file
8
src/test/modules/test_binaryheap/sql/test_binaryheap.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE EXTENSION test_binaryheap;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- These tests don't produce any interesting output. We're checking that
|
||||||
|
-- the operations complete without crashing or hanging and that none of their
|
||||||
|
-- internal sanity tests fail.
|
||||||
|
--
|
||||||
|
SELECT test_binaryheap();
|
@ -0,0 +1,7 @@
|
|||||||
|
/* src/test/modules/test_binaryheap/test_binaryheap--1.0.sql */
|
||||||
|
|
||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "CREATE EXTENSION test_binaryheap" to load this file. \quit
|
||||||
|
|
||||||
|
CREATE FUNCTION test_binaryheap() RETURNS VOID
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C;
|
275
src/test/modules/test_binaryheap/test_binaryheap.c
Normal file
275
src/test/modules/test_binaryheap/test_binaryheap.c
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
/*--------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* test_binaryheap.c
|
||||||
|
* Test correctness of binary heap implementation.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025, PostgreSQL Global Development Group
|
||||||
|
*
|
||||||
|
* IDENTIFICATION
|
||||||
|
* src/test/modules/test_binaryheap/test_binaryheap.c
|
||||||
|
*
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "common/int.h"
|
||||||
|
#include "common/pg_prng.h"
|
||||||
|
#include "fmgr.h"
|
||||||
|
#include "lib/binaryheap.h"
|
||||||
|
|
||||||
|
PG_MODULE_MAGIC;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test binaryheap_comparator for max-heap of integers.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
int_cmp(Datum a, Datum b, void *arg)
|
||||||
|
{
|
||||||
|
return pg_cmp_s32(DatumGetInt32(a), DatumGetInt32(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loops through all nodes and returns the maximum value.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
get_max_from_heap(binaryheap *heap)
|
||||||
|
{
|
||||||
|
int max = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < binaryheap_size(heap); i++)
|
||||||
|
max = Max(max, DatumGetInt32(binaryheap_get_node(heap, i)));
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate a random permutation of the integers 0..size-1.
|
||||||
|
*/
|
||||||
|
static int *
|
||||||
|
get_permutation(int size)
|
||||||
|
{
|
||||||
|
int *permutation = (int *) palloc(size * sizeof(int));
|
||||||
|
|
||||||
|
permutation[0] = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the "inside-out" variant of the Fisher-Yates shuffle algorithm.
|
||||||
|
* Notionally, we append each new value to the array and then swap it with
|
||||||
|
* a randomly-chosen array element (possibly including itself, else we
|
||||||
|
* fail to generate permutations with the last integer last). The swap
|
||||||
|
* step can be optimized by combining it with the insertion.
|
||||||
|
*/
|
||||||
|
for (int i = 1; i < size; i++)
|
||||||
|
{
|
||||||
|
int j = pg_prng_uint64_range(&pg_global_prng_state, 0, i);
|
||||||
|
|
||||||
|
if (j < i) /* avoid fetching undefined data if j=i */
|
||||||
|
permutation[i] = permutation[j];
|
||||||
|
permutation[j] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return permutation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure that the heap property holds for the given heap, i.e., each parent is
|
||||||
|
* greater than or equal to its children.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
verify_heap_property(binaryheap *heap)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < binaryheap_size(heap); i++)
|
||||||
|
{
|
||||||
|
int left = 2 * i + 1;
|
||||||
|
int right = 2 * i + 2;
|
||||||
|
int parent_val = DatumGetInt32(binaryheap_get_node(heap, i));
|
||||||
|
|
||||||
|
if (left < binaryheap_size(heap) &&
|
||||||
|
parent_val < DatumGetInt32(binaryheap_get_node(heap, left)))
|
||||||
|
elog(ERROR, "parent node less than left child");
|
||||||
|
|
||||||
|
if (right < binaryheap_size(heap) &&
|
||||||
|
parent_val < DatumGetInt32(binaryheap_get_node(heap, right)))
|
||||||
|
elog(ERROR, "parent node less than right child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check correctness of basic operations.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_basic(int size)
|
||||||
|
{
|
||||||
|
binaryheap *heap = binaryheap_allocate(size, int_cmp, NULL);
|
||||||
|
int *permutation = get_permutation(size);
|
||||||
|
|
||||||
|
if (!binaryheap_empty(heap))
|
||||||
|
elog(ERROR, "new heap not empty");
|
||||||
|
if (binaryheap_size(heap) != 0)
|
||||||
|
elog(ERROR, "wrong size for new heap");
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
binaryheap_add(heap, Int32GetDatum(permutation[i]));
|
||||||
|
verify_heap_property(heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binaryheap_empty(heap))
|
||||||
|
elog(ERROR, "heap empty after adding values");
|
||||||
|
if (binaryheap_size(heap) != size)
|
||||||
|
elog(ERROR, "wrong size for heap after adding values");
|
||||||
|
|
||||||
|
if (DatumGetInt32(binaryheap_first(heap)) != get_max_from_heap(heap))
|
||||||
|
elog(ERROR, "incorrect root node after adding values");
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
int expected = get_max_from_heap(heap);
|
||||||
|
int actual = DatumGetInt32(binaryheap_remove_first(heap));
|
||||||
|
|
||||||
|
if (actual != expected)
|
||||||
|
elog(ERROR, "incorrect root node after removing root");
|
||||||
|
verify_heap_property(heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!binaryheap_empty(heap))
|
||||||
|
elog(ERROR, "heap not empty after removing all nodes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test building heap after unordered additions.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_build(int size)
|
||||||
|
{
|
||||||
|
binaryheap *heap = binaryheap_allocate(size, int_cmp, NULL);
|
||||||
|
int *permutation = get_permutation(size);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
binaryheap_add_unordered(heap, Int32GetDatum(permutation[i]));
|
||||||
|
|
||||||
|
if (binaryheap_size(heap) != size)
|
||||||
|
elog(ERROR, "wrong size for heap after unordered additions");
|
||||||
|
|
||||||
|
binaryheap_build(heap);
|
||||||
|
verify_heap_property(heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test removing nodes.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_remove_node(int size)
|
||||||
|
{
|
||||||
|
binaryheap *heap = binaryheap_allocate(size, int_cmp, NULL);
|
||||||
|
int *permutation = get_permutation(size);
|
||||||
|
int remove_count = pg_prng_uint64_range(&pg_global_prng_state,
|
||||||
|
0, size - 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
binaryheap_add(heap, Int32GetDatum(permutation[i]));
|
||||||
|
|
||||||
|
for (int i = 0; i < remove_count; i++)
|
||||||
|
{
|
||||||
|
int idx = pg_prng_uint64_range(&pg_global_prng_state,
|
||||||
|
0, binaryheap_size(heap) - 1);
|
||||||
|
|
||||||
|
binaryheap_remove_node(heap, idx);
|
||||||
|
verify_heap_property(heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binaryheap_size(heap) != size - remove_count)
|
||||||
|
elog(ERROR, "wrong size after removing nodes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test replacing the root node.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_replace_first(int size)
|
||||||
|
{
|
||||||
|
binaryheap *heap = binaryheap_allocate(size, int_cmp, NULL);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
binaryheap_add(heap, Int32GetDatum(i));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace root with a value smaller than everything in the heap.
|
||||||
|
*/
|
||||||
|
binaryheap_replace_first(heap, Int32GetDatum(-1));
|
||||||
|
verify_heap_property(heap);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace root with a value in the middle of the heap.
|
||||||
|
*/
|
||||||
|
binaryheap_replace_first(heap, Int32GetDatum(size / 2));
|
||||||
|
verify_heap_property(heap);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace root with a larger value than everything in the heap.
|
||||||
|
*/
|
||||||
|
binaryheap_replace_first(heap, Int32GetDatum(size + 1));
|
||||||
|
verify_heap_property(heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test duplicate values.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_duplicates(int size)
|
||||||
|
{
|
||||||
|
binaryheap *heap = binaryheap_allocate(size, int_cmp, NULL);
|
||||||
|
int dup = pg_prng_uint64_range(&pg_global_prng_state, 0, size - 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
binaryheap_add(heap, Int32GetDatum(dup));
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
if (DatumGetInt32(binaryheap_remove_first(heap)) != dup)
|
||||||
|
elog(ERROR, "unexpected value in heap with duplicates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test resetting.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_reset(int size)
|
||||||
|
{
|
||||||
|
binaryheap *heap = binaryheap_allocate(size, int_cmp, NULL);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
binaryheap_add(heap, Int32GetDatum(i));
|
||||||
|
|
||||||
|
binaryheap_reset(heap);
|
||||||
|
|
||||||
|
if (!binaryheap_empty(heap))
|
||||||
|
elog(ERROR, "heap not empty after resetting");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SQL-callable entry point to perform all tests.
|
||||||
|
*/
|
||||||
|
PG_FUNCTION_INFO_V1(test_binaryheap);
|
||||||
|
|
||||||
|
Datum
|
||||||
|
test_binaryheap(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
static const int test_sizes[] = {1, 2, 3, 10, 100, 1000};
|
||||||
|
|
||||||
|
for (int i = 0; i < sizeof(test_sizes) / sizeof(int); i++)
|
||||||
|
{
|
||||||
|
int size = test_sizes[i];
|
||||||
|
|
||||||
|
test_basic(size);
|
||||||
|
test_build(size);
|
||||||
|
test_remove_node(size);
|
||||||
|
test_replace_first(size);
|
||||||
|
test_duplicates(size);
|
||||||
|
test_reset(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
}
|
5
src/test/modules/test_binaryheap/test_binaryheap.control
Normal file
5
src/test/modules/test_binaryheap/test_binaryheap.control
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# test_binaryheap extension
|
||||||
|
comment = 'Test code for binaryheap'
|
||||||
|
default_version = '1.0'
|
||||||
|
module_pathname = '$libdir/test_binaryheap'
|
||||||
|
relocatable = true
|
Reference in New Issue
Block a user