PageRenderTime 32ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/nltk/nltk/test/doctest_driver.py

http://nltk.googlecode.com/
Python | 1019 lines | 880 code | 56 blank | 83 comment | 94 complexity | fc78ab61002c84bb031b7ba21314e007 MD5 | raw file
Possible License(s): Apache-2.0, AGPL-1.0
  1. #!/usr/bin/env python
  2. # A Test Driver for Doctest
  3. # Author: Edward Loper <edloper@gradient.cis.upenn.edu>
  4. #
  5. # Provided as-is; use at your own risk; no warranty; no promises; enjoy!
  6. """
  7. A driver for testing interactive python examples in text files and
  8. docstrings. This doctest driver performs three functions:
  9. - checking: Runs the interactive examples, and reports any examples
  10. whose actual output does not match their expected output.
  11. - debugging: Runs the interactive examples, and enters the debugger
  12. whenever an example's actual output does not match its expected
  13. output.
  14. - updating: Runs the interactive examples, and replaces the expected
  15. output with the actual output whenever they don't match. This is
  16. used to update the output for new or out-of-date examples.
  17. A number of other flags can be given; call the driver with the
  18. `--help` option for a complete list.
  19. """
  20. import os, os.path, sys, unittest, pdb, bdb, re, tempfile, traceback
  21. import textwrap
  22. from doctest import *
  23. from doctest import DocTestCase, DocTestRunner
  24. from optparse import OptionParser, OptionGroup, Option
  25. from StringIO import StringIO
  26. import coverage
  27. # Use local NLTK.
  28. root_dir = os.path.abspath(os.path.join(sys.path[0], '..', '..'))
  29. sys.path.insert(0, root_dir)
  30. __version__ = '0.1'
  31. # Compiler flags: define '/' to do float division, even for ints.
  32. import __future__
  33. COMPILER_FLAGS = __future__.division.compiler_flag
  34. ###########################################################################
  35. # Monkey-Patch to fix Doctest
  36. ###########################################################################
  37. # The original version of this class has a bug that interferes with
  38. # code coverage functions. See: http://tinyurl.com/2htvfx
  39. class _OutputRedirectingPdb(pdb.Pdb):
  40. def __init__(self, out):
  41. self.__out = out
  42. self.__debugger_used = False
  43. pdb.Pdb.__init__(self)
  44. def set_trace(self):
  45. self.__debugger_used = True
  46. pdb.Pdb.set_trace(self)
  47. def set_continue(self):
  48. # Calling set_continue unconditionally would break unit test coverage
  49. # reporting, as Bdb.set_continue calls sys.settrace(None).
  50. if self.__debugger_used:
  51. pdb.Pdb.set_continue(self)
  52. def trace_dispatch(self, *args):
  53. save_stdout = sys.stdout
  54. sys.stdout = self.__out
  55. pdb.Pdb.trace_dispatch(self, *args)
  56. sys.stdout = save_stdout
  57. # Do the actual monkey-patching.
  58. import doctest
  59. doctest._OutputRedirectingPdb = _OutputRedirectingPdb
  60. ###########################################################################
  61. # Utility Functions
  62. ###########################################################################
  63. # These are copied from doctest; I don't import them because they're
  64. # private. See the versions in doctest for docstrings & comments.
  65. def _exception_traceback(exc_info):
  66. excout = StringIO()
  67. exc_type, exc_val, exc_tb = exc_info
  68. traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
  69. return excout.getvalue()
  70. class _SpoofOut(StringIO):
  71. def getvalue(self):
  72. result = StringIO.getvalue(self)
  73. if result and not result.endswith("\n"):
  74. result += "\n"
  75. if hasattr(self, "softspace"):
  76. del self.softspace
  77. return result
  78. def truncate(self, size=None):
  79. StringIO.truncate(self, size)
  80. if hasattr(self, "softspace"):
  81. del self.softspace
  82. ###########################################################################
  83. # MyParser
  84. ###########################################################################
  85. class MyDocTestParser(DocTestParser):
  86. PYLISTING_RE = re.compile(r'''
  87. (^\.\.[ ]*pylisting::.*\n # directive
  88. (?:[ ]*\n| # blank line or
  89. [ ]+.*\n)*) # indented line
  90. ''', re.VERBOSE+re.MULTILINE)
  91. # [xx] not used: split-pysrc_into_statements is used instead!
  92. PYLISTING_EX = re.compile(r'''
  93. (?:^[^ ].*\n # non-blank line
  94. (?:[ ]*\n | # blank line or
  95. [ ]+.*\n)*) # indented line
  96. ''', re.VERBOSE+re.MULTILINE)
  97. DOCTEST_OPTION_RE = re.compile(r'''
  98. ^[ ]*:\w+:.*\n # :option:
  99. (.*\S.*\n)* # non-blank lines
  100. ''', re.VERBOSE+re.MULTILINE)
  101. def parse(self, string, name='<string>'):
  102. output = []
  103. lineno_offset = 0
  104. for piecenum, piece in enumerate(self.PYLISTING_RE.split(string)):
  105. for example in DocTestParser.parse(self, piece, name):
  106. if isinstance(example, Example):
  107. example.lineno += lineno_offset
  108. output.append(example)
  109. # If we're inside a pylisting, then convert any
  110. # subpieces that are not marked by python prompts into
  111. # examples with an expected output of ''.
  112. elif piecenum%2 == 1 and example.strip():
  113. output.append(example[:example.find('\n')])
  114. # order matters here:
  115. pysrc = example[example.find('\n'):]
  116. pysrc = self.DOCTEST_OPTION_RE.sub('', pysrc)
  117. pysrc = textwrap.dedent(pysrc)
  118. #for ex in self.PYLISTING_EX.findall(pysrc):
  119. for ex in split_pysrc_into_statements(pysrc):
  120. source = ex.strip()
  121. if not source: continue
  122. want = ''
  123. exc_msg = None
  124. indent = 4 # close enough.
  125. lineno = lineno_offset # Not quite right!
  126. options = self._find_options(source, name, lineno)
  127. output.append(Example(source, want, exc_msg,
  128. lineno, indent, options))
  129. else:
  130. output.append(example)
  131. lineno_offset += piece.count('\n')
  132. # For debugging:
  133. #for ex in output:
  134. # if isinstance(ex, Example):
  135. # print '-'*70
  136. # print ex.source
  137. #output = []
  138. return output
  139. def get_examples(self, string, name='<string>'):
  140. examples = []
  141. ignore = False
  142. for x in self.parse(string, name):
  143. if isinstance(x, Example):
  144. if not ignore:
  145. examples.append(x)
  146. else:
  147. #print '.. doctest-ignore:: %s' % x.source.strip()[:50]
  148. pass
  149. else:
  150. if re.search(r'\.\.\s*doctest-ignore::?\s*$', x):
  151. ignore = True
  152. elif x.strip():
  153. ignore = False
  154. return examples
  155. ###########################################################################
  156. # Update Runner
  157. ###########################################################################
  158. class UpdateRunner(DocTestRunner):
  159. """
  160. A subclass of `DocTestRunner` that checks the output of each
  161. example, and replaces the expected output with the actual output
  162. for any examples that fail.
  163. `UpdateRunner` can be used:
  164. - To automatically fill in the expected output for new examples.
  165. - To correct examples whose output has become out-of-date.
  166. However, care must be taken not to update an example's expected
  167. output with an incorrect value.
  168. """
  169. def __init__(self, verbose=False, mark_updates=False):
  170. '''Construct a new update runner'''
  171. self._mark_updates = mark_updates
  172. DocTestRunner.__init__(self, verbose=verbose)
  173. def run(self, test, compileflags=None, out=None, clear_globs=True):
  174. '''Run the update runner'''
  175. self._new_want = {}
  176. (f,t) = DocTestRunner.run(self, test, compileflags, out, clear_globs)
  177. # Update the test's docstring, and the lineno's of the
  178. # examples, by breaking it into lines and replacing the old
  179. # expected outputs with the new expected outputs.
  180. old_lines = test.docstring.split('\n')
  181. new_lines = []
  182. lineno = 0
  183. offset = 0
  184. for example in test.examples:
  185. # Copy the lines up through the start of the example's
  186. # output from old_lines to new_lines.
  187. got_start = example.lineno + example.source.count('\n')
  188. new_lines += old_lines[lineno:got_start]
  189. lineno = got_start
  190. # Do a sanity check to make sure we're at the right lineno
  191. # (In particular, check that the example's expected output
  192. # appears in old_lines where we expect it to appear.)
  193. if example.want:
  194. assert (example.want.split('\n')[0] ==
  195. old_lines[lineno][example.indent:]), \
  196. 'Line number mismatch at %d' % lineno
  197. # Skip over the old expected output.
  198. old_len = example.want.count('\n')
  199. lineno += old_len
  200. # Mark any changes we make.
  201. if self._mark_updates and example in self._new_want:
  202. new_lines.append(' '*example.indent + '... ' +
  203. '# [!!] OUTPUT AUTOMATICALLY UPDATED [!!]')
  204. # Add the new expected output.
  205. new_want = self._new_want.get(example, example.want)
  206. if new_want:
  207. new_want = '\n'.join([' '*example.indent+l
  208. for l in new_want[:-1].split('\n')])
  209. new_lines.append(new_want)
  210. # Update the example's want & lieno fields
  211. example.want = new_want
  212. example.lineno += offset
  213. offset += example.want.count('\n') - old_len
  214. # Add any remaining lines
  215. new_lines += old_lines[lineno:]
  216. # Update the test's docstring.
  217. test.docstring = '\n'.join(new_lines)
  218. # Return failures & tries
  219. return (f,t)
  220. def report_start(self, out, test, example):
  221. pass
  222. def report_success(self, out, test, example, got):
  223. pass
  224. def report_unexpected_exception(self, out, test, example, exc_info):
  225. replacement = _exception_traceback(exc_info)
  226. self._new_want[example] = replacement
  227. if self._verbose:
  228. self._report_replacement(out, test, example, replacement)
  229. def report_failure(self, out, test, example, got):
  230. self._new_want[example] = got
  231. if self._verbose:
  232. self._report_replacement(out, test, example, got)
  233. def _report_replacement(self, out, test, example, replacement):
  234. want = '\n'.join([' '+l for l in example.want.split('\n')[:-1]])
  235. repl = '\n'.join([' '+l for l in replacement.split('\n')[:-1]])
  236. if want and repl:
  237. diff = 'Replacing:\n%s\nWith:\n%s\n' % (want, repl)
  238. elif want:
  239. diff = 'Removing:\n%s\n' % want
  240. elif repl:
  241. diff = 'Adding:\n%s\n' % repl
  242. out(self._header(test, example) + diff)
  243. DIVIDER = '-'*70
  244. def _header(self, test, example):
  245. if test.filename is None:
  246. tag = ("On line #%s of %s" %
  247. (example.lineno+1, test.name))
  248. elif test.lineno is None:
  249. tag = ("On line #%s of %s in %s" %
  250. (example.lineno+1, test.name, test.filename))
  251. else:
  252. lineno = test.lineno+example.lineno+1
  253. tag = ("On line #%s of %s (%s)" %
  254. (lineno, test.filename, test.name))
  255. source_lines = example.source.rstrip().split('\n')
  256. return (self.DIVIDER + '\n' + tag + '\n' +
  257. ' >>> %s\n' % source_lines[0] +
  258. ''.join([' ... %s\n' % l for l in source_lines[1:]]))
  259. ###########################################################################
  260. # Debugger
  261. ###########################################################################
  262. def _indent(s, indent=4):
  263. return re.sub('(?m)^(?!$)', indent*' ', s)
  264. import keyword, token, tokenize
  265. class Debugger:
  266. # Just using this for reporting:
  267. runner = DocTestRunner()
  268. def __init__(self, checker=None, set_trace=None):
  269. if checker is None:
  270. checker = OutputChecker()
  271. self.checker = checker
  272. if set_trace is None:
  273. set_trace = pdb.Pdb().set_trace
  274. self.set_trace = set_trace
  275. def _check_output(self, example):
  276. want = example.want
  277. optionflags = self._get_optionflags(example)
  278. got = sys.stdout.getvalue()
  279. sys.stdout.truncate(0)
  280. if not self.checker.check_output(want, got, optionflags):
  281. self.runner.report_failure(self.save_stdout.write,
  282. self.test, example, got)
  283. return False
  284. else:
  285. return True
  286. def _check_exception(self, example):
  287. want_exc_msg = example.exc_msg
  288. optionflags = self._get_optionflags(example)
  289. exc_info = sys.exc_info()
  290. got_exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
  291. if not self.checker.check_output(want_exc_msg, got_exc_msg,
  292. optionflags):
  293. got = _exception_traceback(exc_info)
  294. self.runner.report_failure(self.save_stdout.write,
  295. self.test, example, got)
  296. return False
  297. else:
  298. return True
  299. def _print_if_not_none(self, *args):
  300. if args == (None,):
  301. pass
  302. elif len(args) == 1:
  303. print `args[0]`
  304. else:
  305. print `args` # not quite right: >>> 1,
  306. def _comment_line(self, line):
  307. "Return a commented form of the given line"
  308. line = line.rstrip()
  309. if line:
  310. return '# '+line
  311. else:
  312. return '#'
  313. def _script_from_examples(self, s):
  314. output = []
  315. examplenum = 0
  316. for piece in MyDocTestParser().parse(s):
  317. if isinstance(piece, Example):
  318. self._script_from_example(piece, examplenum, output)
  319. examplenum += 1
  320. else:
  321. # Add non-example text.
  322. output += [self._comment_line(l)
  323. for l in piece.split('\n')[:-1]]
  324. # Combine the output, and return it.
  325. return '\n'.join(output)
  326. _CHK_OUT = 'if not CHECK_OUTPUT(__examples__[%d]): __set_trace__()'
  327. _CHK_EXC = 'if not CHECK_EXCEPTION(__examples__[%d]): __set_trace__()'
  328. def _script_from_example(self, example, i, output):
  329. source = self._simulate_compile_singlemode(example.source)[:-1]
  330. if example.exc_msg is None:
  331. output.append(source)
  332. output.append(self._CHK_OUT % i)
  333. else:
  334. output.append('try:')
  335. output.append(_indent(source))
  336. output.append(' '+self._CHK_OUT % i)
  337. output.append('except:')
  338. output.append(' '+self._CHK_EXC % i)
  339. def _simulate_compile_singlemode(self, s):
  340. # Calculate line offsets
  341. lines = [0, 0]
  342. pos = 0
  343. while 1:
  344. pos = s.find('\n', pos)+1
  345. if not pos: break
  346. lines.append(pos)
  347. lines.append(len(s))
  348. oldpos = 0
  349. parenlevel = 0
  350. deflevel = 0
  351. output = []
  352. stmt = []
  353. text = StringIO(s)
  354. tok_gen = tokenize.generate_tokens(text.readline)
  355. for toktype, tok, (srow,scol), (erow,ecol), line in tok_gen:
  356. newpos = lines[srow] + scol
  357. stmt.append(s[oldpos:newpos])
  358. if tok != '':
  359. stmt.append(tok)
  360. oldpos = newpos + len(tok)
  361. # Update the paren level.
  362. if tok in '([{':
  363. parenlevel += 1
  364. if tok in '}])':
  365. parenlevel -= 1
  366. if tok in ('def', 'class') and deflevel == 0:
  367. deflevel = 1
  368. if deflevel and toktype == token.INDENT:
  369. deflevel += 1
  370. if deflevel and toktype == token.DEDENT:
  371. deflevel -= 1
  372. # Are we starting a statement?
  373. if ((toktype in (token.NEWLINE, tokenize.NL, tokenize.COMMENT,
  374. token.INDENT, token.ENDMARKER) or
  375. tok==':') and parenlevel == 0):
  376. if deflevel == 0 and self._is_expr(stmt[1:-2]):
  377. output += stmt[0]
  378. output.append('__print__((')
  379. output += stmt[1:-2]
  380. output.append('))')
  381. output += stmt[-2:]
  382. else:
  383. output += stmt
  384. stmt = []
  385. return ''.join(output)
  386. def _is_expr(self, stmt):
  387. stmt = [t for t in stmt if t]
  388. if not stmt:
  389. return False
  390. # An assignment signifies a non-exception, *unless* it
  391. # appears inside of parens (eg, ``f(x=1)``.)
  392. parenlevel = 0
  393. for tok in stmt:
  394. if tok in '([{': parenlevel += 1
  395. if tok in '}])': parenlevel -= 1
  396. if (parenlevel == 0 and
  397. tok in ('=', '+=', '-=', '*=', '/=', '%=', '&=', '+=',
  398. '^=', '<<=', '>>=', '**=', '//=')):
  399. return False
  400. # Any keywords *except* "not", "or", "and", "lambda", "in", "is"
  401. # signifies a non-expression.
  402. if stmt[0] in ("assert", "break", "class", "continue", "def",
  403. "del", "elif", "else", "except", "exec",
  404. "finally", "for", "from", "global", "if",
  405. "import", "pass", "print", "raise", "return",
  406. "try", "while", "yield"):
  407. return False
  408. return True
  409. def _get_optionflags(self, example):
  410. optionflags = 0
  411. for (flag, val) in example.options.items():
  412. if val:
  413. optionflags |= flag
  414. else:
  415. optionflags &= ~flag
  416. return optionflags
  417. def debug(self, test, pm=False):
  418. self.test = test
  419. # Save the old stdout
  420. self.save_stdout = sys.stdout
  421. # Convert the source docstring to a script.
  422. script = self._script_from_examples(test.docstring)
  423. # Create a debugger.
  424. debugger = _OutputRedirectingPdb(sys.stdout)
  425. # Patch pdb.set_trace to restore sys.stdout during interactive
  426. # debugging (so it's not still redirected to self._fakeout).
  427. save_set_trace = pdb.set_trace
  428. pdb.set_trace = debugger.set_trace
  429. # Write the script to a temporary file. Note that
  430. # tempfile.NameTemporaryFile() cannot be used. As the docs
  431. # say, a file so created cannot be opened by name a second
  432. # time on modern Windows boxes, and execfile() needs to open
  433. # it.
  434. srcfilename = tempfile.mktemp(".py", "doctestdebug_")
  435. f = open(srcfilename, 'w')
  436. f.write(script)
  437. f.close()
  438. # Set up the globals
  439. test.globs['CHECK_OUTPUT'] = self._check_output
  440. test.globs['CHECK_EXCEPTION'] = self._check_exception
  441. test.globs['__print__'] = self._print_if_not_none
  442. test.globs['__set_trace__'] = debugger.set_trace
  443. test.globs['__examples__'] = self.test.examples
  444. try:
  445. if pm is False:
  446. debugger.run("execfile(%r)" % srcfilename,
  447. test.globs, test.globs)
  448. else:
  449. try:
  450. sys.stdout = _SpoofOut()
  451. try:
  452. execfile(srcfilename, test.globs)
  453. except bdb.BdbQuit:
  454. return
  455. except:
  456. sys.stdout = self.save_stdout
  457. exc_info = sys.exc_info()
  458. exc_msg = traceback.format_exception_only(
  459. exc_info[0], exc_info[1])[-1]
  460. self.save_stdout.write(self.runner.DIVIDER+'\n')
  461. self.save_stdout.write('Unexpected exception:\n' +
  462. _indent(exc_msg))
  463. raise
  464. #self.post_mortem(debugger, exc_info[2])
  465. finally:
  466. sys.stdout = self.save_stdout
  467. finally:
  468. sys.set_trace = save_set_trace
  469. os.remove(srcfilename)
  470. def post_mortem(self, debugger, t):
  471. debugger.reset()
  472. while t.tb_next is not None:
  473. t = t.tb_next
  474. debugger.interaction(t.tb_frame, t)
  475. ###########################################################################
  476. # Helper functions
  477. ###########################################################################
  478. # Name can be:
  479. # - The filename of a text file
  480. # - The filename of a python file
  481. # - The dotted name of a python module
  482. # Return a list of test!
  483. def find(name):
  484. # Check for test names
  485. if ':' in name:
  486. (name, testname) = name.split(':')
  487. else:
  488. testname = None
  489. if os.path.exists(name):
  490. filename = os.path.normpath(os.path.abspath(name))
  491. ext = os.path.splitext(filename)[-1]
  492. if (ext[-3:] != '.py' and ext[-4:-1] != '.py'):
  493. # It's a text file; return the filename.
  494. if testname is not None:
  495. raise ValueError("test names can't be specified "
  496. "for text files")
  497. s = open(filename).read().decode('utf8')
  498. test = MyDocTestParser().get_doctest(s, {}, name, filename, 0)
  499. return [test]
  500. else:
  501. # It's a python file; import it. Make sure to set the
  502. # path correctly.
  503. basedir, modname = find_module_from_filename(filename)
  504. orig_path = sys.path[:]
  505. try:
  506. sys.path.insert(0, basedir)
  507. module = import_from_name(modname)
  508. finally:
  509. sys.path[:] = orig_path
  510. else:
  511. module = import_from_name(name)
  512. # Find tests.
  513. tests = DocTestFinder().find(module)
  514. if testname is not None:
  515. testname = '%s.%s' % (module.__name__, testname)
  516. tests = [t for t in tests if t.name.startswith(testname)]
  517. if len(tests) == 0:
  518. raise ValueError("test not found")
  519. return tests
  520. def import_from_name(name):
  521. try:
  522. return __import__(name, globals(), locals(), ['*'])
  523. except Exception, e:
  524. raise ValueError, str(e)
  525. except:
  526. raise ValueError, 'Error importing %r' % name
  527. def find_module_from_filename(filename):
  528. """
  529. Given a filename, return a tuple `(basedir, module)`, where
  530. `module` is the module's name, and `basedir` is the directory it
  531. should be loaded from (this directory should be added to the
  532. path to import it). Packages are handled correctly.
  533. """
  534. (basedir, file) = os.path.split(filename)
  535. (module_name, ext) = os.path.splitext(file)
  536. # If it's a package, then import with the directory name (don't
  537. # use __init__ as the module name).
  538. if module_name == '__init__':
  539. (basedir, module_name) = os.path.split(basedir)
  540. # If it's contained inside a package, then find the base dir.
  541. if (os.path.exists(os.path.join(basedir, '__init__.py')) or
  542. os.path.exists(os.path.join(basedir, '__init__.pyc')) or
  543. os.path.exists(os.path.join(basedir, '__init__.pyw'))):
  544. package = []
  545. while os.path.exists(os.path.join(basedir, '__init__.py')):
  546. (basedir,dir) = os.path.split(basedir)
  547. if dir == '': break
  548. package.append(dir)
  549. package.reverse()
  550. module_name = '.'.join(package+[module_name])
  551. return (basedir, module_name)
  552. def split_pysrc_into_statements(s):
  553. parens = 0 # Number of parens deep we're nested?
  554. quote = None # What type of string are we in (if any)?
  555. statements = [] # List of statements we've found
  556. continuation = False # Did last line end with a backslash?
  557. for line in s.lstrip().split('\n'):
  558. # Check indentation level.
  559. indent = re.match(r'\s*', line).end()
  560. # [DEBUG PRINTF]
  561. #print '%4d %6r %6s %5s %r' % (parens, quote, continuation,
  562. # indent, line[:40])
  563. # Add the line as a new statement or a continuation.
  564. if (parens == 0 and quote is None and indent == 0 and
  565. (not continuation)):
  566. if line.strip():
  567. statements.append(line)
  568. else:
  569. statements[-1] += '\n'+line
  570. # Scan the line, checking for quotes, parens, and comment
  571. # markers (so we can decide when a line is a continuation).
  572. line_has_comment = False
  573. for c in re.findall(r'\\.|"""|\'\'\'|"|\'|\(|\)|\[|\]|\{|\}|\#', line):
  574. if quote:
  575. if c == quote:
  576. quote = None
  577. elif c in '([{':
  578. parens += 1
  579. elif c in ')]}':
  580. parens -= 1
  581. elif c == '#':
  582. line_has_comment = True
  583. break
  584. elif c[0] != '\\':
  585. quote = c
  586. if not line_has_comment:
  587. continuation = line.strip().endswith('\\')
  588. return statements
  589. ###########################################################################
  590. # Custom Checker, to ignore [# _foo] callouts in output.
  591. ###########################################################################
  592. class MyOutputChecker(OutputChecker):
  593. CALLOUT_RE = re.compile(r' *#[ ]+\[_([\w-]+)\][ ]*$', re.MULTILINE)
  594. def check_output(self, want, got, optionflags):
  595. if OutputChecker.check_output(self, want, got, optionflags):
  596. return True
  597. else:
  598. want = self.CALLOUT_RE.sub('', want)
  599. return OutputChecker.check_output(self, want, got, optionflags)
  600. ###########################################################################
  601. # Basic Actions
  602. ###########################################################################
  603. # Finer control over output verbosity:
  604. from epydoc.cli import TerminalController
  605. class MyDocTestRunner(DocTestRunner):
  606. def __init__(self, checker=None, verbosity=1, optionflags=0,
  607. kbinterrupt_continue=False):
  608. DocTestRunner.__init__(self, checker, (verbosity>2), optionflags)
  609. self._verbosity = verbosity
  610. self._current_test = None
  611. self._term = TerminalController()
  612. self._stderr_term = TerminalController(sys.__stderr__)
  613. self._kbinterrupt_continue = kbinterrupt_continue
  614. def report_start(self, out, test, example):
  615. if 1 <= self._verbosity <= 2:
  616. src = example.source.split('\n')[0]
  617. if len(src) > 60: src = src[:57]+'...'
  618. if isinstance(src, unicode): src = src.encode('utf8')
  619. lineno = test.lineno + example.lineno + 1
  620. if self._verbosity == 1:
  621. if self._stderr_term.CLEAR_LINE:
  622. sys.__stderr__.write(self._stderr_term.CLEAR_LINE)
  623. else:
  624. sys.__stderr__.write('\n')
  625. sys.__stderr__.write('%s [Line %s] %s%s' %
  626. (self._stderr_term.BOLD, lineno,
  627. self._stderr_term.NORMAL, src))
  628. if self._verbosity == 2:
  629. sys.__stderr__.write('\n')
  630. else:
  631. DocTestRunner.report_start(self, out, test, example)
  632. sys.__stdout__.flush()
  633. self._current_test = (test, example)
  634. # Total hack warning: This munges the original source to
  635. # catch any keyboard interrupts, and turn them into special
  636. # ValueError interrupts.
  637. example.original_source = example.source
  638. if self._kbinterrupt_continue:
  639. example.source = ('try:\n%sexcept KeyboardInterrupt:\n '
  640. 'raise ValueError("KEYBOARD-INTERRUPT")\n' %
  641. doctest._indent(example.source))
  642. def report_failure(self, out, test, example, got):
  643. example.source = example.original_source
  644. if self._verbosity == 1:
  645. out('\n')
  646. out(self._failure_header(test, example) + self._term.RED+
  647. self._checker.output_difference(example, got, self.optionflags)+
  648. self._term.NORMAL)
  649. def report_unexpected_exception(self, out, test, example, exc_info):
  650. example.source = example.original_source
  651. if self._verbosity == 1:
  652. out('\n')
  653. out(self._failure_header(test, example) + self._term.RED)
  654. if (isinstance(exc_info[1], ValueError) and
  655. exc_info[1].args[0] == 'KEYBOARD-INTERRUPT'):
  656. out(self._term.RED+self._term.BOLD)
  657. out('Keyboard interrupt; Continuing!\n\n' + self._term.NORMAL)
  658. else:
  659. out('Exception raised:\n' + self._term.NORMAL +
  660. _indent(_exception_traceback(exc_info)))
  661. def _failure_header(self, test, example):
  662. out = (self._term.CYAN+self._term.BOLD+'*'*75+self._term.NORMAL+'\n')
  663. out += (self._term.GREEN)
  664. if test.filename:
  665. if test.lineno is not None and example.lineno is not None:
  666. lineno = test.lineno + example.lineno + 1
  667. else:
  668. lineno = '?'
  669. out += ('File "%s", line %s, in %s\n' %
  670. (test.filename, lineno, test.name))
  671. else:
  672. out += ('Line %s, in %s\n' % (example.lineno+1, test.name))
  673. out += (self._term.RED)
  674. out += ('Failed example:\n')
  675. source = example.source
  676. out += (_indent(source))
  677. if isinstance(out, unicode): out = out.encode('utf8')
  678. return out
  679. def run(self, test, compileflags=None, out=None, clear_globs=True):
  680. save_stderr = sys.stderr
  681. sys.stderr = _SpoofOut()
  682. if self._verbosity > 0:
  683. print >>save_stderr, (
  684. self._stderr_term.CYAN+self._stderr_term.BOLD+
  685. 'Testing %s...'%test.name+self._stderr_term.NORMAL)
  686. try:
  687. fails, tries = DocTestRunner.run(self, test, compileflags,
  688. out, clear_globs)
  689. except KeyboardInterrupt:
  690. if self._current_test is None: raise
  691. print >>save_stderr, self._failure_header(*self._current_test)
  692. print >>save_stderr, (
  693. self._stderr_term.RED+self._stderr_term.BOLD+
  694. 'Keyboard Interrupt!'+self._stderr_term.NORMAL)
  695. if self._verbosity == 1:
  696. save_stderr.write(self._stderr_term.CLEAR_LINE)
  697. if self._verbosity > 0:
  698. if fails:
  699. print >>save_stderr, (
  700. self._stderr_term.RED+self._stderr_term.BOLD+
  701. ' %d example(s) failed!'%fails+self._stderr_term.NORMAL)
  702. else:
  703. print >>save_stderr, (
  704. self._stderr_term.GREEN+self._stderr_term.BOLD+
  705. ' All examples passed'+self._stderr_term.NORMAL)
  706. print >>save_stderr
  707. sys.stderr = save_stderr
  708. def run(names, optionflags, verbosity, kbinterrupt_continue):
  709. checker = MyOutputChecker()
  710. runner = MyDocTestRunner(checker=checker, verbosity=verbosity,
  711. optionflags=optionflags,
  712. kbinterrupt_continue=kbinterrupt_continue)
  713. for name in names:
  714. try: tests = find(name)
  715. except ValueError, e:
  716. print >>sys.stderr, ('%s: Error processing %s -- %s' %
  717. (sys.argv[0], name, e))
  718. continue
  719. for test in tests:
  720. runner.run(test, COMPILER_FLAGS)
  721. if verbosity == 1:
  722. sys.stdout.write('.')
  723. sys.stdout.flush(); sys.stderr.flush()
  724. return runner
  725. # temporary hack:
  726. # for name in names:
  727. # testfile(name, optionflags=optionflags, verbose=True,
  728. # module_relative=False)
  729. # return
  730. suite = unittest.TestSuite()
  731. for name in names:
  732. try:
  733. for test in find(name):
  734. suite.addTest(DocTestCase(test, optionflags))
  735. except ValueError, e:
  736. print >>sys.stderr, ('%s: Error processing %s -- %s' %
  737. (sys.argv[0], name, e))
  738. unittest.TextTestRunner(verbosity=verbosity).run(suite)
  739. def debug(names, optionflags, verbosity, pm=True):
  740. debugger = Debugger()
  741. for name in names:
  742. try:
  743. for test in find(name):
  744. debugger.debug(test, pm)
  745. except ValueError, e:
  746. raise
  747. print >>sys.stderr, ('%s: Error processing %s -- %s' %
  748. (sys.argv[0], name, e))
  749. def update(names, optionflags, verbosity):
  750. runner = UpdateRunner(verbose=True)
  751. for name in names:
  752. try:
  753. # Make sure we're running on a text file.
  754. tests = find(name)
  755. if len(tests) != 1 or tests[0].lineno != 0:
  756. raise ValueError('update can only be used with text files')
  757. test = tests[0]
  758. # Run the updater!
  759. (failures, tries) = runner.run(test)
  760. # Confirm the changes.
  761. if failures == 0:
  762. print 'No updates needed!'
  763. else:
  764. print '*'*70
  765. print '%d examples updated.' % failures
  766. print '-'*70
  767. sys.stdout.write('Accept updates? [y/N] ')
  768. sys.stdout.flush()
  769. if sys.stdin.readline().lower().strip() in ('y', 'yes'):
  770. # Make a backup of the original contents.
  771. backup = test.filename+'.bak'
  772. print 'Renaming %s -> %s' % (name, backup)
  773. os.rename(test.filename, backup)
  774. # Write the new contents.
  775. print 'Writing updated version to %s' % test.filename
  776. out = open(test.filename, 'w')
  777. out.write(test.docstring)
  778. out.close()
  779. else:
  780. print 'Updates rejected!'
  781. except ValueError, e:
  782. raise
  783. print >>sys.stderr, ('%s: Error processing %s -- %s' %
  784. (sys.argv[0], name, e))
  785. ###########################################################################
  786. # Main script
  787. ###########################################################################
  788. # Action options
  789. CHECK_OPT = Option("--check",
  790. action="store_const", dest="action", const="check",
  791. default="check",
  792. help="Verify the output of the doctest examples in the "
  793. "given files.")
  794. UPDATE_OPT = Option("--update", "-u",
  795. action="store_const", dest="action", const="update",
  796. help="Update the expected output for new or out-of-date "
  797. "doctest examples in the given files. In "
  798. "particular, find every example whose actual output "
  799. "does not match its expected output; and replace its "
  800. "expected output with its actual output. You will "
  801. "be asked to verify the changes before they are "
  802. "written back to the file; be sure to check them over "
  803. "carefully, to ensure that you don't accidentally "
  804. "create broken test cases.")
  805. DEBUG_OPT = Option("--debug",
  806. action="store_const", dest="action", const="debug",
  807. help="Verify the output of the doctest examples in the "
  808. "given files. If any example fails, then enter the "
  809. "python debugger.")
  810. # Reporting options
  811. VERBOSE_OPT = Option("-v", "--verbose",
  812. action="count", dest="verbosity", default=1,
  813. help="Increase verbosity.")
  814. QUIET_OPT = Option("-q", "--quiet",
  815. action="store_const", dest="verbosity", const=0,
  816. help="Decrease verbosity.")
  817. UDIFF_OPT = Option("--udiff", '-d',
  818. action="store_const", dest="udiff", const=1, default=0,
  819. help="Display test failures using unified diffs.")
  820. CDIFF_OPT = Option("--cdiff",
  821. action="store_const", dest="cdiff", const=1, default=0,
  822. help="Display test failures using context diffs.")
  823. NDIFF_OPT = Option("--ndiff",
  824. action="store_const", dest="ndiff", const=1, default=0,
  825. help="Display test failures using ndiffs.")
  826. COVERAGE_OPT = Option("--coverage",
  827. action="store", dest="coverage", metavar='FILENAME',
  828. help="Generate coverage information, and write it to the "
  829. "given file.")
  830. CONTINUE_OPT = Option("--continue", dest='kbinterrupt_continue',
  831. action='store_const', const=1, default=0,
  832. help="If a test is interrupted by a keyboard "
  833. "interrupt, then report the interrupt and continue")
  834. # Output Comparison options
  835. ELLIPSIS_OPT = Option("--ellipsis",
  836. action="store_const", dest="ellipsis", const=1, default=0,
  837. help="Allow \"...\" to be used for ellipsis in the "
  838. "expected output.")
  839. NORMWS_OPT = Option("--normalize_whitespace",
  840. action="store_const", dest="normws", const=1, default=0,
  841. help="Ignore whitespace differences between "
  842. "the expected output and the actual output.")
  843. def main():
  844. # Create the option parser.
  845. optparser = OptionParser(usage='%prog [options] NAME ...',
  846. version="Edloper's Doctest Driver, "
  847. "version %s" % __version__)
  848. action_group = OptionGroup(optparser, 'Actions (default=check)')
  849. action_group.add_options([CHECK_OPT, UPDATE_OPT, DEBUG_OPT])
  850. optparser.add_option_group(action_group)
  851. reporting_group = OptionGroup(optparser, 'Reporting')
  852. reporting_group.add_options([VERBOSE_OPT, QUIET_OPT,
  853. UDIFF_OPT, CDIFF_OPT, NDIFF_OPT,
  854. COVERAGE_OPT, CONTINUE_OPT])
  855. optparser.add_option_group(reporting_group)
  856. compare_group = OptionGroup(optparser, 'Output Comparison')
  857. compare_group.add_options([ELLIPSIS_OPT, NORMWS_OPT])
  858. optparser.add_option_group(compare_group)
  859. # Extract optionflags and the list of file names.
  860. optionvals, names = optparser.parse_args()
  861. if len(names) == 0:
  862. optparser.error("No files specified")
  863. optionflags = (optionvals.udiff * REPORT_UDIFF |
  864. optionvals.cdiff * REPORT_CDIFF |
  865. optionvals.ellipsis * ELLIPSIS |
  866. optionvals.normws * NORMALIZE_WHITESPACE)
  867. # Check coverage, if requested
  868. if optionvals.coverage:
  869. coverage.use_cache(True, cache_file=optionvals.coverage)
  870. coverage.start()
  871. # Perform the requested action.
  872. if optionvals.action == 'check':
  873. run(names, optionflags, optionvals.verbosity,
  874. optionvals.kbinterrupt_continue)
  875. elif optionvals.action == 'update':
  876. update(names, optionflags, optionvals.verbosity)
  877. elif optionvals.action == 'debug':
  878. debug(names, optionflags, optionvals.verbosity)
  879. else:
  880. optparser.error('INTERNAL ERROR: Bad action %s' % optionvals.action)
  881. # Check coverage, if requested
  882. if optionvals.coverage:
  883. coverage.stop()
  884. if __name__ == '__main__': main()