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