From 2bbfaf854ea7a799bfc5734f3a776d8ce4abe03c Mon Sep 17 00:00:00 2001 From: David Robillard Date: Tue, 1 Dec 2020 10:23:55 +0100 Subject: Rewrite state test suite Still a little bit hairy, but much better, and it's at least possible to add cases now without running away screaming. --- test/test_state.c | 1196 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 797 insertions(+), 399 deletions(-) diff --git a/test/test_state.c b/test/test_state.c index 9b0e960..a9260b9 100644 --- a/test/test_state.c +++ b/test/test_state.c @@ -46,19 +46,38 @@ typedef struct { - LilvTestEnv* env; - LilvTestUriMap uri_map; - LV2_URID_Map map; - LV2_Feature map_feature; - LV2_URID_Unmap unmap; - LV2_Feature unmap_feature; - const LV2_Feature* features[3]; - LV2_URID atom_Float; - float in; - float out; - float control; + LilvTestEnv* env; + LilvTestUriMap uri_map; + LV2_URID_Map map; + LV2_Feature map_feature; + LV2_URID_Unmap unmap; + LV2_Feature unmap_feature; + LV2_State_Free_Path free_path; + LV2_Feature free_path_feature; + const LV2_Feature* features[4]; + LV2_URID atom_Float; + float in; + float out; + float control; } TestContext; +typedef struct +{ + char* top; ///< Temporary directory that contains everything + char* shared; ///< Common parent for shared directoryes (top/shared) + char* scratch; ///< Scratch file directory (top/shared/scratch) + char* copy; ///< Copy directory (top/shared/scratch) + char* link; ///< Link directory (top/shared/scratch) +} TestDirectories; + +static void +lilv_free_path(LV2_State_Free_Path_Handle handle, char* path) +{ + (void)handle; + + lilv_free(path); +} + static TestContext* test_context_new(void) { @@ -66,18 +85,22 @@ test_context_new(void) lilv_test_uri_map_init(&ctx->uri_map); - ctx->env = lilv_test_env_new(); - ctx->map.handle = &ctx->uri_map; - ctx->map.map = map_uri; - ctx->map_feature.URI = LV2_URID_MAP_URI; - ctx->map_feature.data = &ctx->map; - ctx->unmap.handle = &ctx->uri_map; - ctx->unmap.unmap = unmap_uri; - ctx->unmap_feature.URI = LV2_URID_UNMAP_URI; - ctx->unmap_feature.data = &ctx->unmap; - ctx->features[0] = &ctx->map_feature; - ctx->features[1] = &ctx->unmap_feature; - ctx->features[2] = NULL; + ctx->env = lilv_test_env_new(); + ctx->map.handle = &ctx->uri_map; + ctx->map.map = map_uri; + ctx->map_feature.URI = LV2_URID_MAP_URI; + ctx->map_feature.data = &ctx->map; + ctx->unmap.handle = &ctx->uri_map; + ctx->unmap.unmap = unmap_uri; + ctx->unmap_feature.URI = LV2_URID_UNMAP_URI; + ctx->unmap_feature.data = &ctx->unmap; + ctx->free_path.free_path = lilv_free_path; + ctx->free_path_feature.URI = LV2_STATE__freePath; + ctx->free_path_feature.data = &ctx->free_path; + ctx->features[0] = &ctx->map_feature; + ctx->features[1] = &ctx->unmap_feature; + ctx->features[2] = &ctx->free_path_feature; + ctx->features[3] = NULL; ctx->atom_Float = map_uri(&ctx->uri_map, "http://lv2plug.in/ns/ext/atom#Float"); @@ -97,6 +120,69 @@ test_context_free(TestContext* ctx) free(ctx); } +static TestDirectories +create_test_directories(void) +{ + TestDirectories dirs; + + char* const top = lilv_create_temporary_directory("lilv_XXXXXX"); + assert(top); + + /* On MacOS, temporary directories from mkdtemp involve symlinks, so + resolve it here so that path comparisons in tests work. */ + + dirs.top = lilv_path_canonical(top); + dirs.shared = lilv_path_join(dirs.top, "shared"); + dirs.scratch = lilv_path_join(dirs.shared, "scratch"); + dirs.copy = lilv_path_join(dirs.shared, "copy"); + dirs.link = lilv_path_join(dirs.shared, "link"); + + assert(!mkdir(dirs.shared, 0700)); + assert(!mkdir(dirs.scratch, 0700)); + assert(!mkdir(dirs.copy, 0700)); + assert(!mkdir(dirs.link, 0700)); + + free(top); + + return dirs; +} + +static TestDirectories +no_test_directories(void) +{ + TestDirectories dirs = {NULL, NULL, NULL, NULL, NULL}; + + return dirs; +} + +static void +remove_file(const char* path, const char* name, void* data) +{ + char* const full_path = lilv_path_join(path, name); + assert(!lilv_remove(full_path)); + free(full_path); +} + +static void +cleanup_test_directories(const TestDirectories dirs) +{ + lilv_dir_for_each(dirs.scratch, NULL, remove_file); + lilv_dir_for_each(dirs.copy, NULL, remove_file); + lilv_dir_for_each(dirs.link, NULL, remove_file); + + assert(!lilv_remove(dirs.link)); + assert(!lilv_remove(dirs.copy)); + assert(!lilv_remove(dirs.scratch)); + assert(!lilv_remove(dirs.shared)); + assert(!lilv_remove(dirs.top)); + + free(dirs.link); + free(dirs.copy); + free(dirs.scratch); + free(dirs.shared); + free(dirs.top); +} + static const void* get_port_value(const char* port_symbol, void* user_data, @@ -148,24 +234,18 @@ set_port_value(const char* port_symbol, } } -static char* temp_dir = NULL; - static char* -lilv_make_path(LV2_State_Make_Path_Handle handle, const char* path) +make_scratch_path(LV2_State_Make_Path_Handle handle, const char* path) { - return lilv_path_join(temp_dir, path); -} + TestDirectories* dirs = (TestDirectories*)handle; -static void -lilv_free_path(LV2_State_Free_Path_Handle handle, char* path) -{ - lilv_free(path); + return lilv_path_join(dirs->scratch, path); } static const LilvPlugin* -load_test_plugin(const TestContext* const ctx, const LilvTestEnv* const env) +load_test_plugin(const TestContext* const ctx) { - LilvWorld* world = env->world; + LilvWorld* world = ctx->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); @@ -181,31 +261,114 @@ load_test_plugin(const TestContext* const ctx, const LilvTestEnv* const env) serd_node_free(&bundle); free(abs_bundle); + assert(plugin); return plugin; } -int -main(void) +static LilvState* +state_from_instance(const LilvPlugin* const plugin, + LilvInstance* const instance, + TestContext* const ctx, + const TestDirectories* const dirs, + const char* const bundle_path) { - TestContext* const ctx = test_context_new(); - LilvTestEnv* const env = ctx->env; - LilvWorld* const world = env->world; - LilvNode* const plugin_uri = lilv_new_uri(world, TEST_PLUGIN_URI); - const LilvPlugin* plugin = load_test_plugin(ctx, env); - assert(plugin); + return lilv_state_new_from_instance(plugin, + instance, + &ctx->map, + dirs->scratch, + dirs->copy, + dirs->link, + bundle_path, + get_port_value, + ctx, + 0, + NULL); +} + +static void +test_instance_state(void) +{ + TestContext* const ctx = test_context_new(); + const TestDirectories dirs = no_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, ctx->features); + + assert(instance); - LV2_URID_Map* const map = &ctx->map; - LV2_URID_Unmap* const unmap = &ctx->unmap; + // Get instance state + LilvState* const state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Check that state contains properties saved by the plugin + assert(lilv_state_get_num_properties(state) == 8); + + // Check that state has no URI + assert(!lilv_state_get_uri(state)); - LilvNode* num = lilv_new_int(world, 5); - LilvState* nostate = lilv_state_new_from_file(world, map, num, "/junk"); + // Check that state can't be saved without a URI + assert(!lilv_state_to_string( + ctx->env->world, &ctx->map, &ctx->unmap, state, NULL, NULL)); + + // Check that we can't delete unsaved state + assert(lilv_state_delete(ctx->env->world, state)); - assert(!nostate); + lilv_state_free(state); + lilv_instance_free(instance); + test_context_free(ctx); +} - LilvInstance* instance = +static void +test_equal(void) +{ + TestContext* const ctx = test_context_new(); + const TestDirectories dirs = no_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + LilvInstance* const instance = lilv_plugin_instantiate(plugin, 48000.0, ctx->features); assert(instance); + + // Get instance state + LilvState* const state_1 = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Get another instance state + LilvState* const state_2 = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Ensure they are equal + assert(lilv_state_equals(state_1, state_2)); + + // Set a label on the second state + assert(lilv_state_get_label(state_2) == NULL); + lilv_state_set_label(state_2, "Test State Old Label"); + + // Ensure they are no longer equal + assert(!lilv_state_equals(state_1, state_2)); + + lilv_state_free(state_2); + lilv_state_free(state_1); + lilv_instance_free(instance); + test_context_free(ctx); +} + +static void +test_changed_plugin_data(void) +{ + TestContext* const ctx = test_context_new(); + const TestDirectories dirs = no_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, ctx->features); + + assert(instance); + + // Get initial state + LilvState* const initial_state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Run plugin to change internal state lilv_instance_activate(instance); lilv_instance_connect_port(instance, 0, &ctx->in); lilv_instance_connect_port(instance, 1, &ctx->out); @@ -213,396 +376,631 @@ main(void) assert(ctx->in == 1.0); assert(ctx->out == 1.0); - temp_dir = lilv_path_canonical("temp"); + // Get a new instance state (which should now differ) + LilvState* const changed_state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); - const char* scratch_dir = NULL; - char* copy_dir = NULL; - char* link_dir = NULL; - char* save_dir = NULL; + // Ensure state has changed + assert(!lilv_state_equals(initial_state, changed_state)); - // Get instance state - LilvState* state = lilv_state_new_from_instance(plugin, - instance, - map, - scratch_dir, - copy_dir, - link_dir, - save_dir, - get_port_value, - ctx, - 0, - NULL); + lilv_state_free(changed_state); + lilv_state_free(initial_state); + lilv_instance_free(instance); + test_context_free(ctx); +} - // 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, - ctx, - 0, - NULL); +static void +test_changed_metadata(void) +{ + TestContext* const ctx = test_context_new(); + const TestDirectories dirs = no_test_directories(); + LV2_URID_Map* const map = &ctx->map; + const LilvPlugin* const plugin = load_test_plugin(ctx); + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, ctx->features); - // Ensure they are equal - assert(lilv_state_equals(state, state2)); + assert(instance); - // Check that we can't delete unsaved state - assert(lilv_state_delete(world, state)); + // Get initial state + LilvState* const initial_state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Save initial state to a string + char* const initial_string = + lilv_state_to_string(ctx->env->world, + &ctx->map, + &ctx->unmap, + initial_state, + "http://example.org/initial", + NULL); + + // Get another state + LilvState* const changed_state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Set a metadata property + const LV2_URID key = map->map(map->handle, "http://example.org/extra"); + const int32_t value = 1; + const LV2_URID type = + map->map(map->handle, "http://lv2plug.in/ns/ext/atom#Int"); + lilv_state_set_metadata(changed_state, + key, + &value, + sizeof(value), + type, + LV2_STATE_IS_PORTABLE | LV2_STATE_IS_POD); + + // Save changed state to a string + char* const changed_string = + lilv_state_to_string(ctx->env->world, + &ctx->map, + &ctx->unmap, + changed_state, + "http://example.org/changed", + NULL); + + // Ensure that strings differ (metadata does not affect state equality) + assert(strcmp(initial_string, changed_string)); + + free(changed_string); + lilv_state_free(changed_state); + free(initial_string); + lilv_state_free(initial_state); + lilv_instance_free(instance); + test_context_free(ctx); +} - // Check that state has no URI - assert(!lilv_state_get_uri(state)); +static void +test_to_string(void) +{ + TestContext* const ctx = test_context_new(); + const TestDirectories dirs = no_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, ctx->features); + + assert(instance); + + // Get initial state + LilvState* const initial_state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); - // 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); + // Run plugin to change internal state + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &ctx->in); + lilv_instance_connect_port(instance, 1, &ctx->out); + lilv_instance_run(instance, 1); + assert(ctx->in == 1.0); + assert(ctx->out == 1.0); + + // Restore instance state to original state + lilv_state_restore(initial_state, instance, set_port_value, ctx, 0, NULL); - // 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); + // Take a new snapshot of the state + LilvState* const restored = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Check that new state matches the initial state + assert(lilv_state_equals(initial_state, restored)); + + lilv_state_free(restored); + lilv_state_free(initial_state); + lilv_instance_free(instance); + test_context_free(ctx); +} + +static void +test_string_round_trip(void) +{ + TestContext* const ctx = test_context_new(); + const TestDirectories dirs = no_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, ctx->features); + + assert(instance); + + // Get initial state + LilvState* const initial_state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); // Save state to a string - char* state1_str = lilv_state_to_string( - world, map, unmap, state, "http://example.org/state1", NULL); + char* const string = lilv_state_to_string(ctx->env->world, + &ctx->map, + &ctx->unmap, + initial_state, + "http://example.org/string", + NULL); // Restore from string - LilvState* from_str = lilv_state_new_from_string(world, map, state1_str); + LilvState* const restored = + lilv_state_new_from_string(ctx->env->world, &ctx->map, string); // Ensure they are equal - assert(lilv_state_equals(state, from_str)); - lilv_free(state1_str); + assert(lilv_state_equals(initial_state, restored)); - const LilvNode* state_plugin_uri = lilv_state_get_plugin_uri(state); - assert(lilv_node_equals(state_plugin_uri, plugin_uri)); + // Check that the restored state refers to the correct plugin + const LilvNode* state_plugin_uri = lilv_state_get_plugin_uri(restored); + assert(!strcmp(lilv_node_as_string(state_plugin_uri), TEST_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")); + lilv_state_free(restored); + free(string); + lilv_state_free(initial_state); + lilv_instance_free(instance); + test_context_free(ctx); +} - assert(!lilv_state_equals(state, state2)); // Label changed +static void +test_to_files(void) +{ + TestContext* const ctx = test_context_new(); + TestDirectories dirs = create_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + + LV2_State_Make_Path make_path = {&dirs, make_scratch_path}; + LV2_Feature make_path_feature = {LV2_STATE__makePath, &make_path}; + + const LV2_Feature* const instance_features[] = {&ctx->map_feature, + &ctx->free_path_feature, + &make_path_feature, + NULL}; + + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, instance_features); + + assert(instance); - // Run and get a new instance state (which should now differ) + // Run plugin to generate some recording file data + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &ctx->in); + lilv_instance_connect_port(instance, 1, &ctx->out); 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, - ctx, - 0, - NULL); - assert(!lilv_state_equals(state2, state3)); // num_runs changed + lilv_instance_run(instance, 2); + assert(ctx->in == 1.0); + assert(ctx->out == 1.0); - // Restore instance state to original state - lilv_state_restore(state2, instance, set_port_value, ctx, 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, - ctx, - 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); + // Check that the test plugin has made its recording scratch file + char* const recfile_path = lilv_path_join(dirs.scratch, "recfile"); + assert(lilv_path_exists(recfile_path)); - // 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); - } + // Get state + char* const bundle_1_path = lilv_path_join(dirs.top, "state1.lv2"); + LilvState* const state_1 = + state_from_instance(plugin, instance, ctx, &dirs, bundle_1_path); - // 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"); + // Check that state contains properties saved by the plugin (with files) + assert(lilv_state_get_num_properties(state_1) == 10); - 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, - &ctx->map_feature, - &free_path_feature, - NULL}; - - lilv_instance_deactivate(instance); + // Check that a snapshop of the recfile was created + char* const recfile_copy_1 = lilv_path_join(dirs.copy, "recfile"); + assert(lilv_path_exists(recfile_copy_1)); + + // Set a label and save state to a bundle + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + state_1, + "http://example.org/state1", + bundle_1_path, + "state.ttl")); + + // Check that a link to the recfile exists in the saved bundle + char* const recfile_link_1 = lilv_path_join(bundle_1_path, "recfile"); + assert(lilv_path_exists(recfile_link_1)); + + // Check that link points to the corresponding copy + char* const recfile_link_1_real = lilv_path_canonical(recfile_link_1); + assert(!strcmp(recfile_link_1_real, recfile_copy_1)); + + // Run plugin again to modify recording file data + lilv_instance_run(instance, 2); + + // Get updated state + char* const bundle_2_path = lilv_path_join(dirs.top, "state2.lv2"); + LilvState* const state_2 = + state_from_instance(plugin, instance, ctx, &dirs, bundle_2_path); + + // Save updated state to a bundle + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + state_2, + NULL, + bundle_2_path, + "state.ttl")); + + // Check that a new snapshop of the recfile was created + char* const recfile_copy_2 = lilv_path_join(dirs.copy, "recfile.2"); + assert(lilv_path_exists(recfile_copy_2)); + + // Check that a link to the recfile exists in the updated bundle + char* const recfile_link_2 = lilv_path_join(bundle_2_path, "recfile"); + assert(lilv_path_exists(recfile_link_2)); + + // Check that link points to the corresponding copy + char* const recfile_link_2_real = lilv_path_canonical(recfile_link_2); + assert(!strcmp(recfile_link_2_real, recfile_copy_2)); + + lilv_dir_for_each(bundle_2_path, NULL, remove_file); + lilv_dir_for_each(bundle_1_path, NULL, remove_file); + lilv_remove(bundle_2_path); + lilv_remove(bundle_1_path); + cleanup_test_directories(dirs); + + free(recfile_link_2_real); + free(recfile_link_2); + free(recfile_copy_2); + lilv_state_free(state_2); + free(bundle_2_path); + free(recfile_link_1_real); + free(recfile_link_1); + free(recfile_copy_1); + lilv_state_free(state_1); + free(bundle_1_path); + free(recfile_path); lilv_instance_free(instance); - instance = lilv_plugin_instantiate(plugin, 48000.0, ffeatures); + test_context_free(ctx); +} + +static void +test_files_round_trip(void) +{ + TestContext* const ctx = test_context_new(); + TestDirectories dirs = create_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + + LV2_State_Make_Path make_path = {&dirs, make_scratch_path}; + LV2_Feature make_path_feature = {LV2_STATE__makePath, &make_path}; + + const LV2_Feature* const instance_features[] = {&ctx->map_feature, + &ctx->free_path_feature, + &make_path_feature, + NULL}; + + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, instance_features); + + assert(instance); + + // Run plugin to generate some recording file data lilv_instance_activate(instance); lilv_instance_connect_port(instance, 0, &ctx->in); lilv_instance_connect_port(instance, 1, &ctx->out); lilv_instance_run(instance, 1); + lilv_instance_run(instance, 2); + assert(ctx->in == 1.0); + assert(ctx->out == 1.0); - // 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, - ctx, - 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, - ctx, - 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 + // Save first state to a bundle + char* const bundle_1_1_path = lilv_path_join(dirs.top, "state1_1.lv2"); + LilvState* const state_1_1 = + state_from_instance(plugin, instance, ctx, &dirs, bundle_1_1_path); + + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + state_1_1, + NULL, + bundle_1_1_path, + "state.ttl")); + + // Save first state to another bundle + char* const bundle_1_2_path = lilv_path_join(dirs.top, "state1_2.lv2"); + LilvState* const state_1_2 = + state_from_instance(plugin, instance, ctx, &dirs, bundle_1_2_path); + + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + state_1_2, + NULL, + bundle_1_2_path, + "state.ttl")); + + // Load both first state bundles and check that the results are equal + char* const state_1_1_path = lilv_path_join(bundle_1_1_path, "state.ttl"); + char* const state_1_2_path = lilv_path_join(bundle_1_2_path, "state.ttl"); + + LilvState* state_1_1_loaded = lilv_state_new_from_file(ctx->env->world, + &ctx->map, + NULL, + state_1_1_path); + + LilvState* state_1_2_loaded = lilv_state_new_from_file(ctx->env->world, + &ctx->map, + NULL, + state_1_2_path); + + assert(state_1_1_loaded); + assert(state_1_2_loaded); + assert(lilv_state_equals(state_1_1_loaded, state_1_2_loaded)); + + // Run plugin again to modify recording file data 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, - ctx, - 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, ctx, 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, - ctx, - 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, - ctx, - 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); - } + // Save updated state to a bundle + char* const bundle_2_path = lilv_path_join(dirs.top, "state2.lv2"); + LilvState* const state_2 = + state_from_instance(plugin, instance, ctx, &dirs, bundle_2_path); + + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + state_2, + NULL, + bundle_2_path, + "state.ttl")); + + // Load updated state bundle and check that it differs from the others + char* const state_2_path = lilv_path_join(bundle_2_path, "state.ttl"); + + LilvState* state_2_loaded = lilv_state_new_from_file(ctx->env->world, + &ctx->map, + NULL, + state_2_path); + + assert(state_2_loaded); + assert(!lilv_state_equals(state_1_1_loaded, state_2_loaded)); + + lilv_dir_for_each(bundle_1_1_path, NULL, remove_file); + lilv_dir_for_each(bundle_1_2_path, NULL, remove_file); + lilv_dir_for_each(bundle_2_path, NULL, remove_file); + lilv_remove(bundle_1_1_path); + lilv_remove(bundle_1_2_path); + lilv_remove(bundle_2_path); + cleanup_test_directories(dirs); + + lilv_state_free(state_2_loaded); + free(state_2_path); + lilv_state_free(state_2); + free(bundle_2_path); + lilv_state_free(state_1_2_loaded); + lilv_state_free(state_1_1_loaded); + free(state_1_2_path); + free(state_1_1_path); + lilv_state_free(state_1_2); + free(bundle_1_2_path); + lilv_state_free(state_1_1); + free(bundle_1_1_path); + lilv_instance_free(instance); + test_context_free(ctx); +} + +static void +test_world_round_trip(void) +{ + TestContext* const ctx = test_context_new(); + LilvWorld* const world = ctx->env->world; + TestDirectories dirs = create_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + static const char* const state_uri = "http://example.org/worldState"; + + LV2_State_Make_Path make_path = {&dirs, make_scratch_path}; + LV2_Feature make_path_feature = {LV2_STATE__makePath, &make_path}; + + const LV2_Feature* const instance_features[] = {&ctx->map_feature, + &ctx->free_path_feature, + &make_path_feature, + NULL}; + + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, instance_features); + + assert(instance); + + // Run plugin to generate some recording file data + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &ctx->in); + lilv_instance_connect_port(instance, 1, &ctx->out); + lilv_instance_run(instance, 1); + lilv_instance_run(instance, 2); + assert(ctx->in == 1.0); + assert(ctx->out == 1.0); - lilv_instance_deactivate(instance); + // Save state to a bundle + char* const bundle_path = lilv_path_join(dirs.top, "state.lv2/"); + LilvState* const start_state = + state_from_instance(plugin, instance, ctx, &dirs, bundle_path); + + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + start_state, + state_uri, + bundle_path, + "state.ttl")); + + // Load state bundle into world + SerdNode bundle_uri = + serd_node_new_file_uri((const uint8_t*)bundle_path, 0, 0, true); + LilvNode* const bundle_node = + lilv_new_uri(world, (const char*)bundle_uri.buf); + LilvNode* const state_node = lilv_new_uri(world, state_uri); + lilv_world_load_bundle(world, bundle_node); + lilv_world_load_resource(world, state_node); + + // Ensure the state loaded from the world matches + LilvState* const restored = + lilv_state_new_from_world(world, &ctx->map, state_node); + assert(lilv_state_equals(start_state, restored)); + + // Unload state from world + lilv_world_unload_resource(world, state_node); + lilv_world_unload_bundle(world, bundle_node); + + // Ensure that it is no longer present + assert(!lilv_state_new_from_world(world, &ctx->map, state_node)); + + lilv_state_delete(world, restored); + cleanup_test_directories(dirs); + + lilv_state_free(restored); + lilv_node_free(state_node); + lilv_node_free(bundle_node); + serd_node_free(&bundle_uri); + lilv_state_free(start_state); + free(bundle_path); lilv_instance_free(instance); + test_context_free(ctx); +} + +static void +test_label_round_trip(void) +{ + TestContext* const ctx = test_context_new(); + const TestDirectories dirs = create_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, ctx->features); + + assert(instance); + + // Get initial state + LilvState* const state = + state_from_instance(plugin, instance, ctx, &dirs, NULL); + + // Set a label + lilv_state_set_label(state, "Monopoly on violence"); + + // Save to a bundle + char* const bundle_path = lilv_path_join(dirs.top, "state.lv2/"); + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + state, + NULL, + bundle_path, + "state.ttl")); - lilv_node_free(num); + // Load bundle and check the label and that the states are equal + char* const state_path = lilv_path_join(bundle_path, "state.ttl"); + LilvState* const loaded = + lilv_state_new_from_file(ctx->env->world, &ctx->map, NULL, state_path); + + assert(loaded); + assert(lilv_state_equals(state, loaded)); + assert(!strcmp(lilv_state_get_label(loaded), "Monopoly on violence")); + + lilv_state_delete(ctx->env->world, state); + cleanup_test_directories(dirs); + + lilv_state_free(loaded); + free(state_path); + free(bundle_path); 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"); + lilv_instance_free(instance); + test_context_free(ctx); +} - lilv_node_free(plugin_uri); - free(link_dir); - free(copy_dir); - free(temp_dir); +static void +test_bad_subject(void) +{ + TestContext* const ctx = test_context_new(); + LilvNode* const string = lilv_new_string(ctx->env->world, "Not a URI"); + + LilvState* const file_state = lilv_state_new_from_file(ctx->env->world, + &ctx->map, + string, + "/I/do/not/matter"); + + assert(!file_state); + + LilvState* const world_state = + lilv_state_new_from_world(ctx->env->world, &ctx->map, string); + assert(!world_state); + + lilv_node_free(string); test_context_free(ctx); +} + +static void +count_file(const char* path, const char* name, void* data) +{ + *(unsigned*)data += 1; +} + +static void +test_delete(void) +{ + TestContext* const ctx = test_context_new(); + TestDirectories dirs = create_test_directories(); + const LilvPlugin* const plugin = load_test_plugin(ctx); + + LV2_State_Make_Path make_path = {&dirs, make_scratch_path}; + LV2_Feature make_path_feature = {LV2_STATE__makePath, &make_path}; + + const LV2_Feature* const instance_features[] = {&ctx->map_feature, + &ctx->free_path_feature, + &make_path_feature, + NULL}; + + LilvInstance* const instance = + lilv_plugin_instantiate(plugin, 48000.0, instance_features); + + assert(instance); + + // Run plugin to generate some recording file data + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &ctx->in); + lilv_instance_connect_port(instance, 1, &ctx->out); + lilv_instance_run(instance, 1); + lilv_instance_run(instance, 2); + assert(ctx->in == 1.0); + assert(ctx->out == 1.0); + + // Save state to a bundle + char* const bundle_path = lilv_path_join(dirs.top, "state.lv2/"); + LilvState* const state = + state_from_instance(plugin, instance, ctx, &dirs, bundle_path); + + assert(!lilv_state_save(ctx->env->world, + &ctx->map, + &ctx->unmap, + state, + NULL, + bundle_path, + "state.ttl")); + + // Count the number of shared files before doing anything + unsigned n_shared_files_before = 0; + lilv_dir_for_each(dirs.shared, &n_shared_files_before, count_file); + + // Delete the state + assert(!lilv_state_delete(ctx->env->world, state)); + + // Ensure the number of shared files is the same after deletion + unsigned n_shared_files_after = 0; + lilv_dir_for_each(dirs.shared, &n_shared_files_after, count_file); + assert(n_shared_files_before == n_shared_files_after); + + // Ensure the state directory has been deleted + assert(!lilv_path_exists(bundle_path)); + + cleanup_test_directories(dirs); + + lilv_state_free(state); + free(bundle_path); + lilv_instance_free(instance); + test_context_free(ctx); +} + +int +main(void) +{ + test_instance_state(); + test_equal(); + test_changed_plugin_data(); + test_changed_metadata(); + test_to_string(); + test_string_round_trip(); + test_to_files(); + test_files_round_trip(); + test_world_round_trip(); + test_label_round_trip(); + test_bad_subject(); + test_delete(); return 0; } -- cgit v1.2.1