8000 Add tests for collection mixins (see issue #2531) · DiegoBaldassarMilleuno/pythonnet@209a3eb · GitHub
[go: up one dir, main page]

Skip to content

Commit 209a3eb

Browse files
Add tests for collection mixins (see issue pythonnet#2531)
Adding testing for adherence to collections.abc protocols for the following classes: Array, List, ImmutableArray, ImmutableList, Dictionary, ImmutableDictionary and ReadOnlyDictionary Tests for Python list and dict are also present as a reference but commented out.
1 parent dfd746b commit 209a3eb

File tree

2 files changed

+532
-0
lines changed

2 files changed

+532
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# pyright: reportMissingImports=warning,reportAssertAlwaysTrue=none
2+
3+
from __future__ import annotations
4+
5+
import operator
6+
from typing import Any
7+
from collections.abc import Container
8+
9+
import pytest
10+
11+
import clr
12+
clr.AddReference('System.Collections.Immutable')
13+
from System import Nullable, Object, ValueType, Int32, String
14+
import System.Collections.Generic as Gen
15+
import System.Collections.Immutable as Imm
16+
import System.Collections.ObjectModel as OM
17+
18+
kv_pairs_1 = ((0, "0"), (10, "10"), (20, "20"), (30, "30"))
19+
kv_pairs_2 = kv_pairs_1 + ((40, None), )
20+
kv_pairs_3 = (("0", 0), ("10", 10), ("20", 20), ("30", 30))
21+
kv_pairs_4 = kv_pairs_3 + (("40", None), )
22+
23+
24+
def exactly_equal(val1, val2):
25+
return type(val1) is type(val2) and val1 == val2, '{0!r} != {1!r}'.format(val1, val2)
26+
27+
28+
def translate_pytype(pytype, *, nullable=False):
29+
if pytype is int:
30+
cstype = Int32
31+
elif pytype is str:
32+
cstype = String
33+
else:
34+
raise NotImplementedError('Unsupported type: {0!r}'.format(pytype))
35+
36+
if nullable and issubclass(cstype, ValueType):
37+
cstype = Nullable[cstype]
38+
39+
return cstype
40+
41+
42+
class MappingTests:
43+
ktype: type
44+
vtype: type
45+
nullable: bool
46+
cs_ktype: type
47+
cs_vtype: type
48+
mapped_to_null: Container[Any]
49+
dct: Any
50+
51+
def __init_subclass__(cls, /, values=None, **kwargs):
52+
if values is not None:
53+
cls.ktype,cls.vtype,cls.nullable,cls.cs_ktype,cls.cs_vtype = cls.deduce_types(values)
54+
cls.mapped_to_null = tuple(key for key,val in values if val is None)
55+
56+
@staticmethod
57+
def deduce_types(values):
58+
(ktype, ) = {type(key) for key,_ in values}
59+
vtypes = {type(val) for _,val in values}
60+
nullable = type(None) in vtypes
61+
if nullable:
62+
vtypes.remove(type(None))
63+
(vtype, ) = vtypes
64+
cs_ktype = translate_pytype(ktype)
65+
cs_vtype = translate_pytype(vtype, nullable=nullable)
66+
return (ktype, vtype, nullable, cs_ktype, cs_vtype)
67+
68+
def test_len(self):
69+
assert type(len(self.dct)) is int
70+
assert len(self.dct) == 4 if not self.nullable else 5
71+
72+
def test_iter(self):
73+
for idx,key in enumerate(self.dct):
74+
assert type(key) is self.ktype and int(key) == idx * 10
75+
76+
def test_keys(self):
77+
keys = sorted(self.dct.keys())
78+
# print(f'### {list(self.dct.keys())}') # TODO: DELME
79+
for idx,key in enumerate(keys):
80+
assert exactly_equal(key, self.ktype(idx * 10))
81+
82+
def test_values(self):
83+
values = sorted(self.dct.values(), key=lambda val: val if val is not None else self.vtype(999))
84+
for idx,val in enumerate(values):
85+
exp_val = None if self.ktype(10 * idx) in self.mapped_to_null else self.vtype(10 * idx)
86+
assert exactly_equal(val, exp_val)
87+
88+
def test_items(self):
89+
items = sorted(self.dct.items(), key=operator.itemgetter(0))
90+
for idx,tpl in enumerate(items):
91+
assert type(tpl) is tuple and len(tpl) == 2
92+
key,val = tpl
93+
assert exactly_equal(key, self.ktype(idx * 10))
94+
exp_val = None if self.ktype(10 * idx) in self.mapped_to_null else self.vtype(10 * idx)
95+
assert exactly_equal(val, exp_val)
96+
97+
def test_contains(self):
98+
assert self.ktype(10) in self.dct
99+
assert self.ktype(50) not in self.dct
100+
assert 12.34 not in self.dct
101+
102+
def test_getitem(self):
103+
for idx in range(len(self.dct)):
104+
val = self.dct[self.ktype(10 * idx)]
105+
assert exactly_equal(val, self.vtype(10 * idx))
106+
107+
def test_getitem_raise(self):
108+
with pytest.raises(KeyError):
109+
self.dct[self.ktype(50)]
110+
with pytest.raises(KeyError):
111+
self.dct[12.34]
112+
113+
def test_get(self):
114+
val = self.dct.get(self.ktype(10))
115+
assert exactly_equal(val, self.vtype(10))
116+
assert self.dct.get(self.ktype(50)) is None
117+
val = self.dct.get(self.ktype(50), 123.1)
118+
assert val == 123.1
119+
120+
121+
class MutableMappingTests(MappingTests):
122+
def get_copy(self) -> Any:
123+
raise NotImplementedError('must be overridden!')
124+
125+
def test_setitem(self):
126+
dct = self.get_copy()
127+
key,val = (self.ktype(10), self.vtype(11))
128+
dct[key] = val
129+
assert exactly_equal(dct[key], val)
130+
131+
def test_setitem_raise(self):
132+
if isinstance(self.dct, Object): # this is only relevant for CLR types
133+
dct = self.get_copy()
134+
with pytest.raises(Exception):
135+
dct[12.34] = self.vtype(0)
136+
137+
@pytest.mark.xfail(reason='Known to crash', run=False)
138+
def test_delitem(self):
139+
dct = self.get_copy()
140+
del dct[self.ktype(10)]
141+
assert self.ktype(10) not in dct
142+
143+
@pytest.mark.xfail(reason='Known to crash', run=False)
144+
def test_delitem_raise(self):
145+
dct = self.get_copy()
146+
with pytest.raises(KeyError):
147+
del dct[12.34]
148+
149+
def test_pop(self):
150+
dct = self.get_copy()
151+
length = len(dct)
152+
val = dct.pop(self.ktype(10))
153+
assert exactly_equal(val, self.vtype(10))
154+
val = dct.pop(self.ktype(10), self.vtype(11))
155+
assert exactly_equal(val, self.vtype(11))
156+
assert len(dct) == length - 1
157+
158+
def test_popitem(self):
159+
dct = self.get_copy()
160+
while len(dct) != 0:
161+
tpl = dct.popitem()
162+
assert type(tpl) is tuple and len(tpl) == 2
163+
key,val = tpl
164+
assert type(key) is self.ktype
165+
assert type(val) is self.vtype or (self.nullable and val is None)
166+
if val is not None:
167+
assert int(key) == int(val)
168+
169+
def test_clear(self):
170+
dct = self.get_copy()
171+
dct.clear()
172+
assert len(dct) == 0
173+
assert dict(dct) == {}
174+
175+
def test_setdefault(self):
176+
dct = self.get_copy()
177+
dct.setdefault(self.ktype(50), self.vtype(50))
178+
assert exactly_equal(dct[self.ktype(50)], self.vtype(50))
179+
180+
def test_update(self):
181+
dct = self.get_copy()
182+
pydict = {self.ktype(num): self.vtype(num) for num in (30, 40)}
183+
if self.nullable:
184+
pydict[self.ktype(50)] = None
185+
dct.update(pydict)
186+
pydict.update({self.ktype(num): self.vtype(num) for num in (0, 10, 20)}) # put in the items we expect to be set already
187+
assert dict(dct) == pydict
188+
extra_vals = tuple((self.ktype(num), self.vtype(num)) for num in (60, 70))
189+
dct.update(extra_vals)
190+
pydict.update(extra_vals)
191+
assert dict(dct) == pydict
192+
if self.ktype is str:
193+
dct.update(aaa=80, bbb=90)
194+
pydict.update(aaa=80, bbb=90)
195+
assert dict(dct) == pydict
196+
197+
198+
class PyDictTests(MutableMappingTests):
199+
def __init_subclass__(cls, /, values, **kwargs):
200+
super().__init_subclass__(values=values, **kwargs)
201+
cls.dct = dict(values)
202+
203+
def get_copy(self):
204+
return self.dct.copy()
205+
206+
# class TestPyDictIntStr (PyDictTests, values=kv_pairs_1): pass
207+
# class TestPyDictIntNullStr(PyDictTests, values=kv_pairs_2): pass
208+
# class TestPyDictStrInt (PyDictTests, values=kv_pairs_3): pass
209+
# class TestPyDictStrNullInt(PyDictTests, values=kv_pairs_4): pass
210+
211+
212+
def make_cs_dictionary(cs_ktype, cs_vtype, values):
213+
dct = Gen.Dictionary[cs_ktype, cs_vtype]()
214+
for key,val in values:
215+
dct[key] = None if val is None else val
216+
return dct
217+
218+
219+
class DictionaryTests(MutableMappingTests):
220+
def __init_subclass__(cls, /, values, **kwargs):
221+
super().__init_subclass__(values=values, **kwargs)
222+
cls.dct = make_cs_dictionary(cls.cs_ktype, cls.cs_vtype, values)
223+
224+
def get_copy(self):
225+
return Gen.Dictionary[self.cs_ktype, self.cs_vtype](self.dct)
226+
227+
class TestDictionaryIntStr (DictionaryTests, values=kv_pairs_1): pass
228+
class TestDictionaryIntNullStr(DictionaryTests, values=kv_pairs_2): pass
229+
class TestDictionaryStrInt (DictionaryTests, values=kv_pairs_3): pass
230+
class TestDictionaryStrNullInt(DictionaryTests, values=kv_pairs_4): pass
231+
232+
233+
class ReadOnlyDictionaryTests(MappingTests):
234+
def __init_subclass__(cls, /, values, **kwargs):
235+
super().__init_subclass__(values=values, **kwargs)
236+
dct = make_cs_dictionary(cls.cs_ktype, cls.cs_vtype, values)
237+
cls.dct = OM.ReadOnlyDictionary[cls.cs_ktype, cls.cs_vtype](dct)
238+
239+
class ReadOnlyDictionaryIntStr (ReadOnlyDictionaryTests, values=kv_pairs_1): pass
240+
class ReadOnlyDictionaryIntNullStr(ReadOnlyDictionaryTests, values=kv_pairs_2): pass
241+
class ReadOnlyDictionaryStrInt (ReadOnlyDictionaryTests, values=kv_pairs_3): pass
242+
class ReadOnlyDictionaryStrNullInt(ReadOnlyDictionaryTests, values=kv_pairs_4): pass
243+
244+
245+
class ImmutableDictionaryTests(MappingTests):
246+
def __init_subclass__(cls, /, values, **kwargs):
247+
super().__init_subclass__(values=values, **kwargs)
248+
dct = make_cs_dictionary(cls.cs_ktype, cls.cs_vtype, values)
249+
cls.dct = Imm.ImmutableDictionary.ToImmutableDictionary[cls.cs_ktype, cls.cs_vtype](dct)
250+
251+
class TestImmutableDictionaryIntStr (ImmutableDictionaryTests, values=kv_pairs_1): pass
252+
class TestImmutableDictionaryIntNullStr(ImmutableDictionaryTests, values=kv_pairs_2): pass
253+
class TestImmutableDictionaryStrInt (ImmutableDictionaryTests, values=kv_pairs_3): pass
254+
class TestImmutableDictionaryStrNullInt(ImmutableDictionaryTests, values=kv_pairs_4): pass

0 commit comments

Comments
 (0)
0