1import os
2import sys
3import builtins
4import contextlib
5import importlib.util
6import inspect
7import pydoc
8import py_compile
9import keyword
10import _pickle
11import pkgutil
12import re
13import stat
14import string
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
34try:
35    import threading
36except ImportError:
37    threading = None
38
39class nonascii:
40    'Це не латиниця'
41    pass
42
43if test.support.HAVE_DOCSTRINGS:
44    expected_data_docstrings = (
45        'dictionary for instance variables (if defined)',
46        'list of weak references to the object (if defined)',
47        ) * 2
48else:
49    expected_data_docstrings = ('', '', '', '')
50
51expected_text_pattern = """
52NAME
53    test.pydoc_mod - This is a test module for test_pydoc
54%s
55CLASSES
56    builtins.object
57        A
58        B
59        C
60\x20\x20\x20\x20
61    class A(builtins.object)
62     |  Hello and goodbye
63     |\x20\x20
64     |  Methods defined here:
65     |\x20\x20
66     |  __init__()
67     |      Wow, I have no function!
68     |\x20\x20
69     |  ----------------------------------------------------------------------
70     |  Data descriptors defined here:
71     |\x20\x20
72     |  __dict__%s
73     |\x20\x20
74     |  __weakref__%s
75\x20\x20\x20\x20
76    class B(builtins.object)
77     |  Data descriptors defined here:
78     |\x20\x20
79     |  __dict__%s
80     |\x20\x20
81     |  __weakref__%s
82     |\x20\x20
83     |  ----------------------------------------------------------------------
84     |  Data and other attributes defined here:
85     |\x20\x20
86     |  NO_MEANING = 'eggs'
87     |\x20\x20
88     |  __annotations__ = {'NO_MEANING': <class 'str'>}
89\x20\x20\x20\x20
90    class C(builtins.object)
91     |  Methods defined here:
92     |\x20\x20
93     |  get_answer(self)
94     |      Return say_no()
95     |\x20\x20
96     |  is_it_true(self)
97     |      Return self.get_answer()
98     |\x20\x20
99     |  say_no(self)
100     |\x20\x20
101     |  ----------------------------------------------------------------------
102     |  Data descriptors defined here:
103     |\x20\x20
104     |  __dict__
105     |      dictionary for instance variables (if defined)
106     |\x20\x20
107     |  __weakref__
108     |      list of weak references to the object (if defined)
109
110FUNCTIONS
111    doc_func()
112        This function solves all of the world's problems:
113        hunger
114        lack of Python
115        war
116\x20\x20\x20\x20
117    nodoc_func()
118
119DATA
120    __xyz__ = 'X, Y and Z'
121
122VERSION
123    1.2.3.4
124
125AUTHOR
126    Benjamin Peterson
127
128CREDITS
129    Nobody
130
131FILE
132    %s
133""".strip()
134
135expected_text_data_docstrings = tuple('\n     |      ' + s if s else ''
136                                      for s in expected_data_docstrings)
137
138expected_html_pattern = """
139<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="heading">
140<tr bgcolor="#7799ee">
141<td valign=bottom>&nbsp;<br>
142<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
143><td align=right valign=bottom
144><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:%s">%s</a>%s</font></td></tr></table>
145    <p><tt>This&nbsp;is&nbsp;a&nbsp;test&nbsp;module&nbsp;for&nbsp;test_pydoc</tt></p>
146<p>
147<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
148<tr bgcolor="#ee77aa">
149<td colspan=3 valign=bottom>&nbsp;<br>
150<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
151\x20\x20\x20\x20
152<tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
153<td width="100%%"><dl>
154<dt><font face="helvetica, arial"><a href="builtins.html#object">builtins.object</a>
155</font></dt><dd>
156<dl>
157<dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#A">A</a>
158</font></dt><dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#B">B</a>
159</font></dt><dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#C">C</a>
160</font></dt></dl>
161</dd>
162</dl>
163 <p>
164<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
165<tr bgcolor="#ffc8d8">
166<td colspan=3 valign=bottom>&nbsp;<br>
167<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>
168\x20\x20\x20\x20
169<tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
170<td colspan=2><tt>Hello&nbsp;and&nbsp;goodbye<br>&nbsp;</tt></td></tr>
171<tr><td>&nbsp;</td>
172<td width="100%%">Methods defined here:<br>
173<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>
174
175<hr>
176Data descriptors defined here:<br>
177<dl><dt><strong>__dict__</strong></dt>
178<dd><tt>%s</tt></dd>
179</dl>
180<dl><dt><strong>__weakref__</strong></dt>
181<dd><tt>%s</tt></dd>
182</dl>
183</td></tr></table> <p>
184<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
185<tr bgcolor="#ffc8d8">
186<td colspan=3 valign=bottom>&nbsp;<br>
187<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>
188\x20\x20\x20\x20
189<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
190<td width="100%%">Data descriptors defined here:<br>
191<dl><dt><strong>__dict__</strong></dt>
192<dd><tt>%s</tt></dd>
193</dl>
194<dl><dt><strong>__weakref__</strong></dt>
195<dd><tt>%s</tt></dd>
196</dl>
197<hr>
198Data and other attributes defined here:<br>
199<dl><dt><strong>NO_MEANING</strong> = 'eggs'</dl>
200
201<dl><dt><strong>__annotations__</strong> = {'NO_MEANING': &lt;class 'str'&gt;}</dl>
202
203</td></tr></table> <p>
204<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
205<tr bgcolor="#ffc8d8">
206<td colspan=3 valign=bottom>&nbsp;<br>
207<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>
208\x20\x20\x20\x20
209<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
210<td width="100%%">Methods defined here:<br>
211<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>
212
213<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>
214
215<dl><dt><a name="C-say_no"><strong>say_no</strong></a>(self)</dt></dl>
216
217<hr>
218Data descriptors defined here:<br>
219<dl><dt><strong>__dict__</strong></dt>
220<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
221</dl>
222<dl><dt><strong>__weakref__</strong></dt>
223<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
224</dl>
225</td></tr></table></td></tr></table><p>
226<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
227<tr bgcolor="#eeaa77">
228<td colspan=3 valign=bottom>&nbsp;<br>
229<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
230\x20\x20\x20\x20
231<tr><td bgcolor="#eeaa77"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
232<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>
233hunger<br>
234lack&nbsp;of&nbsp;Python<br>
235war</tt></dd></dl>
236 <dl><dt><a name="-nodoc_func"><strong>nodoc_func</strong></a>()</dt></dl>
237</td></tr></table><p>
238<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
239<tr bgcolor="#55aa55">
240<td colspan=3 valign=bottom>&nbsp;<br>
241<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
242\x20\x20\x20\x20
243<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
244<td width="100%%"><strong>__xyz__</strong> = 'X, Y and Z'</td></tr></table><p>
245<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
246<tr bgcolor="#7799ee">
247<td colspan=3 valign=bottom>&nbsp;<br>
248<font color="#ffffff" face="helvetica, arial"><big><strong>Author</strong></big></font></td></tr>
249\x20\x20\x20\x20
250<tr><td bgcolor="#7799ee"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
251<td width="100%%">Benjamin&nbsp;Peterson</td></tr></table><p>
252<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
253<tr bgcolor="#7799ee">
254<td colspan=3 valign=bottom>&nbsp;<br>
255<font color="#ffffff" face="helvetica, arial"><big><strong>Credits</strong></big></font></td></tr>
256\x20\x20\x20\x20
257<tr><td bgcolor="#7799ee"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
258<td width="100%%">Nobody</td></tr></table>
259""".strip() # ' <- emacs turd
260
261expected_html_data_docstrings = tuple(s.replace(' ', '&nbsp;')
262                                      for s in expected_data_docstrings)
263
264# output pattern for missing module
265missing_pattern = '''\
266No Python documentation found for %r.
267Use help() to get the interactive help utility.
268Use help(str) for help on the str class.'''.replace('\n', os.linesep)
269
270# output pattern for module with bad imports
271badimport_pattern = "problem in %s - ModuleNotFoundError: No module named %r"
272
273expected_dynamicattribute_pattern = """
274Help on class DA in module %s:
275
276class DA(builtins.object)
277 |  Data descriptors defined here:
278 |\x20\x20
279 |  __dict__%s
280 |\x20\x20
281 |  __weakref__%s
282 |\x20\x20
283 |  ham
284 |\x20\x20
285 |  ----------------------------------------------------------------------
286 |  Data and other attributes inherited from Meta:
287 |\x20\x20
288 |  ham = 'spam'
289""".strip()
290
291expected_virtualattribute_pattern1 = """
292Help on class Class in module %s:
293
294class Class(builtins.object)
295 |  Data and other attributes inherited from Meta:
296 |\x20\x20
297 |  LIFE = 42
298""".strip()
299
300expected_virtualattribute_pattern2 = """
301Help on class Class1 in module %s:
302
303class Class1(builtins.object)
304 |  Data and other attributes inherited from Meta1:
305 |\x20\x20
306 |  one = 1
307""".strip()
308
309expected_virtualattribute_pattern3 = """
310Help on class Class2 in module %s:
311
312class Class2(Class1)
313 |  Method resolution order:
314 |      Class2
315 |      Class1
316 |      builtins.object
317 |\x20\x20
318 |  Data and other attributes inherited from Meta1:
319 |\x20\x20
320 |  one = 1
321 |\x20\x20
322 |  ----------------------------------------------------------------------
323 |  Data and other attributes inherited from Meta3:
324 |\x20\x20
325 |  three = 3
326 |\x20\x20
327 |  ----------------------------------------------------------------------
328 |  Data and other attributes inherited from Meta2:
329 |\x20\x20
330 |  two = 2
331""".strip()
332
333expected_missingattribute_pattern = """
334Help on class C in module %s:
335
336class C(builtins.object)
337 |  Data and other attributes defined here:
338 |\x20\x20
339 |  here = 'present!'
340""".strip()
341
342def run_pydoc(module_name, *args, **env):
343    """
344    Runs pydoc on the specified module. Returns the stripped
345    output of pydoc.
346    """
347    args = args + (module_name,)
348    # do not write bytecode files to avoid caching errors
349    rc, out, err = assert_python_ok('-B', pydoc.__file__, *args, **env)
350    return out.strip()
351
352def get_pydoc_html(module):
353    "Returns pydoc generated output as html"
354    doc = pydoc.HTMLDoc()
355    output = doc.docmodule(module)
356    loc = doc.getdocloc(pydoc_mod) or ""
357    if loc:
358        loc = "<br><a href=\"" + loc + "\">Module Docs</a>"
359    return output.strip(), loc
360
361def get_pydoc_link(module):
362    "Returns a documentation web link of a module"
363    dirname = os.path.dirname
364    basedir = dirname(dirname(__file__))
365    doc = pydoc.TextDoc()
366    loc = doc.getdocloc(module, basedir=basedir)
367    return loc
368
369def get_pydoc_text(module):
370    "Returns pydoc generated output as text"
371    doc = pydoc.TextDoc()
372    loc = doc.getdocloc(pydoc_mod) or ""
373    if loc:
374        loc = "\nMODULE DOCS\n    " + loc + "\n"
375
376    output = doc.docmodule(module)
377
378    # clean up the extra text formatting that pydoc performs
379    patt = re.compile('\b.')
380    output = patt.sub('', output)
381    return output.strip(), loc
382
383def get_html_title(text):
384    # Bit of hack, but good enough for test purposes
385    header, _, _ = text.partition("</head>")
386    _, _, title = header.partition("<title>")
387    title, _, _ = title.partition("</title>")
388    return title
389
390
391class PydocBaseTest(unittest.TestCase):
392
393    def _restricted_walk_packages(self, walk_packages, path=None):
394        """
395        A version of pkgutil.walk_packages() that will restrict itself to
396        a given path.
397        """
398        default_path = path or [os.path.dirname(__file__)]
399        def wrapper(path=None, prefix='', onerror=None):
400            return walk_packages(path or default_path, prefix, onerror)
401        return wrapper
402
403    @contextlib.contextmanager
404    def restrict_walk_packages(self, path=None):
405        walk_packages = pkgutil.walk_packages
406        pkgutil.walk_packages = self._restricted_walk_packages(walk_packages,
407                                                               path)
408        try:
409            yield
410        finally:
411            pkgutil.walk_packages = walk_packages
412
413    def call_url_handler(self, url, expected_title):
414        text = pydoc._url_handler(url, "text/html")
415        result = get_html_title(text)
416        # Check the title to ensure an unexpected error page was not returned
417        self.assertEqual(result, expected_title, text)
418        return text
419
420
421class PydocDocTest(unittest.TestCase):
422
423    @unittest.skipIf(sys.flags.optimize >= 2,
424                     "Docstrings are omitted with -O2 and above")
425    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
426                     'trace function introduces __locals__ unexpectedly')
427    @requires_docstrings
428    def test_html_doc(self):
429        result, doc_loc = get_pydoc_html(pydoc_mod)
430        mod_file = inspect.getabsfile(pydoc_mod)
431        mod_url = urllib.parse.quote(mod_file)
432        expected_html = expected_html_pattern % (
433                        (mod_url, mod_file, doc_loc) +
434                        expected_html_data_docstrings)
435        self.assertEqual(result, expected_html)
436
437    @unittest.skipIf(sys.flags.optimize >= 2,
438                     "Docstrings are omitted with -O2 and above")
439    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
440                     'trace function introduces __locals__ unexpectedly')
441    @requires_docstrings
442    def test_text_doc(self):
443        result, doc_loc = get_pydoc_text(pydoc_mod)
444        expected_text = expected_text_pattern % (
445                        (doc_loc,) +
446                        expected_text_data_docstrings +
447                        (inspect.getabsfile(pydoc_mod),))
448        self.assertEqual(expected_text, result)
449
450    def test_text_enum_member_with_value_zero(self):
451        # Test issue #20654 to ensure enum member with value 0 can be
452        # displayed. It used to throw KeyError: 'zero'.
453        import enum
454        class BinaryInteger(enum.IntEnum):
455            zero = 0
456            one = 1
457        doc = pydoc.render_doc(BinaryInteger)
458        self.assertIn('<BinaryInteger.zero: 0>', doc)
459
460    def test_mixed_case_module_names_are_lower_cased(self):
461        # issue16484
462        doc_link = get_pydoc_link(xml.etree.ElementTree)
463        self.assertIn('xml.etree.elementtree', doc_link)
464
465    def test_issue8225(self):
466        # Test issue8225 to ensure no doc link appears for xml.etree
467        result, doc_loc = get_pydoc_text(xml.etree)
468        self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link")
469
470    def test_getpager_with_stdin_none(self):
471        previous_stdin = sys.stdin
472        try:
473            sys.stdin = None
474            pydoc.getpager() # Shouldn't fail.
475        finally:
476            sys.stdin = previous_stdin
477
478    def test_non_str_name(self):
479        # issue14638
480        # Treat illegal (non-str) name like no name
481        class A:
482            __name__ = 42
483        class B:
484            pass
485        adoc = pydoc.render_doc(A())
486        bdoc = pydoc.render_doc(B())
487        self.assertEqual(adoc.replace("A", "B"), bdoc)
488
489    def test_not_here(self):
490        missing_module = "test.i_am_not_here"
491        result = str(run_pydoc(missing_module), 'ascii')
492        expected = missing_pattern % missing_module
493        self.assertEqual(expected, result,
494            "documentation for missing module found")
495
496    @unittest.skipIf(sys.flags.optimize >= 2,
497                     'Docstrings are omitted with -OO and above')
498    def test_not_ascii(self):
499        result = run_pydoc('test.test_pydoc.nonascii', PYTHONIOENCODING='ascii')
500        encoded = nonascii.__doc__.encode('ascii', 'backslashreplace')
501        self.assertIn(encoded, result)
502
503    def test_input_strip(self):
504        missing_module = " test.i_am_not_here "
505        result = str(run_pydoc(missing_module), 'ascii')
506        expected = missing_pattern % missing_module.strip()
507        self.assertEqual(expected, result)
508
509    def test_stripid(self):
510        # test with strings, other implementations might have different repr()
511        stripid = pydoc.stripid
512        # strip the id
513        self.assertEqual(stripid('<function stripid at 0x88dcee4>'),
514                         '<function stripid>')
515        self.assertEqual(stripid('<function stripid at 0x01F65390>'),
516                         '<function stripid>')
517        # nothing to strip, return the same text
518        self.assertEqual(stripid('42'), '42')
519        self.assertEqual(stripid("<type 'exceptions.Exception'>"),
520                         "<type 'exceptions.Exception'>")
521
522    @unittest.skipIf(sys.flags.optimize >= 2,
523                     'Docstrings are omitted with -O2 and above')
524    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
525                     'trace function introduces __locals__ unexpectedly')
526    @requires_docstrings
527    def test_help_output_redirect(self):
528        # issue 940286, if output is set in Helper, then all output from
529        # Helper.help should be redirected
530        old_pattern = expected_text_pattern
531        getpager_old = pydoc.getpager
532        getpager_new = lambda: (lambda x: x)
533        self.maxDiff = None
534
535        buf = StringIO()
536        helper = pydoc.Helper(output=buf)
537        unused, doc_loc = get_pydoc_text(pydoc_mod)
538        module = "test.pydoc_mod"
539        help_header = """
540        Help on module test.pydoc_mod in test:
541
542        """.lstrip()
543        help_header = textwrap.dedent(help_header)
544        expected_help_pattern = help_header + expected_text_pattern
545
546        pydoc.getpager = getpager_new
547        try:
548            with captured_output('stdout') as output, \
549                 captured_output('stderr') as err:
550                helper.help(module)
551                result = buf.getvalue().strip()
552                expected_text = expected_help_pattern % (
553                                (doc_loc,) +
554                                expected_text_data_docstrings +
555                                (inspect.getabsfile(pydoc_mod),))
556                self.assertEqual('', output.getvalue())
557                self.assertEqual('', err.getvalue())
558                self.assertEqual(expected_text, result)
559        finally:
560            pydoc.getpager = getpager_old
561
562    def test_namedtuple_public_underscore(self):
563        NT = namedtuple('NT', ['abc', 'def'], rename=True)
564        with captured_stdout() as help_io:
565            pydoc.help(NT)
566        helptext = help_io.getvalue()
567        self.assertIn('_1', helptext)
568        self.assertIn('_replace', helptext)
569        self.assertIn('_asdict', helptext)
570
571    def test_synopsis(self):
572        self.addCleanup(unlink, TESTFN)
573        for encoding in ('ISO-8859-1', 'UTF-8'):
574            with open(TESTFN, 'w', encoding=encoding) as script:
575                if encoding != 'UTF-8':
576                    print('#coding: {}'.format(encoding), file=script)
577                print('"""line 1: h\xe9', file=script)
578                print('line 2: hi"""', file=script)
579            synopsis = pydoc.synopsis(TESTFN, {})
580            self.assertEqual(synopsis, 'line 1: h\xe9')
581
582    @unittest.skipIf(sys.flags.optimize >= 2,
583                     'Docstrings are omitted with -OO and above')
584    def test_synopsis_sourceless(self):
585        expected = os.__doc__.splitlines()[0]
586        filename = os.__cached__
587        synopsis = pydoc.synopsis(filename)
588
589        self.assertEqual(synopsis, expected)
590
591    def test_synopsis_sourceless_empty_doc(self):
592        with test.support.temp_cwd() as test_dir:
593            init_path = os.path.join(test_dir, 'foomod42.py')
594            cached_path = importlib.util.cache_from_source(init_path)
595            with open(init_path, 'w') as fobj:
596                fobj.write("foo = 1")
597            py_compile.compile(init_path)
598            synopsis = pydoc.synopsis(init_path, {})
599            self.assertIsNone(synopsis)
600            synopsis_cached = pydoc.synopsis(cached_path, {})
601            self.assertIsNone(synopsis_cached)
602
603    def test_splitdoc_with_description(self):
604        example_string = "I Am A Doc\n\n\nHere is my description"
605        self.assertEqual(pydoc.splitdoc(example_string),
606                         ('I Am A Doc', '\nHere is my description'))
607
608    def test_is_object_or_method(self):
609        doc = pydoc.Doc()
610        # Bound Method
611        self.assertTrue(pydoc._is_some_method(doc.fail))
612        # Method Descriptor
613        self.assertTrue(pydoc._is_some_method(int.__add__))
614        # String
615        self.assertFalse(pydoc._is_some_method("I am not a method"))
616
617    def test_is_package_when_not_package(self):
618        with test.support.temp_cwd() as test_dir:
619            self.assertFalse(pydoc.ispackage(test_dir))
620
621    def test_is_package_when_is_package(self):
622        with test.support.temp_cwd() as test_dir:
623            init_path = os.path.join(test_dir, '__init__.py')
624            open(init_path, 'w').close()
625            self.assertTrue(pydoc.ispackage(test_dir))
626            os.remove(init_path)
627
628    def test_allmethods(self):
629        # issue 17476: allmethods was no longer returning unbound methods.
630        # This test is a bit fragile in the face of changes to object and type,
631        # but I can't think of a better way to do it without duplicating the
632        # logic of the function under test.
633
634        class TestClass(object):
635            def method_returning_true(self):
636                return True
637
638        # What we expect to get back: everything on object...
639        expected = dict(vars(object))
640        # ...plus our unbound method...
641        expected['method_returning_true'] = TestClass.method_returning_true
642        # ...but not the non-methods on object.
643        del expected['__doc__']
644        del expected['__class__']
645        # inspect resolves descriptors on type into methods, but vars doesn't,
646        # so we need to update __subclasshook__ and __init_subclass__.
647        expected['__subclasshook__'] = TestClass.__subclasshook__
648        expected['__init_subclass__'] = TestClass.__init_subclass__
649
650        methods = pydoc.allmethods(TestClass)
651        self.assertDictEqual(methods, expected)
652
653
654class PydocImportTest(PydocBaseTest):
655
656    def setUp(self):
657        self.test_dir = os.mkdir(TESTFN)
658        self.addCleanup(rmtree, TESTFN)
659        importlib.invalidate_caches()
660
661    def test_badimport(self):
662        # This tests the fix for issue 5230, where if pydoc found the module
663        # but the module had an internal import error pydoc would report no doc
664        # found.
665        modname = 'testmod_xyzzy'
666        testpairs = (
667            ('i_am_not_here', 'i_am_not_here'),
668            ('test.i_am_not_here_either', 'test.i_am_not_here_either'),
669            ('test.i_am_not_here.neither_am_i', 'test.i_am_not_here'),
670            ('i_am_not_here.{}'.format(modname), 'i_am_not_here'),
671            ('test.{}'.format(modname), 'test.{}'.format(modname)),
672            )
673
674        sourcefn = os.path.join(TESTFN, modname) + os.extsep + "py"
675        for importstring, expectedinmsg in testpairs:
676            with open(sourcefn, 'w') as f:
677                f.write("import {}\n".format(importstring))
678            result = run_pydoc(modname, PYTHONPATH=TESTFN).decode("ascii")
679            expected = badimport_pattern % (modname, expectedinmsg)
680            self.assertEqual(expected, result)
681
682    def test_apropos_with_bad_package(self):
683        # Issue 7425 - pydoc -k failed when bad package on path
684        pkgdir = os.path.join(TESTFN, "syntaxerr")
685        os.mkdir(pkgdir)
686        badsyntax = os.path.join(pkgdir, "__init__") + os.extsep + "py"
687        with open(badsyntax, 'w') as f:
688            f.write("invalid python syntax = $1\n")
689        with self.restrict_walk_packages(path=[TESTFN]):
690            with captured_stdout() as out:
691                with captured_stderr() as err:
692                    pydoc.apropos('xyzzy')
693            # No result, no error
694            self.assertEqual(out.getvalue(), '')
695            self.assertEqual(err.getvalue(), '')
696            # The package name is still matched
697            with captured_stdout() as out:
698                with captured_stderr() as err:
699                    pydoc.apropos('syntaxerr')
700            self.assertEqual(out.getvalue().strip(), 'syntaxerr')
701            self.assertEqual(err.getvalue(), '')
702
703    def test_apropos_with_unreadable_dir(self):
704        # Issue 7367 - pydoc -k failed when unreadable dir on path
705        self.unreadable_dir = os.path.join(TESTFN, "unreadable")
706        os.mkdir(self.unreadable_dir, 0)
707        self.addCleanup(os.rmdir, self.unreadable_dir)
708        # Note, on Windows the directory appears to be still
709        #   readable so this is not really testing the issue there
710        with self.restrict_walk_packages(path=[TESTFN]):
711            with captured_stdout() as out:
712                with captured_stderr() as err:
713                    pydoc.apropos('SOMEKEY')
714        # No result, no error
715        self.assertEqual(out.getvalue(), '')
716        self.assertEqual(err.getvalue(), '')
717
718    def test_apropos_empty_doc(self):
719        pkgdir = os.path.join(TESTFN, 'walkpkg')
720        os.mkdir(pkgdir)
721        self.addCleanup(rmtree, pkgdir)
722        init_path = os.path.join(pkgdir, '__init__.py')
723        with open(init_path, 'w') as fobj:
724            fobj.write("foo = 1")
725        current_mode = stat.S_IMODE(os.stat(pkgdir).st_mode)
726        try:
727            os.chmod(pkgdir, current_mode & ~stat.S_IEXEC)
728            with self.restrict_walk_packages(path=[TESTFN]), captured_stdout() as stdout:
729                pydoc.apropos('')
730            self.assertIn('walkpkg', stdout.getvalue())
731        finally:
732            os.chmod(pkgdir, current_mode)
733
734    def test_url_search_package_error(self):
735        # URL handler search should cope with packages that raise exceptions
736        pkgdir = os.path.join(TESTFN, "test_error_package")
737        os.mkdir(pkgdir)
738        init = os.path.join(pkgdir, "__init__.py")
739        with open(init, "wt", encoding="ascii") as f:
740            f.write("""raise ValueError("ouch")\n""")
741        with self.restrict_walk_packages(path=[TESTFN]):
742            # Package has to be importable for the error to have any effect
743            saved_paths = tuple(sys.path)
744            sys.path.insert(0, TESTFN)
745            try:
746                with self.assertRaisesRegex(ValueError, "ouch"):
747                    import test_error_package  # Sanity check
748
749                text = self.call_url_handler("search?key=test_error_package",
750                    "Pydoc: Search Results")
751                found = ('<a href="test_error_package.html">'
752                    'test_error_package</a>')
753                self.assertIn(found, text)
754            finally:
755                sys.path[:] = saved_paths
756
757    @unittest.skip('causes undesirable side-effects (#20128)')
758    def test_modules(self):
759        # See Helper.listmodules().
760        num_header_lines = 2
761        num_module_lines_min = 5  # Playing it safe.
762        num_footer_lines = 3
763        expected = num_header_lines + num_module_lines_min + num_footer_lines
764
765        output = StringIO()
766        helper = pydoc.Helper(output=output)
767        helper('modules')
768        result = output.getvalue().strip()
769        num_lines = len(result.splitlines())
770
771        self.assertGreaterEqual(num_lines, expected)
772
773    @unittest.skip('causes undesirable side-effects (#20128)')
774    def test_modules_search(self):
775        # See Helper.listmodules().
776        expected = 'pydoc - '
777
778        output = StringIO()
779        helper = pydoc.Helper(output=output)
780        with captured_stdout() as help_io:
781            helper('modules pydoc')
782        result = help_io.getvalue()
783
784        self.assertIn(expected, result)
785
786    @unittest.skip('some buildbots are not cooperating (#20128)')
787    def test_modules_search_builtin(self):
788        expected = 'gc - '
789
790        output = StringIO()
791        helper = pydoc.Helper(output=output)
792        with captured_stdout() as help_io:
793            helper('modules garbage')
794        result = help_io.getvalue()
795
796        self.assertTrue(result.startswith(expected))
797
798    def test_importfile(self):
799        loaded_pydoc = pydoc.importfile(pydoc.__file__)
800
801        self.assertIsNot(loaded_pydoc, pydoc)
802        self.assertEqual(loaded_pydoc.__name__, 'pydoc')
803        self.assertEqual(loaded_pydoc.__file__, pydoc.__file__)
804        self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__)
805
806
807class TestDescriptions(unittest.TestCase):
808
809    def test_module(self):
810        # Check that pydocfodder module can be described
811        from test import pydocfodder
812        doc = pydoc.render_doc(pydocfodder)
813        self.assertIn("pydocfodder", doc)
814
815    def test_class(self):
816        class C: "New-style class"
817        c = C()
818
819        self.assertEqual(pydoc.describe(C), 'class C')
820        self.assertEqual(pydoc.describe(c), 'C')
821        expected = 'C in module %s object' % __name__
822        self.assertIn(expected, pydoc.render_doc(c))
823
824    def test_typing_pydoc(self):
825        def foo(data: typing.List[typing.Any],
826                x: int) -> typing.Iterator[typing.Tuple[int, typing.Any]]:
827            ...
828        T = typing.TypeVar('T')
829        class C(typing.Generic[T], typing.Mapping[int, str]): ...
830        self.assertEqual(pydoc.render_doc(foo).splitlines()[-1],
831                         'f\x08fo\x08oo\x08o(data:List[Any], x:int)'
832                         ' -> Iterator[Tuple[int, Any]]')
833        self.assertEqual(pydoc.render_doc(C).splitlines()[2],
834                         'class C\x08C(typing.Mapping)')
835
836    def test_builtin(self):
837        for name in ('str', 'str.translate', 'builtins.str',
838                     'builtins.str.translate'):
839            # test low-level function
840            self.assertIsNotNone(pydoc.locate(name))
841            # test high-level function
842            try:
843                pydoc.render_doc(name)
844            except ImportError:
845                self.fail('finding the doc of {!r} failed'.format(name))
846
847        for name in ('notbuiltins', 'strrr', 'strr.translate',
848                     'str.trrrranslate', 'builtins.strrr',
849                     'builtins.str.trrranslate'):
850            self.assertIsNone(pydoc.locate(name))
851            self.assertRaises(ImportError, pydoc.render_doc, name)
852
853    @staticmethod
854    def _get_summary_line(o):
855        text = pydoc.plain(pydoc.render_doc(o))
856        lines = text.split('\n')
857        assert len(lines) >= 2
858        return lines[2]
859
860    # these should include "self"
861    def test_unbound_python_method(self):
862        self.assertEqual(self._get_summary_line(textwrap.TextWrapper.wrap),
863            "wrap(self, text)")
864
865    @requires_docstrings
866    def test_unbound_builtin_method(self):
867        self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
868            "dump(self, obj, /)")
869
870    # these no longer include "self"
871    def test_bound_python_method(self):
872        t = textwrap.TextWrapper()
873        self.assertEqual(self._get_summary_line(t.wrap),
874            "wrap(text) method of textwrap.TextWrapper instance")
875
876    def test_field_order_for_named_tuples(self):
877        Person = namedtuple('Person', ['nickname', 'firstname', 'agegroup'])
878        s = pydoc.render_doc(Person)
879        self.assertLess(s.index('nickname'), s.index('firstname'))
880        self.assertLess(s.index('firstname'), s.index('agegroup'))
881
882        class NonIterableFields:
883            _fields = None
884
885        class NonHashableFields:
886            _fields = [[]]
887
888        # Make sure these doesn't fail
889        pydoc.render_doc(NonIterableFields)
890        pydoc.render_doc(NonHashableFields)
891
892    @requires_docstrings
893    def test_bound_builtin_method(self):
894        s = StringIO()
895        p = _pickle.Pickler(s)
896        self.assertEqual(self._get_summary_line(p.dump),
897            "dump(obj, /) method of _pickle.Pickler instance")
898
899    # this should *never* include self!
900    @requires_docstrings
901    def test_module_level_callable(self):
902        self.assertEqual(self._get_summary_line(os.stat),
903            "stat(path, *, dir_fd=None, follow_symlinks=True)")
904
905
906@unittest.skipUnless(threading, 'Threading required for this test.')
907class PydocServerTest(unittest.TestCase):
908    """Tests for pydoc._start_server"""
909
910    def test_server(self):
911
912        # Minimal test that starts the server, then stops it.
913        def my_url_handler(url, content_type):
914            text = 'the URL sent was: (%s, %s)' % (url, content_type)
915            return text
916
917        serverthread = pydoc._start_server(my_url_handler, port=0)
918        self.assertIn('localhost', serverthread.docserver.address)
919
920        starttime = time.time()
921        timeout = 1  #seconds
922
923        while serverthread.serving:
924            time.sleep(.01)
925            if serverthread.serving and time.time() - starttime > timeout:
926                serverthread.stop()
927                break
928
929        self.assertEqual(serverthread.error, None)
930
931
932class PydocUrlHandlerTest(PydocBaseTest):
933    """Tests for pydoc._url_handler"""
934
935    def test_content_type_err(self):
936        f = pydoc._url_handler
937        self.assertRaises(TypeError, f, 'A', '')
938        self.assertRaises(TypeError, f, 'B', 'foobar')
939
940    def test_url_requests(self):
941        # Test for the correct title in the html pages returned.
942        # This tests the different parts of the URL handler without
943        # getting too picky about the exact html.
944        requests = [
945            ("", "Pydoc: Index of Modules"),
946            ("get?key=", "Pydoc: Index of Modules"),
947            ("index", "Pydoc: Index of Modules"),
948            ("topics", "Pydoc: Topics"),
949            ("keywords", "Pydoc: Keywords"),
950            ("pydoc", "Pydoc: module pydoc"),
951            ("get?key=pydoc", "Pydoc: module pydoc"),
952            ("search?key=pydoc", "Pydoc: Search Results"),
953            ("topic?key=def", "Pydoc: KEYWORD def"),
954            ("topic?key=STRINGS", "Pydoc: TOPIC STRINGS"),
955            ("foobar", "Pydoc: Error - foobar"),
956            ("getfile?key=foobar", "Pydoc: Error - getfile?key=foobar"),
957            ]
958
959        with self.restrict_walk_packages():
960            for url, title in requests:
961                self.call_url_handler(url, title)
962
963            path = string.__file__
964            title = "Pydoc: getfile " + path
965            url = "getfile?key=" + path
966            self.call_url_handler(url, title)
967
968
969class TestHelper(unittest.TestCase):
970    def test_keywords(self):
971        self.assertEqual(sorted(pydoc.Helper.keywords),
972                         sorted(keyword.kwlist))
973
974class PydocWithMetaClasses(unittest.TestCase):
975    @unittest.skipIf(sys.flags.optimize >= 2,
976                     "Docstrings are omitted with -O2 and above")
977    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
978                     'trace function introduces __locals__ unexpectedly')
979    def test_DynamicClassAttribute(self):
980        class Meta(type):
981            def __getattr__(self, name):
982                if name == 'ham':
983                    return 'spam'
984                return super().__getattr__(name)
985        class DA(metaclass=Meta):
986            @types.DynamicClassAttribute
987            def ham(self):
988                return 'eggs'
989        expected_text_data_docstrings = tuple('\n |      ' + s if s else ''
990                                      for s in expected_data_docstrings)
991        output = StringIO()
992        helper = pydoc.Helper(output=output)
993        helper(DA)
994        expected_text = expected_dynamicattribute_pattern % (
995                (__name__,) + expected_text_data_docstrings[:2])
996        result = output.getvalue().strip()
997        self.assertEqual(expected_text, result)
998
999    @unittest.skipIf(sys.flags.optimize >= 2,
1000                     "Docstrings are omitted with -O2 and above")
1001    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1002                     'trace function introduces __locals__ unexpectedly')
1003    def test_virtualClassAttributeWithOneMeta(self):
1004        class Meta(type):
1005            def __dir__(cls):
1006                return ['__class__', '__module__', '__name__', 'LIFE']
1007            def __getattr__(self, name):
1008                if name =='LIFE':
1009                    return 42
1010                return super().__getattr(name)
1011        class Class(metaclass=Meta):
1012            pass
1013        output = StringIO()
1014        helper = pydoc.Helper(output=output)
1015        helper(Class)
1016        expected_text = expected_virtualattribute_pattern1 % __name__
1017        result = output.getvalue().strip()
1018        self.assertEqual(expected_text, result)
1019
1020    @unittest.skipIf(sys.flags.optimize >= 2,
1021                     "Docstrings are omitted with -O2 and above")
1022    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1023                     'trace function introduces __locals__ unexpectedly')
1024    def test_virtualClassAttributeWithTwoMeta(self):
1025        class Meta1(type):
1026            def __dir__(cls):
1027                return ['__class__', '__module__', '__name__', 'one']
1028            def __getattr__(self, name):
1029                if name =='one':
1030                    return 1
1031                return super().__getattr__(name)
1032        class Meta2(type):
1033            def __dir__(cls):
1034                return ['__class__', '__module__', '__name__', 'two']
1035            def __getattr__(self, name):
1036                if name =='two':
1037                    return 2
1038                return super().__getattr__(name)
1039        class Meta3(Meta1, Meta2):
1040            def __dir__(cls):
1041                return list(sorted(set(
1042                    ['__class__', '__module__', '__name__', 'three'] +
1043                    Meta1.__dir__(cls) + Meta2.__dir__(cls))))
1044            def __getattr__(self, name):
1045                if name =='three':
1046                    return 3
1047                return super().__getattr__(name)
1048        class Class1(metaclass=Meta1):
1049            pass
1050        class Class2(Class1, metaclass=Meta3):
1051            pass
1052        fail1 = fail2 = False
1053        output = StringIO()
1054        helper = pydoc.Helper(output=output)
1055        helper(Class1)
1056        expected_text1 = expected_virtualattribute_pattern2 % __name__
1057        result1 = output.getvalue().strip()
1058        self.assertEqual(expected_text1, result1)
1059        output = StringIO()
1060        helper = pydoc.Helper(output=output)
1061        helper(Class2)
1062        expected_text2 = expected_virtualattribute_pattern3 % __name__
1063        result2 = output.getvalue().strip()
1064        self.assertEqual(expected_text2, result2)
1065
1066    @unittest.skipIf(sys.flags.optimize >= 2,
1067                     "Docstrings are omitted with -O2 and above")
1068    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
1069                     'trace function introduces __locals__ unexpectedly')
1070    def test_buggy_dir(self):
1071        class M(type):
1072            def __dir__(cls):
1073                return ['__class__', '__name__', 'missing', 'here']
1074        class C(metaclass=M):
1075            here = 'present!'
1076        output = StringIO()
1077        helper = pydoc.Helper(output=output)
1078        helper(C)
1079        expected_text = expected_missingattribute_pattern % __name__
1080        result = output.getvalue().strip()
1081        self.assertEqual(expected_text, result)
1082
1083    def test_resolve_false(self):
1084        # Issue #23008: pydoc enum.{,Int}Enum failed
1085        # because bool(enum.Enum) is False.
1086        with captured_stdout() as help_io:
1087            pydoc.help('enum.Enum')
1088        helptext = help_io.getvalue()
1089        self.assertIn('class Enum', helptext)
1090
1091
1092@reap_threads
1093def test_main():
1094    try:
1095        test.support.run_unittest(PydocDocTest,
1096                                  PydocImportTest,
1097                                  TestDescriptions,
1098                                  PydocServerTest,
1099                                  PydocUrlHandlerTest,
1100                                  TestHelper,
1101                                  PydocWithMetaClasses,
1102                                  )
1103    finally:
1104        reap_children()
1105
1106if __name__ == "__main__":
1107    test_main()
1108