1# Copyright (C) 2012-2020 Ben Kurtovic <ben.kurtovic@gmail.com>
2#
3# Permission is hereby granted, free of charge, to any person obtaining a copy
4# of this software and associated documentation files (the "Software"), to deal
5# in the Software without restriction, including without limitation the rights
6# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7# copies of the Software, and to permit persons to whom the Software is
8# furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice shall be included in
11# all copies or substantial portions of the Software.
12#
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19# SOFTWARE.
20
21"""
22Test cases for the SmartList class and its child, ListProxy.
23"""
24
25import pytest
26
27from mwparserfromhell.smart_list import SmartList
28from mwparserfromhell.smart_list.list_proxy import ListProxy
29
30
31def _test_get_set_del_item(builder):
32    """Run tests on __get/set/delitem__ of a list built with *builder*."""
33    list1 = builder([0, 1, 2, 3, "one", "two"])
34    list2 = builder(list(range(10)))
35
36    assert 1 == list1[1]
37    assert "one" == list1[-2]
38    assert [2, 3] == list1[2:4]
39    with pytest.raises(IndexError):
40        list1[6]
41    with pytest.raises(IndexError):
42        list1[-7]
43
44    assert [0, 1, 2] == list1[:3]
45    assert [0, 1, 2, 3, "one", "two"] == list1[:]
46    assert [3, "one", "two"] == list1[3:]
47    assert [3, "one", "two"] == list1[3:100]
48    assert ["one", "two"] == list1[-2:]
49    assert [0, 1] == list1[:-4]
50    assert [] == list1[6:]
51    assert [] == list1[4:2]
52
53    assert [0, 2, "one"] == list1[0:5:2]
54    assert [0, 2] == list1[0:-3:2]
55    assert [0, 1, 2, 3, "one", "two"] == list1[::]
56    assert [2, 3, "one", "two"] == list1[2::]
57    assert [0, 1, 2, 3] == list1[:4:]
58    assert [2, 3] == list1[2:4:]
59    assert [0, 2, 4, 6, 8] == list2[::2]
60    assert [2, 5, 8] == list2[2::3]
61    assert [0, 3] == list2[:6:3]
62    assert [2, 5, 8] == list2[-8:9:3]
63    assert [] == list2[100000:1000:-100]
64
65    list1[3] = 100
66    assert 100 == list1[3]
67    list1[-3] = 101
68    assert [0, 1, 2, 101, "one", "two"] == list1
69    list1[5:] = [6, 7, 8]
70    assert [6, 7, 8] == list1[5:]
71    assert [0, 1, 2, 101, "one", 6, 7, 8] == list1
72    list1[2:4] = [-1, -2, -3, -4, -5]
73    assert [0, 1, -1, -2, -3, -4, -5, "one", 6, 7, 8] == list1
74    list1[0:-3] = [99]
75    assert [99, 6, 7, 8] == list1
76    list2[0:6:2] = [100, 102, 104]
77    assert [100, 1, 102, 3, 104, 5, 6, 7, 8, 9] == list2
78    list2[::3] = [200, 203, 206, 209]
79    assert [200, 1, 102, 203, 104, 5, 206, 7, 8, 209] == list2
80    list2[::] = range(7)
81    assert [0, 1, 2, 3, 4, 5, 6] == list2
82    with pytest.raises(ValueError):
83        list2[0:5:2] = [100, 102, 104, 106]
84    with pytest.raises(IndexError):
85        list2[7] = "foo"
86    with pytest.raises(IndexError):
87        list2[-8] = "foo"
88
89    del list2[2]
90    assert [0, 1, 3, 4, 5, 6] == list2
91    del list2[-3]
92    assert [0, 1, 3, 5, 6] == list2
93    with pytest.raises(IndexError):
94        del list2[100]
95    with pytest.raises(IndexError):
96        del list2[-6]
97    list2[:] = range(10)
98    del list2[3:6]
99    assert [0, 1, 2, 6, 7, 8, 9] == list2
100    del list2[-2:]
101    assert [0, 1, 2, 6, 7] == list2
102    del list2[:2]
103    assert [2, 6, 7] == list2
104    list2[:] = range(10)
105    del list2[2:8:2]
106    assert [0, 1, 3, 5, 7, 8, 9] == list2
107
108
109def _test_add_radd_iadd(builder):
110    """Run tests on __r/i/add__ of a list built with *builder*."""
111    list1 = builder(range(5))
112    list2 = builder(range(5, 10))
113    assert [0, 1, 2, 3, 4, 5, 6] == list1 + [5, 6]
114    assert [0, 1, 2, 3, 4] == list1
115    assert list(range(10)) == list1 + list2
116    assert [-2, -1, 0, 1, 2, 3, 4], [-2, -1] + list1
117    assert [0, 1, 2, 3, 4] == list1
118    list1 += ["foo", "bar", "baz"]
119    assert [0, 1, 2, 3, 4, "foo", "bar", "baz"] == list1
120
121
122def _test_other_magic_methods(builder):
123    """Run tests on other magic methods of a list built with *builder*."""
124    list1 = builder([0, 1, 2, 3, "one", "two"])
125    list2 = builder([])
126    list3 = builder([0, 2, 3, 4])
127    list4 = builder([0, 1, 2])
128
129    assert "[0, 1, 2, 3, 'one', 'two']" == str(list1)
130    assert b"\x00\x01\x02" == bytes(list4)
131    assert "[0, 1, 2, 3, 'one', 'two']" == repr(list1)
132
133    assert list1 < list3
134    assert list1 <= list3
135    assert list1 != list3
136    assert list1 != list3
137    assert list1 <= list3
138    assert list1 < list3
139
140    other1 = [0, 2, 3, 4]
141    assert list1 < other1
142    assert list1 <= other1
143    assert list1 != other1
144    assert list1 != other1
145    assert list1 <= other1
146    assert list1 < other1
147
148    other2 = [0, 0, 1, 2]
149    assert list1 >= other2
150    assert list1 > other2
151    assert list1 != other2
152    assert list1 != other2
153    assert list1 > other2
154    assert list1 >= other2
155
156    other3 = [0, 1, 2, 3, "one", "two"]
157    assert list1 >= other3
158    assert list1 <= other3
159    assert list1 == other3
160    assert list1 == other3
161    assert list1 <= other3
162    assert list1 >= other3
163
164    assert bool(list1) is True
165    assert bool(list2) is False
166
167    assert 6 == len(list1)
168    assert 0 == len(list2)
169
170    out = []
171    for obj in list1:
172        out.append(obj)
173    assert [0, 1, 2, 3, "one", "two"] == out
174
175    out = []
176    for ch in list2:
177        out.append(ch)
178    assert [] == out
179
180    gen1 = iter(list1)
181    out = []
182    for _ in range(len(list1)):
183        out.append(next(gen1))
184    with pytest.raises(StopIteration):
185        next(gen1)
186    assert [0, 1, 2, 3, "one", "two"] == out
187    gen2 = iter(list2)
188    with pytest.raises(StopIteration):
189        next(gen2)
190
191    assert ["two", "one", 3, 2, 1, 0] == list(reversed(list1))
192    assert [] == list(reversed(list2))
193
194    assert "one" in list1
195    assert 3 in list1
196    assert 10 not in list1
197    assert 0 not in list2
198
199    assert [] == list2 * 5
200    assert [] == 5 * list2
201    assert [0, 1, 2, 0, 1, 2, 0, 1, 2] == list4 * 3
202    assert [0, 1, 2, 0, 1, 2, 0, 1, 2] == 3 * list4
203    list4 *= 2
204    assert [0, 1, 2, 0, 1, 2] == list4
205
206
207def _test_list_methods(builder):
208    """Run tests on the public methods of a list built with *builder*."""
209    list1 = builder(range(5))
210    list2 = builder(["foo"])
211    list3 = builder([("a", 5), ("d", 2), ("b", 8), ("c", 3)])
212
213    list1.append(5)
214    list1.append(1)
215    list1.append(2)
216    assert [0, 1, 2, 3, 4, 5, 1, 2] == list1
217
218    assert 0 == list1.count(6)
219    assert 2 == list1.count(1)
220
221    list1.extend(range(5, 8))
222    assert [0, 1, 2, 3, 4, 5, 1, 2, 5, 6, 7] == list1
223
224    assert 1 == list1.index(1)
225    assert 6 == list1.index(1, 3)
226    assert 6 == list1.index(1, 3, 7)
227    with pytest.raises(ValueError):
228        list1.index(1, 3, 5)
229
230    list1.insert(0, -1)
231    assert [-1, 0, 1, 2, 3, 4, 5, 1, 2, 5, 6, 7] == list1
232    list1.insert(-1, 6.5)
233    assert [-1, 0, 1, 2, 3, 4, 5, 1, 2, 5, 6, 6.5, 7] == list1
234    list1.insert(13, 8)
235    assert [-1, 0, 1, 2, 3, 4, 5, 1, 2, 5, 6, 6.5, 7, 8] == list1
236
237    assert 8 == list1.pop()
238    assert 7 == list1.pop()
239    assert [-1, 0, 1, 2, 3, 4, 5, 1, 2, 5, 6, 6.5] == list1
240    assert -1 == list1.pop(0)
241    assert 5 == list1.pop(5)
242    assert 6.5 == list1.pop(-1)
243    assert [0, 1, 2, 3, 4, 1, 2, 5, 6] == list1
244    assert "foo" == list2.pop()
245    with pytest.raises(IndexError):
246        list2.pop()
247    assert [] == list2
248
249    list1.remove(6)
250    assert [0, 1, 2, 3, 4, 1, 2, 5] == list1
251    list1.remove(1)
252    assert [0, 2, 3, 4, 1, 2, 5] == list1
253    list1.remove(1)
254    assert [0, 2, 3, 4, 2, 5] == list1
255    with pytest.raises(ValueError):
256        list1.remove(1)
257
258    list1.reverse()
259    assert [5, 2, 4, 3, 2, 0] == list1
260
261    list1.sort()
262    assert [0, 2, 2, 3, 4, 5] == list1
263    list1.sort(reverse=True)
264    assert [5, 4, 3, 2, 2, 0] == list1
265    list3.sort(key=lambda i: i[1])
266    assert [("d", 2), ("c", 3), ("a", 5), ("b", 8)] == list3
267    list3.sort(key=lambda i: i[1], reverse=True)
268    assert [("b", 8), ("a", 5), ("c", 3), ("d", 2)] == list3
269
270
271def _dispatch_test_for_children(meth):
272    """Run a test method on various different types of children."""
273    meth(lambda L: SmartList(list(L))[:])
274    meth(lambda L: SmartList([999] + list(L))[1:])
275    meth(lambda L: SmartList(list(L) + [999])[:-1])
276    meth(lambda L: SmartList([101, 102] + list(L) + [201, 202])[2:-2])
277
278
279def test_docs():
280    """make sure the methods of SmartList/ListProxy have docstrings"""
281    methods = [
282        "append",
283        "count",
284        "extend",
285        "index",
286        "insert",
287        "pop",
288        "remove",
289        "reverse",
290        "sort",
291    ]
292    for meth in methods:
293        expected = getattr(list, meth).__doc__
294        smartlist_doc = getattr(SmartList, meth).__doc__
295        listproxy_doc = getattr(ListProxy, meth).__doc__
296        assert expected == smartlist_doc
297        assert expected == listproxy_doc
298
299
300def test_doctest():
301    """make sure the test embedded in SmartList's docstring passes"""
302    parent = SmartList([0, 1, 2, 3])
303    assert [0, 1, 2, 3] == parent
304    child = parent[2:]
305    assert [2, 3] == child
306    child.append(4)
307    assert [2, 3, 4] == child
308    assert [0, 1, 2, 3, 4] == parent
309
310
311def test_parent_get_set_del():
312    """make sure SmartList's getitem/setitem/delitem work"""
313    _test_get_set_del_item(SmartList)
314
315
316def test_parent_add():
317    """make sure SmartList's add/radd/iadd work"""
318    _test_add_radd_iadd(SmartList)
319
320
321def test_parent_other_magics():
322    """make sure SmartList's other magically implemented features work"""
323    _test_other_magic_methods(SmartList)
324
325
326def test_parent_methods():
327    """make sure SmartList's non-magic methods work, like append()"""
328    _test_list_methods(SmartList)
329
330
331def test_child_get_set_del():
332    """make sure ListProxy's getitem/setitem/delitem work"""
333    _dispatch_test_for_children(_test_get_set_del_item)
334
335
336def test_child_add():
337    """make sure ListProxy's add/radd/iadd work"""
338    _dispatch_test_for_children(_test_add_radd_iadd)
339
340
341def test_child_other_magics():
342    """make sure ListProxy's other magically implemented features work"""
343    _dispatch_test_for_children(_test_other_magic_methods)
344
345
346def test_child_methods():
347    """make sure ListProxy's non-magic methods work, like append()"""
348    _dispatch_test_for_children(_test_list_methods)
349
350
351def test_influence():
352    """make sure changes are propagated from parents to children"""
353    parent = SmartList([0, 1, 2, 3, 4, 5])
354    child1 = parent[2:]
355    child2 = parent[2:5]
356    assert [0, 1, 2, 3, 4, 5] == parent
357    assert [2, 3, 4, 5] == child1
358    assert [2, 3, 4] == child2
359    assert 2 == len(parent._children)
360
361    parent.append(6)
362    child1.append(7)
363    child2.append(4.5)
364    assert [0, 1, 2, 3, 4, 4.5, 5, 6, 7] == parent
365    assert [2, 3, 4, 4.5, 5, 6, 7] == child1
366    assert [2, 3, 4, 4.5] == child2
367
368    parent.insert(0, -1)
369    parent.insert(4, 2.5)
370    parent.insert(10, 6.5)
371    assert [-1, 0, 1, 2, 2.5, 3, 4, 4.5, 5, 6, 6.5, 7] == parent
372    assert [2, 2.5, 3, 4, 4.5, 5, 6, 6.5, 7] == child1
373    assert [2, 2.5, 3, 4, 4.5] == child2
374
375    assert 7 == parent.pop()
376    assert 6.5 == child1.pop()
377    assert 4.5 == child2.pop()
378    assert [-1, 0, 1, 2, 2.5, 3, 4, 5, 6] == parent
379    assert [2, 2.5, 3, 4, 5, 6] == child1
380    assert [2, 2.5, 3, 4] == child2
381
382    parent.remove(-1)
383    child1.remove(2.5)
384    assert [0, 1, 2, 3, 4, 5, 6] == parent
385    assert [2, 3, 4, 5, 6] == child1
386    assert [2, 3, 4] == child2
387
388    assert 0 == parent.pop(0)
389    assert [1, 2, 3, 4, 5, 6] == parent
390    assert [2, 3, 4, 5, 6] == child1
391    assert [2, 3, 4] == child2
392
393    child2.reverse()
394    assert [1, 4, 3, 2, 5, 6] == parent
395    assert [4, 3, 2, 5, 6] == child1
396    assert [4, 3, 2] == child2
397
398    parent.extend([7, 8])
399    child1.extend([8.1, 8.2])
400    child2.extend([1.9, 1.8])
401    assert [1, 4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2] == parent
402    assert [4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2] == child1
403    assert [4, 3, 2, 1.9, 1.8] == child2
404
405    child3 = parent[9:]
406    assert [8, 8.1, 8.2] == child3
407
408    del parent[8:]
409    assert [1, 4, 3, 2, 1.9, 1.8, 5, 6] == parent
410    assert [4, 3, 2, 1.9, 1.8, 5, 6] == child1
411    assert [4, 3, 2, 1.9, 1.8] == child2
412    assert [] == child3
413    assert 0 == len(child3)
414
415    del child1
416    assert [1, 4, 3, 2, 1.9, 1.8, 5, 6] == parent
417    assert [4, 3, 2, 1.9, 1.8] == child2
418    assert [] == child3
419    assert 2 == len(parent._children)
420
421    del child3
422    assert [1, 4, 3, 2, 1.9, 1.8, 5, 6] == parent
423    assert [4, 3, 2, 1.9, 1.8] == child2
424    assert 1 == len(parent._children)
425
426    parent.remove(1.9)
427    parent.remove(1.8)
428    assert [1, 4, 3, 2, 5, 6] == parent
429    assert [4, 3, 2] == child2
430
431    parent.reverse()
432    assert [6, 5, 2, 3, 4, 1] == parent
433    assert [4, 3, 2] == child2
434    assert 0 == len(parent._children)
435