Ticket #12415: 12415_framework.patch

File 12415_framework.patch, 225.4 KB (added by roed, 7 years ago)

The new doctesting code

  • sage/all.py

    # HG changeset patch
    # User David Roe <roed.math@gmail.com>
    # Date 1340304344 14400
    # Node ID 6713d17ffcb8d48f1090483f70d07f9ef0d8801e
    # Parent  a573fe0d7de9f720150313245485ae960fe91eac
    #12415: Addition of the new doctesting framework to the sage library
    
    diff --git a/sage/all.py b/sage/all.py
    a b  
    8686from sage.misc.sh import sh
    8787
    8888from sage.libs.all       import *
     89from sage.doctest.all    import *
    8990
    9091from sage.rings.all      import *
    9192from sage.matrix.all     import *
  • new file sage/doctest/all.py

    diff --git a/sage/doctest/__init__.py b/sage/doctest/__init__.py
    new file mode 100644
    diff --git a/sage/doctest/all.py b/sage/doctest/all.py
    new file mode 100644
    - +  
     1from control import run_doctests
  • new file sage/doctest/control.py

    diff --git a/sage/doctest/control.py b/sage/doctest/control.py
    new file mode 100644
    - +  
     1"""
     2This module controls the various classes involved in doctesting.
     3
     4AUTHORS:
     5
     6- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
     7"""
     8
     9#*****************************************************************************
     10#       Copyright (C) 2012 David Roe <roed.math@gmail.com>
     11#                          Robert Bradshaw <robertwb@gmail.com>
     12#                          William Stein <wstein@gmail.com>
     13#
     14#  Distributed under the terms of the GNU General Public License (GPL)
     15#
     16#                  http://www.gnu.org/licenses/
     17#*****************************************************************************
     18
     19import random, glob, os, time, json, re, types
     20from util import NestedName, Timer, count_noun
     21import sage.misc.flatten
     22from sage.structure.sage_object import SageObject
     23
     24from sources import FileDocTestSource, DictAsObject
     25from forker import DocTestDispatcher
     26from reporting import DocTestReporter
     27
     28branch_matcher = re.compile(r"^(sage(\-[A-Za-z0-9_\-\.]+)?" + os.path.sep + ")?sage(.*)")
     29tested_extensions = [".py",".pyx",".pxi"]
     30file_ext = re.compile("(.*)(" + "|".join(["\\" + a for a in tested_extensions]) + ")")
     31
     32class DocTestDefaults(SageObject):
     33    """
     34    This class is used for doctesting the Sage doctest module.
     35
     36    It fills in attributes to be the defaults defined in SAGE_ROOT/local/bin/sage-runtests.
     37
     38    EXAMPLES::
     39
     40        sage: from sage.doctest.control import DocTestDefaults
     41        sage: D = DocTestDefaults(); D.timeout
     42        -1
     43    """
     44    def __init__(self, **kwds):
     45        """
     46        Edit these parameters after creating an instance.
     47
     48        EXAMPLES::
     49
     50            sage: from sage.doctest.control import DocTestDefaults
     51            sage: D = DocTestDefaults(); D.optional
     52            'sage'
     53        """
     54        self.nthreads = 1
     55        self.serial = False
     56        self.timeout = -1
     57        self.all = False
     58        self.logfile = None
     59        self.sagenb = False
     60        self.long = False
     61        self.warn_long = None
     62        self.optional = "sage"
     63        self.randorder = None
     64        self.global_iterations = 0
     65        self.file_iterations = 0
     66        self.initial = False
     67        self.force_lib = False
     68        self.abspath = True # We don't use the cmd line default so that doctest output is more predictable
     69        self.verbose = False
     70        self.debug = False
     71        self.gdb = False
     72        self.valgrind = False
     73        self.massif = False
     74        self.cachegrind = False
     75        self.omega = False
     76        self.failed = False
     77        self.new = False
     78        # We don't want to use the real stats file by default so that
     79        # we don't overwrite timings for the actual running doctests.
     80        self.stats_path = os.path.join(os.path.expanduser("~/.sage/timings_dt_test.json"))
     81        if not os.path.exists(self.stats_path):
     82            with open(self.stats_path, "w") as stats_file:
     83                json.dump({},stats_file)
     84        self.__dict__.update(kwds)
     85
     86class DocTestController(SageObject):
     87    """
     88    This class controls doctesting of files.
     89
     90    After creating it with appropriate options, call the :meth:run() method to run the doctests.
     91    """
     92    def __init__(self, options, args):
     93        """
     94        Initialization.
     95
     96        INPUT:
     97
     98        - options -- either options generated from the command line by SAGE_ROOT/local/bin/sage-runtests
     99                     or a DocTestDefaults object (possibly with some entries modified)
     100        - args -- a list of filenames to doctest
     101
     102        EXAMPLES::
     103
     104            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     105            sage: DC = DocTestController(DocTestDefaults(), [])
     106            sage: DC
     107            DocTest Controller
     108        """
     109        # First we modify options to take environment variables into
     110        # account and check compatibility of the user's specified
     111        # options.
     112        if options.timeout == -1:
     113            if options.gdb or options.valgrind or options.massif or options.cachegrind or options.omega or options.debug:
     114                options.timeout = os.getenv('SAGE_TIMEOUT_VALGRIND', 1024 * 1024)
     115            elif options.long:
     116                options.timeout = os.getenv('SAGE_TIMEOUT_LONG', 30 * 60)
     117            else:
     118                options.timeout = os.getenv('SAGE_TIMEOUT', 5 * 60)
     119        if not hasattr(options, 'nthreads'):
     120            options.nthreads = 1
     121        elif options.nthreads == 0:
     122            options.nthreads = int(os.getenv('SAGE_NUM_THREADS_PARALLEL',1))
     123        if not hasattr(options, 'warn_long'):
     124            options.warn_long = None
     125        if options.failed and not (args or options.new or options.sagenb):
     126            # If the user doesn't specify any files then we rerun all failed files.
     127            options.all = True
     128        if options.global_iterations == 0:
     129            options.global_iterations = int(os.environ.get('SAGE_TEST_GLOBAL_ITER', 1))
     130        if options.file_iterations == 0:
     131            options.file_iterations = int(os.environ.get('SAGE_TEST_ITER', 1))
     132        self.options = options
     133        self.files = args
     134        if options.all and options.logfile is None:
     135            options.logfile = os.path.join(os.environ['SAGE_TESTDIR'], 'test.log')
     136        if options.logfile:
     137            try:
     138                self.logfile = open(options.logfile, 'a')
     139            except IOError:
     140                print "Unable to open logfile at %s\nProceeding without logging."%(options.logfile)
     141                self.logfile = None
     142        else:
     143            self.logfile = None
     144        self.stats = {}
     145        self.load_stats(options.stats_path)
     146        if (options.serial or options.verbose) and self.options.nthreads > 1:
     147            self.log("You may only use one thread when in verbose mode; resetting number of threads")
     148            self.options.nthreads = 1
     149
     150    def _repr_(self):
     151        """
     152        String representation.
     153
     154        EXAMPLES::
     155
     156            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     157            sage: DC = DocTestController(DocTestDefaults(), [])
     158            sage: repr(DC) # indirect doctest
     159            'DocTest Controller'
     160        """
     161        return "DocTest Controller"
     162
     163    def load_stats(self, filename):
     164        """
     165        Load stats from the most recent run(s).
     166
     167        Stats are stored as a JSON file, and include information on
     168        which files failed tests and the walltime used for execution of the doctests.
     169
     170        EXAMPLES::
     171
     172            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     173            sage: DC = DocTestController(DocTestDefaults(), [])
     174            sage: import json
     175            sage: filename = tmp_filename()
     176            sage: with open(filename, 'w') as stats_file:
     177            ...       json.dump({'sage.doctest.control':{u'walltime':1.0r}}, stats_file)
     178            sage: DC.load_stats(filename)
     179            sage: DC.stats['sage.doctest.control']
     180            {u'walltime': 1.0}
     181        """
     182        try:
     183            with open(filename) as stats_file:
     184                self.stats.update(json.load(stats_file))
     185        except IOError:
     186            pass
     187        #except (ValueError, TypeError):
     188        #    self.log("Error loading stats from %s"%filename)
     189
     190    def save_stats(self, filename):
     191        """
     192        Save stats from the most recent run as a JSON file.
     193
     194        WARNING: This function overwrites the file.
     195
     196        EXAMPLES::
     197
     198            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     199            sage: DC = DocTestController(DocTestDefaults(), [])
     200            sage: DC.stats['sage.doctest.control'] = {u'walltime':1.0r}
     201            sage: filename = tmp_filename()
     202            sage: DC.save_stats(filename)
     203            sage: import json
     204            sage: with open(filename) as stats_file:
     205            ...       D = json.load(stats_file)
     206            sage: D['sage.doctest.control']
     207            {u'walltime': 1.0}
     208        """
     209        try:
     210            with open(filename, 'w') as stats_file:
     211                json.dump(self.stats, stats_file)
     212        except (IOError, ValueError, TypeError):
     213            raise
     214            #self.log("Error saving stats to %s"%filename)
     215
     216    def log(self, s):
     217        """
     218        Logs the string s to the logfile and prints it to standard out.
     219
     220        EXAMPLES::
     221
     222            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     223            sage: DD = DocTestDefaults(); DD.logfile = tmp_filename()
     224            sage: DC = DocTestController(DD, [])
     225            sage: DC.log("hello world")
     226            hello world
     227            sage: DC.logfile.close()
     228            sage: with open(DD.logfile) as logger: print logger.read()
     229            hello world
     230
     231        """
     232        if self.logfile is not None:
     233            self.logfile.write(s + "\n")
     234        print(s)
     235
     236    def test_safe_directory(self, dir = None):
     237        """
     238        Test that the given directory is safe to run Python code from.
     239
     240        We use the check added to Python for this, which gives a
     241        warning when the current directory is considered unsafe.  We promote
     242        this warning to an error with -Werror.  See sage/tests/cmdline.py
     243        for a doctest that this works, see also :trac:`13579`.
     244
     245        TESTS::
     246
     247            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     248            sage: DD = DocTestDefaults()
     249            sage: DC = DocTestController(DD, [])
     250            sage: DC.test_safe_directory()
     251            sage: d = os.path.join(tmp_dir(), "test")
     252            sage: os.mkdir(d)
     253            sage: os.chmod(d, 0o777)
     254            sage: DC.test_safe_directory(d)
     255            Traceback (most recent call last):
     256            ...
     257            RuntimeError: refusing to run doctests...
     258        """
     259        if dir is None:
     260            cur = None
     261        else:
     262            cur = os.getcwd()
     263            os.chdir(dir)
     264        import subprocess
     265        with open(os.devnull, 'w') as dev_null:
     266            if subprocess.call(['python', '-Werror', '-c', ''], stdout=dev_null, stderr=dev_null) != 0:
     267                raise RuntimeError("refusing to run doctests from the current "\
     268                      "directory '{}' since untrusted users could put files in "\
     269                      "this directory, making it unsafe to run Sage code from"\
     270                      .format(os.getcwd()))
     271        if cur is not None:
     272            os.chdir(cur)
     273
     274    def create_run_id(self):
     275        """
     276        Creates the run id.
     277
     278        EXAMPLES::
     279
     280            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     281            sage: DC = DocTestController(DocTestDefaults(), [])
     282            sage: DC.create_run_id()
     283            Running doctests with ID ...
     284        """
     285        self.run_id = time.strftime('%Y-%m-%d-%H-%M-%S-') + "%08x" % random.getrandbits(32)
     286        from sage.version import version
     287        self.log("Running doctests with ID %s."%self.run_id)
     288
     289    def add_files(self):
     290        """
     291        Checks for the flags '--all', '--new' and '--sagenb'.
     292
     293        For each one present, this function adds the appropriate directories and files to the todo list.
     294
     295        EXAMPLES::
     296
     297            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     298            sage: import os
     299            sage: log_location = os.path.join(SAGE_TMP, 'control_dt_log.log')
     300            sage: DD = DocTestDefaults(all=True, logfile=log_location)
     301            sage: DC = DocTestController(DD, [])
     302            sage: DC.add_files()
     303            Doctesting entire Sage library.
     304            sage: br = os.readlink(os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage'))
     305            sage: os.path.join(os.environ['SAGE_ROOT'], 'devel', br, 'sage') in DC.files
     306            True
     307
     308        ::
     309
     310            sage: DD = DocTestDefaults(new = True)
     311            sage: DC = DocTestController(DD, [])
     312            sage: DC.add_files()
     313            Doctesting files changed since last HG commit.
     314            sage: len(DC.files) == len([L for L in hg_sage('status', interactive=False, debug=False)[0].split('\n') if len(L.split()) ==2 and L.split()[0] in ['M','A']])
     315            True
     316
     317        ::
     318
     319            sage: DD = DocTestDefaults(sagenb = True)
     320            sage: DC = DocTestController(DD, [])
     321            sage: DC.add_files()
     322            Doctesting the Sage notebook.
     323            sage: DC.files[0][-6:]
     324            'sagenb'
     325        """
     326        opj = os.path.join
     327        SAGE_ROOT = os.environ['SAGE_ROOT']
     328        br = os.readlink(opj(SAGE_ROOT, 'devel', 'sage'))
     329        base = opj(SAGE_ROOT, 'devel', br)
     330        if self.options.all:
     331            self.log("Doctesting entire Sage library.")
     332            self.files.extend([opj(base, a) for a in [opj('doc', 'common'), opj('doc', '[a-z][a-z]'), 'sage']])
     333            self.options.sagenb = True
     334        elif self.options.new:
     335            self.log("Doctesting files changed since last HG commit.")
     336            import sage.all_cmdline
     337            from sage.misc.hg import hg_sage
     338            for X in hg_sage('status', interactive=False, debug=False)[0].split('\n'):
     339                tup = X.split()
     340                if len(tup) != 2: continue
     341                c, filename = tup
     342                if c in ['M','A']:
     343                    filename = opj(os.environ['SAGE_ROOT'], 'devel', 'sage', filename)
     344                    self.files.append(filename)
     345        if self.options.sagenb:
     346            if not self.options.all:
     347                self.log("Doctesting the Sage notebook.")
     348            from pkg_resources import Requirement, working_set
     349            sagenb_loc = working_set.find(Requirement.parse('sagenb')).location
     350            self.files.append(opj(sagenb_loc, 'sagenb'))
     351
     352    def expand_files_into_sources(self):
     353        """
     354        Expands ``self.files``, which may include directories, into a list of :class:sage.doctest.FileDocTestSource
     355
     356        This function also handles the optional command line option.
     357
     358        EXAMPLES::
     359
     360            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     361            sage: import os
     362            sage: dirname = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'doctest')
     363            sage: DD = DocTestDefaults(); DD.optional = 'all'
     364            sage: DC = DocTestController(DD, [dirname])
     365            sage: DC.expand_files_into_sources()
     366            sage: len(DC.sources)
     367            8
     368            sage: DC.sources[0].optional
     369            True
     370
     371        ::
     372
     373            sage: DD = DocTestDefaults(); DD.optional = 'magma,guava'
     374            sage: DC = DocTestController(DD, [dirname])
     375            sage: DC.expand_files_into_sources()
     376            sage: sorted(list(DC.sources[0].optional))
     377            ['guava', 'magma']
     378        """
     379        def skipdir(dirname):
     380            return os.path.exists(os.path.join(dirname, "nodoctest.py")) or dirname[0] == '.'
     381        def skipfile(filename):
     382            base, ext = os.path.splitext(filename)
     383            if not os.path.exists(filename) or ext not in ('.py', '.pyx', '.pxi', '.sage', '.spyx', '.rst', '.tex'):
     384                return True
     385            with open(filename) as F:
     386                return 'nodoctest' in F.read(50)
     387        def expand():
     388            for pattern in self.files:
     389                real_files = glob.glob(pattern)
     390                if len(real_files) == 0:
     391                    self.log("No files matching %s"%(pattern))
     392                for path in real_files:
     393                    if os.path.isdir(path):
     394                        for root, dirs, files in os.walk(path):
     395                            for dir in list(dirs):
     396                                if skipdir(os.path.join(root,dir)):
     397                                    dirs.remove(dir)
     398                            for file in files:
     399                                if not skipfile(os.path.join(root,file)):
     400                                    yield os.path.join(root, file)
     401                    else:
     402                        # the user input this file explicitly, so we don't skip it
     403                        yield path
     404        if self.options.optional == 'all':
     405            optionals = True
     406        else:
     407            optionals = set(self.options.optional.lower().split(','))
     408        self.sources = [FileDocTestSource(path, self.options.force_lib, long=self.options.long, optional=optionals, randorder=self.options.randorder, useabspath=self.options.abspath) for path in expand()]
     409
     410    def filter_sources(self):
     411        """
     412       
     413        EXAMPLES::
     414
     415            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     416            sage: import os
     417            sage: dirname = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'doctest')
     418            sage: DD = DocTestDefaults(); DD.failed = True
     419            sage: DC = DocTestController(DD, [dirname])
     420            sage: DC.expand_files_into_sources()
     421            sage: for i, source in enumerate(DC.sources):
     422            ...       DC.stats[source.basename] = {'walltime': 0.1*(i+1)}
     423            sage: DC.stats['sage.doctest.control'] = {'failed':True,'walltime':1.0}
     424            sage: DC.filter_sources()
     425            Only doctesting files that failed last test.
     426            sage: len(DC.sources)
     427            1
     428        """
     429        # Filter the sources to only include those with failing doctests if the --failed option is passed
     430        if self.options.failed:
     431            self.log("Only doctesting files that failed last test.")
     432            def is_failure(source):
     433                basename = source.basename
     434                return basename not in self.stats or self.stats[basename].get('failed')
     435            self.sources = filter(is_failure, self.sources)
     436
     437    def sort_sources(self):
     438        """
     439        This function sorts the sources so that slower doctests ar run first.
     440
     441        EXAMPLES::
     442
     443            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     444            sage: import os
     445            sage: dirname = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'doctest')
     446            sage: DD = DocTestDefaults(nthreads=2)
     447            sage: DC = DocTestController(DD, [dirname])
     448            sage: DC.expand_files_into_sources()
     449            sage: DC.sources.sort(key=lambda s:s.basename)
     450            sage: for i, source in enumerate(DC.sources):
     451            ...       DC.stats[source.basename] = {'walltime': 0.1*(i+1)}
     452            sage: DC.sort_sources()
     453            Sorting sources by runtime so that slower doctests are run first....
     454            sage: print "\n".join([source.basename for source in DC.sources])
     455            sage.doctest.util
     456            sage.doctest.sources
     457            sage.doctest.reporting
     458            sage.doctest.parsing
     459            sage.doctest.forker
     460            sage.doctest.control
     461            sage.doctest.all
     462            sage.doctest
     463        """
     464        if self.options.nthreads > 1 and len(self.sources) > self.options.nthreads:
     465            self.log("Sorting sources by runtime so that slower doctests are run first....")
     466            default = dict(walltime=0)
     467            def sort_key(source):
     468                basename = source.basename
     469                return -self.stats.get(basename, default).get('walltime'), basename
     470            self.sources = [x[1] for x in sorted((sort_key(source), source) for source in self.sources)]
     471
     472    def run_doctests(self):
     473        """
     474        Actually runs the doctests.
     475
     476        This function is called by :meth:run().
     477
     478        EXAMPLES::
     479
     480            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     481            sage: import os
     482            sage: dirname = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'rings', 'homset.py')
     483            sage: DD = DocTestDefaults()
     484            sage: DC = DocTestController(DD, [dirname])
     485            sage: DC.expand_files_into_sources()
     486            sage: DC.run_doctests()
     487            Doctesting 1 file.
     488            sage -t .../sage/rings/homset.py
     489                [... tests, ... s]
     490            ------------------------------------------------------------------------
     491            All tests passed!
     492            ------------------------------------------------------------------------
     493            Total time for all tests: ... seconds
     494                cpu time: ... seconds
     495                cumulative wall time: ... seconds
     496
     497        """
     498        nfiles = 0
     499        nother = 0
     500        for F in self.sources:
     501            if isinstance(F, FileDocTestSource):
     502                nfiles += 1
     503            else:
     504                nother += 1
     505        if self.sources:
     506            filestr = ", ".join(([count_noun(nfiles, "file")] if nfiles else []) +
     507                                ([count_noun(nother, "other source")] if nother else []))
     508            if self.options.nthreads > len(self.sources):
     509                self.options.nthreads = len(self.sources)
     510            threads = " using %s threads"%(self.options.nthreads) if self.options.nthreads > 1 else ""
     511            iterations = []
     512            if self.options.global_iterations > 1:
     513                iterations.append("%s global iterations"%(self.options.global_iterations))
     514            if self.options.file_iterations > 1:
     515                iterations.append("%s file iterations"%(self.options.file_iterations))
     516            iterations = ", ".join(iterations)
     517            if iterations:
     518                iterations = " (%s)"%(iterations)
     519            self.log("Doctesting %s%s%s."%(filestr, threads, iterations))
     520            self.reporter = DocTestReporter(self)
     521            self.dispatcher = DocTestDispatcher(self)
     522            N = self.options.global_iterations
     523            for it in range(N):
     524                try:
     525                    self.timer = Timer().start()
     526                    self.dispatcher.dispatch()
     527                except KeyboardInterrupt:
     528                    # This case only occurs in non-serial mode.
     529                    # We wait a small amount so that the Workers can print which tests were interrupted.
     530                    time.sleep(0.01)
     531                    it = N - 1
     532                    break
     533                finally:
     534                    self.timer.stop()
     535                    self.reporter.finalize()
     536                    self.cleanup(it == N - 1)
     537        else:
     538            self.log("No files to doctest")
     539            self.reporter = DictAsObject(dict(error_status=0))
     540
     541    def cleanup(self, final=True):
     542        """
     543        Runs cleanup activities after actually running doctests.
     544
     545        In particular, saves the stats to disk and closes the logfile.
     546
     547        INPUT:
     548
     549        - ``final`` -- whether to close the logfile
     550
     551        EXAMPLES::
     552
     553             sage: from sage.doctest.control import DocTestDefaults, DocTestController
     554             sage: import os
     555             sage: dirname = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'rings', 'infinity.py')
     556             sage: DD = DocTestDefaults()
     557
     558             sage: DC = DocTestController(DD, [dirname])
     559             sage: DC.expand_files_into_sources()
     560             sage: DC.sources.sort(key=lambda s:s.basename)
     561
     562             sage: for i, source in enumerate(DC.sources):
     563             ....:     DC.stats[source.basename] = {'walltime': 0.1*(i+1)}
     564             ....:
     565
     566             sage: DC.run()
     567             Running doctests with ID ...
     568             Doctesting 1 file.
     569             sage -t .../rings/infinity.py
     570                 [... tests, ... s]
     571             ------------------------------------------------------------------------
     572             All tests passed!
     573             ------------------------------------------------------------------------
     574             Total time for all tests: ... seconds
     575                 cpu time: ... seconds
     576                 cumulative wall time: ... seconds
     577             0
     578             sage: DC.cleanup()
     579        """
     580        self.stats.update(self.reporter.stats)
     581        self.save_stats(self.options.stats_path)
     582        # Close the logfile
     583        if final and self.logfile is not None:
     584            self.logfile.close()
     585            self.logfile = None
     586
     587    def _assemble_cmd(self):
     588        """
     589        Assembles a shell command used in running tests under gdb or valgrind.
     590
     591        EXAMPLES::
     592
     593            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     594            sage: DC = DocTestController(DocTestDefaults(), ["hello_world.py"])
     595            sage: DC._assemble_cmd()
     596            'python .../local/bin/sage-runtests --serial --nthreads 1 --timeout 300 --optional sage --stats_path ... hello_world.py'
     597        """
     598        cmd = "python %s --serial "%(os.path.join(os.environ["SAGE_ROOT"],"local","bin","sage-runtests"))
     599        opt = self.options.__dict__
     600        for o in ("all", "sagenb"):
     601            if opt[o]:
     602                raise ValueError("You cannot run gdb/valgrind on the whole sage%s library"%("" if o == "all" else "nb"))
     603        for o in ("all", "sagenb", "long", "force_lib", "verbose", "failed", "new"):
     604            if opt[o]:
     605                cmd += "--%s "%o
     606        for o in ("nthreads", "timeout", "optional", "randorder", "stats_path"):
     607            if opt[o]:
     608                cmd += "--%s %s "%(o, opt[o])
     609        return cmd + " ".join(self.files)
     610
     611    def run_val_gdb(self, testing=False):
     612        """
     613        Spawns a subprocess to run tests under the control of gdb or valgrind.
     614
     615        INPUT:
     616
     617        - ``testing`` -- boolean; if True then the command to be run
     618          will be printed rather than a subprocess started.
     619
     620        EXAMPLES::
     621
     622            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     623            sage: DD = DocTestDefaults()
     624            sage: DD.gdb = True
     625            sage: DC = DocTestController(DD, ["hello_world.py"])
     626            sage: DC.run_val_gdb(testing=True)
     627            gdb -x .../local/bin/sage-gdb-commands --args python .../local/bin/sage-runtests --serial --nthreads 1 --timeout 1048576 --optional sage --stats_path ... hello_world.py
     628            sage: DD.gdb, DD.valgrind = False, True
     629            sage: DC = DocTestController(DD, ["hello_world.py"])
     630            sage: DC.run_val_gdb(testing=True)
     631            valgrind --tool=memcheck --leak-resolution=high --log-file=.../sage-memcheck.%p --leak-check=full --num-callers=25 --suppressions=.../local/lib/valgrind/sage.supp python .../local/bin/sage-runtests --serial --nthreads 1 --timeout 1048576 --optional sage --stats_path ... hello_world.py
     632        """
     633        try:
     634            sage_cmd = self._assemble_cmd()
     635        except ValueError:
     636            self.log(sys.exc_info()[1])
     637            return 2
     638        opt = self.options
     639        if opt.gdb:
     640            sageroot = os.environ["SAGE_ROOT"]
     641            cmd = "gdb -x %s --args "%(os.path.join(sageroot,"local","bin","sage-gdb-commands"))
     642            flags = ""
     643            if opt.logfile:
     644                sage_cmd += " --logfile %s"%(opt.logfile)
     645        else:
     646            if opt.logfile is None:
     647                dotsage = os.environ["DOT_SAGE"]
     648                default_log = os.path.join(dotsage, "valgrind")
     649                if os.path.exists(default_log):
     650                    if not os.path.isdir(default_log):
     651                        self.log("%s must be a directory"%default_log)
     652                        return 2
     653                else:
     654                    os.makedirs(default_log)
     655                logfile = os.path.join(default_log, "sage-%s")
     656            else:
     657                logfile = opt.logfile
     658            logfile = "--log-file=" + logfile
     659            if opt.valgrind:
     660                toolname = "memcheck"
     661                flags = os.getenv("SAGE_MEMCHECK_FLAGS")
     662                if flags is None:
     663                    suppfile = os.path.join(os.getenv("SAGE_LOCAL"), "lib", "valgrind", "sage.supp")
     664                    flags = "--leak-resolution=high %s --leak-check=full --num-callers=25 --suppressions=%s"%(logfile, suppfile)
     665            elif opt.massif:
     666                toolname = "massif"
     667                flags = os.getenv("SAGE_MASSIF_FLAGS", "--depth=6 %s"%logfile)
     668            elif opt.cachegrind:
     669                toolname = "cachegrind"
     670                flags = os.getenv("SAGE_CACHEGRIND_FLAGS", logfile)
     671            elif opt.omega:
     672                toolname = "exp-omega"
     673                flags = os.getenv("SAGE_OMEGA_FLAGS", logfile)
     674            cmd = "valgrind --tool=%s "%(toolname)
     675            if opt.omega:
     676                toolname = "omega"
     677            if "%s" in flags:
     678                flags %= toolname + ".%p" # replace %s with toolname
     679            flags += " "
     680        cmd += flags + sage_cmd
     681        self.log(cmd)
     682        if not testing:
     683            tm = time.time()
     684            import subprocess
     685            proc = subprocess.Popen(cmd, shell=True)
     686            while time.time()-tm <= opt.timeout and proc.poll() is None:
     687                time.sleep(0.1)
     688            if time.time() - tm >= opt.timeout:
     689                os.kill(proc.pid, 9)
     690                self.log("*** *** Error: TIMED OUT! PROCESS KILLED! *** ***")
     691            return proc.poll()
     692
     693    def run(self):
     694        """
     695        This function is called after initialization to set up and run all doctests.
     696
     697        EXAMPLES::
     698
     699            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     700            sage: import os
     701            sage: DD = DocTestDefaults()
     702            sage: filename = os.path.join(os.environ["SAGE_ROOT"], "devel", "sage", "sage", "sets", "non_negative_integers.py")
     703            sage: DC = DocTestController(DD, [filename])
     704            sage: DC.run()
     705            Running doctests with ID ...
     706            Doctesting 1 file.
     707            sage -t .../sage/sets/non_negative_integers.py
     708                [... tests, ... s]
     709            ------------------------------------------------------------------------
     710            All tests passed!
     711            ------------------------------------------------------------------------
     712            Total time for all tests: ... seconds
     713                cpu time: ... seconds
     714                cumulative wall time: ... seconds
     715            0
     716        """
     717        opt = self.options
     718        L = (opt.gdb, opt.valgrind, opt.massif, opt.cachegrind, opt.omega)
     719        if any(L):
     720            if L.count(True) > 1:
     721                self.log("You may only specify one of gdb, valgrind/memcheck, massif, cachegrind, omega")
     722                return 2
     723            return self.run_val_gdb()
     724        else:
     725            self.test_safe_directory()
     726            self.create_run_id()
     727            self.add_files()
     728            self.expand_files_into_sources()
     729            self.filter_sources()
     730            self.sort_sources()
     731            self.run_doctests()
     732            return self.reporter.error_status
     733
     734def run_doctests(module, options = None):
     735    """
     736    Runs the doctests in a given file.
     737
     738    INPUTS:
     739
     740    - ``module`` -- a Sage module, a string, or a list of such.
     741
     742    - ``options`` -- a DocTestDefaults object or None.
     743
     744    EXAMPLES::
     745
     746        sage: run_doctests(sage.rings.infinity)
     747        Doctesting .../sage/rings/infinity.py
     748        Running doctests with ID ...
     749        Doctesting 1 file.
     750        sage -t .../sage/rings/infinity.py
     751            [... tests, ... s]
     752        ------------------------------------------------------------------------
     753        All tests passed!
     754        ------------------------------------------------------------------------
     755        Total time for all tests: ... seconds
     756            cpu time: ... seconds
     757            cumulative wall time: ... seconds
     758    """
     759    import sys
     760    sys.stdout.flush()
     761    def stringify(x):
     762        if isinstance(x, (list, tuple)):
     763            F = [stringify(a) for a in x]
     764            return sage.misc.flatten.flatten(F)
     765        elif isinstance(x, types.ModuleType):
     766            F = re.sub(os.path.join("local","lib", r"python[0-9\.]*","site-packages"),os.path.join("devel","sage"),x.__file__)
     767            base, pyfile = os.path.split(F)
     768            file, ext = os.path.splitext(pyfile)
     769            if ext == ".pyc":
     770                ext = ".py"
     771            elif ext == ".so":
     772                ext = ".pyx"
     773            if file == "__init__":
     774                return [base]
     775            else:
     776                return [os.path.join(base, file) + ext]
     777        elif isinstance(x, basestring):
     778            return [os.path.abspath(x)]
     779    F = stringify(module)
     780    print "Doctesting %s"%(", ".join(F))
     781    if options is None:
     782        options = DocTestDefaults()
     783    DC = DocTestController(options, F)
     784    import sage.plot.plot
     785    save_dtmode = sage.plot.plot.DOCTEST_MODE
     786    try:
     787        DC.run()
     788    finally:
     789        sage.plot.plot.DOCTEST_MODE = save_dtmode
  • new file sage/doctest/forker.py

    diff --git a/sage/doctest/forker.py b/sage/doctest/forker.py
    new file mode 100644
    - +  
     1"""
     2This module controls the processes started by Sage that actually run
     3the doctests.
     4
     5EXAMPLES:
     6
     7The following examples are used in doctesting this file::
     8
     9    sage: doctest_var = 42; doctest_var^2
     10    1764
     11    sage: R.<a> = ZZ[]
     12    sage: a + doctest_var
     13    a + 42
     14
     15AUTHORS:
     16
     17- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
     18"""
     19
     20#*****************************************************************************
     21#       Copyright (C) 2012 David Roe <roed.math@gmail.com>
     22#                          Robert Bradshaw <robertwb@gmail.com>
     23#                          William Stein <wstein@gmail.com>
     24#
     25#  Distributed under the terms of the GNU General Public License (GPL)
     26#
     27#                  http://www.gnu.org/licenses/
     28#*****************************************************************************
     29
     30import hashlib, multiprocessing, os, sys, time, warnings, signal, subprocess, linecache
     31from collections import defaultdict
     32import doctest, pdb, traceback
     33from Queue import Empty
     34import sage.misc.randstate as randstate
     35from util import Timer, RecordingDict, count_noun
     36from sources import DictAsObject
     37from StringIO import StringIO
     38from parsing import OriginalSource, reduce_hex
     39from sage.structure.sage_object import SageObject
     40from parsing import SageOutputChecker, pre_hash, get_source
     41from sage.misc.misc import walltime
     42
     43################
     44from sage.structure.sage_object import dumps, loads
     45################
     46
     47debug_semaphore = None
     48output_semaphore = None
     49
     50def init_sage():
     51    """
     52    Import the Sage library.
     53
     54    This function is called once at the beginning of a doctest run
     55    (rather than once for each file).  It imports the Sage library,
     56    sets DOCTEST_MODE to True, and invalidates any interfaces.
     57
     58    EXAMPLES::
     59
     60        sage: from sage.doctest.forker import init_sage
     61        sage: sage.plot.plot.DOCTEST_MODE = False
     62        sage: init_sage()
     63        sage: sage.plot.plot.DOCTEST_MODE
     64        True
     65    """
     66    # Do this once before forking.
     67    import sage.all_cmdline
     68    sage.plot.plot; sage.plot.plot.DOCTEST_MODE=True
     69    sage.interfaces.quit.invalidate_all()
     70    import sage.misc.displayhook
     71    sys.displayhook = sage.misc.displayhook.DisplayHook(sys.displayhook)
     72
     73def warning_function(file):
     74    """
     75    Creates a function that prints warnings to the given file.
     76
     77    INPUT:
     78
     79    - ``file`` -- an open file handle.
     80
     81    OUPUT:
     82
     83    - a function that prings warnings to the given file.
     84
     85    EXAMPLES::
     86
     87        sage: from sage.doctest.forker import warning_function
     88        sage: import os
     89        sage: F = os.tmpfile()
     90        sage: wrn = warning_function(F)
     91        sage: wrn("bad stuff", UserWarning, "myfile.py", 0)
     92        sage: F.seek(0)
     93        sage: F.read()
     94        'doctest:0: UserWarning: bad stuff\n'
     95    """
     96    def doctest_showwarning(message, category, filename, lineno, file=file, line=None):
     97        try:
     98            file.write(warnings.formatwarning(message, category, 'doctest', lineno, line))
     99        except IOError:
     100            pass # the file (probably stdout) is invalid
     101    return doctest_showwarning
     102
     103def _test_activate_stdin():
     104    """
     105    This function is designed to test
     106    :meth:`SageSpoofOut.activate_stdin` and
     107    :meth:`SageSpoofOut.deactivate_stdin`.
     108
     109    TESTS::
     110
     111        sage: import os
     112        sage: os.environ['SAGE_PEXPECT_LOG'] = "1"
     113        sage: sage0.quit()
     114        sage: _ = sage0.eval("from sage.doctest.forker import _test_activate_stdin")
     115        sage: sage0._prompt = "my_prompt: "
     116        sage: print sage0.eval("_test_activate_stdin()")
     117        bad_input: input not available before activation
     118        sage: sage0._prompt = "sage: "
     119        sage: print sage0.eval("input active")
     120        input received: input active
     121        bad_input: input not available after deactivation
     122    """
     123    O = os.tmpfile()
     124    save_stdin = sys.stdin
     125    sys.stdin = open(os.devnull)
     126    v = None
     127    try:
     128        S = SageSpoofOut(O)
     129        try:
     130            w = raw_input("bad_input: ")
     131        except EOFError:
     132            print "input not available before activation"
     133        S.activate_stdin()
     134        v = raw_input("my_prompt: ")
     135    finally:
     136        print "input received:", v
     137        S.deactivate_stdin()
     138        try:
     139            w = raw_input("bad_input: ")
     140        except EOFError:
     141            print "input not available after deactivation"
     142        sys.stdin = save_stdin
     143
     144class SageSpoofOut(SageObject):
     145    """
     146    We replace the standard :class:`doctest._SpoofOut` for two reasons:
     147
     148    - we need to divert the output of C programs that don't print through sys.stdout,
     149    - we want the ability to recover partial output from doctest processes that segfault.
     150
     151    INPUT:
     152
     153    - ``outfile`` -- an open file handle (usually from os.tmpfile) to which stdout should be redirected.
     154
     155    EXAMPLES::
     156
     157        sage: import os
     158        sage: from sage.doctest.forker import SageSpoofOut
     159        sage: O = os.tmpfile()
     160        sage: S = SageSpoofOut(O)
     161        sage: try:
     162        ....:     S.start_spoofing()
     163        ....:     print "hello world"
     164        ....: finally:
     165        ....:     S.stop_spoofing()
     166        ....:
     167        sage: S.getvalue()
     168        'hello world\n'
     169    """
     170    def __init__(self, outfile):
     171        """
     172        Initialization.
     173
     174        TESTS::
     175
     176            sage: import os
     177            sage: from sage.doctest.forker import SageSpoofOut
     178            sage: SageSpoofOut(os.tmpfile())
     179            <class 'sage.doctest.forker.SageSpoofOut'>
     180        """
     181        self.outfile = outfile
     182        self.spoofing = False
     183        self.stdin_active = False #(sys.stdin.fileno() == 0)
     184        #self.stdin_active_original = self.stdin_active
     185        self.dup_stdout = os.dup(sys.stdout.fileno())
     186        self.dup_stderr = os.dup(sys.stderr.fileno())
     187        self.dup_stdin = os.dup(sys.stdin.fileno())
     188        self.position = 0
     189
     190    def fileno(self):
     191        """
     192        This object serves as stdout in various places, so we need to
     193        emulate stdout's fileno in order to be able to recurse.
     194
     195        EXAMPLES::
     196
     197            sage: import os
     198            sage: from sage.doctest.forker import SageSpoofOut
     199            sage: O = os.tmpfile()
     200            sage: S = SageSpoofOut(O)
     201            sage: S.fileno() == 1
     202            True
     203        """
     204        return sys.stdout.fileno()
     205
     206    def start_spoofing(self):
     207        """
     208        Set stdout to print to outfile.
     209
     210        EXAMPLES::
     211
     212            sage: import os
     213            sage: from sage.doctest.forker import SageSpoofOut
     214            sage: O = os.tmpfile()
     215            sage: S = SageSpoofOut(O)
     216            sage: try:
     217            ....:     S.start_spoofing()
     218            ....:     print "this is not printed"
     219            ....: finally:
     220            ....:     S.stop_spoofing()
     221            ....:
     222            sage: S.getvalue()
     223            'this is not printed\n'
     224
     225        We also catch C output::
     226
     227            sage: try:
     228            ....:     S.start_spoofing()
     229            ....:     retval = os.system('''echo "Hello there"\nif [ $? -eq 0 ]; then\necho "good"\nfi''')
     230            ....: finally:
     231            ....:     S.stop_spoofing()
     232            ....:
     233            sage: S.getvalue()
     234            'Hello there\ngood\n'
     235        """
     236        if not self.spoofing:
     237            sys.stdout.flush()
     238            os.dup2(self.outfile.fileno(), sys.stdout.fileno())
     239            sys.stderr.flush()
     240            os.dup2(self.outfile.fileno(), sys.stderr.fileno())
     241            self.spoofing = True
     242
     243    def stop_spoofing(self):
     244        """
     245        Reset stdout to its original value.
     246
     247        EXAMPLES::
     248
     249            sage: import os
     250            sage: from sage.doctest.forker import SageSpoofOut
     251            sage: O = os.tmpfile()
     252            sage: S = SageSpoofOut(O)
     253            sage: try:
     254            ....:     S.start_spoofing()
     255            ....:     print "this is not printed"
     256            ....: finally:
     257            ....:     S.stop_spoofing()
     258            ....:
     259            sage: print "this is now printed"
     260            this is now printed
     261        """
     262        if self.spoofing:
     263            sys.stdout.flush()
     264            os.dup2(self.dup_stdout, sys.stdout.fileno())
     265            sys.stderr.flush()
     266            os.dup2(self.dup_stderr, sys.stderr.fileno())
     267            self.spoofing = False
     268
     269    def getvalue(self):
     270        """
     271        Gets the value that has been printed to the outfile since the last time this function was called.
     272
     273        EXAMPLES::
     274
     275            sage: import os
     276            sage: from sage.doctest.forker import SageSpoofOut
     277            sage: O = os.tmpfile()
     278            sage: S = SageSpoofOut(O)
     279            sage: try:
     280            ....:     S.start_spoofing()
     281            ....:     print "step 1"
     282            ....: finally:
     283            ....:     S.stop_spoofing()
     284            ....:
     285            sage: S.getvalue()
     286            'step 1\n'
     287            sage: try:
     288            ....:     S.start_spoofing()
     289            ....:     print "step 2"
     290            ....: finally:
     291            ....:     S.stop_spoofing()
     292            ....:
     293            sage: S.getvalue()
     294            'step 2\n'
     295        """
     296        sys.stdout.flush()
     297        self.outfile.seek(self.position)
     298        result = self.outfile.read()
     299        self.position = self.outfile.tell()
     300        if not result.endswith("\n"):
     301            result += "\n"
     302        return result
     303
     304    def write(self, a):
     305        """
     306        When debugging we need to pretend this object is stdout.
     307
     308        EXAMPLES::
     309
     310            sage: import os
     311            sage: from sage.doctest.forker import SageSpoofOut
     312            sage: O = os.tmpfile()
     313            sage: S = SageSpoofOut(O)
     314            sage: try:
     315            ....:     S.start_spoofing()
     316            ....:     S.write("this is not printed\n")
     317            ....: finally:
     318            ....:     S.stop_spoofing()
     319            ....:
     320            sage: S.getvalue()
     321            'this is not printed\n'
     322        """
     323        sys.stdout.write(a)
     324        sys.stdout.flush()
     325
     326    def unspoofed_write(self, a):
     327        """
     328        Prints to the unspoofed standard out.
     329
     330        EXAMPLES::
     331
     332            sage: import os
     333            sage: from sage.doctest.forker import SageSpoofOut
     334            sage: O = os.tmpfile()
     335            sage: S = SageSpoofOut(O)
     336            sage: try:
     337            ....:     S.start_spoofing()
     338            ....:     print "spoofed"
     339            ....:     S.unspoofed_write("unspoofed\n")
     340            ....: finally:
     341            ....:     S.stop_spoofing()
     342            ....:
     343            unspoofed
     344            sage: S.getvalue()
     345            'spoofed\n'
     346        """
     347        spoofed = self.spoofing
     348        try:
     349            if output_semaphore is not None:
     350                output_semaphore.acquire()
     351            if spoofed:
     352                self.stop_spoofing()
     353            sys.stdout.write(a)
     354        finally:
     355            sys.stdout.flush()
     356            if spoofed:
     357                self.start_spoofing()
     358            if output_semaphore is not None:
     359                output_semaphore.release()
     360
     361    def activate_stdin(self):
     362        """
     363        Turns on stdin, which is useful when user input is needed in a subprocess.
     364
     365        TESTS::
     366
     367            sage: import os
     368            sage: os.environ['SAGE_PEXPECT_LOG'] = "1"
     369            sage: sage0.quit()
     370            sage: _ = sage0.eval("from sage.doctest.forker import _test_activate_stdin")
     371            sage: sage0._prompt = "my_prompt: "
     372            sage: print sage0.eval("_test_activate_stdin()")
     373            bad_input: input not available before activation
     374            sage: sage0._prompt = "sage: "
     375            sage: print sage0.eval("input active") # indirect doctest
     376            input received: input active
     377            bad_input: input not available after deactivation
     378        """
     379        if not self.stdin_active:
     380            os.dup2(0, sys.stdin.fileno())
     381            self.stdin_active = True
     382
     383    def deactivate_stdin(self):
     384        """
     385        Restores stdin to its original state (which ignores user input
     386        if this function is being called in a subprocess).
     387
     388        TESTS::
     389
     390            sage: import os
     391            sage: os.environ['SAGE_PEXPECT_LOG'] = "1"
     392            sage: sage0.quit()
     393            sage: _ = sage0.eval("from sage.doctest.forker import _test_activate_stdin")
     394            sage: sage0._prompt = "my_prompt: "
     395            sage: print sage0.eval("_test_activate_stdin()")
     396            bad_input: input not available before activation
     397            sage: sage0._prompt = "sage: "
     398            sage: print sage0.eval("input active") # indirect doctest
     399            input received: input active
     400            bad_input: input not available after deactivation
     401        """
     402        if self.stdin_active:
     403            os.dup2(self.dup_stdin, sys.stdin.fileno())
     404            self.stdin_active = False
     405
     406class SagePdb(doctest._OutputRedirectingPdb):
     407    """
     408    We subclass :class:`doctest._OutputRedirectingPdb` since switching
     409    in and out of spoofing can't be done just be assigning to
     410    sys.stdout.
     411
     412    INPUT:
     413
     414    - ``fakeout`` -- a :class:`SageSpoofOut` instance
     415
     416    EXAMPLES::
     417
     418        sage: from sage.doctest.forker import SageSpoofOut, SagePdb
     419        sage: import os; O = os.tmpfile()
     420        sage: S = SageSpoofOut(O)
     421        sage: SagePdb(S)
     422        <sage.doctest.forker.SagePdb instance at ...>
     423    """
     424    def __init__(self, fakeout):
     425        """
     426        Initialization.
     427
     428        TESTS::
     429
     430            sage: from sage.doctest.forker import SageSpoofOut, SagePdb
     431            sage: import os; O = os.tmpfile()
     432            sage: S = SageSpoofOut(O)
     433            sage: SagePdb(S).use_rawinput
     434            1
     435        """
     436        self._fakeout = fakeout
     437        doctest._OutputRedirectingPdb.__init__(self, fakeout)
     438
     439    def trace_dispatch(self, *args):
     440        """
     441        We stop spoofing before calling :meth:`pdb.Pdb.trace_dispatch` and start again afterward.
     442
     443        TESTS::
     444
     445            sage: import os
     446            sage: os.environ['SAGE_PEXPECT_LOG'] = "1"
     447            sage: sage0.quit()
     448            sage: _ = sage0.eval("import doctest, sys, os, multiprocessing, subprocess")
     449            sage: _ = sage0.eval("from sage.doctest.parsing import SageOutputChecker")
     450            sage: _ = sage0.eval("import sage.doctest.forker as sdf")
     451            sage: _ = sage0.eval("from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()")
     452            sage: _ = sage0.eval("ex = doctest.Example('E = EllipticCurve([0,0]); E', 'A singular Elliptic Curve')")
     453            sage: _ = sage0.eval("DT = doctest.DocTest([ex], globals(), 'singular_curve', None, 0, None)")
     454            sage: _ = sage0.eval("DTR = sdf.SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)")
     455            sage: _ = sage0.eval("sdf.debug_semaphore = multiprocessing.Semaphore()")
     456            sage: _ = sage0.eval("sdf.output_semaphore = multiprocessing.Semaphore()")
     457            sage: sage0._prompt = r"\(Pdb\) "
     458            sage: sage0.eval("DTR.run(DT, clear_globs=False)") # indirect doctest
     459            '... "Invariants %s define a singular curve."%ainvs'
     460            sage: sage0._prompt = "sage: "
     461            sage: tb = sage0.eval("quit")
     462        """
     463        if self._fakeout.spoofing:
     464            self._fakeout.stop_spoofing()
     465            try:
     466                return pdb.Pdb.trace_dispatch(self, *args)
     467            finally:
     468                self._fakeout.start_spoofing()
     469        else:
     470            return pdb.Pdb.trace_dispatch(self, *args)
     471
     472class SageDocTestRunner(doctest.DocTestRunner):
     473    def __init__(self, *args, **kwds):
     474        """
     475        A customized version of DocTestRunner that tracs dependencies of doctests.
     476
     477        INPUT:
     478
     479        - ``stdout`` -- an open file to restore for debugging
     480
     481        - ``checker`` -- None, or an instance of
     482          :class:`doctest.OutputChecker`
     483
     484        - ``verbose`` -- boolean, determines whether verbose printing
     485          is enabled.
     486
     487        - ``optionflags`` -- Controls the comparison with the expected
     488          output.  See :mod:`testmod` for more information.
     489
     490        EXAMPLES::
     491
     492            sage: from sage.doctest.parsing import SageOutputChecker
     493            sage: from sage.doctest.forker import SageDocTestRunner
     494            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     495            sage: import doctest, sys, os
     496            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     497            sage: DTR
     498            <sage.doctest.forker.SageDocTestRunner instance at ...>
     499        """
     500        O = kwds.pop('output')
     501        self.options = kwds.pop('sage_options')
     502        doctest.DocTestRunner.__init__(self, *args, **kwds)
     503        self._fakeout = SageSpoofOut(O)
     504        self.history = []
     505        self.references = []
     506        self.setters = {}
     507        self.running_global_digest = hashlib.md5()
     508        self.delayed_output = []
     509
     510    def _run(self, test, compileflags, out):
     511        """
     512        This function replaces :meth:`doctest.DocTestRunner.__run`.
     513
     514        It changes the following behavior:
     515
     516        - We call :meth:`SageDocTestRunner.execute` rather than just
     517          exec
     518
     519        - We don't truncate _fakeout after each example since we want
     520          the output file to be readable by the calling
     521          :class:`SageWorker`.
     522
     523        Since it needs to be able to read stdout, it should be called
     524        while spoofing using :class:`SageSpoofOut`.
     525
     526        EXAMPLES::
     527
     528            sage: from sage.doctest.parsing import SageOutputChecker
     529            sage: from sage.doctest.forker import SageDocTestRunner
     530            sage: from sage.doctest.sources import FileDocTestSource
     531            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     532            sage: import doctest, sys, os
     533            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     534            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     535            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     536            sage: doctests, extras = FDS.create_doctests(globals())
     537            sage: DTR.run(doctests[0], clear_globs=False) # indirect doctest
     538            TestResults(failed=0, attempted=4)
     539        """
     540        # Keep track of the number of failures and tries.
     541        failures = tries = 0
     542
     543        # Save the option flags (since option directives can be used
     544        # to modify them).
     545        original_optionflags = self.optionflags
     546
     547        SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
     548
     549        check = self._checker.check_output
     550
     551        # Process each example.
     552        for examplenum, example in enumerate(test.examples):
     553
     554            # If REPORT_ONLY_FIRST_FAILURE is set, then suppress
     555            # reporting after the first failure.
     556            quiet = (self.optionflags & doctest.REPORT_ONLY_FIRST_FAILURE and
     557                     failures > 0)
     558
     559            # Merge in the example's options.
     560            self.optionflags = original_optionflags
     561            if example.options:
     562                for (optionflag, val) in example.options.items():
     563                    if val:
     564                        self.optionflags |= optionflag
     565                    else:
     566                        self.optionflags &= ~optionflag
     567
     568            # If 'SKIP' is set, then skip this example.
     569            if self.optionflags & doctest.SKIP:
     570                continue
     571
     572            # Record that we started this example.
     573            tries += 1
     574            # We print the example we're running for easier debugging
     575            # if this file times out or sefaults.
     576            with OriginalSource(example):
     577                print "sage: " + example.source[:-1] + " ## line %s ##"%(test.lineno + example.lineno + 1)
     578            # Update the position so that result comparison works
     579            throwaway = self._fakeout.getvalue()
     580            if not quiet:
     581                self.report_start(out, test, example)
     582
     583            # Use a special filename for compile(), so we can retrieve
     584            # the source code during interactive debugging (see
     585            # __patched_linecache_getlines).
     586            filename = '<doctest %s[%d]>' % (test.name, examplenum)
     587
     588            # Run the example in the given context (globs), and record
     589            # any exception that gets raised.  (But don't intercept
     590            # keyboard interrupts.)
     591            try:
     592                # Don't blink!  This is where the user's code gets run.
     593                compiled = compile(example.source, filename, "single",
     594                             compileflags, 1)
     595                self.execute(example, compiled, test.globs)
     596                self.debugger.set_continue() # ==== Example Finished ====
     597                exception = None
     598            except KeyboardInterrupt:
     599                # We check to see if it's expected
     600                exception = sys.exc_info()
     601                exc_msg = traceback.format_exception_only(*exception[:2])[-1]
     602                if example.exc_msg is None or not check(example.exc_msg, exc_msg, self.optionflags):
     603                    raise
     604                else:
     605                    # KeyboardInterrupt was expected
     606                    self.debugger.set_continue()
     607            except Exception:
     608                exception = sys.exc_info()
     609                self.debugger.set_continue() # ==== Example Finished ====
     610
     611            got = self._fakeout.getvalue()  # the actual output
     612            outcome = FAILURE   # guilty until proved innocent or insane
     613
     614            # If the example executed without raising any exceptions,
     615            # verify its output.
     616            if exception is None:
     617                if check(example.want, got, self.optionflags):
     618                    outcome = SUCCESS
     619
     620            # The example raised an exception:  check if it was expected.
     621            else:
     622                exc_info = sys.exc_info()
     623                exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
     624                if not quiet:
     625                    got += doctest._exception_traceback(exc_info)
     626
     627                # If `example.exc_msg` is None, then we weren't expecting
     628                # an exception.
     629                if example.exc_msg is None:
     630                    outcome = BOOM
     631
     632                # We expected an exception:  see whether it matches.
     633                elif check(example.exc_msg, exc_msg, self.optionflags):
     634                    outcome = SUCCESS
     635
     636                # Another chance if they didn't care about the detail.
     637                elif self.optionflags & doctest.IGNORE_EXCEPTION_DETAIL:
     638                    m1 = re.match(r'(?:[^:]*\.)?([^:]*:)', example.exc_msg)
     639                    m2 = re.match(r'(?:[^:]*\.)?([^:]*:)', exc_msg)
     640                    if m1 and m2 and check(m1.group(1), m2.group(1),
     641                                           self.optionflags):
     642                        outcome = SUCCESS
     643
     644            # Report the outcome.
     645            if outcome is SUCCESS:
     646                if self.options.warn_long and walltime(self.tmp_time) > self.options.warn_long:
     647                    self.report_overtime(out, test, example, got)
     648                    failures += 1
     649                elif not quiet:
     650                    self.report_success(out, test, example, got)
     651            elif outcome is FAILURE:
     652                if not quiet:
     653                    self.report_failure(out, test, example, got, test.globs)
     654                failures += 1
     655            elif outcome is BOOM:
     656                if not quiet:
     657                    self.report_unexpected_exception(out, test, example,
     658                                                     exc_info)
     659                failures += 1
     660            else:
     661                assert False, ("unknown outcome", outcome)
     662
     663        # Restore the option flags (in case they were modified)
     664        self.optionflags = original_optionflags
     665
     666        # Record and return the number of failures and tries.
     667        self._DocTestRunner__record_outcome(test, failures, tries)
     668        return doctest.TestResults(failures, tries)
     669
     670    def run(self, test, compileflags=None, out=None, clear_globs=True):
     671        """
     672        Runs the examples in a given doctest.
     673
     674        This function replaces :class:`doctest.DocTestRunner.run`
     675        since it needs to handle spoofing and the output redirecting
     676        Pdb differently.  It also leaves the display hook in place.
     677
     678        INPUT:
     679
     680        - ``test`` -- an instance of :class:`doctest.DocTest`
     681
     682        - ``compileflags`` -- the set of compiler flags used to
     683          execute examples (passed in to the :func:`compile`).  If
     684          None, they are filled in from the result of
     685          :func:`doctest._extract_future_flags` applied to
     686          ``test.globs``.
     687
     688        - ``out`` -- a function for writing the output (defaults to
     689          :func:`sys.stdout.write`).
     690
     691        - ``clear_globs`` -- boolean (default True): whether to clear
     692          the namespace after running this doctest.
     693
     694        OUTPUT:
     695
     696        - ``f`` -- integer, the number of examples that failed
     697
     698        - ``t`` -- the number of examples tried
     699
     700        EXAMPLES::
     701
     702            sage: from sage.doctest.parsing import SageOutputChecker
     703            sage: from sage.doctest.forker import SageDocTestRunner
     704            sage: from sage.doctest.sources import FileDocTestSource
     705            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     706            sage: import doctest, sys, os
     707            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     708            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     709            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     710            sage: doctests, extras = FDS.create_doctests(globals())
     711            sage: DTR.run(doctests[0], clear_globs=False)
     712            TestResults(failed=0, attempted=4)
     713        """
     714        self.setters = {}
     715        randstate.set_random_seed(long(0))
     716        warnings.showwarning = warning_function(sys.stdout)
     717        self.running_doctest_digest = hashlib.md5()
     718        self.test = test
     719        if compileflags is None:
     720            compileflags = doctest._extract_future_flags(test.globs)
     721        save_set_trace = pdb.set_trace
     722        self.debugger = SagePdb(self._fakeout)
     723        self.debugger.reset()
     724        pdb.set_trace = self.debugger.set_trace
     725        self.save_linecache_getlines = linecache.getlines
     726        linecache.getlines = self._DocTestRunner__patched_linecache_getlines
     727        if out is None and (self.options.serial or self.options.verbose or self.options.debug):
     728            out = self._fakeout.unspoofed_write
     729        elif out is None:
     730            def out(s):
     731                self.delayed_output.append(s)
     732
     733        self._fakeout.start_spoofing()
     734        # If self.options.initial is set, we show only the first failure in each doctest block.
     735        self.no_failure_yet = True
     736        try:
     737            return self._run(test, compileflags, out)
     738        finally:
     739            self._fakeout.stop_spoofing()
     740            pdb.set_trace = save_set_trace
     741            linecache.getlines = self.save_linecache_getlines
     742            if clear_globs:
     743                test.globs.clear()
     744
     745    def summarize(self, verbose=None, delay_list=None):
     746        """
     747        Returns a string summarizing the results of testing.
     748
     749        Summarize works a bit differently for Sage testing since we
     750        want to continue delaying output so that it is printed at the
     751        same time as
     752        :meth:`sage.doctest.reporting.DocTestReporter.report`, and we
     753        want to log it.
     754
     755        INPUT:
     756
     757        - ``verbose`` -- whether to print lots of stuff
     758
     759        - ``delay_list`` -- either None or a list of strings on which to append delayed output
     760
     761        OUTPUT:
     762
     763        - if ``delay_list`` is None, prints a summary of tests run.
     764          Otherwise, appends lines of the summary to ``delay_list``.
     765
     766        - returns ``(f, t)``, a :class:`doctest.TestResults` instance
     767          giving the number of failures and the total number of tests
     768          run.
     769
     770        EXAMPLES::
     771
     772            sage: from sage.doctest.parsing import SageOutputChecker
     773            sage: from sage.doctest.forker import SageDocTestRunner
     774            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     775            sage: import doctest, sys, os
     776            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     777            sage: DTR.delayed_output = ["A delayed failure\n","And another one\n"]
     778            sage: DTR._name2ft['sage.doctest.forker'] = (1,120)
     779            sage: DTR.summarize()
     780            A delayed failure
     781            And another one
     782            **********************************************************************
     783            1 item had failures:
     784                1 of 120 in sage.doctest.forker
     785            TestResults(failed=1, attempted=120)
     786        """
     787        if verbose is None:
     788            verbose = self._verbose
     789        print_now = delay_list is None
     790        if delay_list is None:
     791            delay_list = []
     792        delay_list.extend(self.delayed_output)
     793        notests = []
     794        passed = []
     795        failed = []
     796        totalt = totalf = 0
     797        for x in self._name2ft.items():
     798            name, (f, t) = x
     799            assert f <= t
     800            totalt += t
     801            totalf += f
     802            if not t:
     803                notests.append(name)
     804            elif not f:
     805                passed.append( (name, t) )
     806            else:
     807                failed.append(x)
     808        if verbose:
     809            if notests:
     810                delay_list.append(count_noun(len(notests), "item") + " had no tests:\n")
     811                notests.sort()
     812                for thing in notests:
     813                    delay_list.append("    %s\n"%thing)
     814            if passed:
     815                delay_list.append(count_noun(len(passed), "item") + " passed all tests:\n")
     816                passed.sort()
     817                for thing, count in passed:
     818                    delay_list.append(" %s in %s\n"%(count_noun(count, "test", pad_number=3, pad_noun=True), thing))
     819        if failed:
     820            delay_list.append(self.DIVIDER + "\n")
     821            delay_list.append(count_noun(len(failed), "item") + " had failures:\n")
     822            failed.sort()
     823            for thing, (f, t) in failed:
     824                delay_list.append(" %3d of %3d in %s\n"%(f, t, thing))
     825        if verbose:
     826            delay_list.append(count_noun(totalt, "test") + " in " + count_noun(len(self._name2ft), "item") + ".\n")
     827            delay_list.append("%s passed and %s failed.\n"%(totalt - totalf, totalf))
     828            if totalf:
     829                delay_list.append("***Test Failed***\n")
     830            else:
     831                delay_list.append("Test passed.\n")
     832        if print_now:
     833            delayed_output = "".join(delay_list)
     834            if self.options.logfile is not None:
     835                try:
     836                    with open(self.options.logfile, 'a') as logger:
     837                        logger.write(delayed_output)
     838                except IOError:
     839                    pass
     840            sys.stdout.write(delayed_output)
     841            sys.stdout.flush()
     842        return doctest.TestResults(totalf, totalt)
     843
     844    def update_digests(self, example):
     845        """
     846        Update global and doctest digests.
     847
     848        Sage's doctest runner tracks the state of doctests so that
     849        their dependencies are known.  For example, in the following
     850        two lines ::
     851
     852            sage: R.<x> = ZZ[]
     853            sage: f = x^2 + 1
     854
     855        it records that the second line depends on the first since the
     856        first INSERTS ``x`` into the global namespace and the second
     857        line RETRIEVES ``x`` from the global namespace.
     858
     859        This function updates the hashes that record these
     860        dependencies.
     861
     862        INPUT:
     863
     864        - ``example`` -- a :class:`doctest.Example` instance
     865
     866        EXAMPLES::
     867
     868            sage: from sage.doctest.parsing import SageOutputChecker
     869            sage: from sage.doctest.forker import SageDocTestRunner
     870            sage: from sage.doctest.sources import FileDocTestSource
     871            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     872            sage: import doctest, sys, os, hashlib
     873            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     874            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     875            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     876            sage: doctests, extras = FDS.create_doctests(globals())
     877            sage: DTR.running_global_digest.hexdigest()
     878            'd41d8cd98f00b204e9800998ecf8427e'
     879            sage: DTR.running_doctest_digest = hashlib.md5()
     880            sage: ex = doctests[0].examples[0]; ex.predecessors = None
     881            sage: DTR.update_digests(ex)
     882            sage: DTR.running_global_digest.hexdigest()
     883            '3cb44104292c3a3ab4da3112ce5dc35c'
     884        """
     885        s = pre_hash(get_source(example))
     886        self.running_global_digest.update(s)
     887        self.running_doctest_digest.update(s)
     888        if example.predecessors is not None:
     889            digest = hashlib.md5(s)
     890            digest.update(reduce_hex(e.running_state for e in example.predecessors))
     891            example.running_state = digest.hexdigest()
     892
     893    def execute(self, example, compiled, globs):
     894        """
     895        Runs the given example, recording dependencies.
     896
     897        Rather than using a basic dictionary, Sage's doctest runner
     898        uses a :class:`sage.doctest.util.RecordingDict`, which records
     899        every time a value is set or retrieved.  Executing the given
     900        code with this recording dictionary as the namespace allows
     901        Sage to track dependencies between doctest lines.  For
     902        example, in the following two lines ::
     903
     904            sage: R.<x> = ZZ[]
     905            sage: f = x^2 + 1
     906
     907        the recording dictionary records that the second line depends
     908        on the first since the first INSERTS ``x`` into the global
     909        namespace and the second line RETRIEVES ``x`` from the global
     910        namespace.
     911
     912        INPUT:
     913
     914        - ``example`` -- a :class:`doctest.Example` instance.
     915        - ``compiled`` -- a code object produced by compiling ``example.source``
     916        - ``globs`` -- a dictionary in which to execute ``compiled``.
     917
     918        OUTPUT:
     919
     920        - the output of the compiled code snippet.
     921
     922        EXAMPLES::
     923
     924            sage: from sage.doctest.parsing import SageOutputChecker
     925            sage: from sage.doctest.forker import SageDocTestRunner
     926            sage: from sage.doctest.sources import FileDocTestSource
     927            sage: from sage.doctest.util import RecordingDict
     928            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     929            sage: import doctest, sys, os, hashlib
     930            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     931            sage: DTR.running_doctest_digest = hashlib.md5()
     932            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     933            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     934            sage: globs = RecordingDict(globals())
     935            sage: globs.has_key('doctest_var')
     936            False
     937            sage: doctests, extras = FDS.create_doctests(globs)
     938            sage: ex0 = doctests[0].examples[0]
     939            sage: compiled = compile(ex0.source, '<doctest sage.doctest.forker[0]>', 'single', 32768, 1)
     940            sage: DTR.execute(ex0, compiled, globs)
     941            1764
     942            sage: globs['doctest_var']
     943            42
     944            sage: globs.set
     945            set(['doctest_var'])
     946            sage: globs.got
     947            set(['Integer'])
     948
     949        Now we can execute some more doctests to see the dependencies.
     950
     951            sage: ex1 = doctests[0].examples[1]
     952            sage: compiled = compile(ex1.source, '<doctest sage.doctest.forker[1]>', 'single', 32768, 1)
     953            sage: DTR.execute(ex1, compiled, globs)
     954            sage: sorted(list(globs.set))
     955            ['R', 'a']
     956            sage: globs.got
     957            set(['ZZ'])
     958            sage: ex1.predecessors
     959            []
     960
     961        ::
     962
     963            sage: ex2 = doctests[0].examples[2]
     964            sage: compiled = compile(ex2.source, '<doctest sage.doctest.forker[2]>', 'single', 32768, 1)
     965            sage: DTR.execute(ex2, compiled, globs)
     966            a + 42
     967            sage: list(globs.set)
     968            []
     969            sage: sorted(list(globs.got))
     970            ['a', 'doctest_var']
     971            sage: set(ex2.predecessors) == set([ex0,ex1])
     972            True
     973        """
     974        if isinstance(globs, RecordingDict):
     975            globs.start()
     976        example.sequence_number = len(self.history)
     977        self.history.append(example)
     978        timer = Timer().start()
     979        try:
     980            exec compiled in globs
     981        finally:
     982            timer.stop().annotate(example)
     983            if isinstance(globs, RecordingDict):
     984                example.predecessors = []
     985                for name in globs.got:
     986                    ref = self.setters.get(name)
     987                    if ref is not None:
     988                        example.predecessors.append(ref)
     989                for name in globs.set:
     990                    self.setters[name] = example
     991            else:
     992                example.predecessors = None
     993            self.update_digests(example)
     994            example.total_state = self.running_global_digest.hexdigest()
     995            example.doctest_state = self.running_doctest_digest.hexdigest()
     996
     997    def _failure_header(self, test, example):
     998        """
     999        We strip out ``sage:`` prompts, so we override
     1000        :meth:`doctest.DocTestRunner._failure_header` for better
     1001        reporting.
     1002
     1003        INPUT:
     1004
     1005        - ``test`` -- a :class:`doctest.DocTest` instance
     1006
     1007        - ``example`` -- a :class:`doctest.Example` instance in ``test``.
     1008
     1009        OUTPUT:
     1010
     1011        - a string used for reporting that the given example failed.
     1012
     1013        EXAMPLES::
     1014
     1015            sage: from sage.doctest.parsing import SageOutputChecker
     1016            sage: from sage.doctest.forker import SageDocTestRunner
     1017            sage: from sage.doctest.sources import FileDocTestSource
     1018            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     1019            sage: import doctest, sys, os
     1020            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     1021            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     1022            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1023            sage: doctests, extras = FDS.create_doctests(globals())
     1024            sage: ex = doctests[0].examples[0]
     1025            sage: print DTR._failure_header(doctests[0], ex)
     1026            **********************************************************************
     1027            File ".../sage/doctest/forker.py", line 9, in sage.doctest.forker
     1028            Failed example:
     1029                doctest_var = 42; doctest_var^2
     1030            <BLANKLINE>
     1031
     1032       Without the source swapping::
     1033
     1034            sage: import doctest
     1035            sage: print doctest.DocTestRunner._failure_header(DTR, doctests[0], ex)
     1036            **********************************************************************
     1037            File ".../sage/doctest/forker.py", line 9, in sage.doctest.forker
     1038            Failed example:
     1039                doctest_var = Integer(42); doctest_var**Integer(2)
     1040            <BLANKLINE>
     1041        """
     1042        with OriginalSource(example):
     1043            return doctest.DocTestRunner._failure_header(self, test, example)
     1044
     1045    def report_start(self, out, test, example):
     1046        """
     1047        Called when an example starts.
     1048
     1049        INPUT:
     1050
     1051        - ``out`` -- a function for printing (delayed or immediate)
     1052
     1053        - ``test`` -- a :class:`doctest.DocTest` instance
     1054
     1055        - ``example`` -- a :class:`doctest.Example` instance in ``test``
     1056
     1057        OUTPUT:
     1058
     1059        - prints a report to ``out``
     1060
     1061        EXAMPLES::
     1062
     1063            sage: from sage.doctest.parsing import SageOutputChecker
     1064            sage: from sage.doctest.forker import SageDocTestRunner
     1065            sage: from sage.doctest.sources import FileDocTestSource
     1066            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     1067            sage: import doctest, sys, os
     1068            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     1069            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     1070            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1071            sage: doctests, extras = FDS.create_doctests(globals())
     1072            sage: ex = doctests[0].examples[0]
     1073            sage: DTR.report_start(sys.stdout.write, doctests[0], ex)
     1074            Trying (line 9):    doctest_var = 42; doctest_var^2
     1075            Expecting:
     1076                1764
     1077        """
     1078        # We completely replace doctest.DocTestRunner.report_start so that we can include line numbers
     1079        with OriginalSource(example):
     1080            if self._verbose:
     1081                start_txt = ('Trying (line %s):'%(test.lineno + example.lineno + 1)
     1082                             + doctest._indent(example.source))
     1083                if example.want:
     1084                    start_txt += 'Expecting:\n' + doctest._indent(example.want)
     1085                else:
     1086                    start_txt += 'Expecting nothing\n'
     1087                out(start_txt)
     1088            if self._verbose or self.options.warn_long:
     1089                self.tmp_time = walltime()
     1090
     1091    def report_success(self, out, test, example, got):
     1092        """
     1093        Called when an example succeeds.
     1094
     1095        INPUT:
     1096
     1097        - ``out`` -- a function for printing (delayed or immediate)
     1098
     1099        - ``test`` -- a :class:`doctest.DocTest` instance
     1100
     1101        - ``example`` -- a :class:`doctest.Example` instance in ``test``
     1102
     1103        - ``got`` -- a string, the result of running ``example``
     1104
     1105        OUTPUT:
     1106
     1107        - prints a report to ``out``
     1108
     1109        EXAMPLES::
     1110
     1111            sage: from sage.doctest.parsing import SageOutputChecker
     1112            sage: from sage.doctest.forker import SageDocTestRunner
     1113            sage: from sage.doctest.sources import FileDocTestSource
     1114            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     1115            sage: from sage.misc.misc import walltime
     1116            sage: import doctest, sys, os
     1117            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     1118            sage: DTR.tmp_time = walltime()
     1119            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     1120            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1121            sage: doctests, extras = FDS.create_doctests(globals())
     1122            sage: ex = doctests[0].examples[0]
     1123            sage: DTR.report_success(sys.stdout.write, doctests[0], ex, '1764')
     1124            ok [...s]
     1125        """
     1126        # We completely replace doctest.DocTestRunner.report_success so that we can include time taken for the test
     1127        if self._verbose:
     1128            out("ok [%.2f s]\n"%(walltime(self.tmp_time)))
     1129
     1130    # We name the local strangely since they get imported into
     1131    def report_failure(self, out, test, example, got, globs):
     1132        """
     1133        Called when a doctest fails.
     1134
     1135        INPUT:
     1136
     1137        - ``out`` -- a function for printing (delayed or immediate)
     1138
     1139        - ``test`` -- a :class:`doctest.DocTest` instance
     1140
     1141        - ``example`` -- a :class:`doctest.Example` instance in ``test``
     1142
     1143        - ``got`` -- a string, the result of running ``example``
     1144
     1145        - ``globs`` -- a dictionary of globals, used if in debugging mode
     1146
     1147        OUTPUT:
     1148
     1149        - prints a report to ``out``
     1150
     1151        - if in debugging mode, starts an iPython prompt at the point
     1152          of the failure
     1153
     1154        EXAMPLES::
     1155
     1156            sage: from sage.doctest.parsing import SageOutputChecker
     1157            sage: from sage.doctest.forker import SageDocTestRunner
     1158            sage: from sage.doctest.sources import FileDocTestSource
     1159            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     1160            sage: import doctest, sys, os
     1161            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     1162            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     1163            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1164            sage: doctests, extras = FDS.create_doctests(globals())
     1165            sage: ex = doctests[0].examples[0]
     1166            sage: DTR.no_failure_yet = True
     1167            sage: DTR.report_failure(sys.stdout.write, doctests[0], ex, 'BAD ANSWER\n', {})
     1168            **********************************************************************
     1169            File ".../sage/doctest/forker.py", line 9, in sage.doctest.forker
     1170            Failed example:
     1171                doctest_var = 42; doctest_var^2
     1172            Expected:
     1173                1764
     1174            Got:
     1175                BAD ANSWER
     1176        """
     1177        if not self.options.initial or self.no_failure_yet:
     1178            self.no_failure_yet = False
     1179            doctest.DocTestRunner.report_failure(self, out, test, example, got)
     1180
     1181    def report_overtime(self, out, test, example, got):
     1182        """
     1183        Called when the ``warn_long`` option flag is set and a doctest
     1184        runs longer than the specified time.
     1185
     1186        INPUT:
     1187
     1188        - ``out`` -- a function for printing (delayed or immediate)
     1189
     1190        - ``test`` -- a :class:`doctest.DocTest` instance
     1191
     1192        - ``example`` -- a :class:`doctest.Example` instance in ``test``
     1193
     1194        - ``got`` -- a string, the result of running ``example``
     1195
     1196        OUTPUT:
     1197
     1198        - prints a report to ``out``
     1199
     1200        EXAMPLES::
     1201
     1202            sage: from sage.doctest.parsing import SageOutputChecker
     1203            sage: from sage.doctest.forker import SageDocTestRunner
     1204            sage: from sage.doctest.sources import FileDocTestSource
     1205            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     1206            sage: from sage.misc.misc import walltime
     1207            sage: import doctest, sys, os
     1208            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     1209            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     1210            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1211            sage: doctests, extras = FDS.create_doctests(globals())
     1212            sage: ex = doctests[0].examples[0]
     1213            sage: DTR.tmp_time = walltime() - 1
     1214            sage: DTR.report_overtime(sys.stdout.write, doctests[0], ex, 'BAD ANSWER\n')
     1215            **********************************************************************
     1216            File ".../sage/doctest/forker.py", line 9, in sage.doctest.forker
     1217            Failed example:
     1218                doctest_var = 42; doctest_var^2
     1219            Test ran for ... s
     1220        """
     1221        out(self._failure_header(test, example) +
     1222            "Test ran for %.2f s\n"%(walltime(self.tmp_time)))
     1223
     1224    def report_unexpected_exception(self, out, test, example, exc_info):
     1225        """
     1226        Called when a doctest raises an exception that's not matched by the expected output.
     1227
     1228        If debugging has been turned on, starts an interactive debugger.
     1229
     1230        INPUT:
     1231
     1232        - ``out`` -- a function for printing (delayed or immediate)
     1233
     1234        - ``test`` -- a :class:`doctest.DocTest` instance
     1235
     1236        - ``example`` -- a :class:`doctest.Example` instance in ``test``
     1237
     1238        - ``exc_info`` -- the result of ``sys.exc_info()``
     1239
     1240        OUTPUT:
     1241
     1242        - prints a report to ``out``
     1243
     1244        - if in debugging mode, starts PDB with the given traceback
     1245
     1246        EXAMPLES::
     1247
     1248            sage: import os
     1249            sage: os.environ['SAGE_PEXPECT_LOG'] = "1"
     1250            sage: sage0.quit()
     1251            sage: _ = sage0.eval("import doctest, sys, os, multiprocessing, subprocess")
     1252            sage: _ = sage0.eval("from sage.doctest.parsing import SageOutputChecker")
     1253            sage: _ = sage0.eval("import sage.doctest.forker as sdf")
     1254            sage: _ = sage0.eval("from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()")
     1255            sage: _ = sage0.eval("ex = doctest.Example('E = EllipticCurve([0,0]); E', 'A singular Elliptic Curve')")
     1256            sage: _ = sage0.eval("DT = doctest.DocTest([ex], globals(), 'singular_curve', None, 0, None)")
     1257            sage: _ = sage0.eval("DTR = sdf.SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)")
     1258            sage: _ = sage0.eval("sdf.debug_semaphore = multiprocessing.Semaphore()")
     1259            sage: _ = sage0.eval("sdf.output_semaphore = multiprocessing.Semaphore()")
     1260            sage: sage0._prompt = r"\(Pdb\) "
     1261            sage: sage0.eval("DTR.run(DT, clear_globs=False)") # indirect doctest
     1262            '... "Invariants %s define a singular curve."%ainvs'
     1263            sage: sage0.eval("l")
     1264            '...if self.discriminant() == 0:...raise ArithmeticError...'
     1265            sage: sage0.eval("u")
     1266            '...EllipticCurve_field.__init__(self, [field(x) for x in ainvs])'
     1267            sage: sage0.eval("p ainvs")
     1268            '[0, 0]'
     1269            sage: sage0._prompt = "sage: "
     1270            sage: sage0.eval("quit")
     1271            'TestResults(failed=1, attempted=1)'
     1272        """
     1273        if debug_semaphore is not None or not self.options.initial or self.no_failure_yet:
     1274            self.no_failure_yet = False
     1275            returnval = doctest.DocTestRunner.report_unexpected_exception(self, out, test, example, exc_info)
     1276            if debug_semaphore is not None:
     1277                debug_semaphore.acquire()
     1278                output_semaphore.acquire()
     1279                self._fakeout.stop_spoofing()
     1280                self._fakeout.activate_stdin()
     1281                try:
     1282                    exc_type, exc_val, exc_tb = exc_info
     1283                    self.debugger.reset()
     1284                    self.debugger.interaction(None, exc_tb)
     1285                finally:
     1286                    self._fakeout.start_spoofing()
     1287                    self._fakeout.deactivate_stdin()
     1288                    sys.stdout.flush()
     1289                    output_semaphore.release()
     1290                    debug_semaphore.release()
     1291            return returnval
     1292
     1293    def update_results(self, D):
     1294        """
     1295        When returning results we pick out the results of interest
     1296        since many attributes are not pickleable.
     1297
     1298        INPUT:
     1299
     1300        - ``D`` -- a dictionary to update with cputime and walltime
     1301
     1302        OUTPUT:
     1303
     1304        - the number of failures (or False if there is no failure attribute)
     1305
     1306        EXAMPLES::
     1307
     1308            sage: from sage.doctest.parsing import SageOutputChecker
     1309            sage: from sage.doctest.forker import SageDocTestRunner
     1310            sage: from sage.doctest.sources import FileDocTestSource, DictAsObject
     1311            sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
     1312            sage: import doctest, sys, os
     1313            sage: DTR = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     1314            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     1315            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1316            sage: doctests, extras = FDS.create_doctests(globals())
     1317            sage: from sage.doctest.util import Timer
     1318            sage: T = Timer().start()
     1319            sage: DTR.run(doctests[0])
     1320            TestResults(failed=0, attempted=4)
     1321            sage: T.stop().annotate(DTR)
     1322            sage: D = DictAsObject({'cputime':[],'walltime':[],'err':None})
     1323            sage: DTR.update_results(D)
     1324            0
     1325            sage: sorted(list(D.iteritems()))
     1326            [('cputime', [...]), ('err', None), ('failures', 0), ('walltime', [...])]
     1327        """
     1328        for key in ["cputime","walltime"]:
     1329            if not D.has_key(key):
     1330                D[key] = []
     1331            if hasattr(self, key):
     1332                D[key].append(self.__dict__[key])
     1333        if hasattr(self, 'failures'):
     1334            D['failures'] = self.failures
     1335            return self.failures
     1336        else:
     1337            return False
     1338
     1339class DocTestDispatcher(SageObject):
     1340    """
     1341    Creates and dispatches doctesting tasks to workers in series or parallel.
     1342    """
     1343    def __init__(self, controller):
     1344        """
     1345        INPUT:
     1346
     1347        - ``controller`` -- a :class:`sage.doctest.control.DocTestController` instance
     1348
     1349        EXAMPLES::
     1350
     1351            sage: from sage.doctest.control import DocTestController, DocTestDefaults
     1352            sage: from sage.doctest.forker import DocTestDispatcher
     1353            sage: DocTestDispatcher(DocTestController(DocTestDefaults(), []))
     1354            <class 'sage.doctest.forker.DocTestDispatcher'>
     1355        """
     1356        self.controller = controller
     1357
     1358    def _serial_dispatch(self):
     1359        """
     1360        Run the doctests from the controller's specified sources in series.
     1361
     1362        EXAMPLES::
     1363
     1364            sage: from sage.doctest.control import DocTestController, DocTestDefaults
     1365            sage: from sage.doctest.forker import DocTestDispatcher
     1366            sage: from sage.doctest.reporting import DocTestReporter
     1367            sage: from sage.doctest.util import Timer
     1368            sage: import os
     1369            sage: powser = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'rings', 'homset.py')
     1370            sage: inf = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'rings', 'ideal.py')
     1371            sage: DC = DocTestController(DocTestDefaults(), [powser, inf])
     1372            sage: DC.expand_files_into_sources()
     1373            sage: DD = DocTestDispatcher(DC)
     1374            sage: DR = DocTestReporter(DC)
     1375            sage: DC.reporter = DR
     1376            sage: DC.dispatcher = DD
     1377            sage: DC.timer = Timer().start()
     1378            sage: DD._serial_dispatch()
     1379            sage -t .../rings/homset.py
     1380                [... tests, ... s]
     1381            sage -t .../rings/ideal.py
     1382                [... tests, ... s]
     1383        """
     1384        opt = self.controller.options
     1385        sources = self.controller.sources
     1386        try:
     1387            for source in sources:
     1388                output = os.tmpfile()
     1389                result = DocTestTask(source)(None, output, opt)
     1390                output.seek(0)
     1391                output = output.read()
     1392                self.controller.reporter.report(source, False, 0, result, output)
     1393        except KeyboardInterrupt:
     1394            print "Tesing %s interrupted!"%(source.basename)
     1395            sys.stdout.flush()
     1396            # We rely on the reporter to give more information back to the user.
     1397
     1398    def _parallel_dispatch(self):
     1399        """
     1400        Run the doctests from the controller's specified sources in series.
     1401
     1402        EXAMPLES::
     1403
     1404            sage: from sage.doctest.control import DocTestController, DocTestDefaults
     1405            sage: from sage.doctest.forker import DocTestDispatcher
     1406            sage: from sage.doctest.reporting import DocTestReporter
     1407            sage: from sage.doctest.util import Timer
     1408            sage: import os
     1409            sage: crem = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'databases', 'cremona.py')
     1410            sage: bigo = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'rings', 'big_oh.py')
     1411            sage: DC = DocTestController(DocTestDefaults(), [crem, bigo])
     1412            sage: DC.expand_files_into_sources()
     1413            sage: DD = DocTestDispatcher(DC)
     1414            sage: DR = DocTestReporter(DC)
     1415            sage: DC.reporter = DR
     1416            sage: DC.dispatcher = DD
     1417            sage: DC.timer = Timer().start()
     1418            sage: DD._parallel_dispatch()
     1419            sage -t .../databases/cremona.py
     1420                [... tests, ... s]
     1421            sage -t .../rings/big_oh.py
     1422                [... tests, ... s]
     1423        """
     1424        opt = self.controller.options
     1425        sources = self.controller.sources
     1426        nthreads = opt.nthreads
     1427        tasks = multiprocessing.JoinableQueue(len(sources)+nthreads)
     1428        for source in sources:
     1429            tasks.put(DocTestTask(source))
     1430        results = multiprocessing.Queue(len(sources))
     1431        workers = [DocTestWorker(tasks, results, options=opt) for i in xrange(nthreads)]
     1432        for w in workers:
     1433            w.start()
     1434            tasks.put(None)
     1435        done_count = 0
     1436        while True:
     1437            try:
     1438                result = results.get()
     1439                if result is None:
     1440                    done_count += 1
     1441                    if done_count >= len(workers):
     1442                        break
     1443                else:
     1444                    try:
     1445                        if output_semaphore is not None:
     1446                            output_semaphore.acquire()
     1447                        self.controller.reporter.report(*result)
     1448                    finally:
     1449                        sys.stdout.flush()
     1450                        if output_semaphore is not None:
     1451                            output_semaphore.release()
     1452            except Empty:
     1453                time.sleep(.05)
     1454
     1455    def dispatch(self): # todo, nthreads=options.nthreads, streaming=False, verbose=options.verbose, debug=options.debug, run_id=run_id
     1456        """
     1457        Runs the doctests for the controller's specified sources.
     1458
     1459        It will run in series or parallel depending on the ``serial``
     1460        option on the controller's option object.
     1461
     1462        EXAMPLES::
     1463
     1464            sage: from sage.doctest.control import DocTestController, DocTestDefaults
     1465            sage: from sage.doctest.forker import DocTestDispatcher
     1466            sage: from sage.doctest.reporting import DocTestReporter
     1467            sage: from sage.doctest.util import Timer
     1468            sage: import os
     1469            sage: freehom = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'modules', 'free_module_homspace.py')
     1470            sage: bigo = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage', 'sage', 'rings', 'big_oh.py')
     1471            sage: DC = DocTestController(DocTestDefaults(), [freehom, bigo])
     1472            sage: DC.expand_files_into_sources()
     1473            sage: DD = DocTestDispatcher(DC)
     1474            sage: DR = DocTestReporter(DC)
     1475            sage: DC.reporter = DR
     1476            sage: DC.dispatcher = DD
     1477            sage: DC.timer = Timer().start()
     1478            sage: DD.dispatch()
     1479            sage -t .../sage/modules/free_module_homspace.py
     1480                [... tests, ... s]
     1481            sage -t .../sage/rings/big_oh.py
     1482                [... tests, ... s]
     1483        """
     1484        init_sage()
     1485        global debug_semaphore, output_semaphore
     1486        try:
     1487            output_semaphore = multiprocessing.Semaphore()
     1488            if self.controller.options.debug:
     1489                debug_semaphore = multiprocessing.Semaphore()
     1490            if self.controller.options.serial:
     1491                self._serial_dispatch()
     1492            else:
     1493                self._parallel_dispatch()
     1494        finally:
     1495            output_semaphore = None
     1496            debug_semaphore = None
     1497
     1498class DocTestWorker(multiprocessing.Process):
     1499    """
     1500    The DocTestWorker process pulls tasks from its task queue and runs
     1501    them, putting the results onto its result queue.
     1502
     1503    It can recover gracefully from timeouts and segfaults in the
     1504    tasks.
     1505
     1506    INPUT:
     1507
     1508    - ``task_queue`` -- a :class:multiprocessing.Queue instance
     1509
     1510    - ``result_queue`` -- a :class:multiprocessing.Queue instance
     1511
     1512    - ``timeout`` -- a positive number
     1513
     1514    - ``verbose`` -- a boolean, controls verbosity when killing processes.
     1515
     1516    EXAMPLES::
     1517
     1518        sage: import multiprocessing, os, time
     1519        sage: from sage.doctest.forker import DocTestWorker, DocTestTask
     1520        sage: from sage.doctest.sources import FileDocTestSource
     1521        sage: from sage.doctest.reporting import DocTestReporter
     1522        sage: from sage.doctest.control import DocTestController, DocTestDefaults
     1523        sage: tasks = multiprocessing.JoinableQueue(2)
     1524        sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     1525        sage: DD = DocTestDefaults()
     1526        sage: DC = DocTestController(DD, filename)
     1527        sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1528        sage: DTT = DocTestTask(FDS)
     1529        sage: tasks.put(DTT)
     1530        sage: tasks.put(None)
     1531        sage: results = multiprocessing.Queue(1)
     1532        sage: W = DocTestWorker(tasks, results, DD)
     1533        sage: from Queue import Empty
     1534        sage: W.start()
     1535        sage: reporter = DocTestReporter(DC)
     1536        sage: while True:
     1537        ....:     try:
     1538        ....:         result = results.get()
     1539        ....:         if result is None: break
     1540        ....:         reporter.report(*result)
     1541        ....:     except Empty:
     1542        ....:         time.sleep(0.05)
     1543        ....:
     1544        sage -t .../sage/doctest/sources.py
     1545            [... tests, ... s]
     1546    """
     1547    def __init__(self, task_queue, result_queue, options):
     1548        """
     1549        Initialization.
     1550
     1551        TESTS::
     1552
     1553            sage: run_doctests(sage.rings.big_oh) # indirect doctest
     1554            Doctesting .../sage/rings/big_oh.py
     1555            Running doctests with ID ...
     1556            Doctesting 1 file.
     1557            sage -t .../sage/rings/big_oh.py
     1558                [... tests, ... s]
     1559            ------------------------------------------------------------------------
     1560            All tests passed!
     1561            ------------------------------------------------------------------------
     1562            Total time for all tests: ... seconds
     1563                cpu time: ... seconds
     1564                cumulative wall time: ... seconds
     1565        """
     1566        multiprocessing.Process.__init__(self)
     1567        self.task_queue = task_queue
     1568        self.result_queue = result_queue
     1569        self.options = options
     1570
     1571    def run(self):
     1572        """
     1573        Iterates through the tasks in the task queue and runs the
     1574        doctests in the corresponding sources.
     1575
     1576        TESTS::
     1577
     1578            sage: run_doctests(sage.symbolic.units) # indirect doctest
     1579            Doctesting .../sage/symbolic/units.py
     1580            Running doctests with ID ...
     1581            Doctesting 1 file.
     1582            sage -t .../sage/symbolic/units.py
     1583                [... tests, ... s]
     1584            ------------------------------------------------------------------------
     1585            All tests passed!
     1586            ------------------------------------------------------------------------
     1587            Total time for all tests: ... seconds
     1588                cpu time: ... seconds
     1589                cumulative wall time: ... seconds
     1590        """
     1591        while True:
     1592            next_task = self.task_queue.get()
     1593            if next_task is None:
     1594                self.task_queue.task_done()
     1595                self.result_queue.put(None)
     1596                break
     1597            result_pipe = multiprocessing.Queue() # pipes block
     1598            output_file = os.tmpfile()
     1599            p = multiprocessing.Process(target = next_task, args=(result_pipe,output_file,self.options))
     1600            try:
     1601                p.start()
     1602                ## We would like to do the following:
     1603                # results = result_pipe.get(timeout=self.options.timeout)
     1604                # p.join(timeout=self.options.timeout)
     1605                ## But this hangs when the underlying process segfaults.
     1606                ## So instead we query the underlying process manually.
     1607                maxwait = 0.1
     1608                curwait = 0.004
     1609                deathwait = 1
     1610                results = None
     1611                t = walltime()
     1612                alive = True
     1613                while True:
     1614                    try:
     1615                        results = result_pipe.get(timeout=curwait)
     1616                    except Empty:
     1617                        curwait *= 2
     1618                        if curwait > maxwait:
     1619                            curwait = maxwait
     1620                    if results is not None or walltime(t) >= self.options.timeout:
     1621                        break
     1622                    if not p.is_alive():
     1623                        if not alive:
     1624                            # We've already waited, but results is still None
     1625                            results = 0, DictAsObject(dict(err='noresult')), ""
     1626                            break
     1627                        alive = False
     1628                        if results is None:
     1629                            curwait = deathwait
     1630                answer = next_task.source, walltime(t) >= self.options.timeout, p.exitcode, results
     1631            ## This except block was used in the approch mentioned above
     1632            #except Empty:
     1633            #    answer = next_task.source, True, -1, None
     1634            except KeyboardInterrupt:
     1635                if output_semaphore is not None:
     1636                    output_semaphore.acquire()
     1637                try:
     1638                    print "Testing %s interrupted!"%(next_task.source.basename)
     1639                finally:
     1640                    sys.stdout.flush()
     1641                    if output_semaphore is not None:
     1642                        output_semaphore.release()
     1643                # We quietly die: the child process is killed in the finally block.
     1644                break
     1645            finally:
     1646                output_file.seek(0)
     1647                output = output_file.read()
     1648                self.annihilate(p, False)
     1649            self.task_queue.task_done()
     1650            answer = answer + (output,)
     1651            self.result_queue.put(answer)
     1652
     1653    def annihilate(self, p, verbose):
     1654        """
     1655        Kills the process p and all of its descendents.
     1656
     1657        INPUT:
     1658
     1659        - ``p`` -- a process to kill
     1660
     1661        - ``verbose`` -- boolean
     1662
     1663        EXAMPLES::
     1664
     1665            sage: from sage.doctest.forker import DocTestWorker
     1666            sage: DTW = DocTestWorker(None, None, None)
     1667            sage: from multiprocessing import Process
     1668            sage: import time
     1669            sage: def make_babies():
     1670            ....:     for i in range(3):
     1671            ....:         p = Process(target = time.sleep, args = (4,))
     1672            ....:         p.start()
     1673            ....:     time.sleep(6)
     1674            ....:
     1675            sage: p = Process(target = make_babies)
     1676            sage: p.start()
     1677            sage: p.is_alive()
     1678            True
     1679            sage: DTW.annihilate(p, True)
     1680            killing ...
     1681            subprocesses [...]
     1682            sage: p.is_alive()
     1683            False
     1684        """
     1685        ## The proper way to do this is to create a process group.
     1686        ## But then control-Z doesn't get forwarded automatically....
     1687        #        os.system("ps -fTj")
     1688        #        os.system("./pstree -p %s" % os.getpid())
     1689        #        os.system("kill -9 -%s" % p.pid)
     1690        #        os.system("kill -9 %s" % p.pid)
     1691        #        os.killpg(p.pid, signal.SIGSTOP)
     1692        def kill_all(pids, sig):
     1693            for pid in pids:
     1694                try:
     1695                    os.kill(pid, sig)
     1696                except OSError:
     1697                    pass
     1698        def all_subprocesses(root_pid):
     1699            listing = subprocess.Popen(['ps', '-o', 'pid,ppid'], stdout=subprocess.PIPE).communicate()[0].split('\n')[1:]
     1700            children = defaultdict(list)
     1701            for line in listing:
     1702                line = line.strip()
     1703                if line:
     1704                    pid, ppid = [int(s.strip()) for s in line.split(' ') if s]
     1705                    children[ppid].append(pid)
     1706            all = []
     1707            def add_children(ppid):
     1708                for pid in children[ppid]:
     1709                    all.append(pid)
     1710                    add_children(pid)
     1711            add_children(root_pid)
     1712            return all
     1713        if p.is_alive() and verbose:
     1714            print "killing", p.pid
     1715        if p.is_alive():
     1716            subprocesses = all_subprocesses(p.pid)
     1717            if verbose:
     1718                print "subprocesses", subprocesses
     1719            p.terminate()
     1720            p.join(1)
     1721            if subprocesses:
     1722                kill_all(reversed(subprocesses), signal.SIGTERM)
     1723                time.sleep(.1)
     1724                kill_all(reversed(subprocesses), signal.SIGKILL)
     1725                p.join(1)
     1726        if p.is_alive():
     1727            os.kill(p.pid, signal.SIGKILL)
     1728            p.join(1)
     1729
     1730class DocTestTask(object):
     1731    """
     1732    This class encapsulates the tests from a single source.  It can be
     1733    called in series (via :meth:`DocTestDispatcher._serial_dispatch`)
     1734    or in parallel through :class:`DocTestWorker` instances.
     1735
     1736    This class does not insulate from problems in the source
     1737    (e.g. entering an infinite loop or causing a segfault).
     1738
     1739    INPUT:
     1740
     1741    - ``source`` -- a :class:`sage.doctest.sources.DocTestSource` instance.
     1742
     1743    - ``verbose`` -- boolean, controls reporting of progress by :class:`doctest.DocTestRunner`.
     1744
     1745    EXAMPLES::
     1746
     1747        sage: from sage.doctest.forker import DocTestTask
     1748        sage: from sage.doctest.sources import FileDocTestSource
     1749        sage: from sage.doctest.control import DocTestDefaults, DocTestController
     1750        sage: import os
     1751        sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     1752        sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1753        sage: DTT = DocTestTask(FDS)
     1754        sage: DD = DocTestDefaults()
     1755        sage: DC = DocTestController(DD,[filename])
     1756        sage: ntests, results, delayed_output = DTT(output = os.tmpfile(),options=DD); ntests
     1757        276
     1758        sage: sorted(results.keys())
     1759        ['cputime', 'err', 'failures', 'walltime']
     1760    """
     1761    def __init__(self, source):
     1762        """
     1763        Initialization.
     1764
     1765        TESTS::
     1766
     1767            sage: from sage.doctest.forker import DocTestTask
     1768            sage: from sage.doctest.sources import FileDocTestSource
     1769            sage: import os
     1770            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     1771            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1772            sage: DocTestTask(FDS)
     1773            <sage.doctest.forker.DocTestTask object at ...>
     1774        """
     1775        self.source = source
     1776
     1777    def __call__(self, result_pipe=None, output=None, options=None):
     1778        """
     1779        Calling the task does the actual work of running the doctests.
     1780
     1781        INPUT:
     1782
     1783        - ``result_pipe`` -- either None (if run in series) or a :class:`multiprocessing.Queue`
     1784
     1785        - ``output`` -- a temporary file that's used by the doctest runner to redirect stdout.
     1786
     1787        OUPUT:
     1788
     1789        - If ``result_pipe`` is None, returns a pair ``(doctests,
     1790          runner)``, where ``doctests`` is a list of
     1791          :class:`doctest.DocTest` instances and ``runner`` is an
     1792          annotated ``SageDocTestRunner`` instance.
     1793
     1794        - If ``result_pipe`` is not None, puts ``(doctests, runner)``
     1795          onto the result pipe and returns nothing.
     1796
     1797        EXAMPLES::
     1798
     1799            sage: from sage.doctest.forker import DocTestTask
     1800            sage: from sage.doctest.sources import FileDocTestSource
     1801            sage: from sage.doctest.control import DocTestDefaults, DocTestController
     1802            sage: import os
     1803            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','parsing.py')
     1804            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     1805            sage: DTT = DocTestTask(FDS)
     1806            sage: DD = DocTestDefaults()
     1807            sage: DC = DocTestController(DD, [filename])
     1808            sage: ntests, runner, delayed_output = DTT(None, os.tmpfile(), DD)
     1809            sage: runner.failures
     1810            0
     1811            sage: ntests
     1812            192
     1813        """
     1814        result = None
     1815        try:
     1816            file = self.source.path
     1817            basename = self.source.basename
     1818            import sage.all_cmdline
     1819            runner = SageDocTestRunner(SageOutputChecker(), verbose=options.verbose, output=output, sage_options=options, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     1820            runner.basename = basename
     1821            N = options.file_iterations
     1822            results = DictAsObject(dict(walltime=[],cputime=[],err=None))
     1823            if options.verbose:
     1824                # This will cause summaries to print immediately
     1825                delayed_summaries = None
     1826            else:
     1827                delayed_summaries = []
     1828            for it in range(N):
     1829                sage_namespace = RecordingDict(dict(sage.all_cmdline.__dict__))
     1830                sage_namespace['__name__'] = '__main__'
     1831                sage_namespace['__package__'] = None
     1832                doctests, extras = self.source.create_doctests(sage_namespace)
     1833                timer = Timer().start()
     1834
     1835                for test in doctests:
     1836                    runner.run(test)
     1837                runner.filename = file
     1838                if output_semaphore is not None:
     1839                    output_semaphore.acquire()
     1840                failed, tried = runner.summarize(verbose=options.verbose, delay_list=delayed_summaries)
     1841                if output_semaphore is not None:
     1842                    sys.stdout.flush()
     1843                    output_semaphore.release()
     1844                timer.stop().annotate(runner)
     1845                if runner.update_results(results):
     1846                    break
     1847            if extras['tab']:
     1848                results.err = 'tab'
     1849                results.tab_linenos = extras['tab']
     1850            # We subtract 1 to remove the sig_on_count() tests
     1851            if delayed_summaries is None:
     1852                delayed_summaries = ""
     1853            else:
     1854                delayed_summaries = "".join(delayed_summaries)
     1855            result = sum([max(0,len(test.examples) - 1) for test in doctests]), results, delayed_summaries
     1856        except KeyboardInterrupt:
     1857            if result_pipe is None:
     1858                # We're in serial mode
     1859                raise
     1860            # Otherwise, we've been started by a worker: we'll return the result in the finally block
     1861            result = 0, DictAsObject(dict(err='ctlC')), ""
     1862        except IOError:
     1863            # File doesn't exist
     1864            result = 0, DictAsObject(dict(err='file')), ""
     1865        except Exception:
     1866            exc_info = sys.exc_info()
     1867            tb = "".join(traceback.format_exception(*exc_info))
     1868            result = 0, DictAsObject(dict(err=exc_info[0], tb=tb)), ""
     1869        finally:
     1870            if result_pipe is None:
     1871                return result
     1872            else:
     1873                result_pipe.put(result, False)
  • new file sage/doctest/parsing.py

    diff --git a/sage/doctest/parsing.py b/sage/doctest/parsing.py
    new file mode 100644
    - +  
     1"""
     2This module contains functions and classes that parse docstrings.
     3
     4AUTHORS:
     5
     6- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
     7"""
     8
     9#*****************************************************************************
     10#       Copyright (C) 2012 David Roe <roed.math@gmail.com>
     11#                          Robert Bradshaw <robertwb@gmail.com>
     12#                          William Stein <wstein@gmail.com>
     13#
     14#  Distributed under the terms of the GNU General Public License (GPL)
     15#
     16#                  http://www.gnu.org/licenses/
     17#*****************************************************************************
     18
     19import re, sys
     20import doctest
     21from sage.misc.preparser import preparse
     22from Cython.Build.Dependencies import strip_string_literals
     23
     24float_regex = re.compile('([+-]?((\d*\.?\d+)|(\d+\.?))([eE][+-]?\d+)?)')
     25optional_regex = re.compile(r'(long time|not implemented|not tested|known bug)|(optional\s*[:-]?\s*(\w*))')
     26find_sage_prompt = re.compile(r"^(\s*)sage: ", re.M)
     27find_sage_continuation = re.compile(r"^(\s*)\.\.\.\.:", re.M)
     28random_marker = re.compile('.*random', re.I)
     29tolerance_pattern = re.compile(r'\b((?:abs(?:olute)?)|(?:rel(?:ative)?))? *?tol(?:erance)?\b( +[0-9.e+-]+)?')
     30backslash_replacer = re.compile(r"""(\s*)sage:(.*)\\\ *
     31\ *(((\.){4}:)|((\.){3}))?\ *""")
     32
     33def parse_optional_tags(string):
     34    """
     35    Returns a set consisting of the optional tags from the following
     36    set that occur in a comment on the first line of the input string.
     37
     38    - 'long time'
     39    - 'not implemented'
     40    - 'not tested'
     41    - 'known bug'
     42    - 'optional: PKG_NAME' -- the set will just contain 'PKG_NAME'
     43
     44    EXAMPLES::
     45
     46        sage: from sage.doctest.parsing import parse_optional_tags
     47        sage: parse_optional_tags("    sage: magma('2 + 2') # optional: magma")
     48        set(['magma'])
     49        sage: sorted(list(parse_optional_tags("    sage: factor(10^(10^10) + 1) # long time, not tested")))
     50        ['long time', 'not tested']
     51        sage: parse_optional_tags("    sage: raise RuntimeError # known bug")
     52        set(['known bug'])
     53        sage: sorted(list(parse_optional_tags("    sage: determine_meaning_of_life() # long time, not implemented")))
     54        ['long time', 'not implemented']
     55    """
     56    safe, literals = strip_string_literals(string)
     57    first_line = safe.split('\n', 1)[0]
     58    if '#' not in first_line:
     59        return set()
     60    comment = first_line[first_line.find('#')+1:]
     61    # strip_string_literals replaces comments
     62    comment = literals[comment]
     63    return set(m.group(1) or m.group(3) for m in optional_regex.finditer(comment.lower()))
     64
     65def parse_tolerance(source, want):
     66    """
     67    Returns a version of ``want`` marked up with the tolerance tags
     68    specified in ``source``.
     69
     70    INPUT:
     71
     72    - ``source`` -- a string, the source of a doctest
     73    - ``want`` -- a string, the desired output of the doctest
     74
     75    OUTPUT:
     76
     77    - ``want`` if there are no tolerance tags specified; a
     78      :class:`MarkedOutput` version otherwise.
     79
     80    EXAMPLES::
     81
     82        sage: from sage.doctest.parsing import parse_tolerance
     83        sage: marked = parse_tolerance("sage: s.update(abs_tol = .0000001)", "")
     84        sage: type(marked)
     85        <type 'str'>
     86        sage: marked = parse_tolerance("sage: s.update(tol = 0.1); s.rel_tol # abs tol 0.01", "")
     87        sage: marked.tol
     88        0
     89        sage: marked.rel_tol
     90        0
     91        sage: marked.abs_tol
     92        0.01
     93    """
     94    safe, literals = strip_string_literals(source)
     95    first_line = safe.split('\n', 1)[0]
     96    if '#' not in first_line:
     97        return want
     98    comment = first_line[first_line.find('#')+1:]
     99    # strip_string_literals replaces comments
     100    comment = literals[comment]
     101    if random_marker.search(comment):
     102        want = MarkedOutput(want).update(random=True)
     103    else:
     104        m = tolerance_pattern.search(comment)
     105        if m:
     106            rel_or_abs, epsilon = m.groups()
     107            if epsilon is None:
     108                epsilon = 1e-15
     109            else:
     110                epsilon = float(epsilon.strip())
     111            if rel_or_abs is None:
     112                want = MarkedOutput(want).update(tol=epsilon)
     113            elif rel_or_abs.startswith('rel'):
     114                want = MarkedOutput(want).update(rel_tol=epsilon)
     115            elif rel_or_abs.startswith('abs'):
     116                want = MarkedOutput(want).update(abs_tol=epsilon)
     117            else:
     118                raise RuntimeError
     119    return want
     120
     121def pre_hash(s):
     122    """
     123    Prepends a string with its length.
     124
     125    EXAMPLES::
     126
     127        sage: from sage.doctest.parsing import pre_hash
     128        sage: pre_hash("abc")
     129        '3:abc'
     130    """
     131    return "%s:%s" % (len(s), s)
     132
     133def get_source(example):
     134    """
     135    Returns the source with the leading 'sage: ' stripped off.
     136
     137    EXAMPLES::
     138
     139        sage: from sage.doctest.parsing import get_source
     140        sage: from sage.doctest.sources import DictAsObject
     141        sage: example = DictAsObject({})
     142        sage: example.sage_source = "2 + 2"
     143        sage: example.source = "sage: 2 + 2"
     144        sage: get_source(example)
     145        '2 + 2'
     146        sage: example = DictAsObject({})
     147        sage: example.source = "3 + 3"
     148        sage: get_source(example)
     149        '3 + 3'
     150    """
     151    return getattr(example, 'sage_source', example.source)
     152
     153def reduce_hex(fingerprints):
     154    """
     155    Returns a symmetric function of the arguments as hex strings.
     156
     157    The arguments should be 32 character strings consiting of hex
     158    digits: 0-9 and a-f.
     159
     160    EXAMPLES::
     161
     162        sage: from sage.doctest.parsing import reduce_hex
     163        sage: reduce_hex(["abc", "12399aedf"])
     164        '0000000000000000000000012399a463'
     165        sage: reduce_hex(["12399aedf","abc"])
     166        '0000000000000000000000012399a463'
     167    """
     168    from operator import xor
     169    res = reduce(xor, (int(x, 16) for x in fingerprints), 0)
     170    if res < 0:
     171        res += 1 << 128
     172    return "%032x" % res
     173
     174
     175class MarkedOutput(str):
     176    """
     177    A subclass of string with context for whether another string
     178    matches it.
     179
     180    EXAMPLES::
     181
     182        sage: from sage.doctest.parsing import MarkedOutput
     183        sage: s = MarkedOutput("abc")
     184        sage: s.rel_tol
     185        0
     186        sage: s.update(rel_tol = .05)
     187        'abc'
     188        sage: s.rel_tol
     189        0.0500000000000000
     190    """
     191    random = False
     192    rel_tol = 0
     193    abs_tol = 0
     194    tol = 0
     195    def update(self, **kwds):
     196        """
     197        EXAMPLES::
     198
     199            sage: from sage.doctest.parsing import MarkedOutput
     200            sage: s = MarkedOutput("0.0007401")
     201            sage: s.update(abs_tol = .0000001)
     202            '0.0007401'
     203            sage: s.rel_tol
     204            0
     205            sage: s.abs_tol
     206            1.00000000000000e-7
     207        """
     208        self.__dict__.update(kwds)
     209        return self
     210
     211    def __reduce__(self):
     212        """
     213        Pickling.
     214
     215        EXAMPLES::
     216
     217            sage: from sage.doctest.parsing import MarkedOutput
     218            sage: s = MarkedOutput("0.0007401")
     219            sage: s.update(abs_tol = .0000001)
     220            '0.0007401'
     221            sage: t = loads(dumps(s)) # indirect doctest
     222            sage: t == s
     223            True
     224            sage: t.abs_tol
     225            1.00000000000000e-7
     226        """
     227        return make_marked_output, (str(self), self.__dict__)
     228
     229def make_marked_output(s, D):
     230    """
     231    Auxilliary function for pickling.
     232
     233    EXAMPLES::
     234
     235        sage: from sage.doctest.parsing import make_marked_output
     236        sage: s = make_marked_output("0.0007401",{'abs_tol':.0000001})
     237        sage: s
     238        '0.0007401'
     239        sage: s.abs_tol
     240        1.00000000000000e-7
     241    """
     242    ans = MarkedOutput(s)
     243    ans.__dict__.update(D)
     244    return ans
     245
     246class OriginalSource:
     247    r"""
     248    Context swapping out the pre-parsed source with the original for
     249    better reporting.
     250
     251    EXAMPLES::
     252
     253        sage: from sage.doctest.sources import FileDocTestSource
     254        sage: import os
     255        sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     256        sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     257        sage: doctests, extras = FDS.create_doctests(globals())
     258        sage: ex = doctests[0].examples[0]
     259        sage: ex.sage_source
     260        'doctest_var = 42; doctest_var^2\n'
     261        sage: ex.source
     262        'doctest_var = Integer(42); doctest_var**Integer(2)\n'
     263        sage: from sage.doctest.parsing import OriginalSource
     264        sage: with OriginalSource(ex):
     265        ...       ex.source
     266        'doctest_var = 42; doctest_var^2\n'
     267    """
     268    def __init__(self, example):
     269        """
     270        Swaps out the source for the sage_source of a doctest example.
     271
     272        INPUT:
     273
     274        - ``example`` -- a :class:`doctest.Example` instance
     275
     276        EXAMPLES::
     277
     278            sage: from sage.doctest.sources import FileDocTestSource
     279            sage: import os
     280            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     281            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     282            sage: doctests, extras = FDS.create_doctests(globals())
     283            sage: ex = doctests[0].examples[0]
     284            sage: from sage.doctest.parsing import OriginalSource
     285            sage: OriginalSource(ex)
     286            <sage.doctest.parsing.OriginalSource instance at ...>
     287        """
     288        self.example = example
     289
     290    def __enter__(self):
     291        """
     292        EXAMPLES::
     293
     294            sage: from sage.doctest.sources import FileDocTestSource
     295            sage: import os
     296            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     297            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     298            sage: doctests, extras = FDS.create_doctests(globals())
     299            sage: ex = doctests[0].examples[0]
     300            sage: from sage.doctest.parsing import OriginalSource
     301            sage: with OriginalSource(ex): # indirect doctest
     302            ...       ex.source
     303            ...
     304            'doctest_var = 42; doctest_var^2\n'
     305        """
     306        if hasattr(self.example, 'sage_source'):
     307            self.old_source, self.example.source = self.example.source, self.example.sage_source
     308
     309    def __exit__(self, *args):
     310        r"""
     311        EXAMPLES::
     312
     313            sage: from sage.doctest.sources import FileDocTestSource
     314            sage: import os
     315            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','forker.py')
     316            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     317            sage: doctests, extras = FDS.create_doctests(globals())
     318            sage: ex = doctests[0].examples[0]
     319            sage: from sage.doctest.parsing import OriginalSource
     320            sage: with OriginalSource(ex): # indirect doctest
     321            ...       ex.source
     322            ...
     323            'doctest_var = 42; doctest_var^2\n'
     324            sage: ex.source # indirect doctest
     325            'doctest_var = Integer(42); doctest_var**Integer(2)\n'
     326        """
     327        if hasattr(self.example, 'sage_source'):
     328            self.example.source = self.old_source
     329
     330class SageDocTestParser(doctest.DocTestParser):
     331    """
     332    A version of the standard doctest parser which handles Sage's
     333    custom options and tolerances in floating point arithmetic.
     334    """
     335    def __init__(self, long=False, optional_tags=()):
     336        r"""
     337        INPUT:
     338
     339        - ``long`` -- boolean, whether to run doctests marked as taking a long time.
     340        - ``optional_tags`` -- a list or tuple of strings.
     341
     342        EXAMPLES::
     343
     344            sage: from sage.doctest.parsing import SageDocTestParser
     345            sage: DTP = SageDocTestParser(True, ('sage','magma','guava'))
     346            sage: ex = DTP.parse("sage: 2 + 2\n")[1]
     347            sage: ex.sage_source
     348            '2 + 2\n'
     349            sage: ex = DTP.parse("sage: R.<x> = ZZ[]")[1]
     350            sage: ex.source
     351            "R = ZZ['x']; (x,) = R._first_ngens(1)\n"
     352
     353        TESTS::
     354
     355            sage: TestSuite(DTP).run()
     356        """
     357        self.long = long
     358        if optional_tags is True: # run all optional tests
     359            self.optional_tags = True
     360            self.optional_only = False
     361        else:
     362            self.optional_tags = set(optional_tags)
     363            if 'sage' in self.optional_tags:
     364                self.optional_only = False
     365                self.optional_tags.remove('sage')
     366            else:
     367                self.optional_only = True
     368
     369    def __cmp__(self, other):
     370        """
     371        Comparison.
     372
     373        EXAMPLES::
     374
     375            sage: from sage.doctest.parsing import SageDocTestParser
     376            sage: DTP = SageDocTestParser(True, ('sage','magma','guava'))
     377            sage: DTP2 = SageDocTestParser(False, ('sage','magma','guava'))
     378            sage: DTP == DTP2
     379            False
     380        """
     381        c = cmp(type(self), type(other))
     382        if c: return c
     383        return cmp(self.__dict__, other.__dict__)
     384
     385    def parse(self, string, *args):
     386        r"""
     387        A Sage specialization of :class:`doctest.DocTestParser`.
     388
     389        INPUTS:
     390
     391        - ``string`` -- the string to parse.
     392        - ``name`` -- optional string giving the name indentifying string,
     393          to be used in error messages.
     394
     395        OUTPUTS:
     396
     397        - A list consisting of strings and :class:`doctest.Example`
     398          instances.  There will be at least one string between
     399          successive examples (exactly one unless or long or optional
     400          tests are removed), and it will begin and end with a string.
     401
     402        EXAMPLES::
     403
     404            sage: from sage.doctest.parsing import SageDocTestParser
     405            sage: DTP = SageDocTestParser(True, ('sage','magma','guava'))
     406            sage: example = 'Explanatory text::\n\n    sage: E = magma("EllipticCurve([1, 1, 1, -10, -10])") # optional: magma\n\nLater text'
     407            sage: parsed = DTP.parse(example)
     408            sage: parsed[0]
     409            'Explanatory text::\n\n'
     410            sage: parsed[1].sage_source
     411            'E = magma("EllipticCurve([1, 1, 1, -10, -10])") # optional: magma\n'
     412            sage: parsed[2]
     413            '\nLater text'
     414
     415        If the doctest parser is not created to accept a given
     416        optional argument, the corresponding examples will just be
     417        removed::
     418
     419            sage: DTP2 = SageDocTestParser(True, ('sage',))
     420            sage: parsed2 = DTP2.parse(example)
     421            sage: parsed2
     422            ['Explanatory text::\n\n', '\nLater text']
     423
     424        You can mark doctests as having a particular tolerance::
     425
     426            sage: example2 = 'sage: gamma(1.6) # tol 2.0e-11\n0.893515349287690'
     427            sage: ex = DTP.parse(example2)[1]
     428            sage: ex.sage_source
     429            'gamma(1.6) # tol 2.0e-11\n'
     430            sage: ex.want
     431            '0.893515349287690\n'
     432            sage: type(ex.want)
     433            <class 'sage.doctest.parsing.MarkedOutput'>
     434            sage: ex.want.tol
     435            2e-11
     436
     437        You can use continuation lines:
     438
     439            sage: s = "sage: for i in range(4):\n....:     print i\n....:\n"
     440            sage: ex = DTP2.parse(s)[1]
     441            sage: ex.source
     442            'for i in range(Integer(4)):\n    print i\n'
     443
     444        Sage currently accepts backslashes as indicating that the end
     445        of the current line should be joined to the next line.  This
     446        feature allows for breaking large integers over multiple lines
     447        but is not standard for Python doctesting.  It's not
     448        guaranteed to persist, but works in Sage 5.5::
     449
     450            sage: n = 1234\
     451            ....:     5678
     452            sage: print n
     453            12345678
     454            sage: type(n)
     455            <type 'sage.rings.integer.Integer'>
     456
     457        It also works without the line continuation::
     458
     459            sage: m = 8765\
     460            4321
     461            sage: print m
     462            87654321
     463        """
     464        # Hack for non-standard backslash line escapes accepted by the current
     465        # doctest system.
     466        m = backslash_replacer.search(string)
     467        while m is not None:
     468            next_prompt = find_sage_prompt.search(string,m.end())
     469            g = m.groups()
     470            if next_prompt:
     471                future = string[m.end():next_prompt.start()] + '\n' + string[next_prompt.start():]
     472            else:
     473                future = string[m.end():]
     474            string = string[:m.start()] + g[0] + "sage:" + g[1] + future
     475            m = backslash_replacer.search(string,m.start())
     476
     477        string = find_sage_prompt.sub(r"\1>>> sage: ", string)
     478        string = find_sage_continuation.sub(r"\1...", string)
     479        res = doctest.DocTestParser.parse(self, string, *args)
     480        filtered = []
     481        for item in res:
     482            if isinstance(item, doctest.Example):
     483                optional_tags = parse_optional_tags(item.source)
     484                if optional_tags:
     485                    if ('not implemented' in optional_tags) or ('not tested' in optional_tags):
     486                        continue
     487                    elif 'known bug' in optional_tags:
     488                        optional_tags.remove('known bug')
     489                        optional_tags.add('bug') # so that such tests will be run by sage -t ... -only-optional=bug
     490                    if 'long time' in optional_tags:
     491                        if self.long:
     492                            optional_tags.remove('long time')
     493                        else:
     494                            continue
     495                    if not (self.optional_tags is True or optional_tags.issubset(self.optional_tags)):
     496                        continue
     497                elif self.optional_only:
     498                    continue
     499                item.want = parse_tolerance(item.source, item.want)
     500                if item.source.startswith("sage: "):
     501                    item.sage_source = item.source[6:]
     502                    if item.sage_source.lstrip().startswith('#'):
     503                        continue
     504                    item.source = preparse(item.sage_source)
     505            filtered.append(item)
     506        return filtered
     507
     508class SageOutputChecker(doctest.OutputChecker):
     509    r"""
     510    A modification of the doctest OutputChecker that can check
     511    relative and absolute tolerance of answers.
     512
     513    EXAMPLES::
     514
     515        sage: from sage.doctest.parsing import SageOutputChecker, MarkedOutput, SageDocTestParser
     516        sage: import doctest
     517        sage: optflag = doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS
     518        sage: DTP = SageDocTestParser(True, ('sage','magma','guava'))
     519        sage: OC = SageOutputChecker()
     520        sage: example2 = 'sage: gamma(1.6) # tol 2.0e-11\n0.893515349287690'
     521        sage: ex = DTP.parse(example2)[1]
     522        sage: ex.sage_source
     523        'gamma(1.6) # tol 2.0e-11\n'
     524        sage: ex.want
     525        '0.893515349287690\n'
     526        sage: type(ex.want)
     527        <class 'sage.doctest.parsing.MarkedOutput'>
     528        sage: ex.want.tol
     529        2e-11
     530        sage: OC.check_output(ex.want, '0.893515349287690', optflag)
     531        True
     532        sage: OC.check_output(ex.want, '0.8935153492877', optflag)
     533        True
     534        sage: OC.check_output(ex.want, '0', optflag)
     535        False
     536        sage: OC.check_output(ex.want, 'x + 0.8935153492877', optflag)
     537        False
     538    """
     539    def check_output(self, want, got, optionflags):
     540        """
     541        Checks to see if the output matches the desired output.
     542
     543        If ``want`` is a :class:`MarkedOutput` instance, takes into account the desired tolerance.
     544
     545        INPUT:
     546
     547        - ``want`` -- a string or :class:`MarkedOutput`
     548        - ``got`` -- a string
     549        - ``optionflags`` -- an integer, passed down to :class:`doctest.OutputChecker`
     550
     551        OUTPUT:
     552
     553        - boolean, whether ``got`` matches ``want`` up to the specified tolerance.
     554
     555        EXAMPLES::
     556
     557            sage: from sage.doctest.parsing import MarkedOutput, SageOutputChecker
     558            sage: import doctest
     559            sage: optflag = doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS
     560            sage: rndstr = MarkedOutput("I'm wrong!").update(random=True)
     561            sage: tentol = MarkedOutput("10.0").update(tol=.1)
     562            sage: tenabs = MarkedOutput("10.0").update(abs_tol=.1)
     563            sage: tenrel = MarkedOutput("10.0").update(rel_tol=.1)
     564            sage: zerotol = MarkedOutput("0.0").update(tol=.1)
     565            sage: zeroabs = MarkedOutput("0.0").update(abs_tol=.1)
     566            sage: zerorel = MarkedOutput("0.0").update(rel_tol=.1)
     567            sage: zero = "0.0"
     568            sage: nf = "9.5"
     569            sage: ten = "10.05"
     570            sage: eps = "-0.05"
     571            sage: OC = SageOutputChecker()
     572
     573        ::
     574
     575            sage: OC.check_output(rndstr,nf,optflag)
     576            True
     577
     578            sage: OC.check_output(tentol,nf,optflag)
     579            True
     580            sage: OC.check_output(tentol,ten,optflag)
     581            True
     582            sage: OC.check_output(tentol,zero,optflag)
     583            False
     584
     585            sage: OC.check_output(tenabs,nf,optflag)
     586            False
     587            sage: OC.check_output(tenabs,ten,optflag)
     588            True
     589            sage: OC.check_output(tenabs,zero,optflag)
     590            False
     591
     592            sage: OC.check_output(tenrel,nf,optflag)
     593            True
     594            sage: OC.check_output(tenrel,ten,optflag)
     595            True
     596            sage: OC.check_output(tenrel,zero,optflag)
     597            False
     598
     599            sage: OC.check_output(zerotol,zero,optflag)
     600            True
     601            sage: OC.check_output(zerotol,eps,optflag)
     602            True
     603            sage: OC.check_output(zerotol,ten,optflag)
     604            False
     605
     606            sage: OC.check_output(zeroabs,zero,optflag)
     607            True
     608            sage: OC.check_output(zeroabs,eps,optflag)
     609            True
     610            sage: OC.check_output(zeroabs,ten,optflag)
     611            False
     612
     613            sage: OC.check_output(zerorel,zero,optflag)
     614            True
     615            sage: OC.check_output(zerorel,eps,optflag)
     616            False
     617            sage: OC.check_output(zerorel,ten,optflag)
     618            False
     619        """
     620        if isinstance(want, MarkedOutput):
     621            if want.random:
     622                return True
     623            elif want.tol or want.rel_tol or want.abs_tol:
     624                if want.tol:
     625                    check_tol = lambda a, b: (a == 0 and abs(b) < want.tol) or (a*b != 0 and abs(a-b)/abs(a) < want.tol)
     626                elif want.abs_tol:
     627                    check_tol = lambda a, b: abs(a-b) < want.abs_tol
     628                else:
     629                    check_tol = lambda a, b: (a == b == 0) or (a*b != 0 and abs(a-b)/abs(a) < want.rel_tol)
     630                want_values = [float(g[0]) for g in float_regex.findall(want)]
     631                got_values = [float(g[0]) for g in float_regex.findall(got)]
     632                if len(want_values) != len(got_values):
     633                    return False
     634                if not doctest.OutputChecker.check_output(self,
     635                        float_regex.sub('*', want), float_regex.sub('*', got), optionflags):
     636                    return False
     637                return all(check_tol(*ab) for ab in zip(want_values, got_values))
     638        ok = doctest.OutputChecker.check_output(self, want, got, optionflags)
     639        #sys.stderr.write(str(ok) + " want: " + repr(want) + " got: " + repr(got) + "\n")
     640        return ok
     641
     642    def output_difference(self, example, got, optionflags):
     643        r"""
     644        Report on the differences between the desired result and what
     645        was actually obtained.
     646
     647        If ``want`` is a :class:`MarkedOutput` instance, takes into account the desired tolerance.
     648
     649        INPUT:
     650
     651        - ``example`` -- a :class:`doctest.Example` instance
     652        - ``got`` -- a string
     653        - ``optionflags`` -- an integer, passed down to :class:`doctest.OutputChecker`
     654
     655        OUTPUT:
     656
     657        - a string, describing how ``got`` fails to match ``example.want``
     658
     659        EXAMPLES::
     660
     661            sage: from sage.doctest.parsing import MarkedOutput, SageOutputChecker
     662            sage: import doctest
     663            sage: optflag = doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS
     664            sage: tentol = doctest.Example('',MarkedOutput("10.0\n").update(tol=.1))
     665            sage: tenabs = doctest.Example('',MarkedOutput("10.0\n").update(abs_tol=.1))
     666            sage: tenrel = doctest.Example('',MarkedOutput("10.0\n").update(rel_tol=.1))
     667            sage: zerotol = doctest.Example('',MarkedOutput("0.0\n").update(tol=.1))
     668            sage: zeroabs = doctest.Example('',MarkedOutput("0.0\n").update(abs_tol=.1))
     669            sage: zerorel = doctest.Example('',MarkedOutput("0.0\n").update(rel_tol=.1))
     670            sage: tlist = doctest.Example('',MarkedOutput("[10.0, 10.0, 10.0, 10.0, 10.0, 10.0]\n").update(abs_tol=1.0))
     671            sage: zero = "0.0"
     672            sage: nf = "9.5"
     673            sage: ten = "10.05"
     674            sage: eps = "-0.05"
     675            sage: L = "[9.9, 8.7, 10.3, 11.2, 10.8, 10.0]"
     676            sage: OC = SageOutputChecker()
     677
     678        ::
     679
     680            sage: print OC.output_difference(tenabs,nf,optflag)
     681            Expected:
     682                10.0
     683            Got:
     684                9.5
     685            Tolerance exceeded: 5e-01 > 1e-01
     686
     687            sage: print OC.output_difference(tentol,zero,optflag)
     688            Expected:
     689                10.0
     690            Got:
     691                0.0
     692            Tolerance exceeded: infinity > 1e-01
     693
     694            sage: print OC.output_difference(tentol,eps,optflag)
     695            Expected:
     696                10.0
     697            Got:
     698                -0.05
     699            Tolerance exceeded: 1e+00 > 1e-01
     700
     701            sage: print OC.output_difference(tlist,L,optflag)
     702            Expected:
     703                [10.0, 10.0, 10.0, 10.0, 10.0, 10.0]
     704            Got:
     705                [9.9, 8.7, 10.3, 11.2, 10.8, 10.0]
     706            Tolerance exceeded in 2 of 6
     707                10.0 vs 8.7
     708                10.0 vs 11.2
     709
     710
     711        TESTS::
     712
     713            sage: print OC.output_difference(tenabs,zero,optflag)
     714            Expected:
     715                10.0
     716            Got:
     717                0.0
     718            Tolerance exceeded: 1e+01 > 1e-01
     719
     720            sage: print OC.output_difference(tenrel,zero,optflag)
     721            Expected:
     722                10.0
     723            Got:
     724                0.0
     725            Tolerance exceeded: 1e+00 > 1e-01
     726
     727            sage: print OC.output_difference(tenrel,eps,optflag)
     728            Expected:
     729                10.0
     730            Got:
     731                -0.05
     732            Tolerance exceeded: 1e+00 > 1e-01
     733
     734            sage: print OC.output_difference(zerotol,ten,optflag)
     735            Expected:
     736                0.0
     737            Got:
     738                10.05
     739            Tolerance exceeded: 1e+01 > 1e-01
     740
     741            sage: print OC.output_difference(zeroabs,ten,optflag)
     742            Expected:
     743                0.0
     744            Got:
     745                10.05
     746            Tolerance exceeded: 1e+01 > 1e-01
     747
     748            sage: print OC.output_difference(zerorel,eps,optflag)
     749            Expected:
     750                0.0
     751            Got:
     752                -0.05
     753            Tolerance exceeded: infinity > 1e-01
     754
     755            sage: print OC.output_difference(zerorel,ten,optflag)
     756            Expected:
     757                0.0
     758            Got:
     759                10.05
     760            Tolerance exceeded: infinity > 1e-01
     761
     762        """
     763        want = example.want
     764        diff = doctest.OutputChecker.output_difference(self, example, got, optionflags)
     765        if isinstance(want, MarkedOutput) and (want.tol or want.abs_tol or want.rel_tol):
     766            if diff[-1] != "\n":
     767                diff += "\n"
     768            want_str = [g[0] for g in float_regex.findall(want)]
     769            got_str = [g[0] for g in float_regex.findall(got)]
     770            want_values = [float(g) for g in want_str]
     771            got_values = [float(g) for g in got_str]
     772            starwant = float_regex.sub('*', want)
     773            stargot = float_regex.sub('*', got)
     774            #print want_values, got_values, starwant, stargot, doctest.OutputChecker.check_output(self, starwant, stargot, optionflags)
     775            if len(want_values) > 0 and len(want_values) == len(got_values) and \
     776               doctest.OutputChecker.check_output(self, starwant, stargot, optionflags):
     777                def failstr(c,d,actual,desired):
     778                    if len(want_values) == 1:
     779                        if actual != 'infinity':
     780                            actual = "%.0e"%(actual)
     781                        return "Tolerance exceeded: %s > %.0e\n"%(actual, desired)
     782                    else:
     783                        return "    %s vs %s"%(c,d)
     784                fails = []
     785                for a, b, c, d in zip(want_values, got_values, want_str, got_str):
     786                    if want.tol:
     787                        if a == 0:
     788                            if abs(b) >= want.tol:
     789                                fails.append(failstr(c,d,abs(b),want.tol))
     790                        elif b == 0:
     791                            fails.append(failstr(c,d,"infinity",want.tol))
     792                        elif abs((a - b) / a) >= want.tol:
     793                            fails.append(failstr(c,d,abs((a - b) / a), want.tol))
     794                    elif want.abs_tol:
     795                        if abs(a - b) >= want.abs_tol:
     796                            fails.append(failstr(c,d,abs(a - b),want.abs_tol))
     797                    elif a == 0:
     798                        if b != 0:
     799                            fails.append(failstr(c,d,"infinity",want.rel_tol))
     800                    elif abs((a - b) / a) >= want.rel_tol:
     801                        fails.append(failstr(c,d,abs((a - b) / a),want.rel_tol))
     802                if len(want_values) == 1 and len(fails) == 1: # fails should always be 1...
     803                    diff += fails[0]
     804                else:
     805                    diff += "Tolerance exceeded in %s of %s\n"%(len(fails), len(want_values))
     806                    diff += "\n".join(fails[:3]) + "\n"
     807        return diff
  • new file sage/doctest/reporting.py

    diff --git a/sage/doctest/reporting.py b/sage/doctest/reporting.py
    new file mode 100644
    - +  
     1"""
     2This module determines how doctest results are reported to the user.
     3
     4AUTHORS:
     5
     6- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
     7"""
     8
     9#*****************************************************************************
     10#       Copyright (C) 2012 David Roe <roed.math@gmail.com>
     11#                          Robert Bradshaw <robertwb@gmail.com>
     12#                          William Stein <wstein@gmail.com>
     13#
     14#  Distributed under the terms of the GNU General Public License (GPL)
     15#
     16#                  http://www.gnu.org/licenses/
     17#*****************************************************************************
     18
     19import sys
     20from sage.structure.sage_object import SageObject
     21from sage.doctest.util import count_noun
     22
     23class DocTestReporter(SageObject):
     24    """
     25    This class reports to the users on the results of doctests.
     26    """
     27    def __init__(self, controller):
     28        """
     29        Initialize the reporter.
     30
     31        INPUT:
     32
     33        - ``controller`` -- a
     34          :class:`sage.doctest.control.DocTestController` instance.
     35          Note that some methods assume that appropriate tests have
     36          been run by the controller.
     37
     38        EXAMPLES::
     39
     40            sage: from sage.doctest.reporting import DocTestReporter
     41            sage: from sage.doctest.control import DocTestController, DocTestDefaults
     42            sage: import os
     43            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','reporting.py')
     44            sage: DC = DocTestController(DocTestDefaults(),[filename])
     45            sage: DTR = DocTestReporter(DC)
     46        """
     47        self.controller = controller
     48        self.postscript = dict(lines=[], cputime=0, walltime=0)
     49        self.sources_completed = 0
     50        self.stats = {}
     51        self.error_status = 0 # partially for backward compatibility
     52
     53    def report(self, source, timeout, return_code, results, output):
     54        """
     55        Report on the result of running doctests on a given source.
     56
     57        INPUT:
     58
     59        - ``source`` -- a source from :mod:`sage.doctest.sources`
     60
     61        - ``timeout`` -- a boolean, whether doctests timed out
     62
     63        - ``return_code`` -- an int, the return code of the process
     64          running doctests on that file.
     65
     66        - ``results`` -- (irrelevant if ``timeout`` or
     67          ``return_code``), a tuple
     68
     69          - ``ntests`` -- the number of doctests
     70
     71          - ``timings`` -- a
     72            :class:`sage.doctest.sources.DictAsObject` instance
     73            storing timing data.
     74
     75          - ``delayed_output`` -- delayed output to be printed now
     76
     77        - ``output`` -- a string, printed if there was some kind of
     78          failure
     79
     80        EXAMPLES::
     81
     82            sage: from sage.doctest.reporting import DocTestReporter
     83            sage: from sage.doctest.control import DocTestController, DocTestDefaults
     84            sage: from sage.doctest.sources import FileDocTestSource, DictAsObject
     85            sage: from sage.doctest.forker import SageDocTestRunner
     86            sage: from sage.doctest.parsing import SageOutputChecker
     87            sage: from sage.doctest.util import Timer
     88            sage: import os, sys, doctest
     89            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','reporting.py')
     90            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     91            sage: DD = DocTestDefaults()
     92            sage: DC = DocTestController(DD,[filename])
     93            sage: DTR = DocTestReporter(DC)
     94
     95        You can report a timeout::
     96
     97            sage: DTR.report(FDS, True, 0, None, "Output so far...")
     98            sage -t .../sage/doctest/reporting.py
     99                Timed out!
     100            ********************************************************************************
     101            Tests run before process froze:
     102            Output so far...
     103            ********************************************************************************
     104            sage: DTR.stats
     105            {'sage.doctest.reporting': {'failed': True, 'walltime': 1000000.0}}
     106
     107        Or a process that returned a bad exit code::
     108
     109            sage: DTR.report(FDS, False, 3, None, "Output before bad exit")
     110            sage -t .../sage/doctest/reporting.py
     111                Bad exit: 3
     112            ********************************************************************************
     113            Tests run before process failed:
     114            Output before bad exit
     115            ********************************************************************************
     116            sage: DTR.stats
     117            {'sage.doctest.reporting': {'failed': True, 'walltime': 1000000.0}}
     118
     119        Or tell the user that everything succeeded::
     120
     121            sage: doctests, extras = FDS.create_doctests(globals())
     122            sage: runner = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     123            sage: Timer().start().stop().annotate(runner)
     124            sage: D = DictAsObject({'err':None})
     125            sage: runner.update_results(D)
     126            0
     127            sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D, ""), "Good tests")
     128            sage -t .../doctest/reporting.py
     129                [... tests, 0.0 s]
     130            sage: DTR.stats
     131            {'sage.doctest.reporting': {'walltime': ...}}
     132
     133        Or inform the user that some doctests failed::
     134
     135            sage: runner.failures = 1
     136            sage: runner.update_results(D)
     137            1
     138            sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D, ""), "Doctest output including the failure...")
     139            sage -t .../doctest/reporting.py
     140                [... tests, 1 failure, 0.0 s]
     141        """
     142        log = self.controller.log
     143        postscript = self.postscript
     144        stats = self.stats
     145        try:
     146            basename = source.basename
     147            if self.controller.options.long:
     148                islong = "--long "
     149            else:
     150                islong = ""
     151            warnlong = self.controller.options.warn_long
     152            if warnlong is None:
     153                warnlong = ""
     154            elif warnlong == 1.0:
     155                warnlong = "--warn-long "
     156            else:
     157                warnlong = "--warn-long %.1f "%(warnlong)
     158            cmd = "sage -t %s%s%s"%(islong, warnlong, source.printpath)
     159            log(cmd)
     160            if timeout:
     161                log("    Timed out!\n%s\nTests run before process froze:"%("*"*80))
     162                log(output)
     163                log("*"*80)
     164                postscript['lines'].append(cmd + " # Time out")
     165                stats[basename] = dict(failed=True, walltime=1e6)
     166                self.error_status |= 64
     167            elif return_code:
     168                log("    Bad exit: %s\n%s\nTests run before process failed:"%(return_code,"*"*80))
     169                log(output)
     170                log("*"*80)
     171                postscript['lines'].append(cmd + " # Bad exit: %s" % return_code)
     172                stats[basename] = dict(failed=True, walltime=1e6)
     173                self.error_status |= 4
     174            elif results is None:
     175                log("    Error in doctesting framework!\n%s\nTests run before error:"%("*"*80))
     176                log(output)
     177                log("*"*80)
     178                stats[basename] = dict(failed=True, walltime=1e6)
     179                postscript['lines'].append(cmd + " # Unhandled doctest exception")
     180                self.error_status |= 8
     181            else:
     182                ntests, result_dict, delayed_output = results
     183                if hasattr(result_dict, 'walltime') and hasattr(result_dict.walltime, '__len__') and len(result_dict.walltime) > 0:
     184                    wall = sum(result_dict.walltime) / len(result_dict.walltime)
     185                else:
     186                    wall = 1e6
     187                if hasattr(result_dict, 'cputime') and hasattr(result_dict.cputime, '__len__') and len(result_dict.cputime) > 0:
     188                    cpu = sum(result_dict.cputime) / len(result_dict.cputime)
     189                else:
     190                    cpu = 1e6
     191                if result_dict.err == 'file':
     192                    log("    File not found!")
     193                    postscript['lines'].append(cmd + " # File not found")
     194                    self.error_status |= 1
     195                elif result_dict.err == 'noresult':
     196                    log("    Error in doctesting framework (no result returned)\n%s\nTests run before error:"%("*"*80))
     197                    log(output)
     198                    log("*"*80)
     199                    postscript['lines'].append(cmd + " # Testing error: no result returned")
     200                    self.error_status |= 8
     201                elif result_dict.err == 'ctlC':
     202                    log("    Unexpected KeyboardInterrupt raised in file\n%s\nTests run before interrupt:"%("*"*80))
     203                    log(output)
     204                    log("*"*80)
     205                    postscript['lines'].append(cmd + " # Unhandled KeyboardInterrupt")
     206                    stats[basename] = dict(failed=True, walltime = wall)
     207                    self.error_status |= 8
     208                elif result_dict.err == 'tab':
     209                    if len(result_dict.tab_linenos) > 5:
     210                        result_dict.tab_linenos[3:-1] = "..."
     211                    tabs = " " + ",".join(result_dict.tab_linenos)
     212                    if len(result_dict.tab_linenos) > 1:
     213                        tabs = "s" + tabs
     214                    log("    Error: TAB character found at line%s"%(tabs))
     215                    postscript['lines'].append(cmd + " # Tab character found")
     216                    self.error_status |= 32
     217                elif result_dict.err is True:
     218                    # This case should not occur
     219                    log("    Error in doctesting framework")
     220                    postscript['lines'].append(cmd + " # Unhandled doctest exception")
     221                    self.error_status |= 8
     222                elif result_dict.err is not None:
     223                    if hasattr(result_dict, 'tb'):
     224                        log(result_dict.tb)
     225                    if hasattr(result_dict.err, '__name__'):
     226                        err = result_dict.err.__name__
     227                    else:
     228                        err = repr(result_dict.err)
     229                    postscript['lines'].append(cmd + " # %s in loading"%(err))
     230                    if hasattr(result_dict, 'walltime'):
     231                        stats[basename] = dict(failed=True, walltime=wall)
     232                    else:
     233                        stats[basename] = dict(failed=True, walltime=1e6)
     234                    self.error_status |= 16
     235                if result_dict.err is None or result_dict.err == 'tab':
     236                    f = result_dict.failures
     237                    if f:
     238                        postscript['lines'].append(cmd + " # %s failed" % (count_noun(f, "doctest")))
     239                        self.error_status |= 128
     240                    if f or result_dict.err == 'tab':
     241                        stats[basename] = dict(failed=True, walltime=wall)
     242                    else:
     243                        stats[basename] = dict(walltime=wall)
     244                    postscript['cputime'] += cpu
     245                    postscript['walltime'] += wall
     246
     247                    if delayed_output:
     248                        if delayed_output[-1] == '\n':
     249                            delayed_output = delayed_output[:-1]
     250                        log(delayed_output)
     251                    log("    [%s, %s%.1f s]" % (count_noun(ntests, "test"), "%s, "%(count_noun(f, "failure")) if f else "", wall))
     252            self.sources_completed += 1
     253
     254        except StandardError:
     255            import traceback
     256            traceback.print_exc()
     257
     258    def finalize(self):
     259        """
     260        Print out the postcript that summarizes the doctests that were run.
     261
     262        EXAMPLES:
     263
     264        First we have to set up a bunch of stuff::
     265
     266            sage: from sage.doctest.reporting import DocTestReporter
     267            sage: from sage.doctest.control import DocTestController, DocTestDefaults
     268            sage: from sage.doctest.sources import FileDocTestSource, DictAsObject
     269            sage: from sage.doctest.forker import SageDocTestRunner
     270            sage: from sage.doctest.parsing import SageOutputChecker
     271            sage: from sage.doctest.util import Timer
     272            sage: import os, sys, doctest
     273            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','reporting.py')
     274            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     275            sage: DD = DocTestDefaults()
     276            sage: DC = DocTestController(DD,[filename])
     277            sage: DTR = DocTestReporter(DC)
     278
     279        Now we pretend to run some doctests::
     280
     281            sage: DTR.report(FDS, True, 0, None, "Output so far...")
     282            sage -t .../sage/doctest/reporting.py
     283                Timed out!
     284            ********************************************************************************
     285            Tests run before process froze:
     286            Output so far...
     287            ********************************************************************************
     288            sage: DTR.report(FDS, False, 3, None, "Output before bad exit")
     289            sage -t .../sage/doctest/reporting.py
     290                Bad exit: 3
     291            ********************************************************************************
     292            Tests run before process failed:
     293            Output before bad exit
     294            ********************************************************************************
     295            sage: doctests, extras = FDS.create_doctests(globals())
     296            sage: runner = SageDocTestRunner(SageOutputChecker(), output = os.tmpfile(), verbose=False, sage_options=DD,optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
     297            sage: t = Timer().start().stop()
     298            sage: t.annotate(runner)
     299            sage: DC.timer = t
     300            sage: D = DictAsObject({'err':None})
     301            sage: runner.update_results(D)
     302            0
     303            sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D, ""), "Good tests")
     304            sage -t .../doctest/reporting.py
     305                [... tests, 0.0 s]
     306            sage: runner.failures = 1
     307            sage: runner.update_results(D)
     308            1
     309            sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D, ""), "Doctest output including the failure...")
     310            sage -t .../doctest/reporting.py
     311                [... tests, 1 failure, 0.0 s]
     312
     313        Now we can show the output of finalize::
     314
     315            sage: DC.sources = [None] * 4 # to fool the finalize method
     316            sage: DTR.finalize()
     317            ------------------------------------------------------------------------
     318            sage -t .../sage/doctest/reporting.py # Time out
     319            sage -t .../sage/doctest/reporting.py # Bad exit: 3
     320            sage -t .../sage/doctest/reporting.py # 1 doctest failed
     321            ------------------------------------------------------------------------
     322            Total time for all tests: 0.0 seconds
     323                cpu time: 0.0 seconds
     324                cumulative wall time: 0.0 seconds
     325
     326        If we interrupted doctests, then the number of files tested
     327        will not match the number of sources on the controller::
     328
     329            sage: DC.sources = [None] * 6
     330            sage: DTR.finalize()
     331            <BLANKLINE>
     332            ------------------------------------------------------------------------
     333            sage -t .../sage/doctest/reporting.py # Time out
     334            sage -t .../sage/doctest/reporting.py # Bad exit: 3
     335            sage -t .../sage/doctest/reporting.py # 1 doctest failed
     336            Doctests interrupted: 4/6 files tested
     337            ------------------------------------------------------------------------
     338            Total time for all tests: 0.0 seconds
     339                cpu time: 0.0 seconds
     340                cumulative wall time: 0.0 seconds
     341        """
     342        log = self.controller.log
     343        postscript = self.postscript
     344        if self.sources_completed < len(self.controller.sources) * self.controller.options.global_iterations:
     345            postscript['lines'].append("Doctests interrupted: %s/%s files tested"%(self.sources_completed, len(self.controller.sources)))
     346            self.error_status |= 2
     347        elif not postscript['lines']:
     348            postscript['lines'].append("All tests passed!")
     349        log('-' * 72)
     350        log("\n".join(postscript['lines']))
     351        log('-' * 72)
     352        log("Total time for all tests: %.1f seconds" % self.controller.timer.walltime)
     353        log("    cpu time: %.1f seconds" % postscript['cputime'])
     354        log("    cumulative wall time: %.1f seconds" % postscript['walltime'])
     355        sys.stdout.flush()
  • new file sage/doctest/sources.py

    diff --git a/sage/doctest/sources.py b/sage/doctest/sources.py
    new file mode 100644
    - +  
     1"""
     2This module defines various classes for sources from which doctests
     3originate, such as files, functions or database entries.
     4
     5AUTHORS:
     6
     7- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
     8"""
     9
     10#*****************************************************************************
     11#       Copyright (C) 2012 David Roe <roed.math@gmail.com>
     12#                          Robert Bradshaw <robertwb@gmail.com>
     13#                          William Stein <wstein@gmail.com>
     14#
     15#  Distributed under the terms of the GNU General Public License (GPL)
     16#
     17#                  http://www.gnu.org/licenses/
     18#*****************************************************************************
     19
     20import os, sys, re, random
     21import doctest
     22from sage.misc.preparser import preparse, load
     23from sage.misc.lazy_attribute import lazy_attribute
     24from parsing import SageDocTestParser
     25from util import NestedName
     26from sage.structure.dynamic_class import dynamic_class
     27
     28# Python file parsing
     29triple_quotes = re.compile("\s*[rRuU]*((''')|(\"\"\"))")
     30name_regex = re.compile(r".*\s(\w+)([(].*)?:")
     31
     32# LaTeX file parsing
     33begin_verb = re.compile(r"\s*\\begin{verbatim}")
     34end_verb = re.compile(r"\s*\\end{verbatim}\s*(%link)?")
     35skip = re.compile(r".*%skip.*")
     36
     37# ReST file parsing
     38link_all = re.compile(r"^\s*\.\.\s+linkall\s*$")
     39double_colon = re.compile(r"^(\s*).*::\s*$")
     40
     41whitespace = re.compile("\s*")
     42bitness_marker = re.compile('#.*(32|64)-bit')
     43bitness_value = '64' if sys.maxint > (1 << 32) else '32'
     44
     45# For neutralizing doctests
     46find_prompt = re.compile(r"^(\s*)(>>>|sage:)(.*)")
     47
     48# For testing that enough doctests are created
     49sagestart = re.compile(r"^\s*(>>> |sage: )\s*[^#\s]")
     50untested = re.compile("(not implemented|not tested|known bug)")
     51
     52def get_basename(path):
     53    """
     54    This function returns the basename of the given path, e.g. sage.doctest.sources or doc.ru.tutorial.tour_advanced
     55
     56    EXAMPLES::
     57
     58        sage: from sage.doctest.sources import get_basename
     59        sage: import os
     60        sage: get_basename(os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py'))
     61        'sage.doctest.sources'
     62    """
     63    if path is None:
     64        return None
     65    if not os.path.exists(path):
     66        return path
     67    path = os.path.abspath(path)
     68    root = os.path.dirname(path)
     69    # If the file is in the sage library, we can use our knowledge of
     70    # the directory structure
     71    sage_root = os.environ['SAGE_ROOT']
     72    dev = os.path.join(sage_root, 'devel', 'sage')
     73    sp = os.path.join(sage_root, 'local', 'lib', 'python', 'site-packages')
     74    if path.startswith(dev):
     75        # there will be a branch name
     76        i = path.find(os.path.sep, len(dev))
     77        if i == -1:
     78            # this source is the whole library....
     79            return path
     80        root = path[:i]
     81    elif path.startswith(sp):
     82        root = path[:len(sp)]
     83    else:
     84        # If this file is in some python package we can see how deep
     85        # it goes by the presence of __init__.py files.
     86        while os.path.exists(os.path.join(root, '__init__.py')):
     87            root = os.path.dirname(root)
     88    fully_qualified_path = os.path.splitext(path[len(root) + 1:])[0]
     89    if os.path.split(path)[1] == '__init__.py':
     90        fully_qualified_path = fully_qualified_path[:-9]
     91    return fully_qualified_path.replace(os.path.sep, '.')
     92
     93class DocTestSource(object):
     94    """
     95    This class provides a common base class for different sources of doctests.
     96
     97    INPUT:
     98
     99    - ``long`` -- whether to execute tests marked as #long
     100
     101    - ``optional`` -- either True or a set of optional tags to execute
     102
     103    - ``randorder`` -- whether to execute the doctests in this source
     104      in a random order
     105    """
     106    def __init__(self, long, optional, randorder):
     107        """
     108        Initialization.
     109
     110        EXAMPLES::
     111
     112            sage: from sage.doctest.sources import FileDocTestSource
     113            sage: import os
     114            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     115            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),0)
     116            sage: TestSuite(FDS).run()
     117        """
     118        self.long = long
     119        self.optional = optional
     120        self.randorder = randorder
     121
     122    def __cmp__(self, other):
     123        """
     124        Comparison is just by comparison of attributes.
     125
     126        EXAMPLES::
     127
     128            sage: from sage.doctest.sources import FileDocTestSource
     129            sage: import os
     130            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     131            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),0)
     132            sage: FDS2 = FileDocTestSource(filename,True,False,set(['sage']),0)
     133            sage: FDS == FDS2
     134            True
     135        """
     136        c = cmp(type(self), type(other))
     137        if c: return c
     138        return cmp(self.__dict__, other.__dict__)
     139
     140    def _process_doc(self, doctests, doc, namespace, start):
     141        """
     142        Appends doctests defined in ``doc`` to the list ``doctests``.
     143
     144        This function is called when a docstring block is completed
     145        (either by ending a triple quoted string in a Python file,
     146        unindenting from a comment block in a ReST file, or ending a
     147        verbatim environment in a LaTeX file.
     148
     149        INPUT:
     150
     151        - ``doctests`` -- a running list of doctests to which the new
     152          test(s) will be appended.
     153
     154        - ``doc`` -- a list of lines of a docstring, each including
     155          the trailing newline.
     156
     157        - ``namespace`` -- a dictionary or
     158          :class:`sage.doctest.util.RecordingDict`, used in the
     159          creation of new :class:`doctest.DocTest`s.
     160
     161        - ``start`` -- an integer, giving the line number of the start
     162          of this docstring in the larger file.
     163
     164        EXAMPLES::
     165
     166            sage: from sage.doctest.sources import FileDocTestSource
     167            sage: from sage.doctest.parsing import SageDocTestParser
     168            sage: from sage.doctest.util import NestedName
     169            sage: import os
     170            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','util.py')
     171            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     172            sage: doctests, _ = FDS.create_doctests({})
     173            sage: manual_doctests = []
     174            sage: for dt in doctests:
     175            ....:     FDS.qualified_name = dt.name
     176            ....:     FDS._process_doc(manual_doctests, dt.docstring, {}, dt.lineno-1)
     177            sage: doctests == manual_doctests
     178            True
     179        """
     180        docstring = "".join(doc)
     181        new_doctests = self.parse_docstring(docstring, namespace, start)
     182        #print "New tests! %s"%len(new_doctests)
     183        for dt in new_doctests:
     184            if len(dt.examples) > 0 and not (hasattr(dt.examples[-1],'sage_source')
     185                                             and dt.examples[-1].sage_source == "sig_on_count()\n"):
     186                sigon = doctest.Example("sig_on_count()\n", "0\n", lineno=100000 + len(doc))
     187                sigon.sage_source = "sig_on_count()\n"
     188                dt.examples.append(sigon)
     189            doctests.append(dt)
     190
     191    def _create_doctests(self, namespace, tab_okay=None):
     192        """
     193        Creates a list doctests defined in this source.
     194
     195        This function collects functionality common to file and string
     196        sources, and is called by
     197        :meth:`FileDocTestSource.create_doctests`.
     198
     199        INPUT:
     200
     201        - ``namespace`` -- a dictionary or
     202          :class:`sage.doctest.util.RecordingDict`, used in the
     203          creation of new :class:`doctest.DocTest`s.
     204
     205        - ``tab_okay`` -- whether tabs are allowed in this source.
     206
     207        OUTPUT:
     208
     209        - ``doctests`` -- a list of doctests defined by this source
     210
     211        - ``extras`` -- a dictionary with ``extras['tab']`` either
     212          False or a list of linenumbers on which tabs appear.
     213
     214        EXAMPLES::
     215
     216            sage: from sage.doctest.sources import FileDocTestSource
     217            sage: from sage.doctest.util import NestedName
     218            sage: import os
     219            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     220            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     221            sage: FDS.qualified_name = NestedName('sage.doctest.sources')
     222            sage: doctests, extras = FDS._create_doctests({})
     223            sage: len(doctests)
     224            40
     225            sage: extras['tab']
     226            False
     227        """
     228        if tab_okay is None:
     229            tab_okay = isinstance(self,TexSource)
     230        self._init()
     231        self.line_shift = 0
     232        self.parser = SageDocTestParser(self.long, self.optional)
     233        self.linking = False
     234        doctests = []
     235        in_docstring = False
     236        tab_found = False
     237        unparsed_doc = False
     238        doc = []
     239        start = None
     240        tab_locations = []
     241        for lineno, line in self:
     242            if "\t" in line:
     243                tab_locations.append(str(lineno+1))
     244            if "SAGE_DOCTEST_ALLOW_TABS" in line:
     245                tab_okay = True
     246            just_finished = False
     247            if in_docstring:
     248                if self.ending_docstring(line):
     249                    in_docstring = False
     250                    just_finished = True
     251                    self._process_doc(doctests, doc, namespace, start)
     252                    unparsed_doc = False
     253                else:
     254                    bitness = bitness_marker.search(line)
     255                    if bitness:
     256                        if bitness.groups()[0] != bitness_value:
     257                            self.line_shift += 1
     258                            continue
     259                        else:
     260                            line = line[:bitness.start()] + "\n"
     261                    if self.line_shift and sagestart.match(line):
     262                        # We insert blank lines to make up for the removed lines
     263                        doc.extend(["\n"]*self.line_shift)
     264                        self.line_shift = 0
     265                    doc.append(line)
     266                    unparsed_doc = True
     267            if not in_docstring and (not just_finished or self.start_finish_can_overlap):
     268                #print lineno, "not in docstring"
     269                # to get line numbers in linked docstrings correct we
     270                # append a blank line to the doc list.
     271                doc.append("\n")
     272                if not line.strip():
     273                    continue
     274                if self.starting_docstring(line):
     275                    in_docstring = True
     276                    if self.linking:
     277                        # If there's already a doctest, we overwrite it.
     278                        if len(doctests) > 0:
     279                            doctests.pop()
     280                        if start is None:
     281                            start = lineno
     282                            doc = []
     283                    else:
     284                        self.line_shift = 0
     285                        start = lineno
     286                        doc = []
     287        # In ReST files we can end the file without decreasing the indentation level.
     288        if unparsed_doc:
     289            self._process_doc(doctests, doc, namespace, start)
     290
     291        if self.randorder is not None and self.randorder is not False:
     292            # we want to randomize even when self.randorder = 0
     293            random.seed(self.randorder)
     294            randomized = []
     295            while len(doctests) > 0:
     296                i = random.randint(0, len(doctests)-1)
     297                randomized.append(doctests.pop(i))
     298            return randomized, {'tab':not tab_okay and tab_locations}
     299        else:
     300            return doctests, {'tab':not tab_okay and tab_locations}
     301
     302class StringDocTestSource(DocTestSource):
     303    """
     304    This class creates doctests from a string.
     305
     306    INPUT:
     307
     308    - ``basename`` -- string such as 'sage.doctests.sources', going
     309      into the names of created doctests and examples.
     310
     311    - ``source`` -- a string, giving the source code to be parsed for
     312      doctests.
     313
     314    - ``long`` -- whether to execute tests marked as #long
     315
     316    - ``optional`` -- either True or a set of optional tags to execute
     317
     318    - ``randorder`` -- whether to execute the doctests in this string
     319      in a random order
     320
     321    - ``printpath`` -- a string, to be used in place of a filename
     322      when doctest failures are displayed.
     323
     324    - ``lineno_shift`` -- an integer (default: 0) by which to shift
     325      the line numbers of all doctests defined in this string.
     326
     327    EXAMPLES::
     328
     329        sage: from sage.doctest.sources import StringDocTestSource, PythonSource
     330        sage: from sage.structure.dynamic_class import dynamic_class
     331        sage: s = "'''\n    sage: 2 + 2\n    4\n'''"
     332        sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
     333        sage: PSS = PythonStringSource('<runtime>', s, False, set(['sage']), False, 'runtime')
     334        sage: dt, extras = PSS.create_doctests({})
     335        sage: len(dt)
     336        1
     337        sage: extras['tab']
     338        []
     339    """
     340    def __init__(self, basename, source, long, optional, randorder, printpath, lineno_shift=0):
     341        """
     342        Initialization
     343
     344        TESTS::
     345
     346            sage: from sage.doctest.sources import StringDocTestSource, PythonSource
     347            sage: from sage.structure.dynamic_class import dynamic_class
     348            sage: s = "'''\n    sage: 2 + 2\n    4\n'''"
     349            sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
     350            sage: PSS = PythonStringSource('<runtime>', s, False, set(['sage']), False, 'runtime')
     351            sage: TestSuite(PSS).run()
     352        """
     353        self.qualified_name = NestedName(basename)
     354        self.printpath = printpath
     355        self.source = source
     356        self.lineno_shift = lineno_shift
     357        DocTestSource.__init__(self, long, optional, randorder)
     358
     359    def __iter__(self):
     360        """
     361        Iterating over this source yields pairs ``(lineno, line)``.
     362
     363        EXAMPLES::
     364
     365            sage: from sage.doctest.sources import StringDocTestSource, PythonSource
     366            sage: from sage.structure.dynamic_class import dynamic_class
     367            sage: s = "'''\n    sage: 2 + 2\n    4\n'''"
     368            sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
     369            sage: PSS = PythonStringSource('<runtime>', s, False, set(['sage']), False, 'runtime')
     370            sage: for n, line in PSS:
     371            ....:     print n, line,
     372            0 '''
     373            1     sage: 2 + 2
     374            2     4
     375            3 '''
     376        """
     377        for lineno, line in enumerate(self.source.split('\n')):
     378            yield lineno + self.lineno_shift, line + '\n'
     379
     380    def create_doctests(self, namespace):
     381        """
     382        Creates doctests from this string.
     383
     384        INPUT:
     385
     386        - ``namespace`` -- a dictionary or :class:`sage.doctest.util.RecordingDict`.
     387
     388        OUTPUT:
     389
     390        - ``doctests`` -- a list of doctests defined by this string
     391
     392        - ``tab_locations`` -- either False or a list of linenumbers
     393          on which tabs appear.
     394
     395        EXAMPLES::
     396
     397            sage: from sage.doctest.sources import StringDocTestSource, PythonSource
     398            sage: from sage.structure.dynamic_class import dynamic_class
     399            sage: s = "'''\n    sage: 2 + 2\n    4\n'''"
     400            sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
     401            sage: PSS = PythonStringSource('<runtime>', s, False, set(['sage']), False, 'runtime')
     402            sage: dt, tabs = PSS.create_doctests({})
     403            sage: for t in dt:
     404            ....:     print t.name, t.examples[0].sage_source
     405            <runtime> 2 + 2
     406        """
     407        return self._create_doctests(namespace)
     408
     409class FileDocTestSource(DocTestSource):
     410    """
     411    This class creates doctests from a file.
     412
     413    INPUT:
     414
     415    - ``path`` -- string, the filename
     416
     417    - ``force_lib`` -- bool, whether this file shoule be considered
     418      part of the Sage library
     419
     420    - ``long`` -- whether to execute tests marked as #long
     421
     422    - ``optional`` -- either True or a set of optional tags to execute
     423
     424    - ``randorder`` -- whether to execute the doctests in this file in
     425      a random order
     426
     427    - ``useabspath`` -- bool (default: False), whether paths should be
     428      printed absolutely
     429
     430    EXAMPLES::
     431
     432        sage: from sage.doctest.sources import FileDocTestSource
     433        sage: import os
     434        sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     435        sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     436        sage: FDS.basename
     437        'sage.doctest.sources'
     438
     439    TESTS::
     440
     441        sage: TestSuite(FDS).run()
     442    """
     443    def __init__(self, path, force_lib, long, optional, randorder, useabspath=False):
     444        """
     445        Initialization.
     446
     447        EXAMPLES::
     448
     449            sage: from sage.doctest.sources import FileDocTestSource
     450            sage: import os
     451            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     452            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),0)
     453            sage: FDS.randorder
     454            0
     455        """
     456        self.path = path
     457        self.force_lib = force_lib
     458        DocTestSource.__init__(self, long, optional, randorder)
     459        self.useabspath = useabspath
     460        base, ext = os.path.splitext(path)
     461        if ext in ('.py', '.pyx', '.pxi', '.sage', '.spyx'):
     462            self.__class__ = dynamic_class('PythonFileSource',(FileDocTestSource,PythonSource))
     463        elif ext == '.tex':
     464            self.__class__ = dynamic_class('TexFileSource',(FileDocTestSource,TexSource))
     465        elif ext == '.rst':
     466            self.__class__ = dynamic_class('RestFileSource',(FileDocTestSource,RestSource))
     467
     468    def __iter__(self):
     469        """
     470        Iterating over this source yields pairs ``(lineno, line)``.
     471
     472        EXAMPLES::
     473
     474            sage: from sage.doctest.sources import FileDocTestSource
     475            sage: import os
     476            sage: filename = os.path.join(SAGE_TMP, 'test.py')
     477            sage: s = "'''\n    sage: 2 + 2\n    4\n'''"
     478            sage: with open(filename, 'w') as F:
     479            ....:     F.write(s)
     480            sage: FDS = FileDocTestSource(filename, False, False, set(['sage']), False)
     481            sage: for n, line in FDS:
     482            ....:     print n, line,
     483            0 '''
     484            1     sage: 2 + 2
     485            2     4
     486            3 '''
     487        """
     488        with open(self.path) as source:
     489            for lineno, line in enumerate(source):
     490                yield lineno, line
     491
     492    @lazy_attribute
     493    def printpath(self):
     494        """
     495        Whether the path is printed absolutely or relatively depends on an option.
     496
     497        EXAMPLES::
     498
     499            sage: from sage.doctest.sources import FileDocTestSource
     500            sage: import os
     501            sage: br = os.readlink(os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sage'))
     502            sage: root = os.path.join(os.environ['SAGE_ROOT'],'devel',br,'sage')
     503            sage: filename = os.path.join(root,'doctest','sources.py')
     504            sage: cwd = os.getcwd()
     505            sage: os.chdir(root)
     506            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),0)
     507            sage: FDS.printpath
     508            'doctest/sources.py'
     509            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),0,useabspath=True)
     510            sage: FDS.printpath
     511            '.../sage/doctest/sources.py'
     512            sage: os.chdir(cwd)
     513        """
     514        if self.useabspath:
     515            return os.path.abspath(self.path)
     516        else:
     517            relpath = os.path.relpath(self.path)
     518            if relpath.startswith(".." + os.path.sep):
     519                return self.path
     520            else:
     521                return relpath
     522
     523    @lazy_attribute
     524    def basename(self):
     525        """
     526        The basename of this file source, e.g. sage.doctest.sources
     527
     528        EXAMPLES::
     529
     530            sage: from sage.doctest.sources import FileDocTestSource
     531            sage: import os
     532            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','rings','integer.pyx')
     533            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     534            sage: FDS.basename
     535            'sage.rings.integer'
     536        """
     537        return get_basename(self.path)
     538
     539    @lazy_attribute
     540    def in_lib(self):
     541        """
     542        Whether this file should be considered part of the Sage library.
     543
     544        Such files aren't loaded before running tests.
     545
     546        EXAMPLES::
     547
     548            sage: from sage.doctest.sources import FileDocTestSource
     549            sage: import os
     550            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','rings','integer.pyx')
     551            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     552            sage: FDS.in_lib
     553            True
     554
     555        You can override the default::
     556
     557            sage: FDS = FileDocTestSource("hello_world.py",False,False,set(['sage']),None)
     558            sage: FDS.in_lib
     559            False
     560            sage: FDS = FileDocTestSource("hello_world.py",True,False,set(['sage']),None)
     561        """
     562        return (self.force_lib or
     563                self.basename.startswith('sage.') or
     564                self.basename.startswith('doc.') or
     565                self.basename.startswith('sagenb.'))
     566
     567    def create_doctests(self, namespace):
     568        r"""
     569        Returns a list of doctests for this file.
     570
     571        INPUT:
     572
     573        - ``namespace`` -- a dictionary or :class:`sage.doctest.util.RecordingDict`.
     574
     575        OUTPUT:
     576
     577        - ``doctests`` -- a list of doctests defined in this file.
     578
     579        - ``extras`` -- a dictionary
     580
     581        EXAMPLES::
     582
     583            sage: from sage.doctest.sources import FileDocTestSource
     584            sage: import os
     585            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     586            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     587            sage: doctests, extras = FDS.create_doctests(globals())
     588            sage: len(doctests)
     589            40
     590            sage: extras['tab']
     591            False
     592
     593        We give a self referential example::
     594
     595            sage: doctests[17].name
     596            'sage.doctest.sources.FileDocTestSource.create_doctests'
     597            sage: doctests[17].examples[8].source
     598            'doctests[Integer(17)].examples[Integer(8)].source\n'
     599
     600        TESTS:
     601
     602        We check that we correctly process results that depend on 32
     603        vs 64 bit architecture::
     604
     605            sage: import sys
     606            sage: bitness = '64' if sys.maxint > (1 << 32) else '32'
     607            sage: n = -920390823904823094890238490238484; hash(n) > 0
     608            False # 32-bit
     609            True  # 64-bit
     610            sage: ex = doctests[17].examples[11]
     611            sage: (bitness == '64' and ex.want == 'True  \n') or (bitness == '32' and ex.want == 'False \n')
     612            True
     613
     614        We check that lines starting with a # aren't doctested::
     615
     616            #sage: raise RuntimeError
     617        """
     618        if not os.path.exists(self.path):
     619            raise IOError("File does not exist")
     620        base, filename = os.path.split(self.path)
     621        _, ext = os.path.splitext(filename)
     622        if not self.in_lib and ext in ('.py', '.pyx', '.sage', '.spyx'):
     623            cwd = os.getcwd()
     624            if base:
     625                os.chdir(base)
     626            load(filename, namespace) # errors raised here will be caught in DocTestTask
     627            if base:
     628                os.chdir(cwd)
     629        self.qualified_name = NestedName(self.basename)
     630        return self._create_doctests(namespace)
     631
     632    def _test_enough_doctests(self, check_extras = True, verbose = True):
     633        """
     634        This function checks to see that the doctests are not getting
     635        unexpectedly skipped.  It uses a different (and simpler) code
     636        path than the doctest creation functions, so there are a few
     637        files in Sage that it counts incorrectly.
     638
     639        INPUT:
     640
     641        - ``check_extras`` -- bool (default True), whether to check if
     642          doctests are created that don't correspond to either a
     643          ``sage: `` or a ``>>> `` prompt.
     644
     645        - ``verbose`` -- bool (default True), whether to print
     646          offending line numbers when there are missing or extra
     647          tests.
     648
     649        TESTS::
     650
     651            sage: from sage.doctest.sources import FileDocTestSource
     652            sage: sage_loc = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage')
     653            sage: doc_loc = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','doc')
     654            sage: import itertools
     655            sage: for path, dirs, files in itertools.chain(os.walk(sage_loc), os.walk(doc_loc)): # long time
     656            ....:     path = os.path.relpath(path)
     657            ....:     for F in files:
     658            ....:         _, ext = os.path.splitext(F)
     659            ....:         if ext in ('.py', '.pyx', '.sage', '.spyx', '.rst', '.tex'):
     660            ....:             filename = os.path.join(path, F)
     661            ....:             FDS = FileDocTestSource(filename, True, True, True, False)
     662            ....:             FDS._test_enough_doctests(verbose=False)
     663            There are 3 unexpected tests being run in .../sage/doctest/parsing.py
     664            There are 1 tests in .../sage/ext/c_lib.pyx that are not being run
     665            There are 2 tests in .../sage/server/notebook/worksheet.py that are not being run
     666            There are 5 tests in .../doc/en/tutorial/interfaces.rst that are not being run
     667        """
     668        expected = []
     669        rest = isinstance(self, RestSource)
     670        if rest:
     671            skipping = False
     672            in_block = False
     673            last_line = ''
     674        for lineno, line in self:
     675            if not line.strip():
     676                continue
     677            if rest:
     678                if line.strip().startswith(".. nodoctest"):
     679                    return
     680                # We need to track blocks in order to figure out whether we're skipping.
     681                if in_block:
     682                    indent = whitespace.match(line).end()
     683                    if indent <= starting_indent:
     684                        in_block = False
     685                        skipping = False
     686                if not in_block:
     687                    m = double_colon.match(line)
     688                    if m and not line.strip().startswith(".."):
     689                        if ".. skip" in last_line:
     690                            skipping = True
     691                        in_block = True
     692                        starting_indent = whitespace.match(line).end()
     693                last_line = line
     694            ## print (not rest or in_block), bool(sagestart.match(line)), ((rest and skipping) or untested.search(line.lower())), line
     695            if (not rest or in_block) and sagestart.match(line) and not ((rest and skipping) or untested.search(line.lower())):
     696                expected.append(lineno+1)
     697        actual = []
     698        tests, _ = self.create_doctests({})
     699        for dt in tests:
     700            if len(dt.examples) > 0:
     701                for ex in dt.examples[:-1]: # the last entry is a sig_on()
     702                    actual.append(dt.lineno + ex.lineno + 1)
     703        shortfall = sorted(list(set(expected).difference(set(actual))))
     704        extras = sorted(list(set(actual).difference(set(expected))))
     705        if len(actual) == len(expected):
     706            if len(shortfall) == 0: return
     707            dif = extras[0] - shortfall[0]
     708            for e, s in zip(extras[1:],shortfall[1:]):
     709                if dif != e - s:
     710                    break
     711            else:
     712                print "There are %s tests in %s that are shifted by %s"%(len(shortfall),self.path,dif)
     713                if verbose:
     714                    print "    The correct line numbers are %s"%(", ".join([str(n) for n in shortfall]))
     715                return
     716        elif len(actual) < len(expected):
     717            print "There are %s tests in %s that are not being run"%(len(expected) - len(actual), self.path)
     718        elif check_extras:
     719            print "There are %s unexpected tests being run in %s"%(len(actual) - len(expected), self.path)
     720        if verbose:
     721            if shortfall:
     722                print "    Tests on lines %s are not run"%(", ".join([str(n) for n in shortfall]))
     723            if check_extras and extras:
     724                print "    Tests on lines %s seem extraneous"%(", ".join([str(n) for n in extras]))
     725
     726class SourceLanguage:
     727    """
     728    An abstract class for functions that depend on the programming language of a doctest source.
     729
     730    Currently supported languages include Python, ReST and LaTeX.
     731    """
     732    def parse_docstring(self, docstring, namespace, start):
     733        """
     734        Return a list of doctest defined in this docstring.
     735
     736        This function is called by :meth:`DocTestSource._process_doc`.
     737        The default implementation, defined here, is to use the
     738        :class:`sage.doctest.parsing.SageDocTestParser` attached to
     739        this source to get doctests from the docstring.
     740
     741        INPUT:
     742
     743        - ``docstring`` -- a string containing documentation and tests.
     744
     745        - ``namespace`` -- a dictionary or :class:`sage.doctest.util.RecordingDict`.
     746
     747        - ``start`` -- an integer, one less than the starting line number
     748
     749        EXAMPLES::
     750
     751            sage: from sage.doctest.sources import FileDocTestSource
     752            sage: from sage.doctest.parsing import SageDocTestParser
     753            sage: from sage.doctest.util import NestedName
     754            sage: import os
     755            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','util.py')
     756            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     757            sage: doctests, _ = FDS.create_doctests({})
     758            sage: for dt in doctests:
     759            ....:     FDS.qualified_name = dt.name
     760            ....:     dt.examples = dt.examples[:-1] # strip off the sig_on() test
     761            ....:     assert(FDS.parse_docstring(dt.docstring,{},dt.lineno-1)[0] == dt)
     762        """
     763        return [self.parser.get_doctest(docstring, namespace, str(self.qualified_name),
     764                                        self.printpath, start + 1)]
     765
     766class PythonSource(SourceLanguage):
     767    """
     768    This class defines the functions needed for the extraction of doctests from python sources.
     769
     770    EXAMPLES::
     771
     772        sage: from sage.doctest.sources import FileDocTestSource
     773        sage: import os
     774        sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     775        sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     776        sage: type(FDS)
     777        <class 'sage.doctest.sources.PythonFileSource'>
     778    """
     779    # The same line can't both start and end a docstring
     780    start_finish_can_overlap = False
     781
     782    def _init(self):
     783        """
     784        This function is called before creating doctests from a Python source.
     785
     786        EXAMPLES::
     787
     788            sage: from sage.doctest.sources import FileDocTestSource
     789            sage: import os
     790            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     791            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     792            sage: FDS._init()
     793            sage: FDS.last_indent
     794            -1
     795        """
     796        self.last_indent = -1
     797        self.last_line = None
     798        self.quotetype = None
     799        self.paren_count = 0
     800        self.bracket_count = 0
     801        self.curly_count = 0
     802        self.code_wrapping = False
     803
     804    def _update_quotetype(self, line):
     805        """
     806        Updates the track of what kind of quoted string we're in.
     807
     808        We need to track whether we're inside a triple quoted
     809        string, since a triple quoted string that starts a line
     810        could be the end of a string and thus not the beginning of a
     811        doctest (see sage.misc.sageinspect for an example).
     812
     813        To do this tracking we need to track whether we're inside a
     814        string at all, since ''' inside a string doesn't start a
     815        triple quote (see the top of this file for an example).
     816
     817        We also need to track parentheses and brackets, since we only
     818        want to update our record of last line and indentation level
     819        when the line is actually over.
     820
     821        EXAMPLES::
     822
     823            sage: from sage.doctest.sources import FileDocTestSource
     824            sage: import os
     825            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     826            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     827            sage: FDS._init()
     828            sage: FDS._update_quotetype('\"\"\"'); print " ".join(list(FDS.quotetype))
     829            " " "
     830            sage: FDS._update_quotetype("'''"); print " ".join(list(FDS.quotetype))
     831            " " "
     832            sage: FDS._update_quotetype('\"\"\"'); print FDS.quotetype
     833            None
     834            sage: FDS._update_quotetype("triple_quotes = re.compile(\"\\s*[rRuU]*((''')|(\\\"\\\"\\\"))\")")
     835            sage: print FDS.quotetype
     836            None
     837            sage: FDS._update_quotetype("''' Single line triple quoted string \\''''")
     838            sage: print FDS.quotetype
     839            None
     840            sage: FDS._update_quotetype("' Lots of \\\\\\\\'")
     841            sage: print FDS.quotetype
     842            None
     843        """
     844        def _update_parens(start,end=None):
     845            self.paren_count += line.count("(",start,end) - line.count(")",start,end)
     846            self.bracket_count += line.count("[",start,end) - line.count("]",start,end)
     847            self.curly_count += line.count("{",start,end) - line.count("}",start,end)
     848        pos = 0
     849        while pos < len(line):
     850            if self.quotetype is None:
     851                next_single = line.find("'",pos)
     852                next_double = line.find('"',pos)
     853                if next_single == -1 and next_double == -1:
     854                    next_comment = line.find("#",pos)
     855                    if next_comment == -1:
     856                        _update_parens(pos)
     857                    else:
     858                        _update_parens(pos,next_comment)
     859                    break
     860                elif next_single == -1:
     861                    m = next_double
     862                elif next_double == -1:
     863                    m = next_single
     864                else:
     865                    m = min(next_single, next_double)
     866                next_comment = line.find('#',pos,m)
     867                if next_comment != -1:
     868                    _update_parens(pos,next_comment)
     869                    break
     870                _update_parens(pos,m)
     871                if m+2 < len(line) and line[m] == line[m+1] == line[m+2]:
     872                    self.quotetype = line[m:m+3]
     873                    pos = m+3
     874                else:
     875                    self.quotetype = line[m]
     876                    pos = m+1
     877            else:
     878                next = line.find(self.quotetype,pos)
     879                if next == -1:
     880                    break
     881                elif next == 0 or line[next-1] != '\\':
     882                    pos = next + len(self.quotetype)
     883                    self.quotetype = None
     884                else:
     885                    # We need to worry about the possibility that
     886                    # there are an even number of backslashes before
     887                    # the quote, in which case it is not escaped
     888                    count = 1
     889                    slashpos = next - 2
     890                    while slashpos >= pos and line[slashpos] == '\\':
     891                        count += 1
     892                        slashpos -= 1
     893                    if count % 2 == 0:
     894                        pos = next + len(self.quotetype)
     895                        self.quotetype = None
     896                    else:
     897                        # The possible ending quote was escaped.
     898                        pos = next + 1
     899
     900    def starting_docstring(self, line):
     901        """
     902        Determines whether the input line starts a docstring.
     903
     904        If the input line does start a docstring (a triple quote),
     905        then this function updates ``self.qualified_name``.
     906
     907        INPUT:
     908
     909        - ``line`` -- a string, one line of an input file
     910
     911        OUTPUT:
     912
     913        - either None or a Match object.
     914
     915        EXAMPLES::
     916
     917            sage: from sage.doctest.sources import FileDocTestSource
     918            sage: from sage.doctest.util import NestedName
     919            sage: import os
     920            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     921            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     922            sage: FDS._init()
     923            sage: FDS.starting_docstring("r'''")
     924            <_sre.SRE_Match object at ...>
     925            sage: FDS.ending_docstring("'''")
     926            <_sre.SRE_Match object at ...>
     927            sage: FDS.qualified_name = NestedName(FDS.basename)
     928            sage: FDS.starting_docstring("class MyClass(object):")
     929            sage: FDS.starting_docstring("    def hello_world(self):")
     930            sage: FDS.starting_docstring("        '''")
     931            <_sre.SRE_Match object at ...>
     932            sage: FDS.qualified_name
     933            sage.doctest.sources.MyClass.hello_world
     934            sage: FDS.ending_docstring("    '''")
     935            <_sre.SRE_Match object at ...>
     936            sage: FDS.starting_docstring("class NewClass(object):")
     937            sage: FDS.starting_docstring("    '''")
     938            <_sre.SRE_Match object at ...>
     939            sage: FDS.qualified_name
     940            sage.doctest.sources.NewClass
     941        """
     942        indent = whitespace.match(line).end()
     943        quotematch = None
     944        ## print "S", self.quotetype, max(self.paren_count,self.bracket_count,self.curly_count), self.line_shift, line
     945        if self.quotetype is None:
     946            # We're not inside a triple quote
     947            if line[indent] != '#' and (indent == 0 or indent > self.last_indent):
     948                quotematch = triple_quotes.match(line)
     949                # It would be nice to only run the name_regex when
     950                # quotematch wasn't None, but then we mishandle classes
     951                # that don't have a docstring.
     952                if not self.code_wrapping and self.last_indent >= 0 and indent > self.last_indent:
     953                    name = name_regex.match(self.last_line)
     954                    if name:
     955                        name = name.groups()[0]
     956                        self.qualified_name[indent] = name
     957                    elif quotematch:
     958                        self.qualified_name[indent] = '?'
     959        self._update_quotetype(line)
     960        if line[indent] != '#' and not self.code_wrapping:
     961            self.last_line, self.last_indent = line, indent
     962        self.code_wrapping = not (self.paren_count == self.bracket_count == self.curly_count == 0)
     963        return quotematch
     964
     965    def ending_docstring(self, line):
     966        """
     967        Determines whether the input line ends a docstring.
     968
     969        INPUT:
     970
     971        - ``line`` -- a string, one line of an input file.
     972
     973        OUTPUT:
     974
     975        - an object that, when evaluated in a boolean context, gives
     976          True or False depending on whether the input line marks the
     977          end of a docstring.
     978
     979        EXAMPLES::
     980
     981            sage: from sage.doctest.sources import FileDocTestSource
     982            sage: from sage.doctest.util import NestedName
     983            sage: import os
     984            sage: filename = os.path.join(os.environ['SAGE_ROOT'],'devel','sage','sage','doctest','sources.py')
     985            sage: FDS = FileDocTestSource(filename,True,False,set(['sage']),None)
     986            sage: FDS._init()
     987            sage: FDS.quotetype = "'''"
     988            sage: FDS.ending_docstring("'''")
     989            <_sre.SRE_Match object at ...>
     990            sage: FDS.ending_docstring('\"\"\"')
     991        """
     992        quotematch = triple_quotes.match(line)
     993        ## print "E", self.quotetype, max(self.paren_count,self.bracket_count,self.curly_count), self.line_shift, line
     994        if quotematch is not None and quotematch.groups()[0] != self.quotetype:
     995            quotematch = None
     996        self._update_quotetype(line)
     997        return quotematch
     998
     999    def _neutralize_doctests(self, reindent):
     1000        """
     1001        Returns a string containing the source of self, but with
     1002        doctests modified so they aren't tested.
     1003
     1004        This function is used in creating doctests for ReST files,
     1005        since docstrings of Python functions defined inside verbatim
     1006        blocks screw up Python's doctest parsing.
     1007
     1008        INPUT:
     1009
     1010        - ``reindent`` -- an integer, the number of spaces to indent
     1011          the result.
     1012
     1013        EXAMPLES::
     1014
     1015            sage: from sage.doctest.sources import StringDocTestSource, PythonSource
     1016            sage: from sage.structure.dynamic_class import dynamic_class
     1017            sage: s = "'''\n    sage: 2 + 2\n    4\n'''"
     1018            sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
     1019            sage: PSS = PythonStringSource('<runtime>', s, False, set(['sage']), False, 'runtime')
     1020            sage: print PSS._neutralize_doctests(0),
     1021            '''
     1022                safe: 2 + 2
     1023                4
     1024            '''
     1025        """
     1026        neutralized = []
     1027        in_docstring = False
     1028        self._init()
     1029        for lineno, line in self:
     1030            if not line.strip():
     1031                neutralized.append(line)
     1032            elif in_docstring:
     1033                if self.ending_docstring(line):
     1034                    in_docstring = False
     1035                neutralized.append(" "*reindent + find_prompt.sub(r"\1safe:\3",line))
     1036            else:
     1037                if self.starting_docstring(line):
     1038                    in_docstring = True
     1039                neutralized.append(" "*reindent + line)
     1040        return "".join(neutralized)
     1041
     1042class TexSource(SourceLanguage):
     1043    """
     1044    This class defines the functions needed for the extraction of
     1045    doctests from a LaTeX source.
     1046
     1047    EXAMPLES::
     1048
     1049        sage: from sage.doctest.sources import FileDocTestSource
     1050        sage: filename = "sage_paper.tex"
     1051        sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1052        sage: type(FDS)
     1053        <class 'sage.doctest.sources.TexFileSource'>
     1054    """
     1055    # The same line can't both start and end a docstring
     1056    start_finish_can_overlap = False
     1057
     1058    def _init(self):
     1059        """
     1060        This function is called before creating doctests from a Tex file.
     1061
     1062        EXAMPLES::
     1063
     1064            sage: from sage.doctest.sources import FileDocTestSource
     1065            sage: filename = "sage_paper.tex"
     1066            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1067            sage: FDS._init()
     1068            sage: FDS.skipping
     1069            False
     1070        """
     1071        self.skipping = False
     1072
     1073    def starting_docstring(self, line):
     1074        r"""
     1075        Determines whether the input line starts a docstring.
     1076
     1077        Docstring blocks in tex files are defined by verbatim
     1078        environments, and can be linked together by adding %link
     1079        immediately after the \end{verbatim}.
     1080
     1081        Within a verbatim block, you can tell Sage not to
     1082        process the rest of the block by including a %skip line.
     1083
     1084        INPUT:
     1085
     1086        - ``line`` -- a string, one line of an input file
     1087
     1088        OUTPUT:
     1089
     1090        - a boolean giving whether the input line marks the
     1091          start of a docstring (verbatim block).
     1092
     1093        EXAMPLES::
     1094
     1095            sage: from sage.doctest.sources import FileDocTestSource
     1096            sage: filename = "sage_paper.tex"
     1097            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1098            sage: FDS._init()
     1099
     1100        We start docstrings with \begin{verbatim}::
     1101
     1102            sage: FDS.starting_docstring(r"\begin{verbatim}")
     1103            True
     1104            sage: FDS.skipping
     1105            False
     1106            sage: FDS.ending_docstring("sage: 2+2")
     1107            False
     1108            sage: FDS.ending_docstring("4")
     1109            False
     1110
     1111        To start ignoring the rest of the verbatim block, use %skip::
     1112
     1113            sage: FDS.ending_docstring("%skip")
     1114            True
     1115            sage: FDS.skipping
     1116            True
     1117            sage: FDS.starting_docstring("sage: raise RuntimeError")
     1118            False
     1119
     1120        You can even pretend to start another verbatim block while skipping::
     1121
     1122            sage: FDS.starting_docstring(r"\begin{verbatim}")
     1123            False
     1124            sage: FDS.skipping
     1125            True
     1126
     1127        To stop skipping end the verbatim block::
     1128
     1129            sage: FDS.starting_docstring(r"\end{verbatim} %link")
     1130            False
     1131            sage: FDS.skipping
     1132            False
     1133
     1134        Linking works even when the block was ended while skipping::
     1135
     1136            sage: FDS.linking
     1137            True
     1138            sage: FDS.starting_docstring(r"\begin{verbatim}")
     1139            True
     1140        """
     1141        if self.skipping:
     1142            if self.ending_docstring(line, check_skip=False):
     1143                self.skipping = False
     1144            return False
     1145        return bool(begin_verb.match(line))
     1146
     1147    def ending_docstring(self, line, check_skip=True):
     1148        """
     1149        Determines whether the input line ends a docstring.
     1150
     1151        Docstring blocks in tex files are defined by verbatim
     1152        environments, and can be linked together by adding %link
     1153        immediately after the \end{verbatim}.
     1154
     1155        Within a verbatim block, you can tell Sage not to
     1156        process the rest of the block by including a %skip line.
     1157
     1158        INPUT:
     1159
     1160        - ``line`` -- a string, one line of an input file
     1161
     1162        - ``check_skip`` -- boolean (default True), used internally in starting_docstring.
     1163
     1164        OUTPUT:
     1165
     1166        - a boolean giving whether the input line marks the
     1167          end of a docstring (verbatim block).
     1168
     1169        EXAMPLES::
     1170
     1171            sage: from sage.doctest.sources import FileDocTestSource
     1172            sage: filename = "sage_paper.tex"
     1173            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1174            sage: FDS._init()
     1175            sage: FDS.ending_docstring(r"\end{verbatim}")
     1176            True
     1177            sage: FDS.linking
     1178            False
     1179
     1180        Use %link to link with the next verbatim block::
     1181
     1182            sage: FDS.ending_docstring(r"\end{verbatim}%link")
     1183            True
     1184            sage: FDS.linking
     1185            True
     1186
     1187        %skip also ends a docstring block::
     1188
     1189            sage: FDS.ending_docstring("%skip")
     1190            True
     1191        """
     1192        m = end_verb.match(line)
     1193        if m:
     1194            if m.groups()[0]:
     1195                self.linking = True
     1196            else:
     1197                self.linking = False
     1198            return True
     1199        if check_skip and skip.match(line):
     1200            self.skipping = True
     1201            return True
     1202        return False
     1203
     1204class RestSource(SourceLanguage):
     1205    """
     1206    This class defines the functions needed for the extraction of
     1207    doctests from ReST sources.
     1208
     1209    EXAMPLES::
     1210
     1211        sage: from sage.doctest.sources import FileDocTestSource
     1212        sage: filename = "sage_doc.rst"
     1213        sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1214        sage: type(FDS)
     1215        <class 'sage.doctest.sources.RestFileSource'>
     1216    """
     1217    # The same line can both start and end a docstring
     1218    start_finish_can_overlap = True
     1219
     1220    def _init(self):
     1221        """
     1222        This function is called before creating doctests from a ReST file.
     1223
     1224        EXAMPLES::
     1225
     1226            sage: from sage.doctest.sources import FileDocTestSource
     1227            sage: filename = "sage_doc.rst"
     1228            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1229            sage: FDS._init()
     1230            sage: FDS.link_all
     1231            False
     1232        """
     1233        self.link_all = False
     1234        self.last_line = ""
     1235        self.last_indent = -1
     1236        self.first_line = False
     1237        self.skipping = False
     1238
     1239    def starting_docstring(self, line):
     1240        """
     1241        A line ending with a double quote starts a verbatim block in a ReST file.
     1242
     1243        This function also determines whether the docstring block
     1244        should be joined with the previous one, or should be skipped.
     1245
     1246        INPUT:
     1247
     1248        - ``line`` -- a string, one line of an input file
     1249
     1250        OUTPUT:
     1251
     1252        - either None or a Match object.
     1253
     1254        EXAMPLES::
     1255
     1256            sage: from sage.doctest.sources import FileDocTestSource
     1257            sage: filename = "sage_doc.rst"
     1258            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1259            sage: FDS._init()
     1260            sage: FDS.starting_docstring("Hello world::")
     1261            True
     1262            sage: FDS.ending_docstring("    sage: 2 + 2")
     1263            False
     1264            sage: FDS.ending_docstring("    4")
     1265            False
     1266            sage: FDS.ending_docstring("We are now done")
     1267            True
     1268            sage: FDS.starting_docstring(".. link")
     1269            sage: FDS.starting_docstring("::")
     1270            True
     1271            sage: FDS.linking
     1272            True
     1273        """
     1274        if link_all.match(line):
     1275            self.link_all = True
     1276        if self.skipping:
     1277            end_block = self.ending_docstring(line)
     1278            if end_block:
     1279                self.skipping = False
     1280            else:
     1281                ## print "S continuing skip;", self.last_indent, line
     1282                return False
     1283        m = double_colon.match(line)
     1284        ## print "S", m is not None, self.ending_docstring(line), self.last_indent, whitespace.match(line).end(), line
     1285        starting = m and not line.strip().startswith(".. ")
     1286        if starting:
     1287            self.linking = self.link_all or '.. link' in self.last_line
     1288            self.first_line = True
     1289            indent = len(m.groups()[0])
     1290            if '.. skip' in self.last_line:
     1291                ## print "starting skipping"
     1292                self.skipping = True
     1293                starting = False
     1294        else:
     1295            indent = self.last_indent
     1296        self.last_line, self.last_indent = line, indent
     1297        return starting
     1298
     1299    def ending_docstring(self, line):
     1300        """
     1301        When the indentation level drops below the initial level the
     1302        block ends.
     1303
     1304        INPUT:
     1305
     1306        - ``line`` -- a string, one line of an input file
     1307
     1308        OUTPUT:
     1309
     1310        - a boolean, whether the verbatim block is ending.
     1311
     1312        EXAMPLES::
     1313
     1314            sage: from sage.doctest.sources import FileDocTestSource
     1315            sage: filename = "sage_doc.rst"
     1316            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1317            sage: FDS._init()
     1318            sage: FDS.starting_docstring("Hello world::")
     1319            True
     1320            sage: FDS.ending_docstring("    sage: 2 + 2")
     1321            False
     1322            sage: FDS.ending_docstring("    4")
     1323            False
     1324            sage: FDS.ending_docstring("We are now done")
     1325            True
     1326        """
     1327        if not line.strip():
     1328            return False
     1329        indent = whitespace.match(line).end()
     1330        ## print "E", indent, self.last_indent, self.first_line, line
     1331        if self.first_line:
     1332            self.first_line = False
     1333            if indent <= self.last_indent:
     1334                # We didn't indent at all
     1335                return True
     1336            self.last_indent = indent
     1337        return indent < self.last_indent
     1338
     1339    def parse_docstring(self, docstring, namespace, start):
     1340        """
     1341        Return a list of doctest defined in this docstring.
     1342
     1343        Code blocks in a REST file can contain python functions with
     1344        their own docstrings in addition to in-line doctests.  We want
     1345        to include the tests from these inner docstrings, but Python's
     1346        doctesting module has a problem if we just pass on the whole
     1347        block, since it expects to get just a docstring, not the
     1348        Python code as well.
     1349
     1350        Our solution is to create a new doctest source from this code
     1351        block and append the doctests created from that source.  We
     1352        then replace the occurrences of "sage:" and ">>>" occurring
     1353        inside a triple quote with "safe:" so that the doctest module
     1354        doesn't treat them as tests.
     1355
     1356        EXAMPLES::
     1357
     1358            sage: from sage.doctest.sources import FileDocTestSource
     1359            sage: from sage.doctest.parsing import SageDocTestParser
     1360            sage: from sage.doctest.util import NestedName
     1361            sage: filename = "sage_doc.rst"
     1362            sage: FDS = FileDocTestSource(filename,False,False,set(['sage']),None)
     1363            sage: FDS.parser = SageDocTestParser(False, set(['sage']))
     1364            sage: FDS.qualified_name = NestedName('sage_doc')
     1365            sage: s = "Some text::\n\n    def example_python_function(a, \
     1366            ....:      b):\n        '''\n        Brief description \
     1367            ....:      of function.\n\n        EXAMPLES::\n\n            \
     1368            ....:      sage: test1()\n            sage: test2()\n        \
     1369            ....:      '''\n        return a + b\n\n    sage: test3()\n\nMore \
     1370            ....:      ReST documentation."
     1371            sage: tests = FDS.parse_docstring(s, {}, 100)
     1372            sage: len(tests)
     1373            2
     1374            sage: for ex in tests[0].examples:
     1375            ....:     print ex.sage_source,
     1376            test3()
     1377            sage: for ex in tests[1].examples:
     1378            ....:     print ex.sage_source,
     1379            test1()
     1380            test2()
     1381            sig_on_count()
     1382        """
     1383        PythonStringSource = dynamic_class("sage.doctest.sources.PythonStringSource",
     1384                                           (StringDocTestSource, PythonSource))
     1385        min_indent = self.parser._min_indent(docstring)
     1386        pysource = '\n'.join([l[min_indent:] for l in docstring.split('\n')])
     1387        inner_source = PythonStringSource(self.basename, pysource,
     1388                                          self.long, self.optional, self.randorder,
     1389                                          self.printpath, lineno_shift=start+1)
     1390        inner_doctests, _ = inner_source._create_doctests(namespace, True)
     1391        safe_docstring = inner_source._neutralize_doctests(min_indent)
     1392        outer_doctest = self.parser.get_doctest(safe_docstring, namespace,
     1393                                                str(self.qualified_name),
     1394                                                self.printpath, start + 1)
     1395        return [outer_doctest] + inner_doctests
     1396
     1397class DictAsObject(dict):
     1398    """
     1399    A simple subclass of dict that inserts the items from the initializing dictionary into attributes.
     1400
     1401    EXAMPLES::
     1402
     1403        sage: from sage.doctest.sources import DictAsObject
     1404        sage: D = DictAsObject({'a':2})
     1405        sage: D.a
     1406        2
     1407    """
     1408    def __init__(self, attrs):
     1409        """
     1410        Initialization.
     1411
     1412        INPUT:
     1413
     1414        - ``attrs`` -- a dictionary.
     1415
     1416        EXAMPLES::
     1417
     1418            sage: from sage.doctest.sources import DictAsObject
     1419            sage: D = DictAsObject({'a':2})
     1420            sage: D.a == D['a']
     1421            True
     1422            sage: D.a
     1423            2
     1424        """
     1425        super(DictAsObject, self).__init__(attrs)
     1426        self.__dict__.update(attrs)
     1427
     1428    def __setitem__(self, ky, val):
     1429        """
     1430        We preserve the ability to access entries through either the
     1431        dictionary or attribute interfaces.
     1432
     1433        EXAMPLES::
     1434
     1435            sage: from sage.doctest.sources import DictAsObject
     1436            sage: D = DictAsObject({})
     1437            sage: D['a'] = 2
     1438            sage: D.a
     1439            2
     1440        """
     1441        super(DictAsObject, self).__setitem__(ky, val)
     1442        try:
     1443            super(DictAsObject, self).__setattr__(ky, val)
     1444        except TypeError:
     1445            pass
     1446
     1447    def __setattr__(self, ky, val):
     1448        """
     1449        We preserve the ability to access entries through either the
     1450        dictionary or attribute interfaces.
     1451
     1452        EXAMPLES::
     1453
     1454            sage: from sage.doctest.sources import DictAsObject
     1455            sage: D = DictAsObject({})
     1456            sage: D.a = 2
     1457            sage: D['a']
     1458            2
     1459        """
     1460        super(DictAsObject, self).__setitem__(ky, val)
     1461        super(DictAsObject, self).__setattr__(ky, val)
  • new file sage/doctest/util.py

    diff --git a/sage/doctest/util.py b/sage/doctest/util.py
    new file mode 100644
    - +  
     1"""
     2This module contains various utility functions and classes used in doctesting.
     3
     4AUTHORS:
     5
     6- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
     7"""
     8
     9#*****************************************************************************
     10#       Copyright (C) 2012 David Roe <roed.math@gmail.com>
     11#                          Robert Bradshaw <robertwb@gmail.com>
     12#                          William Stein <wstein@gmail.com>
     13#
     14#  Distributed under the terms of the GNU General Public License (GPL)
     15#
     16#                  http://www.gnu.org/licenses/
     17#*****************************************************************************
     18
     19import re
     20from sage.misc.misc import walltime, cputime
     21
     22def count_noun(number, noun, plural=None, pad_number=False, pad_noun=False):
     23    """
     24    EXAMPLES::
     25
     26        sage: from sage.doctest.util import count_noun
     27        sage: count_noun(1, "apple")
     28        '1 apple'
     29        sage: count_noun(1, "apple", pad_noun=True)
     30        '1 apple '
     31        sage: count_noun(1, "apple", pad_number=3)
     32        '  1 apple'
     33        sage: count_noun(2, "orange")
     34        '2 oranges'
     35        sage: count_noun(3, "peach", "peaches")
     36        '3 peaches'
     37        sage: count_noun(1, "peach", plural="peaches", pad_noun=True)
     38        '1 peach  '
     39    """
     40    if pad_noun:
     41        if plural is None:
     42            pad_noun = " "
     43        else:
     44            # We assume that the plural is never shorter than the noun....
     45            pad_noun = " " * (len(plural) - len(noun))
     46    else:
     47        pad_noun = ""
     48    if pad_number:
     49        number_str = ("%%%sd"%pad_number)%number
     50    else:
     51        number_str = "%d"%number
     52    if number == 1:
     53        return "%s %s%s"%(number_str, noun, pad_noun)
     54    else:
     55        if plural is None:
     56            plural = "%ss"%(noun)
     57        return "%s %s"%(number_str, plural)
     58
     59class Timer:
     60    """
     61    A simple timer.
     62
     63    EXAMPLES::
     64
     65        sage: from sage.doctest.util import Timer
     66        sage: Timer()
     67        {}
     68        sage: TestSuite(Timer()).run()
     69    """
     70    def start(self):
     71        """
     72        Start the timer.
     73
     74        EXAMPLES::
     75
     76            sage: from sage.doctest.util import Timer
     77            sage: Timer().start()
     78            {'cputime': ..., 'walltime': ...}
     79        """
     80        self.cputime = cputime()
     81        self.walltime = walltime()
     82        return self
     83
     84    def stop(self):
     85        """
     86        Stops the timer, recording the time that has passed since it
     87        was started.
     88
     89        EXAMPLES::
     90
     91            sage: from sage.doctest.util import Timer
     92            sage: import time
     93            sage: timer = Timer().start()
     94            sage: time.sleep(0.5)
     95            sage: timer.stop()
     96            {'cputime': ..., 'walltime': ...}
     97        """
     98        self.cputime = cputime(self.cputime)
     99        self.walltime = walltime(self.walltime)
     100        return self
     101
     102    def annotate(self, object):
     103        """
     104        Annotates the given object with the cputime and walltime
     105        stored in this timer.
     106
     107        EXAMPLES::
     108
     109            sage: from sage.doctest.util import Timer
     110            sage: Timer().start().annotate(EllipticCurve)
     111            sage: EllipticCurve.cputime # random
     112            2.817255
     113            sage: EllipticCurve.walltime # random
     114            1332649288.410404
     115        """
     116        object.cputime = self.cputime
     117        object.walltime = self.walltime
     118
     119    def __repr__(self):
     120        """
     121        String representation is from the dictionary.
     122
     123        EXAMPLES::
     124
     125            sage: from sage.doctest.util import Timer
     126            sage: repr(Timer().start()) # indirect doctest
     127            "{'cputime': ..., 'walltime': ...}"
     128        """
     129        return str(self)
     130
     131    def __str__(self):
     132        """
     133        String representation is from the dictionary.
     134
     135        EXAMPLES::
     136
     137            sage: from sage.doctest.util import Timer
     138            sage: str(Timer().start()) # indirect doctest
     139            "{'cputime': ..., 'walltime': ...}"
     140        """
     141        return str(self.__dict__)
     142
     143    def __cmp__(self, other):
     144        """
     145        Comparison.
     146
     147        EXAMPLES::
     148
     149            sage: from sage.doctest.util import Timer
     150            sage: Timer() == Timer()
     151            True
     152            sage: t = Timer().start()
     153            sage: loads(dumps(t)) == t
     154            True
     155        """
     156        c = cmp(type(self), type(other))
     157        if c: return c
     158        return cmp(self.__dict__, other.__dict__)
     159
     160# Inheritance rather then delegation as globals() must be a dict
     161class RecordingDict(dict):
     162    """
     163    This dictionary is used for tracking the dependencies of an example.
     164
     165    This feature allows examples in different doctests to be grouped
     166    for better timing data.  It's obtained by recording whenever
     167    anything is set or retrieved from this dictionary.
     168
     169    EXAMPLES::
     170
     171        sage: from sage.doctest.util import RecordingDict
     172        sage: D = RecordingDict(test=17)
     173        sage: D.got
     174        set([])
     175        sage: D['test']
     176        17
     177        sage: D.got
     178        set(['test'])
     179        sage: D.set
     180        set([])
     181        sage: D['a'] = 1
     182        sage: D['a']
     183        1
     184        sage: D.set
     185        set(['a'])
     186        sage: D.got
     187        set(['test'])
     188
     189    TESTS::
     190
     191        sage: TestSuite(D).run()
     192    """
     193    def __init__(self, *args, **kwds):
     194        """
     195        Initialization arguments are the same as for a normal dictionary.
     196
     197        EXAMPLES::
     198
     199            sage: from sage.doctest.util import RecordingDict
     200            sage: D = RecordingDict(d = 42)
     201            sage: D.got
     202            set([])
     203        """
     204        dict.__init__(self, *args, **kwds)
     205        self.start()
     206
     207    def start(self):
     208        """
     209        We track which variables have been set or retrieved.
     210        This function initializes these lists to be empty.
     211
     212        EXAMPLES::
     213
     214            sage: from sage.doctest.util import RecordingDict
     215            sage: D = RecordingDict(d = 42)
     216            sage: D.set
     217            set([])
     218            sage: D['a'] = 4
     219            sage: D.set
     220            set(['a'])
     221            sage: D.start(); D.set
     222            set([])
     223        """
     224        self.set = set([])
     225        self.got = set([])
     226
     227    def __getitem__(self, name):
     228        """
     229        EXAMPLES::
     230
     231            sage: from sage.doctest.util import RecordingDict
     232            sage: D = RecordingDict(d = 42)
     233            sage: D['a'] = 4
     234            sage: D.got
     235            set([])
     236            sage: D['a'] # indirect doctest
     237            4
     238            sage: D.got
     239            set([])
     240            sage: D['d']
     241            42
     242            sage: D.got
     243            set(['d'])
     244        """
     245        if name not in self.set:
     246            self.got.add(name)
     247        return dict.__getitem__(self, name)
     248
     249    def __setitem__(self, name, value):
     250        """
     251        EXAMPLES::
     252
     253            sage: from sage.doctest.util import RecordingDict
     254            sage: D = RecordingDict(d = 42)
     255            sage: D['a'] = 4 # indirect doctest
     256            sage: D.set
     257            set(['a'])
     258        """
     259        self.set.add(name)
     260        dict.__setitem__(self, name, value)
     261   
     262    def __delitem__(self, name):
     263        """
     264        EXAMPLES::
     265
     266            sage: from sage.doctest.util import RecordingDict
     267            sage: D = RecordingDict(d = 42)
     268            sage: del D['d'] # indirect doctest
     269            sage: D.set
     270            set(['d'])
     271        """
     272        self.set.add(name)
     273        dict.__delitem__(self, name)
     274   
     275    def get(self, name, default=None):
     276        """
     277        EXAMPLES::
     278
     279            sage: from sage.doctest.util import RecordingDict
     280            sage: D = RecordingDict(d = 42)
     281            sage: D.get('d')
     282            42
     283            sage: D.got
     284            set(['d'])
     285            sage: D.get('not_here')
     286            sage: sorted(list(D.got))
     287            ['d', 'not_here']
     288        """
     289        if name not in self.set:
     290            self.got.add(name)
     291        return dict.get(self, name, default)
     292   
     293    def copy(self):
     294        """
     295        Note that set and got are not copied.
     296
     297        EXAMPLES::
     298
     299            sage: from sage.doctest.util import RecordingDict
     300            sage: D = RecordingDict(d = 42)
     301            sage: D['a'] = 4
     302            sage: D.set
     303            set(['a'])
     304            sage: E = D.copy()
     305            sage: E.set
     306            set([])
     307            sage: sorted(E.keys())
     308            ['a', 'd']
     309        """
     310        return RecordingDict(dict.copy(self))
     311
     312    def __reduce__(self):
     313        """
     314        Pickling.
     315
     316        EXAMPLES::
     317
     318            sage: from sage.doctest.util import RecordingDict
     319            sage: D = RecordingDict(d = 42)
     320            sage: D['a'] = 4
     321            sage: D.get('not_here')
     322            sage: E = loads(dumps(D))
     323            sage: E.got
     324            set(['not_here'])
     325        """
     326        return make_recording_dict, (dict(self), self.set, self.got)
     327
     328def make_recording_dict(D, st, gt):
     329    """
     330    Auxilliary function for pickling.
     331
     332    EXAMPLES::
     333
     334        sage: from sage.doctest.util import make_recording_dict
     335        sage: D = make_recording_dict({'a':4,'d':42},set([]),set(['not_here']))
     336        sage: sorted(D.items())
     337        [('a', 4), ('d', 42)]
     338        sage: D.got
     339        set(['not_here'])
     340    """
     341    ans = RecordingDict(D)
     342    ans.set = st
     343    ans.got = gt
     344    return ans
     345
     346class NestedName:
     347    """
     348    Class used to construct fully qualified names based on indentation level.
     349
     350    EXAMPLES::
     351
     352        sage: from sage.doctest.util import NestedName
     353        sage: qname = NestedName('sage.categories.algebras')
     354        sage: qname[0] = 'Algebras'; qname
     355        sage.categories.algebras.Algebras
     356        sage: qname[4] = '__contains__'; qname
     357        sage.categories.algebras.Algebras.__contains__
     358        sage: qname[4] = 'ParentMethods'
     359        sage: qname[8] = 'from_base_ring'; qname
     360        sage.categories.algebras.Algebras.ParentMethods.from_base_ring
     361
     362    TESTS::
     363
     364        sage: TestSuite(qname).run()
     365    """
     366    def __init__(self, base):
     367        """
     368        INPUT:
     369
     370        - base -- a string: the name of the module.
     371
     372        EXAMPLES::
     373
     374            sage: from sage.doctest.util import NestedName
     375            sage: qname = NestedName('sage.categories.algebras')
     376            sage: qname
     377            sage.categories.algebras
     378        """
     379        self.all = [base]
     380
     381    def __setitem__(self, index, value):
     382        """
     383        Sets the value at a given indentation level.
     384
     385        INPUT:
     386
     387        - index -- a positive integer, the indentation level (often a multiple of 4, but not necessarily)
     388        - value -- a string, the name of the class or function at that indentation level.
     389
     390        EXAMPLES::
     391
     392            sage: from sage.doctest.util import NestedName
     393            sage: qname = NestedName('sage.categories.algebras')
     394            sage: qname[1] = 'Algebras' # indirect doctest
     395            sage: qname
     396            sage.categories.algebras.Algebras
     397            sage: qname.all
     398            ['sage.categories.algebras', None, 'Algebras']
     399        """
     400        if index < 0:
     401            raise ValueError
     402        while len(self.all) <= index:
     403            self.all.append(None)
     404        self.all[index+1:] = [value]
     405
     406    def __str__(self):
     407        """
     408        Returns a .-separated string giving the full name.
     409
     410        EXAMPLES::
     411
     412            sage: from sage.doctest.util import NestedName
     413            sage: qname = NestedName('sage.categories.algebras')
     414            sage: qname[1] = 'Algebras'
     415            sage: qname[44] = 'at_the_end_of_the_universe'
     416            sage: str(qname) # indirect doctest
     417            'sage.categories.algebras.Algebras.at_the_end_of_the_universe'
     418        """
     419        return self.__repr__()
     420
     421    def __repr__(self):
     422        """
     423        Returns a .-separated string giving the full name.
     424
     425        EXAMPLES::
     426
     427            sage: from sage.doctest.util import NestedName
     428            sage: qname = NestedName('sage.categories.algebras')
     429            sage: qname[1] = 'Algebras'
     430            sage: qname[44] = 'at_the_end_of_the_universe'
     431            sage: print qname # indirect doctest
     432            sage.categories.algebras.Algebras.at_the_end_of_the_universe
     433        """
     434        return '.'.join(a for a in self.all if a is not None)
     435
     436    def __cmp__(self, other):
     437        """
     438        Comparison ist just comparison of the underlying lists.
     439
     440        EXAMPLES::
     441
     442            sage: from sage.doctest.util import NestedName
     443            sage: qname = NestedName('sage.categories.algebras')
     444            sage: qname2 = NestedName('sage.categories.algebras')
     445            sage: qname == qname2
     446            True
     447            sage: qname[0] = 'Algebras'
     448            sage: qname2[2] = 'Algebras'
     449            sage: repr(qname) == repr(qname2)
     450            True
     451            sage: qname == qname2
     452            False
     453        """
     454        c = cmp(type(self), type(other))
     455        if c: return c
     456        return cmp(self.all, other.all)
  • setup.py

    diff --git a/setup.py b/setup.py
    a b  
    894894                     'sage.crypto.public_key',
    895895                     
    896896                     'sage.databases',
     897
     898                     'sage.doctest',
    897899                     
    898900                     'sage.ext',
    899901                     'sage.ext.interpreters',