Skip to content

Commit a482dc5

Browse files
authored
bpo-40602: Write unit tests for _Py_hashtable_t (GH-20091)
Cleanup also hashtable.c. Rename _Py_hashtable_t members: * Rename entries to nentries * Rename num_buckets to nbuckets
1 parent f2c3b68 commit a482dc5

File tree

4 files changed

+151
-120
lines changed

4 files changed

+151
-120
lines changed

Include/internal/pycore_hashtable.h

+12-11
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,18 @@ typedef _Py_hashtable_entry_t* (*_Py_hashtable_get_entry_func)(_Py_hashtable_t *
4848
const void *key);
4949

5050
typedef struct {
51-
/* allocate a memory block */
51+
// Allocate a memory block
5252
void* (*malloc) (size_t size);
5353

54-
/* release a memory block */
54+
// Release a memory block
5555
void (*free) (void *ptr);
5656
} _Py_hashtable_allocator_t;
5757

5858

5959
/* _Py_hashtable: table */
6060
struct _Py_hashtable_t {
61-
size_t num_buckets;
62-
size_t entries; /* Total number of entries in the table. */
61+
size_t nentries; // Total number of entries in the table
62+
size_t nbuckets;
6363
_Py_slist_t *buckets;
6464

6565
_Py_hashtable_get_entry_func get_entry_func;
@@ -70,10 +70,10 @@ struct _Py_hashtable_t {
7070
_Py_hashtable_allocator_t alloc;
7171
};
7272

73-
/* hash a pointer (void*) */
73+
/* Hash a pointer (void*) */
7474
PyAPI_FUNC(Py_uhash_t) _Py_hashtable_hash_ptr(const void *key);
7575

76-
/* comparison using memcmp() */
76+
/* Comparison using memcmp() */
7777
PyAPI_FUNC(int) _Py_hashtable_compare_direct(
7878
const void *key1,
7979
const void *key2);
@@ -129,13 +129,14 @@ _Py_hashtable_get_entry(_Py_hashtable_t *ht, const void *key)
129129
130130
Use _Py_hashtable_get_entry() to distinguish entry value equal to NULL
131131
and entry not found. */
132-
extern void *_Py_hashtable_get(_Py_hashtable_t *ht, const void *key);
132+
PyAPI_FUNC(void*) _Py_hashtable_get(_Py_hashtable_t *ht, const void *key);
133133

134134

135-
// Remove a key and its associated value without calling key and value destroy
136-
// functions.
137-
// Return the removed value if the key was found.
138-
// Return NULL if the key was not found.
135+
/* Remove a key and its associated value without calling key and value destroy
136+
functions.
137+
138+
Return the removed value if the key was found.
139+
Return NULL if the key was not found. */
139140
PyAPI_FUNC(void*) _Py_hashtable_steal(
140141
_Py_hashtable_t *ht,
141142
const void *key);

Modules/_testinternalcapi.c

+88
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "Python.h"
1515
#include "pycore_byteswap.h" // _Py_bswap32()
1616
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
17+
#include "pycore_hashtable.h" // _Py_hashtable_new()
1718
#include "pycore_gc.h" // PyGC_Head
1819

1920

@@ -62,10 +63,97 @@ test_bswap(PyObject *self, PyObject *Py_UNUSED(args))
6263
}
6364

6465

66+
#define TO_PTR(ch) ((void*)(uintptr_t)ch)
67+
#define FROM_PTR(ptr) ((uintptr_t)ptr)
68+
#define VALUE(key) (1 + ((int)(key) - 'a'))
69+
70+
static Py_uhash_t
71+
hash_char(const void *key)
72+
{
73+
char ch = (char)FROM_PTR(key);
74+
return ch;
75+
}
76+
77+
78+
static int
79+
hashtable_cb(_Py_hashtable_t *table,
80+
const void *key_ptr, const void *value_ptr,
81+
void *user_data)
82+
{
83+
int *count = (int *)user_data;
84+
char key = (char)FROM_PTR(key_ptr);
85+
int value = (int)FROM_PTR(value_ptr);
86+
assert(value == VALUE(key));
87+
*count += 1;
88+
return 0;
89+
}
90+
91+
92+
static PyObject*
93+
test_hashtable(PyObject *self, PyObject *Py_UNUSED(args))
94+
{
95+
_Py_hashtable_t *table = _Py_hashtable_new(hash_char,
96+
_Py_hashtable_compare_direct);
97+
if (table == NULL) {
98+
return PyErr_NoMemory();
99+
}
100+
101+
// Test _Py_hashtable_set()
102+
char key;
103+
for (key='a'; key <= 'z'; key++) {
104+
int value = VALUE(key);
105+
if (_Py_hashtable_set(table, TO_PTR(key), TO_PTR(value)) < 0) {
106+
_Py_hashtable_destroy(table);
107+
return PyErr_NoMemory();
108+
}
109+
}
110+
assert(table->nentries == 26);
111+
assert(table->nbuckets > table->nentries);
112+
113+
// Test _Py_hashtable_get_entry()
114+
for (key='a'; key <= 'z'; key++) {
115+
_Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(table, TO_PTR(key));
116+
assert(entry != NULL);
117+
assert(entry->key = TO_PTR(key));
118+
assert(entry->value = TO_PTR(VALUE(key)));
119+
}
120+
121+
// Test _Py_hashtable_get()
122+
for (key='a'; key <= 'z'; key++) {
123+
void *value_ptr = _Py_hashtable_get(table, TO_PTR(key));
124+
int value = (int)FROM_PTR(value_ptr);
125+
assert(value == VALUE(key));
126+
}
127+
128+
// Test _Py_hashtable_steal()
129+
key = 'p';
130+
void *value_ptr = _Py_hashtable_steal(table, TO_PTR(key));
131+
int value = (int)FROM_PTR(value_ptr);
132+
assert(value == VALUE(key));
133+
134+
assert(table->nentries == 25);
135+
136+
// Test _Py_hashtable_foreach()
137+
int count = 0;
138+
int res = _Py_hashtable_foreach(table, hashtable_cb, &count);
139+
assert(res == 0);
140+
assert(count == 25);
141+
142+
// Test _Py_hashtable_clear()
143+
_Py_hashtable_clear(table);
144+
assert(table->nentries == 0);
145+
assert(_Py_hashtable_get(table, TO_PTR('x')) == NULL);
146+
147+
_Py_hashtable_destroy(table);
148+
Py_RETURN_NONE;
149+
}
150+
151+
65152
static PyMethodDef TestMethods[] = {
66153
{"get_configs", get_configs, METH_NOARGS},
67154
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
68155
{"test_bswap", test_bswap, METH_NOARGS},
156+
{"test_hashtable", test_hashtable, METH_NOARGS},
69157
{NULL, NULL} /* sentinel */
70158
};
71159

0 commit comments

Comments
 (0)