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