Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e62e1b4
rtcheck: Added rtcheck on macOS and added this check to the allocat…
drowaudio Jun 11, 2024
d0eaff1
CI: Added codesign of librtcheck.dylib
drowaudio Jun 11, 2024
a759fd6
rtcheck: Added rtcheck to all processBlock calls at level 10
drowaudio Jun 12, 2024
9f7bbb9
Merge branch 'develop' into feature/rtcheck
drowaudio Oct 28, 2025
ff0c7bf
Added rtcheck options with disabled/enable/relaxed modes
drowaudio Oct 28, 2025
550849e
Update macOS version in build workflow
drowaudio Oct 27, 2025
f777aa2
rtcheck: Switched to using cpm
drowaudio Oct 28, 2025
061e063
rtcheck: Fixed a warning
drowaudio Oct 28, 2025
3df1f87
rtcheck: Fixed macro when rtcheck isn't enabled
drowaudio Oct 28, 2025
8b7bf58
rtcheck: Add rtcheck to juce plugin tests
drowaudio Oct 28, 2025
f398793
rtcheck: Updated docs
drowaudio Oct 28, 2025
b6a5074
rtcheck: codesign the rtcheck lib
drowaudio Oct 28, 2025
5a9f4e5
rtcheck: Use relaxed mode for juce plugin tests
drowaudio Oct 28, 2025
fcbb16a
rtcheck: Disable rtcheck for now to see if signing works
drowaudio Oct 28, 2025
8fca75d
rtcheck: Don't run spctl
drowaudio Oct 28, 2025
b6590c4
rtcheck: Pre-allocate MIDI buffers
drowaudio Oct 28, 2025
4368136
rtcheck: Test a debug build
drowaudio Oct 28, 2025
e33b3aa
rtcheck: Debug juce plugin builds
drowaudio Oct 28, 2025
4aa4f76
rtcheck: Avoid caching
drowaudio Oct 28, 2025
d8e8e0c
rtcheck: Try getting a core dump
drowaudio Oct 29, 2025
8a04f45
rtcheck: Fixed syntax
drowaudio Oct 29, 2025
8994a94
rtcheck: Run under gdb
drowaudio Oct 29, 2025
d4e4ac2
rtcheck: Add gdb
drowaudio Oct 29, 2025
29eb894
rtcheck: Disable rt checking
drowaudio Oct 29, 2025
0c021ce
rtcheck: Disable rtcheck for Linux
drowaudio Oct 29, 2025
1699490
rtcheck: Switch back to release
drowaudio Oct 29, 2025
fc0156d
rtcheck: Try ubuntu 24
drowaudio Oct 29, 2025
adfc908
rtcheck: Avoid checking
drowaudio Oct 29, 2025
c85645a
rtcheck: Webkit update
drowaudio Oct 29, 2025
31bae17
rtcheck: Build plugins in release
drowaudio Oct 29, 2025
9185072
rtcheck: Enable rtcheck on Linux again
drowaudio Oct 29, 2025
ac258a5
rtcheck: Test MultiOutSynthPlugin first
drowaudio Nov 4, 2025
7de5fc1
Don't test the juce plugin demos with rtcheck
drowaudio Nov 20, 2025
e1d2010
rtcheck: Disable on Linux for now
drowaudio Nov 24, 2025
60514e7
rtcheck: Re-enabled test plugin cache
drowaudio Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ jobs:
os: ubuntu-latest
app: pluginval
test-binary: ./pluginval
container: ubuntu:22.04
container: ubuntu:24.04
- name: macOS
os: macos-13
os: macos-15
app: pluginval.app
test-binary: pluginval.app/Contents/MacOS/pluginval
- name: Windows
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install -y freeglut3-dev g++ libasound2-dev libcurl4-openssl-dev libfreetype6-dev libjack-jackd2-dev libx11-dev libxcomposite-dev libxcursor-dev libxinerama-dev libxrandr-dev mesa-common-dev ladspa-sdk webkit2gtk-4.0 libgtk-3-dev xvfb
sudo apt-get install -y freeglut3-dev g++ libasound2-dev libcurl4-openssl-dev libfreetype6-dev libjack-jackd2-dev libx11-dev libxcomposite-dev libxcursor-dev libxinerama-dev libxrandr-dev mesa-common-dev ladspa-sdk libwebkit2gtk-4.1-dev xvfb gdb
sudo /usr/bin/Xvfb $DISPLAY &

- name: Make VST2 SDK available
Expand Down Expand Up @@ -147,7 +147,9 @@ jobs:

- name: Codesign (macOS)
if: ${{ matrix.name == 'macOS' }}
run: codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.APP_DIR }}/${{ matrix.app }} --entitlements ${{ env.BUILD_DIR }}/${{ env.BINARY_NAME }}_artefacts/JuceLibraryCode/${{ env.BINARY_NAME }}.entitlements --deep --strict --options=runtime --timestamp
run: |
codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.APP_DIR }}/${{ matrix.app }}/Contents/Libraries/librtcheck.dylib --entitlements ${{ env.BUILD_DIR }}/${{ env.BINARY_NAME }}_artefacts/JuceLibraryCode/${{ env.BINARY_NAME }}.entitlements --deep --strict --options=runtime --timestamp
codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.APP_DIR }}/${{ matrix.app }} --entitlements ${{ env.BUILD_DIR }}/${{ env.BINARY_NAME }}_artefacts/JuceLibraryCode/${{ env.BINARY_NAME }}.entitlements --deep --strict --options=runtime --timestamp

- name: "Notarize and staple (macOS)"
if: ${{ matrix.name == 'macOS' }}
Expand Down
34 changes: 31 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ if (APPLE)

# Uncomment to produce a universal binary
# set(CMAKE_OSX_ARCHITECTURES arm64 x86_64)
set(PLUGINVAL_ENABLE_RTCHECK ON)
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Disable rtcheck on Linux for now until further testing on real Linux systems has been done
# set(PLUGINVAL_ENABLE_RTCHECK ON)
endif()

# sanitizer options, from https://github.com/sudara/cmake-includes/blob/main/Sanitizers.cmake
Expand Down Expand Up @@ -41,7 +45,15 @@ endif ()
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Enable Module Source Groups" ON)

option(PLUGINVAL_FETCH_JUCE "Fetch JUCE along with PluginVal" ON)
include(cmake/CPM.cmake)
CPMAddPackage("gh:Neargye/magic_enum#v0.9.7")

if(PLUGINVAL_ENABLE_RTCHECK)
CPMAddPackage("gh:Tracktion/rtcheck#main")
endif()


option(PLUGINVAL_FETCH_JUCE "Fetch JUCE along with pluginval" ON)

if(PLUGINVAL_FETCH_JUCE)
add_subdirectory(modules/juce)
Expand All @@ -63,7 +75,7 @@ juce_add_gui_app(pluginval

juce_generate_juce_header(pluginval)

target_compile_features(pluginval PRIVATE cxx_std_17)
target_compile_features(pluginval PRIVATE cxx_std_20)

set_target_properties(pluginval PROPERTIES
C_VISIBILITY_PRESET hidden
Expand Down Expand Up @@ -105,6 +117,7 @@ target_compile_definitions(pluginval PRIVATE
JUCE_WEB_BROWSER=0
JUCE_MODAL_LOOPS_PERMITTED=1
JUCE_GUI_BASICS_INCLUDE_XHEADERS=1
$<$<BOOL:${PLUGINVAL_ENABLE_RTCHECK}>:PLUGINVAL_ENABLE_RTCHECK=1>
VERSION="${CURRENT_VERSION}")

if(MSVC AND NOT CMAKE_MSVC_RUNTIME_LIBRARY)
Expand All @@ -116,13 +129,28 @@ target_link_libraries(pluginval PRIVATE
juce::juce_audio_devices
juce::juce_audio_processors
juce::juce_audio_utils
juce::juce_recommended_warning_flags)
juce::juce_recommended_warning_flags
magic_enum)

if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries(pluginval PRIVATE
-static-libstdc++)
endif()

if (PLUGINVAL_ENABLE_RTCHECK)
target_link_libraries(pluginval PRIVATE
rtcheck)

if (APPLE)
add_custom_command(TARGET pluginval POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Executable path: $<TARGET_FILE_DIR:pluginval>")
add_custom_command(TARGET pluginval POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/_deps/rtcheck-build/src/librtcheck.dylib
$<TARGET_FILE_DIR:pluginval>/../Libraries/librtcheck.dylib)
endif ()
endif()

set (cmdline_docs_out "${CMAKE_CURRENT_LIST_DIR}/docs/Command line options.md")

add_custom_command (OUTPUT "${cmdline_docs_out}"
Expand Down
14 changes: 14 additions & 0 deletions Source/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <unistd.h>
#endif

#include <magic_enum/magic_enum.hpp>

//==============================================================================
static void exitWithError (const juce::String& error)
Expand Down Expand Up @@ -285,6 +286,7 @@ static Option possibleOptions[] =
{ "--sample-rates", true },
{ "--block-sizes", true },
{ "--vst3validator", true },
{ "--rtcheck", false },
};

static juce::StringArray mergeEnvironmentVariables (juce::StringArray args, std::function<juce::String (const juce::String& name, const juce::String& defaultValue)> environmentVariableProvider = [] (const juce::String& name, const juce::String& defaultValue) { return juce::SystemStats::getEnvironmentVariable (name, defaultValue); })
Expand Down Expand Up @@ -362,6 +364,10 @@ static juce::String getHelpMessage()
<< " Sets a timout which will stop validation with an error if no output from any" << newLine
<< " test has happened for this number of ms." << newLine
<< " By default this is 30s but can be set to \"-1\" (must be quoted) to never timeout." << newLine
<< " --rtcheck [empty, disabled, enabled or relaxed]" << newLine
<< " Turns on real-time saftey checks using rtcheck (macOS and Linux only)." << newLine
<< " relaxed mode doesn't run the checks for the first processing block as a lot of plugins" << newLine
<< " use this to allocate or initialise thread-locals (which can allocate)" << newLine
<< newLine
// repeating tests
<< " --repeat [num repeats]" << newLine
Expand Down Expand Up @@ -552,6 +558,8 @@ std::pair<juce::String, PluginTests::Options> parseCommandLine (const juce::Argu
options.sampleRates = getSampleRates (args);
options.blockSizes = getBlockSizes (args);
options.vst3Validator = getOptionValue (args, "--vst3validator", "", "Expected a path for the --vst3validator option");
options.realtimeCheck = magic_enum::enum_cast<RealtimeCheck> (getOptionValue (args, "--rtcheck", "", "Expected one of [disabled, enabled, relaxed]").toString().toStdString())
.value_or (RealtimeCheck::disabled);

return { fileOrID, options };
}
Expand Down Expand Up @@ -622,6 +630,12 @@ juce::StringArray createCommandLine (juce::String fileOrID, PluginTests::Options
if (options.vst3Validator != juce::File())
args.addArray ({ "--vst3validator", options.vst3Validator.getFullPathName().quoted() });

if (auto rtCheckMode = options.realtimeCheck;
rtCheckMode != RealtimeCheck::disabled)
{
args.addArray ({ "--rtcheck", std::string (magic_enum::enum_name (rtCheckMode)) });
}

args.addArray ({ "--validate", fileOrID });

return args;
Expand Down
33 changes: 32 additions & 1 deletion Source/MainComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
==============================================================================*/

#include "MainComponent.h"

#include <magic_enum/magic_enum.hpp>

#include "PluginTests.h"

//==============================================================================
Expand Down Expand Up @@ -114,6 +117,18 @@ namespace
return getAppPreferences().getValue ("vst3validator", juce::String());
}

void setRealtimeCheckMode (RealtimeCheck rt)
{
getAppPreferences().setValue ("realtimeCheckMode", juce::String (std::string (magic_enum::enum_name (rt))));
}

RealtimeCheck getRealtimeCheckMode()
{
auto modeString = getAppPreferences().getValue ("realtimeCheckMode", juce::String());
return magic_enum::enum_cast<RealtimeCheck> (modeString.toStdString())
.value_or (RealtimeCheck::disabled);
}

PluginTests::Options getTestOptions()
{
PluginTests::Options options;
Expand All @@ -127,6 +142,7 @@ namespace
options.sampleRates = getSampleRates();
options.blockSizes = getBlockSizes();
options.vst3Validator = getVST3Validator();
options.realtimeCheck = getRealtimeCheckMode();

return options;
}
Expand Down Expand Up @@ -336,10 +352,25 @@ MainComponent::MainComponent (Validator& v)
randomise,
chooseOutputDir,
showVST3Validator,
showSettingsDir
showSettingsDir,
rtCheck
};

juce::PopupMenu m;

{
juce::PopupMenu rtCheckMenu;

for (auto currentMode = getRealtimeCheckMode();
auto mode : magic_enum::enum_values<RealtimeCheck>())
{
rtCheckMenu.addItem (getDisplayString (mode), true, mode == currentMode,
[newMode = mode] { setRealtimeCheckMode (newMode); });
}

m.addSubMenu ("Realtime check mode", rtCheckMenu);
}

m.addItem (validateInProcess, TRANS("Validate in process"), true, getValidateInProcess());
m.addItem (showRandomSeed, TRANS("Set random seed (123)").replace ("123", "0x" + juce::String::toHexString (getRandomSeed()) + "/" + juce::String (getRandomSeed())));
m.addItem (showTimeout, TRANS("Set timeout (123ms)").replace ("123",juce::String (getTimeoutMs())));
Expand Down
17 changes: 17 additions & 0 deletions Source/PluginTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,25 @@
==============================================================================*/

#include "PluginTests.h"

#include "TestUtilities.h"
#include <random>
#include <cassert>

juce::String getDisplayString (RealtimeCheck rtc)
{
if (rtc == RealtimeCheck::disabled)
return "Disabled (don't check for real-time safety)";

if (rtc == RealtimeCheck::enabled)
return "Enabled (check for real-time safety in all process calls)";

if (rtc == RealtimeCheck::relaxed)
return "Relaxed (check for real-time safety in all but the first process call)";

assert(false);
return {};
}

namespace
{
Expand Down
40 changes: 26 additions & 14 deletions Source/PluginTests.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@

#include "juce_audio_processors/juce_audio_processors.h"

/** Determines the type of real-time checking to perform. */
enum class RealtimeCheck
{
disabled, ///< Doesn't check for realtime safety
enabled, ///< Checks for realtime safety
relaxed ///< Checks for realtime safety after the first audio callback (to allow initialisation)
};

juce::String getDisplayString (RealtimeCheck);


//==============================================================================
/**
The juce::UnitTest which will create the plugins and run each of the registered tests on them.
Expand All @@ -26,20 +37,21 @@ struct PluginTests : public juce::UnitTest
/** A set of options to use when running tests. */
struct Options
{
int strictnessLevel = 5; /**< Max test level to run. */
juce::int64 randomSeed = 0; /**< The seed to use for the tests, 0 signifies a randomly generated seed. */
juce::int64 timeoutMs = 30000; /**< Timeout after which to kill the test. */
bool verbose = false; /**< Whether or not to log additional information. */
int numRepeats = 1; /**< The number of times to repeat the tests. */
bool randomiseTestOrder = false; /**< Whether to randomise the order of the tests in each repeat. */
bool withGUI = true; /**< Whether or not avoid tests that instantiate a gui. */
juce::File dataFile; /**< juce::File which tests can use to run user provided data. */
juce::File outputDir; /**< Directory in which to write the log files for each test run. */
juce::String outputFilename; /**< Filename to write logs into */
juce::StringArray disabledTests; /**< List of disabled tests. */
std::vector<double> sampleRates; /**< List of sample rates. */
std::vector<int> blockSizes; /**< List of block sizes. */
juce::File vst3Validator; /**< juce::File to use as the VST3 validator app. */
int strictnessLevel = 5; /**< Max test level to run. */
juce::int64 randomSeed = 0; /**< The seed to use for the tests, 0 signifies a randomly generated seed. */
juce::int64 timeoutMs = 30000; /**< Timeout after which to kill the test. */
bool verbose = false; /**< Whether or not to log additional information. */
int numRepeats = 1; /**< The number of times to repeat the tests. */
bool randomiseTestOrder = false; /**< Whether to randomise the order of the tests in each repeat. */
bool withGUI = true; /**< Whether or not avoid tests that instantiate a gui. */
juce::File dataFile; /**< juce::File which tests can use to run user provided data. */
juce::File outputDir; /**< Directory in which to write the log files for each test run. */
juce::String outputFilename; /**< Filename to write logs into */
juce::StringArray disabledTests; /**< List of disabled tests. */
std::vector<double> sampleRates; /**< List of sample rates. */
std::vector<int> blockSizes; /**< List of block sizes. */
juce::File vst3Validator; /**< juce::File to use as the VST3 validator app. */
RealtimeCheck realtimeCheck = RealtimeCheck::disabled; /**< The type of real-time safety checking to perform. */
};

/** Creates a set of tests for a fileOrIdentifier. */
Expand Down
47 changes: 47 additions & 0 deletions Source/RTCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*==============================================================================

Copyright 2018 by Tracktion Corporation.
For more information visit www.tracktion.com

You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).

pluginval IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.

==============================================================================*/

#pragma once

#if PLUGINVAL_ENABLE_RTCHECK
#include <rtcheck.h>
#define RTC_REALTIME_CONTEXT rtc::realtime_context rc##__LINE__; rtc::disable_checks_for_thread (static_cast<uint64_t>(rtc::check_flags::pthread_mutex_lock) | static_cast<uint64_t>(rtc::check_flags::pthread_mutex_unlock));

#define RTC_REALTIME_CONTEXT_IF_LEVEL_10(level) \
std::optional<rtc::realtime_context> rc; \
\
if (level >= 10) \
{ \
rc.emplace(); \
rtc::disable_checks_for_thread (static_cast<uint64_t>(rtc::check_flags::pthread_mutex_lock) \
| static_cast<uint64_t>(rtc::check_flags::pthread_mutex_unlock)); \
}

#define RTC_REALTIME_CONTEXT_IF_ENABLED(realtimeCheckMode, blockNum) \
std::optional<rtc::realtime_context> rc; \
\
if (realtimeCheckMode != RealtimeCheck::disabled) \
{ \
if (realtimeCheckMode != RealtimeCheck::relaxed || blockNum > 0) \
{ \
rc.emplace(); \
rtc::disable_checks_for_thread (static_cast<uint64_t>(rtc::check_flags::pthread_mutex_lock) \
| static_cast<uint64_t>(rtc::check_flags::pthread_mutex_unlock)); \
} \
}
#else
#define RTC_REALTIME_CONTEXT_IF_ENABLED(realtimeCheckMode, blockNum) \
(void) realtimeCheckMode; \
(void) blockNum;
#endif
3 changes: 2 additions & 1 deletion Source/Validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ inline juce::Array<juce::UnitTestRunner::TestResult> runTests (PluginTests& test

updateFileNameIfPossible (test, testRunner);
const int failures = getNumFailures (results);
if (!failures)

if (! failures)
testRunner.logMessage("SUCCESS");
else
testRunner.logMessage("FAILURE");
Expand Down
Loading