3
3
4
4
5
5
class _AbstractSignalBlocker (object ):
6
-
7
6
"""
8
7
Base class for :class:`SignalBlocker` and :class:`MultiSignalBlocker`.
9
8
@@ -71,7 +70,6 @@ def __exit__(self, type, value, traceback):
71
70
72
71
73
72
class SignalBlocker (_AbstractSignalBlocker ):
74
-
75
73
"""
76
74
Returned by :meth:`pytestqt.qtbot.QtBot.waitSignal` method.
77
75
@@ -101,10 +99,11 @@ class SignalBlocker(_AbstractSignalBlocker):
101
99
.. automethod:: connect
102
100
"""
103
101
104
- def __init__ (self , timeout = 1000 , raising = True ):
102
+ def __init__ (self , timeout = 1000 , raising = True , check_params_cb = None ):
105
103
super (SignalBlocker , self ).__init__ (timeout , raising = raising )
106
104
self ._signals = []
107
105
self .args = None
106
+ self .check_params_callback = check_params_cb
108
107
109
108
def connect (self , signal ):
110
109
"""
@@ -123,6 +122,9 @@ def _quit_loop_by_signal(self, *args):
123
122
"""
124
123
quits the event loop and marks that we finished because of a signal.
125
124
"""
125
+ if self .check_params_callback :
126
+ if not self .check_params_callback (* args ):
127
+ return # parameter check did not pass
126
128
try :
127
129
self .signal_triggered = True
128
130
self .args = list (args )
@@ -138,7 +140,6 @@ def _cleanup(self):
138
140
139
141
140
142
class MultiSignalBlocker (_AbstractSignalBlocker ):
141
-
142
143
"""
143
144
Returned by :meth:`pytestqt.qtbot.QtBot.waitSignals` method, blocks until
144
145
all signals connected to it are triggered or the timeout is reached.
@@ -151,48 +152,121 @@ class MultiSignalBlocker(_AbstractSignalBlocker):
151
152
.. automethod:: wait
152
153
"""
153
154
154
- def __init__ (self , timeout = 1000 , raising = True ):
155
+ def __init__ (self , timeout = 1000 , raising = True , check_params_cbs = None , order = "none" ):
155
156
super (MultiSignalBlocker , self ).__init__ (timeout , raising = raising )
156
- self ._signals = {}
157
- self ._slots = {}
158
-
159
- def _add_signal (self , signal ):
157
+ self .order = order
158
+ self .check_params_callbacks = check_params_cbs
159
+ self ._signals_emitted = [] # list of booleans, indicates whether the signal was already emitted
160
+ self ._signals_map = {} # maps from a unique Signal to a list of indices where to expect signal instance emits
161
+ self ._signals = [] # list of all Signals (for compatibility with _AbstractSignalBlocker)
162
+ self ._slots = [] # list of slot functions
163
+ self ._signal_expected_index = 0 # only used when forcing order
164
+ self ._strict_order_violated = False
165
+
166
+ def add_signals (self , signals ):
160
167
"""
161
168
Adds the given signal to the list of signals which :meth:`wait()` waits
162
169
for.
163
170
164
- :param signal: QtCore.Signal
171
+ :param list signals: list of QtCore.Signal`s
165
172
"""
166
- self ._signals [signal ] = False
167
- slot = functools .partial (self ._signal_emitted , signal )
168
- self ._slots [signal ] = slot
169
- signal .connect (slot )
173
+ # determine uniqueness of signals, creating a map that maps from a unique signal to a list of indices
174
+ # (positions) where this signal is expected (in case order matters)
175
+ signals_as_str = [str (signal ) for signal in signals ]
176
+ signal_str_to_signal = {} # maps from a signal-string to one of the signal instances (the first one found)
177
+ for index , signal_str in enumerate (signals_as_str ):
178
+ signal = signals [index ]
179
+ if signal_str not in signal_str_to_signal :
180
+ signal_str_to_signal [signal_str ] = signal
181
+ self ._signals_map [signal ] = [index ] # create a new list
182
+ else :
183
+ # append to existing list
184
+ first_signal_that_occurred = signal_str_to_signal [signal_str ]
185
+ self ._signals_map [first_signal_that_occurred ].append (index )
170
186
171
- def _signal_emitted (self , signal ):
187
+ for signal in signals :
188
+ self ._signals_emitted .append (False )
189
+
190
+ for unique_signal in self ._signals_map :
191
+ slot = functools .partial (self ._signal_emitted , unique_signal )
192
+ self ._slots .append (slot )
193
+ unique_signal .connect (slot )
194
+ self ._signals .append (unique_signal )
195
+
196
+ def _signal_emitted (self , signal , * args ):
172
197
"""
173
198
Called when a given signal is emitted.
174
199
175
200
If all expected signals have been emitted, quits the event loop and
176
201
marks that we finished because signals.
177
202
"""
178
- self ._signals [signal ] = True
179
- if all (self ._signals .values ()):
203
+ if self .order == "none" :
204
+ # perform the test for every matching index (stop after the first one that matches)
205
+ successfully_emitted = False
206
+ successful_index = - 1
207
+ potential_indices = self ._get_unemitted_signal_indices (signal )
208
+ for potential_index in potential_indices :
209
+ if self ._check_callback (potential_index , * args ):
210
+ successful_index = potential_index
211
+ successfully_emitted = True
212
+ break
213
+
214
+ if successfully_emitted :
215
+ self ._signals_emitted [successful_index ] = True
216
+ elif self .order == "simple" :
217
+ potential_indices = self ._get_unemitted_signal_indices (signal )
218
+ if potential_indices :
219
+ if self ._signal_expected_index == potential_indices [0 ]:
220
+ if self ._check_callback (self ._signal_expected_index , * args ):
221
+ self ._signals_emitted [self ._signal_expected_index ] = True
222
+ self ._signal_expected_index += 1
223
+ else : # self.order == "strict"
224
+ if not self ._strict_order_violated :
225
+ # only do the check if the strict order has not been violated yet
226
+ self ._strict_order_violated = True # assume the order has been violated this time
227
+ potential_indices = self ._get_unemitted_signal_indices (signal )
228
+ if potential_indices :
229
+ if self ._signal_expected_index == potential_indices [0 ]:
230
+ if self ._check_callback (self ._signal_expected_index , * args ):
231
+ self ._signals_emitted [self ._signal_expected_index ] = True
232
+ self ._signal_expected_index += 1
233
+ self ._strict_order_violated = False # order has not been violated after all!
234
+
235
+ if not self ._strict_order_violated and all (self ._signals_emitted ):
180
236
try :
181
237
self .signal_triggered = True
182
238
self ._cleanup ()
183
239
finally :
184
240
self ._loop .quit ()
185
241
242
+ def _check_callback (self , index , * args ):
243
+ """
244
+ Checks if there's a callback that evaluates the validity of the parameters. Returns False if there is one
245
+ and its evaluation revealed that the parameters were invalid. Returns True otherwise.
246
+ """
247
+ if self .check_params_callbacks :
248
+ callback_func = self .check_params_callbacks [index ]
249
+ if callback_func :
250
+ if not callback_func (* args ):
251
+ return False
252
+ return True
253
+
254
+ def _get_unemitted_signal_indices (self , signal ):
255
+ """Returns the indices for the provided signal for which NO signal instance has been emitted yet."""
256
+ return [index for index in self ._signals_map [signal ] if self ._signals_emitted [index ] == False ]
257
+
186
258
def _cleanup (self ):
187
259
super (MultiSignalBlocker , self )._cleanup ()
188
- for signal , slot in self ._slots .items ():
260
+ for i in range (len (self ._signals )):
261
+ signal = self ._signals [i ]
262
+ slot = self ._slots [i ]
189
263
_silent_disconnect (signal , slot )
190
- self ._signals .clear ()
191
- self ._slots .clear ()
264
+ del self ._signals_emitted [:]
265
+ self ._signals_map .clear ()
266
+ del self ._slots [:]
192
267
193
268
194
269
class SignalEmittedSpy (object ):
195
-
196
270
"""
197
271
.. versionadded:: 1.11
198
272
0 commit comments