1"""
2  Test cases for the repr module
3  Nick Mathewson
4"""
5
6import sys
7import os
8import shutil
9import importlib
10import importlib.util
11import unittest
12
13from test.support import verbose
14from test.support.os_helper import create_empty_file
15from reprlib import repr as r # Don't shadow builtin repr
16from reprlib import Repr
17from reprlib import recursive_repr
18
19
20def nestedTuple(nesting):
21    t = ()
22    for i in range(nesting):
23        t = (t,)
24    return t
25
26class ReprTests(unittest.TestCase):
27
28    def test_string(self):
29        eq = self.assertEqual
30        eq(r("abc"), "'abc'")
31        eq(r("abcdefghijklmnop"),"'abcdefghijklmnop'")
32
33        s = "a"*30+"b"*30
34        expected = repr(s)[:13] + "..." + repr(s)[-14:]
35        eq(r(s), expected)
36
37        eq(r("\"'"), repr("\"'"))
38        s = "\""*30+"'"*100
39        expected = repr(s)[:13] + "..." + repr(s)[-14:]
40        eq(r(s), expected)
41
42    def test_tuple(self):
43        eq = self.assertEqual
44        eq(r((1,)), "(1,)")
45
46        t3 = (1, 2, 3)
47        eq(r(t3), "(1, 2, 3)")
48
49        r2 = Repr()
50        r2.maxtuple = 2
51        expected = repr(t3)[:-2] + "...)"
52        eq(r2.repr(t3), expected)
53
54        # modified fillvalue:
55        r3 = Repr()
56        r3.fillvalue = '+++'
57        r3.maxtuple = 2
58        expected = repr(t3)[:-2] + "+++)"
59        eq(r3.repr(t3), expected)
60
61    def test_container(self):
62        from array import array
63        from collections import deque
64
65        eq = self.assertEqual
66        # Tuples give up after 6 elements
67        eq(r(()), "()")
68        eq(r((1,)), "(1,)")
69        eq(r((1, 2, 3)), "(1, 2, 3)")
70        eq(r((1, 2, 3, 4, 5, 6)), "(1, 2, 3, 4, 5, 6)")
71        eq(r((1, 2, 3, 4, 5, 6, 7)), "(1, 2, 3, 4, 5, 6, ...)")
72
73        # Lists give up after 6 as well
74        eq(r([]), "[]")
75        eq(r([1]), "[1]")
76        eq(r([1, 2, 3]), "[1, 2, 3]")
77        eq(r([1, 2, 3, 4, 5, 6]), "[1, 2, 3, 4, 5, 6]")
78        eq(r([1, 2, 3, 4, 5, 6, 7]), "[1, 2, 3, 4, 5, 6, ...]")
79
80        # Sets give up after 6 as well
81        eq(r(set([])), "set()")
82        eq(r(set([1])), "{1}")
83        eq(r(set([1, 2, 3])), "{1, 2, 3}")
84        eq(r(set([1, 2, 3, 4, 5, 6])), "{1, 2, 3, 4, 5, 6}")
85        eq(r(set([1, 2, 3, 4, 5, 6, 7])), "{1, 2, 3, 4, 5, 6, ...}")
86
87        # Frozensets give up after 6 as well
88        eq(r(frozenset([])), "frozenset()")
89        eq(r(frozenset([1])), "frozenset({1})")
90        eq(r(frozenset([1, 2, 3])), "frozenset({1, 2, 3})")
91        eq(r(frozenset([1, 2, 3, 4, 5, 6])), "frozenset({1, 2, 3, 4, 5, 6})")
92        eq(r(frozenset([1, 2, 3, 4, 5, 6, 7])), "frozenset({1, 2, 3, 4, 5, 6, ...})")
93
94        # collections.deque after 6
95        eq(r(deque([1, 2, 3, 4, 5, 6, 7])), "deque([1, 2, 3, 4, 5, 6, ...])")
96
97        # Dictionaries give up after 4.
98        eq(r({}), "{}")
99        d = {'alice': 1, 'bob': 2, 'charles': 3, 'dave': 4}
100        eq(r(d), "{'alice': 1, 'bob': 2, 'charles': 3, 'dave': 4}")
101        d['arthur'] = 1
102        eq(r(d), "{'alice': 1, 'arthur': 1, 'bob': 2, 'charles': 3, ...}")
103
104        # array.array after 5.
105        eq(r(array('i')), "array('i')")
106        eq(r(array('i', [1])), "array('i', [1])")
107        eq(r(array('i', [1, 2])), "array('i', [1, 2])")
108        eq(r(array('i', [1, 2, 3])), "array('i', [1, 2, 3])")
109        eq(r(array('i', [1, 2, 3, 4])), "array('i', [1, 2, 3, 4])")
110        eq(r(array('i', [1, 2, 3, 4, 5])), "array('i', [1, 2, 3, 4, 5])")
111        eq(r(array('i', [1, 2, 3, 4, 5, 6])),
112                   "array('i', [1, 2, 3, 4, 5, ...])")
113
114    def test_set_literal(self):
115        eq = self.assertEqual
116        eq(r({1}), "{1}")
117        eq(r({1, 2, 3}), "{1, 2, 3}")
118        eq(r({1, 2, 3, 4, 5, 6}), "{1, 2, 3, 4, 5, 6}")
119        eq(r({1, 2, 3, 4, 5, 6, 7}), "{1, 2, 3, 4, 5, 6, ...}")
120
121    def test_frozenset(self):
122        eq = self.assertEqual
123        eq(r(frozenset({1})), "frozenset({1})")
124        eq(r(frozenset({1, 2, 3})), "frozenset({1, 2, 3})")
125        eq(r(frozenset({1, 2, 3, 4, 5, 6})), "frozenset({1, 2, 3, 4, 5, 6})")
126        eq(r(frozenset({1, 2, 3, 4, 5, 6, 7})), "frozenset({1, 2, 3, 4, 5, 6, ...})")
127
128    def test_numbers(self):
129        eq = self.assertEqual
130        eq(r(123), repr(123))
131        eq(r(123), repr(123))
132        eq(r(1.0/3), repr(1.0/3))
133
134        n = 10**100
135        expected = repr(n)[:18] + "..." + repr(n)[-19:]
136        eq(r(n), expected)
137
138    def test_instance(self):
139        eq = self.assertEqual
140        i1 = ClassWithRepr("a")
141        eq(r(i1), repr(i1))
142
143        i2 = ClassWithRepr("x"*1000)
144        expected = repr(i2)[:13] + "..." + repr(i2)[-14:]
145        eq(r(i2), expected)
146
147        i3 = ClassWithFailingRepr()
148        eq(r(i3), ("<ClassWithFailingRepr instance at %#x>"%id(i3)))
149
150        s = r(ClassWithFailingRepr)
151        self.assertTrue(s.startswith("<class "))
152        self.assertTrue(s.endswith(">"))
153        self.assertIn(s.find("..."), [12, 13])
154
155    def test_lambda(self):
156        r = repr(lambda x: x)
157        self.assertTrue(r.startswith("<function ReprTests.test_lambda.<locals>.<lambda"), r)
158        # XXX anonymous functions?  see func_repr
159
160    def test_builtin_function(self):
161        eq = self.assertEqual
162        # Functions
163        eq(repr(hash), '<built-in function hash>')
164        # Methods
165        self.assertTrue(repr(''.split).startswith(
166            '<built-in method split of str object at 0x'))
167
168    def test_range(self):
169        eq = self.assertEqual
170        eq(repr(range(1)), 'range(0, 1)')
171        eq(repr(range(1, 2)), 'range(1, 2)')
172        eq(repr(range(1, 4, 3)), 'range(1, 4, 3)')
173
174    def test_nesting(self):
175        eq = self.assertEqual
176        # everything is meant to give up after 6 levels.
177        eq(r([[[[[[[]]]]]]]), "[[[[[[[]]]]]]]")
178        eq(r([[[[[[[[]]]]]]]]), "[[[[[[[...]]]]]]]")
179
180        eq(r(nestedTuple(6)), "(((((((),),),),),),)")
181        eq(r(nestedTuple(7)), "(((((((...),),),),),),)")
182
183        eq(r({ nestedTuple(5) : nestedTuple(5) }),
184           "{((((((),),),),),): ((((((),),),),),)}")
185        eq(r({ nestedTuple(6) : nestedTuple(6) }),
186           "{((((((...),),),),),): ((((((...),),),),),)}")
187
188        eq(r([[[[[[{}]]]]]]), "[[[[[[{}]]]]]]")
189        eq(r([[[[[[[{}]]]]]]]), "[[[[[[[...]]]]]]]")
190
191    def test_cell(self):
192        def get_cell():
193            x = 42
194            def inner():
195                return x
196            return inner
197        x = get_cell().__closure__[0]
198        self.assertRegex(repr(x), r'<cell at 0x[0-9A-Fa-f]+: '
199                                  r'int object at 0x[0-9A-Fa-f]+>')
200        self.assertRegex(r(x), r'<cell at 0x.*\.\.\..*>')
201
202    def test_descriptors(self):
203        eq = self.assertEqual
204        # method descriptors
205        eq(repr(dict.items), "<method 'items' of 'dict' objects>")
206        # XXX member descriptors
207        # XXX attribute descriptors
208        # XXX slot descriptors
209        # static and class methods
210        class C:
211            def foo(cls): pass
212        x = staticmethod(C.foo)
213        self.assertEqual(repr(x), f'<staticmethod({C.foo!r})>')
214        x = classmethod(C.foo)
215        self.assertEqual(repr(x), f'<classmethod({C.foo!r})>')
216
217    def test_unsortable(self):
218        # Repr.repr() used to call sorted() on sets, frozensets and dicts
219        # without taking into account that not all objects are comparable
220        x = set([1j, 2j, 3j])
221        y = frozenset(x)
222        z = {1j: 1, 2j: 2}
223        r(x)
224        r(y)
225        r(z)
226
227def write_file(path, text):
228    with open(path, 'w', encoding='ASCII') as fp:
229        fp.write(text)
230
231class LongReprTest(unittest.TestCase):
232    longname = 'areallylongpackageandmodulenametotestreprtruncation'
233
234    def setUp(self):
235        self.pkgname = os.path.join(self.longname)
236        self.subpkgname = os.path.join(self.longname, self.longname)
237        # Make the package and subpackage
238        shutil.rmtree(self.pkgname, ignore_errors=True)
239        os.mkdir(self.pkgname)
240        create_empty_file(os.path.join(self.pkgname, '__init__.py'))
241        shutil.rmtree(self.subpkgname, ignore_errors=True)
242        os.mkdir(self.subpkgname)
243        create_empty_file(os.path.join(self.subpkgname, '__init__.py'))
244        # Remember where we are
245        self.here = os.getcwd()
246        sys.path.insert(0, self.here)
247        # When regrtest is run with its -j option, this command alone is not
248        # enough.
249        importlib.invalidate_caches()
250
251    def tearDown(self):
252        actions = []
253        for dirpath, dirnames, filenames in os.walk(self.pkgname):
254            for name in dirnames + filenames:
255                actions.append(os.path.join(dirpath, name))
256        actions.append(self.pkgname)
257        actions.sort()
258        actions.reverse()
259        for p in actions:
260            if os.path.isdir(p):
261                os.rmdir(p)
262            else:
263                os.remove(p)
264        del sys.path[0]
265
266    def _check_path_limitations(self, module_name):
267        # base directory
268        source_path_len = len(self.here)
269        # a path separator + `longname` (twice)
270        source_path_len += 2 * (len(self.longname) + 1)
271        # a path separator + `module_name` + ".py"
272        source_path_len += len(module_name) + 1 + len(".py")
273        cached_path_len = (source_path_len +
274            len(importlib.util.cache_from_source("x.py")) - len("x.py"))
275        if os.name == 'nt' and cached_path_len >= 258:
276            # Under Windows, the max path len is 260 including C's terminating
277            # NUL character.
278            # (see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#maxpath)
279            self.skipTest("test paths too long (%d characters) for Windows' 260 character limit"
280                          % cached_path_len)
281        elif os.name == 'nt' and verbose:
282            print("cached_path_len =", cached_path_len)
283
284    def test_module(self):
285        self.maxDiff = None
286        self._check_path_limitations(self.pkgname)
287        create_empty_file(os.path.join(self.subpkgname, self.pkgname + '.py'))
288        importlib.invalidate_caches()
289        from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import areallylongpackageandmodulenametotestreprtruncation
290        module = areallylongpackageandmodulenametotestreprtruncation
291        self.assertEqual(repr(module), "<module %r from %r>" % (module.__name__, module.__file__))
292        self.assertEqual(repr(sys), "<module 'sys' (built-in)>")
293
294    def test_type(self):
295        self._check_path_limitations('foo')
296        eq = self.assertEqual
297        write_file(os.path.join(self.subpkgname, 'foo.py'), '''\
298class foo(object):
299    pass
300''')
301        importlib.invalidate_caches()
302        from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import foo
303        eq(repr(foo.foo),
304               "<class '%s.foo'>" % foo.__name__)
305
306    @unittest.skip('need a suitable object')
307    def test_object(self):
308        # XXX Test the repr of a type with a really long tp_name but with no
309        # tp_repr.  WIBNI we had ::Inline? :)
310        pass
311
312    def test_class(self):
313        self._check_path_limitations('bar')
314        write_file(os.path.join(self.subpkgname, 'bar.py'), '''\
315class bar:
316    pass
317''')
318        importlib.invalidate_caches()
319        from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import bar
320        # Module name may be prefixed with "test.", depending on how run.
321        self.assertEqual(repr(bar.bar), "<class '%s.bar'>" % bar.__name__)
322
323    def test_instance(self):
324        self._check_path_limitations('baz')
325        write_file(os.path.join(self.subpkgname, 'baz.py'), '''\
326class baz:
327    pass
328''')
329        importlib.invalidate_caches()
330        from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import baz
331        ibaz = baz.baz()
332        self.assertTrue(repr(ibaz).startswith(
333            "<%s.baz object at 0x" % baz.__name__))
334
335    def test_method(self):
336        self._check_path_limitations('qux')
337        eq = self.assertEqual
338        write_file(os.path.join(self.subpkgname, 'qux.py'), '''\
339class aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
340    def amethod(self): pass
341''')
342        importlib.invalidate_caches()
343        from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import qux
344        # Unbound methods first
345        r = repr(qux.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod)
346        self.assertTrue(r.startswith('<function aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod'), r)
347        # Bound method next
348        iqux = qux.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa()
349        r = repr(iqux.amethod)
350        self.assertTrue(r.startswith(
351            '<bound method aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod of <%s.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa object at 0x' \
352            % (qux.__name__,) ), r)
353
354    @unittest.skip('needs a built-in function with a really long name')
355    def test_builtin_function(self):
356        # XXX test built-in functions and methods with really long names
357        pass
358
359class ClassWithRepr:
360    def __init__(self, s):
361        self.s = s
362    def __repr__(self):
363        return "ClassWithRepr(%r)" % self.s
364
365
366class ClassWithFailingRepr:
367    def __repr__(self):
368        raise Exception("This should be caught by Repr.repr_instance")
369
370class MyContainer:
371    'Helper class for TestRecursiveRepr'
372    def __init__(self, values):
373        self.values = list(values)
374    def append(self, value):
375        self.values.append(value)
376    @recursive_repr()
377    def __repr__(self):
378        return '<' + ', '.join(map(str, self.values)) + '>'
379
380class MyContainer2(MyContainer):
381    @recursive_repr('+++')
382    def __repr__(self):
383        return '<' + ', '.join(map(str, self.values)) + '>'
384
385class MyContainer3:
386    def __repr__(self):
387        'Test document content'
388        pass
389    wrapped = __repr__
390    wrapper = recursive_repr()(wrapped)
391
392class TestRecursiveRepr(unittest.TestCase):
393    def test_recursive_repr(self):
394        m = MyContainer(list('abcde'))
395        m.append(m)
396        m.append('x')
397        m.append(m)
398        self.assertEqual(repr(m), '<a, b, c, d, e, ..., x, ...>')
399        m = MyContainer2(list('abcde'))
400        m.append(m)
401        m.append('x')
402        m.append(m)
403        self.assertEqual(repr(m), '<a, b, c, d, e, +++, x, +++>')
404
405    def test_assigned_attributes(self):
406        from functools import WRAPPER_ASSIGNMENTS as assigned
407        wrapped = MyContainer3.wrapped
408        wrapper = MyContainer3.wrapper
409        for name in assigned:
410            self.assertIs(getattr(wrapper, name), getattr(wrapped, name))
411
412if __name__ == "__main__":
413    unittest.main()
414