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