• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import datetime
2import os
3import sys
4import contextlib
5import importlib.util
6import inspect
7import pydoc
8import py_compile
9import keyword
10import _pickle
11import pkgutil
12import re
13import stat
14import tempfile
15import test.support
16import time
17import types
18import typing
19import unittest
20import unittest.mock
21import urllib.parse
22import xml.etree
23import xml.etree.ElementTree
24import textwrap
25from io import StringIO
26from collections import namedtuple
27from urllib.request import urlopen, urlcleanup
28from test import support
29from test.support import import_helper
30from test.support import os_helper
31from test.support.script_helper import (assert_python_ok,
32                                        assert_python_failure, spawn_python)
33from test.support import threading_helper
34from test.support import (reap_children, captured_stdout,
35                          captured_stderr, is_emscripten, is_wasi,
36                          requires_docstrings, MISSING_C_DOCSTRINGS)
37from test.support.os_helper import (TESTFN, rmtree, unlink)
38from test.test_pydoc import pydoc_mod
39from test.test_pydoc import pydocfodder
40
41
42class nonascii:
43    'Це не латиниця'
44    pass
45
46if test.support.HAVE_DOCSTRINGS:
47    expected_data_docstrings = (
48        'dictionary for instance variables',
49        'list of weak references to the object',
50        ) * 2
51else:
52    expected_data_docstrings = ('', '', '', '')
53
54expected_text_pattern = """
55NAME
56    test.test_pydoc.pydoc_mod - This is a test module for test_pydoc
57%s
58CLASSES
59    builtins.object
60        A
61        B
62        C
63
64    class A(builtins.object)
65     |  Hello and goodbye
66     |
67     |  Methods defined here:
68     |
69     |  __init__()
70     |      Wow, I have no function!
71     |
72     |  ----------------------------------------------------------------------
73     |  Data descriptors defined here:
74     |
75     |  __dict__%s
76     |
77     |  __weakref__%s
78
79    class B(builtins.object)
80     |  Data descriptors defined here:
81     |
82     |  __dict__%s
83     |
84     |  __weakref__%s
85     |
86     |  ----------------------------------------------------------------------
87     |  Data and other attributes defined here:
88     |
89     |  NO_MEANING = 'eggs'
90     |
91     |  __annotations__ = {'NO_MEANING': <class 'str'>}
92
93    class C(builtins.object)
94     |  Methods defined here:
95     |
96     |  get_answer(self)
97     |      Return say_no()
98     |
99     |  is_it_true(self)
100     |      Return self.get_answer()
101     |
102     |  say_no(self)
103     |
104     |  ----------------------------------------------------------------------
105     |  Class methods defined here:
106     |
107     |  __class_getitem__(item)
108     |
109     |  ----------------------------------------------------------------------
110     |  Data descriptors defined here:
111     |
112     |  __dict__
113     |      dictionary for instance variables
114     |
115     |  __weakref__
116     |      list of weak references to the object
117
118FUNCTIONS
119    doc_func()
120        This function solves all of the world's problems:
121        hunger
122        lack of Python
123        war
124
125    nodoc_func()
126
127DATA
128    __xyz__ = 'X, Y and Z'
129    c_alias = test.test_pydoc.pydoc_mod.C[int]
130    list_alias1 = typing.List[int]
131    list_alias2 = list[int]
132    type_union1 = typing.Union[int, str]
133    type_union2 = int | str
134
135VERSION
136    1.2.3.4
137
138AUTHOR
139    Benjamin Peterson
140
141CREDITS
142    Nobody
143
144FILE
145    %s
146""".strip()
147
148expected_text_data_docstrings = tuple('\n     |      ' + s if s else ''
149                                      for s in expected_data_docstrings)
150
151html2text_of_expected = """
152test.test_pydoc.pydoc_mod (version 1.2.3.4)
153This is a test module for test_pydoc
154
155Modules
156    types
157    typing
158
159Classes
160    builtins.object
161    A
162    B
163    C
164
165class A(builtins.object)
166    Hello and goodbye
167
168    Methods defined here:
169        __init__()
170            Wow, I have no function!
171    ----------------------------------------------------------------------
172    Data descriptors defined here:
173        __dict__
174            dictionary for instance variables
175        __weakref__
176            list of weak references to the object
177
178class B(builtins.object)
179    Data descriptors defined here:
180        __dict__
181            dictionary for instance variables
182        __weakref__
183            list of weak references to the object
184    ----------------------------------------------------------------------
185    Data and other attributes defined here:
186        NO_MEANING = 'eggs'
187        __annotations__ = {'NO_MEANING': <class 'str'>}
188
189
190class C(builtins.object)
191    Methods defined here:
192        get_answer(self)
193            Return say_no()
194        is_it_true(self)
195            Return self.get_answer()
196        say_no(self)
197    ----------------------------------------------------------------------
198    Class methods defined here:
199        __class_getitem__(item)
200    ----------------------------------------------------------------------
201    Data descriptors defined here:
202        __dict__
203            dictionary for instance variables
204        __weakref__
205             list of weak references to the object
206
207Functions
208    doc_func()
209        This function solves all of the world's problems:
210        hunger
211        lack of Python
212        war
213    nodoc_func()
214
215Data
216    __xyz__ = 'X, Y and Z'
217    c_alias = test.test_pydoc.pydoc_mod.C[int]
218    list_alias1 = typing.List[int]
219    list_alias2 = list[int]
220    type_union1 = typing.Union[int, str]
221    type_union2 = int | str
222
223Author
224    Benjamin Peterson
225
226Credits
227    Nobody
228"""
229
230expected_html_data_docstrings = tuple(s.replace(' ', '&nbsp;')
231                                      for s in expected_data_docstrings)
232
233# output pattern for missing module
234missing_pattern = '''\
235No Python documentation found for %r.
236Use help() to get the interactive help utility.
237Use help(str) for help on the str class.'''.replace('\n', os.linesep)
238
239# output pattern for module with bad imports
240badimport_pattern = "problem in %s - ModuleNotFoundError: No module named %r"
241
242expected_dynamicattribute_pattern = """
243Help on class DA in module %s:
244
245class DA(builtins.object)
246 |  Data descriptors defined here:
247 |
248 |  __dict__%s
249 |
250 |  __weakref__%s
251 |
252 |  ham
253 |
254 |  ----------------------------------------------------------------------
255 |  Data and other attributes inherited from Meta:
256 |
257 |  ham = 'spam'
258""".strip()
259
260expected_virtualattribute_pattern1 = """
261Help on class Class in module %s:
262
263class Class(builtins.object)
264 |  Data and other attributes inherited from Meta:
265 |
266 |  LIFE = 42
267""".strip()
268
269expected_virtualattribute_pattern2 = """
270Help on class Class1 in module %s:
271
272class Class1(builtins.object)
273 |  Data and other attributes inherited from Meta1:
274 |
275 |  one = 1
276""".strip()
277
278expected_virtualattribute_pattern3 = """
279Help on class Class2 in module %s:
280
281class Class2(Class1)
282 |  Method resolution order:
283 |      Class2
284 |      Class1
285 |      builtins.object
286 |
287 |  Data and other attributes inherited from Meta1:
288 |
289 |  one = 1
290 |
291 |  ----------------------------------------------------------------------
292 |  Data and other attributes inherited from Meta3:
293 |
294 |  three = 3
295 |
296 |  ----------------------------------------------------------------------
297 |  Data and other attributes inherited from Meta2:
298 |
299 |  two = 2
300""".strip()
301
302expected_missingattribute_pattern = """
303Help on class C in module %s:
304
305class C(builtins.object)
306 |  Data and other attributes defined here:
307 |
308 |  here = 'present!'
309""".strip()
310
311def run_pydoc(module_name, *args, **env):
312    """
313    Runs pydoc on the specified module. Returns the stripped
314    output of pydoc.
315    """
316    args = args + (module_name,)
317    # do not write bytecode files to avoid caching errors
318    rc, out, err = assert_python_ok('-B', pydoc.__file__, *args, **env)
319    return out.strip()
320
321def run_pydoc_fail(module_name, *args, **env):
322    """
323    Runs pydoc on the specified module expecting a failure.
324    """
325    args = args + (module_name,)
326    rc, out, err = assert_python_failure('-B', pydoc.__file__, *args, **env)
327    return out.strip()
328
329def get_pydoc_html(module):
330    "Returns pydoc generated output as html"
331    doc = pydoc.HTMLDoc()
332    output = doc.docmodule(module)
333    loc = doc.getdocloc(pydoc_mod) or ""
334    if loc:
335        loc = "<br><a href=\"" + loc + "\">Module Docs</a>"
336    return output.strip(), loc
337
338def clean_text(doc):
339    # clean up the extra text formatting that pydoc performs
340    return re.sub('\b.', '', doc)
341
342def get_pydoc_link(module):
343    "Returns a documentation web link of a module"
344    abspath = os.path.abspath
345    dirname = os.path.dirname
346    basedir = dirname(dirname(dirname(abspath(__file__))))
347    doc = pydoc.TextDoc()
348    loc = doc.getdocloc(module, basedir=basedir)
349    return loc
350
351def get_pydoc_text(module):
352    "Returns pydoc generated output as text"
353    doc = pydoc.TextDoc()
354    loc = doc.getdocloc(pydoc_mod) or ""
355    if loc:
356        loc = "\nMODULE DOCS\n    " + loc + "\n"
357
358    output = doc.docmodule(module)
359    output = clean_text(output)
360    return output.strip(), loc
361
362def get_html_title(text):
363    # Bit of hack, but good enough for test purposes
364    header, _, _ = text.partition("</head>")
365    _, _, title = header.partition("<title>")
366    title, _, _ = title.partition("</title>")
367    return title
368
369
370def html2text(html):
371    """A quick and dirty implementation of html2text.
372
373    Tailored for pydoc tests only.
374    """
375    html = html.replace("<dd>", "\n")
376    html = html.replace("<hr>", "-"*70)
377    html = re.sub("<.*?>", "", html)
378    html = pydoc.replace(html, "&nbsp;", " ", "&gt;", ">", "&lt;", "<")
379    return html
380
381
382class PydocBaseTest(unittest.TestCase):
383    def tearDown(self):
384        # Self-testing. Mocking only works if sys.modules['pydoc'] and pydoc
385        # are the same. But some pydoc functions reload the module and change
386        # sys.modules, so check that it was restored.
387        self.assertIs(sys.modules['pydoc'], pydoc)
388
389    def _restricted_walk_packages(self, walk_packages, path=None):
390        """
391        A version of pkgutil.walk_packages() that will restrict itself to
392        a given path.
393        """
394        default_path = path or [os.path.dirname(__file__)]
395        def wrapper(path=None, prefix='', onerror=None):
396            return walk_packages(path or default_path, prefix, onerror)
397        return wrapper
398
399    @contextlib.contextmanager
400    def restrict_walk_packages(self, path=None):
401        walk_packages = pkgutil.walk_packages
402        pkgutil.walk_packages = self._restricted_walk_packages(walk_packages,
403                                                               path)
404        try:
405            yield
406        finally:
407            pkgutil.walk_packages = walk_packages
408
409    def call_url_handler(self, url, expected_title):
410        text = pydoc._url_handler(url, "text/html")
411        result = get_html_title(text)
412        # Check the title to ensure an unexpected error page was not returned
413        self.assertEqual(result, expected_title, text)
414        return text
415
416
417class PydocDocTest(unittest.TestCase):
418    maxDiff = None
419    def tearDown(self):
420        self.assertIs(sys.modules['pydoc'], pydoc)
421
422    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
423                     'trace function introduces __locals__ unexpectedly')
424    @requires_docstrings
425    def test_html_doc(self):
426        result, doc_loc = get_pydoc_html(pydoc_mod)
427        text_result = html2text(result)
428        text_lines = [line.strip() for line in text_result.splitlines()]
429        text_lines = [line for line in text_lines if line]
430        del text_lines[1]
431        expected_lines = html2text_of_expected.splitlines()
432        expected_lines = [line.strip() for line in expected_lines if line]
433        self.assertEqual(text_lines, expected_lines)
434        mod_file = inspect.getabsfile(pydoc_mod)
435        mod_url = urllib.parse.quote(mod_file)
436        self.assertIn(mod_url, result)
437        self.assertIn(mod_file, result)
438        self.assertIn(doc_loc, result)
439
440    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
441                     'trace function introduces __locals__ unexpectedly')
442    @requires_docstrings
443    def test_text_doc(self):
444        result, doc_loc = get_pydoc_text(pydoc_mod)
445        expected_text = expected_text_pattern % (
446                        (doc_loc,) +
447                        expected_text_data_docstrings +
448                        (inspect.getabsfile(pydoc_mod),))
449        self.assertEqual(expected_text, result)
450
451    def test_text_enum_member_with_value_zero(self):
452        # Test issue #20654 to ensure enum member with value 0 can be
453        # displayed. It used to throw KeyError: 'zero'.
454        import enum
455        class BinaryInteger(enum.IntEnum):
456            zero = 0
457            one = 1
458        doc = pydoc.render_doc(BinaryInteger)
459        self.assertIn('BinaryInteger.zero', doc)
460
461    def test_mixed_case_module_names_are_lower_cased(self):
462        # issue16484
463        doc_link = get_pydoc_link(xml.etree.ElementTree)
464        self.assertIn('xml.etree.elementtree', doc_link)
465
466    def test_issue8225(self):
467        # Test issue8225 to ensure no doc link appears for xml.etree
468        result, doc_loc = get_pydoc_text(xml.etree)
469        self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link")
470
471    def test_getpager_with_stdin_none(self):
472        previous_stdin = sys.stdin
473        try:
474            sys.stdin = None
475            pydoc.getpager() # Shouldn't fail.
476        finally:
477            sys.stdin = previous_stdin
478
479    def test_non_str_name(self):
480        # issue14638
481        # Treat illegal (non-str) name like no name
482
483        class A:
484            __name__ = 42
485        class B:
486            pass
487        adoc = pydoc.render_doc(A())
488        bdoc = pydoc.render_doc(B())
489        self.assertEqual(adoc.replace("A", "B"), bdoc)
490
491    def test_not_here(self):
492        missing_module = "test.i_am_not_here"
493        result = str(run_pydoc_fail(missing_module), 'ascii')
494        expected = missing_pattern % missing_module
495        self.assertEqual(expected, result,
496            "documentation for missing module found")
497
498    @requires_docstrings
499    def test_not_ascii(self):
500        result = run_pydoc('test.test_pydoc.test_pydoc.nonascii', PYTHONIOENCODING='ascii')
501        encoded = nonascii.__doc__.encode('ascii', 'backslashreplace')
502        self.assertIn(encoded, result)
503
504    def test_input_strip(self):
505        missing_module = " test.i_am_not_here "
506        result = str(run_pydoc_fail(missing_module), 'ascii')
507        expected = missing_pattern % missing_module.strip()
508        self.assertEqual(expected, result)
509
510    def test_stripid(self):
511        # test with strings, other implementations might have different repr()
512        stripid = pydoc.stripid
513        # strip the id
514        self.assertEqual(stripid('<function stripid at 0x88dcee4>'),
515                         '<function stripid>')
516        self.assertEqual(stripid('<function stripid at 0x01F65390>'),
517                         '<function stripid>')
518        # nothing to strip, return the same text
519        self.assertEqual(stripid('42'), '42')
520        self.assertEqual(stripid("<type 'exceptions.Exception'>"),
521                         "<type 'exceptions.Exception'>")
522
523    def test_builtin_with_more_than_four_children(self):
524        """Tests help on builtin object which have more than four child classes.
525
526        When running help() on a builtin class which has child classes, it
527        should contain a "Built-in subclasses" section and only 4 classes
528        should be displayed with a hint on how many more subclasses are present.
529        For example:
530
531        >>> help(object)
532        Help on class object in module builtins:
533
534        class object
535         |  The most base type
536         |
537         |  Built-in subclasses:
538         |      async_generator
539         |      BaseException
540         |      builtin_function_or_method
541         |      bytearray
542         |      ... and 82 other subclasses
543        """
544        doc = pydoc.TextDoc()
545        text = doc.docclass(object)
546        snip = (" |  Built-in subclasses:\n"
547                " |      async_generator\n"
548                " |      BaseException\n"
549                " |      builtin_function_or_method\n"
550                " |      bytearray\n"
551                " |      ... and \\d+ other subclasses")
552        self.assertRegex(text, snip)
553
554    def test_builtin_with_child(self):
555        """Tests help on builtin object which have only child classes.
556
557        When running help() on a builtin class which has child classes, it
558        should contain a "Built-in subclasses" section. For example:
559
560        >>> help(ArithmeticError)
561        Help on class ArithmeticError in module builtins:
562
563        class ArithmeticError(Exception)
564         |  Base class for arithmetic errors.
565         |
566         ...
567         |
568         |  Built-in subclasses:
569         |      FloatingPointError
570         |      OverflowError
571         |      ZeroDivisionError
572        """
573        doc = pydoc.TextDoc()
574        text = doc.docclass(ArithmeticError)
575        snip = (" |  Built-in subclasses:\n"
576                " |      FloatingPointError\n"
577                " |      OverflowError\n"
578                " |      ZeroDivisionError")
579        self.assertIn(snip, text)
580
581    def test_builtin_with_grandchild(self):
582        """Tests help on builtin classes which have grandchild classes.
583
584        When running help() on a builtin class which has child classes, it
585        should contain a "Built-in subclasses" section. However, if it also has
586        grandchildren, these should not show up on the subclasses section.
587        For example:
588
589        >>> help(Exception)
590        Help on class Exception in module builtins:
591
592        class Exception(BaseException)
593         |  Common base class for all non-exit exceptions.
594         |
595         ...
596         |
597         |  Built-in subclasses:
598         |      ArithmeticError
599         |      AssertionError
600         |      AttributeError
601         ...
602        """
603        doc = pydoc.TextDoc()
604        text = doc.docclass(Exception)
605        snip = (" |  Built-in subclasses:\n"
606                " |      ArithmeticError\n"
607                " |      AssertionError\n"
608                " |      AttributeError")
609        self.assertIn(snip, text)
610        # Testing that the grandchild ZeroDivisionError does not show up
611        self.assertNotIn('ZeroDivisionError', text)
612
613    def test_builtin_no_child(self):
614        """Tests help on builtin object which have no child classes.
615
616        When running help() on a builtin class which has no child classes, it
617        should not contain any "Built-in subclasses" section. For example:
618
619        >>> help(ZeroDivisionError)
620
621        Help on class ZeroDivisionError in module builtins:
622
623        class ZeroDivisionError(ArithmeticError)
624         |  Second argument to a division or modulo operation was zero.
625         |
626         |  Method resolution order:
627         |      ZeroDivisionError
628         |      ArithmeticError
629         |      Exception
630         |      BaseException
631         |      object
632         |
633         |  Methods defined here:
634         ...
635        """
636        doc = pydoc.TextDoc()
637        text = doc.docclass(ZeroDivisionError)
638        # Testing that the subclasses section does not appear
639        self.assertNotIn('Built-in subclasses', text)
640
641    def test_builtin_on_metaclasses(self):
642        """Tests help on metaclasses.
643
644        When running help() on a metaclasses such as type, it
645        should not contain any "Built-in subclasses" section.
646        """
647        doc = pydoc.TextDoc()
648        text = doc.docclass(type)
649        # Testing that the subclasses section does not appear
650        self.assertNotIn('Built-in subclasses', text)
651
652    def test_fail_help_cli(self):
653        elines = (missing_pattern % 'abd').splitlines()
654        with spawn_python("-c" "help()") as proc:
655            out, _ = proc.communicate(b"abd")
656            olines = out.decode().splitlines()[-9:-6]
657            olines[0] = olines[0].removeprefix('help> ')
658            self.assertEqual(elines, olines)
659
660    def test_fail_help_output_redirect(self):
661        with StringIO() as buf:
662            helper = pydoc.Helper(output=buf)
663            helper.help("abd")
664            expected = missing_pattern % "abd"
665            self.assertEqual(expected, buf.getvalue().strip().replace('\n', os.linesep))
666
667    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
668                     'trace function introduces __locals__ unexpectedly')
669    @unittest.mock.patch('pydoc.pager')
670    @requires_docstrings
671    def test_help_output_redirect(self, pager_mock):
672        # issue 940286, if output is set in Helper, then all output from
673        # Helper.help should be redirected
674        self.maxDiff = None
675
676        unused, doc_loc = get_pydoc_text(pydoc_mod)
677        module = "test.test_pydoc.pydoc_mod"
678        help_header = """
679        Help on module test.test_pydoc.pydoc_mod in test.test_pydoc:
680
681        """.lstrip()
682        help_header = textwrap.dedent(help_header)
683        expected_help_pattern = help_header + expected_text_pattern
684
685        with captured_stdout() as output, captured_stderr() as err:
686            buf = StringIO()
687            helper = pydoc.Helper(output=buf)
688            helper.help(module)
689            result = buf.getvalue().strip()
690            expected_text = expected_help_pattern % (
691                            (doc_loc,) +
692                            expected_text_data_docstrings +
693                            (inspect.getabsfile(pydoc_mod),))
694            self.assertEqual('', output.getvalue())
695            self.assertEqual('', err.getvalue())
696            self.assertEqual(expected_text, result)
697
698        pager_mock.assert_not_called()
699
700    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
701                     'trace function introduces __locals__ unexpectedly')
702    @requires_docstrings
703    @unittest.mock.patch('pydoc.pager')
704    def test_help_output_redirect_various_requests(self, pager_mock):
705        # issue 940286, if output is set in Helper, then all output from
706        # Helper.help should be redirected
707
708        def run_pydoc_for_request(request, expected_text_part):
709            """Helper function to run pydoc with its output redirected"""
710            with captured_stdout() as output, captured_stderr() as err:
711                buf = StringIO()
712                helper = pydoc.Helper(output=buf)
713                helper.help(request)
714                result = buf.getvalue().strip()
715                self.assertEqual('', output.getvalue(), msg=f'failed on request "{request}"')
716                self.assertEqual('', err.getvalue(), msg=f'failed on request "{request}"')
717                self.assertIn(expected_text_part, result, msg=f'failed on request "{request}"')
718                pager_mock.assert_not_called()
719
720        self.maxDiff = None
721
722        # test for "keywords"
723        run_pydoc_for_request('keywords', 'Here is a list of the Python keywords.')
724        # test for "symbols"
725        run_pydoc_for_request('symbols', 'Here is a list of the punctuation symbols')
726        # test for "topics"
727        run_pydoc_for_request('topics', 'Here is a list of available topics.')
728        # test for "modules" skipped, see test_modules()
729        # test for symbol "%"
730        run_pydoc_for_request('%', 'The power operator')
731        # test for special True, False, None keywords
732        run_pydoc_for_request('True', 'class bool(int)')
733        run_pydoc_for_request('False', 'class bool(int)')
734        run_pydoc_for_request('None', 'class NoneType(object)')
735        # test for keyword "assert"
736        run_pydoc_for_request('assert', 'The "assert" statement')
737        # test for topic "TYPES"
738        run_pydoc_for_request('TYPES', 'The standard type hierarchy')
739        # test for "pydoc.Helper.help"
740        run_pydoc_for_request('pydoc.Helper.help', 'Help on function help in pydoc.Helper:')
741        # test for pydoc.Helper.help
742        run_pydoc_for_request(pydoc.Helper.help, 'Help on function help in module pydoc:')
743        # test for pydoc.Helper() instance skipped because it is always meant to be interactive
744
745    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
746                     'trace function introduces __locals__ unexpectedly')
747    @requires_docstrings
748    def test_help_output_pager(self):
749        def run_pydoc_pager(request, what, expected_first_line):
750            with (captured_stdout() as output,
751                  captured_stderr() as err,
752                  unittest.mock.patch('pydoc.pager') as pager_mock,
753                  self.subTest(repr(request))):
754                helper = pydoc.Helper()
755                helper.help(request)
756                self.assertEqual('', err.getvalue())
757                self.assertEqual('\n', output.getvalue())
758                pager_mock.assert_called_once()
759                result = clean_text(pager_mock.call_args.args[0])
760                self.assertEqual(result.splitlines()[0], expected_first_line)
761                self.assertEqual(pager_mock.call_args.args[1], f'Help on {what}')
762
763        run_pydoc_pager('%', 'EXPRESSIONS', 'Operator precedence')
764        run_pydoc_pager('True', 'bool object', 'Help on bool object:')
765        run_pydoc_pager(True, 'bool object', 'Help on bool object:')
766        run_pydoc_pager('assert', 'assert', 'The "assert" statement')
767        run_pydoc_pager('TYPES', 'TYPES', 'The standard type hierarchy')
768        run_pydoc_pager('pydoc.Helper.help', 'pydoc.Helper.help',
769                        'Help on function help in pydoc.Helper:')
770        run_pydoc_pager(pydoc.Helper.help, 'Helper.help',
771                        'Help on function help in module pydoc:')
772        run_pydoc_pager('str', 'str', 'Help on class str in module builtins:')
773        run_pydoc_pager(str, 'str', 'Help on class str in module builtins:')
774        run_pydoc_pager('str.upper', 'str.upper', 'Help on method_descriptor in str:')
775        run_pydoc_pager(str.upper, 'str.upper', 'Help on method_descriptor:')
776        run_pydoc_pager(str.__add__, 'str.__add__', 'Help on wrapper_descriptor:')
777        run_pydoc_pager(int.numerator, 'int.numerator',
778                        'Help on getset descriptor builtins.int.numerator:')
779        run_pydoc_pager(list[int], 'list',
780                        'Help on GenericAlias in module builtins:')
781        run_pydoc_pager('sys', 'sys', 'Help on built-in module sys:')
782        run_pydoc_pager(sys, 'sys', 'Help on built-in module sys:')
783
784    def test_showtopic(self):
785        with captured_stdout() as showtopic_io:
786            helper = pydoc.Helper()
787            helper.showtopic('with')
788        helptext = showtopic_io.getvalue()
789        self.assertIn('The "with" statement', helptext)
790
791    def test_fail_showtopic(self):
792        with captured_stdout() as showtopic_io:
793            helper = pydoc.Helper()
794            helper.showtopic('abd')
795            expected = "no documentation found for 'abd'"
796            self.assertEqual(expected, showtopic_io.getvalue().strip())
797
798    @unittest.mock.patch('pydoc.pager')
799    def test_fail_showtopic_output_redirect(self, pager_mock):
800        with StringIO() as buf:
801            helper = pydoc.Helper(output=buf)
802            helper.showtopic("abd")
803            expected = "no documentation found for 'abd'"
804            self.assertEqual(expected, buf.getvalue().strip())
805
806        pager_mock.assert_not_called()
807
808    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
809                     'trace function introduces __locals__ unexpectedly')
810    @requires_docstrings
811    @unittest.mock.patch('pydoc.pager')
812    def test_showtopic_output_redirect(self, pager_mock):
813        # issue 940286, if output is set in Helper, then all output from
814        # Helper.showtopic should be redirected
815        self.maxDiff = None
816
817        with captured_stdout() as output, captured_stderr() as err:
818            buf = StringIO()
819            helper = pydoc.Helper(output=buf)
820            helper.showtopic('with')
821            result = buf.getvalue().strip()
822            self.assertEqual('', output.getvalue())
823            self.assertEqual('', err.getvalue())
824            self.assertIn('The "with" statement', result)
825
826        pager_mock.assert_not_called()
827
828    def test_lambda_with_return_annotation(self):
829        func = lambda a, b, c: 1
830        func.__annotations__ = {"return": int}
831        with captured_stdout() as help_io:
832            pydoc.help(func)
833        helptext = help_io.getvalue()
834        self.assertIn("lambda (a, b, c) -> int", helptext)
835
836    def test_lambda_without_return_annotation(self):
837        func = lambda a, b, c: 1
838        func.__annotations__ = {"a": int, "b": int, "c": int}
839        with captured_stdout() as help_io:
840            pydoc.help(func)
841        helptext = help_io.getvalue()
842        self.assertIn("lambda (a: int, b: int, c: int)", helptext)
843
844    def test_lambda_with_return_and_params_annotation(self):
845        func = lambda a, b, c: 1
846        func.__annotations__ = {"a": int, "b": int, "c": int, "return": int}
847        with captured_stdout() as help_io:
848            pydoc.help(func)
849        helptext = help_io.getvalue()
850        self.assertIn("lambda (a: int, b: int, c: int) -> int", helptext)
851
852    def test_namedtuple_fields(self):
853        Person = namedtuple('Person', ['nickname', 'firstname'])
854        with captured_stdout() as help_io:
855            pydoc.help(Person)
856        helptext = help_io.getvalue()
857        self.assertIn("nickname", helptext)
858        self.assertIn("firstname", helptext)
859        self.assertIn("Alias for field number 0", helptext)
860        self.assertIn("Alias for field number 1", helptext)
861
862    def test_namedtuple_public_underscore(self):
863        NT = namedtuple('NT', ['abc', 'def'], rename=True)
864        with captured_stdout() as help_io:
865            pydoc.help(NT)
866        helptext = help_io.getvalue()
867        self.assertIn('_1', helptext)
868        self.assertIn('_replace', helptext)
869        self.assertIn('_asdict', helptext)
870
871    def test_synopsis(self):
872        self.addCleanup(unlink, TESTFN)
873        for encoding in ('ISO-8859-1', 'UTF-8'):
874            with open(TESTFN, 'w', encoding=encoding) as script:
875                if encoding != 'UTF-8':
876                    print('#coding: {}'.format(encoding), file=script)
877                print('"""line 1: h\xe9', file=script)
878                print('line 2: hi"""', file=script)
879            synopsis = pydoc.synopsis(TESTFN, {})
880            self.assertEqual(synopsis, 'line 1: h\xe9')
881
882    @requires_docstrings
883    def test_synopsis_sourceless(self):
884        os = import_helper.import_fresh_module('os')
885        expected = os.__doc__.splitlines()[0]
886        filename = os.__spec__.cached
887        synopsis = pydoc.synopsis(filename)
888
889        self.assertEqual(synopsis, expected)
890
891    def test_synopsis_sourceless_empty_doc(self):
892        with os_helper.temp_cwd() as test_dir:
893            init_path = os.path.join(test_dir, 'foomod42.py')
894            cached_path = importlib.util.cache_from_source(init_path)
895            with open(init_path, 'w') as fobj:
896                fobj.write("foo = 1")
897            py_compile.compile(init_path)
898            synopsis = pydoc.synopsis(init_path, {})
899            self.assertIsNone(synopsis)
900            synopsis_cached = pydoc.synopsis(cached_path, {})
901            self.assertIsNone(synopsis_cached)
902
903    def test_splitdoc_with_description(self):
904        example_string = "I Am A Doc\n\n\nHere is my description"
905        self.assertEqual(pydoc.splitdoc(example_string),
906                         ('I Am A Doc', '\nHere is my description'))
907
908    def test_is_package_when_not_package(self):
909        with os_helper.temp_cwd() as test_dir:
910            with self.assertWarns(DeprecationWarning) as cm:
911                self.assertFalse(pydoc.ispackage(test_dir))
912            self.assertEqual(cm.filename, __file__)
913
914    def test_is_package_when_is_package(self):
915        with os_helper.temp_cwd() as test_dir:
916            init_path = os.path.join(test_dir, '__init__.py')
917            open(init_path, 'w').close()
918            with self.assertWarns(DeprecationWarning) as cm:
919                self.assertTrue(pydoc.ispackage(test_dir))
920            os.remove(init_path)
921            self.assertEqual(cm.filename, __file__)
922
923    def test_allmethods(self):
924        # issue 17476: allmethods was no longer returning unbound methods.
925        # This test is a bit fragile in the face of changes to object and type,
926        # but I can't think of a better way to do it without duplicating the
927        # logic of the function under test.
928
929        class TestClass(object):
930            def method_returning_true(self):
931                return True
932
933        # What we expect to get back: everything on object...
934        expected = dict(vars(object))
935        # ...plus our unbound method...
936        expected['method_returning_true'] = TestClass.method_returning_true
937        # ...but not the non-methods on object.
938        del expected['__doc__']
939        del expected['__class__']
940        # inspect resolves descriptors on type into methods, but vars doesn't,
941        # so we need to update __subclasshook__ and __init_subclass__.
942        expected['__subclasshook__'] = TestClass.__subclasshook__
943        expected['__init_subclass__'] = TestClass.__init_subclass__
944
945        methods = pydoc.allmethods(TestClass)
946        self.assertDictEqual(methods, expected)
947
948    @requires_docstrings
949    def test_method_aliases(self):
950        class A:
951            def tkraise(self, aboveThis=None):
952                """Raise this widget in the stacking order."""
953            lift = tkraise
954            def a_size(self):
955                """Return size"""
956        class B(A):
957            def itemconfigure(self, tagOrId, cnf=None, **kw):
958                """Configure resources of an item TAGORID."""
959            itemconfig = itemconfigure
960            b_size = A.a_size
961
962        doc = pydoc.render_doc(B)
963        doc = clean_text(doc)
964        self.assertEqual(doc, '''\
965Python Library Documentation: class B in module %s
966
967class B(A)
968 |  Method resolution order:
969 |      B
970 |      A
971 |      builtins.object
972 |
973 |  Methods defined here:
974 |
975 |  b_size = a_size(self)
976 |
977 |  itemconfig = itemconfigure(self, tagOrId, cnf=None, **kw)
978 |
979 |  itemconfigure(self, tagOrId, cnf=None, **kw)
980 |      Configure resources of an item TAGORID.
981 |
982 |  ----------------------------------------------------------------------
983 |  Methods inherited from A:
984 |
985 |  a_size(self)
986 |      Return size
987 |
988 |  lift = tkraise(self, aboveThis=None)
989 |
990 |  tkraise(self, aboveThis=None)
991 |      Raise this widget in the stacking order.
992 |
993 |  ----------------------------------------------------------------------
994 |  Data descriptors inherited from A:
995 |
996 |  __dict__
997 |      dictionary for instance variables
998 |
999 |  __weakref__
1000 |      list of weak references to the object
1001''' % __name__)
1002
1003        doc = pydoc.render_doc(B, renderer=pydoc.HTMLDoc())
1004        expected_text = f"""
1005Python Library Documentation
1006
1007class B in module {__name__}
1008class B(A)
1009    Method resolution order:
1010        B
1011        A
1012        builtins.object
1013
1014    Methods defined here:
1015        b_size = a_size(self)
1016        itemconfig = itemconfigure(self, tagOrId, cnf=None, **kw)
1017        itemconfigure(self, tagOrId, cnf=None, **kw)
1018            Configure resources of an item TAGORID.
1019
1020    Methods inherited from A:
1021        a_size(self)
1022            Return size
1023        lift = tkraise(self, aboveThis=None)
1024        tkraise(self, aboveThis=None)
1025            Raise this widget in the stacking order.
1026
1027    Data descriptors inherited from A:
1028        __dict__
1029            dictionary for instance variables
1030        __weakref__
1031            list of weak references to the object
1032"""
1033        as_text = html2text(doc)
1034        expected_lines = [line.strip() for line in expected_text.split("\n") if line]
1035        for expected_line in expected_lines:
1036            self.assertIn(expected_line, as_text)
1037
1038    def test_long_signatures(self):
1039        from collections.abc import Callable
1040        from typing import Literal, Annotated
1041
1042        class A:
1043            def __init__(self,
1044                         arg1: Callable[[int, int, int], str],
1045                         arg2: Literal['some value', 'other value'],
1046                         arg3: Annotated[int, 'some docs about this type'],
1047                         ) -> None:
1048                ...
1049
1050        doc = pydoc.render_doc(A)
1051        doc = clean_text(doc)
1052        self.assertEqual(doc, '''Python Library Documentation: class A in module %s
1053
1054class A(builtins.object)
1055 |  A(
1056 |      arg1: collections.abc.Callable[[int, int, int], str],
1057 |      arg2: Literal['some value', 'other value'],
1058 |      arg3: Annotated[int, 'some docs about this type']
1059 |  ) -> None
1060 |
1061 |  Methods defined here:
1062 |
1063 |  __init__(
1064 |      self,
1065 |      arg1: collections.abc.Callable[[int, int, int], str],
1066 |      arg2: Literal['some value', 'other value'],
1067 |      arg3: Annotated[int, 'some docs about this type']
1068 |  ) -> None
1069 |
1070 |  ----------------------------------------------------------------------
1071 |  Data descriptors defined here:
1072 |
1073 |  __dict__%s
1074 |
1075 |  __weakref__%s
1076''' % (__name__,
1077       '' if MISSING_C_DOCSTRINGS else '\n |      dictionary for instance variables',
1078       '' if MISSING_C_DOCSTRINGS else '\n |      list of weak references to the object',
1079      ))
1080
1081        def func(
1082            arg1: Callable[[Annotated[int, 'Some doc']], str],
1083            arg2: Literal[1, 2, 3, 4, 5, 6, 7, 8],
1084        ) -> Annotated[int, 'Some other']:
1085            ...
1086
1087        doc = pydoc.render_doc(func)
1088        doc = clean_text(doc)
1089        self.assertEqual(doc, '''Python Library Documentation: function func in module %s
1090
1091func(
1092    arg1: collections.abc.Callable[[typing.Annotated[int, 'Some doc']], str],
1093    arg2: Literal[1, 2, 3, 4, 5, 6, 7, 8]
1094) -> Annotated[int, 'Some other']
1095''' % __name__)
1096
1097        def function_with_really_long_name_so_annotations_can_be_rather_small(
1098            arg1: int,
1099            arg2: str,
1100        ):
1101            ...
1102
1103        doc = pydoc.render_doc(function_with_really_long_name_so_annotations_can_be_rather_small)
1104        doc = clean_text(doc)
1105        self.assertEqual(doc, '''Python Library Documentation: function function_with_really_long_name_so_annotations_can_be_rather_small in module %s
1106
1107function_with_really_long_name_so_annotations_can_be_rather_small(
1108    arg1: int,
1109    arg2: str
1110)
1111''' % __name__)
1112
1113        does_not_have_name = lambda \
1114            very_long_parameter_name_that_should_not_fit_into_a_single_line, \
1115            second_very_long_parameter_name: ...
1116
1117        doc = pydoc.render_doc(does_not_have_name)
1118        doc = clean_text(doc)
1119        self.assertEqual(doc, '''Python Library Documentation: function <lambda> in module %s
1120
1121<lambda> lambda very_long_parameter_name_that_should_not_fit_into_a_single_line, second_very_long_parameter_name
1122''' % __name__)
1123
1124    def test__future__imports(self):
1125        # __future__ features are excluded from module help,
1126        # except when it's the __future__ module itself
1127        import __future__
1128        future_text, _ = get_pydoc_text(__future__)
1129        future_html, _ = get_pydoc_html(__future__)
1130        pydoc_mod_text, _ = get_pydoc_text(pydoc_mod)
1131        pydoc_mod_html, _ = get_pydoc_html(pydoc_mod)
1132
1133        for feature in __future__.all_feature_names:
1134            txt = f"{feature} = _Feature"
1135            html = f"<strong>{feature}</strong> = _Feature"
1136            self.assertIn(txt, future_text)
1137            self.assertIn(html, future_html)
1138            self.assertNotIn(txt, pydoc_mod_text)
1139            self.assertNotIn(html, pydoc_mod_html)
1140
1141
1142class PydocImportTest(PydocBaseTest):
1143
1144    def setUp(self):
1145        self.test_dir = os.mkdir(TESTFN)
1146        self.addCleanup(rmtree, TESTFN)
1147        importlib.invalidate_caches()
1148
1149    def test_badimport(self):
1150        # This tests the fix for issue 5230, where if pydoc found the module
1151        # but the module had an internal import error pydoc would report no doc
1152        # found.
1153        modname = 'testmod_xyzzy'
1154        testpairs = (
1155            ('i_am_not_here', 'i_am_not_here'),
1156            ('test.i_am_not_here_either', 'test.i_am_not_here_either'),
1157            ('test.i_am_not_here.neither_am_i', 'test.i_am_not_here'),
1158            ('i_am_not_here.{}'.format(modname), 'i_am_not_here'),
1159            ('test.{}'.format(modname), 'test.{}'.format(modname)),
1160            )
1161
1162        sourcefn = os.path.join(TESTFN, modname) + os.extsep + "py"
1163        for importstring, expectedinmsg in testpairs:
1164            with open(sourcefn, 'w') as f:
1165                f.write("import {}\n".format(importstring))
1166            result = run_pydoc_fail(modname, PYTHONPATH=TESTFN).decode("ascii")
1167            expected = badimport_pattern % (modname, expectedinmsg)
1168            self.assertEqual(expected, result)
1169
1170    def test_apropos_with_bad_package(self):
1171        # Issue 7425 - pydoc -k failed when bad package on path
1172        pkgdir = os.path.join(TESTFN, "syntaxerr")
1173        os.mkdir(pkgdir)
1174        badsyntax = os.path.join(pkgdir, "__init__") + os.extsep + "py"
1175        with open(badsyntax, 'w') as f:
1176            f.write("invalid python syntax = $1\n")
1177        with self.restrict_walk_packages(path=[TESTFN]):
1178            with captured_stdout() as out:
1179                with captured_stderr() as err:
1180                    pydoc.apropos('xyzzy')
1181            # No result, no error
1182            self.assertEqual(out.getvalue(), '')
1183            self.assertEqual(err.getvalue(), '')
1184            # The package name is still matched
1185            with captured_stdout() as out:
1186                with captured_stderr() as err:
1187                    pydoc.apropos('syntaxerr')
1188            self.assertEqual(out.getvalue().strip(), 'syntaxerr')
1189            self.assertEqual(err.getvalue(), '')
1190
1191    def test_apropos_with_unreadable_dir(self):
1192        # Issue 7367 - pydoc -k failed when unreadable dir on path
1193        self.unreadable_dir = os.path.join(TESTFN, "unreadable")
1194        os.mkdir(self.unreadable_dir, 0)
1195        self.addCleanup(os.rmdir, self.unreadable_dir)
1196        # Note, on Windows the directory appears to be still
1197        #   readable so this is not really testing the issue there
1198        with self.restrict_walk_packages(path=[TESTFN]):
1199            with captured_stdout() as out:
1200                with captured_stderr() as err:
1201                    pydoc.apropos('SOMEKEY')
1202        # No result, no error
1203        self.assertEqual(out.getvalue(), '')
1204        self.assertEqual(err.getvalue(), '')
1205
1206    @os_helper.skip_unless_working_chmod
1207    @unittest.skipIf(is_emscripten, "cannot remove x bit")
1208    def test_apropos_empty_doc(self):
1209        pkgdir = os.path.join(TESTFN, 'walkpkg')
1210        os.mkdir(pkgdir)
1211        self.addCleanup(rmtree, pkgdir)
1212        init_path = os.path.join(pkgdir, '__init__.py')
1213        with open(init_path, 'w') as fobj:
1214            fobj.write("foo = 1")
1215        current_mode = stat.S_IMODE(os.stat(pkgdir).st_mode)
1216        try:
1217            os.chmod(pkgdir, current_mode & ~stat.S_IEXEC)
1218            with self.restrict_walk_packages(path=[TESTFN]), captured_stdout() as stdout:
1219                pydoc.apropos('')
1220            self.assertIn('walkpkg', stdout.getvalue())
1221        finally:
1222            os.chmod(pkgdir, current_mode)
1223
1224    def test_url_search_package_error(self):
1225        # URL handler search should cope with packages that raise exceptions
1226        pkgdir = os.path.join(TESTFN, "test_error_package")
1227        os.mkdir(pkgdir)
1228        init = os.path.join(pkgdir, "__init__.py")
1229        with open(init, "wt", encoding="ascii") as f:
1230            f.write("""raise ValueError("ouch")\n""")
1231        with self.restrict_walk_packages(path=[TESTFN]):
1232            # Package has to be importable for the error to have any effect
1233            saved_paths = tuple(sys.path)
1234            sys.path.insert(0, TESTFN)
1235            try:
1236                with self.assertRaisesRegex(ValueError, "ouch"):
1237                    import test_error_package  # Sanity check
1238
1239                text = self.call_url_handler("search?key=test_error_package",
1240                    "Pydoc: Search Results")
1241                found = ('<a href="test_error_package.html">'
1242                    'test_error_package</a>')
1243                self.assertIn(found, text)
1244            finally:
1245                sys.path[:] = saved_paths
1246
1247    @unittest.skip('causes undesirable side-effects (#20128)')
1248    def test_modules(self):
1249        # See Helper.listmodules().
1250        num_header_lines = 2
1251        num_module_lines_min = 5  # Playing it safe.
1252        num_footer_lines = 3
1253        expected = num_header_lines + num_module_lines_min + num_footer_lines
1254
1255        output = StringIO()
1256        helper = pydoc.Helper(output=output)
1257        helper('modules')
1258        result = output.getvalue().strip()
1259        num_lines = len(result.splitlines())
1260
1261        self.assertGreaterEqual(num_lines, expected)
1262
1263    @unittest.skip('causes undesirable side-effects (#20128)')
1264    def test_modules_search(self):
1265        # See Helper.listmodules().
1266        expected = 'pydoc - '
1267
1268        output = StringIO()
1269        helper = pydoc.Helper(output=output)
1270        with captured_stdout() as help_io:
1271            helper('modules pydoc')
1272        result = help_io.getvalue()
1273
1274        self.assertIn(expected, result)
1275
1276    @unittest.skip('some buildbots are not cooperating (#20128)')
1277    def test_modules_search_builtin(self):
1278        expected = 'gc - '
1279
1280        output = StringIO()
1281        helper = pydoc.Helper(output=output)
1282        with captured_stdout() as help_io:
1283            helper('modules garbage')
1284        result = help_io.getvalue()
1285
1286        self.assertTrue(result.startswith(expected))
1287
1288    def test_importfile(self):
1289        try:
1290            loaded_pydoc = pydoc.importfile(pydoc.__file__)
1291
1292            self.assertIsNot(loaded_pydoc, pydoc)
1293            self.assertEqual(loaded_pydoc.__name__, 'pydoc')
1294            self.assertEqual(loaded_pydoc.__file__, pydoc.__file__)
1295            self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__)
1296        finally:
1297            sys.modules['pydoc'] = pydoc
1298
1299
1300class Rect:
1301    @property
1302    def area(self):
1303        '''Area of the rect'''
1304        return self.w * self.h
1305
1306
1307class Square(Rect):
1308    area = property(lambda self: self.side**2)
1309
1310
1311class TestDescriptions(unittest.TestCase):
1312    def tearDown(self):
1313        self.assertIs(sys.modules['pydoc'], pydoc)
1314
1315    def test_module(self):
1316        # Check that pydocfodder module can be described
1317        doc = pydoc.render_doc(pydocfodder)
1318        self.assertIn("pydocfodder", doc)
1319
1320    def test_class(self):
1321        class C: "New-style class"
1322        c = C()
1323
1324        self.assertEqual(pydoc.describe(C), 'class C')
1325        self.assertEqual(pydoc.describe(c), 'C')
1326        expected = 'C in module %s object' % __name__
1327        self.assertIn(expected, pydoc.render_doc(c))
1328
1329    def test_generic_alias(self):
1330        self.assertEqual(pydoc.describe(typing.List[int]), '_GenericAlias')
1331        doc = pydoc.render_doc(typing.List[int], renderer=pydoc.plaintext)
1332        self.assertIn('_GenericAlias in module typing', doc)
1333        self.assertIn('List = class list(object)', doc)
1334        if not MISSING_C_DOCSTRINGS:
1335            self.assertIn(list.__doc__.strip().splitlines()[0], doc)
1336
1337        self.assertEqual(pydoc.describe(list[int]), 'GenericAlias')
1338        doc = pydoc.render_doc(list[int], renderer=pydoc.plaintext)
1339        self.assertIn('GenericAlias in module builtins', doc)
1340        self.assertIn('\nclass list(object)', doc)
1341        if not MISSING_C_DOCSTRINGS:
1342            self.assertIn(list.__doc__.strip().splitlines()[0], doc)
1343
1344    def test_union_type(self):
1345        self.assertEqual(pydoc.describe(typing.Union[int, str]), '_UnionGenericAlias')
1346        doc = pydoc.render_doc(typing.Union[int, str], renderer=pydoc.plaintext)
1347        self.assertIn('_UnionGenericAlias in module typing', doc)
1348        self.assertIn('Union = typing.Union', doc)
1349        if typing.Union.__doc__:
1350            self.assertIn(typing.Union.__doc__.strip().splitlines()[0], doc)
1351
1352        self.assertEqual(pydoc.describe(int | str), 'UnionType')
1353        doc = pydoc.render_doc(int | str, renderer=pydoc.plaintext)
1354        self.assertIn('UnionType in module types object', doc)
1355        self.assertIn('\nclass UnionType(builtins.object)', doc)
1356        if not MISSING_C_DOCSTRINGS:
1357            self.assertIn(types.UnionType.__doc__.strip().splitlines()[0], doc)
1358
1359    def test_special_form(self):
1360        self.assertEqual(pydoc.describe(typing.NoReturn), '_SpecialForm')
1361        doc = pydoc.render_doc(typing.NoReturn, renderer=pydoc.plaintext)
1362        self.assertIn('_SpecialForm in module typing', doc)
1363        if typing.NoReturn.__doc__:
1364            self.assertIn('NoReturn = typing.NoReturn', doc)
1365            self.assertIn(typing.NoReturn.__doc__.strip().splitlines()[0], doc)
1366        else:
1367            self.assertIn('NoReturn = class _SpecialForm(_Final)', doc)
1368
1369    def test_typing_pydoc(self):
1370        def foo(data: typing.List[typing.Any],
1371                x: int) -> typing.Iterator[typing.Tuple[int, typing.Any]]:
1372            ...
1373        T = typing.TypeVar('T')
1374        class C(typing.Generic[T], typing.Mapping[int, str]): ...
1375        self.assertEqual(pydoc.render_doc(foo).splitlines()[-1],
1376                         'f\x08fo\x08oo\x08o(data: List[Any], x: int)'
1377                         ' -> Iterator[Tuple[int, Any]]')
1378        self.assertEqual(pydoc.render_doc(C).splitlines()[2],
1379                         'class C\x08C(collections.abc.Mapping, typing.Generic)')
1380
1381    def test_builtin(self):
1382        for name in ('str', 'str.translate', 'builtins.str',
1383                     'builtins.str.translate'):
1384            # test low-level function
1385            self.assertIsNotNone(pydoc.locate(name))
1386            # test high-level function
1387            try:
1388                pydoc.render_doc(name)
1389            except ImportError:
1390                self.fail('finding the doc of {!r} failed'.format(name))
1391
1392        for name in ('notbuiltins', 'strrr', 'strr.translate',
1393                     'str.trrrranslate', 'builtins.strrr',
1394                     'builtins.str.trrranslate'):
1395            self.assertIsNone(pydoc.locate(name))
1396            self.assertRaises(ImportError, pydoc.render_doc, name)
1397
1398    @staticmethod
1399    def _get_summary_line(o):
1400        text = pydoc.plain(pydoc.render_doc(o))
1401        lines = text.split('\n')
1402        assert len(lines) >= 2
1403        return lines[2]
1404
1405    @staticmethod
1406    def _get_summary_lines(o):
1407        text = pydoc.plain(pydoc.render_doc(o))
1408        lines = text.split('\n')
1409        return '\n'.join(lines[2:])
1410
1411    # these should include "self"
1412    def test_unbound_python_method(self):
1413        self.assertEqual(self._get_summary_line(textwrap.TextWrapper.wrap),
1414            "wrap(self, text)")
1415
1416    @requires_docstrings
1417    def test_unbound_builtin_method(self):
1418        self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
1419            "dump(self, obj, /) unbound _pickle.Pickler method")
1420
1421    # these no longer include "self"
1422    def test_bound_python_method(self):
1423        t = textwrap.TextWrapper()
1424        self.assertEqual(self._get_summary_line(t.wrap),
1425            "wrap(text) method of textwrap.TextWrapper instance")
1426    def test_field_order_for_named_tuples(self):
1427        Person = namedtuple('Person', ['nickname', 'firstname', 'agegroup'])
1428        s = pydoc.render_doc(Person)
1429        self.assertLess(s.index('nickname'), s.index('firstname'))
1430        self.assertLess(s.index('firstname'), s.index('agegroup'))
1431
1432        class NonIterableFields:
1433            _fields = None
1434
1435        class NonHashableFields:
1436            _fields = [[]]
1437
1438        # Make sure these doesn't fail
1439        pydoc.render_doc(NonIterableFields)
1440        pydoc.render_doc(NonHashableFields)
1441
1442    @requires_docstrings
1443    def test_bound_builtin_method(self):
1444        s = StringIO()
1445        p = _pickle.Pickler(s)
1446        self.assertEqual(self._get_summary_line(p.dump),
1447            "dump(obj, /) method of _pickle.Pickler instance")
1448
1449    # this should *never* include self!
1450    @requires_docstrings
1451    def test_module_level_callable(self):
1452        self.assertEqual(self._get_summary_line(os.stat),
1453            "stat(path, *, dir_fd=None, follow_symlinks=True)")
1454
1455    def test_module_level_callable_noargs(self):
1456        self.assertEqual(self._get_summary_line(time.time),
1457            "time()")
1458
1459    def test_module_level_callable_o(self):
1460        try:
1461            import _stat
1462        except ImportError:
1463            # stat.S_IMODE() and _stat.S_IMODE() have a different signature
1464            self.skipTest('_stat extension is missing')
1465
1466        self.assertEqual(self._get_summary_line(_stat.S_IMODE),
1467            "S_IMODE(object, /)")
1468
1469    def test_unbound_builtin_method_noargs(self):
1470        self.assertEqual(self._get_summary_line(str.lower),
1471            "lower(self, /) unbound builtins.str method")
1472
1473    def test_bound_builtin_method_noargs(self):
1474        self.assertEqual(self._get_summary_line(''.lower),
1475            "lower() method of builtins.str instance")
1476
1477    def test_unbound_builtin_method_o(self):
1478        self.assertEqual(self._get_summary_line(set.add),
1479            "add(self, object, /) unbound builtins.set method")
1480
1481    def test_bound_builtin_method_o(self):
1482        self.assertEqual(self._get_summary_line(set().add),
1483            "add(object, /) method of builtins.set instance")
1484
1485    def test_unbound_builtin_method_coexist_o(self):
1486        self.assertEqual(self._get_summary_line(set.__contains__),
1487            "__contains__(self, object, /) unbound builtins.set method")
1488
1489    def test_bound_builtin_method_coexist_o(self):
1490        self.assertEqual(self._get_summary_line(set().__contains__),
1491            "__contains__(object, /) method of builtins.set instance")
1492
1493    def test_unbound_builtin_classmethod_noargs(self):
1494        self.assertEqual(self._get_summary_line(datetime.datetime.__dict__['utcnow']),
1495            "utcnow(type, /) unbound datetime.datetime method")
1496
1497    def test_bound_builtin_classmethod_noargs(self):
1498        self.assertEqual(self._get_summary_line(datetime.datetime.utcnow),
1499            "utcnow() class method of datetime.datetime")
1500
1501    def test_unbound_builtin_classmethod_o(self):
1502        self.assertEqual(self._get_summary_line(dict.__dict__['__class_getitem__']),
1503            "__class_getitem__(type, object, /) unbound builtins.dict method")
1504
1505    def test_bound_builtin_classmethod_o(self):
1506        self.assertEqual(self._get_summary_line(dict.__class_getitem__),
1507            "__class_getitem__(object, /) class method of builtins.dict")
1508
1509    @support.cpython_only
1510    @requires_docstrings
1511    def test_module_level_callable_unrepresentable_default(self):
1512        _testcapi = import_helper.import_module("_testcapi")
1513        builtin = _testcapi.func_with_unrepresentable_signature
1514        self.assertEqual(self._get_summary_line(builtin),
1515            "func_with_unrepresentable_signature(a, b=<x>)")
1516
1517    @support.cpython_only
1518    @requires_docstrings
1519    def test_builtin_staticmethod_unrepresentable_default(self):
1520        self.assertEqual(self._get_summary_line(str.maketrans),
1521            "maketrans(x, y=<unrepresentable>, z=<unrepresentable>, /)")
1522        _testcapi = import_helper.import_module("_testcapi")
1523        cls = _testcapi.DocStringUnrepresentableSignatureTest
1524        self.assertEqual(self._get_summary_line(cls.staticmeth),
1525            "staticmeth(a, b=<x>)")
1526
1527    @support.cpython_only
1528    @requires_docstrings
1529    def test_unbound_builtin_method_unrepresentable_default(self):
1530        self.assertEqual(self._get_summary_line(dict.pop),
1531            "pop(self, key, default=<unrepresentable>, /) "
1532            "unbound builtins.dict method")
1533        _testcapi = import_helper.import_module("_testcapi")
1534        cls = _testcapi.DocStringUnrepresentableSignatureTest
1535        self.assertEqual(self._get_summary_line(cls.meth),
1536            "meth(self, /, a, b=<x>) unbound "
1537            "_testcapi.DocStringUnrepresentableSignatureTest method")
1538
1539    @support.cpython_only
1540    @requires_docstrings
1541    def test_bound_builtin_method_unrepresentable_default(self):
1542        self.assertEqual(self._get_summary_line({}.pop),
1543            "pop(key, default=<unrepresentable>, /) "
1544            "method of builtins.dict instance")
1545        _testcapi = import_helper.import_module("_testcapi")
1546        obj = _testcapi.DocStringUnrepresentableSignatureTest()
1547        self.assertEqual(self._get_summary_line(obj.meth),
1548            "meth(a, b=<x>) "
1549            "method of _testcapi.DocStringUnrepresentableSignatureTest instance")
1550
1551    @support.cpython_only
1552    @requires_docstrings
1553    def test_unbound_builtin_classmethod_unrepresentable_default(self):
1554        _testcapi = import_helper.import_module("_testcapi")
1555        cls = _testcapi.DocStringUnrepresentableSignatureTest
1556        descr = cls.__dict__['classmeth']
1557        self.assertEqual(self._get_summary_line(descr),
1558            "classmeth(type, /, a, b=<x>) unbound "
1559            "_testcapi.DocStringUnrepresentableSignatureTest method")
1560
1561    @support.cpython_only
1562    @requires_docstrings
1563    def test_bound_builtin_classmethod_unrepresentable_default(self):
1564        _testcapi = import_helper.import_module("_testcapi")
1565        cls = _testcapi.DocStringUnrepresentableSignatureTest
1566        self.assertEqual(self._get_summary_line(cls.classmeth),
1567            "classmeth(a, b=<x>) class method of "
1568            "_testcapi.DocStringUnrepresentableSignatureTest")
1569
1570    def test_overridden_text_signature(self):
1571        class C:
1572            def meth(*args, **kwargs):
1573                pass
1574            @classmethod
1575            def cmeth(*args, **kwargs):
1576                pass
1577            @staticmethod
1578            def smeth(*args, **kwargs):
1579                pass
1580        for text_signature, unbound, bound in [
1581            ("($slf)", "(slf, /)", "()"),
1582            ("($slf, /)", "(slf, /)", "()"),
1583            ("($slf, /, arg)", "(slf, /, arg)", "(arg)"),
1584            ("($slf, /, arg=<x>)", "(slf, /, arg=<x>)", "(arg=<x>)"),
1585            ("($slf, arg, /)", "(slf, arg, /)", "(arg, /)"),
1586            ("($slf, arg=<x>, /)", "(slf, arg=<x>, /)", "(arg=<x>, /)"),
1587            ("(/, slf, arg)", "(/, slf, arg)", "(/, slf, arg)"),
1588            ("(/, slf, arg=<x>)", "(/, slf, arg=<x>)", "(/, slf, arg=<x>)"),
1589            ("(slf, /, arg)", "(slf, /, arg)", "(arg)"),
1590            ("(slf, /, arg=<x>)", "(slf, /, arg=<x>)", "(arg=<x>)"),
1591            ("(slf, arg, /)", "(slf, arg, /)", "(arg, /)"),
1592            ("(slf, arg=<x>, /)", "(slf, arg=<x>, /)", "(arg=<x>, /)"),
1593        ]:
1594            with self.subTest(text_signature):
1595                C.meth.__text_signature__ = text_signature
1596                self.assertEqual(self._get_summary_line(C.meth),
1597                        "meth" + unbound)
1598                self.assertEqual(self._get_summary_line(C().meth),
1599                        "meth" + bound + " method of test.test_pydoc.test_pydoc.C instance")
1600                C.cmeth.__func__.__text_signature__ = text_signature
1601                self.assertEqual(self._get_summary_line(C.cmeth),
1602                        "cmeth" + bound + " class method of test.test_pydoc.test_pydoc.C")
1603                C.smeth.__text_signature__ = text_signature
1604                self.assertEqual(self._get_summary_line(C.smeth),
1605                        "smeth" + unbound)
1606
1607    @requires_docstrings
1608    def test_staticmethod(self):
1609        class X:
1610            @staticmethod
1611            def sm(x, y):
1612                '''A static method'''
1613                ...
1614        self.assertEqual(self._get_summary_lines(X.__dict__['sm']),
1615                         'sm(x, y)\n'
1616                         '    A static method\n')
1617        self.assertEqual(self._get_summary_lines(X.sm), """\
1618sm(x, y)
1619    A static method
1620""")
1621        self.assertIn("""
1622 |  Static methods defined here:
1623 |
1624 |  sm(x, y)
1625 |      A static method
1626""", pydoc.plain(pydoc.render_doc(X)))
1627
1628    @requires_docstrings
1629    def test_classmethod(self):
1630        class X:
1631            @classmethod
1632            def cm(cls, x):
1633                '''A class method'''
1634                ...
1635        self.assertEqual(self._get_summary_lines(X.__dict__['cm']),
1636                         'cm(...)\n'
1637                         '    A class method\n')
1638        self.assertEqual(self._get_summary_lines(X.cm), """\
1639cm(x) class method of test.test_pydoc.test_pydoc.X
1640    A class method
1641""")
1642        self.assertIn("""
1643 |  Class methods defined here:
1644 |
1645 |  cm(x)
1646 |      A class method
1647""", pydoc.plain(pydoc.render_doc(X)))
1648
1649    @requires_docstrings
1650    def test_getset_descriptor(self):
1651        # Currently these attributes are implemented as getset descriptors
1652        # in CPython.
1653        self.assertEqual(self._get_summary_line(int.numerator), "numerator")
1654        self.assertEqual(self._get_summary_line(float.real), "real")
1655        self.assertEqual(self._get_summary_line(Exception.args), "args")
1656        self.assertEqual(self._get_summary_line(memoryview.obj), "obj")
1657
1658    @requires_docstrings
1659    def test_member_descriptor(self):
1660        # Currently these attributes are implemented as member descriptors
1661        # in CPython.
1662        self.assertEqual(self._get_summary_line(complex.real), "real")
1663        self.assertEqual(self._get_summary_line(range.start), "start")
1664        self.assertEqual(self._get_summary_line(slice.start), "start")
1665        self.assertEqual(self._get_summary_line(property.fget), "fget")
1666        self.assertEqual(self._get_summary_line(StopIteration.value), "value")
1667
1668    @requires_docstrings
1669    def test_slot_descriptor(self):
1670        class Point:
1671            __slots__ = 'x', 'y'
1672        self.assertEqual(self._get_summary_line(Point.x), "x")
1673
1674    @requires_docstrings
1675    def test_dict_attr_descriptor(self):
1676        class NS:
1677            pass
1678        self.assertEqual(self._get_summary_line(NS.__dict__['__dict__']),
1679                         "__dict__")
1680
1681    @requires_docstrings
1682    def test_structseq_member_descriptor(self):
1683        self.assertEqual(self._get_summary_line(type(sys.hash_info).width),
1684                         "width")
1685        self.assertEqual(self._get_summary_line(type(sys.flags).debug),
1686                         "debug")
1687        self.assertEqual(self._get_summary_line(type(sys.version_info).major),
1688                         "major")
1689        self.assertEqual(self._get_summary_line(type(sys.float_info).max),
1690                         "max")
1691
1692    @requires_docstrings
1693    def test_namedtuple_field_descriptor(self):
1694        Box = namedtuple('Box', ('width', 'height'))
1695        self.assertEqual(self._get_summary_lines(Box.width), """\
1696    Alias for field number 0
1697""")
1698
1699    @requires_docstrings
1700    def test_property(self):
1701        self.assertEqual(self._get_summary_lines(Rect.area), """\
1702area
1703    Area of the rect
1704""")
1705        # inherits the docstring from Rect.area
1706        self.assertEqual(self._get_summary_lines(Square.area), """\
1707area
1708    Area of the rect
1709""")
1710        self.assertIn("""
1711 |  area
1712 |      Area of the rect
1713""", pydoc.plain(pydoc.render_doc(Rect)))
1714
1715    @requires_docstrings
1716    def test_custom_non_data_descriptor(self):
1717        class Descr:
1718            def __get__(self, obj, cls):
1719                if obj is None:
1720                    return self
1721                return 42
1722        class X:
1723            attr = Descr()
1724
1725        self.assertEqual(self._get_summary_lines(X.attr), f"""\
1726<{__name__}.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>""")
1727
1728        X.attr.__doc__ = 'Custom descriptor'
1729        self.assertEqual(self._get_summary_lines(X.attr), f"""\
1730<{__name__}.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>
1731    Custom descriptor
1732""")
1733
1734        X.attr.__name__ = 'foo'
1735        self.assertEqual(self._get_summary_lines(X.attr), """\
1736foo(...)
1737    Custom descriptor
1738""")
1739
1740    @requires_docstrings
1741    def test_custom_data_descriptor(self):
1742        class Descr:
1743            def __get__(self, obj, cls):
1744                if obj is None:
1745                    return self
1746                return 42
1747            def __set__(self, obj, cls):
1748                1/0
1749        class X:
1750            attr = Descr()
1751
1752        self.assertEqual(self._get_summary_lines(X.attr), "")
1753
1754        X.attr.__doc__ = 'Custom descriptor'
1755        self.assertEqual(self._get_summary_lines(X.attr), """\
1756    Custom descriptor
1757""")
1758
1759        X.attr.__name__ = 'foo'
1760        self.assertEqual(self._get_summary_lines(X.attr), """\
1761foo
1762    Custom descriptor
1763""")
1764
1765    def test_async_annotation(self):
1766        async def coro_function(ign) -> int:
1767            return 1
1768
1769        text = pydoc.plain(pydoc.plaintext.document(coro_function))
1770        self.assertIn('async coro_function', text)
1771
1772        html = pydoc.HTMLDoc().document(coro_function)
1773        self.assertIn(
1774            'async <a name="-coro_function"><strong>coro_function',
1775            html)
1776
1777    def test_async_generator_annotation(self):
1778        async def an_async_generator():
1779            yield 1
1780
1781        text = pydoc.plain(pydoc.plaintext.document(an_async_generator))
1782        self.assertIn('async an_async_generator', text)
1783
1784        html = pydoc.HTMLDoc().document(an_async_generator)
1785        self.assertIn(
1786            'async <a name="-an_async_generator"><strong>an_async_generator',
1787            html)
1788
1789    @requires_docstrings
1790    def test_html_for_https_links(self):
1791        def a_fn_with_https_link():
1792            """a link https://localhost/"""
1793            pass
1794
1795        html = pydoc.HTMLDoc().document(a_fn_with_https_link)
1796        self.assertIn(
1797            '<a href="https://localhost/">https://localhost/</a>',
1798            html
1799        )
1800
1801
1802class PydocFodderTest(unittest.TestCase):
1803    def tearDown(self):
1804        self.assertIs(sys.modules['pydoc'], pydoc)
1805
1806    def getsection(self, text, beginline, endline):
1807        lines = text.splitlines()
1808        beginindex, endindex = 0, None
1809        if beginline is not None:
1810            beginindex = lines.index(beginline)
1811        if endline is not None:
1812            endindex = lines.index(endline, beginindex)
1813        return lines[beginindex:endindex]
1814
1815    def test_text_doc_routines_in_class(self, cls=pydocfodder.B):
1816        doc = pydoc.TextDoc()
1817        result = doc.docclass(cls)
1818        result = clean_text(result)
1819        where = 'defined here' if cls is pydocfodder.B else 'inherited from B'
1820        lines = self.getsection(result, f' |  Methods {where}:', ' |  ' + '-'*70)
1821        self.assertIn(' |  A_method_alias = A_method(self)', lines)
1822        self.assertIn(' |  B_method_alias = B_method(self)', lines)
1823        self.assertIn(' |  A_staticmethod(x, y) from test.test_pydoc.pydocfodder.A', lines)
1824        self.assertIn(' |  A_staticmethod_alias = A_staticmethod(x, y)', lines)
1825        self.assertIn(' |  global_func(x, y) from test.test_pydoc.pydocfodder', lines)
1826        self.assertIn(' |  global_func_alias = global_func(x, y)', lines)
1827        self.assertIn(' |  global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines)
1828        self.assertIn(' |  count(self, value, /) from builtins.list', lines)
1829        self.assertIn(' |  list_count = count(self, value, /)', lines)
1830        self.assertIn(' |  __repr__(self, /) from builtins.object', lines)
1831        self.assertIn(' |  object_repr = __repr__(self, /)', lines)
1832
1833        lines = self.getsection(result, f' |  Static methods {where}:', ' |  ' + '-'*70)
1834        self.assertIn(' |  A_classmethod_ref = A_classmethod(x) class method of test.test_pydoc.pydocfodder.A', lines)
1835        note = '' if cls is pydocfodder.B else ' class method of test.test_pydoc.pydocfodder.B'
1836        self.assertIn(' |  B_classmethod_ref = B_classmethod(x)' + note, lines)
1837        self.assertIn(' |  A_method_ref = A_method() method of test.test_pydoc.pydocfodder.A instance', lines)
1838        self.assertIn(' |  get(key, default=None, /) method of builtins.dict instance', lines)
1839        self.assertIn(' |  dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
1840
1841        lines = self.getsection(result, f' |  Class methods {where}:', ' |  ' + '-'*70)
1842        self.assertIn(' |  B_classmethod(x)', lines)
1843        self.assertIn(' |  B_classmethod_alias = B_classmethod(x)', lines)
1844
1845    def test_html_doc_routines_in_class(self, cls=pydocfodder.B):
1846        doc = pydoc.HTMLDoc()
1847        result = doc.docclass(cls)
1848        result = html2text(result)
1849        where = 'defined here' if cls is pydocfodder.B else 'inherited from B'
1850        lines = self.getsection(result, f'Methods {where}:', '-'*70)
1851        self.assertIn('A_method_alias = A_method(self)', lines)
1852        self.assertIn('B_method_alias = B_method(self)', lines)
1853        self.assertIn('A_staticmethod(x, y) from test.test_pydoc.pydocfodder.A', lines)
1854        self.assertIn('A_staticmethod_alias = A_staticmethod(x, y)', lines)
1855        self.assertIn('global_func(x, y) from test.test_pydoc.pydocfodder', lines)
1856        self.assertIn('global_func_alias = global_func(x, y)', lines)
1857        self.assertIn('global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines)
1858        self.assertIn('count(self, value, /) from builtins.list', lines)
1859        self.assertIn('list_count = count(self, value, /)', lines)
1860        self.assertIn('__repr__(self, /) from builtins.object', lines)
1861        self.assertIn('object_repr = __repr__(self, /)', lines)
1862
1863        lines = self.getsection(result, f'Static methods {where}:', '-'*70)
1864        self.assertIn('A_classmethod_ref = A_classmethod(x) class method of test.test_pydoc.pydocfodder.A', lines)
1865        note = '' if cls is pydocfodder.B else ' class method of test.test_pydoc.pydocfodder.B'
1866        self.assertIn('B_classmethod_ref = B_classmethod(x)' + note, lines)
1867        self.assertIn('A_method_ref = A_method() method of test.test_pydoc.pydocfodder.A instance', lines)
1868
1869        lines = self.getsection(result, f'Class methods {where}:', '-'*70)
1870        self.assertIn('B_classmethod(x)', lines)
1871        self.assertIn('B_classmethod_alias = B_classmethod(x)', lines)
1872
1873    def test_text_doc_inherited_routines_in_class(self):
1874        self.test_text_doc_routines_in_class(pydocfodder.D)
1875
1876    def test_html_doc_inherited_routines_in_class(self):
1877        self.test_html_doc_routines_in_class(pydocfodder.D)
1878
1879    def test_text_doc_routines_in_module(self):
1880        doc = pydoc.TextDoc()
1881        result = doc.docmodule(pydocfodder)
1882        result = clean_text(result)
1883        lines = self.getsection(result, 'FUNCTIONS', 'FILE')
1884        # function alias
1885        self.assertIn('    global_func_alias = global_func(x, y)', lines)
1886        self.assertIn('    A_staticmethod(x, y)', lines)
1887        self.assertIn('    A_staticmethod_alias = A_staticmethod(x, y)', lines)
1888        # bound class methods
1889        self.assertIn('    A_classmethod(x) class method of A', lines)
1890        self.assertIn('    A_classmethod2 = A_classmethod(x) class method of A', lines)
1891        self.assertIn('    A_classmethod3 = A_classmethod(x) class method of B', lines)
1892        # bound methods
1893        self.assertIn('    A_method() method of A instance', lines)
1894        self.assertIn('    A_method2 = A_method() method of A instance', lines)
1895        self.assertIn('    A_method3 = A_method() method of B instance', lines)
1896        self.assertIn('    A_staticmethod_ref = A_staticmethod(x, y)', lines)
1897        self.assertIn('    A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines)
1898        self.assertIn('    get(key, default=None, /) method of builtins.dict instance', lines)
1899        self.assertIn('    dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
1900        # unbound methods
1901        self.assertIn('    B_method(self)', lines)
1902        self.assertIn('    B_method2 = B_method(self)', lines)
1903        self.assertIn('    count(self, value, /) unbound builtins.list method', lines)
1904        self.assertIn('    list_count = count(self, value, /) unbound builtins.list method', lines)
1905        self.assertIn('    __repr__(self, /) unbound builtins.object method', lines)
1906        self.assertIn('    object_repr = __repr__(self, /) unbound builtins.object method', lines)
1907
1908    def test_html_doc_routines_in_module(self):
1909        doc = pydoc.HTMLDoc()
1910        result = doc.docmodule(pydocfodder)
1911        result = html2text(result)
1912        lines = self.getsection(result, ' Functions', None)
1913        # function alias
1914        self.assertIn(' global_func_alias = global_func(x, y)', lines)
1915        self.assertIn(' A_staticmethod(x, y)', lines)
1916        self.assertIn(' A_staticmethod_alias = A_staticmethod(x, y)', lines)
1917        # bound class methods
1918        self.assertIn('A_classmethod(x) class method of A', lines)
1919        self.assertIn(' A_classmethod2 = A_classmethod(x) class method of A', lines)
1920        self.assertIn(' A_classmethod3 = A_classmethod(x) class method of B', lines)
1921        # bound methods
1922        self.assertIn(' A_method() method of A instance', lines)
1923        self.assertIn(' A_method2 = A_method() method of A instance', lines)
1924        self.assertIn(' A_method3 = A_method() method of B instance', lines)
1925        self.assertIn(' A_staticmethod_ref = A_staticmethod(x, y)', lines)
1926        self.assertIn(' A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines)
1927        self.assertIn(' get(key, default=None, /) method of builtins.dict instance', lines)
1928        self.assertIn(' dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
1929        # unbound methods
1930        self.assertIn(' B_method(self)', lines)
1931        self.assertIn(' B_method2 = B_method(self)', lines)
1932        self.assertIn(' count(self, value, /) unbound builtins.list method', lines)
1933        self.assertIn(' list_count = count(self, value, /) unbound builtins.list method', lines)
1934        self.assertIn(' __repr__(self, /) unbound builtins.object method', lines)
1935        self.assertIn(' object_repr = __repr__(self, /) unbound builtins.object method', lines)
1936
1937
1938@unittest.skipIf(
1939    is_emscripten or is_wasi,
1940    "Socket server not available on Emscripten/WASI."
1941)
1942class PydocServerTest(unittest.TestCase):
1943    """Tests for pydoc._start_server"""
1944    def tearDown(self):
1945        self.assertIs(sys.modules['pydoc'], pydoc)
1946
1947    def test_server(self):
1948        # Minimal test that starts the server, checks that it works, then stops
1949        # it and checks its cleanup.
1950        def my_url_handler(url, content_type):
1951            text = 'the URL sent was: (%s, %s)' % (url, content_type)
1952            return text
1953
1954        serverthread = pydoc._start_server(
1955            my_url_handler,
1956            hostname='localhost',
1957            port=0,
1958            )
1959        self.assertEqual(serverthread.error, None)
1960        self.assertTrue(serverthread.serving)
1961        self.addCleanup(
1962            lambda: serverthread.stop() if serverthread.serving else None
1963            )
1964        self.assertIn('localhost', serverthread.url)
1965
1966        self.addCleanup(urlcleanup)
1967        self.assertEqual(
1968            b'the URL sent was: (/test, text/html)',
1969            urlopen(urllib.parse.urljoin(serverthread.url, '/test')).read(),
1970            )
1971        self.assertEqual(
1972            b'the URL sent was: (/test.css, text/css)',
1973            urlopen(urllib.parse.urljoin(serverthread.url, '/test.css')).read(),
1974            )
1975
1976        serverthread.stop()
1977        self.assertFalse(serverthread.serving)
1978        self.assertIsNone(serverthread.docserver)
1979        self.assertIsNone(serverthread.url)
1980
1981
1982class PydocUrlHandlerTest(PydocBaseTest):
1983    """Tests for pydoc._url_handler"""
1984
1985    def test_content_type_err(self):
1986        f = pydoc._url_handler
1987        self.assertRaises(TypeError, f, 'A', '')
1988        self.assertRaises(TypeError, f, 'B', 'foobar')
1989
1990    def test_url_requests(self):
1991        # Test for the correct title in the html pages returned.
1992        # This tests the different parts of the URL handler without
1993        # getting too picky about the exact html.
1994        requests = [
1995            ("", "Pydoc: Index of Modules"),
1996            ("get?key=", "Pydoc: Index of Modules"),
1997            ("index", "Pydoc: Index of Modules"),
1998            ("topics", "Pydoc: Topics"),
1999            ("keywords", "Pydoc: Keywords"),
2000            ("pydoc", "Pydoc: module pydoc"),
2001            ("get?key=pydoc", "Pydoc: module pydoc"),
2002            ("search?key=pydoc", "Pydoc: Search Results"),
2003            ("topic?key=def", "Pydoc: KEYWORD def"),
2004            ("topic?key=STRINGS", "Pydoc: TOPIC STRINGS"),
2005            ("foobar", "Pydoc: Error - foobar"),
2006            ]
2007
2008        self.assertIs(sys.modules['pydoc'], pydoc)
2009        try:
2010            with self.restrict_walk_packages():
2011                for url, title in requests:
2012                    self.call_url_handler(url, title)
2013        finally:
2014            # Some requests reload the module and change sys.modules.
2015            sys.modules['pydoc'] = pydoc
2016
2017
2018class TestHelper(unittest.TestCase):
2019    def test_keywords(self):
2020        self.assertEqual(sorted(pydoc.Helper.keywords),
2021                         sorted(keyword.kwlist))
2022
2023
2024class PydocWithMetaClasses(unittest.TestCase):
2025    def tearDown(self):
2026        self.assertIs(sys.modules['pydoc'], pydoc)
2027
2028    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
2029                     'trace function introduces __locals__ unexpectedly')
2030    @requires_docstrings
2031    def test_DynamicClassAttribute(self):
2032        class Meta(type):
2033            def __getattr__(self, name):
2034                if name == 'ham':
2035                    return 'spam'
2036                return super().__getattr__(name)
2037        class DA(metaclass=Meta):
2038            @types.DynamicClassAttribute
2039            def ham(self):
2040                return 'eggs'
2041        expected_text_data_docstrings = tuple('\n |      ' + s if s else ''
2042                                      for s in expected_data_docstrings)
2043        output = StringIO()
2044        helper = pydoc.Helper(output=output)
2045        helper(DA)
2046        expected_text = expected_dynamicattribute_pattern % (
2047                (__name__,) + expected_text_data_docstrings[:2])
2048        result = output.getvalue().strip()
2049        self.assertEqual(expected_text, result)
2050
2051    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
2052                     'trace function introduces __locals__ unexpectedly')
2053    @requires_docstrings
2054    def test_virtualClassAttributeWithOneMeta(self):
2055        class Meta(type):
2056            def __dir__(cls):
2057                return ['__class__', '__module__', '__name__', 'LIFE']
2058            def __getattr__(self, name):
2059                if name =='LIFE':
2060                    return 42
2061                return super().__getattr(name)
2062        class Class(metaclass=Meta):
2063            pass
2064        output = StringIO()
2065        helper = pydoc.Helper(output=output)
2066        helper(Class)
2067        expected_text = expected_virtualattribute_pattern1 % __name__
2068        result = output.getvalue().strip()
2069        self.assertEqual(expected_text, result)
2070
2071    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
2072                     'trace function introduces __locals__ unexpectedly')
2073    @requires_docstrings
2074    def test_virtualClassAttributeWithTwoMeta(self):
2075        class Meta1(type):
2076            def __dir__(cls):
2077                return ['__class__', '__module__', '__name__', 'one']
2078            def __getattr__(self, name):
2079                if name =='one':
2080                    return 1
2081                return super().__getattr__(name)
2082        class Meta2(type):
2083            def __dir__(cls):
2084                return ['__class__', '__module__', '__name__', 'two']
2085            def __getattr__(self, name):
2086                if name =='two':
2087                    return 2
2088                return super().__getattr__(name)
2089        class Meta3(Meta1, Meta2):
2090            def __dir__(cls):
2091                return list(sorted(set(
2092                    ['__class__', '__module__', '__name__', 'three'] +
2093                    Meta1.__dir__(cls) + Meta2.__dir__(cls))))
2094            def __getattr__(self, name):
2095                if name =='three':
2096                    return 3
2097                return super().__getattr__(name)
2098        class Class1(metaclass=Meta1):
2099            pass
2100        class Class2(Class1, metaclass=Meta3):
2101            pass
2102        output = StringIO()
2103        helper = pydoc.Helper(output=output)
2104        helper(Class1)
2105        expected_text1 = expected_virtualattribute_pattern2 % __name__
2106        result1 = output.getvalue().strip()
2107        self.assertEqual(expected_text1, result1)
2108        output = StringIO()
2109        helper = pydoc.Helper(output=output)
2110        helper(Class2)
2111        expected_text2 = expected_virtualattribute_pattern3 % __name__
2112        result2 = output.getvalue().strip()
2113        self.assertEqual(expected_text2, result2)
2114
2115    @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
2116                     'trace function introduces __locals__ unexpectedly')
2117    @requires_docstrings
2118    def test_buggy_dir(self):
2119        class M(type):
2120            def __dir__(cls):
2121                return ['__class__', '__name__', 'missing', 'here']
2122        class C(metaclass=M):
2123            here = 'present!'
2124        output = StringIO()
2125        helper = pydoc.Helper(output=output)
2126        helper(C)
2127        expected_text = expected_missingattribute_pattern % __name__
2128        result = output.getvalue().strip()
2129        self.assertEqual(expected_text, result)
2130
2131    def test_resolve_false(self):
2132        # Issue #23008: pydoc enum.{,Int}Enum failed
2133        # because bool(enum.Enum) is False.
2134        with captured_stdout() as help_io:
2135            pydoc.help('enum.Enum')
2136        helptext = help_io.getvalue()
2137        self.assertIn('class Enum', helptext)
2138
2139
2140class TestInternalUtilities(unittest.TestCase):
2141
2142    def setUp(self):
2143        tmpdir = tempfile.TemporaryDirectory()
2144        self.argv0dir = tmpdir.name
2145        self.argv0 = os.path.join(tmpdir.name, "nonexistent")
2146        self.addCleanup(tmpdir.cleanup)
2147        self.abs_curdir = abs_curdir = os.getcwd()
2148        self.curdir_spellings = ["", os.curdir, abs_curdir]
2149
2150    def _get_revised_path(self, given_path, argv0=None):
2151        # Checking that pydoc.cli() actually calls pydoc._get_revised_path()
2152        # is handled via code review (at least for now).
2153        if argv0 is None:
2154            argv0 = self.argv0
2155        return pydoc._get_revised_path(given_path, argv0)
2156
2157    def _get_starting_path(self):
2158        # Get a copy of sys.path without the current directory.
2159        clean_path = sys.path.copy()
2160        for spelling in self.curdir_spellings:
2161            for __ in range(clean_path.count(spelling)):
2162                clean_path.remove(spelling)
2163        return clean_path
2164
2165    def test_sys_path_adjustment_adds_missing_curdir(self):
2166        clean_path = self._get_starting_path()
2167        expected_path = [self.abs_curdir] + clean_path
2168        self.assertEqual(self._get_revised_path(clean_path), expected_path)
2169
2170    def test_sys_path_adjustment_removes_argv0_dir(self):
2171        clean_path = self._get_starting_path()
2172        expected_path = [self.abs_curdir] + clean_path
2173        leading_argv0dir = [self.argv0dir] + clean_path
2174        self.assertEqual(self._get_revised_path(leading_argv0dir), expected_path)
2175        trailing_argv0dir = clean_path + [self.argv0dir]
2176        self.assertEqual(self._get_revised_path(trailing_argv0dir), expected_path)
2177
2178    def test_sys_path_adjustment_protects_pydoc_dir(self):
2179        def _get_revised_path(given_path):
2180            return self._get_revised_path(given_path, argv0=pydoc.__file__)
2181        clean_path = self._get_starting_path()
2182        leading_argv0dir = [self.argv0dir] + clean_path
2183        expected_path = [self.abs_curdir] + leading_argv0dir
2184        self.assertEqual(_get_revised_path(leading_argv0dir), expected_path)
2185        trailing_argv0dir = clean_path + [self.argv0dir]
2186        expected_path = [self.abs_curdir] + trailing_argv0dir
2187        self.assertEqual(_get_revised_path(trailing_argv0dir), expected_path)
2188
2189    def test_sys_path_adjustment_when_curdir_already_included(self):
2190        clean_path = self._get_starting_path()
2191        for spelling in self.curdir_spellings:
2192            with self.subTest(curdir_spelling=spelling):
2193                # If curdir is already present, no alterations are made at all
2194                leading_curdir = [spelling] + clean_path
2195                self.assertIsNone(self._get_revised_path(leading_curdir))
2196                trailing_curdir = clean_path + [spelling]
2197                self.assertIsNone(self._get_revised_path(trailing_curdir))
2198                leading_argv0dir = [self.argv0dir] + leading_curdir
2199                self.assertIsNone(self._get_revised_path(leading_argv0dir))
2200                trailing_argv0dir = trailing_curdir + [self.argv0dir]
2201                self.assertIsNone(self._get_revised_path(trailing_argv0dir))
2202
2203
2204def setUpModule():
2205    thread_info = threading_helper.threading_setup()
2206    unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info)
2207    unittest.addModuleCleanup(reap_children)
2208
2209
2210if __name__ == "__main__":
2211    unittest.main()
2212