commit d262ea878ccf23df24ca7a361d068a8125476c34
parent 0ae8ac7a8fca7f3aeaf0111cea7ebbc483ecfd6d
Author: Johannes Lorenz <[email protected]>
Date: Mon, 29 May 2023 00:11:45 +0200
SaveOSC: Add a lot of tests
Diffstat:
3 files changed, 320 insertions(+), 34 deletions(-)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -18,6 +18,9 @@ find_package(Alsa)
find_package(Sndio)
find_package(FLTK)
find_package(OpenGL) #for FLTK
+find_package(ECM)
+set(CMAKE_MODULE_PATH ${ECM_FIND_MODULE_DIR})
+find_package(LibGit2)
# lash
if(PKG_CONFIG_FOUND AND NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Windows"))
message("Looking For pkg config modules")
@@ -76,6 +79,7 @@ else()
SET (GuiModule off CACHE STRING "GUI module, either fltk, ntk, zest, or off")
endif()
SET (CompileTests ON CACHE BOOL "whether tests should be compiled in or not")
+SET (CompileExtensiveTests OFF CACHE BOOL "whether tests that take a long time should be compiled in or not")
SET (AlsaEnable ${ALSA_FOUND} CACHE BOOL
"Enable support for Advanced Linux Sound Architecture")
SET (JackEnable ${JACK_FOUND} CACHE BOOL
diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt
@@ -71,14 +71,32 @@ if(NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Windows"))
target_include_directories(PortChecker PRIVATE ${RTOSC_TEST_INCLUDE_DIR})
target_link_directories(PortChecker PRIVATE ${RTOSC_TEST_LIB_DIR})
endif()
+
add_executable(save-osc SaveOSC.cpp)
- target_link_libraries(save-osc
- zynaddsubfx_core zynaddsubfx_nio
- zynaddsubfx_gui_bridge
- ${GUI_LIBRARIES} ${NIO_LIBRARIES} ${AUDIO_LIBRARIES}
- ${PLATFORM_LIBRARIES})
- #this will be replaced with a for loop when the code will get more stable:
- add_test(SaveOsc save-osc ${CMAKE_CURRENT_SOURCE_DIR}/../../instruments/examples/Arpeggio\ 1.xmz)
+ if(LIBGIT2_FOUND)
+ target_include_directories(save-osc PRIVATE ${LIBGIT2_INCLUDE_DIRS})
+ target_link_libraries(save-osc
+ zynaddsubfx_core zynaddsubfx_nio
+ zynaddsubfx_gui_bridge
+ ${GUI_LIBRARIES} ${NIO_LIBRARIES} ${AUDIO_LIBRARIES}
+ ${LIBGIT2_LIBRARIES}
+ ${PLATFORM_LIBRARIES})
+ else()
+ target_compile_definitions(save-osc PRIVATE -DZYN_GIT_WORKTREE="${CMAKE_SOURCE_DIR}")
+ target_link_libraries(save-osc
+ zynaddsubfx_core zynaddsubfx_nio
+ zynaddsubfx_gui_bridge
+ ${GUI_LIBRARIES} ${NIO_LIBRARIES} ${AUDIO_LIBRARIES}
+ ${PLATFORM_LIBRARIES})
+ endif()
+ # test all presets
+ add_test(SaveOscPresets save-osc)
+ # test the most insane XIZ file
+ add_test(SaveOscBigFile save-osc ${CMAKE_CURRENT_SOURCE_DIR}/../../instruments/banks/olivers-100/0032-Drum\ Kit.xiz)
+ # not realized, because it takes 15 minutes each time:
+ if (CompileExtensiveTests)
+ add_test(SaveOscAllFiles save-osc test-all)
+ endif()
endif()
diff --git a/src/Tests/SaveOSC.cpp b/src/Tests/SaveOSC.cpp
@@ -1,4 +1,5 @@
-#include <cassert>
+#include <cassert>
+#include <dirent.h>
#include <thread>
#include <mutex>
#include <iostream>
@@ -7,6 +8,10 @@
#include <rtosc/thread-link.h>
#include <rtosc/rtosc-time.h>
+#ifndef ZYN_GIT_WORKTREE
+ #include <git2.h>
+#endif
+
#include "../Misc/Master.h"
#include "../Misc/MiddleWare.h"
#include "../UI/NSM.H"
@@ -91,7 +96,7 @@ class SaveOSCTest
std::mutex cb_mutex;
using mutex_guard = std::lock_guard<std::mutex>;
- bool timeOutOperation(const char* osc_path, const char* arg1, int tries)
+ bool timeOutOperation(const char* osc_path, const char* arg1, int tries, int prependArg = -1)
{
clock_t begin = clock(); // just for statistics
@@ -99,7 +104,12 @@ class SaveOSCTest
rtosc_arg_val_t start_time;
rtosc_arg_val_current_time(&start_time);
- mw->transmitMsgGui(osc_path, "stT", arg1, start_time.val.t);
+ if(prependArg == -1)
+ {
+ mw->transmitMsgGui(osc_path, "stT", arg1, start_time.val.t);
+ } else {
+ mw->transmitMsgGui(osc_path, "istT", prependArg, arg1, start_time.val.t);
+ }
int attempt;
for(attempt = 0; attempt < tries; ++attempt)
@@ -131,7 +141,7 @@ class SaveOSCTest
void uiCallback(const char* msg)
{
- if(!strcmp(msg, "/save_osc") || !strcmp(msg, "/load_xmz"))
+ if(!strcmp(msg, "/save_osc") || !strcmp(msg, "/load_xmz") || !strcmp(msg, "/load_xiz"))
{
mutex_guard guard(cb_mutex);
#ifdef SAVE_OSC_DEBUG
@@ -176,6 +186,34 @@ class SaveOSCTest
}
}
+ void wait_for_message()
+ {
+ int attempt;
+ for(attempt = 0; attempt < 1000; ++attempt)
+ {
+ mutex_guard guard(cb_mutex);
+ if((recent.msgmax != -1) &&
+ recent.msgnext > recent.msgmax)
+ {
+ break;
+ }
+ usleep(1000);
+ }
+ assert(attempt < 1000);
+ }
+
+ void dump_savefile(int res)
+ {
+ const std::string& savefile = recent.savefile_content;
+ std::cout << "Saving "
+ << (res == EXIT_SUCCESS ? "successful" : "failed")
+ << "." << std::endl;
+ std::cout << "The savefile content follows" << std::endl;
+ std::cout << "----8<----" << std::endl;
+ std::cout << savefile << std::endl;
+ std::cout << "---->8----" << std::endl;
+ }
+
public:
SaveOSCTest() { setUp(); }
~SaveOSCTest() { tearDown(); }
@@ -185,33 +223,185 @@ class SaveOSCTest
((SaveOSCTest*)ptr)->uiCallback(msg);
}
- int run(int argc, char** argv)
+ int test_files(const std::vector<std::string>& filenames)
{
- assert(argc == 2);
- const char *filename = argv[1];
- assert(mw);
- int rval;
-
- fputs("Loading XML file...\n", stderr);
- if(timeOutOperation("/load_xmz", filename, 1000))
+ int rval_total = EXIT_SUCCESS;
+ for(std::size_t idx = 0; idx < filenames.size() && rval_total == EXIT_SUCCESS; ++idx)
{
- fputs("Saving OSC file now...\n", stderr);
- // There is actually no need to wait for /save_osc, since
- // we're in the "UI" thread which does the saving itself,
- // but this gives an example how it works with remote fron-ends
- // The filename '""' will write the savefile to stdout
- rval = timeOutOperation("/save_osc", "", 1000)
- ? EXIT_SUCCESS
- : EXIT_FAILURE;
+ const std::string& filename = filenames[idx];
+ assert(mw);
+ int rval;
+
+ bool load_ok;
+ if (strstr(filename.c_str(), ".xiz") ==
+ filename.c_str() + filename.length() - 4)
+ {
+ mw->transmitMsgGui("/reset_master", "");
+ fprintf(stderr, "Loading XIZ file %s...\n", filename.c_str());
+ load_ok = timeOutOperation("/load_xiz", filename.c_str(), 1000, 0);
+ }
+ else {
+ fprintf(stderr, "Loading XMZ file %s...\n", filename.c_str());
+ load_ok = timeOutOperation("/load_xmz", filename.c_str(), 1000);
+ }
+
+ if(load_ok)
+ {
+ fputs("Saving OSC file now...\n", stderr);
+ // There is actually no need to wait for /save_osc, since
+ // we're in the "UI" thread which does the saving itself,
+ // but this gives an example how it works with remote front-ends
+ // The filename '""' will write the savefile to stdout
+ rval = timeOutOperation("/save_osc", "", 1000)
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE;
+ wait_for_message();
+ dump_savefile(rval);
+ }
+ else
+ {
+ std::cerr << "ERROR: Could not load XML file "
+ << filename << "." << std::endl;
+ rval = EXIT_FAILURE;
+ }
+
+ if(rval == EXIT_FAILURE)
+ rval_total = EXIT_FAILURE;
}
- else
+
+ return rval_total;
+ }
+
+
+ // enable everything, cycle through all presets
+ // => only preset ports and enable ports are saved
+ // => all other ports are defaults and thus not saved
+ int test_presets()
+ {
+ // enable almost everything, in order to test all ports
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFreqEnvelopeEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFreqLfoEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PAAEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PAmpEnvelopeEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PAmpLfoEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterEnvelopeEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterLfoEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFMEnabled", "i", 1);
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFMFreqEnvelopeEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFMAmpEnvelopeEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/Psubenabled", "T");
+ mw->transmitMsgGui("/part0/kit0/subpars/PBandWidthEnvelopeEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/subpars/PFreqEnvelopeEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/subpars/PGlobalFilterEnabled", "T");
+ mw->transmitMsgGui("/part0/kit0/Ppadenabled", "T");
+
+ // use all effects as ins fx
+ mw->transmitMsgGui("/insefx0/efftype", "S", "Reverb");
+ mw->transmitMsgGui("/insefx1/efftype", "S", "Phaser");
+ mw->transmitMsgGui("/insefx2/efftype", "S", "Echo");
+ mw->transmitMsgGui("/insefx3/efftype", "S", "Distortion");
+ mw->transmitMsgGui("/insefx4/efftype", "S", "Sympathetic");
+ mw->transmitMsgGui("/insefx5/efftype", "S", "DynFilter");
+ mw->transmitMsgGui("/insefx6/efftype", "S", "Alienwah");
+ mw->transmitMsgGui("/insefx7/efftype", "S", "EQ");
+ mw->transmitMsgGui("/part0/partefx0/efftype", "S", "Chorus");
+ // use all effects as sys fx (except Chorus, it does not differ)
+ mw->transmitMsgGui("/sysefx0/efftype", "S", "Reverb");
+ mw->transmitMsgGui("/sysefx1/efftype", "S", "Phaser");
+ mw->transmitMsgGui("/sysefx2/efftype", "S", "Echo");
+ mw->transmitMsgGui("/sysefx3/efftype", "S", "Distortion");
+
+ int res = EXIT_SUCCESS;
+
+ for (int preset = 0; preset < 18 && res == EXIT_SUCCESS; ++preset)
{
- std::cerr << "ERROR: Could not load master file " << filename
- << "." << std::endl;
- rval = EXIT_FAILURE;
- }
+ std::cout << "testing preset " << preset << std::endl;
+
+ // save all insefx with preset "preset"
+ char insefxstr[] = "/insefx0/preset";
+ char partefxstr[] = "/part0/partefx0/preset";
+ char sysefxstr[] = "/sysefx0/preset";
+ int npresets_ins[] = {13, 12, 9, 6, 5, 5, 4, 2};
+ int npresets_part[] = {10};
+ for(; insefxstr[7] < '8'; ++insefxstr[7])
+ if(preset < npresets_ins[insefxstr[7]-'0'])
+ mw->transmitMsgGui(insefxstr, "i", preset);
+ for(; partefxstr[14] < '1'; ++partefxstr[14])
+ if(preset < npresets_part[partefxstr[14]-'0'])
+ mw->transmitMsgGui(partefxstr, "i", preset);
+ if(preset == 13) // for presets 13-17, test the up to 5 sysefx
+ {
+ mw->transmitMsgGui("/sysefx0/efftype", "S", "Sympathetic");
+ mw->transmitMsgGui("/sysefx1/efftype", "S", "DynFilter");
+ mw->transmitMsgGui("/sysefx2/efftype", "S", "Alienwah");
+ mw->transmitMsgGui("/sysefx3/efftype", "S", "EQ");
+ }
+ int type_offset = (preset>=13)?4:0;
+ for(; sysefxstr[7] < '4'; ++sysefxstr[7])
+ if(preset%13 < npresets_ins[sysefxstr[7]-'0'+type_offset])
+ mw->transmitMsgGui(sysefxstr, "i", preset%13);
+
+ char filename[] = "file0";
+ filename[4] += preset;
+ res = timeOutOperation("/save_osc", filename, 1000)
+ ? res
+ : EXIT_FAILURE;
+
+
+ wait_for_message();
+ dump_savefile(res);
+
+ const std::string& savefile = recent.savefile_content;
+ const char* next_line;
+ for(const char* line = savefile.c_str();
+ *line && res == EXIT_SUCCESS;
+ line = next_line)
+ {
+ next_line = strchr(line, '\n');
+ if (next_line) { ++next_line; } // first char of new line
+ else { next_line = line + strlen(line); } // \0 terminator
+
+ auto line_contains = [](const char* line, const char* next_line, const char* what) {
+ auto pos = strstr(line, what);
+ return (pos && pos < next_line);
+ };
+
+ if(// empty line / comment
+ line[0] == '\n' || line[0] == '%' ||
+ // accept [Ee]nabled, presets, and effect types,
+ // because we set them ourselves
+ line_contains(line, next_line, "nabled") ||
+ line_contains(line, next_line, "/preset") ||
+ line_contains(line, next_line, "/efftype") ||
+ // formants have random values:
+ line_contains(line, next_line, "/Pformants")
+ )
+ {
+ // OK
+ } else {
+ // everything else will not be OK, because this means
+ // a derivation from the default value, while we only
+ // used default values
+ // => that would mean an rDefault/rPreset does not match
+ // what the oject really uses as default value
+ std::string bad_line(line, next_line-1);
+ std::cout << "Error: invalid rDefault/rPreset: "
+ << bad_line
+ << std::endl;
+ res = EXIT_FAILURE;
+ }
+ }
- return rval;
+ if(unknown_addresses_count)
+ {
+ std::cout << "Error: master caught unknown addresses"
+ << std::endl;
+ res = EXIT_FAILURE;
+ }
+ }
+ return res;
}
@@ -246,6 +436,59 @@ class SaveOSCTest
realtime = NULL;
}
+ static void findfiles(std::string dirname, std::vector<std::string>& all_files)
+ {
+ if(dirname.back() != '/' && dirname.back() != '\\')
+ dirname += '/';
+
+ DIR *dir = opendir(dirname.c_str());
+ if(dir == NULL)
+ return;
+
+ struct dirent *fn;
+ while((fn = readdir(dir))) {
+ const char *filename = fn->d_name;
+
+ if(fn->d_type == DT_DIR) {
+ if(strcmp(filename, ".") && strcmp(filename, ".."))
+ findfiles(dirname + filename, all_files);
+ }
+ else
+ {
+ std::size_t len = strlen(filename);
+ //check for extension
+ if( (strstr(filename, ".xiz") == filename + len - 4)
+ || (strstr(filename, ".xmz") == filename + len - 4))
+ {
+ all_files.push_back(dirname + filename);
+ }
+ }
+ }
+
+ closedir(dir);
+ }
+
+#ifndef ZYN_GIT_WORKTREE
+ static std::string get_git_worktree()
+ {
+ git_libgit2_init();
+
+ git_buf root_path = {0};
+ git_repository_discover(&root_path, ".", 0, NULL);
+
+ git_repository *repo = NULL;
+ git_repository_open(&repo, root_path.ptr);
+
+ std::string toplevel = git_repository_workdir(repo);
+
+ git_repository_free(repo);
+ git_buf_free(&root_path);
+ git_libgit2_shutdown();
+
+ return toplevel;
+ }
+#endif
+
private:
zyn::Config config;
zyn::SYNTH_T* synth;
@@ -259,7 +502,28 @@ int main(int argc, char** argv)
{
SaveOSCTest test;
test.start_realtime();
- int res = test.run(argc, argv);
+
+ std::vector<std::string> all_files;
+ if(argc == 1)
+ {
+ // leave vector empty - test presets
+ } else if(!strcmp(argv[1], "test-all")) {
+ std::string zyn_git_worktree;
+#ifdef ZYN_GIT_WORKTREE
+ zyn_git_worktree = ZYN_GIT_WORKTREE;
+#else
+ zyn_git_worktree = SaveOSCTest::get_git_worktree();
+#endif
+ SaveOSCTest::findfiles(zyn_git_worktree, all_files);
+ } else {
+ all_files.reserve(argc-1);
+ for(int idx = 1; idx < argc; ++idx)
+ all_files.push_back(argv[idx]);
+ }
+
+
+ int res = all_files.size() == 0 ? test.test_presets()
+ : test.test_files(all_files);
test.stop_realtime();
std::cerr << "Summary: " << ((res == EXIT_SUCCESS) ? "SUCCESS" : "FAILURE")
<< std::endl;