OpenShot Audio Library | OpenShotAudio 0.4.0
 
Loading...
Searching...
No Matches
juce_ListenerList.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26#if JUCE_UNIT_TESTS
27
28class ListenerListTests final : public UnitTest
29{
30public:
31 //==============================================================================
32 class TestListener
33 {
34 public:
35 explicit TestListener (std::function<void()> cb) : callback (std::move (cb)) {}
36
37 void doCallback()
38 {
39 ++numCalls;
40 callback();
41 }
42
43 int getNumCalls() const { return numCalls; }
44
45 private:
46 int numCalls = 0;
47 std::function<void()> callback;
48 };
49
50 class TestObject
51 {
52 public:
53 void addListener (std::function<void()> cb)
54 {
55 listeners.push_back (std::make_unique<TestListener> (std::move (cb)));
56 listenerList.add (listeners.back().get());
57 }
58
59 void removeListener (int i) { listenerList.remove (listeners[(size_t) i].get()); }
60
61 void callListeners()
62 {
63 ++callLevel;
64 listenerList.call ([] (auto& l) { l.doCallback(); });
65 --callLevel;
66 }
67
68 int getNumListeners() const { return (int) listeners.size(); }
69
70 auto& getListener (int i) { return *listeners[(size_t) i]; }
71
72 int getCallLevel() const
73 {
74 return callLevel;
75 }
76
77 bool wereAllNonRemovedListenersCalled (int numCalls) const
78 {
79 return std::all_of (std::begin (listeners),
80 std::end (listeners),
81 [&] (auto& listener)
82 {
83 return (! listenerList.contains (listener.get())) || listener->getNumCalls() == numCalls;
84 });
85 }
86
87 private:
88 std::vector<std::unique_ptr<TestListener>> listeners;
89 ListenerList<TestListener> listenerList;
90 int callLevel = 0;
91 };
92
93 //==============================================================================
94 ListenerListTests() : UnitTest ("ListenerList", UnitTestCategories::containers) {}
95
96 void runTest() override
97 {
98 // This is a test that the pre-iterator adjustment implementation should pass too
99 beginTest ("All non-removed listeners should be called - removing an already called listener");
100 {
101 TestObject test;
102
103 for (int i = 0; i < 20; ++i)
104 {
105 test.addListener ([i, &test]
106 {
107 if (i == 5)
108 test.removeListener (6);
109 });
110 }
111
112 test.callListeners();
113 expect (test.wereAllNonRemovedListenersCalled (1));
114 }
115
116 // Iterator adjustment is necessary for passing this
117 beginTest ("All non-removed listeners should be called - removing a yet uncalled listener");
118 {
119 TestObject test;
120
121 for (int i = 0; i < 20; ++i)
122 {
123 test.addListener ([i, &test]
124 {
125 if (i == 5)
126 test.removeListener (4);
127 });
128 }
129
130 test.callListeners();
131 expect (test.wereAllNonRemovedListenersCalled (1));
132 }
133
134 // This test case demonstrates why we have to call --it.index instead of it.next()
135 beginTest ("All non-removed listeners should be called - one callback removes multiple listeners");
136 {
137 TestObject test;
138
139 for (int i = 0; i < 20; ++i)
140 {
141 test.addListener ([i, &test]
142 {
143 if (i == 19)
144 {
145 test.removeListener (19);
146 test.removeListener (0);
147 }
148 });
149 }
150
151 test.callListeners();
152 expect (test.wereAllNonRemovedListenersCalled (1));
153 }
154
155 beginTest ("All non-removed listeners should be called - removing listeners randomly");
156 {
157 auto random = getRandom();
158
159 for (auto run = 0; run < 10; ++run)
160 {
161 const auto numListeners = random.nextInt ({ 10, 100 });
162 const auto listenersThatRemoveListeners = chooseUnique (random,
163 numListeners,
164 random.nextInt ({ 0, numListeners / 2 }));
165
166 // The listener in position [key] should remove listeners in [value]
167 std::map<int, std::set<int>> removals;
168
169 for (auto i : listenersThatRemoveListeners)
170 {
171 // Random::nextInt ({1, 1}); triggers an assertion
172 removals[i] = chooseUnique (random,
173 numListeners,
174 random.nextInt ({ 1, std::max (2, numListeners / 10) }));
175 }
176
177 TestObject test;
178
179 for (int i = 0; i < numListeners; ++i)
180 {
181 test.addListener ([i, &removals, &test]
182 {
183 const auto iter = removals.find (i);
184
185 if (iter == removals.end())
186 return;
187
188 for (auto j : iter->second)
189 {
190 test.removeListener (j);
191 }
192 });
193 }
194
195 test.callListeners();
196 expect (test.wereAllNonRemovedListenersCalled (1));
197 }
198 }
199
200 // Iterator adjustment is not necessary for passing this
201 beginTest ("All non-removed listeners should be called - add listener during iteration");
202 {
203 TestObject test;
204 const auto numStartingListeners = 20;
205
206 for (int i = 0; i < numStartingListeners; ++i)
207 {
208 test.addListener ([i, &test]
209 {
210 if (i == 5 || i == 6)
211 test.addListener ([] {});
212 });
213 }
214
215 test.callListeners();
216
217 // Only the Listeners added before the test can be expected to have been called
218 bool success = true;
219
220 for (int i = 0; i < numStartingListeners; ++i)
221 success = success && test.getListener (i).getNumCalls() == 1;
222
223 // Listeners added during the iteration must not be called in that iteration
224 for (int i = numStartingListeners; i < test.getNumListeners(); ++i)
225 success = success && test.getListener (i).getNumCalls() == 0;
226
227 expect (success);
228 }
229
230 beginTest ("All non-removed listeners should be called - nested ListenerList::call()");
231 {
232 TestObject test;
233
234 for (int i = 0; i < 20; ++i)
235 {
236 test.addListener ([i, &test]
237 {
238 const auto callLevel = test.getCallLevel();
239
240 if (i == 6 && callLevel == 1)
241 {
242 test.callListeners();
243 }
244
245 if (i == 5)
246 {
247 if (callLevel == 1)
248 test.removeListener (4);
249 else if (callLevel == 2)
250 test.removeListener (6);
251 }
252 });
253 }
254
255 test.callListeners();
256 expect (test.wereAllNonRemovedListenersCalled (2));
257 }
258
259 beginTest ("All non-removed listeners should be called - random ListenerList::call()");
260 {
261 const auto numListeners = 20;
262 auto random = getRandom();
263
264 for (int run = 0; run < 10; ++run)
265 {
266 TestObject test;
267 auto numCalls = 0;
268
269 auto listenersToRemove = chooseUnique (random, numListeners, numListeners / 2);
270
271 for (int i = 0; i < numListeners; ++i)
272 {
273 // Capturing numListeners is a warning on MacOS, not capturing it is an error on Windows
274 test.addListener ([&]
275 {
276 const auto callLevel = test.getCallLevel();
277
278 if (callLevel < 4 && random.nextFloat() < 0.05f)
279 {
280 ++numCalls;
281 test.callListeners();
282 }
283
284 if (random.nextFloat() < 0.5f)
285 {
286 const auto listenerToRemove = random.nextInt ({ 0, numListeners });
287
288 if (listenersToRemove.erase (listenerToRemove) > 0)
289 test.removeListener (listenerToRemove);
290 }
291 });
292 }
293
294 while (listenersToRemove.size() > 0)
295 {
296 test.callListeners();
297 ++numCalls;
298 }
299
300 expect (test.wereAllNonRemovedListenersCalled (numCalls));
301 }
302 }
303
304 beginTest ("Deleting the listener list from a callback");
305 {
306 struct Listener
307 {
308 std::function<void()> onCallback;
309 void notify() { onCallback(); }
310 };
311
312 auto listeners = std::make_unique<juce::ListenerList<Listener>>();
313
314 const auto callback = [&]
315 {
316 expect (listeners != nullptr);
317 listeners.reset();
318 };
319
320 Listener listener1 { callback };
321 Listener listener2 { callback };
322
323 listeners->add (&listener1);
324 listeners->add (&listener2);
325
326 listeners->call (&Listener::notify);
327
328 expect (listeners == nullptr);
329 }
330
331 beginTest ("Using a BailOutChecker");
332 {
333 struct Listener
334 {
335 std::function<void()> onCallback;
336 void notify() { onCallback(); }
337 };
338
339 ListenerList<Listener> listeners;
340
341 bool listener1Called = false;
342 bool listener2Called = false;
343 bool listener3Called = false;
344
345 Listener listener1 { [&]{ listener1Called = true; } };
346 Listener listener2 { [&]{ listener2Called = true; } };
347 Listener listener3 { [&]{ listener3Called = true; } };
348
349 listeners.add (&listener1);
350 listeners.add (&listener2);
351 listeners.add (&listener3);
352
353 struct BailOutChecker
354 {
355 bool& bailOutBool;
356 bool shouldBailOut() const { return bailOutBool; }
357 };
358
359 BailOutChecker bailOutChecker { listener2Called };
360 listeners.callChecked (bailOutChecker, &Listener::notify);
361
362 expect ( listener1Called);
363 expect ( listener2Called);
364 expect (! listener3Called);
365 }
366
367 beginTest ("Using a critical section");
368 {
369 struct Listener
370 {
371 std::function<void()> onCallback;
372 void notify() { onCallback(); }
373 };
374
375 struct TestCriticalSection
376 {
377 TestCriticalSection() { isAlive() = true; }
378 ~TestCriticalSection() { isAlive() = false; }
379
380 static void enter() noexcept { numOutOfScopeCalls() += isAlive() ? 0 : 1; }
381 static void exit() noexcept { numOutOfScopeCalls() += isAlive() ? 0 : 1; }
382
383 static bool tryEnter() noexcept
384 {
385 numOutOfScopeCalls() += isAlive() ? 0 : 1;
386 return true;
387 }
388
389 using ScopedLockType = GenericScopedLock<TestCriticalSection>;
390
391 static bool& isAlive()
392 {
393 static bool inScope = false;
394 return inScope;
395 }
396
397 static int& numOutOfScopeCalls()
398 {
399 static int numOutOfScopeCalls = 0;
400 return numOutOfScopeCalls;
401 }
402 };
403
404 auto listeners = std::make_unique<juce::ListenerList<Listener, Array<Listener*, TestCriticalSection>>>();
405
406 const auto callback = [&]{ listeners.reset(); };
407
408 Listener listener { callback };
409
410 listeners->add (&listener);
411 listeners->call (&Listener::notify);
412
413 expect (listeners == nullptr);
414 expect (TestCriticalSection::numOutOfScopeCalls() == 0);
415 }
416
417 beginTest ("Adding a listener during a callback when one has already been removed");
418 {
419 struct Listener{};
420
421 ListenerList<Listener> listeners;
422 expect (listeners.size() == 0);
423
424 Listener listener;
425 listeners.add (&listener);
426 expect (listeners.size() == 1);
427
428 bool listenerCalled = false;
429
430 listeners.call ([&] (auto& l)
431 {
432 listeners.remove (&l);
433 expect (listeners.size() == 0);
434
435 listeners.add (&l);
436 expect (listeners.size() == 1);
437
438 listenerCalled = true;
439 });
440
441 expect (listenerCalled);
442 expect (listeners.size() == 1);
443 }
444 }
445
446private:
447 static std::set<int> chooseUnique (Random& random, int max, int numChosen)
448 {
449 std::set<int> result;
450
451 while ((int) result.size() < numChosen)
452 result.insert (random.nextInt ({ 0, max }));
453
454 return result;
455 }
456};
457
458static ListenerListTests listenerListTests;
459
460#endif
461
462} // namespace juce