33
44
55class _AbstractSignalBlocker (object ):
6-
76 """
87 Base class for :class:`SignalBlocker` and :class:`MultiSignalBlocker`.
98
@@ -71,7 +70,6 @@ def __exit__(self, type, value, traceback):
7170
7271
7372class SignalBlocker (_AbstractSignalBlocker ):
74-
7573 """
7674 Returned by :meth:`pytestqt.qtbot.QtBot.waitSignal` method.
7775
@@ -101,10 +99,11 @@ class SignalBlocker(_AbstractSignalBlocker):
10199 .. automethod:: connect
102100 """
103101
104- def __init__ (self , timeout = 1000 , raising = True ):
102+ def __init__ (self , timeout = 1000 , raising = True , check_params_cb = None ):
105103 super (SignalBlocker , self ).__init__ (timeout , raising = raising )
106104 self ._signals = []
107105 self .args = None
106+ self .check_params_callback = check_params_cb
108107
109108 def connect (self , signal ):
110109 """
@@ -123,6 +122,9 @@ def _quit_loop_by_signal(self, *args):
123122 """
124123 quits the event loop and marks that we finished because of a signal.
125124 """
125+ if self .check_params_callback :
126+ if not self .check_params_callback (* args ):
127+ return # parameter check did not pass
126128 try :
127129 self .signal_triggered = True
128130 self .args = list (args )
@@ -138,7 +140,6 @@ def _cleanup(self):
138140
139141
140142class MultiSignalBlocker (_AbstractSignalBlocker ):
141-
142143 """
143144 Returned by :meth:`pytestqt.qtbot.QtBot.waitSignals` method, blocks until
144145 all signals connected to it are triggered or the timeout is reached.
@@ -151,48 +152,121 @@ class MultiSignalBlocker(_AbstractSignalBlocker):
151152 .. automethod:: wait
152153 """
153154
154- def __init__ (self , timeout = 1000 , raising = True ):
155+ def __init__ (self , timeout = 1000 , raising = True , check_params_cbs = None , order = "none" ):
155156 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 ):
160167 """
161168 Adds the given signal to the list of signals which :meth:`wait()` waits
162169 for.
163170
164- :param signal: QtCore.Signal
171+ :param list signals: list of QtCore.Signal`s
165172 """
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 )
170186
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 ):
172197 """
173198 Called when a given signal is emitted.
174199
175200 If all expected signals have been emitted, quits the event loop and
176201 marks that we finished because signals.
177202 """
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 ):
180236 try :
181237 self .signal_triggered = True
182238 self ._cleanup ()
183239 finally :
184240 self ._loop .quit ()
185241
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+
186258 def _cleanup (self ):
187259 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 ]
189263 _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 [:]
192267
193268
194269class SignalEmittedSpy (object ):
195-
196270 """
197271 .. versionadded:: 1.11
198272
0 commit comments