Home | History | Annotate | Download | only in test
      1 import os
      2 import sys
      3 import difflib
      4 import __builtin__
      5 import re
      6 import pydoc
      7 import inspect
      8 import keyword
      9 import unittest
     10 import xml.etree
     11 import test.test_support
     12 from collections import namedtuple
     13 from test.script_helper import assert_python_ok
     14 from test.test_support import (
     15     TESTFN, rmtree, reap_children, captured_stdout)
     16 
     17 from test import pydoc_mod
     18 
     19 if test.test_support.HAVE_DOCSTRINGS:
     20     expected_data_docstrings = (
     21         'dictionary for instance variables (if defined)',
     22         'list of weak references to the object (if defined)',
     23         )
     24 else:
     25     expected_data_docstrings = ('', '')
     26 
     27 expected_text_pattern = \
     28 """
     29 NAME
     30     test.pydoc_mod - This is a test module for test_pydoc
     31 
     32 FILE
     33     %s
     34 %s
     35 CLASSES
     36     __builtin__.object
     37         B
     38     A
     39 \x20\x20\x20\x20
     40     class A
     41      |  Hello and goodbye
     42      |\x20\x20
     43      |  Methods defined here:
     44      |\x20\x20
     45      |  __init__()
     46      |      Wow, I have no function!
     47 \x20\x20\x20\x20
     48     class B(__builtin__.object)
     49      |  Data descriptors defined here:
     50      |\x20\x20
     51      |  __dict__%s
     52      |\x20\x20
     53      |  __weakref__%s
     54      |\x20\x20
     55      |  ----------------------------------------------------------------------
     56      |  Data and other attributes defined here:
     57      |\x20\x20
     58      |  NO_MEANING = 'eggs'
     59 
     60 FUNCTIONS
     61     doc_func()
     62         This function solves all of the world's problems:
     63         hunger
     64         lack of Python
     65         war
     66 \x20\x20\x20\x20
     67     nodoc_func()
     68 
     69 DATA
     70     __author__ = 'Benjamin Peterson'
     71     __credits__ = 'Nobody'
     72     __version__ = '1.2.3.4'
     73 
     74 VERSION
     75     1.2.3.4
     76 
     77 AUTHOR
     78     Benjamin Peterson
     79 
     80 CREDITS
     81     Nobody
     82 """.strip()
     83 
     84 expected_text_data_docstrings = tuple('\n     |      ' + s if s else ''
     85                                       for s in expected_data_docstrings)
     86 
     87 expected_html_pattern = \
     88 """
     89 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="heading">
     90 <tr bgcolor="#7799ee">
     91 <td valign=bottom>&nbsp;<br>
     92 <font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong><a href="test.html"><font color="#ffffff">test</font></a>.pydoc_mod</strong></big></big> (version 1.2.3.4)</font></td
     93 ><td align=right valign=bottom
     94 ><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:%s">%s</a>%s</font></td></tr></table>
     95     <p><tt>This&nbsp;is&nbsp;a&nbsp;test&nbsp;module&nbsp;for&nbsp;test_pydoc</tt></p>
     96 <p>
     97 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
     98 <tr bgcolor="#ee77aa">
     99 <td colspan=3 valign=bottom>&nbsp;<br>
    100 <font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
    101 \x20\x20\x20\x20
    102 <tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
    103 <td width="100%%"><dl>
    104 <dt><font face="helvetica, arial"><a href="__builtin__.html#object">__builtin__.object</a>
    105 </font></dt><dd>
    106 <dl>
    107 <dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#B">B</a>
    108 </font></dt></dl>
    109 </dd>
    110 <dt><font face="helvetica, arial"><a href="test.pydoc_mod.html#A">A</a>
    111 </font></dt></dl>
    112  <p>
    113 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
    114 <tr bgcolor="#ffc8d8">
    115 <td colspan=3 valign=bottom>&nbsp;<br>
    116 <font color="#000000" face="helvetica, arial"><a name="A">class <strong>A</strong></a></font></td></tr>
    117 \x20\x20\x20\x20
    118 <tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
    119 <td colspan=2><tt>Hello&nbsp;and&nbsp;goodbye<br>&nbsp;</tt></td></tr>
    120 <tr><td>&nbsp;</td>
    121 <td width="100%%">Methods defined here:<br>
    122 <dl><dt><a name="A-__init__"><strong>__init__</strong></a>()</dt><dd><tt>Wow,&nbsp;I&nbsp;have&nbsp;no&nbsp;function!</tt></dd></dl>
    123 
    124 </td></tr></table> <p>
    125 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
    126 <tr bgcolor="#ffc8d8">
    127 <td colspan=3 valign=bottom>&nbsp;<br>
    128 <font color="#000000" face="helvetica, arial"><a name="B">class <strong>B</strong></a>(<a href="__builtin__.html#object">__builtin__.object</a>)</font></td></tr>
    129 \x20\x20\x20\x20
    130 <tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
    131 <td width="100%%">Data descriptors defined here:<br>
    132 <dl><dt><strong>__dict__</strong></dt>
    133 <dd><tt>%s</tt></dd>
    134 </dl>
    135 <dl><dt><strong>__weakref__</strong></dt>
    136 <dd><tt>%s</tt></dd>
    137 </dl>
    138 <hr>
    139 Data and other attributes defined here:<br>
    140 <dl><dt><strong>NO_MEANING</strong> = 'eggs'</dl>
    141 
    142 </td></tr></table></td></tr></table><p>
    143 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
    144 <tr bgcolor="#eeaa77">
    145 <td colspan=3 valign=bottom>&nbsp;<br>
    146 <font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
    147 \x20\x20\x20\x20
    148 <tr><td bgcolor="#eeaa77"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
    149 <td width="100%%"><dl><dt><a name="-doc_func"><strong>doc_func</strong></a>()</dt><dd><tt>This&nbsp;function&nbsp;solves&nbsp;all&nbsp;of&nbsp;the&nbsp;world's&nbsp;problems:<br>
    150 hunger<br>
    151 lack&nbsp;of&nbsp;Python<br>
    152 war</tt></dd></dl>
    153  <dl><dt><a name="-nodoc_func"><strong>nodoc_func</strong></a>()</dt></dl>
    154 </td></tr></table><p>
    155 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
    156 <tr bgcolor="#55aa55">
    157 <td colspan=3 valign=bottom>&nbsp;<br>
    158 <font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
    159 \x20\x20\x20\x20
    160 <tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
    161 <td width="100%%"><strong>__author__</strong> = 'Benjamin Peterson'<br>
    162 <strong>__credits__</strong> = 'Nobody'<br>
    163 <strong>__version__</strong> = '1.2.3.4'</td></tr></table><p>
    164 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
    165 <tr bgcolor="#7799ee">
    166 <td colspan=3 valign=bottom>&nbsp;<br>
    167 <font color="#ffffff" face="helvetica, arial"><big><strong>Author</strong></big></font></td></tr>
    168 \x20\x20\x20\x20
    169 <tr><td bgcolor="#7799ee"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
    170 <td width="100%%">Benjamin&nbsp;Peterson</td></tr></table><p>
    171 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
    172 <tr bgcolor="#7799ee">
    173 <td colspan=3 valign=bottom>&nbsp;<br>
    174 <font color="#ffffff" face="helvetica, arial"><big><strong>Credits</strong></big></font></td></tr>
    175 \x20\x20\x20\x20
    176 <tr><td bgcolor="#7799ee"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
    177 <td width="100%%">Nobody</td></tr></table>
    178 """.strip()
    179 
    180 expected_html_data_docstrings = tuple(s.replace(' ', '&nbsp;')
    181                                       for s in expected_data_docstrings)
    182 
    183 # output pattern for missing module
    184 missing_pattern = "no Python documentation found for '%s'"
    185 
    186 # output pattern for module with bad imports
    187 badimport_pattern = "problem in %s - <type 'exceptions.ImportError'>: No module named %s"
    188 
    189 def run_pydoc(module_name, *args, **env):
    190     """
    191     Runs pydoc on the specified module. Returns the stripped
    192     output of pydoc.
    193     """
    194     args = args + (module_name,)
    195     # do not write bytecode files to avoid caching errors
    196     rc, out, err = assert_python_ok('-B', pydoc.__file__, *args, **env)
    197     return out.strip()
    198 
    199 def get_pydoc_html(module):
    200     "Returns pydoc generated output as html"
    201     doc = pydoc.HTMLDoc()
    202     output = doc.docmodule(module)
    203     loc = doc.getdocloc(pydoc_mod) or ""
    204     if loc:
    205         loc = "<br><a href=\"" + loc + "\">Module Docs</a>"
    206     return output.strip(), loc
    207 
    208 def get_pydoc_text(module):
    209     "Returns pydoc generated output as text"
    210     doc = pydoc.TextDoc()
    211     loc = doc.getdocloc(pydoc_mod) or ""
    212     if loc:
    213         loc = "\nMODULE DOCS\n    " + loc + "\n"
    214 
    215     output = doc.docmodule(module)
    216 
    217     # cleanup the extra text formatting that pydoc preforms
    218     patt = re.compile('\b.')
    219     output = patt.sub('', output)
    220     return output.strip(), loc
    221 
    222 def print_diffs(text1, text2):
    223     "Prints unified diffs for two texts"
    224     lines1 = text1.splitlines(True)
    225     lines2 = text2.splitlines(True)
    226     diffs = difflib.unified_diff(lines1, lines2, n=0, fromfile='expected',
    227                                  tofile='got')
    228     print '\n' + ''.join(diffs)
    229 
    230 
    231 class PyDocDocTest(unittest.TestCase):
    232 
    233     @unittest.skipIf(sys.flags.optimize >= 2,
    234                      "Docstrings are omitted with -O2 and above")
    235     def test_html_doc(self):
    236         result, doc_loc = get_pydoc_html(pydoc_mod)
    237         mod_file = inspect.getabsfile(pydoc_mod)
    238         if sys.platform == 'win32':
    239             import nturl2path
    240             mod_url = nturl2path.pathname2url(mod_file)
    241         else:
    242             mod_url = mod_file
    243         expected_html = expected_html_pattern % (
    244                         (mod_url, mod_file, doc_loc) +
    245                         expected_html_data_docstrings)
    246         if result != expected_html:
    247             print_diffs(expected_html, result)
    248             self.fail("outputs are not equal, see diff above")
    249 
    250     @unittest.skipIf(sys.flags.optimize >= 2,
    251                      "Docstrings are omitted with -O2 and above")
    252     def test_text_doc(self):
    253         result, doc_loc = get_pydoc_text(pydoc_mod)
    254         expected_text = expected_text_pattern % (
    255                         (inspect.getabsfile(pydoc_mod), doc_loc) +
    256                         expected_text_data_docstrings)
    257         if result != expected_text:
    258             print_diffs(expected_text, result)
    259             self.fail("outputs are not equal, see diff above")
    260 
    261     def test_issue8225(self):
    262         # Test issue8225 to ensure no doc link appears for xml.etree
    263         result, doc_loc = get_pydoc_text(xml.etree)
    264         self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link")
    265 
    266     def test_non_str_name(self):
    267         # issue14638
    268         # Treat illegal (non-str) name like no name
    269         class A:
    270             __name__ = 42
    271         class B:
    272             pass
    273         adoc = pydoc.render_doc(A())
    274         bdoc = pydoc.render_doc(B())
    275         self.assertEqual(adoc.replace("A", "B"), bdoc)
    276 
    277     def test_not_here(self):
    278         missing_module = "test.i_am_not_here"
    279         result = run_pydoc(missing_module)
    280         expected = missing_pattern % missing_module
    281         self.assertEqual(expected, result,
    282             "documentation for missing module found")
    283 
    284     def test_input_strip(self):
    285         missing_module = " test.i_am_not_here "
    286         result = run_pydoc(missing_module)
    287         expected = missing_pattern % missing_module.strip()
    288         self.assertEqual(expected, result,
    289             "white space was not stripped from module name "
    290             "or other error output mismatch")
    291 
    292     def test_stripid(self):
    293         # test with strings, other implementations might have different repr()
    294         stripid = pydoc.stripid
    295         # strip the id
    296         self.assertEqual(stripid('<function stripid at 0x88dcee4>'),
    297                          '<function stripid>')
    298         self.assertEqual(stripid('<function stripid at 0x01F65390>'),
    299                          '<function stripid>')
    300         # nothing to strip, return the same text
    301         self.assertEqual(stripid('42'), '42')
    302         self.assertEqual(stripid("<type 'exceptions.Exception'>"),
    303                          "<type 'exceptions.Exception'>")
    304 
    305 
    306 class PydocImportTest(unittest.TestCase):
    307 
    308     def setUp(self):
    309         self.test_dir = os.mkdir(TESTFN)
    310         self.addCleanup(rmtree, TESTFN)
    311 
    312     def test_badimport(self):
    313         # This tests the fix for issue 5230, where if pydoc found the module
    314         # but the module had an internal import error pydoc would report no doc
    315         # found.
    316         modname = 'testmod_xyzzy'
    317         testpairs = (
    318             ('i_am_not_here', 'i_am_not_here'),
    319             ('test.i_am_not_here_either', 'i_am_not_here_either'),
    320             ('test.i_am_not_here.neither_am_i', 'i_am_not_here.neither_am_i'),
    321             ('i_am_not_here.{}'.format(modname),
    322              'i_am_not_here.{}'.format(modname)),
    323             ('test.{}'.format(modname), modname),
    324             )
    325 
    326         sourcefn = os.path.join(TESTFN, modname) + os.extsep + "py"
    327         for importstring, expectedinmsg in testpairs:
    328             with open(sourcefn, 'w') as f:
    329                 f.write("import {}\n".format(importstring))
    330             result = run_pydoc(modname, PYTHONPATH=TESTFN)
    331             expected = badimport_pattern % (modname, expectedinmsg)
    332             self.assertEqual(expected, result)
    333 
    334     def test_apropos_with_bad_package(self):
    335         # Issue 7425 - pydoc -k failed when bad package on path
    336         pkgdir = os.path.join(TESTFN, "syntaxerr")
    337         os.mkdir(pkgdir)
    338         badsyntax = os.path.join(pkgdir, "__init__") + os.extsep + "py"
    339         with open(badsyntax, 'w') as f:
    340             f.write("invalid python syntax = $1\n")
    341         result = run_pydoc('zqwykjv', '-k', PYTHONPATH=TESTFN)
    342         self.assertEqual('', result)
    343 
    344     def test_apropos_with_unreadable_dir(self):
    345         # Issue 7367 - pydoc -k failed when unreadable dir on path
    346         self.unreadable_dir = os.path.join(TESTFN, "unreadable")
    347         os.mkdir(self.unreadable_dir, 0)
    348         self.addCleanup(os.rmdir, self.unreadable_dir)
    349         # Note, on Windows the directory appears to be still
    350         #   readable so this is not really testing the issue there
    351         result = run_pydoc('zqwykjv', '-k', PYTHONPATH=TESTFN)
    352         self.assertEqual('', result)
    353 
    354 
    355 class TestDescriptions(unittest.TestCase):
    356 
    357     def test_module(self):
    358         # Check that pydocfodder module can be described
    359         from test import pydocfodder
    360         doc = pydoc.render_doc(pydocfodder)
    361         self.assertIn("pydocfodder", doc)
    362 
    363     def test_classic_class(self):
    364         class C: "Classic class"
    365         c = C()
    366         self.assertEqual(pydoc.describe(C), 'class C')
    367         self.assertEqual(pydoc.describe(c), 'instance of C')
    368         expected = 'instance of C in module %s' % __name__
    369         self.assertIn(expected, pydoc.render_doc(c))
    370 
    371     def test_class(self):
    372         class C(object): "New-style class"
    373         c = C()
    374 
    375         self.assertEqual(pydoc.describe(C), 'class C')
    376         self.assertEqual(pydoc.describe(c), 'C')
    377         expected = 'C in module %s object' % __name__
    378         self.assertIn(expected, pydoc.render_doc(c))
    379 
    380     def test_namedtuple_public_underscore(self):
    381         NT = namedtuple('NT', ['abc', 'def'], rename=True)
    382         with captured_stdout() as help_io:
    383             help(NT)
    384         helptext = help_io.getvalue()
    385         self.assertIn('_1', helptext)
    386         self.assertIn('_replace', helptext)
    387         self.assertIn('_asdict', helptext)
    388 
    389 
    390 class TestHelper(unittest.TestCase):
    391     def test_keywords(self):
    392         self.assertEqual(sorted(pydoc.Helper.keywords),
    393                          sorted(keyword.kwlist))
    394 
    395     def test_builtin(self):
    396         for name in ('str', 'str.translate', '__builtin__.str',
    397                      '__builtin__.str.translate'):
    398             # test low-level function
    399             self.assertIsNotNone(pydoc.locate(name))
    400             # test high-level function
    401             try:
    402                 pydoc.render_doc(name)
    403             except ImportError:
    404                 self.fail('finding the doc of {!r} failed'.format(o))
    405 
    406         for name in ('not__builtin__', 'strrr', 'strr.translate',
    407                      'str.trrrranslate', '__builtin__.strrr',
    408                      '__builtin__.str.trrranslate'):
    409             self.assertIsNone(pydoc.locate(name))
    410             self.assertRaises(ImportError, pydoc.render_doc, name)
    411 
    412 
    413 def test_main():
    414     try:
    415         test.test_support.run_unittest(PyDocDocTest,
    416                                        PydocImportTest,
    417                                        TestDescriptions,
    418                                        TestHelper)
    419     finally:
    420         reap_children()
    421 
    422 if __name__ == "__main__":
    423     test_main()
    424