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