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