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