OpenShot Audio Library | OpenShotAudio  0.6.0
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 
23 namespace juce
24 {
25 
26 #if JUCE_UNIT_TESTS
27 
28 class ListenerListTests final : public UnitTest
29 {
30 public:
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 
446 private:
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 
458 static ListenerListTests listenerListTests;
459 
460 #endif
461 
462 } // namespace juce