From 8708ccb858ddbf5d521c4755e137bd04544a6ae5 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Fri, 30 Dec 2011 08:23:19 +0000 Subject: Support arbitrary binary data in plugin state via base64 encoding. Unit testing for plugin instantiation and state. Build without LV2 state available. Support URID values in plugin state nicely. Fix various holes in state implementation exposed by tests. git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@3908 a436a847-0d15-0410-975c-d299462d15a1 --- test/lilv_test.c | 154 +++++++++++++++++++++++++++++++++ test/manifest.ttl | 7 ++ test/test_plugin.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_plugin.ttl | 40 +++++++++ 4 files changed, 436 insertions(+) create mode 100644 test/manifest.ttl create mode 100644 test/test_plugin.c create mode 100644 test/test_plugin.ttl (limited to 'test') diff --git a/test/lilv_test.c b/test/lilv_test.c index b5c5302..1994832 100644 --- a/test/lilv_test.c +++ b/test/lilv_test.c @@ -28,6 +28,9 @@ #include #include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" #define TEST_PATH_MAX 1024 @@ -972,6 +975,156 @@ test_ui(void) /*****************************************************************************/ +float in = 1.0; +float out = 42.0; + +LilvNode* +get_port_value(const char* port_symbol, void* user_data) +{ + if (!strcmp(port_symbol, "input")) { + return lilv_new_float((LilvWorld*)user_data, in); + } else if (!strcmp(port_symbol, "output")) { + return lilv_new_float((LilvWorld*)user_data, out); + } else { + fprintf(stderr, "error: get_port_value for nonexistent port `%s'\n", + port_symbol); + return NULL; + } +} + +void +set_port_value(const char* port_symbol, + const LilvNode* value, + void* user_data) +{ + if (!strcmp(port_symbol, "input")) { + in = lilv_node_as_float(value); + } else if (!strcmp(port_symbol, "output")) { + out = lilv_node_as_float(value); + } else { + fprintf(stderr, "error: set_port_value for nonexistent port `%s'\n", + port_symbol); + } +} + +char** uris = NULL; +size_t n_uris = 0; + +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; + } + } + + uris = realloc(uris, ++n_uris * sizeof(char*)); + uris[n_uris - 1] = lilv_strdup(uri); + return n_uris; +} + +const char* +unmap_uri(LV2_URID_Map_Handle handle, + LV2_URID urid) +{ + if (urid > 0 && urid <= n_uris) { + return uris[urid - 1]; + } + return NULL; +} + +int +test_state(void) +{ + LilvWorld* world = lilv_world_new(); + LilvNode* bundle_uri = lilv_new_uri(world, LILV_TEST_BUNDLE); + LilvNode* plugin_uri = lilv_new_uri(world, + "http://example.org/lilv-test-plugin"); + lilv_world_load_bundle(world, bundle_uri); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_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 }; + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, features); + TEST_ASSERT(instance); + + lilv_instance_connect_port(instance, 0, &in); + lilv_instance_connect_port(instance, 1, &out); + + lilv_instance_run(instance, 1); + TEST_ASSERT(in == 1.0); + TEST_ASSERT(out == 1.0); + + // Get instance state state + LilvState* state = lilv_state_new_from_instance( + plugin, instance, get_port_value, world, 0, NULL); + + // Get another instance state + LilvState* state2 = lilv_state_new_from_instance( + plugin, instance, get_port_value, world, 0, NULL); + + // Ensure they are equal + TEST_ASSERT(lilv_state_equals(state, state2)); + + const LilvNode* state_plugin_uri = lilv_state_get_plugin_uri(state); + TEST_ASSERT(lilv_node_equals(state_plugin_uri, plugin_uri)); + + // Tinker with the label of the first state + TEST_ASSERT(lilv_state_get_label(state) == NULL); + lilv_state_set_label(state, "Test State Old Label"); + TEST_ASSERT(!strcmp(lilv_state_get_label(state), "Test State Old Label")); + lilv_state_set_label(state, "Test State"); + TEST_ASSERT(!strcmp(lilv_state_get_label(state), "Test State")); + + TEST_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, get_port_value, world, 0, NULL); + TEST_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, get_port_value, world, 0, NULL); + TEST_ASSERT(lilv_state_equals(state2, state4)); + + // Save state to a file + int ret = lilv_state_save(world, &unmap, state, NULL, + "state.ttl", "manifest.ttl"); + TEST_ASSERT(!ret); + + // Load state from file + LilvState* state5 = lilv_state_new_from_file(world, &map, NULL, "state.ttl"); + TEST_ASSERT(lilv_state_equals(state, state5)); // Round trip accuracy + + lilv_state_free(state); + lilv_state_free(state2); + lilv_state_free(state3); + lilv_state_free(state4); + lilv_state_free(state5); + + lilv_instance_free(instance); + + lilv_node_free(bundle_uri); + lilv_world_free(world); + return 1; +} + +/*****************************************************************************/ + /* add tests here */ static struct TestCase tests[] = { TEST_CASE(utils), @@ -983,6 +1136,7 @@ static struct TestCase tests[] = { TEST_CASE(plugin), TEST_CASE(port), TEST_CASE(ui), + TEST_CASE(state), { NULL, NULL } }; diff --git a/test/manifest.ttl b/test/manifest.ttl new file mode 100644 index 0000000..5af7e88 --- /dev/null +++ b/test/manifest.ttl @@ -0,0 +1,7 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/test/test_plugin.c b/test/test_plugin.c new file mode 100644 index 0000000..0ef76f2 --- /dev/null +++ b/test/test_plugin.c @@ -0,0 +1,235 @@ +/* + Lilv Test Plugin + Copyright 2011 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. +*/ + +#include +#include +#include + +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define TEST_URI "http://example.org/lilv-test-plugin" + +#define NS_ATOM "http://lv2plug.in/ns/ext/atom#" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + LV2_URID_Map* map; + + struct { + LV2_URID atom_Float; + } uris; + + float* input; + float* output; + unsigned num_runs; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)malloc(sizeof(Test)); + if (!test) { + return NULL; + } + + test->map = NULL; + test->input = NULL; + test->output = NULL; + test->num_runs = 0; + + /* Scan host features for URID map */ + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + test->map = (LV2_URID_Map*)features[i]->data; + test->uris.atom_Float = test->map->map( + test->map->handle, NS_ATOM "Float"); + } + } + + if (!test->map) { + fprintf(stderr, "Host does not support urid:map.\n"); + free(test); + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, + uint32_t sample_count) +{ + Test* test = (Test*)instance; + *test->output = *test->input; + ++test->num_runs; +} + +static uint32_t +map_uri(Test* plugin, const char* uri) +{ + return plugin->map->map(plugin->map->handle, uri); +} + +static void +save(LV2_Handle instance, + LV2_State_Store_Function store, + void* callback_data, + uint32_t flags, + const LV2_Feature* const* features) +{ + Test* plugin = (Test*)instance; + + store(callback_data, + map_uri(plugin, "http://example.org/greeting"), + "hello", + strlen("hello") + 1, + map_uri(plugin, NS_ATOM "String"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint32_t urid = map_uri(plugin, "http://example.org/urivalue"); + store(callback_data, + map_uri(plugin, "http://example.org/uri"), + &urid, + sizeof(uint32_t), + map_uri(plugin, NS_ATOM "URID"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + store(callback_data, + map_uri(plugin, "http://example.org/num-runs"), + &plugin->num_runs, + sizeof(plugin->num_runs), + map_uri(plugin, NS_ATOM "Int32"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const float two = 2.0f; + store(callback_data, + map_uri(plugin, "http://example.org/two"), + &two, + sizeof(two), + map_uri(plugin, NS_ATOM "Float"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint32_t affirmative = 1; + store(callback_data, + map_uri(plugin, "http://example.org/true"), + &affirmative, + sizeof(affirmative), + map_uri(plugin, NS_ATOM "Bool"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint32_t negative = 0; + store(callback_data, + map_uri(plugin, "http://example.org/false"), + &negative, + sizeof(negative), + map_uri(plugin, NS_ATOM "Bool"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint8_t blob[] = "This is a bunch of data that happens to be text" + " but could really be anything at all, lest you feel like cramming" + " all sorts of ridiculous binary stuff in Turtle"; + store(callback_data, + map_uri(plugin, "http://example.org/blob"), + blob, + sizeof(blob), + map_uri(plugin, "http://example.org/SomeUnknownType"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); +} + +static void +restore(LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + void* callback_data, + uint32_t flags, + const LV2_Feature* const* features) +{ + Test* plugin = (Test*)instance; + + size_t size; + uint32_t type; + uint32_t valflags; + + plugin->num_runs = *(int32_t*)retrieve( + callback_data, + map_uri(plugin, "http://example.org/num-runs"), + &size, &type, &valflags); +} + +const void* +extension_data(const char* uri) +{ + static const LV2_State_Interface state = { save, restore }; + if (!strcmp(uri, LV2_STATE_INTERFACE_URI)) { + return &state; + } + return NULL; +} + +static const LV2_Descriptor descriptor = { + TEST_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/test/test_plugin.ttl b/test/test_plugin.ttl new file mode 100644 index 0000000..ec16d00 --- /dev/null +++ b/test/test_plugin.ttl @@ -0,0 +1,40 @@ +# Lilv Test Plugin +# Copyright 2011 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. + +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix ui: . + + + a lv2:Plugin ; + doap:name "Lilv Test" ; + doap:license ; + lv2:requiredFeature ; + lv2:optionalFeature lv2:hardRtCapable ; + lv2:extensionData ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . -- cgit v1.2.1