• 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.script_helper import assert_python_ok
27from test.support import (
28    TESTFN, rmtree,
29    reap_children, reap_threads, captured_output, captured_stdout,
30    captured_stderr, unlink, requires_docstrings
31)
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        class A:
480            __name__ = 42
481        class B:
482            pass
483        adoc = pydoc.render_doc(A())
484        bdoc = pydoc.render_doc(B())
485        self.assertEqual(adoc.replace("A", "B"), bdoc)
486
487    def test_not_here(self):
488        missing_module = "test.i_am_not_here"
489        result = str(run_pydoc(missing_module), 'ascii')
490        expected = missing_pattern % missing_module
491        self.assertEqual(expected, result,
492            "documentation for missing module found")
493
494    @unittest.skipIf(sys.flags.optimize >= 2,
495                     'Docstrings are omitted with -OO and above')
496    def test_not_ascii(self):
497        result = run_pydoc('test.test_pydoc.nonascii', PYTHONIOENCODING='ascii')
498        encoded = nonascii.__doc__.encode('ascii', 'backslashreplace')
499        self.assertIn(encoded, result)
500
501    def test_input_strip(self):
502        missing_module = " test.i_am_not_here "
503        result = str(run_pydoc(missing_module), 'ascii')
504        expected = missing_pattern % missing_module.strip()
505        self.assertEqual(expected, result)
506
507    def test_stripid(self):
508        # test with strings, other implementations might have different repr()
509        stripid = pydoc.stripid
510        # strip the id
511        self.assertEqual(stripid('<function stripid at 0x88dcee4>'),
512                         '<function stripid>')
513        self.assertEqual(stripid('<function stripid at 0x01F65390>'),
514                         '<function stripid>')
515        # nothing to strip, return the same text
516        self.assertEqual(stripid('42'), '42')
517        self.assertEqual(stripid("<type 'exceptions.Exception'>"),
518                         "<type 'exceptions.Exception'>")
519
520    def test_builtin_with_more_than_four_children(self):
521        """Tests help on builtin object which have more than four child classes.
522
523        When running help() on a builtin class which has child classes, it
524        should contain a "Built-in subclasses" section and only 4 classes
525        should be displayed with a hint on how many more subclasses are present.
526        For example:
527
528        >>> help(object)
529        Help on class object in module builtins:
530
531        class object
532         |  The most base type
533         |
534         |  Built-in subclasses:
535         |      async_generator
536         |      BaseException
537         |      builtin_function_or_method
538         |      bytearray
539         |      ... and 82 other subclasses
540        """
541        doc = pydoc.TextDoc()
542        text = doc.docclass(object)
543        snip = (" |  Built-in subclasses:\n"
544                " |      async_generator\n"
545                " |      BaseException\n"
546                " |      builtin_function_or_method\n"
547                " |      bytearray\n"
548                " |      ... and \\d+ other subclasses")
549        self.assertRegex(text, snip)
550
551    def test_builtin_with_child(self):
552        """Tests help on builtin object which have only child classes.
553
554        When running help() on a builtin class which has child classes, it
555        should contain a "Built-in subclasses" section. For example:
556
557        >>> help(ArithmeticError)
558        Help on class ArithmeticError in module builtins:
559
560        class ArithmeticError(Exception)
561         |  Base class for arithmetic errors.
562         |
563         ...
564         |
565         |  Built-in subclasses:
566         |      FloatingPointError
567         |      OverflowError
568         |      ZeroDivisionError
569        """
570        doc = pydoc.TextDoc()
571        text = doc.docclass(ArithmeticError)
572        snip = (" |  Built-in subclasses:\n"
573                " |      FloatingPointError\n"
574                " |      OverflowError\n"
575                " |      ZeroDivisionError")
576        self.assertIn(snip, text)
577
578    def test_builtin_with_grandchild(self):
579        """Tests help on builtin classes which have grandchild classes.
580
581        When running help() on a builtin class which has child classes, it
582        should contain a "Built-in subclasses" section. However, if it also has
583        grandchildren, these should not show up on the subclasses section.
584        For example:
585
586        >>> help(Exception)
587        Help on class Exception in module builtins:
588
589        class Exception(BaseException)
590         |  Common base class for all non-exit exceptions.
591         |
592         ...
593         |
594         |  Built-in subclasses:
595         |      ArithmeticError
596         |      AssertionError
597         |      AttributeError
598         ...
599        """
600        doc = pydoc.TextDoc()
601        text = doc.docclass(Exception)
602        snip = (" |  Built-in subclasses:\n"
603                " |      ArithmeticError\n"
604                " |      AssertionError\n"
605                " |      AttributeError")
606        self.assertIn(snip, text)
607        # Testing that the grandchild ZeroDivisionError does not show up
608        self.assertNotIn('ZeroDivisionError', text)
609
610    def test_builtin_no_child(self):
611        """Tests help on builtin object which have no child classes.
612
613        When running help() on a builtin class which has no child classes, it
614        should not contain any "Built-in subclasses" section. For example:
615
616        >>> help(ZeroDivisionError)
617
618        Help on class ZeroDivisionError in module builtins:
619
620        class ZeroDivisionError(ArithmeticError)
621         |  Second argument to a division or modulo operation was zero.
622         |
623         |  Method resolution order:
624         |      ZeroDivisionError
625         |      ArithmeticError
626         |      Exception
627         |      BaseException
628         |      object
629         |
630         |  Methods defined here:
631         ...
632        """
633        doc = pydoc.TextDoc()
634        text = doc.docclass(ZeroDivisionError)
635        # Testing that the subclasses section does not appear
636        self.assertNotIn('Built-in subclasses', text)
637
638    def test_builtin_on_metaclasses(self):
639        """Tests help on metaclasses.
640
641        When running help() on a metaclasses such as type, it
642        should not contain any "Built-in subclasses" section.
643        """
644        doc = pydoc.TextDoc()
645        text = doc.docclass(type)
646        # Testing that the subclasses section does not appear
647        self.assertNotIn('Built-in subclasses', text)
648
649    @unittest.skipIf(sys.flags.optimize >= 2,
650                     'Docstrings are omitted with -O2 and above')
651    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
652                     'trace function introduces __locals__ unexpectedly')
653    @requires_docstrings
654    def test_help_output_redirect(self):
655        # issue 940286, if output is set in Helper, then all output from
656        # Helper.help should be redirected
657        old_pattern = expected_text_pattern
658        getpager_old = pydoc.getpager
659        getpager_new = lambda: (lambda x: x)
660        self.maxDiff = None
661
662        buf = StringIO()
663        helper = pydoc.Helper(output=buf)
664        unused, doc_loc = get_pydoc_text(pydoc_mod)
665        module = "test.pydoc_mod"
666        help_header = """
667        Help on module test.pydoc_mod in test:
668
669        """.lstrip()
670        help_header = textwrap.dedent(help_header)
671        expected_help_pattern = help_header + expected_text_pattern
672
673        pydoc.getpager = getpager_new
674        try:
675            with captured_output('stdout') as output, \
676                 captured_output('stderr') as err:
677                helper.help(module)
678                result = buf.getvalue().strip()
679                expected_text = expected_help_pattern % (
680                                (doc_loc,) +
681                                expected_text_data_docstrings +
682                                (inspect.getabsfile(pydoc_mod),))
683                self.assertEqual('', output.getvalue())
684                self.assertEqual('', err.getvalue())
685                self.assertEqual(expected_text, result)
686        finally:
687            pydoc.getpager = getpager_old
688
689    def test_namedtuple_fields(self):
690        Person = namedtuple('Person', ['nickname', 'firstname'])
691        with captured_stdout() as help_io:
692            pydoc.help(Person)
693        helptext = help_io.getvalue()
694        self.assertIn("nickname", helptext)
695        self.assertIn("firstname", helptext)
696        self.assertIn("Alias for field number 0", helptext)
697        self.assertIn("Alias for field number 1", helptext)
698
699    def test_namedtuple_public_underscore(self):
700        NT = namedtuple('NT', ['abc', 'def'], rename=True)
701        with captured_stdout() as help_io:
702            pydoc.help(NT)
703        helptext = help_io.getvalue()
704        self.assertIn('_1', helptext)
705        self.assertIn('_replace', helptext)
706        self.assertIn('_asdict', helptext)
707
708    def test_synopsis(self):
709        self.addCleanup(unlink, TESTFN)
710        for encoding in ('ISO-8859-1', 'UTF-8'):
711            with open(TESTFN, 'w', encoding=encoding) as script:
712                if encoding != 'UTF-8':
713                    print('#coding: {}'.format(encoding), file=script)
714                print('"""line 1: h\xe9', file=script)
715                print('line 2: hi"""', file=script)
716            synopsis = pydoc.synopsis(TESTFN, {})
717            self.assertEqual(synopsis, 'line 1: h\xe9')
718
719    @unittest.skipIf(sys.flags.optimize >= 2,
720                     'Docstrings are omitted with -OO and above')
721    def test_synopsis_sourceless(self):
722        expected = os.__doc__.splitlines()[0]
723        filename = os.__cached__
724        synopsis = pydoc.synopsis(filename)
725
726        self.assertEqual(synopsis, expected)
727
728    def test_synopsis_sourceless_empty_doc(self):
729        with test.support.temp_cwd() as test_dir:
730            init_path = os.path.join(test_dir, 'foomod42.py')
731            cached_path = importlib.util.cache_from_source(init_path)
732            with open(init_path, 'w') as fobj:
733                fobj.write("foo = 1")
734            py_compile.compile(init_path)
735            synopsis = pydoc.synopsis(init_path, {})
736            self.assertIsNone(synopsis)
737            synopsis_cached = pydoc.synopsis(cached_path, {})
738            self.assertIsNone(synopsis_cached)
739
740    def test_splitdoc_with_description(self):
741        example_string = "I Am A Doc\n\n\nHere is my description"
742        self.assertEqual(pydoc.splitdoc(example_string),
743                         ('I Am A Doc', '\nHere is my description'))
744
745    def test_is_package_when_not_package(self):
746        with test.support.temp_cwd() as test_dir:
747            self.assertFalse(pydoc.ispackage(test_dir))
748
749    def test_is_package_when_is_package(self):
750        with test.support.temp_cwd() as test_dir:
751            init_path = os.path.join(test_dir, '__init__.py')
752            open(init_path, 'w').close()
753            self.assertTrue(pydoc.ispackage(test_dir))
754            os.remove(init_path)
755
756    def test_allmethods(self):
757        # issue 17476: allmethods was no longer returning unbound methods.
758        # This test is a bit fragile in the face of changes to object and type,
759        # but I can't think of a better way to do it without duplicating the
760        # logic of the function under test.
761
762        class TestClass(object):
763            def method_returning_true(self):
764                return True
765
766        # What we expect to get back: everything on object...
767        expected = dict(vars(object))
768        # ...plus our unbound method...
769        expected['method_returning_true'] = TestClass.method_returning_true
770        # ...but not the non-methods on object.
771        del expected['__doc__']
772        del expected['__class__']
773        # inspect resolves descriptors on type into methods, but vars doesn't,
774        # so we need to update __subclasshook__ and __init_subclass__.
775        expected['__subclasshook__'] = TestClass.__subclasshook__
776        expected['__init_subclass__'] = TestClass.__init_subclass__
777
778        methods = pydoc.allmethods(TestClass)
779        self.assertDictEqual(methods, expected)
780
781    def test_method_aliases(self):
782        class A:
783            def tkraise(self, aboveThis=None):
784                """Raise this widget in the stacking order."""
785            lift = tkraise
786            def a_size(self):
787                """Return size"""
788        class B(A):
789            def itemconfigure(self, tagOrId, cnf=None, **kw):
790                """Configure resources of an item TAGORID."""
791            itemconfig = itemconfigure
792            b_size = A.a_size
793
794        doc = pydoc.render_doc(B)
795        # clean up the extra text formatting that pydoc performs
796        doc = re.sub('\b.', '', doc)
797        self.assertEqual(doc, '''\
798Python Library Documentation: class B in module %s
799
800class B(A)
801 |  Method resolution order:
802 |      B
803 |      A
804 |      builtins.object
805 |\x20\x20
806 |  Methods defined here:
807 |\x20\x20
808 |  b_size = a_size(self)
809 |\x20\x20
810 |  itemconfig = itemconfigure(self, tagOrId, cnf=None, **kw)
811 |\x20\x20
812 |  itemconfigure(self, tagOrId, cnf=None, **kw)
813 |      Configure resources of an item TAGORID.
814 |\x20\x20
815 |  ----------------------------------------------------------------------
816 |  Methods inherited from A:
817 |\x20\x20
818 |  a_size(self)
819 |      Return size
820 |\x20\x20
821 |  lift = tkraise(self, aboveThis=None)
822 |\x20\x20
823 |  tkraise(self, aboveThis=None)
824 |      Raise this widget in the stacking order.
825 |\x20\x20
826 |  ----------------------------------------------------------------------
827 |  Data descriptors inherited from A:
828 |\x20\x20
829 |  __dict__
830 |      dictionary for instance variables (if defined)
831 |\x20\x20
832 |  __weakref__
833 |      list of weak references to the object (if defined)
834''' % __name__)
835
836        doc = pydoc.render_doc(B, renderer=pydoc.HTMLDoc())
837        self.assertEqual(doc, '''\
838Python Library Documentation: class B in module %s
839
840<p>
841<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
842<tr bgcolor="#ffc8d8">
843<td colspan=3 valign=bottom>&nbsp;<br>
844<font color="#000000" face="helvetica, arial"><a name="B">class <strong>B</strong></a>(A)</font></td></tr>
845\x20\x20\x20\x20
846<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
847<td width="100%%"><dl><dt>Method resolution order:</dt>
848<dd>B</dd>
849<dd>A</dd>
850<dd><a href="builtins.html#object">builtins.object</a></dd>
851</dl>
852<hr>
853Methods defined here:<br>
854<dl><dt><a name="B-b_size"><strong>b_size</strong></a> = <a href="#B-a_size">a_size</a>(self)</dt></dl>
855
856<dl><dt><a name="B-itemconfig"><strong>itemconfig</strong></a> = <a href="#B-itemconfigure">itemconfigure</a>(self, tagOrId, cnf=None, **kw)</dt></dl>
857
858<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>
859
860<hr>
861Methods inherited from A:<br>
862<dl><dt><a name="B-a_size"><strong>a_size</strong></a>(self)</dt><dd><tt>Return&nbsp;size</tt></dd></dl>
863
864<dl><dt><a name="B-lift"><strong>lift</strong></a> = <a href="#B-tkraise">tkraise</a>(self, aboveThis=None)</dt></dl>
865
866<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>
867
868<hr>
869Data descriptors inherited from A:<br>
870<dl><dt><strong>__dict__</strong></dt>
871<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
872</dl>
873<dl><dt><strong>__weakref__</strong></dt>
874<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
875</dl>
876</td></tr></table>\
877''' % __name__)
878
879
880class PydocImportTest(PydocBaseTest):
881
882    def setUp(self):
883        self.test_dir = os.mkdir(TESTFN)
884        self.addCleanup(rmtree, TESTFN)
885        importlib.invalidate_caches()
886
887    def test_badimport(self):
888        # This tests the fix for issue 5230, where if pydoc found the module
889        # but the module had an internal import error pydoc would report no doc
890        # found.
891        modname = 'testmod_xyzzy'
892        testpairs = (
893            ('i_am_not_here', 'i_am_not_here'),
894            ('test.i_am_not_here_either', 'test.i_am_not_here_either'),
895            ('test.i_am_not_here.neither_am_i', 'test.i_am_not_here'),
896            ('i_am_not_here.{}'.format(modname), 'i_am_not_here'),
897            ('test.{}'.format(modname), 'test.{}'.format(modname)),
898            )
899
900        sourcefn = os.path.join(TESTFN, modname) + os.extsep + "py"
901        for importstring, expectedinmsg in testpairs:
902            with open(sourcefn, 'w') as f:
903                f.write("import {}\n".format(importstring))
904            result = run_pydoc(modname, PYTHONPATH=TESTFN).decode("ascii")
905            expected = badimport_pattern % (modname, expectedinmsg)
906            self.assertEqual(expected, result)
907
908    def test_apropos_with_bad_package(self):
909        # Issue 7425 - pydoc -k failed when bad package on path
910        pkgdir = os.path.join(TESTFN, "syntaxerr")
911        os.mkdir(pkgdir)
912        badsyntax = os.path.join(pkgdir, "__init__") + os.extsep + "py"
913        with open(badsyntax, 'w') as f:
914            f.write("invalid python syntax = $1\n")
915        with self.restrict_walk_packages(path=[TESTFN]):
916            with captured_stdout() as out:
917                with captured_stderr() as err:
918                    pydoc.apropos('xyzzy')
919            # No result, no error
920            self.assertEqual(out.getvalue(), '')
921            self.assertEqual(err.getvalue(), '')
922            # The package name is still matched
923            with captured_stdout() as out:
924                with captured_stderr() as err:
925                    pydoc.apropos('syntaxerr')
926            self.assertEqual(out.getvalue().strip(), 'syntaxerr')
927            self.assertEqual(err.getvalue(), '')
928
929    def test_apropos_with_unreadable_dir(self):
930        # Issue 7367 - pydoc -k failed when unreadable dir on path
931        self.unreadable_dir = os.path.join(TESTFN, "unreadable")
932        os.mkdir(self.unreadable_dir, 0)
933        self.addCleanup(os.rmdir, self.unreadable_dir)
934        # Note, on Windows the directory appears to be still
935        #   readable so this is not really testing the issue there
936        with self.restrict_walk_packages(path=[TESTFN]):
937            with captured_stdout() as out:
938                with captured_stderr() as err:
939                    pydoc.apropos('SOMEKEY')
940        # No result, no error
941        self.assertEqual(out.getvalue(), '')
942        self.assertEqual(err.getvalue(), '')
943
944    def test_apropos_empty_doc(self):
945        pkgdir = os.path.join(TESTFN, 'walkpkg')
946        os.mkdir(pkgdir)
947        self.addCleanup(rmtree, pkgdir)
948        init_path = os.path.join(pkgdir, '__init__.py')
949        with open(init_path, 'w') as fobj:
950            fobj.write("foo = 1")
951        current_mode = stat.S_IMODE(os.stat(pkgdir).st_mode)
952        try:
953            os.chmod(pkgdir, current_mode & ~stat.S_IEXEC)
954            with self.restrict_walk_packages(path=[TESTFN]), captured_stdout() as stdout:
955                pydoc.apropos('')
956            self.assertIn('walkpkg', stdout.getvalue())
957        finally:
958            os.chmod(pkgdir, current_mode)
959
960    def test_url_search_package_error(self):
961        # URL handler search should cope with packages that raise exceptions
962        pkgdir = os.path.join(TESTFN, "test_error_package")
963        os.mkdir(pkgdir)
964        init = os.path.join(pkgdir, "__init__.py")
965        with open(init, "wt", encoding="ascii") as f:
966            f.write("""raise ValueError("ouch")\n""")
967        with self.restrict_walk_packages(path=[TESTFN]):
968            # Package has to be importable for the error to have any effect
969            saved_paths = tuple(sys.path)
970            sys.path.insert(0, TESTFN)
971            try:
972                with self.assertRaisesRegex(ValueError, "ouch"):
973                    import test_error_package  # Sanity check
974
975                text = self.call_url_handler("search?key=test_error_package",
976                    "Pydoc: Search Results")
977                found = ('<a href="test_error_package.html">'
978                    'test_error_package</a>')
979                self.assertIn(found, text)
980            finally:
981                sys.path[:] = saved_paths
982
983    @unittest.skip('causes undesirable side-effects (#20128)')
984    def test_modules(self):
985        # See Helper.listmodules().
986        num_header_lines = 2
987        num_module_lines_min = 5  # Playing it safe.
988        num_footer_lines = 3
989        expected = num_header_lines + num_module_lines_min + num_footer_lines
990
991        output = StringIO()
992        helper = pydoc.Helper(output=output)
993        helper('modules')
994        result = output.getvalue().strip()
995        num_lines = len(result.splitlines())
996
997        self.assertGreaterEqual(num_lines, expected)
998
999    @unittest.skip('causes undesirable side-effects (#20128)')
1000    def test_modules_search(self):
1001        # See Helper.listmodules().
1002        expected = 'pydoc - '
1003
1004        output = StringIO()
1005        helper = pydoc.Helper(output=output)
1006        with captured_stdout() as help_io:
1007            helper('modules pydoc')
1008        result = help_io.getvalue()
1009
1010        self.assertIn(expected, result)
1011
1012    @unittest.skip('some buildbots are not cooperating (#20128)')
1013    def test_modules_search_builtin(self):
1014        expected = 'gc - '
1015
1016        output = StringIO()
1017        helper = pydoc.Helper(output=output)
1018        with captured_stdout() as help_io:
1019            helper('modules garbage')
1020        result = help_io.getvalue()
1021
1022        self.assertTrue(result.startswith(expected))
1023
1024    def test_importfile(self):
1025        loaded_pydoc = pydoc.importfile(pydoc.__file__)
1026
1027        self.assertIsNot(loaded_pydoc, pydoc)
1028        self.assertEqual(loaded_pydoc.__name__, 'pydoc')
1029        self.assertEqual(loaded_pydoc.__file__, pydoc.__file__)
1030        self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__)
1031
1032
1033class TestDescriptions(unittest.TestCase):
1034
1035    def test_module(self):
1036        # Check that pydocfodder module can be described
1037        from test import pydocfodder
1038        doc = pydoc.render_doc(pydocfodder)
1039        self.assertIn("pydocfodder", doc)
1040
1041    def test_class(self):
1042        class C: "New-style class"
1043        c = C()
1044
1045        self.assertEqual(pydoc.describe(C), 'class C')
1046        self.assertEqual(pydoc.describe(c), 'C')
1047        expected = 'C in module %s object' % __name__
1048        self.assertIn(expected, pydoc.render_doc(c))
1049
1050    def test_typing_pydoc(self):
1051        def foo(data: typing.List[typing.Any],
1052                x: int) -> typing.Iterator[typing.Tuple[int, typing.Any]]:
1053            ...
1054        T = typing.TypeVar('T')
1055        class C(typing.Generic[T], typing.Mapping[int, str]): ...
1056        self.assertEqual(pydoc.render_doc(foo).splitlines()[-1],
1057                         'f\x08fo\x08oo\x08o(data: List[Any], x: int)'
1058                         ' -> Iterator[Tuple[int, Any]]')
1059        self.assertEqual(pydoc.render_doc(C).splitlines()[2],
1060                         'class C\x08C(collections.abc.Mapping, typing.Generic)')
1061
1062    def test_builtin(self):
1063        for name in ('str', 'str.translate', 'builtins.str',
1064                     'builtins.str.translate'):
1065            # test low-level function
1066            self.assertIsNotNone(pydoc.locate(name))
1067            # test high-level function
1068            try:
1069                pydoc.render_doc(name)
1070            except ImportError:
1071                self.fail('finding the doc of {!r} failed'.format(name))
1072
1073        for name in ('notbuiltins', 'strrr', 'strr.translate',
1074                     'str.trrrranslate', 'builtins.strrr',
1075                     'builtins.str.trrranslate'):
1076            self.assertIsNone(pydoc.locate(name))
1077            self.assertRaises(ImportError, pydoc.render_doc, name)
1078
1079    @staticmethod
1080    def _get_summary_line(o):
1081        text = pydoc.plain(pydoc.render_doc(o))
1082        lines = text.split('\n')
1083        assert len(lines) >= 2
1084        return lines[2]
1085
1086    @staticmethod
1087    def _get_summary_lines(o):
1088        text = pydoc.plain(pydoc.render_doc(o))
1089        lines = text.split('\n')
1090        return '\n'.join(lines[2:])
1091
1092    # these should include "self"
1093    def test_unbound_python_method(self):
1094        self.assertEqual(self._get_summary_line(textwrap.TextWrapper.wrap),
1095            "wrap(self, text)")
1096
1097    @requires_docstrings
1098    def test_unbound_builtin_method(self):
1099        self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
1100            "dump(self, obj, /)")
1101
1102    # these no longer include "self"
1103    def test_bound_python_method(self):
1104        t = textwrap.TextWrapper()
1105        self.assertEqual(self._get_summary_line(t.wrap),
1106            "wrap(text) method of textwrap.TextWrapper instance")
1107    def test_field_order_for_named_tuples(self):
1108        Person = namedtuple('Person', ['nickname', 'firstname', 'agegroup'])
1109        s = pydoc.render_doc(Person)
1110        self.assertLess(s.index('nickname'), s.index('firstname'))
1111        self.assertLess(s.index('firstname'), s.index('agegroup'))
1112
1113        class NonIterableFields:
1114            _fields = None
1115
1116        class NonHashableFields:
1117            _fields = [[]]
1118
1119        # Make sure these doesn't fail
1120        pydoc.render_doc(NonIterableFields)
1121        pydoc.render_doc(NonHashableFields)
1122
1123    @requires_docstrings
1124    def test_bound_builtin_method(self):
1125        s = StringIO()
1126        p = _pickle.Pickler(s)
1127        self.assertEqual(self._get_summary_line(p.dump),
1128            "dump(obj, /) method of _pickle.Pickler instance")
1129
1130    # this should *never* include self!
1131    @requires_docstrings
1132    def test_module_level_callable(self):
1133        self.assertEqual(self._get_summary_line(os.stat),
1134            "stat(path, *, dir_fd=None, follow_symlinks=True)")
1135
1136    @requires_docstrings
1137    def test_staticmethod(self):
1138        class X:
1139            @staticmethod
1140            def sm(x, y):
1141                '''A static method'''
1142                ...
1143        self.assertEqual(self._get_summary_lines(X.__dict__['sm']),
1144                         "<staticmethod object>")
1145        self.assertEqual(self._get_summary_lines(X.sm), """\
1146sm(x, y)
1147    A static method
1148""")
1149        self.assertIn("""
1150 |  Static methods defined here:
1151 |\x20\x20
1152 |  sm(x, y)
1153 |      A static method
1154""", pydoc.plain(pydoc.render_doc(X)))
1155
1156    @requires_docstrings
1157    def test_classmethod(self):
1158        class X:
1159            @classmethod
1160            def cm(cls, x):
1161                '''A class method'''
1162                ...
1163        self.assertEqual(self._get_summary_lines(X.__dict__['cm']),
1164                         "<classmethod object>")
1165        self.assertEqual(self._get_summary_lines(X.cm), """\
1166cm(x) method of builtins.type instance
1167    A class method
1168""")
1169        self.assertIn("""
1170 |  Class methods defined here:
1171 |\x20\x20
1172 |  cm(x) from builtins.type
1173 |      A class method
1174""", pydoc.plain(pydoc.render_doc(X)))
1175
1176    @requires_docstrings
1177    def test_getset_descriptor(self):
1178        # Currently these attributes are implemented as getset descriptors
1179        # in CPython.
1180        self.assertEqual(self._get_summary_line(int.numerator), "numerator")
1181        self.assertEqual(self._get_summary_line(float.real), "real")
1182        self.assertEqual(self._get_summary_line(Exception.args), "args")
1183        self.assertEqual(self._get_summary_line(memoryview.obj), "obj")
1184
1185    @requires_docstrings
1186    def test_member_descriptor(self):
1187        # Currently these attributes are implemented as member descriptors
1188        # in CPython.
1189        self.assertEqual(self._get_summary_line(complex.real), "real")
1190        self.assertEqual(self._get_summary_line(range.start), "start")
1191        self.assertEqual(self._get_summary_line(slice.start), "start")
1192        self.assertEqual(self._get_summary_line(property.fget), "fget")
1193        self.assertEqual(self._get_summary_line(StopIteration.value), "value")
1194
1195    @requires_docstrings
1196    def test_slot_descriptor(self):
1197        class Point:
1198            __slots__ = 'x', 'y'
1199        self.assertEqual(self._get_summary_line(Point.x), "x")
1200
1201    @requires_docstrings
1202    def test_dict_attr_descriptor(self):
1203        class NS:
1204            pass
1205        self.assertEqual(self._get_summary_line(NS.__dict__['__dict__']),
1206                         "__dict__")
1207
1208    @requires_docstrings
1209    def test_structseq_member_descriptor(self):
1210        self.assertEqual(self._get_summary_line(type(sys.hash_info).width),
1211                         "width")
1212        self.assertEqual(self._get_summary_line(type(sys.flags).debug),
1213                         "debug")
1214        self.assertEqual(self._get_summary_line(type(sys.version_info).major),
1215                         "major")
1216        self.assertEqual(self._get_summary_line(type(sys.float_info).max),
1217                         "max")
1218
1219    @requires_docstrings
1220    def test_namedtuple_field_descriptor(self):
1221        Box = namedtuple('Box', ('width', 'height'))
1222        self.assertEqual(self._get_summary_lines(Box.width), """\
1223    Alias for field number 0
1224""")
1225
1226    @requires_docstrings
1227    def test_property(self):
1228        class Rect:
1229            @property
1230            def area(self):
1231                '''Area of the rect'''
1232                return self.w * self.h
1233
1234        self.assertEqual(self._get_summary_lines(Rect.area), """\
1235    Area of the rect
1236""")
1237        self.assertIn("""
1238 |  area
1239 |      Area of the rect
1240""", pydoc.plain(pydoc.render_doc(Rect)))
1241
1242    @requires_docstrings
1243    def test_custom_non_data_descriptor(self):
1244        class Descr:
1245            def __get__(self, obj, cls):
1246                if obj is None:
1247                    return self
1248                return 42
1249        class X:
1250            attr = Descr()
1251
1252        self.assertEqual(self._get_summary_lines(X.attr), """\
1253<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>""")
1254
1255        X.attr.__doc__ = 'Custom descriptor'
1256        self.assertEqual(self._get_summary_lines(X.attr), """\
1257<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>""")
1258
1259        X.attr.__name__ = 'foo'
1260        self.assertEqual(self._get_summary_lines(X.attr), """\
1261foo(...)
1262    Custom descriptor
1263""")
1264
1265    @requires_docstrings
1266    def test_custom_data_descriptor(self):
1267        class Descr:
1268            def __get__(self, obj, cls):
1269                if obj is None:
1270                    return self
1271                return 42
1272            def __set__(self, obj, cls):
1273                1/0
1274        class X:
1275            attr = Descr()
1276
1277        self.assertEqual(self._get_summary_lines(X.attr), "")
1278
1279        X.attr.__doc__ = 'Custom descriptor'
1280        self.assertEqual(self._get_summary_lines(X.attr), """\
1281    Custom descriptor
1282""")
1283
1284        X.attr.__name__ = 'foo'
1285        self.assertEqual(self._get_summary_lines(X.attr), """\
1286foo
1287    Custom descriptor
1288""")
1289
1290    def test_async_annotation(self):
1291        async def coro_function(ign) -> int:
1292            return 1
1293
1294        text = pydoc.plain(pydoc.plaintext.document(coro_function))
1295        self.assertIn('async coro_function', text)
1296
1297        html = pydoc.HTMLDoc().document(coro_function)
1298        self.assertIn(
1299            'async <a name="-coro_function"><strong>coro_function',
1300            html)
1301
1302    def test_async_generator_annotation(self):
1303        async def an_async_generator():
1304            yield 1
1305
1306        text = pydoc.plain(pydoc.plaintext.document(an_async_generator))
1307        self.assertIn('async an_async_generator', text)
1308
1309        html = pydoc.HTMLDoc().document(an_async_generator)
1310        self.assertIn(
1311            'async <a name="-an_async_generator"><strong>an_async_generator',
1312            html)
1313
1314class PydocServerTest(unittest.TestCase):
1315    """Tests for pydoc._start_server"""
1316
1317    def test_server(self):
1318
1319        # Minimal test that starts the server, then stops it.
1320        def my_url_handler(url, content_type):
1321            text = 'the URL sent was: (%s, %s)' % (url, content_type)
1322            return text
1323
1324        serverthread = pydoc._start_server(my_url_handler, hostname='0.0.0.0', port=0)
1325        self.assertIn('0.0.0.0', serverthread.docserver.address)
1326
1327        starttime = time.monotonic()
1328        timeout = 1  #seconds
1329
1330        while serverthread.serving:
1331            time.sleep(.01)
1332            if serverthread.serving and time.monotonic() - starttime > timeout:
1333                serverthread.stop()
1334                break
1335
1336        self.assertEqual(serverthread.error, None)
1337
1338
1339class PydocUrlHandlerTest(PydocBaseTest):
1340    """Tests for pydoc._url_handler"""
1341
1342    def test_content_type_err(self):
1343        f = pydoc._url_handler
1344        self.assertRaises(TypeError, f, 'A', '')
1345        self.assertRaises(TypeError, f, 'B', 'foobar')
1346
1347    def test_url_requests(self):
1348        # Test for the correct title in the html pages returned.
1349        # This tests the different parts of the URL handler without
1350        # getting too picky about the exact html.
1351        requests = [
1352            ("", "Pydoc: Index of Modules"),
1353            ("get?key=", "Pydoc: Index of Modules"),
1354            ("index", "Pydoc: Index of Modules"),
1355            ("topics", "Pydoc: Topics"),
1356            ("keywords", "Pydoc: Keywords"),
1357            ("pydoc", "Pydoc: module pydoc"),
1358            ("get?key=pydoc", "Pydoc: module pydoc"),
1359            ("search?key=pydoc", "Pydoc: Search Results"),
1360            ("topic?key=def", "Pydoc: KEYWORD def"),
1361            ("topic?key=STRINGS", "Pydoc: TOPIC STRINGS"),
1362            ("foobar", "Pydoc: Error - foobar"),
1363            ("getfile?key=foobar", "Pydoc: Error - getfile?key=foobar"),
1364            ]
1365
1366        with self.restrict_walk_packages():
1367            for url, title in requests:
1368                self.call_url_handler(url, title)
1369
1370            path = string.__file__
1371            title = "Pydoc: getfile " + path
1372            url = "getfile?key=" + path
1373            self.call_url_handler(url, title)
1374
1375
1376class TestHelper(unittest.TestCase):
1377    def test_keywords(self):
1378        self.assertEqual(sorted(pydoc.Helper.keywords),
1379                         sorted(keyword.kwlist))
1380
1381class PydocWithMetaClasses(unittest.TestCase):
1382    @unittest.skipIf(sys.flags.optimize >= 2,
1383                     "Docstrings are omitted with -O2 and above")
1384    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1385                     'trace function introduces __locals__ unexpectedly')
1386    def test_DynamicClassAttribute(self):
1387        class Meta(type):
1388            def __getattr__(self, name):
1389                if name == 'ham':
1390                    return 'spam'
1391                return super().__getattr__(name)
1392        class DA(metaclass=Meta):
1393            @types.DynamicClassAttribute
1394            def ham(self):
1395                return 'eggs'
1396        expected_text_data_docstrings = tuple('\n |      ' + s if s else ''
1397                                      for s in expected_data_docstrings)
1398        output = StringIO()
1399        helper = pydoc.Helper(output=output)
1400        helper(DA)
1401        expected_text = expected_dynamicattribute_pattern % (
1402                (__name__,) + expected_text_data_docstrings[:2])
1403        result = output.getvalue().strip()
1404        self.assertEqual(expected_text, result)
1405
1406    @unittest.skipIf(sys.flags.optimize >= 2,
1407                     "Docstrings are omitted with -O2 and above")
1408    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1409                     'trace function introduces __locals__ unexpectedly')
1410    def test_virtualClassAttributeWithOneMeta(self):
1411        class Meta(type):
1412            def __dir__(cls):
1413                return ['__class__', '__module__', '__name__', 'LIFE']
1414            def __getattr__(self, name):
1415                if name =='LIFE':
1416                    return 42
1417                return super().__getattr(name)
1418        class Class(metaclass=Meta):
1419            pass
1420        output = StringIO()
1421        helper = pydoc.Helper(output=output)
1422        helper(Class)
1423        expected_text = expected_virtualattribute_pattern1 % __name__
1424        result = output.getvalue().strip()
1425        self.assertEqual(expected_text, result)
1426
1427    @unittest.skipIf(sys.flags.optimize >= 2,
1428                     "Docstrings are omitted with -O2 and above")
1429    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1430                     'trace function introduces __locals__ unexpectedly')
1431    def test_virtualClassAttributeWithTwoMeta(self):
1432        class Meta1(type):
1433            def __dir__(cls):
1434                return ['__class__', '__module__', '__name__', 'one']
1435            def __getattr__(self, name):
1436                if name =='one':
1437                    return 1
1438                return super().__getattr__(name)
1439        class Meta2(type):
1440            def __dir__(cls):
1441                return ['__class__', '__module__', '__name__', 'two']
1442            def __getattr__(self, name):
1443                if name =='two':
1444                    return 2
1445                return super().__getattr__(name)
1446        class Meta3(Meta1, Meta2):
1447            def __dir__(cls):
1448                return list(sorted(set(
1449                    ['__class__', '__module__', '__name__', 'three'] +
1450                    Meta1.__dir__(cls) + Meta2.__dir__(cls))))
1451            def __getattr__(self, name):
1452                if name =='three':
1453                    return 3
1454                return super().__getattr__(name)
1455        class Class1(metaclass=Meta1):
1456            pass
1457        class Class2(Class1, metaclass=Meta3):
1458            pass
1459        fail1 = fail2 = False
1460        output = StringIO()
1461        helper = pydoc.Helper(output=output)
1462        helper(Class1)
1463        expected_text1 = expected_virtualattribute_pattern2 % __name__
1464        result1 = output.getvalue().strip()
1465        self.assertEqual(expected_text1, result1)
1466        output = StringIO()
1467        helper = pydoc.Helper(output=output)
1468        helper(Class2)
1469        expected_text2 = expected_virtualattribute_pattern3 % __name__
1470        result2 = output.getvalue().strip()
1471        self.assertEqual(expected_text2, result2)
1472
1473    @unittest.skipIf(sys.flags.optimize >= 2,
1474                     "Docstrings are omitted with -O2 and above")
1475    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1476                     'trace function introduces __locals__ unexpectedly')
1477    def test_buggy_dir(self):
1478        class M(type):
1479            def __dir__(cls):
1480                return ['__class__', '__name__', 'missing', 'here']
1481        class C(metaclass=M):
1482            here = 'present!'
1483        output = StringIO()
1484        helper = pydoc.Helper(output=output)
1485        helper(C)
1486        expected_text = expected_missingattribute_pattern % __name__
1487        result = output.getvalue().strip()
1488        self.assertEqual(expected_text, result)
1489
1490    def test_resolve_false(self):
1491        # Issue #23008: pydoc enum.{,Int}Enum failed
1492        # because bool(enum.Enum) is False.
1493        with captured_stdout() as help_io:
1494            pydoc.help('enum.Enum')
1495        helptext = help_io.getvalue()
1496        self.assertIn('class Enum', helptext)
1497
1498
1499class TestInternalUtilities(unittest.TestCase):
1500
1501    def setUp(self):
1502        tmpdir = tempfile.TemporaryDirectory()
1503        self.argv0dir = tmpdir.name
1504        self.argv0 = os.path.join(tmpdir.name, "nonexistent")
1505        self.addCleanup(tmpdir.cleanup)
1506        self.abs_curdir = abs_curdir = os.getcwd()
1507        self.curdir_spellings = ["", os.curdir, abs_curdir]
1508
1509    def _get_revised_path(self, given_path, argv0=None):
1510        # Checking that pydoc.cli() actually calls pydoc._get_revised_path()
1511        # is handled via code review (at least for now).
1512        if argv0 is None:
1513            argv0 = self.argv0
1514        return pydoc._get_revised_path(given_path, argv0)
1515
1516    def _get_starting_path(self):
1517        # Get a copy of sys.path without the current directory.
1518        clean_path = sys.path.copy()
1519        for spelling in self.curdir_spellings:
1520            for __ in range(clean_path.count(spelling)):
1521                clean_path.remove(spelling)
1522        return clean_path
1523
1524    def test_sys_path_adjustment_adds_missing_curdir(self):
1525        clean_path = self._get_starting_path()
1526        expected_path = [self.abs_curdir] + clean_path
1527        self.assertEqual(self._get_revised_path(clean_path), expected_path)
1528
1529    def test_sys_path_adjustment_removes_argv0_dir(self):
1530        clean_path = self._get_starting_path()
1531        expected_path = [self.abs_curdir] + clean_path
1532        leading_argv0dir = [self.argv0dir] + clean_path
1533        self.assertEqual(self._get_revised_path(leading_argv0dir), expected_path)
1534        trailing_argv0dir = clean_path + [self.argv0dir]
1535        self.assertEqual(self._get_revised_path(trailing_argv0dir), expected_path)
1536
1537
1538    def test_sys_path_adjustment_protects_pydoc_dir(self):
1539        def _get_revised_path(given_path):
1540            return self._get_revised_path(given_path, argv0=pydoc.__file__)
1541        clean_path = self._get_starting_path()
1542        leading_argv0dir = [self.argv0dir] + clean_path
1543        expected_path = [self.abs_curdir] + leading_argv0dir
1544        self.assertEqual(_get_revised_path(leading_argv0dir), expected_path)
1545        trailing_argv0dir = clean_path + [self.argv0dir]
1546        expected_path = [self.abs_curdir] + trailing_argv0dir
1547        self.assertEqual(_get_revised_path(trailing_argv0dir), expected_path)
1548
1549    def test_sys_path_adjustment_when_curdir_already_included(self):
1550        clean_path = self._get_starting_path()
1551        for spelling in self.curdir_spellings:
1552            with self.subTest(curdir_spelling=spelling):
1553                # If curdir is already present, no alterations are made at all
1554                leading_curdir = [spelling] + clean_path
1555                self.assertIsNone(self._get_revised_path(leading_curdir))
1556                trailing_curdir = clean_path + [spelling]
1557                self.assertIsNone(self._get_revised_path(trailing_curdir))
1558                leading_argv0dir = [self.argv0dir] + leading_curdir
1559                self.assertIsNone(self._get_revised_path(leading_argv0dir))
1560                trailing_argv0dir = trailing_curdir + [self.argv0dir]
1561                self.assertIsNone(self._get_revised_path(trailing_argv0dir))
1562
1563
1564@reap_threads
1565def test_main():
1566    try:
1567        test.support.run_unittest(PydocDocTest,
1568                                  PydocImportTest,
1569                                  TestDescriptions,
1570                                  PydocServerTest,
1571                                  PydocUrlHandlerTest,
1572                                  TestHelper,
1573                                  PydocWithMetaClasses,
1574                                  TestInternalUtilities,
1575                                  )
1576    finally:
1577        reap_children()
1578
1579if __name__ == "__main__":
1580    test_main()
1581