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