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