1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - 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 
UnitTest(const String & nm,const String & ctg)26 UnitTest::UnitTest (const String& nm, const String& ctg)
27     : name (nm), category (ctg)
28 {
29     getAllTests().add (this);
30 }
31 
~UnitTest()32 UnitTest::~UnitTest()
33 {
34     getAllTests().removeFirstMatchingValue (this);
35 }
36 
getAllTests()37 Array<UnitTest*>& UnitTest::getAllTests()
38 {
39     static Array<UnitTest*> tests;
40     return tests;
41 }
42 
getTestsInCategory(const String & category)43 Array<UnitTest*> UnitTest::getTestsInCategory (const String& category)
44 {
45     if (category.isEmpty())
46         return getAllTests();
47 
48     Array<UnitTest*> unitTests;
49 
50     for (auto* test : getAllTests())
51         if (test->getCategory() == category)
52             unitTests.add (test);
53 
54     return unitTests;
55 }
56 
getAllCategories()57 StringArray UnitTest::getAllCategories()
58 {
59     StringArray categories;
60 
61     for (auto* test : getAllTests())
62         if (test->getCategory().isNotEmpty())
63             categories.addIfNotAlreadyThere (test->getCategory());
64 
65     return categories;
66 }
67 
initialise()68 void UnitTest::initialise()  {}
shutdown()69 void UnitTest::shutdown()   {}
70 
performTest(UnitTestRunner * const newRunner)71 void UnitTest::performTest (UnitTestRunner* const newRunner)
72 {
73     jassert (newRunner != nullptr);
74     runner = newRunner;
75 
76     initialise();
77     runTest();
78     shutdown();
79 }
80 
logMessage(const String & message)81 void UnitTest::logMessage (const String& message)
82 {
83     // This method's only valid while the test is being run!
84     jassert (runner != nullptr);
85 
86     runner->logMessage (message);
87 }
88 
beginTest(const String & testName)89 void UnitTest::beginTest (const String& testName)
90 {
91     // This method's only valid while the test is being run!
92     jassert (runner != nullptr);
93 
94     runner->beginNewTest (this, testName);
95 }
96 
expect(const bool result,const String & failureMessage)97 void UnitTest::expect (const bool result, const String& failureMessage)
98 {
99     // This method's only valid while the test is being run!
100     jassert (runner != nullptr);
101 
102     if (result)
103         runner->addPass();
104     else
105         runner->addFail (failureMessage);
106 }
107 
getRandom() const108 Random UnitTest::getRandom() const
109 {
110     // This method's only valid while the test is being run!
111     jassert (runner != nullptr);
112 
113     return runner->randomForTest;
114 }
115 
116 //==============================================================================
UnitTestRunner()117 UnitTestRunner::UnitTestRunner() {}
~UnitTestRunner()118 UnitTestRunner::~UnitTestRunner() {}
119 
setAssertOnFailure(bool shouldAssert)120 void UnitTestRunner::setAssertOnFailure (bool shouldAssert) noexcept
121 {
122     assertOnFailure = shouldAssert;
123 }
124 
setPassesAreLogged(bool shouldDisplayPasses)125 void UnitTestRunner::setPassesAreLogged (bool shouldDisplayPasses) noexcept
126 {
127     logPasses = shouldDisplayPasses;
128 }
129 
getNumResults() const130 int UnitTestRunner::getNumResults() const noexcept
131 {
132     return results.size();
133 }
134 
getResult(int index) const135 const UnitTestRunner::TestResult* UnitTestRunner::getResult (int index) const noexcept
136 {
137     return results [index];
138 }
139 
resultsUpdated()140 void UnitTestRunner::resultsUpdated()
141 {
142 }
143 
runTests(const Array<UnitTest * > & tests,int64 randomSeed)144 void UnitTestRunner::runTests (const Array<UnitTest*>& tests, int64 randomSeed)
145 {
146     results.clear();
147     resultsUpdated();
148 
149     if (randomSeed == 0)
150         randomSeed = Random().nextInt (0x7ffffff);
151 
152     randomForTest = Random (randomSeed);
153     logMessage ("Random seed: 0x" + String::toHexString (randomSeed));
154 
155     for (auto* t : tests)
156     {
157         if (shouldAbortTests())
158             break;
159 
160        #if JUCE_EXCEPTIONS_DISABLED
161         t->performTest (this);
162        #else
163         try
164         {
165             t->performTest (this);
166         }
167         catch (...)
168         {
169             addFail ("An unhandled exception was thrown!");
170         }
171        #endif
172     }
173 
174     endTest();
175 }
176 
runAllTests(int64 randomSeed)177 void UnitTestRunner::runAllTests (int64 randomSeed)
178 {
179     runTests (UnitTest::getAllTests(), randomSeed);
180 }
181 
runTestsInCategory(const String & category,int64 randomSeed)182 void UnitTestRunner::runTestsInCategory (const String& category, int64 randomSeed)
183 {
184     runTests (UnitTest::getTestsInCategory (category), randomSeed);
185 }
186 
logMessage(const String & message)187 void UnitTestRunner::logMessage (const String& message)
188 {
189     Logger::writeToLog (message);
190 }
191 
shouldAbortTests()192 bool UnitTestRunner::shouldAbortTests()
193 {
194     return false;
195 }
196 
beginNewTest(UnitTest * const test,const String & subCategory)197 void UnitTestRunner::beginNewTest (UnitTest* const test, const String& subCategory)
198 {
199     endTest();
200     currentTest = test;
201 
202     auto testName = test->getName();
203     results.add (new TestResult (testName, subCategory));
204 
205     logMessage ("-----------------------------------------------------------------");
206     logMessage ("Starting test: " + testName + " / " + subCategory + "...");
207 
208     resultsUpdated();
209 }
210 
endTest()211 void UnitTestRunner::endTest()
212 {
213     if (auto* r = results.getLast())
214     {
215         r->endTime = Time::getCurrentTime();
216 
217         if (r->failures > 0)
218         {
219             String m ("FAILED!!  ");
220             m << r->failures << (r->failures == 1 ? " test" : " tests")
221               << " failed, out of a total of " << (r->passes + r->failures);
222 
223             logMessage (String());
224             logMessage (m);
225             logMessage (String());
226         }
227         else
228         {
229             logMessage ("All tests completed successfully");
230         }
231     }
232 }
233 
addPass()234 void UnitTestRunner::addPass()
235 {
236     {
237         const ScopedLock sl (results.getLock());
238 
239         auto* r = results.getLast();
240         jassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests!
241 
242         r->passes++;
243 
244         if (logPasses)
245         {
246             String message ("Test ");
247             message << (r->failures + r->passes) << " passed";
248             logMessage (message);
249         }
250     }
251 
252     resultsUpdated();
253 }
254 
addFail(const String & failureMessage)255 void UnitTestRunner::addFail (const String& failureMessage)
256 {
257     {
258         const ScopedLock sl (results.getLock());
259 
260         auto* r = results.getLast();
261         jassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests!
262 
263         r->failures++;
264 
265         String message ("!!! Test ");
266         message << (r->failures + r->passes) << " failed";
267 
268         if (failureMessage.isNotEmpty())
269             message << ": " << failureMessage;
270 
271         r->messages.add (message);
272 
273         logMessage (message);
274     }
275 
276     resultsUpdated();
277 
278     if (assertOnFailure) { jassertfalse; }
279 }
280 
281 } // namespace juce
282