1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE examples.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    The code included in this file is provided under the terms of the ISC license
8    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
9    To use, copy, modify, and/or distribute this software for any purpose with or
10    without fee is hereby granted provided that the above copyright notice and
11    this permission notice appear in all copies.
12 
13    THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
14    WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
15    PURPOSE, ARE DISCLAIMED.
16 
17   ==============================================================================
18 */
19 
20 /*******************************************************************************
21  The block below describes the properties of this PIP. A PIP is a short snippet
22  of code that can be read by the Projucer and used to generate a JUCE project.
23 
24  BEGIN_JUCE_PIP_METADATA
25 
26  name:             UnitTestsDemo
27  version:          1.0.0
28  vendor:           JUCE
29  website:          http://juce.com
30  description:      Performs unit tests.
31 
32  dependencies:     juce_analytics, juce_audio_basics, juce_audio_devices,
33                    juce_audio_formats, juce_audio_processors, juce_audio_utils,
34                    juce_core, juce_cryptography, juce_data_structures, juce_dsp,
35                    juce_events, juce_graphics, juce_gui_basics, juce_gui_extra,
36                    juce_opengl, juce_osc, juce_product_unlocking, juce_video
37  exporters:        xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
38 
39  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
40  defines:          JUCE_UNIT_TESTS=1
41 
42  type:             Component
43  mainClass:        UnitTestsDemo
44 
45  useLocalCopy:     1
46 
47  END_JUCE_PIP_METADATA
48 
49 *******************************************************************************/
50 
51 #pragma once
52 
53 #include "../Assets/DemoUtilities.h"
54 
55 //==============================================================================
56 class UnitTestsDemo  : public Component
57 {
58 public:
UnitTestsDemo()59     UnitTestsDemo()
60     {
61         setOpaque (true);
62 
63         addAndMakeVisible (startTestButton);
64         startTestButton.onClick = [this] { start(); };
65 
66         addAndMakeVisible (testResultsBox);
67         testResultsBox.setMultiLine (true);
68         testResultsBox.setFont (Font (Font::getDefaultMonospacedFontName(), 12.0f, Font::plain));
69 
70         addAndMakeVisible (categoriesBox);
71         categoriesBox.addItem ("All Tests", 1);
72 
73         auto categories = UnitTest::getAllCategories();
74         categories.sort (true);
75 
76         categoriesBox.addItemList (categories, 2);
77         categoriesBox.setSelectedId (1);
78 
79         logMessage ("This panel runs the built-in JUCE unit-tests from the selected category.\n");
80         logMessage ("To add your own unit-tests, see the JUCE_UNIT_TESTS macro.");
81 
82         setSize (500, 500);
83     }
84 
~UnitTestsDemo()85     ~UnitTestsDemo() override
86     {
87         stopTest();
88     }
89 
90     //==============================================================================
paint(Graphics & g)91     void paint (Graphics& g) override
92     {
93         g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
94                                            Colours::grey));
95     }
96 
resized()97     void resized() override
98     {
99         auto bounds = getLocalBounds().reduced (6);
100 
101         auto topSlice = bounds.removeFromTop (25);
102         startTestButton.setBounds (topSlice.removeFromLeft (200));
103         topSlice.removeFromLeft (10);
104         categoriesBox  .setBounds (topSlice.removeFromLeft (250));
105 
106         bounds.removeFromTop (5);
107         testResultsBox.setBounds (bounds);
108     }
109 
start()110     void start()
111     {
112         startTest (categoriesBox.getText());
113     }
114 
startTest(const String & category)115     void startTest (const String& category)
116     {
117         testResultsBox.clear();
118         startTestButton.setEnabled (false);
119 
120         currentTestThread.reset (new TestRunnerThread (*this, category));
121         currentTestThread->startThread();
122     }
123 
stopTest()124     void stopTest()
125     {
126         if (currentTestThread.get() != nullptr)
127         {
128             currentTestThread->stopThread (15000);
129             currentTestThread.reset();
130         }
131     }
132 
logMessage(const String & message)133     void logMessage (const String& message)
134     {
135         testResultsBox.moveCaretToEnd();
136         testResultsBox.insertTextAtCaret (message + newLine);
137         testResultsBox.moveCaretToEnd();
138     }
139 
testFinished()140     void testFinished()
141     {
142         stopTest();
143         startTestButton.setEnabled (true);
144         logMessage (newLine + "*** Tests finished ***");
145     }
146 
147 private:
148     //==============================================================================
149     class TestRunnerThread  : public Thread,
150                               private Timer
151     {
152     public:
TestRunnerThread(UnitTestsDemo & utd,const String & ctg)153         TestRunnerThread (UnitTestsDemo& utd, const String& ctg)
154             : Thread ("Unit Tests"),
155               owner (utd),
156               category (ctg)
157         {}
158 
run()159         void run() override
160         {
161             CustomTestRunner runner (*this);
162 
163             if (category == "All Tests")
164                 runner.runAllTests();
165             else
166                 runner.runTestsInCategory (category);
167 
168             startTimer (50); // when finished, start the timer which will
169                              // wait for the thread to end, then tell our component.
170         }
171 
logMessage(const String & message)172         void logMessage (const String& message)
173         {
174             WeakReference<UnitTestsDemo> safeOwner (&owner);
175 
176             MessageManager::callAsync ([=]
177             {
178                 if (auto* o = safeOwner.get())
179                     o->logMessage (message);
180             });
181         }
182 
timerCallback()183         void timerCallback() override
184         {
185             if (! isThreadRunning())
186                 owner.testFinished(); // inform the demo page when done, so it can delete this thread.
187         }
188 
189     private:
190         //==============================================================================
191         // This subclass of UnitTestRunner is used to redirect the test output to our
192         // TextBox, and to interrupt the running tests when our thread is asked to stop..
193         class CustomTestRunner  : public UnitTestRunner
194         {
195         public:
CustomTestRunner(TestRunnerThread & trt)196             CustomTestRunner (TestRunnerThread& trt)  : owner (trt) {}
197 
logMessage(const String & message)198             void logMessage (const String& message) override
199             {
200                 owner.logMessage (message);
201             }
202 
shouldAbortTests()203             bool shouldAbortTests() override
204             {
205                 return owner.threadShouldExit();
206             }
207 
208         private:
209             TestRunnerThread& owner;
210 
211             JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTestRunner)
212         };
213 
214         UnitTestsDemo& owner;
215         const String category;
216 
217         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestRunnerThread)
218     };
219     std::unique_ptr<TestRunnerThread> currentTestThread;
220 
221     TextButton startTestButton { "Run Unit Tests..." };
222     ComboBox categoriesBox;
223     TextEditor testResultsBox;
224 
lookAndFeelChanged()225     void lookAndFeelChanged() override
226     {
227         testResultsBox.applyFontToAllText (testResultsBox.getFont());
228     }
229 
230     JUCE_DECLARE_WEAK_REFERENCEABLE (UnitTestsDemo)
231     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnitTestsDemo)
232 };
233