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