The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #if JUCE_ENABLE_ALLOCATION_HOOKS
  19. #define JUCE_FAIL_ON_ALLOCATION_IN_SCOPE const UnitTestAllocationChecker checker (*this)
  20. #else
  21. #define JUCE_FAIL_ON_ALLOCATION_IN_SCOPE
  22. #endif
  23. namespace juce
  24. {
  25. namespace dsp
  26. {
  27. namespace
  28. {
  29. class ConstructCounts
  30. {
  31. auto tie() const noexcept { return std::tie (constructions, copies, moves, calls, destructions); }
  32. public:
  33. int constructions = 0;
  34. int copies = 0;
  35. int moves = 0;
  36. int calls = 0;
  37. int destructions = 0;
  38. ConstructCounts withConstructions (int i) const noexcept { auto c = *this; c.constructions = i; return c; }
  39. ConstructCounts withCopies (int i) const noexcept { auto c = *this; c.copies = i; return c; }
  40. ConstructCounts withMoves (int i) const noexcept { auto c = *this; c.moves = i; return c; }
  41. ConstructCounts withCalls (int i) const noexcept { auto c = *this; c.calls = i; return c; }
  42. ConstructCounts withDestructions (int i) const noexcept { auto c = *this; c.destructions = i; return c; }
  43. bool operator== (const ConstructCounts& other) const noexcept { return tie() == other.tie(); }
  44. bool operator!= (const ConstructCounts& other) const noexcept { return tie() != other.tie(); }
  45. };
  46. String& operator<< (String& str, const ConstructCounts& c)
  47. {
  48. return str << "{ constructions: " << c.constructions
  49. << ", copies: " << c.copies
  50. << ", moves: " << c.moves
  51. << ", calls: " << c.calls
  52. << ", destructions: " << c.destructions
  53. << " }";
  54. }
  55. class FixedSizeFunctionTest : public UnitTest
  56. {
  57. static void toggleBool (bool& b) { b = ! b; }
  58. struct ConstructCounter
  59. {
  60. explicit ConstructCounter (ConstructCounts& countsIn)
  61. : counts (countsIn) {}
  62. ConstructCounter (const ConstructCounter& c)
  63. : counts (c.counts)
  64. {
  65. counts.copies += 1;
  66. }
  67. ConstructCounter (ConstructCounter&& c) noexcept
  68. : counts (c.counts)
  69. {
  70. counts.moves += 1;
  71. }
  72. ~ConstructCounter() noexcept { counts.destructions += 1; }
  73. void operator()() const noexcept { counts.calls += 1; }
  74. ConstructCounts& counts;
  75. };
  76. public:
  77. FixedSizeFunctionTest()
  78. : UnitTest ("Fixed Size Function", UnitTestCategories::dsp)
  79. {}
  80. void runTest() override
  81. {
  82. beginTest ("Can be constructed and called from a lambda");
  83. {
  84. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  85. const auto result = 5;
  86. bool wasCalled = false;
  87. const auto lambda = [&] { wasCalled = true; return result; };
  88. const FixedSizeFunction<sizeof (lambda), int()> fn (lambda);
  89. const auto out = fn();
  90. expect (wasCalled);
  91. expectEquals (result, out);
  92. }
  93. beginTest ("void fn can be constructed from function with return value");
  94. {
  95. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  96. bool wasCalled = false;
  97. const auto lambda = [&] { wasCalled = true; return 5; };
  98. const FixedSizeFunction<sizeof (lambda), void()> fn (lambda);
  99. fn();
  100. expect (wasCalled);
  101. }
  102. beginTest ("Can be constructed and called from a function pointer");
  103. {
  104. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  105. bool state = false;
  106. const FixedSizeFunction<sizeof (void*), void (bool&)> fn (toggleBool);
  107. fn (state);
  108. expect (state);
  109. fn (state);
  110. expect (! state);
  111. fn (state);
  112. expect (state);
  113. }
  114. beginTest ("Default constructed functions throw if called");
  115. {
  116. const auto a = FixedSizeFunction<8, void()>();
  117. expectThrowsType (a(), std::bad_function_call)
  118. const auto b = FixedSizeFunction<8, void()> (nullptr);
  119. expectThrowsType (b(), std::bad_function_call)
  120. }
  121. beginTest ("Functions can be moved");
  122. {
  123. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  124. ConstructCounts counts;
  125. auto a = FixedSizeFunction<sizeof (ConstructCounter), void()> (ConstructCounter { counts });
  126. expectEquals (counts, ConstructCounts().withMoves (1).withDestructions (1)); // The temporary gets destroyed
  127. a();
  128. expectEquals (counts, ConstructCounts().withMoves (1).withDestructions (1).withCalls (1));
  129. const auto b = std::move (a);
  130. expectEquals (counts, ConstructCounts().withMoves (2).withDestructions (1).withCalls (1));
  131. b();
  132. expectEquals (counts, ConstructCounts().withMoves (2).withDestructions (1).withCalls (2));
  133. b();
  134. expectEquals (counts, ConstructCounts().withMoves (2).withDestructions (1).withCalls (3));
  135. }
  136. beginTest ("Functions are destructed properly");
  137. {
  138. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  139. ConstructCounts counts;
  140. const ConstructCounter toCopy { counts };
  141. {
  142. auto a = FixedSizeFunction<sizeof (ConstructCounter), void()> (toCopy);
  143. expectEquals (counts, ConstructCounts().withCopies (1));
  144. }
  145. expectEquals (counts, ConstructCounts().withCopies (1).withDestructions (1));
  146. }
  147. beginTest ("Avoid destructing functions that fail to construct");
  148. {
  149. struct BadConstructor
  150. {
  151. explicit BadConstructor (ConstructCounts& c)
  152. : counts (c)
  153. {
  154. counts.constructions += 1;
  155. throw std::runtime_error { "this was meant to happen" };
  156. }
  157. BadConstructor (const BadConstructor&) = default;
  158. BadConstructor& operator= (const BadConstructor&) = delete;
  159. ~BadConstructor() noexcept { counts.destructions += 1; }
  160. void operator()() const noexcept { counts.calls += 1; }
  161. ConstructCounts& counts;
  162. };
  163. ConstructCounts counts;
  164. expectThrowsType ((FixedSizeFunction<sizeof (BadConstructor), void()> (BadConstructor { counts })),
  165. std::runtime_error)
  166. expectEquals (counts, ConstructCounts().withConstructions (1));
  167. }
  168. beginTest ("Equality checks work");
  169. {
  170. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  171. FixedSizeFunction<8, void()> a;
  172. expect (! bool (a));
  173. expect (a == nullptr);
  174. expect (nullptr == a);
  175. expect (! (a != nullptr));
  176. expect (! (nullptr != a));
  177. FixedSizeFunction<8, void()> b ([] {});
  178. expect (bool (b));
  179. expect (b != nullptr);
  180. expect (nullptr != b);
  181. expect (! (b == nullptr));
  182. expect (! (nullptr == b));
  183. }
  184. beginTest ("Functions can be cleared");
  185. {
  186. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  187. FixedSizeFunction<8, void()> fn ([] {});
  188. expect (bool (fn));
  189. fn = nullptr;
  190. expect (! bool (fn));
  191. }
  192. beginTest ("Functions can be assigned");
  193. {
  194. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  195. using Fn = FixedSizeFunction<8, void()>;
  196. int numCallsA = 0;
  197. int numCallsB = 0;
  198. Fn x;
  199. Fn y;
  200. expect (! bool (x));
  201. expect (! bool (y));
  202. x = [&] { numCallsA += 1; };
  203. y = [&] { numCallsB += 1; };
  204. expect (bool (x));
  205. expect (bool (y));
  206. x();
  207. expectEquals (numCallsA, 1);
  208. expectEquals (numCallsB, 0);
  209. y();
  210. expectEquals (numCallsA, 1);
  211. expectEquals (numCallsB, 1);
  212. x = std::move (y);
  213. expectEquals (numCallsA, 1);
  214. expectEquals (numCallsB, 1);
  215. x();
  216. expectEquals (numCallsA, 1);
  217. expectEquals (numCallsB, 2);
  218. }
  219. beginTest ("Functions may mutate internal state");
  220. {
  221. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  222. using Fn = FixedSizeFunction<64, void()>;
  223. Fn x;
  224. expect (! bool (x));
  225. int numCalls = 0;
  226. x = [&numCalls, counter = 0]() mutable { counter += 1; numCalls = counter; };
  227. expect (bool (x));
  228. expectEquals (numCalls, 0);
  229. x();
  230. expectEquals (numCalls, 1);
  231. x();
  232. expectEquals (numCalls, 2);
  233. }
  234. beginTest ("Functions can sink move-only parameters");
  235. {
  236. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  237. using Fn = FixedSizeFunction<64, int (std::unique_ptr<int>)>;
  238. auto value = 5;
  239. auto ptr = std::make_unique<int> (value);
  240. Fn fn = [] (std::unique_ptr<int> p) { return *p; };
  241. expect (value == fn (std::move (ptr)));
  242. }
  243. beginTest ("Functions be converted from smaller functions");
  244. {
  245. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  246. using SmallFn = FixedSizeFunction<20, void()>;
  247. using LargeFn = FixedSizeFunction<21, void()>;
  248. bool smallCalled = false;
  249. bool largeCalled = false;
  250. SmallFn small = [&smallCalled, a = std::array<char, 8>{}] { smallCalled = true; ignoreUnused (a); };
  251. LargeFn large = [&largeCalled, a = std::array<char, 8>{}] { largeCalled = true; ignoreUnused (a); };
  252. large = std::move (small);
  253. large();
  254. expect (smallCalled);
  255. expect (! largeCalled);
  256. }
  257. }
  258. };
  259. FixedSizeFunctionTest fixedSizedFunctionTest;
  260. }
  261. }
  262. }
  263. #undef JUCE_FAIL_ON_ALLOCATION_IN_SCOPE