7
7
import sys
8
8
9
9
from coverage import env
10
+ from coverage .config import CoverageConfig
10
11
from coverage .debug import short_stack
11
12
from coverage .disposition import FileDisposition
12
13
from coverage .exceptions import ConfigError
@@ -55,7 +56,7 @@ class Collector:
55
56
_collectors = []
56
57
57
58
# The concurrency settings we support here.
58
- SUPPORTED_CONCURRENCIES = {"greenlet" , "eventlet" , "gevent" , "thread " }
59
+ LIGHT_THREADS = {"greenlet" , "eventlet" , "gevent" }
59
60
60
61
def __init__ (
61
62
self , should_trace , check_include , should_start_context , file_mapper ,
@@ -93,59 +94,28 @@ def __init__(
93
94
94
95
`concurrency` is a list of strings indicating the concurrency libraries
95
96
in use. Valid values are "greenlet", "eventlet", "gevent", or "thread"
96
- (the default). Of these four values, only one can be supplied. Other
97
- values are ignored.
97
+ (the default). "thread" can be combined with one of the other three.
98
+ Other values are ignored.
98
99
99
100
"""
100
101
self .should_trace = should_trace
101
102
self .check_include = check_include
102
103
self .should_start_context = should_start_context
103
104
self .file_mapper = file_mapper
104
- self .warn = warn
105
105
self .branch = branch
106
+ self .warn = warn
107
+ self .concurrency = concurrency
108
+ assert isinstance (self .concurrency , list ), f"Expected a list: { self .concurrency !r} "
109
+
106
110
self .threading = None
107
111
self .covdata = None
108
-
109
112
self .static_context = None
110
113
111
114
self .origin = short_stack ()
112
115
113
116
self .concur_id_func = None
114
117
self .mapped_file_cache = {}
115
118
116
- # We can handle a few concurrency options here, but only one at a time.
117
- these_concurrencies = self .SUPPORTED_CONCURRENCIES .intersection (concurrency )
118
- if len (these_concurrencies ) > 1 :
119
- raise ConfigError (f"Conflicting concurrency settings: { concurrency } " )
120
- self .concurrency = these_concurrencies .pop () if these_concurrencies else ''
121
-
122
- try :
123
- if self .concurrency == "greenlet" :
124
- import greenlet
125
- self .concur_id_func = greenlet .getcurrent
126
- elif self .concurrency == "eventlet" :
127
- import eventlet .greenthread # pylint: disable=import-error,useless-suppression
128
- self .concur_id_func = eventlet .greenthread .getcurrent
129
- elif self .concurrency == "gevent" :
130
- import gevent # pylint: disable=import-error,useless-suppression
131
- self .concur_id_func = gevent .getcurrent
132
- elif self .concurrency == "thread" or not self .concurrency :
133
- # It's important to import threading only if we need it. If
134
- # it's imported early, and the program being measured uses
135
- # gevent, then gevent's monkey-patching won't work properly.
136
- import threading
137
- self .threading = threading
138
- else :
139
- raise ConfigError (f"Don't understand concurrency={ concurrency } " )
140
- except ImportError as ex :
141
- raise ConfigError (
142
- "Couldn't trace with concurrency={}, the module isn't installed." .format (
143
- self .concurrency ,
144
- )
145
- ) from ex
146
-
147
- self .reset ()
148
-
149
119
if timid :
150
120
# Being timid: use the simple Python trace function.
151
121
self ._trace_class = PyTracer
@@ -163,6 +133,54 @@ def __init__(
163
133
self .supports_plugins = False
164
134
self .packed_arcs = False
165
135
136
+ # We can handle a few concurrency options here, but only one at a time.
137
+ concurrencies = set (self .concurrency )
138
+ unknown = concurrencies - CoverageConfig .CONCURRENCY_CHOICES
139
+ if unknown :
140
+ show = ", " .join (sorted (unknown ))
141
+ raise ConfigError (f"Unknown concurrency choices: { show } " )
142
+ light_threads = concurrencies & self .LIGHT_THREADS
143
+ if len (light_threads ) > 1 :
144
+ show = ", " .join (sorted (light_threads ))
145
+ raise ConfigError (f"Conflicting concurrency settings: { show } " )
146
+ do_threading = False
147
+
148
+ try :
149
+ if "greenlet" in concurrencies :
150
+ tried = "greenlet"
151
+ import greenlet
152
+ self .concur_id_func = greenlet .getcurrent
153
+ elif "eventlet" in concurrencies :
154
+ tried = "eventlet"
155
+ import eventlet .greenthread # pylint: disable=import-error,useless-suppression
156
+ self .concur_id_func = eventlet .greenthread .getcurrent
157
+ elif "gevent" in concurrencies :
158
+ tried = "gevent"
159
+ import gevent # pylint: disable=import-error,useless-suppression
160
+ self .concur_id_func = gevent .getcurrent
161
+
162
+ if "thread" in concurrencies :
163
+ do_threading = True
164
+ except ImportError as ex :
165
+ msg = f"Couldn't trace with concurrency={ tried } , the module isn't installed."
166
+ raise ConfigError (msg ) from ex
167
+
168
+ if self .concur_id_func and not hasattr (self ._trace_class , "concur_id_func" ):
169
+ raise ConfigError (
170
+ "Can't support concurrency={} with {}, only threads are supported." .format (
171
+ tried , self .tracer_name (),
172
+ )
173
+ )
174
+
175
+ if do_threading or not concurrencies :
176
+ # It's important to import threading only if we need it. If
177
+ # it's imported early, and the program being measured uses
178
+ # gevent, then gevent's monkey-patching won't work properly.
179
+ import threading
180
+ self .threading = threading
181
+
182
+ self .reset ()
183
+
166
184
def __repr__ (self ):
167
185
return f"<Collector at 0x{ id (self ):x} : { self .tracer_name ()} >"
168
186
@@ -244,13 +262,6 @@ def _start_tracer(self):
244
262
245
263
if hasattr (tracer , 'concur_id_func' ):
246
264
tracer .concur_id_func = self .concur_id_func
247
- elif self .concur_id_func :
248
- raise ConfigError (
249
- "Can't support concurrency={} with {}, only threads are supported" .format (
250
- self .concurrency , self .tracer_name (),
251
- )
252
- )
253
-
254
265
if hasattr (tracer , 'file_tracers' ):
255
266
tracer .file_tracers = self .file_tracers
256
267
if hasattr (tracer , 'threading' ):
0 commit comments