From 133de18003a7fe1d7ab4b88beeeef413d1eb28ac Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 15 Jul 2020 16:27:19 +0200 Subject: Split test suite up into several independent programs --- test/test_state.c | 581 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 581 insertions(+) create mode 100644 test/test_state.c (limited to 'test/test_state.c') diff --git a/test/test_state.c b/test/test_state.c new file mode 100644 index 0000000..6eb7793 --- /dev/null +++ b/test/test_state.c @@ -0,0 +1,581 @@ +/* + Copyright 2007-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "lilv_test_utils.h" + +#include "../src/lilv_internal.h" + +#include "lilv/lilv.h" +#include "lv2/core/lv2.h" +#include "lv2/state/state.h" +#include "lv2/urid/urid.h" +#include "serd/serd.h" + +#ifdef _WIN32 +# include +# define mkdir(path, flags) _mkdir(path) +#else +# include +# include +#endif + +#include +#include +#include +#include +#include +#include + +uint32_t atom_Float = 0; +float in = 1.0; +float out = 42.0; +float control = 1234.0; + +static const void* +get_port_value(const char* port_symbol, + void* user_data, + uint32_t* size, + uint32_t* type) +{ + if (!strcmp(port_symbol, "input")) { + *size = sizeof(float); + *type = atom_Float; + return ∈ + } else if (!strcmp(port_symbol, "output")) { + *size = sizeof(float); + *type = atom_Float; + return &out; + } else if (!strcmp(port_symbol, "control")) { + *size = sizeof(float); + *type = atom_Float; + return &control; + } else { + fprintf(stderr, + "error: get_port_value for nonexistent port `%s'\n", + port_symbol); + *size = *type = 0; + return NULL; + } +} + +static void +set_port_value(const char* port_symbol, + void* user_data, + const void* value, + uint32_t size, + uint32_t type) +{ + if (!strcmp(port_symbol, "input")) { + in = *(const float*)value; + } else if (!strcmp(port_symbol, "output")) { + out = *(const float*)value; + } else if (!strcmp(port_symbol, "control")) { + control = *(const float*)value; + } else { + fprintf(stderr, + "error: set_port_value for nonexistent port `%s'\n", + port_symbol); + } +} + +char** uris = NULL; +size_t n_uris = 0; + +static LV2_URID +map_uri(LV2_URID_Map_Handle handle, const char* uri) +{ + for (size_t i = 0; i < n_uris; ++i) { + if (!strcmp(uris[i], uri)) { + return i + 1; + } + } + + assert(serd_uri_string_has_scheme((const uint8_t*)uri)); + uris = (char**)realloc(uris, ++n_uris * sizeof(char*)); + uris[n_uris - 1] = lilv_strdup(uri); + return n_uris; +} + +static const char* +unmap_uri(LV2_URID_Map_Handle handle, LV2_URID urid) +{ + if (urid > 0 && urid <= n_uris) { + return uris[urid - 1]; + } + return NULL; +} + +static char* temp_dir = NULL; + +static char* +lilv_make_path(LV2_State_Make_Path_Handle handle, const char* path) +{ + return lilv_path_join(temp_dir, path); +} + +static void +lilv_free_path(LV2_State_Free_Path_Handle handle, char* path) +{ + lilv_free(path); +} + +int +main(void) +{ + LilvTestEnv* const env = lilv_test_env_new(); + LilvWorld* const world = env->world; + + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(LILV_TEST_BUNDLE); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + LilvNode* plugin_uri = + lilv_new_uri(world, "http://example.org/lilv-test-plugin"); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + assert(plugin); + + LV2_URID_Map map = {NULL, map_uri}; + LV2_Feature map_feature = {LV2_URID_MAP_URI, &map}; + LV2_URID_Unmap unmap = {NULL, unmap_uri}; + LV2_Feature unmap_feature = {LV2_URID_UNMAP_URI, &unmap}; + const LV2_Feature* features[] = {&map_feature, &unmap_feature, NULL}; + + atom_Float = map.map(map.handle, "http://lv2plug.in/ns/ext/atom#Float"); + + LilvNode* num = lilv_new_int(world, 5); + LilvState* nostate = lilv_state_new_from_file(world, &map, num, "/junk"); + assert(!nostate); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, features); + assert(instance); + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &in); + lilv_instance_connect_port(instance, 1, &out); + lilv_instance_run(instance, 1); + assert(in == 1.0); + assert(out == 1.0); + + temp_dir = lilv_realpath("temp"); + + const char* scratch_dir = NULL; + char* copy_dir = NULL; + char* link_dir = NULL; + char* save_dir = NULL; + + // Get instance state state + LilvState* state = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + save_dir, + get_port_value, + world, + 0, + NULL); + + // Get another instance state + LilvState* state2 = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + save_dir, + get_port_value, + world, + 0, + NULL); + + // Ensure they are equal + assert(lilv_state_equals(state, state2)); + + // Check that we can't delete unsaved state + assert(lilv_state_delete(world, state)); + + // Check that state has no URI + assert(!lilv_state_get_uri(state)); + + // Check that we can't save a state with no URI + char* bad_state_str = + lilv_state_to_string(world, &map, &unmap, state, NULL, NULL); + assert(!bad_state_str); + + // Check that we can't restore the NULL string (and it doesn't crash) + LilvState* bad_state = lilv_state_new_from_string(world, &map, NULL); + assert(!bad_state); + + // Save state to a string + char* state1_str = lilv_state_to_string( + world, &map, &unmap, state, "http://example.org/state1", NULL); + + // Restore from string + LilvState* from_str = lilv_state_new_from_string(world, &map, state1_str); + + // Ensure they are equal + assert(lilv_state_equals(state, from_str)); + lilv_free(state1_str); + + const LilvNode* state_plugin_uri = lilv_state_get_plugin_uri(state); + assert(lilv_node_equals(state_plugin_uri, plugin_uri)); + + // Tinker with the label of the first state + assert(lilv_state_get_label(state) == NULL); + lilv_state_set_label(state, "Test State Old Label"); + assert(!strcmp(lilv_state_get_label(state), "Test State Old Label")); + lilv_state_set_label(state, "Test State"); + assert(!strcmp(lilv_state_get_label(state), "Test State")); + + assert(!lilv_state_equals(state, state2)); // Label changed + + // Run and get a new instance state (which should now differ) + lilv_instance_run(instance, 1); + LilvState* state3 = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + save_dir, + get_port_value, + world, + 0, + NULL); + assert(!lilv_state_equals(state2, state3)); // num_runs changed + + // Restore instance state to original state + lilv_state_restore(state2, instance, set_port_value, NULL, 0, NULL); + + // Take a new snapshot and ensure it matches the set state + LilvState* state4 = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + save_dir, + get_port_value, + world, + 0, + NULL); + assert(lilv_state_equals(state2, state4)); + + // Set some metadata properties + lilv_state_set_metadata(state, + map.map(map.handle, LILV_NS_RDFS "comment"), + "This is a comment", + strlen("This is a comment") + 1, + map.map(map.handle, + "http://lv2plug.in/ns/ext/atom#Literal"), + LV2_STATE_IS_POD); + lilv_state_set_metadata(state, + map.map(map.handle, "http://example.org/metablob"), + "LIVEBEEF", + strlen("LIVEBEEF") + 1, + map.map(map.handle, "http://example.org/MetaBlob"), + 0); + + // Save state to a directory + int ret = lilv_state_save( + world, &map, &unmap, state, NULL, "state/state.lv2", "state.ttl"); + assert(!ret); + + // Load state from directory + LilvState* state5 = lilv_state_new_from_file(world, + &map, + NULL, + "state/state.lv2/state.ttl"); + + assert(lilv_state_equals(state, state5)); // Round trip accuracy + assert(lilv_state_get_num_properties(state) == 8); + + // Attempt to save state to nowhere (error) + ret = lilv_state_save(world, &map, &unmap, state, NULL, NULL, NULL); + assert(ret); + + // Save another state to the same directory (update manifest) + ret = lilv_state_save( + world, &map, &unmap, state, NULL, "state/state.lv2", "state2.ttl"); + assert(!ret); + + // Save state with URI to a directory + const char* state_uri = "http://example.org/state"; + ret = lilv_state_save(world, + &map, + &unmap, + state, + state_uri, + "state/state6.lv2", + "state6.ttl"); + assert(!ret); + + // Load state bundle into world and verify it matches + { + uint8_t* state6_path = + (uint8_t*)lilv_path_absolute("state/state6.lv2/"); + SerdNode state6_uri = serd_node_new_file_uri(state6_path, 0, 0, true); + LilvNode* state6_bundle = + lilv_new_uri(world, (const char*)state6_uri.buf); + LilvNode* state6_node = lilv_new_uri(world, state_uri); + lilv_world_load_bundle(world, state6_bundle); + lilv_world_load_resource(world, state6_node); + serd_node_free(&state6_uri); + lilv_free(state6_path); + + LilvState* state6 = lilv_state_new_from_world(world, &map, state6_node); + assert(lilv_state_equals(state, state6)); // Round trip accuracy + + // Check that loaded state has correct URI + assert(lilv_state_get_uri(state6)); + assert(!strcmp(lilv_node_as_string(lilv_state_get_uri(state6)), + state_uri)); + + // Unload state from world + lilv_world_unload_resource(world, state6_node); + lilv_world_unload_bundle(world, state6_bundle); + + // Ensure that it is no longer present + assert(!lilv_state_new_from_world(world, &map, state6_node)); + lilv_node_free(state6_bundle); + lilv_node_free(state6_node); + + // Delete state + lilv_state_delete(world, state6); + lilv_state_free(state6); + } + + // Make directories and test files support + mkdir("temp", 0700); + scratch_dir = temp_dir; + mkdir("files", 0700); + copy_dir = lilv_realpath("files"); + mkdir("links", 0700); + link_dir = lilv_realpath("links"); + + LV2_State_Make_Path make_path = {NULL, lilv_make_path}; + LV2_Feature make_path_feature = {LV2_STATE__makePath, &make_path}; + LV2_State_Free_Path free_path = {NULL, lilv_free_path}; + LV2_Feature free_path_feature = {LV2_STATE__freePath, &free_path}; + const LV2_Feature* ffeatures[] = {&make_path_feature, + &map_feature, + &free_path_feature, + NULL}; + + lilv_instance_deactivate(instance); + lilv_instance_free(instance); + instance = lilv_plugin_instantiate(plugin, 48000.0, ffeatures); + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &in); + lilv_instance_connect_port(instance, 1, &out); + lilv_instance_run(instance, 1); + + // Test instantiating twice + LilvInstance* instance2 = + lilv_plugin_instantiate(plugin, 48000.0, ffeatures); + if (!instance2) { + fprintf(stderr, + "Failed to create multiple instances of <%s>\n", + lilv_node_as_uri(state_plugin_uri)); + return 1; + } + lilv_instance_free(instance2); + + // Get instance state state + LilvState* fstate = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + "state/fstate.lv2", + get_port_value, + world, + 0, + ffeatures); + + { + // Get another instance state + LilvState* fstate2 = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + "state/fstate2.lv2", + get_port_value, + world, + 0, + ffeatures); + + // Check that it is identical + assert(lilv_state_equals(fstate, fstate2)); + + lilv_state_delete(world, fstate2); + lilv_state_free(fstate2); + } + + // Run, writing more to rec file + lilv_instance_run(instance, 2); + + // Get yet another instance state + LilvState* fstate3 = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + "state/fstate3.lv2", + get_port_value, + world, + 0, + ffeatures); + + // Should be different + assert(!lilv_state_equals(fstate, fstate3)); + + // Save state to a directory + ret = lilv_state_save( + world, &map, &unmap, fstate, NULL, "state/fstate.lv2", "fstate.ttl"); + assert(!ret); + + // Load state from directory + LilvState* fstate4 = lilv_state_new_from_file( + world, &map, NULL, "state/fstate.lv2/fstate.ttl"); + assert(lilv_state_equals(fstate, fstate4)); // Round trip accuracy + + // Restore instance state to loaded state + lilv_state_restore(fstate4, instance, set_port_value, NULL, 0, ffeatures); + + // Take a new snapshot and ensure it matches + LilvState* fstate5 = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + "state/fstate5.lv2", + get_port_value, + world, + 0, + ffeatures); + assert(lilv_state_equals(fstate3, fstate5)); + + // Save state to a (different) directory again + ret = lilv_state_save( + world, &map, &unmap, fstate, NULL, "state/fstate6.lv2", "fstate6.ttl"); + assert(!ret); + + // Reload it and ensure it's identical to the other loaded version + LilvState* fstate6 = lilv_state_new_from_file( + world, &map, NULL, "state/fstate6.lv2/fstate6.ttl"); + assert(lilv_state_equals(fstate4, fstate6)); + + // Run, changing rec file (without changing size) + lilv_instance_run(instance, 3); + + // Take a new snapshot + LilvState* fstate7 = lilv_state_new_from_instance(plugin, + instance, + &map, + scratch_dir, + copy_dir, + link_dir, + "state/fstate7.lv2", + get_port_value, + world, + 0, + ffeatures); + assert(!lilv_state_equals(fstate6, fstate7)); + + // Save the changed state to a (different) directory again + ret = lilv_state_save( + world, &map, &unmap, fstate7, NULL, "state/fstate7.lv2", "fstate7.ttl"); + assert(!ret); + + // Reload it and ensure it's changed + LilvState* fstate72 = lilv_state_new_from_file( + world, &map, NULL, "state/fstate7.lv2/fstate7.ttl"); + assert(lilv_state_equals(fstate72, fstate7)); + assert(!lilv_state_equals(fstate6, fstate72)); + + // Delete saved state we still have a state in memory that points to + lilv_state_delete(world, fstate7); + lilv_state_delete(world, fstate6); + lilv_state_delete(world, fstate5); + lilv_state_delete(world, fstate3); + lilv_state_delete(world, fstate); + lilv_state_delete(world, state2); + lilv_state_delete(world, state); + + // Delete remaining states on disk we've lost a reference to + const char* const old_state_paths[] = {"state/state.lv2/state.ttl", + "state/state.lv2/state2.ttl", + "state/fstate.lv2/fstate.ttl", + NULL}; + + for (const char* const* p = old_state_paths; *p; ++p) { + const char* path = *p; + LilvState* old_state = + lilv_state_new_from_file(world, &map, NULL, path); + lilv_state_delete(world, old_state); + lilv_state_free(old_state); + } + + lilv_instance_deactivate(instance); + lilv_instance_free(instance); + + lilv_node_free(num); + + lilv_state_free(state); + lilv_state_free(from_str); + lilv_state_free(state2); + lilv_state_free(state3); + lilv_state_free(state4); + lilv_state_free(state5); + lilv_state_free(fstate); + lilv_state_free(fstate3); + lilv_state_free(fstate4); + lilv_state_free(fstate5); + lilv_state_free(fstate6); + lilv_state_free(fstate7); + lilv_state_free(fstate72); + + rmdir("state"); + + // Free URI map + for (size_t i = 0; i < n_uris; ++i) { + free(uris[i]); + } + free(uris); + n_uris = 0; + + lilv_node_free(plugin_uri); + lilv_node_free(bundle_uri); + free(link_dir); + free(copy_dir); + free(temp_dir); + + lilv_test_env_free(env); + + return 0; +} -- cgit v1.2.1