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

Skip to content

Commit bc78cbb

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 bc78cbb

File tree

2 files changed

+528
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)
0