From 2737cb361225c2e8375be8947a0aa36202337419 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 19 Aug 2014 08:41:54 -0400 Subject: Use a set where a set is appropriate --- coverage/control.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'coverage/control.py') diff --git a/coverage/control.py b/coverage/control.py index cb917e52..84e85666 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -186,7 +186,7 @@ class coverage(object): ) # The dirs for files considered "installed with the interpreter". - self.pylib_dirs = [] + self.pylib_dirs = set() if not self.config.cover_pylib: # Look at where some standard modules are located. That's the # indication for "installed with the interpreter". In some @@ -195,9 +195,7 @@ class coverage(object): # we've imported, and take all the different ones. for m in (atexit, os, random, socket, _structseq): if m is not None and hasattr(m, "__file__"): - m_dir = self._canonical_dir(m) - if m_dir not in self.pylib_dirs: - self.pylib_dirs.append(m_dir) + self.pylib_dirs.add(self._canonical_dir(m)) # To avoid tracing the coverage code itself, we skip anything located # where we are. -- cgit v1.2.1 From 2a84eaf05129d335064ffbb75346b5b106a5cfb0 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 19 Aug 2014 13:26:15 -0400 Subject: The class should be called Coverage --- coverage/control.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'coverage/control.py') diff --git a/coverage/control.py b/coverage/control.py index 84e85666..1bfd081d 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -28,14 +28,14 @@ except ImportError: _structseq = None -class coverage(object): +class Coverage(object): """Programmatic access to coverage.py. To use:: from coverage import coverage - cov = coverage() + cov = Coverage() cov.start() #.. call your code .. cov.stop() @@ -822,7 +822,7 @@ def process_startup(): """ cps = os.environ.get("COVERAGE_PROCESS_START") if cps: - cov = coverage(config_file=cps, auto_data=True) + cov = Coverage(config_file=cps, auto_data=True) cov.start() cov._warn_no_data = False cov._warn_unimported_source = False -- cgit v1.2.1 From cb17b350caf9e113b7a3a36c5add5d16a88ea46d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 20 Aug 2014 09:47:21 -0400 Subject: Rename extensions to plugins, since extensions reminds me of parts of filenames --HG-- rename : coverage/extension.py => coverage/plugin.py --- coverage/control.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'coverage/control.py') diff --git a/coverage/control.py b/coverage/control.py index 1bfd081d..deb4e00f 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -9,7 +9,7 @@ from coverage.collector import Collector from coverage.config import CoverageConfig from coverage.data import CoverageData from coverage.debug import DebugControl -from coverage.extension import load_extensions +from coverage.plugin import load_plugins from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher from coverage.files import PathAliases, find_python_files, prep_patterns from coverage.html import HtmlReporter @@ -45,7 +45,7 @@ class Coverage(object): def __init__(self, data_file=None, data_suffix=None, cover_pylib=None, auto_data=False, timid=None, branch=None, config_file=True, source=None, omit=None, include=None, debug=None, - debug_file=None, coroutine=None): + debug_file=None, coroutine=None, plugins=None): """ `data_file` is the base name of the data file to use, defaulting to ".coverage". `data_suffix` is appended (with a dot) to `data_file` to @@ -87,7 +87,9 @@ class Coverage(object): `coroutine` is a string indicating the coroutining library being used in the measured code. Without this, coverage.py will get incorrect results. Valid strings are "greenlet", "eventlet", or "gevent", which - are all equivalent. + are all equivalent. TODO: really? + + `plugins` TODO. """ from coverage import __version__ @@ -126,15 +128,15 @@ class Coverage(object): data_file=data_file, cover_pylib=cover_pylib, timid=timid, branch=branch, parallel=bool_or_none(data_suffix), source=source, omit=omit, include=include, debug=debug, - coroutine=coroutine, + coroutine=coroutine, plugins=plugins, ) # Create and configure the debugging controller. self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) - # Load extensions - tracer_classes = load_extensions(self.config.extensions, "tracer") - self.tracer_extensions = [cls() for cls in tracer_classes] + # Load plugins + tracer_classes = load_plugins(self.config.plugins, "tracer") + self.tracer_plugins = [cls() for cls in tracer_classes] self.auto_data = auto_data @@ -278,8 +280,8 @@ class Coverage(object): canonical = self.file_locator.canonical_filename(filename) - # Try the extensions, see if they have an opinion about the file. - for tracer in self.tracer_extensions: + # Try the plugins, see if they have an opinion about the file. + for tracer in self.tracer_plugins: ext_disp = tracer.should_trace(canonical) if ext_disp: ext_disp.extension = tracer @@ -543,7 +545,7 @@ class Coverage(object): # TODO: seems like this parallel structure is getting kinda old... self.data.add_line_data(self.collector.get_line_data()) self.data.add_arc_data(self.collector.get_arc_data()) - self.data.add_extension_data(self.collector.get_extension_data()) + self.data.add_plugin_data(self.collector.get_plugin_data()) self.collector.reset() # If there are still entries in the source_pkgs list, then we never @@ -611,7 +613,7 @@ class Coverage(object): """ self._harvest_data() if not isinstance(it, CodeUnit): - get_ext = self.data.extension_data().get + get_ext = self.data.plugin_data().get it = code_unit_factory(it, self.file_locator, get_ext)[0] return Analysis(self, it) @@ -784,7 +786,7 @@ class FileDisposition(object): self.original_filename = original_filename self.filename = None self.reason = "" - self.extension = None + self.plugin = None def nope(self, reason): """A helper for returning a NO answer from should_trace.""" -- cgit v1.2.1 From 44238cb66d14a6fb2f4d30f1cef9fdf0bb5f2e2d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 21 Aug 2014 10:35:15 -0400 Subject: More-serious plugin support --- coverage/control.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'coverage/control.py') diff --git a/coverage/control.py b/coverage/control.py index deb4e00f..6d232e5b 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -135,8 +135,8 @@ class Coverage(object): self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) # Load plugins - tracer_classes = load_plugins(self.config.plugins, "tracer") - self.tracer_plugins = [cls() for cls in tracer_classes] + plugins = load_plugins(self.config.plugins, self.config) + self.tracer_plugins = []#[cls() for cls in tracer_classes] self.auto_data = auto_data @@ -282,10 +282,10 @@ class Coverage(object): # Try the plugins, see if they have an opinion about the file. for tracer in self.tracer_plugins: - ext_disp = tracer.should_trace(canonical) - if ext_disp: - ext_disp.extension = tracer - return ext_disp + plugin_disp = tracer.should_trace(canonical) + if plugin_disp: + plugin_disp.plugin = tracer + return plugin_disp # If the user specified source or include, then that's authoritative # about the outer bound of what to measure and we don't have to apply -- cgit v1.2.1 From 7862e1fee15b6ef8fcadfd112cf93121ed8f388e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 29 Aug 2014 07:29:44 -0400 Subject: Minor cleanup of environment variable debugging --- coverage/control.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'coverage/control.py') diff --git a/coverage/control.py b/coverage/control.py index 6d232e5b..fab2ea19 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -1,6 +1,6 @@ """Core control stuff for Coverage.""" -import atexit, os, random, socket, sys +import atexit, os, platform, random, socket, sys from coverage.annotate import AnnotateReporter from coverage.backward import string_class, iitems @@ -195,7 +195,7 @@ class Coverage(object): # environments (virtualenv, for example), these modules may be # spread across a few locations. Look at all the candidate modules # we've imported, and take all the different ones. - for m in (atexit, os, random, socket, _structseq): + for m in (atexit, os, platform, random, socket, _structseq): if m is not None and hasattr(m, "__file__"): self.pylib_dirs.add(self._canonical_dir(m)) @@ -738,7 +738,6 @@ class Coverage(object): """Return a list of (key, value) pairs showing internal information.""" import coverage as covmod - import platform, re try: implementation = platform.python_implementation() @@ -760,10 +759,10 @@ class Coverage(object): ('executable', sys.executable), ('cwd', os.getcwd()), ('path', sys.path), - ('environment', sorted([ + ('environment', sorted( ("%s = %s" % (k, v)) for k, v in iitems(os.environ) - if re.search(r"^COV|^PY", k) - ])), + if k.startswith(("COV", "PY")) + )), ('command_line', " ".join(getattr(sys, 'argv', ['???']))), ] if self.source_match: -- cgit v1.2.1 From 0891e02eb490494ee40a7f840e4ab9fd6b3d2d7b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 6 Sep 2014 14:24:15 -0400 Subject: Move dispositions closer to useful plugins --- coverage/control.py | 133 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 41 deletions(-) (limited to 'coverage/control.py') diff --git a/coverage/control.py b/coverage/control.py index fab2ea19..42550508 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -135,8 +135,14 @@ class Coverage(object): self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) # Load plugins - plugins = load_plugins(self.config.plugins, self.config) - self.tracer_plugins = []#[cls() for cls in tracer_classes] + self.plugins = load_plugins(self.config.plugins, self.config) + + self.trace_judges = [] + for plugin in self.plugins: + trace_judge = plugin.trace_judge() + if trace_judge: + self.trace_judges.append((plugin, trace_judge)) + self.trace_judges.append((None, None)) # The Python case. self.auto_data = auto_data @@ -160,8 +166,11 @@ class Coverage(object): self.include = prep_patterns(self.config.include) self.collector = Collector( - self._should_trace, timid=self.config.timid, - branch=self.config.branch, warn=self._warn, + should_trace=self._should_trace, + check_include=self._tracing_check_include_omit_etc, + timid=self.config.timid, + branch=self.config.branch, + warn=self._warn, coroutine=self.config.coroutine, ) @@ -246,21 +255,11 @@ class Coverage(object): Returns a FileDisposition object. """ - disp = FileDisposition(filename) - - if not filename: - # Empty string is pretty useless - return disp.nope("empty string isn't a filename") - - if filename.startswith('memory:'): - return disp.nope("memory isn't traceable") - - if filename.startswith('<'): - # Lots of non-file execution is represented with artificial - # filenames like "", "", or - # "". Don't ever trace these executions, since we - # can't do anything with the data later anyway. - return disp.nope("not a real filename") + original_filename = filename + def nope(reason): + disp = FileDisposition(original_filename) + disp.nope(reason) + return disp self._check_for_packages() @@ -274,6 +273,20 @@ class Coverage(object): if dunder_file: filename = self._source_for_file(dunder_file) + if not filename: + # Empty string is pretty useless + return nope("empty string isn't a filename") + + if filename.startswith('memory:'): + return nope("memory isn't traceable") + + if filename.startswith('<'): + # Lots of non-file execution is represented with artificial + # filenames like "", "", or + # "". Don't ever trace these executions, since we + # can't do anything with the data later anyway. + return nope("not a real filename") + # Jython reports the .class file to the tracer, use the source file. if filename.endswith("$py.class"): filename = filename[:-9] + ".py" @@ -281,39 +294,60 @@ class Coverage(object): canonical = self.file_locator.canonical_filename(filename) # Try the plugins, see if they have an opinion about the file. - for tracer in self.tracer_plugins: - plugin_disp = tracer.should_trace(canonical) - if plugin_disp: - plugin_disp.plugin = tracer - return plugin_disp + for plugin, judge in self.trace_judges: + if plugin: + disp = judge(canonical) + else: + disp = FileDisposition(original_filename) + if disp is not None: + if plugin: + disp.source_filename = plugin.source_file_name(canonical) + else: + disp.source_filename = canonical + disp.plugin = plugin + + reason = self._check_include_omit_etc(disp.source_filename) + if reason: + disp.nope(reason) + + return disp + + return nope("no plugin found") # TODO: a test that causes this. + def _check_include_omit_etc(self, filename): + """Check a filename against the include, omit, etc, rules. + + Returns a string or None. String means, don't trace, and is the reason + why. None means no reason found to not trace. + + """ # If the user specified source or include, then that's authoritative # about the outer bound of what to measure and we don't have to apply # any canned exclusions. If they didn't, then we have to exclude the # stdlib and coverage.py directories. if self.source_match: - if not self.source_match.match(canonical): - return disp.nope("falls outside the --source trees") + if not self.source_match.match(filename): + return "falls outside the --source trees" elif self.include_match: - if not self.include_match.match(canonical): - return disp.nope("falls outside the --include trees") + if not self.include_match.match(filename): + return "falls outside the --include trees" else: # 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 self.pylib_match and self.pylib_match.match(canonical): - return disp.nope("is in the stdlib") + if self.pylib_match and self.pylib_match.match(filename): + return "is in the stdlib" # We exclude the coverage code itself, since a little of it will be # measured otherwise. - if self.cover_match and self.cover_match.match(canonical): - return disp.nope("is part of coverage.py") + if self.cover_match and self.cover_match.match(filename): + return "is part of coverage.py" # Check the file against the omit pattern. - if self.omit_match and self.omit_match.match(canonical): - return disp.nope("is inside an --omit pattern") + if self.omit_match and self.omit_match.match(filename): + return "is inside an --omit pattern" - disp.filename = canonical - return disp + # No reason found to skip this file. + return None def _should_trace(self, filename, frame): """Decide whether to trace execution in `filename`. @@ -326,6 +360,22 @@ class Coverage(object): self.debug.write(disp.debug_message()) return disp + def _tracing_check_include_omit_etc(self, filename): + """Check a filename against the include, omit, etc, rules, and say so. + + Returns a boolean: True if the file should be traced, False if not. + + """ + reason = self._check_include_omit_etc(filename) + if self.debug.should('trace'): + if not reason: + msg = "Tracing %r" % (filename,) + else: + msg = "Not tracing %r: %s" % (filename, reason) + self.debug.write(msg) + + return not reason + def _warn(self, msg): """Use `msg` as a warning.""" self._warnings.append(msg) @@ -783,21 +833,22 @@ class FileDisposition(object): """A simple object for noting a number of details of files to trace.""" def __init__(self, original_filename): self.original_filename = original_filename - self.filename = None + self.source_filename = None + self.trace = True self.reason = "" self.plugin = None def nope(self, reason): """A helper for returning a NO answer from should_trace.""" + self.trace = False self.reason = reason - return self def debug_message(self): """Produce a debugging message explaining the outcome.""" - if not self.filename: - msg = "Not tracing %r: %s" % (self.original_filename, self.reason) - else: + if self.trace: msg = "Tracing %r" % (self.original_filename,) + else: + msg = "Not tracing %r: %s" % (self.original_filename, self.reason) return msg -- cgit v1.2.1 From 941eeb588459098a85266b4c5d176d6deed2eb53 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 14 Sep 2014 16:44:21 -0400 Subject: Progress on plugins --- coverage/control.py | 68 +++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) (limited to 'coverage/control.py') diff --git a/coverage/control.py b/coverage/control.py index 42550508..86a2ae23 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -9,7 +9,7 @@ from coverage.collector import Collector from coverage.config import CoverageConfig from coverage.data import CoverageData from coverage.debug import DebugControl -from coverage.plugin import load_plugins +from coverage.plugin import Plugins, plugin_implements from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher from coverage.files import PathAliases, find_python_files, prep_patterns from coverage.html import HtmlReporter @@ -135,14 +135,13 @@ class Coverage(object): self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) # Load plugins - self.plugins = load_plugins(self.config.plugins, self.config) + self.plugins = Plugins.load_plugins(self.config.plugins, self.config) self.trace_judges = [] for plugin in self.plugins: - trace_judge = plugin.trace_judge() - if trace_judge: - self.trace_judges.append((plugin, trace_judge)) - self.trace_judges.append((None, None)) # The Python case. + if plugin_implements(plugin, "trace_judge"): + self.trace_judges.append(plugin) + self.trace_judges.append(None) # The Python case. self.auto_data = auto_data @@ -255,10 +254,10 @@ class Coverage(object): Returns a FileDisposition object. """ - original_filename = filename - def nope(reason): - disp = FileDisposition(original_filename) - disp.nope(reason) + disp = FileDisposition(filename) + def nope(disp, reason): + disp.trace = False + disp.reason = reason return disp self._check_for_packages() @@ -275,44 +274,43 @@ class Coverage(object): if not filename: # Empty string is pretty useless - return nope("empty string isn't a filename") + return nope(disp, "empty string isn't a filename") if filename.startswith('memory:'): - return nope("memory isn't traceable") + return nope(disp, "memory isn't traceable") if filename.startswith('<'): # Lots of non-file execution is represented with artificial # filenames like "", "", or # "". Don't ever trace these executions, since we # can't do anything with the data later anyway. - return nope("not a real filename") + return nope(disp, "not a real filename") # Jython reports the .class file to the tracer, use the source file. if filename.endswith("$py.class"): filename = filename[:-9] + ".py" canonical = self.file_locator.canonical_filename(filename) + disp.canonical_filename = canonical # Try the plugins, see if they have an opinion about the file. - for plugin, judge in self.trace_judges: + for plugin in self.trace_judges: if plugin: - disp = judge(canonical) + plugin.trace_judge(disp) else: - disp = FileDisposition(original_filename) - if disp is not None: - if plugin: - disp.source_filename = plugin.source_file_name(canonical) - else: - disp.source_filename = canonical + disp.trace = True + disp.source_filename = canonical + if disp.trace: disp.plugin = plugin - reason = self._check_include_omit_etc(disp.source_filename) - if reason: - disp.nope(reason) + if disp.check_filters: + reason = self._check_include_omit_etc(disp.source_filename) + if reason: + nope(disp, reason) return disp - return nope("no plugin found") # TODO: a test that causes this. + return nope(disp, "no plugin found") # TODO: a test that causes this. def _check_include_omit_etc(self, filename): """Check a filename against the include, omit, etc, rules. @@ -661,10 +659,17 @@ class Coverage(object): Returns an `Analysis` object. """ + def get_plugin(filename): + """For code_unit_factory to use to find the plugin for a file.""" + plugin = None + plugin_name = self.data.plugin_data().get(filename) + if plugin_name: + plugin = self.plugins.get(plugin_name) + return plugin + self._harvest_data() if not isinstance(it, CodeUnit): - get_ext = self.data.plugin_data().get - it = code_unit_factory(it, self.file_locator, get_ext)[0] + it = code_unit_factory(it, self.file_locator, get_plugin)[0] return Analysis(self, it) @@ -833,16 +838,13 @@ class FileDisposition(object): """A simple object for noting a number of details of files to trace.""" def __init__(self, original_filename): self.original_filename = original_filename + self.canonical_filename = original_filename self.source_filename = None - self.trace = True + self.check_filters = True + self.trace = False self.reason = "" self.plugin = None - def nope(self, reason): - """A helper for returning a NO answer from should_trace.""" - self.trace = False - self.reason = reason - def debug_message(self): """Produce a debugging message explaining the outcome.""" if self.trace: -- cgit v1.2.1