Skip to content

Commit d107c3e

Browse files
committed
A bunch of fixes
* Fix #26: `ifdef NDEBUG` should be `ifndef NDEBUG` * More tests for #24 * Add a `DEBUG_IMMUTABLES` env var to build debug builds
1 parent ad137a3 commit d107c3e

File tree

3 files changed

+157
-12
lines changed

3 files changed

+157
-12
lines changed

immutables/_map.c

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,13 @@ static MapObject *
393393
map_update(uint64_t mutid, MapObject *o, PyObject *src);
394394

395395

396-
#ifdef NDEBUG
396+
#ifndef NDEBUG
397397
static void
398398
_map_node_array_validate(void *o)
399399
{
400400
assert(IS_ARRAY_NODE(o));
401401
MapNode_Array *node = (MapNode_Array*)(o);
402+
assert(node->a_count <= HAMT_ARRAY_NODE_SIZE);
402403
Py_ssize_t i = 0, count = 0;
403404
for (; i < HAMT_ARRAY_NODE_SIZE; i++) {
404405
if (node->a_array[i] != NULL) {
@@ -1109,7 +1110,7 @@ map_node_bitmap_without(MapNode_Bitmap *self,
11091110
}
11101111
}
11111112

1112-
#ifdef NDEBUG
1113+
#ifndef NDEBUG
11131114
/* Ensure that Collision.without implementation
11141115
converts to Bitmap nodes itself.
11151116
*/
@@ -1744,6 +1745,7 @@ map_node_array_clone(MapNode_Array *node, uint64_t mutid)
17441745
Py_ssize_t i;
17451746

17461747
VALIDATE_ARRAY_NODE(node)
1748+
assert(node->a_count <= HAMT_ARRAY_NODE_SIZE);
17471749

17481750
/* Create a new Array node. */
17491751
clone = (MapNode_Array *)map_node_array_new(node->a_count, mutid);
@@ -1806,7 +1808,7 @@ map_node_array_assoc(MapNode_Array *self,
18061808

18071809
if (mutid != 0 && self->a_mutid == mutid) {
18081810
new_node = self;
1809-
self->a_count++; /*must update count*/
1811+
self->a_count++;
18101812
Py_INCREF(self);
18111813
}
18121814
else {
@@ -2007,7 +2009,7 @@ map_node_array_without(MapNode_Array *self,
20072009
}
20082010
else {
20092011

2010-
#ifdef NDEBUG
2012+
#ifndef NDEBUG
20112013
if (IS_COLLISION_NODE(node)) {
20122014
assert(
20132015
(map_node_collision_count(
@@ -2102,7 +2104,9 @@ map_node_array_dump(MapNode_Array *node,
21022104
goto error;
21032105
}
21042106

2105-
if (_map_dump_format(writer, "ArrayNode(id=%p):\n", node)) {
2107+
if (_map_dump_format(writer, "ArrayNode(id=%p count=%zd):\n",
2108+
node, node->a_count)
2109+
) {
21062110
goto error;
21072111
}
21082112

@@ -2299,7 +2303,7 @@ map_iterator_bitmap_next(MapIteratorState *iter,
22992303
Py_ssize_t pos = iter->i_pos[level];
23002304

23012305
if (pos + 1 >= Py_SIZE(node)) {
2302-
#ifdef NDEBUG
2306+
#ifndef NDEBUG
23032307
assert(iter->i_level >= 0);
23042308
iter->i_nodes[iter->i_level] = NULL;
23052309
#endif
@@ -2336,7 +2340,7 @@ map_iterator_collision_next(MapIteratorState *iter,
23362340
Py_ssize_t pos = iter->i_pos[level];
23372341

23382342
if (pos + 1 >= Py_SIZE(node)) {
2339-
#ifdef NDEBUG
2343+
#ifndef NDEBUG
23402344
assert(iter->i_level >= 0);
23412345
iter->i_nodes[iter->i_level] = NULL;
23422346
#endif
@@ -2360,7 +2364,7 @@ map_iterator_array_next(MapIteratorState *iter,
23602364
Py_ssize_t pos = iter->i_pos[level];
23612365

23622366
if (pos >= HAMT_ARRAY_NODE_SIZE) {
2363-
#ifdef NDEBUG
2367+
#ifndef NDEBUG
23642368
assert(iter->i_level >= 0);
23652369
iter->i_nodes[iter->i_level] = NULL;
23662370
#endif
@@ -2382,7 +2386,7 @@ map_iterator_array_next(MapIteratorState *iter,
23822386
}
23832387
}
23842388

2385-
#ifdef NDEBUG
2389+
#ifndef NDEBUG
23862390
assert(iter->i_level >= 0);
23872391
iter->i_nodes[iter->i_level] = NULL;
23882392
#endif

setup.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os.path
1+
import os
22
import platform
33
import setuptools
44

@@ -22,11 +22,17 @@
2222

2323

2424
if platform.python_implementation() == 'CPython':
25+
if os.environ.get("DEBUG_IMMUTABLES") == '1':
26+
undef_macros = ["NDEBUG"]
27+
else:
28+
undef_macros = None
29+
2530
ext_modules = [
2631
setuptools.Extension(
2732
"immutables._map",
2833
["immutables/_map.c"],
29-
extra_compile_args=CFLAGS)
34+
extra_compile_args=CFLAGS,
35+
undef_macros=undef_macros)
3036
]
3137
else:
3238
ext_modules = []

tests/test_map.py

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,9 @@ def test_map_collision_2(self):
240240
# <Key name:E hash:362244>: 'e'
241241
# <Key name:B hash:101>: 'b'
242242

243-
def test_map_stress(self):
243+
244+
245+
def test_map_stress_01(self):
244246
COLLECTION_SIZE = 7000
245247
TEST_ITERS_EVERY = 647
246248
CRASH_HASH_EVERY = 97
@@ -330,6 +332,78 @@ def test_map_stress(self):
330332
self.assertEqual(len(h), 0)
331333
self.assertEqual(list(h.items()), [])
332334

335+
def test_map_stress_02(self):
336+
COLLECTION_SIZE = 20000
337+
TEST_ITERS_EVERY = 647
338+
CRASH_HASH_EVERY = 97
339+
DELETE_EVERY = 3
340+
CRASH_EQ_EVERY = 11
341+
342+
h = self.Map()
343+
d = dict()
344+
345+
for i in range(COLLECTION_SIZE // 2):
346+
key = KeyStr(i)
347+
348+
if not (i % CRASH_HASH_EVERY):
349+
with HashKeyCrasher(error_on_hash=True):
350+
with self.assertRaises(HashingError):
351+
h.set(key, i)
352+
353+
h = h.set(key, i)
354+
355+
if not (i % CRASH_EQ_EVERY):
356+
with HashKeyCrasher(error_on_eq=True):
357+
with self.assertRaises(EqError):
358+
h.get(KeyStr(i)) # really trigger __eq__
359+
360+
d[key] = i
361+
self.assertEqual(len(d), len(h))
362+
363+
if not (i % TEST_ITERS_EVERY):
364+
self.assertEqual(set(h.items()), set(d.items()))
365+
self.assertEqual(len(h.items()), len(d.items()))
366+
367+
with h.mutate() as m:
368+
for i in range(COLLECTION_SIZE // 2, COLLECTION_SIZE):
369+
key = KeyStr(i)
370+
371+
if not (i % CRASH_HASH_EVERY):
372+
with HashKeyCrasher(error_on_hash=True):
373+
with self.assertRaises(HashingError):
374+
m[key] = i
375+
376+
m[key] = i
377+
378+
if not (i % CRASH_EQ_EVERY):
379+
with HashKeyCrasher(error_on_eq=True):
380+
with self.assertRaises(EqError):
381+
m[KeyStr(i)]
382+
383+
d[key] = i
384+
self.assertEqual(len(d), len(m))
385+
386+
if not (i % DELETE_EVERY):
387+
del m[key]
388+
del d[key]
389+
390+
self.assertEqual(len(d), len(m))
391+
392+
h = m.finish()
393+
394+
self.assertEqual(len(h), len(d))
395+
self.assertEqual(set(h.items()), set(d.items()))
396+
397+
with h.mutate() as m:
398+
for key in list(d):
399+
del d[key]
400+
del m[key]
401+
self.assertEqual(len(m), len(d))
402+
h = m.finish()
403+
404+
self.assertEqual(len(h), len(d))
405+
self.assertEqual(set(h.items()), set(d.items()))
406+
333407
def test_map_delete_1(self):
334408
A = HashKey(100, 'A')
335409
B = HashKey(101, 'B')
@@ -1235,6 +1309,67 @@ def test_map_mut_19(self):
12351309
m2 = m.update({'a': 20})
12361310
self.assertEqual(len(m2), 2)
12371311

1312+
def test_map_mut_20(self):
1313+
# Issue 24:
1314+
1315+
h = self.Map()
1316+
1317+
for i in range(19):
1318+
# Create more than 16 keys to trigger the root bitmap
1319+
# node to be converted into an array node
1320+
h = h.set(HashKey(i, i), i)
1321+
1322+
1323+
h = h.set(HashKey(18, '18-collision'), 18)
1324+
1325+
with h.mutate() as m:
1326+
del m[HashKey(18, 18)]
1327+
del m[HashKey(18, '18-collision')]
1328+
1329+
# The pre-issue-24 code failed to update the number of array
1330+
# node element, so at this point it would be greater than it
1331+
# actually is.
1332+
h = m.finish()
1333+
1334+
# Any of the below operations shouldn't crash the debug build.
1335+
with h.mutate() as m:
1336+
for i in range(18):
1337+
del m[HashKey(i, i)]
1338+
h = m.finish()
1339+
h = h.set(HashKey(21, 21), 21)
1340+
h = h.set(HashKey(22, 22), 22)
1341+
1342+
def test_map_mut_21(self):
1343+
# Issue 24:
1344+
# Array nodes, while in mutation, failed to increment the
1345+
# internal count of elements when adding a new key to it.
1346+
# Because the internal count
1347+
1348+
h = self.Map()
1349+
1350+
for i in range(18):
1351+
# Create more than 16 keys to trigger the root bitmap
1352+
# node to be converted into an array node
1353+
h = h.set(HashKey(i, i), i)
1354+
1355+
with h.mutate() as m:
1356+
# Add one new key to the array node
1357+
m[HashKey(18, 18)] = 18
1358+
# Add another key -- after this the old code failed
1359+
# to increment the number of elements in the mutated
1360+
# array node.
1361+
m[HashKey(19, 19)] = 19
1362+
h = m.finish()
1363+
1364+
for i in range(20):
1365+
# Start deleting keys one by one. Because array node
1366+
# element count was accounted incorrectly (smaller by 1
1367+
# than it actually is, the mutation for "del h[18]" would
1368+
# create an empty array node, clipping the "19" key).
1369+
# Before the issue #24 fix, the below line would crash
1370+
# on i=19.
1371+
h = h.delete(HashKey(i, i))
1372+
12381373
def test_map_mut_stress(self):
12391374
COLLECTION_SIZE = 7000
12401375
TEST_ITERS_EVERY = 647

0 commit comments

Comments
 (0)