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