PageRenderTime 61ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/nltk/test/doctest_driver.py

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