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