Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/test_files/cpp17_stl_test.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Generated with autowrap 0.24.0 and Cython (Parser) 3.2.1
#Generated with autowrap 0.23.0 and Cython (Parser) 3.2.1
#cython: c_string_encoding=ascii
#cython: embedsignature=False
from enum import Enum as _PyEnum
Expand Down
10 changes: 10 additions & 0 deletions tests/test_files/wrapped_container_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@ class Item {
bool operator==(const Item& other) const {
return value_ == other.value_ && name_ == other.name_;
}
bool operator!=(const Item& other) const {
return !(*this == other);
}

int getValue() const { return value_; }
void setValue(int v) { value_ = v; }
std::string getName() const { return name_; }

// For Python __hash__ - returns combined hash of both members
size_t getHashValue() const {
size_t h1 = std::hash<int>()(value_);
size_t h2 = std::hash<std::string>()(name_);
return h1 ^ (h2 << 1);
}
};

// Hash function for Item - required for unordered_map/unordered_set
Expand Down
8 changes: 8 additions & 0 deletions tests/test_files/wrapped_container_test.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ from libcpp cimport bool
cdef extern from "wrapped_container_test.hpp":

# A simple wrapped class used in containers
# wrap-hash and operator== enable Python dict/set lookups with d[item]
cdef cppclass Item:
# wrap-hash:
# getHashValue()

int value_
libcpp_string name_

Expand All @@ -22,9 +26,13 @@ cdef extern from "wrapped_container_test.hpp":
Item(int v, libcpp_string n)
Item(Item&)

bool operator==(Item)
bool operator!=(Item)

int getValue()
void setValue(int v)
libcpp_string getName()
size_t getHashValue()

# Test class with methods that use containers of wrapped classes
cdef cppclass WrappedContainerTest:
Expand Down
130 changes: 117 additions & 13 deletions tests/test_wrapped_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def test_map_int_to_item_has_key(self, wrapped_container_module):
assert t.hasKeyMapIntToItem(map_data, 999) is False



class TestMapWithWrappedClassKey:
"""Tests for map<Item, int>."""

Expand All @@ -193,16 +194,25 @@ def test_map_item_to_int_sum(self, wrapped_container_module):
assert result == 30 # 10 + 20 (sum of keys)

def test_map_item_to_int_create(self, wrapped_container_module):
"""Test returning map<Item, int>."""
"""Test returning map<Item, int> and using Python d[key] lookup."""
m = wrapped_container_module
t = m.WrappedContainerTest()

result = t.createMapItemToInt(3)
assert len(result) == 3
# Values should be 0, 10, 20 for keys with value_ 0, 1, 2
keys_values = [(k.value_, v) for k, v in result.items()]
keys_values.sort()
assert keys_values == [(0, 0), (1, 10), (2, 20)]

# Direct Python dict lookup with wrapped class key - NOT iteration!
key0 = m.Item(0)
key1 = m.Item(1)
key2 = m.Item(2)
assert result[key0] == 0
assert result[key1] == 10
assert result[key2] == 20

# Test 'in' operator
assert key1 in result
missing = m.Item(999)
assert missing not in result
Comment on lines +204 to +215
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jpfeuffer like this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isso!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

darauf nen Caipi. :) Danke!



class TestNestedVectorOfWrappedClass:
Expand Down Expand Up @@ -300,16 +310,26 @@ def test_unordered_map_item_to_int_sum(self, wrapped_container_module):
assert result == 30 # 10 + 20 (sum of keys)

def test_unordered_map_item_to_int_create(self, wrapped_container_module):
"""Test returning unordered_map<Item, int>."""
"""Test returning unordered_map<Item, int> and using Python d[key] lookup."""
m = wrapped_container_module
t = m.WrappedContainerTest()

result = t.createUnorderedMapItemToInt(3)
assert len(result) == 3
# Values should be 0, 10, 20 for keys with value_ 0, 1, 2
keys_values = [(k.value_, v) for k, v in result.items()]
keys_values.sort()
assert keys_values == [(0, 0), (1, 10), (2, 20)]

# Direct Python dict lookup with wrapped class key - NOT iteration!
# This tests that the wrapped Item class has proper __hash__ and __eq__
key0 = m.Item(0)
key1 = m.Item(1)
key2 = m.Item(2)
assert result[key0] == 0
assert result[key1] == 10
assert result[key2] == 20

# Test 'in' operator (uses hash)
assert key1 in result
missing = m.Item(999)
assert missing not in result


class TestUnorderedMapWithWrappedClassValue:
Expand Down Expand Up @@ -371,6 +391,7 @@ def test_unordered_map_int_to_item_has_key(self, wrapped_container_module):
assert t.hasKeyUnorderedMapIntToItem(map_data, 999) is False



class TestUnorderedMapWithWrappedClassBoth:
"""Tests for unordered_map<Item, Item> - wrapped class as both key and value."""

Expand Down Expand Up @@ -491,14 +512,97 @@ def test_unordered_set_items_sum(self, wrapped_container_module):
assert result == 60

def test_unordered_set_items_create(self, wrapped_container_module):
"""Test returning unordered_set<Item>."""
"""Test returning unordered_set<Item> and using Python 'in' operator."""
m = wrapped_container_module
t = m.WrappedContainerTest()

items = t.createUnorderedSetItems(3)
assert len(items) == 3
values = sorted([item.value_ for item in items])
assert values == [0, 10, 20]

# Direct Python 'in' operator with wrapped class - NOT iteration!
# This tests that the wrapped Item class has proper __hash__ and __eq__
item0 = m.Item(0)
item1 = m.Item(10)
item2 = m.Item(20)
assert item0 in items
assert item1 in items
assert item2 in items

# Test missing item
missing = m.Item(999)
assert missing not in items

def test_unordered_set_two_member_hash(self, wrapped_container_module):
"""Test that hash function uses both value_ AND name_ members.

This verifies that items with the same value_ but different name_
are treated as different items (different hash + equality check).
Uses Python's native 'in' operator - no C++ lookup functions.
"""
m = wrapped_container_module

# Create items with same value but different names
item_alice = m.Item(100, b"alice")
item_bob = m.Item(100, b"bob")
item_charlie = m.Item(200, b"charlie")

items = {item_alice, item_bob, item_charlie}

# All three should be in the set (even though two have same value_)
assert len(items) == 3, "Set should have 3 items despite same value_"

# Search for exact match using Python 'in' (value_ AND name_ must match)
search_alice = m.Item(100, b"alice")
search_bob = m.Item(100, b"bob")

assert search_alice in items, "Should find alice (100, 'alice')"
assert search_bob in items, "Should find bob (100, 'bob')"

# Search with wrong name should NOT find item
# Same value_ but different name_ = different hash/different item
wrong_name = m.Item(100, b"eve")
assert wrong_name not in items, \
"Should NOT find (100, 'eve') - name doesn't match"

# Search with wrong value should NOT find item
wrong_value = m.Item(999, b"alice")
assert wrong_value not in items, \
"Should NOT find (999, 'alice') - value doesn't match"

def test_dict_lookup_with_multi_member_key(self, wrapped_container_module):
"""Test d[key] lookup where key is a wrapped class with multiple members.

This verifies that Python dict lookup uses the full hash (both value_ and name_)
to find the correct value, not just one member.
"""
m = wrapped_container_module

# Create keys with multiple members
key_alice = m.Item(100, b"alice")
key_bob = m.Item(100, b"bob") # Same value_, different name_
key_charlie = m.Item(200, b"charlie")

# Build a dict with multi-member keys
data = {key_alice: 1000, key_bob: 2000, key_charlie: 3000}
assert len(data) == 3 # All three are distinct keys

# Direct d[key] lookup with new Item objects (not the same object!)
lookup_alice = m.Item(100, b"alice")
lookup_bob = m.Item(100, b"bob")
lookup_charlie = m.Item(200, b"charlie")

# These lookups must find the correct values based on BOTH members
assert data[lookup_alice] == 1000, "d[Item(100, 'alice')] should be 1000"
assert data[lookup_bob] == 2000, "d[Item(100, 'bob')] should be 2000"
assert data[lookup_charlie] == 3000, "d[Item(200, 'charlie')] should be 3000"

# Lookup with wrong name should raise KeyError
wrong_name = m.Item(100, b"eve")
try:
_ = data[wrong_name]
assert False, "Should have raised KeyError for wrong name"
except KeyError:
pass # Expected

def test_unordered_set_has_item(self, wrapped_container_module):
"""Test checking if Item exists in unordered_set (hash-based O(1) membership test)."""
Expand Down