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