diff options
Diffstat (limited to 'coverage')
-rw-r--r-- | coverage/cmdline.py | 16 | ||||
-rw-r--r-- | coverage/collector.py | 101 | ||||
-rw-r--r-- | coverage/config.py | 2 | ||||
-rw-r--r-- | coverage/control.py | 2 | ||||
-rw-r--r-- | coverage/version.py | 2 |
5 files changed, 69 insertions, 54 deletions
diff --git a/coverage/cmdline.py b/coverage/cmdline.py index ae20acc5..ec809330 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -17,6 +17,7 @@ import coverage from coverage import Coverage from coverage import env from coverage.collector import CTracer +from coverage.config import CoverageConfig from coverage.data import combinable_files, debug_data_file from coverage.debug import info_formatter, info_header, short_stack from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource @@ -39,16 +40,12 @@ class Opts: '', '--branch', action='store_true', help="Measure branch coverage in addition to statement coverage.", ) - CONCURRENCY_CHOICES = [ - "thread", "gevent", "greenlet", "eventlet", "multiprocessing", - ] concurrency = optparse.make_option( - '', '--concurrency', action='store', metavar="LIB", - choices=CONCURRENCY_CHOICES, + '', '--concurrency', action='store', metavar="LIBS", help=( "Properly measure code using a concurrency library. " + "Valid values are: {}." - ).format(", ".join(CONCURRENCY_CHOICES)), + ).format(", ".join(sorted(CoverageConfig.CONCURRENCY_CHOICES))), ) context = optparse.make_option( '', '--context', action='store', metavar="LABEL", @@ -570,6 +567,11 @@ class CoverageScript: debug = unshell_list(options.debug) contexts = unshell_list(options.contexts) + if options.concurrency is not None: + concurrency = options.concurrency.split(",") + else: + concurrency = None + # Do something. self.coverage = Coverage( data_suffix=options.parallel_mode, @@ -581,7 +583,7 @@ class CoverageScript: omit=omit, include=include, debug=debug, - concurrency=options.concurrency, + concurrency=concurrency, check_preimported=True, context=options.context, messages=not options.quiet, diff --git a/coverage/collector.py b/coverage/collector.py index 89ba66ba..0397031a 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -7,6 +7,7 @@ import os import sys from coverage import env +from coverage.config import CoverageConfig from coverage.debug import short_stack from coverage.disposition import FileDisposition from coverage.exceptions import ConfigError @@ -55,7 +56,7 @@ class Collector: _collectors = [] # The concurrency settings we support here. - SUPPORTED_CONCURRENCIES = {"greenlet", "eventlet", "gevent", "thread"} + LIGHT_THREADS = {"greenlet", "eventlet", "gevent"} def __init__( self, should_trace, check_include, should_start_context, file_mapper, @@ -93,19 +94,21 @@ class Collector: `concurrency` is a list of strings indicating the concurrency libraries in use. Valid values are "greenlet", "eventlet", "gevent", or "thread" - (the default). Of these four values, only one can be supplied. Other - values are ignored. + (the default). "thread" can be combined with one of the other three. + Other values are ignored. """ self.should_trace = should_trace self.check_include = check_include self.should_start_context = should_start_context self.file_mapper = file_mapper - self.warn = warn self.branch = branch + self.warn = warn + self.concurrency = concurrency + assert isinstance(self.concurrency, list), f"Expected a list: {self.concurrency!r}" + self.threading = None self.covdata = None - self.static_context = None self.origin = short_stack() @@ -113,39 +116,6 @@ class Collector: self.concur_id_func = None self.mapped_file_cache = {} - # We can handle a few concurrency options here, but only one at a time. - these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency) - if len(these_concurrencies) > 1: - raise ConfigError(f"Conflicting concurrency settings: {concurrency}") - self.concurrency = these_concurrencies.pop() if these_concurrencies else '' - - try: - if self.concurrency == "greenlet": - import greenlet - self.concur_id_func = greenlet.getcurrent - elif self.concurrency == "eventlet": - import eventlet.greenthread # pylint: disable=import-error,useless-suppression - self.concur_id_func = eventlet.greenthread.getcurrent - elif self.concurrency == "gevent": - import gevent # pylint: disable=import-error,useless-suppression - self.concur_id_func = gevent.getcurrent - elif self.concurrency == "thread" or not self.concurrency: - # It's important to import threading only if we need it. If - # it's imported early, and the program being measured uses - # gevent, then gevent's monkey-patching won't work properly. - import threading - self.threading = threading - else: - raise ConfigError(f"Don't understand concurrency={concurrency}") - except ImportError as ex: - raise ConfigError( - "Couldn't trace with concurrency={}, the module isn't installed.".format( - self.concurrency, - ) - ) from ex - - self.reset() - if timid: # Being timid: use the simple Python trace function. self._trace_class = PyTracer @@ -163,6 +133,54 @@ class Collector: self.supports_plugins = False self.packed_arcs = False + # We can handle a few concurrency options here, but only one at a time. + concurrencies = set(self.concurrency) + unknown = concurrencies - CoverageConfig.CONCURRENCY_CHOICES + if unknown: + show = ", ".join(sorted(unknown)) + raise ConfigError(f"Unknown concurrency choices: {show}") + light_threads = concurrencies & self.LIGHT_THREADS + if len(light_threads) > 1: + show = ", ".join(sorted(light_threads)) + raise ConfigError(f"Conflicting concurrency settings: {show}") + do_threading = False + + try: + if "greenlet" in concurrencies: + tried = "greenlet" + import greenlet + self.concur_id_func = greenlet.getcurrent + elif "eventlet" in concurrencies: + tried = "eventlet" + import eventlet.greenthread # pylint: disable=import-error,useless-suppression + self.concur_id_func = eventlet.greenthread.getcurrent + elif "gevent" in concurrencies: + tried = "gevent" + import gevent # pylint: disable=import-error,useless-suppression + self.concur_id_func = gevent.getcurrent + + if "thread" in concurrencies: + do_threading = True + except ImportError as ex: + msg = f"Couldn't trace with concurrency={tried}, the module isn't installed." + raise ConfigError(msg) from ex + + if self.concur_id_func and not hasattr(self._trace_class, "concur_id_func"): + raise ConfigError( + "Can't support concurrency={} with {}, only threads are supported.".format( + tried, self.tracer_name(), + ) + ) + + if do_threading or not concurrencies: + # It's important to import threading only if we need it. If + # it's imported early, and the program being measured uses + # gevent, then gevent's monkey-patching won't work properly. + import threading + self.threading = threading + + self.reset() + def __repr__(self): return f"<Collector at 0x{id(self):x}: {self.tracer_name()}>" @@ -244,13 +262,6 @@ class Collector: if hasattr(tracer, 'concur_id_func'): tracer.concur_id_func = self.concur_id_func - elif self.concur_id_func: - raise ConfigError( - "Can't support concurrency={} with {}, only threads are supported".format( - self.concurrency, self.tracer_name(), - ) - ) - if hasattr(tracer, 'file_tracers'): tracer.file_tracers = self.file_tracers if hasattr(tracer, 'threading'): diff --git a/coverage/config.py b/coverage/config.py index 8ed2dee7..9835e341 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -334,6 +334,8 @@ class CoverageConfig: """Return a copy of the configuration.""" return copy.deepcopy(self) + CONCURRENCY_CHOICES = {"thread", "gevent", "greenlet", "eventlet", "multiprocessing"} + CONFIG_FILE_OPTIONS = [ # These are *args for _set_attr_from_config_option: # (attr, where, type_="") diff --git a/coverage/control.py b/coverage/control.py index 00836b3c..99319c05 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -448,7 +448,7 @@ class Coverage: def _init_for_start(self): """Initialization for start()""" # Construct the collector. - concurrency = self.config.concurrency or () + concurrency = self.config.concurrency or [] if "multiprocessing" in concurrency: if not patch_multiprocessing: raise ConfigError( # pragma: only jython diff --git a/coverage/version.py b/coverage/version.py index 394cd076..110eceb7 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -5,7 +5,7 @@ # This file is exec'ed in setup.py, don't import anything! # Same semantics as sys.version_info. -version_info = (6, 1, 3, "alpha", 0) +version_info = (6, 2, 0, "alpha", 0) def _make_version(major, minor, micro, releaselevel, serial): |