diff options
Diffstat (limited to 'test/lilv_test.c')
-rw-r--r-- | test/lilv_test.c | 2299 |
1 files changed, 2299 insertions, 0 deletions
diff --git a/test/lilv_test.c b/test/lilv_test.c new file mode 100644 index 0000000..b55f3e9 --- /dev/null +++ b/test/lilv_test.c @@ -0,0 +1,2299 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + Copyright 2008 Krzysztof Foltman + + 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. +*/ + +#define _POSIX_C_SOURCE 200809L /* for setenv */ +#define _XOPEN_SOURCE 600 /* for mkstemp */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <float.h> +#include <limits.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#ifdef _WIN32 +# include <direct.h> +# include <io.h> +# define mkdir(path, flags) _mkdir(path) +# define setenv(n, v, r) SetEnvironmentVariable((n), (v)) +# define unsetenv(n) SetEnvironmentVariable((n), NULL) +# define mkstemp(pat) _mktemp(pat) +#else +# include <dirent.h> +# include <unistd.h> +#endif + +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#define TEST_PATH_MAX 1024 + +#if defined(__APPLE__) +# define SHLIB_EXT ".dylib" +#elif defined(_WIN32) +# define SHLIB_EXT ".dll" +#else +# define SHLIB_EXT ".so" +#endif + +static char bundle_dir_name[TEST_PATH_MAX + sizeof("/.lv2/lilv-test.lv2")]; +static char bundle_dir_uri[sizeof(bundle_dir_name) + sizeof("file:///")]; +static char manifest_name[sizeof(bundle_dir_name) + sizeof("/manifest.ttl")]; +static char content_name[sizeof(bundle_dir_name) + sizeof("plugin.ttl")]; + +static LilvWorld* world; + +int test_count = 0; +int error_count = 0; + +static void +delete_bundle(void) +{ + unlink(content_name); + unlink(manifest_name); + remove(bundle_dir_name); +} + +static void +init_tests(void) +{ + snprintf(bundle_dir_name, sizeof(bundle_dir_name), "%s/.lv2/lilv-test.lv2", + getenv("HOME")); + lilv_mkdir_p(bundle_dir_name); + + snprintf(bundle_dir_uri, sizeof(bundle_dir_uri), "file://%s/", + bundle_dir_name); + snprintf(manifest_name, sizeof(manifest_name), "%s/manifest.ttl", + bundle_dir_name); + snprintf(content_name, sizeof(content_name), "%s/plugin.ttl", + bundle_dir_name); + + delete_bundle(); +} + +static void +fatal_error(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, args); + va_end(args); + exit(1); +} + +static void +write_file(const char* name, const char* content) +{ + FILE* f = fopen(name, "w"); + size_t len = strlen(content); + if (fwrite(content, 1, len, f) != len) { + fatal_error("Failed to write to file '%s' (%s)\n", + name, strerror(errno)); + } + fclose(f); +} + +static int +init_world(void) +{ + world = lilv_world_new(); + return world != NULL; +} + +static int +load_all_bundles(void) +{ + if (!init_world()) { + return 0; + } + lilv_world_load_all(world); + return 1; +} + +static void +create_bundle(const char* manifest, const char* content) +{ + if (mkdir(bundle_dir_name, 0700) && errno != EEXIST) { + fatal_error("Failed to create directory '%s' (%s)\n", + bundle_dir_name, strerror(errno)); + } + write_file(manifest_name, manifest); + write_file(content_name, content); +} + +static int +start_bundle(const char* manifest, const char* content) +{ + create_bundle(manifest, content); + return load_all_bundles(); +} + +static void +unload_bundle(void) +{ + if (world) { + lilv_world_free(world); + } + world = NULL; +} + +static void +cleanup(void) +{ + delete_bundle(); +} + +/*****************************************************************************/ + +#define TEST_CASE(name) { #name, test_##name } +#define TEST_ASSERT(check) do {\ + test_count++;\ + if (!(check)) {\ + error_count++;\ + fprintf(stderr, "lilv_test.c:%d: error: test `%s' failed\n", __LINE__, #check);\ + abort();\ + }\ +} while (0) + +typedef int (*TestFunc)(void); + +struct TestCase { + const char* title; + TestFunc func; +}; + +#define PREFIX_ATOM "@prefix atom: <http://lv2plug.in/ns/ext/atom#> . \n" +#define PREFIX_LINE "@prefix : <http://example.org/> .\n" +#define PREFIX_LV2 "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n" +#define PREFIX_LV2EV "@prefix lv2ev: <http://lv2plug.in/ns/ext/event#> . \n" +#define PREFIX_LV2UI "@prefix lv2ui: <http://lv2plug.in/ns/extensions/ui#> .\n" +#define PREFIX_RDF "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" +#define PREFIX_RDFS "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +#define PREFIX_FOAF "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n" +#define PREFIX_DOAP "@prefix doap: <http://usefulinc.com/ns/doap#> .\n" +#define PREFIX_PSET "@prefix pset: <http://lv2plug.in/ns/ext/presets#> .\n" + +#define MANIFEST_PREFIXES PREFIX_LINE PREFIX_LV2 PREFIX_RDFS +#define BUNDLE_PREFIXES PREFIX_ATOM PREFIX_LINE PREFIX_LV2 PREFIX_RDF PREFIX_RDFS PREFIX_FOAF PREFIX_DOAP PREFIX_PSET +#define PLUGIN_NAME(name) "doap:name \"" name "\"" +#define LICENSE_GPL "doap:license <http://usefulinc.com/doap/licenses/gpl>" + +static const char* uris_plugin = "http://example.org/plug"; +static LilvNode* plugin_uri_value; +static LilvNode* plugin2_uri_value; + +/*****************************************************************************/ + +static void +init_uris(void) +{ + plugin_uri_value = lilv_new_uri(world, uris_plugin); + plugin2_uri_value = lilv_new_uri(world, "http://example.org/foobar"); + TEST_ASSERT(plugin_uri_value); + TEST_ASSERT(plugin2_uri_value); +} + +static void +cleanup_uris(void) +{ + lilv_node_free(plugin2_uri_value); + lilv_node_free(plugin_uri_value); + plugin2_uri_value = NULL; + plugin_uri_value = NULL; +} + +/*****************************************************************************/ + +static int +test_value(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"Foo\" ; " + "] .")) { + return 0; + } + + init_uris(); + + LilvNode* uval = lilv_new_uri(world, "http://example.org"); + LilvNode* sval = lilv_new_string(world, "Foo"); + LilvNode* ival = lilv_new_int(world, 42); + LilvNode* fval = lilv_new_float(world, 1.6180); + + TEST_ASSERT(lilv_node_is_uri(uval)); + TEST_ASSERT(lilv_node_is_string(sval)); + TEST_ASSERT(lilv_node_is_int(ival)); + TEST_ASSERT(lilv_node_is_float(fval)); + + TEST_ASSERT(!lilv_node_is_literal(NULL)); + TEST_ASSERT(!lilv_node_is_literal(uval)); + TEST_ASSERT(lilv_node_is_literal(sval)); + TEST_ASSERT(lilv_node_is_literal(ival)); + TEST_ASSERT(lilv_node_is_literal(fval)); + TEST_ASSERT(!lilv_node_get_path(fval, NULL)); + + TEST_ASSERT(!strcmp(lilv_node_as_uri(uval), "http://example.org")); + TEST_ASSERT(!strcmp(lilv_node_as_string(sval), "Foo")); + TEST_ASSERT(lilv_node_as_int(ival) == 42); + TEST_ASSERT(fabs(lilv_node_as_float(fval) - 1.6180) < FLT_EPSILON); + TEST_ASSERT(isnan(lilv_node_as_float(sval))); + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + TEST_ASSERT(!strcmp(lilv_uri_to_path("file:///foo"), "/foo")); + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic pop +#endif + + LilvNode* loc_abs = lilv_new_file_uri(world, NULL, "/foo/bar"); + LilvNode* loc_rel = lilv_new_file_uri(world, NULL, "foo"); + LilvNode* host_abs = lilv_new_file_uri(world, "host", "/foo/bar"); + LilvNode* host_rel = lilv_new_file_uri(world, "host", "foo"); + + TEST_ASSERT(!strcmp(lilv_node_as_uri(loc_abs), "file:///foo/bar")); + TEST_ASSERT(!strncmp(lilv_node_as_uri(loc_rel), "file:///", 8)); + TEST_ASSERT(!strcmp(lilv_node_as_uri(host_abs), "file://host/foo/bar")); + TEST_ASSERT(!strncmp(lilv_node_as_uri(host_rel), "file://host/", 12)); + + lilv_node_free(host_rel); + lilv_node_free(host_abs); + lilv_node_free(loc_rel); + lilv_node_free(loc_abs); + + char* tok = lilv_node_get_turtle_token(uval); + TEST_ASSERT(!strcmp(tok, "<http://example.org>")); + lilv_free(tok); + tok = lilv_node_get_turtle_token(sval); + TEST_ASSERT(!strcmp(tok, "Foo")); + lilv_free(tok); + tok = lilv_node_get_turtle_token(ival); + TEST_ASSERT(!strcmp(tok, "42")); + lilv_free(tok); + tok = lilv_node_get_turtle_token(fval); + TEST_ASSERT(!strncmp(tok, "1.6180", 6)); + lilv_free(tok); + + LilvNode* uval_e = lilv_new_uri(world, "http://example.org"); + LilvNode* sval_e = lilv_new_string(world, "Foo"); + LilvNode* ival_e = lilv_new_int(world, 42); + LilvNode* fval_e = lilv_new_float(world, 1.6180); + LilvNode* uval_ne = lilv_new_uri(world, "http://no-example.org"); + LilvNode* sval_ne = lilv_new_string(world, "Bar"); + LilvNode* ival_ne = lilv_new_int(world, 24); + LilvNode* fval_ne = lilv_new_float(world, 3.14159); + + TEST_ASSERT(lilv_node_equals(uval, uval_e)); + TEST_ASSERT(lilv_node_equals(sval, sval_e)); + TEST_ASSERT(lilv_node_equals(ival, ival_e)); + TEST_ASSERT(lilv_node_equals(fval, fval_e)); + + TEST_ASSERT(!lilv_node_equals(uval, uval_ne)); + TEST_ASSERT(!lilv_node_equals(sval, sval_ne)); + TEST_ASSERT(!lilv_node_equals(ival, ival_ne)); + TEST_ASSERT(!lilv_node_equals(fval, fval_ne)); + + TEST_ASSERT(!lilv_node_equals(uval, sval)); + TEST_ASSERT(!lilv_node_equals(sval, ival)); + TEST_ASSERT(!lilv_node_equals(ival, fval)); + + LilvNode* uval_dup = lilv_node_duplicate(uval); + TEST_ASSERT(lilv_node_equals(uval, uval_dup)); + + LilvNode* ifval = lilv_new_float(world, 42.0); + TEST_ASSERT(!lilv_node_equals(ival, ifval)); + lilv_node_free(ifval); + + LilvNode* nil = NULL; + TEST_ASSERT(!lilv_node_equals(uval, nil)); + TEST_ASSERT(!lilv_node_equals(nil, uval)); + TEST_ASSERT(lilv_node_equals(nil, nil)); + + LilvNode* nil2 = lilv_node_duplicate(nil); + TEST_ASSERT(lilv_node_equals(nil, nil2)); + + lilv_node_free(uval); + lilv_node_free(sval); + lilv_node_free(ival); + lilv_node_free(fval); + lilv_node_free(uval_e); + lilv_node_free(sval_e); + lilv_node_free(ival_e); + lilv_node_free(fval_e); + lilv_node_free(uval_ne); + lilv_node_free(sval_ne); + lilv_node_free(ival_ne); + lilv_node_free(fval_ne); + lilv_node_free(uval_dup); + lilv_node_free(nil2); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_util(void) +{ + TEST_ASSERT(!lilv_realpath(NULL)); + + char a_path[16]; + char b_path[16]; + strncpy(a_path, "copy_a_XXXXXX", sizeof(a_path)); + strncpy(b_path, "copy_b_XXXXXX", sizeof(b_path)); + mkstemp(a_path); + mkstemp(b_path); + + FILE* fa = fopen(a_path, "w"); + FILE* fb = fopen(b_path, "w"); + fprintf(fa, "AA\n"); + fprintf(fb, "AB\n"); + fclose(fa); + fclose(fb); + + TEST_ASSERT(lilv_copy_file("does/not/exist", "copy")); + TEST_ASSERT(lilv_copy_file(a_path, "not/a/dir/copy")); + TEST_ASSERT(!lilv_copy_file(a_path, "copy_c")); + TEST_ASSERT(!lilv_file_equals(a_path, b_path)); + TEST_ASSERT(lilv_file_equals(a_path, a_path)); + TEST_ASSERT(lilv_file_equals(a_path, "copy_c")); + TEST_ASSERT(!lilv_file_equals("does/not/exist", b_path)); + TEST_ASSERT(!lilv_file_equals(a_path, "does/not/exist")); + TEST_ASSERT(!lilv_file_equals("does/not/exist", "/does/not/either")); + return 1; +} + +/*****************************************************************************/ + +static int discovery_plugin_found = 0; + +static void +discovery_verify_plugin(const LilvPlugin* plugin) +{ + const LilvNode* value = lilv_plugin_get_uri(plugin); + if (lilv_node_equals(value, plugin_uri_value)) { + const LilvNode* lib_uri = NULL; + TEST_ASSERT(!lilv_node_equals(value, plugin2_uri_value)); + discovery_plugin_found = 1; + lib_uri = lilv_plugin_get_library_uri(plugin); + TEST_ASSERT(lib_uri); + TEST_ASSERT(lilv_node_is_uri(lib_uri)); + TEST_ASSERT(lilv_node_as_uri(lib_uri)); + TEST_ASSERT(strstr(lilv_node_as_uri(lib_uri), "foo" SHLIB_EXT)); + TEST_ASSERT(lilv_plugin_verify(plugin)); + } +} + +static int +test_discovery(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ;" + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ a lv2:ControlPort ; a lv2:InputPort ;" + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; ] .")) { + return 0; + } + + init_uris(); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + TEST_ASSERT(lilv_plugins_size(plugins) > 0); + + const LilvPlugin* explug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(explug != NULL); + const LilvPlugin* explug2 = lilv_plugins_get_by_uri(plugins, plugin2_uri_value); + TEST_ASSERT(explug2 == NULL); + + if (explug) { + LilvNode* name = lilv_plugin_get_name(explug); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "Test plugin")); + lilv_node_free(name); + } + + discovery_plugin_found = 0; + LILV_FOREACH(plugins, i, plugins) + discovery_verify_plugin(lilv_plugins_get(plugins, i)); + + TEST_ASSERT(discovery_plugin_found); + plugins = NULL; + + cleanup_uris(); + + return 1; +} + +/*****************************************************************************/ + +static int +test_lv2_path(void) +{ +#ifndef _WIN32 + char* orig_lv2_path = lilv_strdup(getenv("LV2_PATH")); + + setenv("LV2_PATH", "~/.lv2:/usr/local/lib/lv2:/usr/lib/lv2", 1); + + world = lilv_world_new(); + lilv_world_load_all(world); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const size_t n_plugins = lilv_plugins_size(plugins); + + lilv_world_free(world); + + setenv("LV2_PATH", "$HOME/.lv2:/usr/local/lib/lv2:/usr/lib/lv2", 1); + world = lilv_world_new(); + lilv_world_load_all(world); + plugins = lilv_world_get_all_plugins(world); + TEST_ASSERT(lilv_plugins_size(plugins) == n_plugins); + lilv_world_free(world); + world = NULL; + + if (orig_lv2_path) { + setenv("LV2_PATH", orig_lv2_path, 1); + } else { + unsetenv("LV2_PATH"); + } + free(orig_lv2_path); +#endif + return 1; +} + +/*****************************************************************************/ + +static int +test_verify(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ a lv2:ControlPort ; a lv2:InputPort ;" + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ] .")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* explug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(explug); + TEST_ASSERT(lilv_plugin_verify(explug)); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_no_verify(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin . ")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* explug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(explug); + TEST_ASSERT(!lilv_plugin_verify(explug)); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_classes(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"Foo\" ; " + "] .")) { + return 0; + } + + init_uris(); + const LilvPluginClass* plugin = lilv_world_get_plugin_class(world); + const LilvPluginClasses* classes = lilv_world_get_plugin_classes(world); + LilvPluginClasses* children = lilv_plugin_class_get_children(plugin); + + TEST_ASSERT(lilv_plugin_class_get_parent_uri(plugin) == NULL); + TEST_ASSERT(lilv_plugin_classes_size(classes) > lilv_plugin_classes_size(children)); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_plugin_class_get_label(plugin)), "Plugin")); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_plugin_class_get_uri(plugin)), + "http://lv2plug.in/ns/lv2core#Plugin")); + + LILV_FOREACH(plugin_classes, i, children) { + TEST_ASSERT(lilv_node_equals( + lilv_plugin_class_get_parent_uri(lilv_plugin_classes_get(children, i)), + lilv_plugin_class_get_uri(plugin))); + } + + LilvNode* some_uri = lilv_new_uri(world, "http://example.org/whatever"); + TEST_ASSERT(lilv_plugin_classes_get_by_uri(classes, some_uri) == NULL); + lilv_node_free(some_uri); + + lilv_plugin_classes_free(children); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_plugin(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:optionalFeature lv2:hardRTCapable ; " + "lv2:requiredFeature <http://lv2plug.in/ns/ext/event> ; " + "lv2:extensionData <http://example.org/extdata> ;" + ":foo 1.6180 ; " + ":bar true ; " + ":baz false ; " + ":blank [ a <http://example.org/blank> ] ; " + "doap:maintainer [ foaf:name \"David Robillard\" ; " + " foaf:homepage <http://drobilla.net> ; foaf:mbox <mailto:d@drobilla.net> ] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + const LilvPluginClass* klass = lilv_plugin_get_class(plug); + const LilvNode* klass_uri = lilv_plugin_class_get_uri(klass); + TEST_ASSERT(!strcmp(lilv_node_as_string(klass_uri), + "http://lv2plug.in/ns/lv2core#CompressorPlugin")); + + LilvNode* rdf_type = lilv_new_uri( + world, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); + TEST_ASSERT(lilv_world_ask(world, + lilv_plugin_get_uri(plug), + rdf_type, + klass_uri)); + lilv_node_free(rdf_type); + + TEST_ASSERT(!lilv_plugin_is_replaced(plug)); + TEST_ASSERT(!lilv_plugin_get_related(plug, NULL)); + + const LilvNode* plug_bundle_uri = lilv_plugin_get_bundle_uri(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(plug_bundle_uri), bundle_dir_uri)); + + const LilvNodes* data_uris = lilv_plugin_get_data_uris(plug); + TEST_ASSERT(lilv_nodes_size(data_uris) == 2); + + LilvNode* project = lilv_plugin_get_project(plug); + TEST_ASSERT(!project); + + char* manifest_uri = (char*)malloc(TEST_PATH_MAX); + char* data_uri = (char*)malloc(TEST_PATH_MAX); + snprintf(manifest_uri, TEST_PATH_MAX, "%s%s", + lilv_node_as_string(plug_bundle_uri), "manifest.ttl"); + snprintf(data_uri, TEST_PATH_MAX, "%s%s", + lilv_node_as_string(plug_bundle_uri), "plugin.ttl"); + + LilvNode* manifest_uri_val = lilv_new_uri(world, manifest_uri); + TEST_ASSERT(lilv_nodes_contains(data_uris, manifest_uri_val)); + lilv_node_free(manifest_uri_val); + + LilvNode* data_uri_val = lilv_new_uri(world, data_uri); + TEST_ASSERT(lilv_nodes_contains(data_uris, data_uri_val)); + lilv_node_free(data_uri_val); + + LilvNode* unknown_uri_val = lilv_new_uri(world, "http://example.org/unknown"); + TEST_ASSERT(!lilv_nodes_contains(data_uris, unknown_uri_val)); + lilv_node_free(unknown_uri_val); + + free(manifest_uri); + free(data_uri); + + float mins[3]; + float maxs[3]; + float defs[3]; + lilv_plugin_get_port_ranges_float(plug, mins, maxs, defs); + TEST_ASSERT(mins[0] == -1.0f); + TEST_ASSERT(maxs[0] == 1.0f); + TEST_ASSERT(defs[0] == 0.5f); + + LilvNode* audio_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#AudioPort"); + LilvNode* control_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#ControlPort"); + LilvNode* in_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#InputPort"); + LilvNode* out_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#OutputPort"); + + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, NULL) == 3); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class, NULL) == 0); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, in_class, NULL) == 2); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, out_class, NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, in_class, NULL) == 2); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, out_class, NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class, in_class, NULL) == 0); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class, out_class, NULL) == 0); + + TEST_ASSERT(lilv_plugin_has_latency(plug)); + TEST_ASSERT(lilv_plugin_get_latency_port_index(plug) == 2); + + LilvNode* lv2_latency = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#latency"); + const LilvPort* latency_port = lilv_plugin_get_port_by_designation( + plug, out_class, lv2_latency); + lilv_node_free(lv2_latency); + + TEST_ASSERT(latency_port); + TEST_ASSERT(lilv_port_get_index(plug, latency_port) == 2); + TEST_ASSERT(lilv_node_is_blank(lilv_port_get_node(plug, latency_port))); + + LilvNode* rt_feature = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#hardRTCapable"); + LilvNode* event_feature = lilv_new_uri(world, + "http://lv2plug.in/ns/ext/event"); + LilvNode* pretend_feature = lilv_new_uri(world, + "http://example.org/solvesWorldHunger"); + + TEST_ASSERT(lilv_plugin_has_feature(plug, rt_feature)); + TEST_ASSERT(lilv_plugin_has_feature(plug, event_feature)); + TEST_ASSERT(!lilv_plugin_has_feature(plug, pretend_feature)); + + lilv_node_free(rt_feature); + lilv_node_free(event_feature); + lilv_node_free(pretend_feature); + + LilvNodes* supported = lilv_plugin_get_supported_features(plug); + LilvNodes* required = lilv_plugin_get_required_features(plug); + LilvNodes* optional = lilv_plugin_get_optional_features(plug); + TEST_ASSERT(lilv_nodes_size(supported) == 2); + TEST_ASSERT(lilv_nodes_size(required) == 1); + TEST_ASSERT(lilv_nodes_size(optional) == 1); + lilv_nodes_free(supported); + lilv_nodes_free(required); + lilv_nodes_free(optional); + + LilvNode* foo_p = lilv_new_uri(world, "http://example.org/foo"); + LilvNodes* foos = lilv_plugin_get_value(plug, foo_p); + TEST_ASSERT(lilv_nodes_size(foos) == 1); + TEST_ASSERT(fabs(lilv_node_as_float(lilv_nodes_get_first(foos)) - 1.6180) < FLT_EPSILON); + lilv_node_free(foo_p); + lilv_nodes_free(foos); + + LilvNode* bar_p = lilv_new_uri(world, "http://example.org/bar"); + LilvNodes* bars = lilv_plugin_get_value(plug, bar_p); + TEST_ASSERT(lilv_nodes_size(bars) == 1); + TEST_ASSERT(lilv_node_as_bool(lilv_nodes_get_first(bars)) == true); + lilv_node_free(bar_p); + lilv_nodes_free(bars); + + LilvNode* baz_p = lilv_new_uri(world, "http://example.org/baz"); + LilvNodes* bazs = lilv_plugin_get_value(plug, baz_p); + TEST_ASSERT(lilv_nodes_size(bazs) == 1); + TEST_ASSERT(lilv_node_as_bool(lilv_nodes_get_first(bazs)) == false); + lilv_node_free(baz_p); + lilv_nodes_free(bazs); + + LilvNode* blank_p = lilv_new_uri(world, "http://example.org/blank"); + LilvNodes* blanks = lilv_plugin_get_value(plug, blank_p); + TEST_ASSERT(lilv_nodes_size(blanks) == 1); + LilvNode* blank = lilv_nodes_get_first(blanks); + TEST_ASSERT(lilv_node_is_blank(blank)); + const char* blank_str = lilv_node_as_blank(blank); + char* blank_tok = lilv_node_get_turtle_token(blank); + TEST_ASSERT(!strncmp(blank_tok, "_:", 2)); + TEST_ASSERT(!strcmp(blank_tok + 2, blank_str)); + lilv_free(blank_tok); + lilv_node_free(blank_p); + lilv_nodes_free(blanks); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_name), "David Robillard")); + lilv_node_free(author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_email), "mailto:d@drobilla.net")); + lilv_node_free(author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_homepage), "http://drobilla.net")); + lilv_node_free(author_homepage); + + LilvNode* thing_uri = lilv_new_uri(world, "http://example.org/thing"); + LilvNode* name_p = lilv_new_uri(world, "http://usefulinc.com/ns/doap#name"); + LilvNodes* thing_names = lilv_world_find_nodes(world, thing_uri, name_p, NULL); + TEST_ASSERT(lilv_nodes_size(thing_names) == 1); + LilvNode* thing_name = lilv_nodes_get_first(thing_names); + TEST_ASSERT(thing_name); + TEST_ASSERT(lilv_node_is_string(thing_name)); + TEST_ASSERT(!strcmp(lilv_node_as_string(thing_name), "Something else")); + LilvNode* thing_name2 = lilv_world_get(world, thing_uri, name_p, NULL); + TEST_ASSERT(lilv_node_equals(thing_name, thing_name2)); + + LilvUIs* uis = lilv_plugin_get_uis(plug); + TEST_ASSERT(lilv_uis_size(uis) == 0); + lilv_uis_free(uis); + + LilvNode* extdata = lilv_new_uri(world, "http://example.org/extdata"); + LilvNode* noextdata = lilv_new_uri(world, "http://example.org/noextdata"); + LilvNodes* extdatas = lilv_plugin_get_extension_data(plug); + TEST_ASSERT(lilv_plugin_has_extension_data(plug, extdata)); + TEST_ASSERT(!lilv_plugin_has_extension_data(plug, noextdata)); + TEST_ASSERT(lilv_nodes_size(extdatas) == 1); + TEST_ASSERT(lilv_node_equals(lilv_nodes_get_first(extdatas), extdata)); + lilv_node_free(noextdata); + lilv_node_free(extdata); + lilv_nodes_free(extdatas); + + lilv_nodes_free(thing_names); + lilv_node_free(thing_uri); + lilv_node_free(thing_name2); + lilv_node_free(name_p); + lilv_node_free(control_class); + lilv_node_free(audio_class); + lilv_node_free(in_class); + lilv_node_free(out_class); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_project(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:maintainer [ " + " foaf:name \"David Robillard\" ; " + " foaf:homepage <http://drobilla.net> ; foaf:mbox <mailto:d@drobilla.net> ] ; " + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_name), "David Robillard")); + lilv_node_free(author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_email), "mailto:d@drobilla.net")); + lilv_node_free(author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_homepage), "http://drobilla.net")); + lilv_node_free(author_homepage); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_no_author(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!author_homepage); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_project_no_author(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:name \"Fake project\" ;" + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!author_homepage); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_preset(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:name \"Fake project\" ;" + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + "<http://example.org/preset> a pset:Preset ;" + " lv2:appliesTo :plug ;" + " rdfs:label \"some preset\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* pset_Preset = lilv_new_uri(world, LV2_PRESETS__Preset); + LilvNodes* related = lilv_plugin_get_related(plug, pset_Preset); + + TEST_ASSERT(lilv_nodes_size(related) == 1); + + lilv_node_free(pset_Preset); + lilv_nodes_free(related); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_prototype(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":prot a lv2:PluginBase ; rdfs:seeAlso <plugin.ttl> .\n" + ":plug a lv2:Plugin ; lv2:binary <inst" SHLIB_EXT "> ; lv2:prototype :prot .\n", + BUNDLE_PREFIXES + ":prot a lv2:Plugin ; a lv2:CompressorPlugin ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:name \"Fake project\" ;" + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":plug doap:name \"Instance\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + // Test non-inherited property + LilvNode* name = lilv_plugin_get_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "Instance")); + lilv_node_free(name); + + // Test inherited property + const LilvNode* binary = lilv_plugin_get_library_uri(plug); + TEST_ASSERT(strstr(lilv_node_as_string(binary), "inst" SHLIB_EXT)); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_port(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "doap:homepage <http://example.org/someplug> ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; " + " lv2:name \"store\" ; " + " lv2:name \"dépanneur\"@fr-ca ; lv2:name \"épicerie\"@fr-fr ; " + " lv2:name \"tienda\"@es ; " + " rdfs:comment \"comment\"@en , \"commentaires\"@fr ; " + " lv2:portProperty lv2:integer ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 ; " + " lv2:scalePoint [ rdfs:label \"Sin\"; rdf:value 3 ] ; " + " lv2:scalePoint [ rdfs:label \"Cos\"; rdf:value 4 ] " + "] , [\n" + " a lv2:EventPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"event_in\" ; " + " lv2:name \"Event Input\" ; " + " lv2ev:supportsEvent <http://example.org/event> ;" + " atom:supports <http://example.org/atomEvent> " + "] , [\n" + " a lv2:AudioPort ; a lv2:InputPort ; " + " lv2:index 2 ; lv2:symbol \"audio_in\" ; " + " lv2:name \"Audio Input\" ; " + "] , [\n" + " a lv2:AudioPort ; a lv2:OutputPort ; " + " lv2:index 3 ; lv2:symbol \"audio_out\" ; " + " lv2:name \"Audio Output\" ; " + "] .")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* psym = lilv_new_string(world, "foo"); + const LilvPort* p = lilv_plugin_get_port_by_index(plug, 0); + const LilvPort* p2 = lilv_plugin_get_port_by_symbol(plug, psym); + lilv_node_free(psym); + TEST_ASSERT(p != NULL); + TEST_ASSERT(p2 != NULL); + TEST_ASSERT(p == p2); + + LilvNode* nopsym = lilv_new_string(world, "thisaintnoportfoo"); + const LilvPort* p3 = lilv_plugin_get_port_by_symbol(plug, nopsym); + TEST_ASSERT(p3 == NULL); + lilv_node_free(nopsym); + + // Try getting an invalid property + LilvNode* num = lilv_new_int(world, 1); + LilvNodes* nothing = lilv_port_get_value(plug, p, num); + TEST_ASSERT(!nothing); + lilv_node_free(num); + + LilvNode* audio_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#AudioPort"); + LilvNode* control_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#ControlPort"); + LilvNode* in_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#InputPort"); + LilvNode* out_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#OutputPort"); + + TEST_ASSERT(lilv_nodes_size(lilv_port_get_classes(plug, p)) == 2); + TEST_ASSERT(lilv_plugin_get_num_ports(plug) == 4); + TEST_ASSERT(lilv_port_is_a(plug, p, control_class)); + TEST_ASSERT(lilv_port_is_a(plug, p, in_class)); + TEST_ASSERT(!lilv_port_is_a(plug, p, audio_class)); + + LilvNodes* port_properties = lilv_port_get_properties(plug, p); + TEST_ASSERT(lilv_nodes_size(port_properties) == 1); + lilv_nodes_free(port_properties); + + // Untranslated name (current locale is set to "C" in main) + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_port_get_symbol(plug, p)), "foo")); + LilvNode* name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "store")); + lilv_node_free(name); + + // Exact language match + setenv("LANG", "fr_FR", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "épicerie")); + lilv_node_free(name); + + // Exact language match (with charset suffix) + setenv("LANG", "fr_CA.utf8", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "dépanneur")); + lilv_node_free(name); + + // Partial language match (choose value translated for different country) + setenv("LANG", "fr_BE", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT((!strcmp(lilv_node_as_string(name), "dépanneur")) + ||(!strcmp(lilv_node_as_string(name), "épicerie"))); + lilv_node_free(name); + + // Partial language match (choose country-less language tagged value) + setenv("LANG", "es_MX", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "tienda")); + lilv_node_free(name); + + // No language match (choose untranslated value) + setenv("LANG", "cn", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "store")); + lilv_node_free(name); + + // Invalid language + setenv("LANG", "1!", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "store")); + lilv_node_free(name); + + setenv("LANG", "en_CA.utf-8", 1); + + // Language tagged value with no untranslated values + LilvNode* rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); + LilvNodes* comments = lilv_port_get_value(plug, p, rdfs_comment); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(comments)), + "comment")); + LilvNode* comment = lilv_port_get(plug, p, rdfs_comment); + TEST_ASSERT(!strcmp(lilv_node_as_string(comment), "comment")); + lilv_node_free(comment); + lilv_nodes_free(comments); + + setenv("LANG", "fr", 1); + + comments = lilv_port_get_value(plug, p, rdfs_comment); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(comments)), + "commentaires")); + lilv_nodes_free(comments); + + setenv("LANG", "cn", 1); + + comments = lilv_port_get_value(plug, p, rdfs_comment); + TEST_ASSERT(!comments); + lilv_nodes_free(comments); + + lilv_node_free(rdfs_comment); + + setenv("LANG", "C", 1); // Reset locale + + LilvScalePoints* points = lilv_port_get_scale_points(plug, p); + TEST_ASSERT(lilv_scale_points_size(points) == 2); + + LilvIter* sp_iter = lilv_scale_points_begin(points); + const LilvScalePoint* sp0 = lilv_scale_points_get(points, sp_iter); + TEST_ASSERT(sp0); + sp_iter = lilv_scale_points_next(points, sp_iter); + const LilvScalePoint* sp1 = lilv_scale_points_get(points, sp_iter); + TEST_ASSERT(sp1); + + TEST_ASSERT( + ((!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp0)), "Sin") + && lilv_node_as_float(lilv_scale_point_get_value(sp0)) == 3) + && + (!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp1)), "Cos") + && lilv_node_as_float(lilv_scale_point_get_value(sp1)) == 4)) + || + ((!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp0)), "Cos") + && lilv_node_as_float(lilv_scale_point_get_value(sp0)) == 4) + && + (!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp1)), "Sin") + && lilv_node_as_float(lilv_scale_point_get_value(sp1)) == 3))); + + LilvNode* homepage_p = lilv_new_uri(world, "http://usefulinc.com/ns/doap#homepage"); + LilvNodes* homepages = lilv_plugin_get_value(plug, homepage_p); + TEST_ASSERT(lilv_nodes_size(homepages) == 1); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(homepages)), + "http://example.org/someplug")); + + LilvNode *min, *max, *def; + lilv_port_get_range(plug, p, &def, &min, &max); + TEST_ASSERT(def); + TEST_ASSERT(min); + TEST_ASSERT(max); + TEST_ASSERT(lilv_node_as_float(def) == 0.5); + TEST_ASSERT(lilv_node_as_float(min) == -1.0); + TEST_ASSERT(lilv_node_as_float(max) == 1.0); + + LilvNode* integer_prop = lilv_new_uri(world, "http://lv2plug.in/ns/lv2core#integer"); + LilvNode* toggled_prop = lilv_new_uri(world, "http://lv2plug.in/ns/lv2core#toggled"); + + TEST_ASSERT(lilv_port_has_property(plug, p, integer_prop)); + TEST_ASSERT(!lilv_port_has_property(plug, p, toggled_prop)); + + const LilvPort* ep = lilv_plugin_get_port_by_index(plug, 1); + + LilvNode* event_type = lilv_new_uri(world, "http://example.org/event"); + LilvNode* event_type_2 = lilv_new_uri(world, "http://example.org/otherEvent"); + LilvNode* atom_event = lilv_new_uri(world, "http://example.org/atomEvent"); + TEST_ASSERT(lilv_port_supports_event(plug, ep, event_type)); + TEST_ASSERT(!lilv_port_supports_event(plug, ep, event_type_2)); + TEST_ASSERT(lilv_port_supports_event(plug, ep, atom_event)); + + LilvNode* name_p = lilv_new_uri(world, "http://lv2plug.in/ns/lv2core#name"); + LilvNodes* names = lilv_port_get_value(plug, p, name_p); + TEST_ASSERT(lilv_nodes_size(names) == 1); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(names)), + "store")); + lilv_nodes_free(names); + + LilvNode* true_val = lilv_new_bool(world, true); + LilvNode* false_val = lilv_new_bool(world, false); + + TEST_ASSERT(!lilv_node_equals(true_val, false_val)); + + lilv_world_set_option(world, LILV_OPTION_FILTER_LANG, false_val); + names = lilv_port_get_value(plug, p, name_p); + TEST_ASSERT(lilv_nodes_size(names) == 4); + lilv_nodes_free(names); + lilv_world_set_option(world, LILV_OPTION_FILTER_LANG, true_val); + + lilv_node_free(false_val); + lilv_node_free(true_val); + + names = lilv_port_get_value(plug, ep, name_p); + TEST_ASSERT(lilv_nodes_size(names) == 1); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(names)), + "Event Input")); + + const LilvPort* ap_in = lilv_plugin_get_port_by_index(plug, 2); + + TEST_ASSERT(lilv_port_is_a(plug, ap_in, in_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_in, out_class)); + TEST_ASSERT(lilv_port_is_a(plug, ap_in, audio_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_in, control_class)); + + const LilvPort* ap_out = lilv_plugin_get_port_by_index(plug, 3); + + TEST_ASSERT(lilv_port_is_a(plug, ap_out, out_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_out, in_class)); + TEST_ASSERT(lilv_port_is_a(plug, ap_out, audio_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_out, control_class)); + + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, in_class , NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class , in_class , NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class , out_class, NULL) == 1); + + lilv_nodes_free(names); + lilv_node_free(name_p); + + lilv_node_free(integer_prop); + lilv_node_free(toggled_prop); + lilv_node_free(event_type); + lilv_node_free(event_type_2); + lilv_node_free(atom_event); + + lilv_node_free(min); + lilv_node_free(max); + lilv_node_free(def); + + lilv_node_free(homepage_p); + lilv_nodes_free(homepages); + + lilv_scale_points_free(points); + lilv_node_free(control_class); + lilv_node_free(audio_class); + lilv_node_free(out_class); + lilv_node_free(in_class); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static unsigned +ui_supported(const char* container_type_uri, + const char* ui_type_uri) +{ + return !strcmp(container_type_uri, ui_type_uri); +} + +static int +test_ui(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2UI + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:optionalFeature lv2:hardRTCapable ; " + "lv2:requiredFeature <http://lv2plug.in/ns/ext/event> ; " + "lv2ui:ui :ui , :ui2 , :ui3 , :ui4 ; " + "doap:maintainer [ foaf:name \"David Robillard\" ; " + " foaf:homepage <http://drobilla.net> ; foaf:mbox <mailto:d@drobilla.net> ] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency " + "] .\n" + ":ui a lv2ui:GtkUI ; " + " lv2ui:requiredFeature lv2ui:makeResident ; " + " lv2ui:binary <ui" SHLIB_EXT "> ; " + " lv2ui:optionalFeature lv2ui:ext_presets . " + ":ui2 a lv2ui:GtkUI ; lv2ui:binary <ui2" SHLIB_EXT "> . " + ":ui3 a lv2ui:GtkUI ; lv2ui:binary <ui3" SHLIB_EXT "> . " + ":ui4 a lv2ui:GtkUI ; lv2ui:binary <ui4" SHLIB_EXT "> . ")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvUIs* uis = lilv_plugin_get_uis(plug); + TEST_ASSERT(lilv_uis_size(uis) == 4); + + const LilvUI* ui0 = lilv_uis_get(uis, lilv_uis_begin(uis)); + TEST_ASSERT(ui0); + + LilvNode* ui_uri = lilv_new_uri(world, "http://example.org/ui"); + LilvNode* ui2_uri = lilv_new_uri(world, "http://example.org/ui3"); + LilvNode* ui3_uri = lilv_new_uri(world, "http://example.org/ui4"); + LilvNode* noui_uri = lilv_new_uri(world, "http://example.org/notaui"); + + const LilvUI* ui0_2 = lilv_uis_get_by_uri(uis, ui_uri); + TEST_ASSERT(ui0 == ui0_2); + TEST_ASSERT(lilv_node_equals(lilv_ui_get_uri(ui0_2), ui_uri)); + + const LilvUI* ui2 = lilv_uis_get_by_uri(uis, ui2_uri); + TEST_ASSERT(ui2 != ui0); + + const LilvUI* ui3 = lilv_uis_get_by_uri(uis, ui3_uri); + TEST_ASSERT(ui3 != ui0); + + const LilvUI* noui = lilv_uis_get_by_uri(uis, noui_uri); + TEST_ASSERT(noui == NULL); + + const LilvNodes* classes = lilv_ui_get_classes(ui0); + TEST_ASSERT(lilv_nodes_size(classes) == 1); + + LilvNode* ui_class_uri = lilv_new_uri(world, + "http://lv2plug.in/ns/extensions/ui#GtkUI"); + + LilvNode* unknown_ui_class_uri = lilv_new_uri(world, + "http://example.org/mysteryUI"); + + TEST_ASSERT(lilv_node_equals(lilv_nodes_get_first(classes), ui_class_uri)); + TEST_ASSERT(lilv_ui_is_a(ui0, ui_class_uri)); + + const LilvNode* ui_type = NULL; + TEST_ASSERT(lilv_ui_is_supported(ui0, ui_supported, ui_class_uri, &ui_type)); + TEST_ASSERT(!lilv_ui_is_supported(ui0, ui_supported, unknown_ui_class_uri, &ui_type)); + TEST_ASSERT(lilv_node_equals(ui_type, ui_class_uri)); + + const LilvNode* plug_bundle_uri = lilv_plugin_get_bundle_uri(plug); + const LilvNode* ui_bundle_uri = lilv_ui_get_bundle_uri(ui0); + TEST_ASSERT(lilv_node_equals(plug_bundle_uri, ui_bundle_uri)); + + char* ui_binary_uri_str = (char*)malloc(TEST_PATH_MAX); + snprintf(ui_binary_uri_str, TEST_PATH_MAX, "%s%s", + lilv_node_as_string(plug_bundle_uri), "ui" SHLIB_EXT); + + const LilvNode* ui_binary_uri = lilv_ui_get_binary_uri(ui0); + + LilvNode* expected_uri = lilv_new_uri(world, ui_binary_uri_str); + TEST_ASSERT(lilv_node_equals(expected_uri, ui_binary_uri)); + + free(ui_binary_uri_str); + lilv_node_free(unknown_ui_class_uri); + lilv_node_free(ui_class_uri); + lilv_node_free(ui_uri); + lilv_node_free(ui2_uri); + lilv_node_free(ui3_uri); + lilv_node_free(noui_uri); + lilv_node_free(expected_uri); + lilv_uis_free(uis); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +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 int +test_state(void) +{ + init_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); + 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 }; + + 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"); + TEST_ASSERT(!nostate); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, features); + TEST_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); + TEST_ASSERT(in == 1.0); + TEST_ASSERT(out == 1.0); + + temp_dir = lilv_realpath("temp"); + + const char* file_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, + file_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, + file_dir, copy_dir, link_dir, save_dir, + get_port_value, world, 0, NULL); + + // Ensure they are equal + TEST_ASSERT(lilv_state_equals(state, state2)); + + // Check that we can't delete unsaved state + TEST_ASSERT(lilv_state_delete(world, state)); + + // Check that state has no URI + TEST_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); + TEST_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); + TEST_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 + TEST_ASSERT(lilv_state_equals(state, from_str)); + free(state1_str); + + 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, &map, + file_dir, copy_dir, link_dir, save_dir, + 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, &map, + file_dir, copy_dir, link_dir, save_dir, + get_port_value, world, 0, NULL); + TEST_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"); + TEST_ASSERT(!ret); + + // Load state from directory + LilvState* state5 = lilv_state_new_from_file(world, &map, NULL, + "state/state.lv2/state.ttl"); + + TEST_ASSERT(lilv_state_equals(state, state5)); // Round trip accuracy + TEST_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); + TEST_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"); + TEST_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"); + TEST_ASSERT(!ret); + + // Load default bundle into world and load state from it + 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* test_state_bundle = lilv_new_uri(world, (const char*)state6_uri.buf); + LilvNode* test_state_node = lilv_new_uri(world, state_uri); + lilv_world_load_bundle(world, test_state_bundle); + lilv_world_load_resource(world, test_state_node); + serd_node_free(&state6_uri); + free(state6_path); + + LilvState* state6 = lilv_state_new_from_world(world, &map, test_state_node); + TEST_ASSERT(lilv_state_equals(state, state6)); // Round trip accuracy + + // Check that loaded state has correct URI + TEST_ASSERT(lilv_state_get_uri(state6)); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_state_get_uri(state6)), + state_uri)); + + lilv_world_unload_resource(world, test_state_node); + lilv_world_unload_bundle(world, test_state_bundle); + + LilvState* state6_2 = lilv_state_new_from_world(world, &map, test_state_node); + TEST_ASSERT(!state6_2); // No longer present + lilv_state_free(state6_2); + + lilv_node_free(test_state_bundle); + lilv_node_free(test_state_node); + + unsetenv("LV2_STATE_BUNDLE"); + + // Make directories and test files support + mkdir("temp", 0700); + file_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 }; + const LV2_Feature* ffeatures[] = { &make_path_feature, &map_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) { + fatal_error("Failed to create multiple instances of <%s>\n", + lilv_node_as_uri(state_plugin_uri)); + return 0; + } + lilv_instance_free(instance2); + + // Get instance state state + LilvState* fstate = lilv_state_new_from_instance( + plugin, instance, &map, + file_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, + file_dir, copy_dir, link_dir, "state/fstate2.lv2", + get_port_value, world, 0, ffeatures); + + // Should be identical + TEST_ASSERT(lilv_state_equals(fstate, 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, file_dir, copy_dir, link_dir, "state/fstate3.lv2", + get_port_value, world, 0, ffeatures); + + // Should be different + TEST_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"); + TEST_ASSERT(!ret); + + // Load state from directory + LilvState* fstate4 = lilv_state_new_from_file(world, &map, NULL, + "state/fstate.lv2/fstate.ttl"); + TEST_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, + file_dir, copy_dir, link_dir, "state/fstate5.lv2", + get_port_value, world, 0, ffeatures); + TEST_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"); + TEST_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"); + TEST_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, + file_dir, copy_dir, link_dir, "state/fstate7.lv2", + get_port_value, world, 0, ffeatures); + TEST_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"); + TEST_ASSERT(!ret); + + // Reload it and ensure it's changed + LilvState* fstate72 = lilv_state_new_from_file(world, &map, NULL, + "state/fstate7.lv2/fstate7.ttl"); + TEST_ASSERT(lilv_state_equals(fstate72, fstate7)); + TEST_ASSERT(!lilv_state_equals(fstate6, fstate72)); + + // Delete saved state + lilv_state_delete(world, fstate7); + + 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(state6); + lilv_state_free(fstate); + lilv_state_free(fstate2); + lilv_state_free(fstate3); + lilv_state_free(fstate4); + lilv_state_free(fstate5); + lilv_state_free(fstate6); + lilv_state_free(fstate7); + lilv_state_free(fstate72); + + // 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); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_bad_port_symbol(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "doap:homepage <http://example.org/someplug> ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"0invalid\" ;" + " lv2:name \"Invalid\" ; " + "] .")) { + return 0; + } + + init_uris(); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + + uint32_t n_ports = lilv_plugin_get_num_ports(plug); + TEST_ASSERT(n_ports == 0); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_bad_port_index(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "doap:homepage <http://example.org/someplug> ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index \"notaninteger\" ; lv2:symbol \"invalid\" ;" + " lv2:name \"Invalid\" ; " + "] .")) { + return 0; + } + + init_uris(); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + + uint32_t n_ports = lilv_plugin_get_num_ports(plug); + TEST_ASSERT(n_ports == 0); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_string(void) +{ + char* s = NULL; + + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo/bar")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo/bar/")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo///bar/")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo///bar//")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("foo")), ".")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo")), "/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/")), "/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("//")), "/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b", "/a/")), "b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a", "/b/c/")), "/a")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b/c", "/a/b/d/")), "../c")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b/c", "/a/b/d/e/")), "../../c")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a", "b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a", "/b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a/", "/b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a/", "b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a", NULL)), "/a/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join(NULL, "/b")), "/b")); free(s); + +#ifndef _WIN32 + setenv("LILV_TEST_1", "test", 1); + char* home_foo = lilv_strjoin(getenv("HOME"), "/foo", NULL); + TEST_ASSERT(!strcmp((s = lilv_expand("$LILV_TEST_1")), "test")); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("~")), getenv("HOME"))); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("~foo")), "~foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("~/foo")), home_foo)); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("$NOT_A_VAR")), "$NOT_A_VAR")); free(s); + free(home_foo); + unsetenv("LILV_TEST_1"); +#endif + + return 1; +} + +/*****************************************************************************/ + +static int +test_world(void) +{ + if (!init_world()) { + return 0; + } + + LilvNode* num = lilv_new_int(world, 4); + LilvNode* uri = lilv_new_uri(world, "http://example.org/object"); + + LilvNodes* matches = lilv_world_find_nodes(world, num, NULL, NULL); + TEST_ASSERT(!matches); + + matches = lilv_world_find_nodes(world, NULL, num, NULL); + TEST_ASSERT(!matches); + + matches = lilv_world_find_nodes(world, NULL, uri, NULL); + TEST_ASSERT(!matches); + + lilv_node_free(uri); + lilv_node_free(num); + + lilv_world_unload_bundle(world, NULL); + + return 1; +} + +/*****************************************************************************/ + +static int +test_reload_bundle(void) +{ + // Create a simple plugin bundle + create_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; " + PLUGIN_NAME("First name") " ."); + + if (!init_world()) { + return 0; + } + + init_uris(); + lilv_world_load_specifications(world); + + // Load bundle + LilvNode* bundle_uri = lilv_new_uri(world, bundle_dir_uri); + lilv_world_load_bundle(world, bundle_uri); + + // Check that plugin is present + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + // Check that plugin name is correct + LilvNode* name = lilv_plugin_get_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "First name")); + lilv_node_free(name); + + // Unload bundle from world and delete it + lilv_world_unload_bundle(world, bundle_uri); + delete_bundle(); + + // Create a new version of the same bundle, but with a different name + create_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Second name") " ."); + + // Check that plugin is no longer in the world's plugin list + TEST_ASSERT(lilv_plugins_size(plugins) == 0); + + // Load new bundle + lilv_world_load_bundle(world, bundle_uri); + + // Check that plugin is present again and is the same LilvPlugin + const LilvPlugin* plug2 = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug2); + TEST_ASSERT(plug2 == plug); + + // Check that plugin now has new name + LilvNode* name2 = lilv_plugin_get_name(plug2); + TEST_ASSERT(name2); + TEST_ASSERT(!strcmp(lilv_node_as_string(name2), "Second name")); + lilv_node_free(name2); + + // Load new bundle again (noop) + lilv_world_load_bundle(world, bundle_uri); + + cleanup_uris(); + lilv_node_free(bundle_uri); + lilv_world_free(world); + world = NULL; + + return 1; +} + +/*****************************************************************************/ + +static int +test_replace_version(void) +{ + if (!init_world()) { + return 0; + } + + LilvNode* plug_uri = lilv_new_uri(world, "http://example.org/versioned"); + LilvNode* lv2_minorVersion = lilv_new_uri(world, LV2_CORE__minorVersion); + LilvNode* lv2_microVersion = lilv_new_uri(world, LV2_CORE__microVersion); + LilvNode* minor = NULL; + LilvNode* micro = NULL; + + char* old_bundle_path = lilv_strjoin(LILV_TEST_DIR, "old_version.lv2/", 0); + + // Load plugin from old bundle + LilvNode* old_bundle = lilv_new_file_uri(world, NULL, old_bundle_path); + lilv_world_load_bundle(world, old_bundle); + lilv_world_load_resource(world, plug_uri); + + // Check version + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* old_plug = lilv_plugins_get_by_uri(plugins, plug_uri); + TEST_ASSERT(old_plug); + minor = lilv_world_get(world, plug_uri, lv2_minorVersion, 0); + micro = lilv_world_get(world, plug_uri, lv2_microVersion, 0); + TEST_ASSERT(!strcmp(lilv_node_as_string(minor), "1")); + TEST_ASSERT(!strcmp(lilv_node_as_string(micro), "0")); + lilv_node_free(micro); + lilv_node_free(minor); + + char* new_bundle_path = lilv_strjoin(LILV_TEST_DIR, "new_version.lv2/", 0); + + // Load plugin from new bundle + LilvNode* new_bundle = lilv_new_file_uri(world, NULL, new_bundle_path); + lilv_world_load_bundle(world, new_bundle); + lilv_world_load_resource(world, plug_uri); + + // Check that version in the world model has changed + plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* new_plug = lilv_plugins_get_by_uri(plugins, plug_uri); + TEST_ASSERT(new_plug); + TEST_ASSERT(lilv_node_equals(lilv_plugin_get_bundle_uri(new_plug), new_bundle)); + minor = lilv_world_get(world, plug_uri, lv2_minorVersion, 0); + micro = lilv_world_get(world, plug_uri, lv2_microVersion, 0); + TEST_ASSERT(!strcmp(lilv_node_as_string(minor), "2")); + TEST_ASSERT(!strcmp(lilv_node_as_string(micro), "1")); + lilv_node_free(micro); + lilv_node_free(minor); + + // Try to load the old version again + lilv_world_load_bundle(world, old_bundle); + lilv_world_load_resource(world, plug_uri); + + // Check that version in the world model has not changed + plugins = lilv_world_get_all_plugins(world); + new_plug = lilv_plugins_get_by_uri(plugins, plug_uri); + TEST_ASSERT(new_plug); + minor = lilv_world_get(world, plug_uri, lv2_minorVersion, 0); + micro = lilv_world_get(world, plug_uri, lv2_microVersion, 0); + TEST_ASSERT(!strcmp(lilv_node_as_string(minor), "2")); + TEST_ASSERT(!strcmp(lilv_node_as_string(micro), "1")); + lilv_node_free(micro); + lilv_node_free(minor); + + lilv_node_free(new_bundle); + lilv_node_free(old_bundle); + free(new_bundle_path); + free(old_bundle_path); + lilv_node_free(plug_uri); + lilv_node_free(lv2_minorVersion); + lilv_node_free(lv2_microVersion); + return 1; +} + +/*****************************************************************************/ + +static int +test_get_symbol(void) +{ + if (!start_bundle( + MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:symbol \"plugsym\" ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + "lv2:symbol \"plugsym\" .")) { + return 0; + } + + init_uris(); + + LilvNode* plug_sym = lilv_world_get_symbol(world, plugin_uri_value); + LilvNode* path = lilv_new_uri(world, "http://example.org/foo"); + LilvNode* path_sym = lilv_world_get_symbol(world, path); + LilvNode* query = lilv_new_uri(world, "http://example.org/foo?bar=baz"); + LilvNode* query_sym = lilv_world_get_symbol(world, query); + LilvNode* frag = lilv_new_uri(world, "http://example.org/foo#bar"); + LilvNode* frag_sym = lilv_world_get_symbol(world, frag); + LilvNode* queryfrag = lilv_new_uri(world, "http://example.org/foo?bar=baz#quux"); + LilvNode* queryfrag_sym = lilv_world_get_symbol(world, queryfrag); + LilvNode* nonuri = lilv_new_int(world, 42); + + TEST_ASSERT(lilv_world_get_symbol(world, nonuri) == NULL); + TEST_ASSERT(!strcmp(lilv_node_as_string(plug_sym), "plugsym")); + TEST_ASSERT(!strcmp(lilv_node_as_string(path_sym), "foo")); + TEST_ASSERT(!strcmp(lilv_node_as_string(query_sym), "bar_baz")); + TEST_ASSERT(!strcmp(lilv_node_as_string(frag_sym), "bar")); + TEST_ASSERT(!strcmp(lilv_node_as_string(queryfrag_sym), "quux")); + + lilv_node_free(nonuri); + lilv_node_free(queryfrag_sym); + lilv_node_free(queryfrag); + lilv_node_free(frag_sym); + lilv_node_free(frag); + lilv_node_free(query_sym); + lilv_node_free(query); + lilv_node_free(path_sym); + lilv_node_free(path); + lilv_node_free(plug_sym); + cleanup_uris(); + + return 1; +} + +/*****************************************************************************/ + +/* add tests here */ +static struct TestCase tests[] = { + TEST_CASE(util), + TEST_CASE(value), + TEST_CASE(verify), + TEST_CASE(no_verify), + TEST_CASE(discovery), + TEST_CASE(lv2_path), + TEST_CASE(classes), + TEST_CASE(plugin), + TEST_CASE(project), + TEST_CASE(no_author), + TEST_CASE(project_no_author), + TEST_CASE(preset), + TEST_CASE(prototype), + TEST_CASE(port), + TEST_CASE(ui), + TEST_CASE(bad_port_symbol), + TEST_CASE(bad_port_index), + TEST_CASE(bad_port_index), + TEST_CASE(string), + TEST_CASE(world), + TEST_CASE(state), + TEST_CASE(reload_bundle), + TEST_CASE(replace_version), + TEST_CASE(get_symbol), + { NULL, NULL } +}; + +static void +run_tests(void) +{ + int i; + for (i = 0; tests[i].title; i++) { + printf("*** Test %s\n", tests[i].title); + if (!tests[i].func()) { + printf("\nTest failed\n"); + /* test case that wasn't able to be executed at all counts as 1 test + 1 error */ + error_count++; + test_count++; + } + unload_bundle(); + cleanup(); + } +} + +int +main(int argc, char* argv[]) +{ + if (argc != 1) { + printf("Syntax: %s\n", argv[0]); + return 0; + } + setenv("LANG", "C", 1); + init_tests(); + run_tests(); + cleanup(); + printf("\n*** Test Results: %d tests, %d errors\n\n", test_count, error_count); + return error_count ? 1 : 0; +} |