• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os
2import sys
3import contextlib
4import importlib.util
5import inspect
6import pydoc
7import py_compile
8import keyword
9import _pickle
10import pkgutil
11import re
12import stat
13import string
14import tempfile
15import test.support
16import time
17import types
18import typing
19import unittest
20import urllib.parse
21import xml.etree
22import xml.etree.ElementTree
23import textwrap
24from io import StringIO
25from collections import namedtuple
26from test.support import os_helper
27from test.support.script_helper import assert_python_ok
28from test.support import threading_helper
29from test.support import (reap_children, captured_output, captured_stdout,
30                          captured_stderr, requires_docstrings)
31from test.support.os_helper import (TESTFN, rmtree, unlink)
32from test import pydoc_mod
33
34
35class nonascii:
36    'Це не латиниця'
37    pass
38
39if test.support.HAVE_DOCSTRINGS:
40    expected_data_docstrings = (
41        'dictionary for instance variables (if defined)',
42        'list of weak references to the object (if defined)',
43        ) * 2
44else:
45    expected_data_docstrings = ('', '', '', '')
46
47expected_text_pattern = """
48NAME
49    test.pydoc_mod - This is a test module for test_pydoc
50%s
51CLASSES
52    builtins.object
53        A
54        B
55        C
56\x20\x20\x20\x20
57    class A(builtins.object)
58     |  Hello and goodbye
59     |\x20\x20
60     |  Methods defined here:
61     |\x20\x20
62     |  __init__()
63     |      Wow, I have no function!
64     |\x20\x20
65     |  ----------------------------------------------------------------------
66     |  Data descriptors defined here:
67     |\x20\x20
68     |  __dict__%s
69     |\x20\x20
70     |  __weakref__%s
71\x20\x20\x20\x20
72    class B(builtins.object)
73     |  Data descriptors defined here:
74     |\x20\x20
75     |  __dict__%s
76     |\x20\x20
77     |  __weakref__%s
78     |\x20\x20
79     |  ----------------------------------------------------------------------
80     |  Data and other attributes defined here:
81     |\x20\x20
82     |  NO_MEANING = 'eggs'
83     |\x20\x20
84     |  __annotations__ = {'NO_MEANING': <class 'str'>}
85\x20\x20\x20\x20
86    class C(builtins.object)
87     |  Methods defined here:
88     |\x20\x20
89     |  get_answer(self)
90     |      Return say_no()
91     |\x20\x20
92     |  is_it_true(self)
93     |      Return self.get_answer()
94     |\x20\x20
95     |  say_no(self)
96     |\x20\x20
97     |  ----------------------------------------------------------------------
98     |  Data descriptors defined here:
99     |\x20\x20
100     |  __dict__
101     |      dictionary for instance variables (if defined)
102     |\x20\x20
103     |  __weakref__
104     |      list of weak references to the object (if defined)
105
106FUNCTIONS
107    doc_func()
108        This function solves all of the world's problems:
109        hunger
110        lack of Python
111        war
112\x20\x20\x20\x20
113    nodoc_func()
114
115DATA
116    __xyz__ = 'X, Y and Z'
117
118VERSION
119    1.2.3.4
120
121AUTHOR
122    Benjamin Peterson
123
124CREDITS
125    Nobody
126
127FILE
128    %s
129""".strip()
130
131expected_text_data_docstrings = tuple('\n     |      ' + s if s else ''
132                                      for s in expected_data_docstrings)
133
134expected_html_pattern = """
135<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="heading">
136<tr bgcolor="#7799ee">
137<td valign=bottom>&nbsp;<br>
138<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong><a href="test.html"><font color="#ffffff">test</font></a>.pydoc_mod</strong></big></big> (version 1.2.3.4)</font></td
139><td align=right valign=bottom
140><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:%s">%s</a>%s</font></td></tr></table>
141    <p><tt>This&nbsp;is&nbsp;a&nbsp;test&nbsp;module&nbsp;for&nbsp;test_pydoc</tt></p>
142<p>
143<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
144<tr bgcolor="#ee77aa">
145<td colspan=3 valign=bottom>&nbsp;<br>
146<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
147\x20\x20\x20\x20
148<tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
149<td width="100%%"><dl>
150<dt><font face="helvetica, arial"><a href="builtins.html#object">builtins.object</a>
151</font></dt><dd>
152<dl>
153<dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#A">A</a>
154</font></dt><dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#B">B</a>
155</font></dt><dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#C">C</a>
156</font></dt></dl>
157</dd>
158</dl>
159 <p>
160<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
161<tr bgcolor="#ffc8d8">
162<td colspan=3 valign=bottom>&nbsp;<br>
163<font color="#000000" face="helvetica, arial"><a name="A">class <strong>A</strong></a>(<a href="builtins.html#object">builtins.object</a>)</font></td></tr>
164\x20\x20\x20\x20
165<tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
166<td colspan=2><tt>Hello&nbsp;and&nbsp;goodbye<br>&nbsp;</tt></td></tr>
167<tr><td>&nbsp;</td>
168<td width="100%%">Methods defined here:<br>
169<dl><dt><a name="A-__init__"><strong>__init__</strong></a>()</dt><dd><tt>Wow,&nbsp;I&nbsp;have&nbsp;no&nbsp;function!</tt></dd></dl>
170
171<hr>
172Data descriptors defined here:<br>
173<dl><dt><strong>__dict__</strong></dt>
174<dd><tt>%s</tt></dd>
175</dl>
176<dl><dt><strong>__weakref__</strong></dt>
177<dd><tt>%s</tt></dd>
178</dl>
179</td></tr></table> <p>
180<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
181<tr bgcolor="#ffc8d8">
182<td colspan=3 valign=bottom>&nbsp;<br>
183<font color="#000000" face="helvetica, arial"><a name="B">class <strong>B</strong></a>(<a href="builtins.html#object">builtins.object</a>)</font></td></tr>
184\x20\x20\x20\x20
185<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
186<td width="100%%">Data descriptors defined here:<br>
187<dl><dt><strong>__dict__</strong></dt>
188<dd><tt>%s</tt></dd>
189</dl>
190<dl><dt><strong>__weakref__</strong></dt>
191<dd><tt>%s</tt></dd>
192</dl>
193<hr>
194Data and other attributes defined here:<br>
195<dl><dt><strong>NO_MEANING</strong> = 'eggs'</dl>
196
197<dl><dt><strong>__annotations__</strong> = {'NO_MEANING': &lt;class 'str'&gt;}</dl>
198
199</td></tr></table> <p>
200<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
201<tr bgcolor="#ffc8d8">
202<td colspan=3 valign=bottom>&nbsp;<br>
203<font color="#000000" face="helvetica, arial"><a name="C">class <strong>C</strong></a>(<a href="builtins.html#object">builtins.object</a>)</font></td></tr>
204\x20\x20\x20\x20
205<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
206<td width="100%%">Methods defined here:<br>
207<dl><dt><a name="C-get_answer"><strong>get_answer</strong></a>(self)</dt><dd><tt>Return&nbsp;<a href="#C-say_no">say_no</a>()</tt></dd></dl>
208
209<dl><dt><a name="C-is_it_true"><strong>is_it_true</strong></a>(self)</dt><dd><tt>Return&nbsp;self.<a href="#C-get_answer">get_answer</a>()</tt></dd></dl>
210
211<dl><dt><a name="C-say_no"><strong>say_no</strong></a>(self)</dt></dl>
212
213<hr>
214Data descriptors defined here:<br>
215<dl><dt><strong>__dict__</strong></dt>
216<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
217</dl>
218<dl><dt><strong>__weakref__</strong></dt>
219<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
220</dl>
221</td></tr></table></td></tr></table><p>
222<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
223<tr bgcolor="#eeaa77">
224<td colspan=3 valign=bottom>&nbsp;<br>
225<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
226\x20\x20\x20\x20
227<tr><td bgcolor="#eeaa77"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
228<td width="100%%"><dl><dt><a name="-doc_func"><strong>doc_func</strong></a>()</dt><dd><tt>This&nbsp;function&nbsp;solves&nbsp;all&nbsp;of&nbsp;the&nbsp;world's&nbsp;problems:<br>
229hunger<br>
230lack&nbsp;of&nbsp;Python<br>
231war</tt></dd></dl>
232 <dl><dt><a name="-nodoc_func"><strong>nodoc_func</strong></a>()</dt></dl>
233</td></tr></table><p>
234<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
235<tr bgcolor="#55aa55">
236<td colspan=3 valign=bottom>&nbsp;<br>
237<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
238\x20\x20\x20\x20
239<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
240<td width="100%%"><strong>__xyz__</strong> = 'X, Y and Z'</td></tr></table><p>
241<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
242<tr bgcolor="#7799ee">
243<td colspan=3 valign=bottom>&nbsp;<br>
244<font color="#ffffff" face="helvetica, arial"><big><strong>Author</strong></big></font></td></tr>
245\x20\x20\x20\x20
246<tr><td bgcolor="#7799ee"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
247<td width="100%%">Benjamin&nbsp;Peterson</td></tr></table><p>
248<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
249<tr bgcolor="#7799ee">
250<td colspan=3 valign=bottom>&nbsp;<br>
251<font color="#ffffff" face="helvetica, arial"><big><strong>Credits</strong></big></font></td></tr>
252\x20\x20\x20\x20
253<tr><td bgcolor="#7799ee"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
254<td width="100%%">Nobody</td></tr></table>
255""".strip() # ' <- emacs turd
256
257expected_html_data_docstrings = tuple(s.replace(' ', '&nbsp;')
258                                      for s in expected_data_docstrings)
259
260# output pattern for missing module
261missing_pattern = '''\
262No Python documentation found for %r.
263Use help() to get the interactive help utility.
264Use help(str) for help on the str class.'''.replace('\n', os.linesep)
265
266# output pattern for module with bad imports
267badimport_pattern = "problem in %s - ModuleNotFoundError: No module named %r"
268
269expected_dynamicattribute_pattern = """
270Help on class DA in module %s:
271
272class DA(builtins.object)
273 |  Data descriptors defined here:
274 |\x20\x20
275 |  __dict__%s
276 |\x20\x20
277 |  __weakref__%s
278 |\x20\x20
279 |  ham
280 |\x20\x20
281 |  ----------------------------------------------------------------------
282 |  Data and other attributes inherited from Meta:
283 |\x20\x20
284 |  ham = 'spam'
285""".strip()
286
287expected_virtualattribute_pattern1 = """
288Help on class Class in module %s:
289
290class Class(builtins.object)
291 |  Data and other attributes inherited from Meta:
292 |\x20\x20
293 |  LIFE = 42
294""".strip()
295
296expected_virtualattribute_pattern2 = """
297Help on class Class1 in module %s:
298
299class Class1(builtins.object)
300 |  Data and other attributes inherited from Meta1:
301 |\x20\x20
302 |  one = 1
303""".strip()
304
305expected_virtualattribute_pattern3 = """
306Help on class Class2 in module %s:
307
308class Class2(Class1)
309 |  Method resolution order:
310 |      Class2
311 |      Class1
312 |      builtins.object
313 |\x20\x20
314 |  Data and other attributes inherited from Meta1:
315 |\x20\x20
316 |  one = 1
317 |\x20\x20
318 |  ----------------------------------------------------------------------
319 |  Data and other attributes inherited from Meta3:
320 |\x20\x20
321 |  three = 3
322 |\x20\x20
323 |  ----------------------------------------------------------------------
324 |  Data and other attributes inherited from Meta2:
325 |\x20\x20
326 |  two = 2
327""".strip()
328
329expected_missingattribute_pattern = """
330Help on class C in module %s:
331
332class C(builtins.object)
333 |  Data and other attributes defined here:
334 |\x20\x20
335 |  here = 'present!'
336""".strip()
337
338def run_pydoc(module_name, *args, **env):
339    """
340    Runs pydoc on the specified module. Returns the stripped
341    output of pydoc.
342    """
343    args = args + (module_name,)
344    # do not write bytecode files to avoid caching errors
345    rc, out, err = assert_python_ok('-B', pydoc.__file__, *args, **env)
346    return out.strip()
347
348def get_pydoc_html(module):
349    "Returns pydoc generated output as html"
350    doc = pydoc.HTMLDoc()
351    output = doc.docmodule(module)
352    loc = doc.getdocloc(pydoc_mod) or ""
353    if loc:
354        loc = "<br><a href=\"" + loc + "\">Module Docs</a>"
355    return output.strip(), loc
356
357def get_pydoc_link(module):
358    "Returns a documentation web link of a module"
359    abspath = os.path.abspath
360    dirname = os.path.dirname
361    basedir = dirname(dirname(abspath(__file__)))
362    doc = pydoc.TextDoc()
363    loc = doc.getdocloc(module, basedir=basedir)
364    return loc
365
366def get_pydoc_text(module):
367    "Returns pydoc generated output as text"
368    doc = pydoc.TextDoc()
369    loc = doc.getdocloc(pydoc_mod) or ""
370    if loc:
371        loc = "\nMODULE DOCS\n    " + loc + "\n"
372
373    output = doc.docmodule(module)
374
375    # clean up the extra text formatting that pydoc performs
376    patt = re.compile('\b.')
377    output = patt.sub('', output)
378    return output.strip(), loc
379
380def get_html_title(text):
381    # Bit of hack, but good enough for test purposes
382    header, _, _ = text.partition("</head>")
383    _, _, title = header.partition("<title>")
384    title, _, _ = title.partition("</title>")
385    return title
386
387
388class PydocBaseTest(unittest.TestCase):
389
390    def _restricted_walk_packages(self, walk_packages, path=None):
391        """
392        A version of pkgutil.walk_packages() that will restrict itself to
393        a given path.
394        """
395        default_path = path or [os.path.dirname(__file__)]
396        def wrapper(path=None, prefix='', onerror=None):
397            return walk_packages(path or default_path, prefix, onerror)
398        return wrapper
399
400    @contextlib.contextmanager
401    def restrict_walk_packages(self, path=None):
402        walk_packages = pkgutil.walk_packages
403        pkgutil.walk_packages = self._restricted_walk_packages(walk_packages,
404                                                               path)
405        try:
406            yield
407        finally:
408            pkgutil.walk_packages = walk_packages
409
410    def call_url_handler(self, url, expected_title):
411        text = pydoc._url_handler(url, "text/html")
412        result = get_html_title(text)
413        # Check the title to ensure an unexpected error page was not returned
414        self.assertEqual(result, expected_title, text)
415        return text
416
417
418class PydocDocTest(unittest.TestCase):
419    maxDiff = None
420
421    @unittest.skipIf(sys.flags.optimize >= 2,
422                     "Docstrings are omitted with -O2 and above")
423    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
424                     'trace function introduces __locals__ unexpectedly')
425    @requires_docstrings
426    def test_html_doc(self):
427        result, doc_loc = get_pydoc_html(pydoc_mod)
428        mod_file = inspect.getabsfile(pydoc_mod)
429        mod_url = urllib.parse.quote(mod_file)
430        expected_html = expected_html_pattern % (
431                        (mod_url, mod_file, doc_loc) +
432                        expected_html_data_docstrings)
433        self.assertEqual(result, expected_html)
434
435    @unittest.skipIf(sys.flags.optimize >= 2,
436                     "Docstrings are omitted with -O2 and above")
437    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
438                     'trace function introduces __locals__ unexpectedly')
439    @requires_docstrings
440    def test_text_doc(self):
441        result, doc_loc = get_pydoc_text(pydoc_mod)
442        expected_text = expected_text_pattern % (
443                        (doc_loc,) +
444                        expected_text_data_docstrings +
445                        (inspect.getabsfile(pydoc_mod),))
446        self.assertEqual(expected_text, result)
447
448    def test_text_enum_member_with_value_zero(self):
449        # Test issue #20654 to ensure enum member with value 0 can be
450        # displayed. It used to throw KeyError: 'zero'.
451        import enum
452        class BinaryInteger(enum.IntEnum):
453            zero = 0
454            one = 1
455        doc = pydoc.render_doc(BinaryInteger)
456        self.assertIn('<BinaryInteger.zero: 0>', doc)
457
458    def test_mixed_case_module_names_are_lower_cased(self):
459        # issue16484
460        doc_link = get_pydoc_link(xml.etree.ElementTree)
461        self.assertIn('xml.etree.elementtree', doc_link)
462
463    def test_issue8225(self):
464        # Test issue8225 to ensure no doc link appears for xml.etree
465        result, doc_loc = get_pydoc_text(xml.etree)
466        self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link")
467
468    def test_getpager_with_stdin_none(self):
469        previous_stdin = sys.stdin
470        try:
471            sys.stdin = None
472            pydoc.getpager() # Shouldn't fail.
473        finally:
474            sys.stdin = previous_stdin
475
476    def test_non_str_name(self):
477        # issue14638
478        # Treat illegal (non-str) name like no name
479
480        class A:
481            __name__ = 42
482        class B:
483            pass
484        adoc = pydoc.render_doc(A())
485        bdoc = pydoc.render_doc(B())
486        self.assertEqual(adoc.replace("A", "B"), bdoc)
487
488    def test_not_here(self):
489        missing_module = "test.i_am_not_here"
490        result = str(run_pydoc(missing_module), 'ascii')
491        expected = missing_pattern % missing_module
492        self.assertEqual(expected, result,
493            "documentation for missing module found")
494
495    @unittest.skipIf(sys.flags.optimize >= 2,
496                     'Docstrings are omitted with -OO and above')
497    def test_not_ascii(self):
498        result = run_pydoc('test.test_pydoc.nonascii', PYTHONIOENCODING='ascii')
499        encoded = nonascii.__doc__.encode('ascii', 'backslashreplace')
500        self.assertIn(encoded, result)
501
502    def test_input_strip(self):
503        missing_module = " test.i_am_not_here "
504        result = str(run_pydoc(missing_module), 'ascii')
505        expected = missing_pattern % missing_module.strip()
506        self.assertEqual(expected, result)
507
508    def test_stripid(self):
509        # test with strings, other implementations might have different repr()
510        stripid = pydoc.stripid
511        # strip the id
512        self.assertEqual(stripid('<function stripid at 0x88dcee4>'),
513                         '<function stripid>')
514        self.assertEqual(stripid('<function stripid at 0x01F65390>'),
515                         '<function stripid>')
516        # nothing to strip, return the same text
517        self.assertEqual(stripid('42'), '42')
518        self.assertEqual(stripid("<type 'exceptions.Exception'>"),
519                         "<type 'exceptions.Exception'>")
520
521    def test_builtin_with_more_than_four_children(self):
522        """Tests help on builtin object which have more than four child classes.
523
524        When running help() on a builtin class which has child classes, it
525        should contain a "Built-in subclasses" section and only 4 classes
526        should be displayed with a hint on how many more subclasses are present.
527        For example:
528
529        >>> help(object)
530        Help on class object in module builtins:
531
532        class object
533         |  The most base type
534         |
535         |  Built-in subclasses:
536         |      async_generator
537         |      BaseException
538         |      builtin_function_or_method
539         |      bytearray
540         |      ... and 82 other subclasses
541        """
542        doc = pydoc.TextDoc()
543        text = doc.docclass(object)
544        snip = (" |  Built-in subclasses:\n"
545                " |      async_generator\n"
546                " |      BaseException\n"
547                " |      builtin_function_or_method\n"
548                " |      bytearray\n"
549                " |      ... and \\d+ other subclasses")
550        self.assertRegex(text, snip)
551
552    def test_builtin_with_child(self):
553        """Tests help on builtin object which have only child classes.
554
555        When running help() on a builtin class which has child classes, it
556        should contain a "Built-in subclasses" section. For example:
557
558        >>> help(ArithmeticError)
559        Help on class ArithmeticError in module builtins:
560
561        class ArithmeticError(Exception)
562         |  Base class for arithmetic errors.
563         |
564         ...
565         |
566         |  Built-in subclasses:
567         |      FloatingPointError
568         |      OverflowError
569         |      ZeroDivisionError
570        """
571        doc = pydoc.TextDoc()
572        text = doc.docclass(ArithmeticError)
573        snip = (" |  Built-in subclasses:\n"
574                " |      FloatingPointError\n"
575                " |      OverflowError\n"
576                " |      ZeroDivisionError")
577        self.assertIn(snip, text)
578
579    def test_builtin_with_grandchild(self):
580        """Tests help on builtin classes which have grandchild classes.
581
582        When running help() on a builtin class which has child classes, it
583        should contain a "Built-in subclasses" section. However, if it also has
584        grandchildren, these should not show up on the subclasses section.
585        For example:
586
587        >>> help(Exception)
588        Help on class Exception in module builtins:
589
590        class Exception(BaseException)
591         |  Common base class for all non-exit exceptions.
592         |
593         ...
594         |
595         |  Built-in subclasses:
596         |      ArithmeticError
597         |      AssertionError
598         |      AttributeError
599         ...
600        """
601        doc = pydoc.TextDoc()
602        text = doc.docclass(Exception)
603        snip = (" |  Built-in subclasses:\n"
604                " |      ArithmeticError\n"
605                " |      AssertionError\n"
606                " |      AttributeError")
607        self.assertIn(snip, text)
608        # Testing that the grandchild ZeroDivisionError does not show up
609        self.assertNotIn('ZeroDivisionError', text)
610
611    def test_builtin_no_child(self):
612        """Tests help on builtin object which have no child classes.
613
614        When running help() on a builtin class which has no child classes, it
615        should not contain any "Built-in subclasses" section. For example:
616
617        >>> help(ZeroDivisionError)
618
619        Help on class ZeroDivisionError in module builtins:
620
621        class ZeroDivisionError(ArithmeticError)
622         |  Second argument to a division or modulo operation was zero.
623         |
624         |  Method resolution order:
625         |      ZeroDivisionError
626         |      ArithmeticError
627         |      Exception
628         |      BaseException
629         |      object
630         |
631         |  Methods defined here:
632         ...
633        """
634        doc = pydoc.TextDoc()
635        text = doc.docclass(ZeroDivisionError)
636        # Testing that the subclasses section does not appear
637        self.assertNotIn('Built-in subclasses', text)
638
639    def test_builtin_on_metaclasses(self):
640        """Tests help on metaclasses.
641
642        When running help() on a metaclasses such as type, it
643        should not contain any "Built-in subclasses" section.
644        """
645        doc = pydoc.TextDoc()
646        text = doc.docclass(type)
647        # Testing that the subclasses section does not appear
648        self.assertNotIn('Built-in subclasses', text)
649
650    @unittest.skipIf(sys.flags.optimize >= 2,
651                     'Docstrings are omitted with -O2 and above')
652    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
653                     'trace function introduces __locals__ unexpectedly')
654    @requires_docstrings
655    def test_help_output_redirect(self):
656        # issue 940286, if output is set in Helper, then all output from
657        # Helper.help should be redirected
658        old_pattern = expected_text_pattern
659        getpager_old = pydoc.getpager
660        getpager_new = lambda: (lambda x: x)
661        self.maxDiff = None
662
663        buf = StringIO()
664        helper = pydoc.Helper(output=buf)
665        unused, doc_loc = get_pydoc_text(pydoc_mod)
666        module = "test.pydoc_mod"
667        help_header = """
668        Help on module test.pydoc_mod in test:
669
670        """.lstrip()
671        help_header = textwrap.dedent(help_header)
672        expected_help_pattern = help_header + expected_text_pattern
673
674        pydoc.getpager = getpager_new
675        try:
676            with captured_output('stdout') as output, \
677                 captured_output('stderr') as err:
678                helper.help(module)
679                result = buf.getvalue().strip()
680                expected_text = expected_help_pattern % (
681                                (doc_loc,) +
682                                expected_text_data_docstrings +
683                                (inspect.getabsfile(pydoc_mod),))
684                self.assertEqual('', output.getvalue())
685                self.assertEqual('', err.getvalue())
686                self.assertEqual(expected_text, result)
687        finally:
688            pydoc.getpager = getpager_old
689
690    def test_namedtuple_fields(self):
691        Person = namedtuple('Person', ['nickname', 'firstname'])
692        with captured_stdout() as help_io:
693            pydoc.help(Person)
694        helptext = help_io.getvalue()
695        self.assertIn("nickname", helptext)
696        self.assertIn("firstname", helptext)
697        self.assertIn("Alias for field number 0", helptext)
698        self.assertIn("Alias for field number 1", helptext)
699
700    def test_namedtuple_public_underscore(self):
701        NT = namedtuple('NT', ['abc', 'def'], rename=True)
702        with captured_stdout() as help_io:
703            pydoc.help(NT)
704        helptext = help_io.getvalue()
705        self.assertIn('_1', helptext)
706        self.assertIn('_replace', helptext)
707        self.assertIn('_asdict', helptext)
708
709    def test_synopsis(self):
710        self.addCleanup(unlink, TESTFN)
711        for encoding in ('ISO-8859-1', 'UTF-8'):
712            with open(TESTFN, 'w', encoding=encoding) as script:
713                if encoding != 'UTF-8':
714                    print('#coding: {}'.format(encoding), file=script)
715                print('"""line 1: h\xe9', file=script)
716                print('line 2: hi"""', file=script)
717            synopsis = pydoc.synopsis(TESTFN, {})
718            self.assertEqual(synopsis, 'line 1: h\xe9')
719
720    @unittest.skipIf(sys.flags.optimize >= 2,
721                     'Docstrings are omitted with -OO and above')
722    def test_synopsis_sourceless(self):
723        expected = os.__doc__.splitlines()[0]
724        filename = os.__cached__
725        synopsis = pydoc.synopsis(filename)
726
727        self.assertEqual(synopsis, expected)
728
729    def test_synopsis_sourceless_empty_doc(self):
730        with os_helper.temp_cwd() as test_dir:
731            init_path = os.path.join(test_dir, 'foomod42.py')
732            cached_path = importlib.util.cache_from_source(init_path)
733            with open(init_path, 'w') as fobj:
734                fobj.write("foo = 1")
735            py_compile.compile(init_path)
736            synopsis = pydoc.synopsis(init_path, {})
737            self.assertIsNone(synopsis)
738            synopsis_cached = pydoc.synopsis(cached_path, {})
739            self.assertIsNone(synopsis_cached)
740
741    def test_splitdoc_with_description(self):
742        example_string = "I Am A Doc\n\n\nHere is my description"
743        self.assertEqual(pydoc.splitdoc(example_string),
744                         ('I Am A Doc', '\nHere is my description'))
745
746    def test_is_package_when_not_package(self):
747        with os_helper.temp_cwd() as test_dir:
748            self.assertFalse(pydoc.ispackage(test_dir))
749
750    def test_is_package_when_is_package(self):
751        with os_helper.temp_cwd() as test_dir:
752            init_path = os.path.join(test_dir, '__init__.py')
753            open(init_path, 'w').close()
754            self.assertTrue(pydoc.ispackage(test_dir))
755            os.remove(init_path)
756
757    def test_allmethods(self):
758        # issue 17476: allmethods was no longer returning unbound methods.
759        # This test is a bit fragile in the face of changes to object and type,
760        # but I can't think of a better way to do it without duplicating the
761        # logic of the function under test.
762
763        class TestClass(object):
764            def method_returning_true(self):
765                return True
766
767        # What we expect to get back: everything on object...
768        expected = dict(vars(object))
769        # ...plus our unbound method...
770        expected['method_returning_true'] = TestClass.method_returning_true
771        # ...but not the non-methods on object.
772        del expected['__doc__']
773        del expected['__class__']
774        # inspect resolves descriptors on type into methods, but vars doesn't,
775        # so we need to update __subclasshook__ and __init_subclass__.
776        expected['__subclasshook__'] = TestClass.__subclasshook__
777        expected['__init_subclass__'] = TestClass.__init_subclass__
778
779        methods = pydoc.allmethods(TestClass)
780        self.assertDictEqual(methods, expected)
781
782    def test_method_aliases(self):
783        class A:
784            def tkraise(self, aboveThis=None):
785                """Raise this widget in the stacking order."""
786            lift = tkraise
787            def a_size(self):
788                """Return size"""
789        class B(A):
790            def itemconfigure(self, tagOrId, cnf=None, **kw):
791                """Configure resources of an item TAGORID."""
792            itemconfig = itemconfigure
793            b_size = A.a_size
794
795        doc = pydoc.render_doc(B)
796        # clean up the extra text formatting that pydoc performs
797        doc = re.sub('\b.', '', doc)
798        self.assertEqual(doc, '''\
799Python Library Documentation: class B in module %s
800
801class B(A)
802 |  Method resolution order:
803 |      B
804 |      A
805 |      builtins.object
806 |\x20\x20
807 |  Methods defined here:
808 |\x20\x20
809 |  b_size = a_size(self)
810 |\x20\x20
811 |  itemconfig = itemconfigure(self, tagOrId, cnf=None, **kw)
812 |\x20\x20
813 |  itemconfigure(self, tagOrId, cnf=None, **kw)
814 |      Configure resources of an item TAGORID.
815 |\x20\x20
816 |  ----------------------------------------------------------------------
817 |  Methods inherited from A:
818 |\x20\x20
819 |  a_size(self)
820 |      Return size
821 |\x20\x20
822 |  lift = tkraise(self, aboveThis=None)
823 |\x20\x20
824 |  tkraise(self, aboveThis=None)
825 |      Raise this widget in the stacking order.
826 |\x20\x20
827 |  ----------------------------------------------------------------------
828 |  Data descriptors inherited from A:
829 |\x20\x20
830 |  __dict__
831 |      dictionary for instance variables (if defined)
832 |\x20\x20
833 |  __weakref__
834 |      list of weak references to the object (if defined)
835''' % __name__)
836
837        doc = pydoc.render_doc(B, renderer=pydoc.HTMLDoc())
838        self.assertEqual(doc, '''\
839Python Library Documentation: class B in module %s
840
841<p>
842<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
843<tr bgcolor="#ffc8d8">
844<td colspan=3 valign=bottom>&nbsp;<br>
845<font color="#000000" face="helvetica, arial"><a name="B">class <strong>B</strong></a>(A)</font></td></tr>
846\x20\x20\x20\x20
847<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
848<td width="100%%"><dl><dt>Method resolution order:</dt>
849<dd>B</dd>
850<dd>A</dd>
851<dd><a href="builtins.html#object">builtins.object</a></dd>
852</dl>
853<hr>
854Methods defined here:<br>
855<dl><dt><a name="B-b_size"><strong>b_size</strong></a> = <a href="#B-a_size">a_size</a>(self)</dt></dl>
856
857<dl><dt><a name="B-itemconfig"><strong>itemconfig</strong></a> = <a href="#B-itemconfigure">itemconfigure</a>(self, tagOrId, cnf=None, **kw)</dt></dl>
858
859<dl><dt><a name="B-itemconfigure"><strong>itemconfigure</strong></a>(self, tagOrId, cnf=None, **kw)</dt><dd><tt>Configure&nbsp;resources&nbsp;of&nbsp;an&nbsp;item&nbsp;TAGORID.</tt></dd></dl>
860
861<hr>
862Methods inherited from A:<br>
863<dl><dt><a name="B-a_size"><strong>a_size</strong></a>(self)</dt><dd><tt>Return&nbsp;size</tt></dd></dl>
864
865<dl><dt><a name="B-lift"><strong>lift</strong></a> = <a href="#B-tkraise">tkraise</a>(self, aboveThis=None)</dt></dl>
866
867<dl><dt><a name="B-tkraise"><strong>tkraise</strong></a>(self, aboveThis=None)</dt><dd><tt>Raise&nbsp;this&nbsp;widget&nbsp;in&nbsp;the&nbsp;stacking&nbsp;order.</tt></dd></dl>
868
869<hr>
870Data descriptors inherited from A:<br>
871<dl><dt><strong>__dict__</strong></dt>
872<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
873</dl>
874<dl><dt><strong>__weakref__</strong></dt>
875<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
876</dl>
877</td></tr></table>\
878''' % __name__)
879
880
881class PydocImportTest(PydocBaseTest):
882
883    def setUp(self):
884        self.test_dir = os.mkdir(TESTFN)
885        self.addCleanup(rmtree, TESTFN)
886        importlib.invalidate_caches()
887
888    def test_badimport(self):
889        # This tests the fix for issue 5230, where if pydoc found the module
890        # but the module had an internal import error pydoc would report no doc
891        # found.
892        modname = 'testmod_xyzzy'
893        testpairs = (
894            ('i_am_not_here', 'i_am_not_here'),
895            ('test.i_am_not_here_either', 'test.i_am_not_here_either'),
896            ('test.i_am_not_here.neither_am_i', 'test.i_am_not_here'),
897            ('i_am_not_here.{}'.format(modname), 'i_am_not_here'),
898            ('test.{}'.format(modname), 'test.{}'.format(modname)),
899            )
900
901        sourcefn = os.path.join(TESTFN, modname) + os.extsep + "py"
902        for importstring, expectedinmsg in testpairs:
903            with open(sourcefn, 'w') as f:
904                f.write("import {}\n".format(importstring))
905            result = run_pydoc(modname, PYTHONPATH=TESTFN).decode("ascii")
906            expected = badimport_pattern % (modname, expectedinmsg)
907            self.assertEqual(expected, result)
908
909    def test_apropos_with_bad_package(self):
910        # Issue 7425 - pydoc -k failed when bad package on path
911        pkgdir = os.path.join(TESTFN, "syntaxerr")
912        os.mkdir(pkgdir)
913        badsyntax = os.path.join(pkgdir, "__init__") + os.extsep + "py"
914        with open(badsyntax, 'w') as f:
915            f.write("invalid python syntax = $1\n")
916        with self.restrict_walk_packages(path=[TESTFN]):
917            with captured_stdout() as out:
918                with captured_stderr() as err:
919                    pydoc.apropos('xyzzy')
920            # No result, no error
921            self.assertEqual(out.getvalue(), '')
922            self.assertEqual(err.getvalue(), '')
923            # The package name is still matched
924            with captured_stdout() as out:
925                with captured_stderr() as err:
926                    pydoc.apropos('syntaxerr')
927            self.assertEqual(out.getvalue().strip(), 'syntaxerr')
928            self.assertEqual(err.getvalue(), '')
929
930    def test_apropos_with_unreadable_dir(self):
931        # Issue 7367 - pydoc -k failed when unreadable dir on path
932        self.unreadable_dir = os.path.join(TESTFN, "unreadable")
933        os.mkdir(self.unreadable_dir, 0)
934        self.addCleanup(os.rmdir, self.unreadable_dir)
935        # Note, on Windows the directory appears to be still
936        #   readable so this is not really testing the issue there
937        with self.restrict_walk_packages(path=[TESTFN]):
938            with captured_stdout() as out:
939                with captured_stderr() as err:
940                    pydoc.apropos('SOMEKEY')
941        # No result, no error
942        self.assertEqual(out.getvalue(), '')
943        self.assertEqual(err.getvalue(), '')
944
945    def test_apropos_empty_doc(self):
946        pkgdir = os.path.join(TESTFN, 'walkpkg')
947        os.mkdir(pkgdir)
948        self.addCleanup(rmtree, pkgdir)
949        init_path = os.path.join(pkgdir, '__init__.py')
950        with open(init_path, 'w') as fobj:
951            fobj.write("foo = 1")
952        current_mode = stat.S_IMODE(os.stat(pkgdir).st_mode)
953        try:
954            os.chmod(pkgdir, current_mode & ~stat.S_IEXEC)
955            with self.restrict_walk_packages(path=[TESTFN]), captured_stdout() as stdout:
956                pydoc.apropos('')
957            self.assertIn('walkpkg', stdout.getvalue())
958        finally:
959            os.chmod(pkgdir, current_mode)
960
961    def test_url_search_package_error(self):
962        # URL handler search should cope with packages that raise exceptions
963        pkgdir = os.path.join(TESTFN, "test_error_package")
964        os.mkdir(pkgdir)
965        init = os.path.join(pkgdir, "__init__.py")
966        with open(init, "wt", encoding="ascii") as f:
967            f.write("""raise ValueError("ouch")\n""")
968        with self.restrict_walk_packages(path=[TESTFN]):
969            # Package has to be importable for the error to have any effect
970            saved_paths = tuple(sys.path)
971            sys.path.insert(0, TESTFN)
972            try:
973                with self.assertRaisesRegex(ValueError, "ouch"):
974                    import test_error_package  # Sanity check
975
976                text = self.call_url_handler("search?key=test_error_package",
977                    "Pydoc: Search Results")
978                found = ('<a href="test_error_package.html">'
979                    'test_error_package</a>')
980                self.assertIn(found, text)
981            finally:
982                sys.path[:] = saved_paths
983
984    @unittest.skip('causes undesirable side-effects (#20128)')
985    def test_modules(self):
986        # See Helper.listmodules().
987        num_header_lines = 2
988        num_module_lines_min = 5  # Playing it safe.
989        num_footer_lines = 3
990        expected = num_header_lines + num_module_lines_min + num_footer_lines
991
992        output = StringIO()
993        helper = pydoc.Helper(output=output)
994        helper('modules')
995        result = output.getvalue().strip()
996        num_lines = len(result.splitlines())
997
998        self.assertGreaterEqual(num_lines, expected)
999
1000    @unittest.skip('causes undesirable side-effects (#20128)')
1001    def test_modules_search(self):
1002        # See Helper.listmodules().
1003        expected = 'pydoc - '
1004
1005        output = StringIO()
1006        helper = pydoc.Helper(output=output)
1007        with captured_stdout() as help_io:
1008            helper('modules pydoc')
1009        result = help_io.getvalue()
1010
1011        self.assertIn(expected, result)
1012
1013    @unittest.skip('some buildbots are not cooperating (#20128)')
1014    def test_modules_search_builtin(self):
1015        expected = 'gc - '
1016
1017        output = StringIO()
1018        helper = pydoc.Helper(output=output)
1019        with captured_stdout() as help_io:
1020            helper('modules garbage')
1021        result = help_io.getvalue()
1022
1023        self.assertTrue(result.startswith(expected))
1024
1025    def test_importfile(self):
1026        loaded_pydoc = pydoc.importfile(pydoc.__file__)
1027
1028        self.assertIsNot(loaded_pydoc, pydoc)
1029        self.assertEqual(loaded_pydoc.__name__, 'pydoc')
1030        self.assertEqual(loaded_pydoc.__file__, pydoc.__file__)
1031        self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__)
1032
1033
1034class TestDescriptions(unittest.TestCase):
1035
1036    def test_module(self):
1037        # Check that pydocfodder module can be described
1038        from test import pydocfodder
1039        doc = pydoc.render_doc(pydocfodder)
1040        self.assertIn("pydocfodder", doc)
1041
1042    def test_class(self):
1043        class C: "New-style class"
1044        c = C()
1045
1046        self.assertEqual(pydoc.describe(C), 'class C')
1047        self.assertEqual(pydoc.describe(c), 'C')
1048        expected = 'C in module %s object' % __name__
1049        self.assertIn(expected, pydoc.render_doc(c))
1050
1051    def test_typing_pydoc(self):
1052        def foo(data: typing.List[typing.Any],
1053                x: int) -> typing.Iterator[typing.Tuple[int, typing.Any]]:
1054            ...
1055        T = typing.TypeVar('T')
1056        class C(typing.Generic[T], typing.Mapping[int, str]): ...
1057        self.assertEqual(pydoc.render_doc(foo).splitlines()[-1],
1058                         'f\x08fo\x08oo\x08o(data: List[Any], x: int)'
1059                         ' -> Iterator[Tuple[int, Any]]')
1060        self.assertEqual(pydoc.render_doc(C).splitlines()[2],
1061                         'class C\x08C(collections.abc.Mapping, typing.Generic)')
1062
1063    def test_builtin(self):
1064        for name in ('str', 'str.translate', 'builtins.str',
1065                     'builtins.str.translate'):
1066            # test low-level function
1067            self.assertIsNotNone(pydoc.locate(name))
1068            # test high-level function
1069            try:
1070                pydoc.render_doc(name)
1071            except ImportError:
1072                self.fail('finding the doc of {!r} failed'.format(name))
1073
1074        for name in ('notbuiltins', 'strrr', 'strr.translate',
1075                     'str.trrrranslate', 'builtins.strrr',
1076                     'builtins.str.trrranslate'):
1077            self.assertIsNone(pydoc.locate(name))
1078            self.assertRaises(ImportError, pydoc.render_doc, name)
1079
1080    @staticmethod
1081    def _get_summary_line(o):
1082        text = pydoc.plain(pydoc.render_doc(o))
1083        lines = text.split('\n')
1084        assert len(lines) >= 2
1085        return lines[2]
1086
1087    @staticmethod
1088    def _get_summary_lines(o):
1089        text = pydoc.plain(pydoc.render_doc(o))
1090        lines = text.split('\n')
1091        return '\n'.join(lines[2:])
1092
1093    # these should include "self"
1094    def test_unbound_python_method(self):
1095        self.assertEqual(self._get_summary_line(textwrap.TextWrapper.wrap),
1096            "wrap(self, text)")
1097
1098    @requires_docstrings
1099    def test_unbound_builtin_method(self):
1100        self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
1101            "dump(self, obj, /)")
1102
1103    # these no longer include "self"
1104    def test_bound_python_method(self):
1105        t = textwrap.TextWrapper()
1106        self.assertEqual(self._get_summary_line(t.wrap),
1107            "wrap(text) method of textwrap.TextWrapper instance")
1108    def test_field_order_for_named_tuples(self):
1109        Person = namedtuple('Person', ['nickname', 'firstname', 'agegroup'])
1110        s = pydoc.render_doc(Person)
1111        self.assertLess(s.index('nickname'), s.index('firstname'))
1112        self.assertLess(s.index('firstname'), s.index('agegroup'))
1113
1114        class NonIterableFields:
1115            _fields = None
1116
1117        class NonHashableFields:
1118            _fields = [[]]
1119
1120        # Make sure these doesn't fail
1121        pydoc.render_doc(NonIterableFields)
1122        pydoc.render_doc(NonHashableFields)
1123
1124    @requires_docstrings
1125    def test_bound_builtin_method(self):
1126        s = StringIO()
1127        p = _pickle.Pickler(s)
1128        self.assertEqual(self._get_summary_line(p.dump),
1129            "dump(obj, /) method of _pickle.Pickler instance")
1130
1131    # this should *never* include self!
1132    @requires_docstrings
1133    def test_module_level_callable(self):
1134        self.assertEqual(self._get_summary_line(os.stat),
1135            "stat(path, *, dir_fd=None, follow_symlinks=True)")
1136
1137    @requires_docstrings
1138    def test_staticmethod(self):
1139        class X:
1140            @staticmethod
1141            def sm(x, y):
1142                '''A static method'''
1143                ...
1144        self.assertEqual(self._get_summary_lines(X.__dict__['sm']),
1145                         'sm(x, y)\n'
1146                         '    A static method\n')
1147        self.assertEqual(self._get_summary_lines(X.sm), """\
1148sm(x, y)
1149    A static method
1150""")
1151        self.assertIn("""
1152 |  Static methods defined here:
1153 |\x20\x20
1154 |  sm(x, y)
1155 |      A static method
1156""", pydoc.plain(pydoc.render_doc(X)))
1157
1158    @requires_docstrings
1159    def test_classmethod(self):
1160        class X:
1161            @classmethod
1162            def cm(cls, x):
1163                '''A class method'''
1164                ...
1165        self.assertEqual(self._get_summary_lines(X.__dict__['cm']),
1166                         'cm(...)\n'
1167                         '    A class method\n')
1168        self.assertEqual(self._get_summary_lines(X.cm), """\
1169cm(x) method of builtins.type instance
1170    A class method
1171""")
1172        self.assertIn("""
1173 |  Class methods defined here:
1174 |\x20\x20
1175 |  cm(x) from builtins.type
1176 |      A class method
1177""", pydoc.plain(pydoc.render_doc(X)))
1178
1179    @requires_docstrings
1180    def test_getset_descriptor(self):
1181        # Currently these attributes are implemented as getset descriptors
1182        # in CPython.
1183        self.assertEqual(self._get_summary_line(int.numerator), "numerator")
1184        self.assertEqual(self._get_summary_line(float.real), "real")
1185        self.assertEqual(self._get_summary_line(Exception.args), "args")
1186        self.assertEqual(self._get_summary_line(memoryview.obj), "obj")
1187
1188    @requires_docstrings
1189    def test_member_descriptor(self):
1190        # Currently these attributes are implemented as member descriptors
1191        # in CPython.
1192        self.assertEqual(self._get_summary_line(complex.real), "real")
1193        self.assertEqual(self._get_summary_line(range.start), "start")
1194        self.assertEqual(self._get_summary_line(slice.start), "start")
1195        self.assertEqual(self._get_summary_line(property.fget), "fget")
1196        self.assertEqual(self._get_summary_line(StopIteration.value), "value")
1197
1198    @requires_docstrings
1199    def test_slot_descriptor(self):
1200        class Point:
1201            __slots__ = 'x', 'y'
1202        self.assertEqual(self._get_summary_line(Point.x), "x")
1203
1204    @requires_docstrings
1205    def test_dict_attr_descriptor(self):
1206        class NS:
1207            pass
1208        self.assertEqual(self._get_summary_line(NS.__dict__['__dict__']),
1209                         "__dict__")
1210
1211    @requires_docstrings
1212    def test_structseq_member_descriptor(self):
1213        self.assertEqual(self._get_summary_line(type(sys.hash_info).width),
1214                         "width")
1215        self.assertEqual(self._get_summary_line(type(sys.flags).debug),
1216                         "debug")
1217        self.assertEqual(self._get_summary_line(type(sys.version_info).major),
1218                         "major")
1219        self.assertEqual(self._get_summary_line(type(sys.float_info).max),
1220                         "max")
1221
1222    @requires_docstrings
1223    def test_namedtuple_field_descriptor(self):
1224        Box = namedtuple('Box', ('width', 'height'))
1225        self.assertEqual(self._get_summary_lines(Box.width), """\
1226    Alias for field number 0
1227""")
1228
1229    @requires_docstrings
1230    def test_property(self):
1231        class Rect:
1232            @property
1233            def area(self):
1234                '''Area of the rect'''
1235                return self.w * self.h
1236
1237        self.assertEqual(self._get_summary_lines(Rect.area), """\
1238    Area of the rect
1239""")
1240        self.assertIn("""
1241 |  area
1242 |      Area of the rect
1243""", pydoc.plain(pydoc.render_doc(Rect)))
1244
1245    @requires_docstrings
1246    def test_custom_non_data_descriptor(self):
1247        class Descr:
1248            def __get__(self, obj, cls):
1249                if obj is None:
1250                    return self
1251                return 42
1252        class X:
1253            attr = Descr()
1254
1255        self.assertEqual(self._get_summary_lines(X.attr), """\
1256<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>""")
1257
1258        X.attr.__doc__ = 'Custom descriptor'
1259        self.assertEqual(self._get_summary_lines(X.attr), """\
1260<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>
1261    Custom descriptor
1262""")
1263
1264        X.attr.__name__ = 'foo'
1265        self.assertEqual(self._get_summary_lines(X.attr), """\
1266foo(...)
1267    Custom descriptor
1268""")
1269
1270    @requires_docstrings
1271    def test_custom_data_descriptor(self):
1272        class Descr:
1273            def __get__(self, obj, cls):
1274                if obj is None:
1275                    return self
1276                return 42
1277            def __set__(self, obj, cls):
1278                1/0
1279        class X:
1280            attr = Descr()
1281
1282        self.assertEqual(self._get_summary_lines(X.attr), "")
1283
1284        X.attr.__doc__ = 'Custom descriptor'
1285        self.assertEqual(self._get_summary_lines(X.attr), """\
1286    Custom descriptor
1287""")
1288
1289        X.attr.__name__ = 'foo'
1290        self.assertEqual(self._get_summary_lines(X.attr), """\
1291foo
1292    Custom descriptor
1293""")
1294
1295    def test_async_annotation(self):
1296        async def coro_function(ign) -> int:
1297            return 1
1298
1299        text = pydoc.plain(pydoc.plaintext.document(coro_function))
1300        self.assertIn('async coro_function', text)
1301
1302        html = pydoc.HTMLDoc().document(coro_function)
1303        self.assertIn(
1304            'async <a name="-coro_function"><strong>coro_function',
1305            html)
1306
1307    def test_async_generator_annotation(self):
1308        async def an_async_generator():
1309            yield 1
1310
1311        text = pydoc.plain(pydoc.plaintext.document(an_async_generator))
1312        self.assertIn('async an_async_generator', text)
1313
1314        html = pydoc.HTMLDoc().document(an_async_generator)
1315        self.assertIn(
1316            'async <a name="-an_async_generator"><strong>an_async_generator',
1317            html)
1318
1319    def test_html_for_https_links(self):
1320        def a_fn_with_https_link():
1321            """a link https://localhost/"""
1322            pass
1323
1324        html = pydoc.HTMLDoc().document(a_fn_with_https_link)
1325        self.assertIn(
1326            '<a href="https://localhost/">https://localhost/</a>',
1327            html
1328        )
1329
1330class PydocServerTest(unittest.TestCase):
1331    """Tests for pydoc._start_server"""
1332
1333    def test_server(self):
1334
1335        # Minimal test that starts the server, then stops it.
1336        def my_url_handler(url, content_type):
1337            text = 'the URL sent was: (%s, %s)' % (url, content_type)
1338            return text
1339
1340        serverthread = pydoc._start_server(my_url_handler, hostname='0.0.0.0', port=0)
1341        self.assertIn('0.0.0.0', serverthread.docserver.address)
1342
1343        starttime = time.monotonic()
1344        timeout = test.support.SHORT_TIMEOUT
1345
1346        while serverthread.serving:
1347            time.sleep(.01)
1348            if serverthread.serving and time.monotonic() - starttime > timeout:
1349                serverthread.stop()
1350                break
1351
1352        self.assertEqual(serverthread.error, None)
1353
1354
1355class PydocUrlHandlerTest(PydocBaseTest):
1356    """Tests for pydoc._url_handler"""
1357
1358    def test_content_type_err(self):
1359        f = pydoc._url_handler
1360        self.assertRaises(TypeError, f, 'A', '')
1361        self.assertRaises(TypeError, f, 'B', 'foobar')
1362
1363    def test_url_requests(self):
1364        # Test for the correct title in the html pages returned.
1365        # This tests the different parts of the URL handler without
1366        # getting too picky about the exact html.
1367        requests = [
1368            ("", "Pydoc: Index of Modules"),
1369            ("get?key=", "Pydoc: Index of Modules"),
1370            ("index", "Pydoc: Index of Modules"),
1371            ("topics", "Pydoc: Topics"),
1372            ("keywords", "Pydoc: Keywords"),
1373            ("pydoc", "Pydoc: module pydoc"),
1374            ("get?key=pydoc", "Pydoc: module pydoc"),
1375            ("search?key=pydoc", "Pydoc: Search Results"),
1376            ("topic?key=def", "Pydoc: KEYWORD def"),
1377            ("topic?key=STRINGS", "Pydoc: TOPIC STRINGS"),
1378            ("foobar", "Pydoc: Error - foobar"),
1379            ]
1380
1381        with self.restrict_walk_packages():
1382            for url, title in requests:
1383                self.call_url_handler(url, title)
1384
1385
1386class TestHelper(unittest.TestCase):
1387    def test_keywords(self):
1388        self.assertEqual(sorted(pydoc.Helper.keywords),
1389                         sorted(keyword.kwlist))
1390
1391class PydocWithMetaClasses(unittest.TestCase):
1392    @unittest.skipIf(sys.flags.optimize >= 2,
1393                     "Docstrings are omitted with -O2 and above")
1394    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1395                     'trace function introduces __locals__ unexpectedly')
1396    def test_DynamicClassAttribute(self):
1397        class Meta(type):
1398            def __getattr__(self, name):
1399                if name == 'ham':
1400                    return 'spam'
1401                return super().__getattr__(name)
1402        class DA(metaclass=Meta):
1403            @types.DynamicClassAttribute
1404            def ham(self):
1405                return 'eggs'
1406        expected_text_data_docstrings = tuple('\n |      ' + s if s else ''
1407                                      for s in expected_data_docstrings)
1408        output = StringIO()
1409        helper = pydoc.Helper(output=output)
1410        helper(DA)
1411        expected_text = expected_dynamicattribute_pattern % (
1412                (__name__,) + expected_text_data_docstrings[:2])
1413        result = output.getvalue().strip()
1414        self.assertEqual(expected_text, result)
1415
1416    @unittest.skipIf(sys.flags.optimize >= 2,
1417                     "Docstrings are omitted with -O2 and above")
1418    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1419                     'trace function introduces __locals__ unexpectedly')
1420    def test_virtualClassAttributeWithOneMeta(self):
1421        class Meta(type):
1422            def __dir__(cls):
1423                return ['__class__', '__module__', '__name__', 'LIFE']
1424            def __getattr__(self, name):
1425                if name =='LIFE':
1426                    return 42
1427                return super().__getattr(name)
1428        class Class(metaclass=Meta):
1429            pass
1430        output = StringIO()
1431        helper = pydoc.Helper(output=output)
1432        helper(Class)
1433        expected_text = expected_virtualattribute_pattern1 % __name__
1434        result = output.getvalue().strip()
1435        self.assertEqual(expected_text, result)
1436
1437    @unittest.skipIf(sys.flags.optimize >= 2,
1438                     "Docstrings are omitted with -O2 and above")
1439    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1440                     'trace function introduces __locals__ unexpectedly')
1441    def test_virtualClassAttributeWithTwoMeta(self):
1442        class Meta1(type):
1443            def __dir__(cls):
1444                return ['__class__', '__module__', '__name__', 'one']
1445            def __getattr__(self, name):
1446                if name =='one':
1447                    return 1
1448                return super().__getattr__(name)
1449        class Meta2(type):
1450            def __dir__(cls):
1451                return ['__class__', '__module__', '__name__', 'two']
1452            def __getattr__(self, name):
1453                if name =='two':
1454                    return 2
1455                return super().__getattr__(name)
1456        class Meta3(Meta1, Meta2):
1457            def __dir__(cls):
1458                return list(sorted(set(
1459                    ['__class__', '__module__', '__name__', 'three'] +
1460                    Meta1.__dir__(cls) + Meta2.__dir__(cls))))
1461            def __getattr__(self, name):
1462                if name =='three':
1463                    return 3
1464                return super().__getattr__(name)
1465        class Class1(metaclass=Meta1):
1466            pass
1467        class Class2(Class1, metaclass=Meta3):
1468            pass
1469        fail1 = fail2 = False
1470        output = StringIO()
1471        helper = pydoc.Helper(output=output)
1472        helper(Class1)
1473        expected_text1 = expected_virtualattribute_pattern2 % __name__
1474        result1 = output.getvalue().strip()
1475        self.assertEqual(expected_text1, result1)
1476        output = StringIO()
1477        helper = pydoc.Helper(output=output)
1478        helper(Class2)
1479        expected_text2 = expected_virtualattribute_pattern3 % __name__
1480        result2 = output.getvalue().strip()
1481        self.assertEqual(expected_text2, result2)
1482
1483    @unittest.skipIf(sys.flags.optimize >= 2,
1484                     "Docstrings are omitted with -O2 and above")
1485    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1486                     'trace function introduces __locals__ unexpectedly')
1487    def test_buggy_dir(self):
1488        class M(type):
1489            def __dir__(cls):
1490                return ['__class__', '__name__', 'missing', 'here']
1491        class C(metaclass=M):
1492            here = 'present!'
1493        output = StringIO()
1494        helper = pydoc.Helper(output=output)
1495        helper(C)
1496        expected_text = expected_missingattribute_pattern % __name__
1497        result = output.getvalue().strip()
1498        self.assertEqual(expected_text, result)
1499
1500    def test_resolve_false(self):
1501        # Issue #23008: pydoc enum.{,Int}Enum failed
1502        # because bool(enum.Enum) is False.
1503        with captured_stdout() as help_io:
1504            pydoc.help('enum.Enum')
1505        helptext = help_io.getvalue()
1506        self.assertIn('class Enum', helptext)
1507
1508
1509class TestInternalUtilities(unittest.TestCase):
1510
1511    def setUp(self):
1512        tmpdir = tempfile.TemporaryDirectory()
1513        self.argv0dir = tmpdir.name
1514        self.argv0 = os.path.join(tmpdir.name, "nonexistent")
1515        self.addCleanup(tmpdir.cleanup)
1516        self.abs_curdir = abs_curdir = os.getcwd()
1517        self.curdir_spellings = ["", os.curdir, abs_curdir]
1518
1519    def _get_revised_path(self, given_path, argv0=None):
1520        # Checking that pydoc.cli() actually calls pydoc._get_revised_path()
1521        # is handled via code review (at least for now).
1522        if argv0 is None:
1523            argv0 = self.argv0
1524        return pydoc._get_revised_path(given_path, argv0)
1525
1526    def _get_starting_path(self):
1527        # Get a copy of sys.path without the current directory.
1528        clean_path = sys.path.copy()
1529        for spelling in self.curdir_spellings:
1530            for __ in range(clean_path.count(spelling)):
1531                clean_path.remove(spelling)
1532        return clean_path
1533
1534    def test_sys_path_adjustment_adds_missing_curdir(self):
1535        clean_path = self._get_starting_path()
1536        expected_path = [self.abs_curdir] + clean_path
1537        self.assertEqual(self._get_revised_path(clean_path), expected_path)
1538
1539    def test_sys_path_adjustment_removes_argv0_dir(self):
1540        clean_path = self._get_starting_path()
1541        expected_path = [self.abs_curdir] + clean_path
1542        leading_argv0dir = [self.argv0dir] + clean_path
1543        self.assertEqual(self._get_revised_path(leading_argv0dir), expected_path)
1544        trailing_argv0dir = clean_path + [self.argv0dir]
1545        self.assertEqual(self._get_revised_path(trailing_argv0dir), expected_path)
1546
1547
1548    def test_sys_path_adjustment_protects_pydoc_dir(self):
1549        def _get_revised_path(given_path):
1550            return self._get_revised_path(given_path, argv0=pydoc.__file__)
1551        clean_path = self._get_starting_path()
1552        leading_argv0dir = [self.argv0dir] + clean_path
1553        expected_path = [self.abs_curdir] + leading_argv0dir
1554        self.assertEqual(_get_revised_path(leading_argv0dir), expected_path)
1555        trailing_argv0dir = clean_path + [self.argv0dir]
1556        expected_path = [self.abs_curdir] + trailing_argv0dir
1557        self.assertEqual(_get_revised_path(trailing_argv0dir), expected_path)
1558
1559    def test_sys_path_adjustment_when_curdir_already_included(self):
1560        clean_path = self._get_starting_path()
1561        for spelling in self.curdir_spellings:
1562            with self.subTest(curdir_spelling=spelling):
1563                # If curdir is already present, no alterations are made at all
1564                leading_curdir = [spelling] + clean_path
1565                self.assertIsNone(self._get_revised_path(leading_curdir))
1566                trailing_curdir = clean_path + [spelling]
1567                self.assertIsNone(self._get_revised_path(trailing_curdir))
1568                leading_argv0dir = [self.argv0dir] + leading_curdir
1569                self.assertIsNone(self._get_revised_path(leading_argv0dir))
1570                trailing_argv0dir = trailing_curdir + [self.argv0dir]
1571                self.assertIsNone(self._get_revised_path(trailing_argv0dir))
1572
1573
1574def setUpModule():
1575    thread_info = threading_helper.threading_setup()
1576    unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info)
1577    unittest.addModuleCleanup(reap_children)
1578
1579
1580if __name__ == "__main__":
1581    unittest.main()
1582