Skip to content

Commit ac190d8

Browse files
committed
Revert "feat: multiple --concurrency values. nedbat#1012 nedbat#1082"
This reverts commit c9d821d.
1 parent 1a416b6 commit ac190d8

File tree

8 files changed

+65
-90
lines changed

8 files changed

+65
-90
lines changed

coverage/cmdline.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,16 @@ class Opts:
4646
"", "--branch", action="store_true",
4747
help="Measure branch coverage in addition to statement coverage.",
4848
)
49+
CONCURRENCY_CHOICES = [
50+
"thread", "gevent", "greenlet", "eventlet", "multiprocessing",
51+
]
4952
concurrency = optparse.make_option(
50-
"", "--concurrency", action="store", metavar="LIBS",
53+
'', '--concurrency', action='store', metavar="LIB",
54+
choices=CONCURRENCY_CHOICES,
5155
help=(
5256
"Properly measure code using a concurrency library. " +
53-
"Valid values are: {}, or a comma-list of them."
54-
).format(", ".join(sorted(CoverageConfig.CONCURRENCY_CHOICES))),
57+
"Valid values are: {}."
58+
).format(", ".join(CONCURRENCY_CHOICES)),
5559
)
5660
context = optparse.make_option(
5761
"", "--context", action="store", metavar="LABEL",
@@ -647,11 +651,6 @@ def command_line(self, argv: List[str]) -> int:
647651
debug = unshell_list(options.debug)
648652
contexts = unshell_list(options.contexts)
649653

650-
if options.concurrency is not None:
651-
concurrency = options.concurrency.split(",")
652-
else:
653-
concurrency = None
654-
655654
# Do something.
656655
self.coverage = Coverage(
657656
data_file=options.data_file or DEFAULT_DATAFILE,
@@ -664,7 +663,7 @@ def command_line(self, argv: List[str]) -> int:
664663
omit=omit,
665664
include=include,
666665
debug=debug,
667-
concurrency=concurrency,
666+
concurrency=options.concurrency,
668667
check_preimported=True,
669668
context=options.context,
670669
messages=not options.quiet,

coverage/collector.py

Lines changed: 46 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class Collector:
7373
_collectors: List[Collector] = []
7474

7575
# The concurrency settings we support here.
76-
LIGHT_THREADS = {"greenlet", "eventlet", "gevent"}
76+
SUPPORTED_CONCURRENCIES = {"greenlet", "eventlet", "gevent", "thread"}
7777

7878
def __init__(
7979
self,
@@ -119,18 +119,17 @@ def __init__(
119119
120120
`concurrency` is a list of strings indicating the concurrency libraries
121121
in use. Valid values are "greenlet", "eventlet", "gevent", or "thread"
122-
(the default). "thread" can be combined with one of the other three.
123-
Other values are ignored.
122+
(the default). Of these four values, only one can be supplied. Other
123+
values are ignored.
124124
125125
"""
126126
self.should_trace = should_trace
127127
self.check_include = check_include
128128
self.should_start_context = should_start_context
129129
self.file_mapper = file_mapper
130-
self.branch = branch
131130
self.warn = warn
132-
self.concurrency = concurrency
133-
assert isinstance(self.concurrency, list), f"Expected a list: {self.concurrency!r}"
131+
# self.concurrency = concurrency
132+
# assert isinstance(self.concurrency, list), f"Expected a list: {self.concurrency!r}"
134133

135134
self.pid = os.getpid()
136135

@@ -146,6 +145,40 @@ def __init__(
146145
self.file_disposition_class: Type[TFileDisposition]
147146

148147
core: Optional[str]
148+
149+
# We can handle a few concurrency options here, but only one at a time.
150+
these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency)
151+
if len(these_concurrencies) > 1:
152+
raise ConfigError(f"Conflicting concurrency settings: {concurrency}")
153+
self.concurrency = these_concurrencies.pop() if these_concurrencies else ''
154+
155+
try:
156+
if self.concurrency == "greenlet":
157+
import greenlet
158+
self.concur_id_func = greenlet.getcurrent
159+
elif self.concurrency == "eventlet":
160+
import eventlet.greenthread # pylint: disable=import-error,useless-suppression
161+
self.concur_id_func = eventlet.greenthread.getcurrent
162+
elif self.concurrency == "gevent":
163+
import gevent # pylint: disable=import-error,useless-suppression
164+
self.concur_id_func = gevent.getcurrent
165+
elif self.concurrency == "thread" or not self.concurrency:
166+
# It's important to import threading only if we need it. If
167+
# it's imported early, and the program being measured uses
168+
# gevent, then gevent's monkey-patching won't work properly.
169+
import threading
170+
self.threading = threading
171+
else:
172+
raise ConfigError(f"Don't understand concurrency={concurrency}")
173+
except ImportError as ex:
174+
raise ConfigError(
175+
"Couldn't trace with concurrency={}, the module isn't installed.".format(
176+
self.concurrency,
177+
)
178+
) from ex
179+
180+
self.reset()
181+
149182
if timid:
150183
core = "pytrace"
151184
else:
@@ -183,54 +216,6 @@ def __init__(
183216
else:
184217
raise ConfigError(f"Unknown core value: {core!r}")
185218

186-
# We can handle a few concurrency options here, but only one at a time.
187-
concurrencies = set(self.concurrency)
188-
unknown = concurrencies - CoverageConfig.CONCURRENCY_CHOICES
189-
if unknown:
190-
show = ", ".join(sorted(unknown))
191-
raise ConfigError(f"Unknown concurrency choices: {show}")
192-
light_threads = concurrencies & self.LIGHT_THREADS
193-
if len(light_threads) > 1:
194-
show = ", ".join(sorted(light_threads))
195-
raise ConfigError(f"Conflicting concurrency settings: {show}")
196-
do_threading = False
197-
198-
tried = "nothing" # to satisfy pylint
199-
try:
200-
if "greenlet" in concurrencies:
201-
tried = "greenlet"
202-
import greenlet
203-
self.concur_id_func = greenlet.getcurrent
204-
elif "eventlet" in concurrencies:
205-
tried = "eventlet"
206-
import eventlet.greenthread # pylint: disable=import-error,useless-suppression
207-
self.concur_id_func = eventlet.greenthread.getcurrent
208-
elif "gevent" in concurrencies:
209-
tried = "gevent"
210-
import gevent # pylint: disable=import-error,useless-suppression
211-
self.concur_id_func = gevent.getcurrent
212-
213-
if "thread" in concurrencies:
214-
do_threading = True
215-
except ImportError as ex:
216-
msg = f"Couldn't trace with concurrency={tried}, the module isn't installed."
217-
raise ConfigError(msg) from ex
218-
219-
if self.concur_id_func and not hasattr(self._trace_class, "concur_id_func"):
220-
raise ConfigError(
221-
"Can't support concurrency={} with {}, only threads are supported.".format(
222-
tried, self.tracer_name(),
223-
)
224-
)
225-
226-
if do_threading or not concurrencies:
227-
# It's important to import threading only if we need it. If
228-
# it's imported early, and the program being measured uses
229-
# gevent, then gevent's monkey-patching won't work properly.
230-
import threading
231-
self.threading = threading
232-
233-
self.reset()
234219

235220
def __repr__(self) -> str:
236221
return f"<Collector at {id(self):#x}: {self.tracer_name()}>"
@@ -311,6 +296,13 @@ def _start_tracer(self) -> TTraceFn | None:
311296

312297
if hasattr(tracer, 'concur_id_func'):
313298
tracer.concur_id_func = self.concur_id_func
299+
elif self.concur_id_func:
300+
raise ConfigError(
301+
"Can't support concurrency={} with {}, only threads are supported".format(
302+
self.concurrency, self.tracer_name(),
303+
)
304+
)
305+
314306
if hasattr(tracer, 'file_tracers'):
315307
tracer.file_tracers = self.file_tracers
316308
if hasattr(tracer, 'threading'):

coverage/config.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,6 @@ def copy(self) -> CoverageConfig:
358358
"""Return a copy of the configuration."""
359359
return copy.deepcopy(self)
360360

361-
CONCURRENCY_CHOICES = {"thread", "gevent", "greenlet", "eventlet", "multiprocessing"}
362-
363361
CONFIG_FILE_OPTIONS = [
364362
# These are *args for _set_attr_from_config_option:
365363
# (attr, where, type_="")

coverage/control.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ def load(self) -> None:
511511
def _init_for_start(self) -> None:
512512
"""Initialization for start()"""
513513
# Construct the collector.
514-
concurrency: List[str] = self.config.concurrency or []
514+
concurrency = self.config.concurrency or ()
515515
if "multiprocessing" in concurrency:
516516
if self.config.config_file is None:
517517
raise ConfigError("multiprocessing requires a configuration file")

doc/cmd.rst

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ There are many options:
114114
clean each time.
115115
--branch Measure branch coverage in addition to statement
116116
coverage.
117-
--concurrency=LIBS Properly measure code using a concurrency library.
118-
Valid values are: eventlet, gevent, greenlet,
119-
multiprocessing, thread, or a comma-list of them.
117+
--concurrency=LIB Properly measure code using a concurrency library.
118+
Valid values are: thread, gevent, greenlet, eventlet,
119+
multiprocessing.
120120
--context=LABEL The context label to record for this coverage run.
121121
--data-file=OUTFILE Write the recorded coverage data to this file.
122122
Defaults to '.coverage'. [env: COVERAGE_FILE]
@@ -143,7 +143,7 @@ There are many options:
143143
--rcfile=RCFILE Specify configuration file. By default '.coveragerc',
144144
'setup.cfg', 'tox.ini', and 'pyproject.toml' are
145145
tried. [env: COVERAGE_RCFILE]
146-
.. [[[end]]] (checksum: b1a0fffe2768fc142f1d97ae556b621d)
146+
.. [[[end]]] (checksum: 869a31153b3cf401c52523ae9b52c7ab)
147147
148148
If you want :ref:`branch coverage <branch>` measurement, use the ``--branch``
149149
flag. Otherwise only statement coverage is measured.
@@ -165,17 +165,13 @@ but before the program invocation::
165165

166166

167167
Coverage.py can measure multi-threaded programs by default. If you are using
168-
more other concurrency support, with the `multiprocessing`_, `greenlet`_,
169-
`eventlet`_, or `gevent`_ libraries, then coverage.py can get confused. Use the
168+
more exotic concurrency, with the `multiprocessing`_, `greenlet`_, `eventlet`_,
169+
or `gevent`_ libraries, then coverage.py will get very confused. Use the
170170
``--concurrency`` switch to properly measure programs using these libraries.
171171
Give it a value of ``multiprocessing``, ``thread``, ``greenlet``, ``eventlet``,
172172
or ``gevent``. Values other than ``thread`` require the :ref:`C extension
173173
<install_extension>`.
174174

175-
You can combine multiple values for ``--concurrency``, separated with commas.
176-
You can specify ``thread`` and also one of ``eventlet``, ``gevent``, or
177-
``greenlet``.
178-
179175
If you are using ``--concurrency=multiprocessing``, you must set other options
180176
in the configuration file. Options on the command line will not be passed to
181177
the processes that multiprocessing creates. Best practice is to use the

requirements/light-threads.in

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# The light-threads packages we test against.
77

88
eventlet
9-
gevent
109
greenlet
1110

1211
# gevent needs cffi, but only on Windows, not sure why.

tests/test_cmdline.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ def test_run(self) -> None:
727727
cov.save()
728728
""")
729729
self.cmd_executes("run --concurrency=gevent foo.py", """\
730-
cov = Coverage(concurrency=['gevent'])
730+
cov = Coverage(concurrency='gevent')
731731
runner = PyRunner(['foo.py'], as_module=False)
732732
runner.prepare()
733733
cov.start()
@@ -736,16 +736,7 @@ def test_run(self) -> None:
736736
cov.save()
737737
""")
738738
self.cmd_executes("run --concurrency=multiprocessing foo.py", """\
739-
cov = Coverage(concurrency=['multiprocessing'])
740-
runner = PyRunner(['foo.py'], as_module=False)
741-
runner.prepare()
742-
cov.start()
743-
runner.run()
744-
cov.stop()
745-
cov.save()
746-
""")
747-
self.cmd_executes("run --concurrency=gevent,thread foo.py", """\
748-
cov = Coverage(concurrency=['gevent', 'thread'])
739+
cov = Coverage(concurrency='multiprocessing')
749740
runner = PyRunner(['foo.py'], as_module=False)
750741
runner.prepare()
751742
cov.start()

tests/test_concurrency.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import coverage
2525
from coverage import env
2626
from coverage.data import line_counts
27-
from coverage.exceptions import ConfigError
2827
from coverage.files import abs_file
2928
from coverage.misc import import_local_file
3029

@@ -193,7 +192,7 @@ def cant_trace_msg(concurrency: str, the_module: Optional[ModuleType]) -> Option
193192
expected_out = None
194193
else:
195194
expected_out = (
196-
f"Can't support concurrency={concurrency} with PyTracer, only threads are supported.\n"
195+
f"Can't support concurrency={concurrency} with PyTracer, only threads are supported\n"
197196
)
198197
return expected_out
199198

@@ -218,6 +217,7 @@ def try_some_code(
218217
is the text we expect the code to produce.
219218
220219
"""
220+
221221
self.make_file("try_it.py", code)
222222

223223
cmd = f"coverage run --concurrency={concurrency} try_it.py"

0 commit comments

Comments
 (0)