/* 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/filesystem.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 #endif #include #include #include #include #include #include static uint32_t atom_Float = 0; static float in = 1.0; static float out = 42.0; static 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); } } static char** uris = NULL; static 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_path_canonical("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_path_canonical("files"); mkdir("links", 0700); link_dir = lilv_path_canonical("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); lilv_remove("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; }