DebugClients/Python3/coverage/control.py

changeset 0
de9c2efb9d02
child 29
391dc0bc4ae5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DebugClients/Python3/coverage/control.py	Mon Dec 28 16:03:33 2009 +0000
@@ -0,0 +1,301 @@
+"""Core control stuff for Coverage."""
+
+import os, socket
+
+from .annotate import AnnotateReporter
+from .codeunit import code_unit_factory
+from .collector import Collector
+from .data import CoverageData
+from .files import FileLocator
+from .html import HtmlReporter
+from .misc import format_lines, CoverageException
+from .summary import SummaryReporter
+
+class coverage:
+    """Programmatic access to Coverage.
+
+    To use::
+    
+        from coverage import coverage
+        
+        cov = coverage()
+        cov.start()
+        #.. blah blah (run your code) blah blah
+        cov.stop()
+        cov.html_report(directory='covhtml')
+
+    """
+    def __init__(self, data_file=None, data_suffix=False, cover_pylib=False,
+                auto_data=False):
+        """Create a new coverage measurement context.
+        
+        `data_file` is the base name of the data file to use, defaulting to
+        ".coverage".  `data_suffix` is appended to `data_file` to create the
+        final file name.  If `data_suffix` is simply True, then a suffix is
+        created with the machine and process identity included.
+        
+        `cover_pylib` is a boolean determining whether Python code installed
+        with the Python interpreter is measured.  This includes the Python
+        standard library and any packages installed with the interpreter.
+        
+        If `auto_data` is true, then any existing data file will be read when
+        coverage measurement starts, and data will be saved automatically when
+        measurement stops.
+        
+        """
+        from coverage import __version__
+        
+        self.cover_pylib = cover_pylib
+        self.auto_data = auto_data
+        
+        self.exclude_re = ""
+        self.exclude_list = []
+        
+        self.file_locator = FileLocator()
+        
+        self.collector = Collector(self._should_trace)
+
+        # Create the data file.
+        if data_suffix:
+            if not isinstance(data_suffix, str):
+                # if data_suffix=True, use .machinename.pid
+                data_suffix = ".%s.%s" % (socket.gethostname(), os.getpid())
+        else:
+            data_suffix = None
+
+        self.data = CoverageData(
+            basename=data_file, suffix=data_suffix,
+            collector="coverage v%s" % __version__
+            )
+
+        # The default exclude pattern.
+        self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
+
+        # The prefix for files considered "installed with the interpreter".
+        if not self.cover_pylib:
+            os_file = self.file_locator.canonical_filename(os.__file__)
+            self.pylib_prefix = os.path.split(os_file)[0]
+
+        here = self.file_locator.canonical_filename(__file__)
+        self.cover_prefix = os.path.split(here)[0]
+
+    def _should_trace(self, filename, frame):
+        """Decide whether to trace execution in `filename`
+        
+        Returns a canonicalized filename if it should be traced, False if it
+        should not.
+        
+        """
+        if filename == '<string>':
+            # There's no point in ever tracing string executions, we can't do
+            # anything with the data later anyway.
+            return False
+
+        # Compiled Python files have two filenames: frame.f_code.co_filename is
+        # the filename at the time the .pyc was compiled.  The second name
+        # is __file__, which is where the .pyc was actually loaded from.  Since
+        # .pyc files can be moved after compilation (for example, by being
+        # installed), we look for __file__ in the frame and prefer it to the
+        # co_filename value.
+        dunder_file = frame.f_globals.get('__file__')
+        if dunder_file:
+            if not dunder_file.endswith(".py"):
+                if dunder_file[-4:-1] == ".py":
+                    dunder_file = dunder_file[:-1]
+            filename = dunder_file
+
+        canonical = self.file_locator.canonical_filename(filename)
+
+        # If we aren't supposed to trace installed code, then check if this is
+        # near the Python standard library and skip it if so.
+        if not self.cover_pylib:
+            if canonical.startswith(self.pylib_prefix):
+                return False
+
+        # We exclude the coverage code itself, since a little of it will be
+        # measured otherwise.
+        if canonical.startswith(self.cover_prefix):
+            return False
+
+        return canonical
+
+    def use_cache(self, usecache):
+        """Control the use of a data file (incorrectly called a cache).
+        
+        `usecache` is true or false, whether to read and write data on disk.
+        
+        """
+        self.data.usefile(usecache)
+
+    def load(self):
+        """Load previously-collected coverage data from the data file."""
+        self.collector.reset()
+        self.data.read()
+        
+    def start(self):
+        """Start measuring code coverage."""
+        if self.auto_data:
+            self.load()
+            # Save coverage data when Python exits.
+            import atexit
+            atexit.register(self.save)
+        self.collector.start()
+        
+    def stop(self):
+        """Stop measuring code coverage."""
+        self.collector.stop()
+        self._harvest_data()
+
+    def erase(self):
+        """Erase previously-collected coverage data.
+        
+        This removes the in-memory data collected in this session as well as
+        discarding the data file.
+        
+        """
+        self.collector.reset()
+        self.data.erase()
+
+    def clear_exclude(self):
+        """Clear the exclude list."""
+        self.exclude_list = []
+        self.exclude_re = ""
+
+    def exclude(self, regex):
+        """Exclude source lines from execution consideration.
+        
+        `regex` is a regular expression.  Lines matching this expression are
+        not considered executable when reporting code coverage.  A list of
+        regexes is maintained; this function adds a new regex to the list.
+        Matching any of the regexes excludes a source line.
+        
+        """
+        self.exclude_list.append(regex)
+        self.exclude_re = "(" + ")|(".join(self.exclude_list) + ")"
+
+    def get_exclude_list(self):
+        """Return the list of excluded regex patterns."""
+        return self.exclude_list
+
+    def save(self):
+        """Save the collected coverage data to the data file."""
+        self._harvest_data()
+        self.data.write()
+
+    def combine(self):
+        """Combine together a number of similarly-named coverage data files.
+        
+        All coverage data files whose name starts with `data_file` (from the
+        coverage() constructor) will be read, and combined together into the
+        current measurements.
+        
+        """
+        self.data.combine_parallel_data()
+
+    def _harvest_data(self):
+        """Get the collected data by filename and reset the collector."""
+        self.data.add_line_data(self.collector.data_points())
+        self.collector.reset()
+
+    # Backward compatibility with version 1.
+    def analysis(self, morf):
+        """Like `analysis2` but doesn't return excluded line numbers."""
+        f, s, _, m, mf = self.analysis2(morf)
+        return f, s, m, mf
+
+    def analysis2(self, morf):
+        """Analyze a module.
+        
+        `morf` is a module or a filename.  It will be analyzed to determine
+        its coverage statistics.  The return value is a 5-tuple:
+        
+        * The filename for the module.
+        * A list of line numbers of executable statements.
+        * A list of line numbers of excluded statements.
+        * A list of line numbers of statements not run (missing from execution).
+        * A readable formatted string of the missing line numbers.
+
+        The analysis uses the source file itself and the current measured
+        coverage data.
+
+        """
+        code_unit = code_unit_factory(morf, self.file_locator)[0]
+        st, ex, m, mf = self._analyze(code_unit)
+        return code_unit.filename, st, ex, m, mf
+
+    def _analyze(self, code_unit):
+        """Analyze a single code unit.
+        
+        Returns a 4-tuple: (statements, excluded, missing, missing formatted).
+
+        """
+        from .parser import CodeParser
+
+        filename = code_unit.filename
+        ext = os.path.splitext(filename)[1]
+        source = None
+        if ext == '.py':
+            if not os.path.exists(filename):
+                source = self.file_locator.get_zip_data(filename)
+                if not source:
+                    raise CoverageException(
+                        "No source for code '%s'." % code_unit.filename
+                        )
+
+        parser = CodeParser()
+        statements, excluded, line_map = parser.parse_source(
+            text=source, filename=filename, exclude=self.exclude_re
+            )
+
+        # Identify missing statements.
+        missing = []
+        execed = self.data.executed_lines(filename)
+        for line in statements:
+            lines = line_map.get(line)
+            if lines:
+                for l in range(lines[0], lines[1]+1):
+                    if l in execed:
+                        break
+                else:
+                    missing.append(line)
+            else:
+                if line not in execed:
+                    missing.append(line)
+
+        return (
+            statements, excluded, missing, format_lines(statements, missing)
+            )
+
+    def report(self, morfs=None, show_missing=True, ignore_errors=False,
+                file=None, omit_prefixes=None):     # pylint: disable-msg=W0622
+        """Write a summary report to `file`.
+        
+        Each module in `morfs` is listed, with counts of statements, executed
+        statements, missing statements, and a list of lines missed.
+        
+        """
+        reporter = SummaryReporter(self, show_missing, ignore_errors)
+        reporter.report(morfs, outfile=file, omit_prefixes=omit_prefixes)
+
+    def annotate(self, morfs=None, directory=None, ignore_errors=False,
+                    omit_prefixes=None):
+        """Annotate a list of modules.
+        
+        Each module in `morfs` is annotated.  The source is written to a new
+        file, named with a ",cover" suffix, with each line prefixed with a
+        marker to indicate the coverage of the line.  Covered lines have ">",
+        excluded lines have "-", and missing lines have "!".
+        
+        """
+        reporter = AnnotateReporter(self, ignore_errors)
+        reporter.report(
+            morfs, directory=directory, omit_prefixes=omit_prefixes)
+
+    def html_report(self, morfs=None, directory=None, ignore_errors=False,
+                    omit_prefixes=None):
+        """Generate an HTML report.
+        
+        """
+        reporter = HtmlReporter(self, ignore_errors)
+        reporter.report(
+            morfs, directory=directory, omit_prefixes=omit_prefixes)
\ No newline at end of file

eric ide

mercurial