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:
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)
+};