AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

commit c3177ae5e2c0a65c2b7327a8c4d86d489c3e2f6d
parent 86a53c7575893cc98813ea8fab87e9647e242ff2
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date:   Sat,  3 Apr 2021 14:32:23 -0400

Set up unit tests (#176)

* Set up unit test framework

* Disable ScreenshotHelper for Linux

* Fix Linux headless compilation

* {Apply clang-format}

* Implement MixGroupsTest

* {Apply clang-format}

* Implement SpeedTest

Co-authored-by: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Diffstat:
M.github/labeler.yml | 3+++
M.github/workflows/cmake.yml | 12++++++++++++
MPlugin/Source/GUI/AutoUpdating.cpp | 2+-
MPlugin/Source/Headless/CMakeLists.txt | 4++++
MPlugin/Source/Headless/Main.cpp | 6++++++
MPlugin/Source/Headless/ScreenshotHelper.cpp | 4++++
MPlugin/Source/Headless/ScreenshotHelper.h | 4++++
APlugin/Source/Headless/UnitTests/MixGroupsTest.cpp | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Headless/UnitTests/SpeedTest.cpp | 46++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Headless/UnitTests/UnitTests.cpp | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Headless/UnitTests/UnitTests.h | 15+++++++++++++++
11 files changed, 274 insertions(+), 1 deletion(-)

diff --git a/.github/labeler.yml b/.github/labeler.yml @@ -20,3 +20,6 @@ ci-pipeline: installers: - Plugin/Installers/**/* + +tests: + - Plugin/Source/Headless/UnitTests/* diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml @@ -49,14 +49,26 @@ jobs: - name: Configure shell: bash + if: runner.os == 'Windows' || runner.os == 'MacOS' working-directory: ${{github.workspace}}/Plugin run: cmake -Bbuild + - name: Configure + shell: bash + if: runner.os == 'Linux' + working-directory: ${{github.workspace}}/Plugin + run: cmake -Bbuild -DBUILD_HEADLESS=ON + - name: Build shell: bash working-directory: ${{github.workspace}}/Plugin run: cmake --build build --config Release --parallel 4 + - name: Unit Tests + if: runner.os == 'Linux' + working-directory: ${{github.workspace}}/Plugin + run: build/ChowTapeModel --unit-tests --all + - name: Pluginval if: runner.os == 'Windows' || runner.os == 'MacOS' run: bash validate.sh diff --git a/Plugin/Source/GUI/AutoUpdating.cpp b/Plugin/Source/GUI/AutoUpdating.cpp @@ -12,7 +12,7 @@ const Colour textColour = Colour (0xFFEAA92C); // Method to compare two versions. // Returns 1 if v2 is smaller, -1 if v1 is smaller, 0 if equal // Adapted from: https://www.geeksforgeeks.org/compare-two-version-numbers/ -int compareVersions (String v1, String v2) +[[maybe_unused]] int compareVersions (String v1, String v2) { v1.removeCharacters ("v"); v2.removeCharacters ("v"); diff --git a/Plugin/Source/Headless/CMakeLists.txt b/Plugin/Source/Headless/CMakeLists.txt @@ -14,6 +14,10 @@ target_sources(ChowTapeModel_Headless PRIVATE Benchmarks.cpp ScreenshotHelper.cpp + + UnitTests/UnitTests.cpp + UnitTests/MixGroupsTest.cpp + UnitTests/SpeedTest.cpp ) target_include_directories(ChowTapeModel_Headless PRIVATE ../) diff --git a/Plugin/Source/Headless/Main.cpp b/Plugin/Source/Headless/Main.cpp @@ -1,6 +1,7 @@ #include "Benchmarks.h" #include "FirBench.h" #include "ScreenshotHelper.h" +#include "UnitTests/UnitTests.h" String getVersion() { @@ -25,8 +26,10 @@ int main (int argc, char* argv[]) app.addVersionCommand ("--version", getVersion()); app.addHelpCommand ("--help|-h", getHelp(), true); +#if ! JUCE_LINUX // ScreenshotHelper doesn't work on Linux right now ScreenshotHelper screenshooter; app.addCommand (screenshooter); +#endif Benchmarks benchmarks; app.addCommand (benchmarks); @@ -34,5 +37,8 @@ int main (int argc, char* argv[]) FirBench firBench; app.addCommand (firBench); + UnitTests unitTests; + app.addCommand (unitTests); + return app.findAndRunCommand (argc, argv); } diff --git a/Plugin/Source/Headless/ScreenshotHelper.cpp b/Plugin/Source/Headless/ScreenshotHelper.cpp @@ -1,6 +1,8 @@ #include "ScreenshotHelper.h" #include "../PluginProcessor.h" +#if ! JUCE_LINUX + ScreenshotHelper::ScreenshotHelper() { this->commandOption = "--screenshots"; @@ -116,3 +118,5 @@ void ScreenshotHelper::screenshotForBounds (Component* editor, Rectangle<int> bo pngImage.writeImageToStream (screenshot, *pngStream.get()); } } + +#endif // ! JUCE_LINUX diff --git a/Plugin/Source/Headless/ScreenshotHelper.h b/Plugin/Source/Headless/ScreenshotHelper.h @@ -1,8 +1,11 @@ #ifndef SCREENSHOTHELPER_H_INCLUDED #define SCREENSHOTHELPER_H_INCLUDED +#if ! (defined(LINUX) || defined(__linux__)) + // weird hack, but I need acces to some private // member variables from foleys::Container +// @TODO: figure out a better way! #define _XKEYCHECK_H #define private public #include <JuceHeader.h> @@ -26,4 +29,5 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScreenshotHelper) }; +#endif // defined (LINUX) || defined (__linux__) #endif // SCREENSHOTHELPER_H_INCLUDED diff --git a/Plugin/Source/Headless/UnitTests/MixGroupsTest.cpp b/Plugin/Source/Headless/UnitTests/MixGroupsTest.cpp @@ -0,0 +1,100 @@ +#include "MixGroups/MixGroupsController.h" +#include "PluginProcessor.h" + +class MixGroupsTest : public UnitTest +{ + using Proc = ChowtapeModelAudioProcessor; + +public: + MixGroupsTest() : UnitTest ("MixGroupsTest") + { + } + + void setMixGroup (Proc* plugin, float value) + { + for (auto* p : plugin->getParameters()) + { + if (auto* pCast = dynamic_cast<RangedAudioParameter*> (p)) + { + if (pCast->paramID == MixGroupsConstants::mixGroupParamID) + pCast->setValueNotifyingHost (value); + } + } + } + + std::unique_ptr<Proc> createPlugin() + { + auto proc = createPluginFilterOfType (AudioProcessor::WrapperType::wrapperType_Standalone); + std::unique_ptr<Proc> plugin (dynamic_cast<Proc*> (proc)); + return std::move (plugin); + } + + void compareStates (Proc* plugin1, Proc* plugin2) + { + for (int i = 0; i < plugin1->getParameters().size(); ++i) + { + auto* param1 = dynamic_cast<RangedAudioParameter*> (plugin1->getParameters()[i]); + auto* param2 = dynamic_cast<RangedAudioParameter*> (plugin2->getParameters()[i]); + + if (! param1 || ! param2) + continue; + + if (auto* paramBool1 = dynamic_cast<AudioParameterBool*> (param1)) + { + auto* paramBool2 = dynamic_cast<AudioParameterBool*> (param2); + + auto bool1 = paramBool1->get(); + auto bool2 = paramBool2->get(); + + expect (bool1 == bool2, "Boolean Parameter " + param1->name + " is not linked correctly!"); + } + else + { + expectWithinAbsoluteError (param1->getValue(), param2->getValue(), 0.001f, "Parameter " + param1->name + " is not linked correctly!"); + } + } + } + + void copyStateTest() + { + auto plugin1 = createPlugin(); + auto plugin2 = createPlugin(); + + plugin1->setCurrentProgram (4); + setMixGroup (plugin1.get(), 1.0f); + setMixGroup (plugin2.get(), 1.0f); + + compareStates (plugin1.get(), plugin2.get()); + } + + void parameterLinkTest() + { + auto plugin1 = createPlugin(); + auto plugin2 = createPlugin(); + + setMixGroup (plugin1.get(), 1.0f); + setMixGroup (plugin2.get(), 1.0f); + + auto params1 = plugin1->getParameters(); + auto params2 = plugin2->getParameters(); + + constexpr int numRuns = 100; + for (int i = 0; i < numRuns; ++i) + { + auto paramIdxToChange = getRandom().nextInt (params1.size()); + params1[paramIdxToChange]->setValueNotifyingHost (getRandom().nextFloat()); + compareStates (plugin1.get(), plugin2.get()); + } + } + + void runTest() override + { + beginTest ("Copy State Test"); + copyStateTest(); + + beginTest ("Parameter Link Test"); + parameterLinkTest(); + } +}; + +static MixGroupsTest mixGroupsTest; diff --git a/Plugin/Source/Headless/UnitTests/SpeedTest.cpp b/Plugin/Source/Headless/UnitTests/SpeedTest.cpp @@ -0,0 +1,46 @@ +#include "PluginProcessor.h" + +class SpeedTest : public UnitTest +{ +public: + SpeedTest() : UnitTest ("SpeedTest") + { + } + + void createNoiseBuffer (AudioBuffer<float>& buffer) + { + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + { + auto* x = buffer.getWritePointer (ch); + for (int n = 0; n < buffer.getNumSamples(); ++n) + x[n] = getRandom().nextFloat() * 2.0f - 1.0f; + } + } + + void runTest() override + { + beginTest ("Speed Test"); + std::unique_ptr<AudioProcessor> plugin (createPluginFilterOfType (AudioProcessor::WrapperType::wrapperType_Standalone)); + + AudioBuffer<float> buffer (plugin->getMainBusNumInputChannels(), 512); + createNoiseBuffer (buffer); + MidiBuffer mb; + + constexpr int numIters = 500; + plugin->prepareToPlay (48000.0, 512); + + Time time; + auto start = time.getMillisecondCounterHiRes(); + for (int i = 0; i < numIters; ++i) + plugin->processBlock (buffer, mb); + auto end = time.getMillisecondCounterHiRes(); + + plugin->releaseResources(); + + auto duration = (end - start) / 1000.0f; + logMessage ("Plugin processing time: " + String (duration) + " seconds"); + expectLessThan (duration, 1.0, "Plugin is not fast enough!"); + } +}; + +static SpeedTest speedTest; diff --git a/Plugin/Source/Headless/UnitTests/UnitTests.cpp b/Plugin/Source/Headless/UnitTests/UnitTests.cpp @@ -0,0 +1,79 @@ +#include "UnitTests.h" + +//============================================================================== +class ConsoleLogger : public Logger +{ + void logMessage (const String& message) override + { + std::cout << message << std::endl; + +#if JUCE_WINDOWS + Logger::outputDebugString (message); +#endif + } +}; + +//============================================================================== +class ConsoleUnitTestRunner : public UnitTestRunner +{ + void logMessage (const String& message) override + { + Logger::writeToLog (message); + } +}; + +//============================================================================== +UnitTests::UnitTests() +{ + this->commandOption = "--unit-tests"; + this->argumentDescription = "--unit-tests [--seed=RANDOM_SEED --all] TEST1 TEST2 ..."; + this->shortDescription = "Runs unit tests for ChowTapeModel"; + this->longDescription = ""; + this->command = std::bind (&UnitTests::runUnitTests, this, std::placeholders::_1); +} + +void UnitTests::runUnitTests (const ArgumentList& args) +{ + ConsoleLogger logger; + Logger::setCurrentLogger (&logger); + + ConsoleUnitTestRunner runner; + auto seed = getRandomSeed (args); + + auto tests = UnitTest::getAllTests(); + getTestsForArgs (tests, args); + + runner.runTests (tests, seed); + + Logger::setCurrentLogger (nullptr); + + for (int i = 0; i < runner.getNumResults(); ++i) + if (runner.getResult (i)->failures > 0) + ConsoleApplication::fail ("Unit Tests failed!"); +} + +int64 UnitTests::getRandomSeed (const ArgumentList& args) +{ + if (args.containsOption ("--seed")) + { + auto seedValueString = args.getValueForOption ("--seed"); + if (seedValueString.startsWith ("0x")) + return seedValueString.getHexValue64(); + + return seedValueString.getLargeIntValue(); + } + + return int64 (0); +} + +void UnitTests::getTestsForArgs (Array<UnitTest*>& tests, const ArgumentList& args) +{ + if (args.containsOption ("--all")) + return; + + for (auto* t : tests) + { + if (! args.containsOption (t->getName())) + tests.remove (&t); + } +} diff --git a/Plugin/Source/Headless/UnitTests/UnitTests.h b/Plugin/Source/Headless/UnitTests/UnitTests.h @@ -0,0 +1,15 @@ +#include <JuceHeader.h> + +class UnitTests : public ConsoleApplication::Command +{ +public: + UnitTests(); + + void runUnitTests (const ArgumentList& args); + +private: + int64 getRandomSeed (const ArgumentList& args); + void getTestsForArgs (Array<UnitTest*>& tests, const ArgumentList& args); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnitTests) +};