1#!/usr/bin/env python 2""" 3refguide_check.py [OPTIONS] [-- ARGS] 4 5Check for a pyEDFlib submodule whether the objects in its __all__ dict 6correspond to the objects included in the reference guide. 7 8Example of usage:: 9 10 $ python refguide_check.py optimize 11 12Note that this is a helper script to be able to check if things are missing; 13the output of this script does need to be checked manually. In some cases 14objects are left out of the refguide for a good reason (it's an alias of 15another function, or deprecated, or ...) 16 17Another use of this helper script is to check validity of code samples 18in docstrings. This is differenpt from doctesting [we do not aim to have 19scipy docstrings doctestable!], this is just to make sure that code in 20docstrings is valid python:: 21 22 $ python refguide_check.py --check_docs optimize 23 24""" 25from __future__ import print_function 26 27import sys 28import os 29import re 30import copy 31import inspect 32import warnings 33import doctest 34import tempfile 35import io 36import docutils.core 37from docutils.parsers.rst import directives 38import shutil 39import glob 40from doctest import NORMALIZE_WHITESPACE, ELLIPSIS, IGNORE_EXCEPTION_DETAIL 41from argparse import ArgumentParser 42import numpy as np 43 44# sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'doc', 45# 'sphinxext')) 46from numpydoc.docscrape_sphinx import get_doc_object 47# Remove sphinx directives that don't run without Sphinx environment 48directives._directives.pop('versionadded', None) 49directives._directives.pop('versionchanged', None) 50directives._directives.pop('moduleauthor', None) 51directives._directives.pop('sectionauthor', None) 52directives._directives.pop('codeauthor', None) 53directives._directives.pop('toctree', None) 54 55 56BASE_MODULE = "pyedflib" 57 58PUBLIC_SUBMODULES = [] 59 60# Docs for these modules are included in the parent module 61OTHER_MODULE_DOCS = {} 62 63# these names are known to fail doctesting and we like to keep it that way 64# e.g. sometimes pseudocode is acceptable etc 65DOCTEST_SKIPLIST = set([]) 66 67# these names are not required to be present in ALL despite being in 68# autosummary:: listing 69REFGUIDE_ALL_SKIPLIST = [] 70 71HAVE_MATPLOTLIB = False 72 73 74def short_path(path, cwd=None): 75 """ 76 Return relative or absolute path name, whichever is shortest. 77 """ 78 if not isinstance(path, str): 79 return path 80 if cwd is None: 81 cwd = os.getcwd() 82 abspath = os.path.abspath(path) 83 relpath = os.path.relpath(path, cwd) 84 if len(abspath) <= len(relpath): 85 return abspath 86 return relpath 87 88 89def find_names(module, names_dict): 90 # Refguide entries: 91 # 92 # - 3 spaces followed by function name, and maybe some spaces, some 93 # dashes, and an explanation; only function names listed in 94 # refguide are formatted like this (mostly, there may be some false 95 # positives) 96 # 97 # - special directives, such as data and function 98 # 99 # - (scipy.constants only): quoted list 100 # 101 patterns = [ 102 r"^\s\s\s([a-z_0-9A-Z]+)(\s+-+.*)?$", 103 r"^\.\. (?:data|function)::\s*([a-z_0-9A-Z]+)\s*$" 104 ] 105 106 if module.__name__ == 'scipy.constants': 107 patterns += ["^``([a-z_0-9A-Z]+)``"] 108 109 patterns = [re.compile(pattern) for pattern in patterns] 110 module_name = module.__name__ 111 112 for line in module.__doc__.splitlines(): 113 res = re.search(r"^\s*\.\. (?:currentmodule|module):: ([a-z0-9A-Z_.]+)\s*$", line) 114 if res: 115 module_name = res.group(1) 116 continue 117 118 for pattern in patterns: 119 res = re.match(pattern, line) 120 if res is not None: 121 name = res.group(1) 122 entry = '.'.join([module_name, name]) 123 names_dict.setdefault(module_name, set()).add(name) 124 break 125 126 127def get_all_dict(module): 128 """Return a copy of the __all__ dict with irrelevant items removed.""" 129 if hasattr(module, "__all__"): 130 all_dict = copy.deepcopy(module.__all__) 131 else: 132 all_dict = copy.deepcopy(dir(module)) 133 all_dict = [name for name in all_dict 134 if not name.startswith("_")] 135 for name in ['absolute_import', 'division', 'print_function']: 136 try: 137 all_dict.remove(name) 138 except ValueError: 139 pass 140 141 # Modules are almost always private; real submodules need a separate 142 # run of refguide_check. 143 all_dict = [name for name in all_dict 144 if not inspect.ismodule(getattr(module, name, None))] 145 146 deprecated = [] 147 not_deprecated = [] 148 for name in all_dict: 149 f = getattr(module, name, None) 150 if callable(f) and is_deprecated(f): 151 deprecated.append(name) 152 else: 153 not_deprecated.append(name) 154 155 others = set(dir(module)).difference(set(deprecated)).difference(set(not_deprecated)) 156 157 return not_deprecated, deprecated, others 158 159 160def compare(all_dict, others, names, module_name): 161 """Return sets of objects only in __all__, refguide, or completely missing.""" 162 only_all = set() 163 for name in all_dict: 164 if name not in names: 165 only_all.add(name) 166 167 only_ref = set() 168 missing = set() 169 for name in names: 170 if name not in all_dict: 171 for pat in REFGUIDE_ALL_SKIPLIST: 172 if re.match(pat, module_name + '.' + name): 173 if name not in others: 174 missing.add(name) 175 break 176 else: 177 only_ref.add(name) 178 179 return only_all, only_ref, missing 180 181 182def is_deprecated(f): 183 with warnings.catch_warnings(record=True) as w: 184 warnings.simplefilter("error") 185 try: 186 f(**{"not a kwarg": None}) 187 except DeprecationWarning: 188 return True 189 except: 190 pass 191 return False 192 193 194def check_items(all_dict, names, deprecated, others, module_name, dots=True): 195 num_all = len(all_dict) 196 num_ref = len(names) 197 198 output = "" 199 200 output += "Non-deprecated objects in __all__: %i\n" % num_all 201 output += "Objects in refguide: %i\n\n" % num_ref 202 203 only_all, only_ref, missing = compare(all_dict, others, names, module_name) 204 dep_in_ref = set(only_ref).intersection(deprecated) 205 only_ref = set(only_ref).difference(deprecated) 206 207 if len(dep_in_ref) > 0: 208 output += "Deprecated objects in refguide::\n\n" 209 for name in sorted(deprecated): 210 output += " " + name + "\n" 211 212 if len(only_all) == len(only_ref) == len(missing) == 0: 213 if dots: 214 output_dot('.') 215 return [(None, True, output)] 216 else: 217 if len(only_all) > 0: 218 output += "ERROR: objects in %s.__all__ but not in refguide::\n\n" % module_name 219 for name in sorted(only_all): 220 output += " " + name + "\n" 221 222 if len(only_ref) > 0: 223 output += "ERROR: objects in refguide but not in %s.__all__::\n\n" % module_name 224 for name in sorted(only_ref): 225 output += " " + name + "\n" 226 227 if len(missing) > 0: 228 output += "ERROR: missing objects::\n\n" 229 for name in sorted(missing): 230 output += " " + name + "\n" 231 232 if dots: 233 output_dot('F') 234 return [(None, False, output)] 235 236 237def validate_rst_syntax(text, name, dots=True): 238 if text is None: 239 if dots: 240 output_dot('E') 241 return False, "ERROR: %s: no documentation" % (name,) 242 243 ok_unknown_items = set([ 244 'mod', 'currentmodule', 'autosummary', 'data', 245 'obj', 'versionadded', 'versionchanged', 'module', 'class', 246 'ref', 'func', 'toctree', 'moduleauthor', 247 'sectionauthor', 'codeauthor', 'eq', 248 ]) 249 250 # Run through docutils 251 error_stream = io.StringIO() 252 253 def resolve(name, is_label=False): 254 return ("http://foo", name) 255 256 token = '<RST-VALIDATE-SYNTAX-CHECK>' 257 258 docutils.core.publish_doctree( 259 text, token, 260 settings_overrides = dict(halt_level=5, 261 traceback=True, 262 default_reference_context='title-reference', 263 default_role='emphasis', 264 link_base='', 265 resolve_name=resolve, 266 stylesheet_path='', 267 raw_enabled=0, 268 file_insertion_enabled=0, 269 warning_stream=error_stream)) 270 271 # Print errors, disregarding unimportant ones 272 error_msg = error_stream.getvalue() 273 errors = error_msg.split(token) 274 success = True 275 output = "" 276 277 for error in errors: 278 lines = error.splitlines() 279 if not lines: 280 continue 281 282 m = re.match(r'.*Unknown (?:interpreted text role|directive type) "(.*)".*$', lines[0]) 283 if m: 284 if m.group(1) in ok_unknown_items: 285 continue 286 287 m = re.match(r'.*Error in "math" directive:.*unknown option: "label"', " ".join(lines), re.S) 288 if m: 289 continue 290 291 output += name + lines[0] + "::\n " + "\n ".join(lines[1:]).rstrip() + "\n" 292 success = False 293 294 if not success: 295 output += " " + "-"*72 + "\n" 296 for lineno, line in enumerate(text.splitlines()): 297 output += " %-4d %s\n" % (lineno+1, line) 298 output += " " + "-"*72 + "\n\n" 299 300 if dots: 301 output_dot('.' if success else 'F') 302 return success, output 303 304 305def output_dot(msg='.', stream=sys.stderr): 306 stream.write(msg) 307 stream.flush() 308 309 310def check_rest(module, names, dots=True): 311 """ 312 Check reStructuredText formatting of docstrings 313 314 Returns: [(name, success_flag, output), ...] 315 """ 316 317 try: 318 skip_types = (dict, str, unicode, float, int) 319 except NameError: 320 # python 3 321 skip_types = (dict, str, float, int) 322 323 results = [] 324 325 if module.__name__[6:] not in OTHER_MODULE_DOCS: 326 results += [(module.__name__,) + 327 validate_rst_syntax(inspect.getdoc(module), 328 module.__name__, dots=dots)] 329 330 for name in names: 331 full_name = module.__name__ + '.' + name 332 obj = getattr(module, name, None) 333 334 if obj is None: 335 results.append((full_name, False, "%s has no docstring" % (full_name,))) 336 continue 337 elif isinstance(obj, skip_types): 338 continue 339 340 if inspect.ismodule(obj): 341 text = inspect.getdoc(obj) 342 else: 343 try: 344 text = str(get_doc_object(obj)) 345 except: 346 import traceback 347 results.append((full_name, False, 348 "Error in docstring format!\n" + 349 traceback.format_exc())) 350 continue 351 352 m = re.search("([\x00-\x09\x0b-\x1f])", text) 353 if m: 354 msg = ("Docstring contains a non-printable character %r! " 355 "Maybe forgot r\"\"\"?" % (m.group(1),)) 356 results.append((full_name, False, msg)) 357 continue 358 359 try: 360 src_file = short_path(inspect.getsourcefile(obj)) 361 except TypeError: 362 src_file = None 363 364 if src_file: 365 file_full_name = src_file + ':' + full_name 366 else: 367 file_full_name = full_name 368 369 results.append((full_name,) + 370 validate_rst_syntax(text, file_full_name, dots=dots)) 371 372 return results 373 374 375### Doctest helpers #### 376 377# the namespace to run examples in 378DEFAULT_NAMESPACE = {'np': np} 379 380# the namespace to do checks in 381CHECK_NAMESPACE = { 382 'np': np, 383 'assert_allclose': np.testing.assert_allclose, 384 'assert_equal': np.testing.assert_equal, 385 # recognize numpy repr's 386 'array': np.array, 387 'matrix': np.matrix, 388 'int64': np.int64, 389 'uint64': np.uint64, 390 'int8': np.int8, 391 'int32': np.int32, 392 'float64': np.float64, 393 'dtype': np.dtype, 394 'nan': np.nan, 395 'NaN': np.nan, 396 'inf': np.inf, 397 'Inf': np.inf, } 398 399 400class DTRunner(doctest.DocTestRunner): 401 DIVIDER = "\n" 402 403 def __init__(self, item_name, checker=None, verbose=None, optionflags=0): 404 self._item_name = item_name 405 doctest.DocTestRunner.__init__(self, checker=checker, verbose=verbose, 406 optionflags=optionflags) 407 408 def _report_item_name(self, out, new_line=False): 409 if self._item_name is not None: 410 if new_line: 411 out("\n") 412 self._item_name = None 413 414 def report_start(self, out, test, example): 415 self._checker._source = example.source 416 return doctest.DocTestRunner.report_start(self, out, test, example) 417 418 def report_success(self, out, test, example, got): 419 if self._verbose: 420 self._report_item_name(out, new_line=True) 421 return doctest.DocTestRunner.report_success( 422 self, out, test, example, got) 423 424 def report_unexpected_exception(self, out, test, example, exc_info): 425 self._report_item_name(out) 426 return doctest.DocTestRunner.report_unexpected_exception( 427 self, out, test, example, exc_info) 428 429 def report_failure(self, out, test, example, got): 430 self._report_item_name(out) 431 return doctest.DocTestRunner.report_failure(self, out, test, 432 example, got) 433 434class Checker(doctest.OutputChecker): 435 obj_pattern = re.compile('at 0x[0-9a-fA-F]+>') 436 vanilla = doctest.OutputChecker() 437 rndm_markers = {'# random', '# Random', '#random', '#Random', "# may vary"} 438 stopwords = {'plt.', '.hist', '.show', '.ylim', '.subplot(', 439 'set_title', 'imshow', 'plt.show', 'ax.axis', 'plt.plot(', 440 '.bar(', '.title', '.ylabel', '.xlabel', 'set_ylim', 441 'set_xlim', '# reformatted'} 442 443 def __init__(self, parse_namedtuples=True, ns=None, atol=1e-8, rtol=1e-2): 444 self.parse_namedtuples = parse_namedtuples 445 self.atol, self.rtol = atol, rtol 446 if ns is None: 447 self.ns = dict(CHECK_NAMESPACE) 448 else: 449 self.ns = ns 450 451 def check_output(self, want, got, optionflags): 452 # cut it short if they are equal 453 if want == got: 454 return True 455 456 # skip stopwords in source 457 if any(word in self._source for word in self.stopwords): 458 return True 459 460 # skip random stuff 461 if any(word in want for word in self.rndm_markers): 462 return True 463 464 # skip function/object addresses 465 if self.obj_pattern.search(got): 466 return True 467 468 # ignore comments (e.g. signal.freqresp) 469 if want.lstrip().startswith("#"): 470 return True 471 472 # try the standard doctest 473 try: 474 if self.vanilla.check_output(want, got, optionflags): 475 return True 476 except Exception: 477 pass 478 479 # OK then, convert strings to objects 480 try: 481 a_want = eval(want, dict(self.ns)) 482 a_got = eval(got, dict(self.ns)) 483 except: 484 if not self.parse_namedtuples: 485 return False 486 # suppose that "want" is a tuple, and "got" is smth like 487 # MoodResult(statistic=10, pvalue=0.1). 488 # Then convert the latter to the tuple (10, 0.1), 489 # and then compare the tuples. 490 try: 491 num = len(a_want) 492 regex = ('[\w\d_]+\(' + 493 ', '.join(['[\w\d_]+=(.+)']*num) + 494 '\)') 495 grp = re.findall(regex, got.replace('\n', ' ')) 496 if len(grp) > 1: # no more than one for now 497 return False 498 # fold it back to a tuple 499 got_again = '(' + ', '.join(grp[0]) + ')' 500 return self.check_output(want, got_again, optionflags) 501 except Exception: 502 return False 503 504 # ... and defer to numpy 505 try: 506 return self._do_check(a_want, a_got) 507 except Exception: 508 # heterog tuple, eg (1, np.array([1., 2.])) 509 try: 510 return all(self._do_check(w, g) for w, g in zip(a_want, a_got)) 511 except (TypeError, ValueError): 512 return False 513 514 def _do_check(self, want, got): 515 # This should be done exactly as written to correctly handle all of 516 # numpy-comparable objects, strings, and heterogenous tuples 517 try: 518 if want == got: 519 return True 520 except Exception: 521 pass 522 return np.allclose(want, got, atol=self.atol, rtol=self.rtol) 523 524 525def _run_doctests(tests, full_name, verbose, doctest_warnings): 526 """Run modified doctests for the set of `tests`. 527 528 Returns: list of [(success_flag, output), ...] 529 """ 530 flags = NORMALIZE_WHITESPACE | ELLIPSIS | IGNORE_EXCEPTION_DETAIL 531 runner = DTRunner(full_name, checker=Checker(), optionflags=flags, 532 verbose=verbose) 533 534 output = [] 535 success = True 536 def out(msg): 537 output.append(msg) 538 539 class MyStderr(object): 540 """Redirect stderr to the current stdout""" 541 def write(self, msg): 542 if doctest_warnings: 543 sys.stdout.write(msg) 544 else: 545 out(msg) 546 547 # Run tests, trying to restore global state afterward 548 old_printoptions = np.get_printoptions() 549 old_errstate = np.seterr() 550 old_stderr = sys.stderr 551 cwd = os.getcwd() 552 tmpdir = tempfile.mkdtemp() 553 sys.stderr = MyStderr() 554 try: 555 os.chdir(tmpdir) 556 557 # try to ensure random seed is NOT reproducible 558 np.random.seed(None) 559 560 for t in tests: 561 t.filename = short_path(t.filename, cwd) 562 fails, successes = runner.run(t, out=out) 563 if fails > 0: 564 success = False 565 finally: 566 sys.stderr = old_stderr 567 os.chdir(cwd) 568 shutil.rmtree(tmpdir) 569 np.set_printoptions(**old_printoptions) 570 np.seterr(**old_errstate) 571 572 return success, output 573 574 575def check_doctests(module, verbose, ns=None, 576 dots=True, doctest_warnings=False): 577 """Check code in docstrings of the module's public symbols. 578 579 Returns: list of [(item_name, success_flag, output), ...] 580 """ 581 if ns is None: 582 ns = dict(DEFAULT_NAMESPACE) 583 584 # Loop over non-deprecated items 585 results = [] 586 587 for name in get_all_dict(module)[0]: 588 full_name = module.__name__ + '.' + name 589 590 if full_name in DOCTEST_SKIPLIST: 591 continue 592 593 try: 594 obj = getattr(module, name) 595 except AttributeError: 596 import traceback 597 results.append((full_name, False, 598 "Missing item!\n" + 599 traceback.format_exc())) 600 continue 601 602 finder = doctest.DocTestFinder() 603 try: 604 tests = finder.find(obj, name, globs=dict(ns)) 605 except: 606 import traceback 607 results.append((full_name, False, 608 "Failed to get doctests!\n" + 609 traceback.format_exc())) 610 continue 611 612 success, output = _run_doctests(tests, full_name, verbose, 613 doctest_warnings) 614 615 if dots: 616 output_dot('.' if success else 'F') 617 618 results.append((full_name, success, "".join(output))) 619 620 if HAVE_MATPLOTLIB: 621 import matplotlib.pyplot as plt 622 plt.close('all') 623 624 return results 625 626 627def check_doctests_testfile(fname, verbose, ns=None, 628 dots=True, doctest_warnings=False): 629 """Check code in a text file. 630 631 Mimic `check_doctests` above, differing mostly in test discovery. 632 (which is borrowed from stdlib's doctest.testfile here, 633 https://github.com/python-git/python/blob/master/Lib/doctest.py) 634 635 Returns: list of [(item_name, success_flag, output), ...] 636 637 Notes 638 ----- 639 640 We also try to weed out pseudocode: 641 * We maintain a list of exceptions which signal pseudocode, 642 * We split the text file into "blocks" of code separated by empty lines 643 and/or intervening text. 644 * If a block contains a marker, the whole block is then assumed to be 645 pseudocode. It is then not being doctested. 646 647 The rationale is that typically, the text looks like this: 648 649 blah 650 <BLANKLINE> 651 >>> from numpy import some_module # pseudocode! 652 >>> func = some_module.some_function 653 >>> func(42) # still pseudocode 654 146 655 <BLANKLINE> 656 blah 657 <BLANKLINE> 658 >>> 2 + 3 # real code, doctest it 659 5 660 661 """ 662 results = [] 663 664 if ns is None: 665 ns = dict(DEFAULT_NAMESPACE) 666 667 _, short_name = os.path.split(fname) 668 if short_name in DOCTEST_SKIPLIST: 669 return results 670 671 full_name = fname 672 text = open(fname).read() 673 674 PSEUDOCODE = set(['some_function', 'some_module', 'import example', 675 'ctypes.CDLL', # likely need compiling, skip it 676 'integrate.nquad(func,' # ctypes integrate tutotial 677 ]) 678 679 # split the text into "blocks" and try to detect and omit pseudocode blocks. 680 parser = doctest.DocTestParser() 681 good_parts = [] 682 for part in text.split('\n\n'): 683 tests = parser.get_doctest(part, ns, fname, fname, 0) 684 if any(word in ex.source for word in PSEUDOCODE 685 for ex in tests.examples): 686 # omit it 687 pass 688 else: 689 # `part` looks like a good code, let's doctest it 690 good_parts += [part] 691 692 # Reassemble the good bits and doctest them: 693 good_text = '\n\n'.join(good_parts) 694 tests = parser.get_doctest(good_text, ns, fname, fname, 0) 695 success, output = _run_doctests([tests], full_name, verbose, 696 doctest_warnings) 697 698 if dots: 699 output_dot('.' if success else 'F') 700 701 results.append((full_name, success, "".join(output))) 702 703 if HAVE_MATPLOTLIB: 704 import matplotlib.pyplot as plt 705 plt.close('all') 706 707 return results 708 709 710def init_matplotlib(): 711 global HAVE_MATPLOTLIB 712 713 try: 714 import matplotlib 715 matplotlib.use('Agg') 716 HAVE_MATPLOTLIB = True 717 except ImportError: 718 HAVE_MATPLOTLIB = False 719 720 721def main(argv): 722 parser = ArgumentParser(usage=__doc__.lstrip()) 723 parser.add_argument("module_names", metavar="SUBMODULES", default=[], 724 nargs='*', 725 help="Submodules to check (default: all public)") 726 parser.add_argument("--doctests", action="store_true", 727 help="Run also doctests") 728 parser.add_argument("-v", "--verbose", action="count", default=0) 729 parser.add_argument("--doctest-warnings", action="store_true", 730 help="Enforce warning checking for doctests") 731 parser.add_argument("--skip-examples", action="store_true", 732 help="Skip running doctests in the examples.") 733 args = parser.parse_args(argv) 734 735 modules = [] 736 names_dict = {} 737 738 if args.module_names: 739 args.skip_examples = True 740 else: 741 args.module_names = list(PUBLIC_SUBMODULES) 742 743 os.environ['SCIPY_PIL_IMAGE_VIEWER'] = 'true' 744 745 module_names = list(args.module_names) 746 for name in list(module_names): 747 if name in OTHER_MODULE_DOCS: 748 name = OTHER_MODULE_DOCS[name] 749 if name not in module_names: 750 module_names.append(name) 751 752 for submodule_name in module_names: 753 module_name = BASE_MODULE + '.' + submodule_name 754 __import__(module_name) 755 module = sys.modules[module_name] 756 757 if submodule_name not in OTHER_MODULE_DOCS: 758 find_names(module, names_dict) 759 760 if submodule_name in args.module_names: 761 modules.append(module) 762 763 dots = True 764 success = True 765 results = [] 766 767 print("Running checks for %d modules:" % (len(modules),)) 768 769 if args.doctests or not args.skip_examples: 770 init_matplotlib() 771 772 for module in modules: 773 if dots: 774 if module is not modules[0]: 775 sys.stderr.write(' ') 776 sys.stderr.write(module.__name__ + ' ') 777 sys.stderr.flush() 778 779 all_dict, deprecated, others = get_all_dict(module) 780 names = names_dict.get(module.__name__, set()) 781 782 mod_results = [] 783 mod_results += check_items(all_dict, names, deprecated, others, module.__name__) 784 mod_results += check_rest(module, set(names).difference(deprecated), 785 dots=dots) 786 if args.doctests: 787 mod_results += check_doctests(module, (args.verbose >= 2), dots=dots, 788 doctest_warnings=args.doctest_warnings) 789 790 for v in mod_results: 791 assert isinstance(v, tuple), v 792 793 results.append((module, mod_results)) 794 795 if dots: 796 sys.stderr.write("\n") 797 sys.stderr.flush() 798 799 if not args.skip_examples: 800 examples_path = os.path.join( 801 os.getcwd(), 'doc', 'source', 'regression', '*.rst') 802 print('\nChecking examples files at %s:' % examples_path) 803 for filename in sorted(glob.glob(examples_path)): 804 if dots: 805 sys.stderr.write('\n') 806 sys.stderr.write(os.path.split(filename)[1] + ' ') 807 sys.stderr.flush() 808 809 examples_results = check_doctests_testfile( 810 filename, (args.verbose >= 2), dots=dots, 811 doctest_warnings=args.doctest_warnings) 812 813 def scratch(): pass # stub out a "module", see below 814 scratch.__name__ = filename 815 results.append((scratch, examples_results)) 816 817 if dots: 818 sys.stderr.write("\n") 819 sys.stderr.flush() 820 821 # Report results 822 all_success = True 823 824 for module, mod_results in results: 825 success = all(x[1] for x in mod_results) 826 all_success = all_success and success 827 828 if success and args.verbose == 0: 829 continue 830 831 print("") 832 print("=" * len(module.__name__)) 833 print(module.__name__) 834 print("=" * len(module.__name__)) 835 print("") 836 837 for name, success, output in mod_results: 838 if name is None: 839 if not success or args.verbose >= 1: 840 print(output.strip()) 841 print("") 842 elif not success or (args.verbose >= 2 and output.strip()): 843 print(name) 844 print("-"*len(name)) 845 print("") 846 print(output.strip()) 847 print("") 848 849 if all_success: 850 print("\nOK: refguide and doctests checks passed!") 851 sys.exit(0) 852 else: 853 print("\nERROR: refguide or doctests have errors") 854 sys.exit(1) 855 856 857if __name__ == '__main__': 858 main(argv=sys.argv[1:])