summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/meson.build156
-rw-r--r--test/pwtest-compat.c55
-rw-r--r--test/pwtest-implementation.h137
-rw-r--r--test/pwtest.c1486
-rw-r--r--test/pwtest.h577
-rw-r--r--test/test-array.c146
-rw-r--r--test/test-client.c70
-rw-r--r--test/test-config.c102
-rw-r--r--test/test-context.c277
-rw-r--r--test/test-example.c265
-rw-r--r--test/test-functional.c58
-rw-r--r--test/test-lib.c69
-rw-r--r--test/test-logger.c656
-rw-r--r--test/test-loop.c463
-rw-r--r--test/test-map.c242
-rw-r--r--test/test-properties.c629
-rw-r--r--test/test-pwtest.c55
-rw-r--r--test/test-spa-buffer.c150
-rw-r--r--test/test-spa-json.c333
-rw-r--r--test/test-spa-log.c213
-rw-r--r--test/test-spa-node.c251
-rw-r--r--test/test-spa-pod.c1706
-rw-r--r--test/test-spa-utils.c1043
-rw-r--r--test/test-support.c88
-rw-r--r--test/test-utils.c253
25 files changed, 9480 insertions, 0 deletions
diff --git a/test/meson.build b/test/meson.build
new file mode 100644
index 0000000..1ce7d00
--- /dev/null
+++ b/test/meson.build
@@ -0,0 +1,156 @@
+pwtest_sources = [
+ 'pwtest.h',
+ 'pwtest-implementation.h',
+ 'pwtest.c',
+ 'pwtest-compat.c',
+]
+
+pwtest_deps = [
+ pipewire_dep,
+ mathlib,
+ dl_lib,
+ cap_lib,
+ epoll_shim_dep
+]
+
+pwtest_c_args = [
+ '-DBUILD_ROOT="@0@"'.format(meson.project_build_root()),
+ '-DSOURCE_ROOT="@0@"'.format(meson.project_source_root()),
+]
+
+pwtest_inc = [
+ pipewire_inc,
+ configinc,
+ includes_inc,
+]
+
+pwtest_lib = static_library(
+ 'pwtest',
+ pwtest_sources,
+ c_args: pwtest_c_args,
+ dependencies: pwtest_deps,
+ include_directories: pwtest_inc,
+)
+
+test('test-pwtest',
+ executable('test-pwtest',
+ 'test-pwtest.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+)
+
+# Compilation only, this is the example file for how pwtest works and most
+# of its tests will fail.
+executable('test-example',
+ 'test-example.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+
+test('test-pw-utils',
+ executable('test-pw-utils',
+ 'test-properties.c',
+ 'test-array.c',
+ 'test-map.c',
+ 'test-utils.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+)
+
+test('test-lib',
+ executable('test-lib',
+ 'test-lib.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+)
+
+test('test-client',
+ executable('test-client',
+ 'test-client.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+)
+
+test('test-loop',
+ executable('test-loop',
+ 'test-loop.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+)
+
+test('test-context',
+ executable('test-context',
+ 'test-context.c',
+ 'test-config.c',
+ include_directories: pwtest_inc,
+ dependencies: [spa_dep, spa_support_dep, spa_dbus_dep],
+ link_with: [pwtest_lib,
+ pipewire_module_protocol_native,
+ pipewire_module_client_node,
+ pipewire_module_client_device,
+ pipewire_module_adapter,
+ pipewire_module_metadata,
+ pipewire_module_session_manager])
+)
+
+test('test-support',
+ executable('test-support',
+ 'test-support.c',
+ 'test-logger.c',
+ include_directories: pwtest_inc,
+ dependencies: [spa_dep, systemd_dep, spa_support_dep, spa_journal_dep],
+ link_with: [pwtest_lib])
+)
+test('test-spa',
+ executable('test-spa',
+ 'test-spa-buffer.c',
+ 'test-spa-json.c',
+ 'test-spa-utils.c',
+ 'test-spa-log.c',
+ 'test-spa-node.c',
+ 'test-spa-pod.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+)
+
+openal_info = find_program('openal-info', required: false)
+if openal_info.found()
+ cdata.set_quoted('OPENAL_INFO_PATH', openal_info.full_path())
+endif
+summary({'openal-info': openal_info.found()}, bool_yn: true, section: 'Functional test programs')
+
+pactl = find_program('pactl', required: false)
+if pactl.found()
+ cdata.set_quoted('PACTL_PATH', pactl.full_path())
+endif
+summary({'pactl': pactl.found()}, bool_yn: true, section: 'Functional test programs')
+
+if default_sm == 'media-session' or default_sm == 'wireplumber'
+ test('test-functional',
+ executable('test-functional',
+ 'test-functional.c',
+ include_directories: pwtest_inc,
+ dependencies: [ spa_dep ],
+ link_with: pwtest_lib)
+ )
+endif
+
+valgrind = find_program('valgrind', required: false)
+summary({'valgrind (test setup)': valgrind.found()}, bool_yn: true, section: 'Optional programs')
+if valgrind.found()
+ valgrind_env = environment({'PIPEWIRE_DEBUG': 'D'})
+ add_test_setup('valgrind',
+ exe_wrapper : [ valgrind,
+ '--leak-check=full',
+ '--gen-suppressions=all',
+ '--error-exitcode=3',
+ ],
+ env : valgrind_env,
+ timeout_multiplier : 3)
+endif
diff --git a/test/pwtest-compat.c b/test/pwtest-compat.c
new file mode 100644
index 0000000..fa89756
--- /dev/null
+++ b/test/pwtest-compat.c
@@ -0,0 +1,55 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#ifndef HAVE_SIGABBREV_NP
+#include <stddef.h>
+#include <signal.h>
+
+/* glibc >= 2.32 */
+static inline const char *sigabbrev_np(int sig)
+{
+#define SIGABBREV(a_) case SIG##a_: return #a_
+ switch(sig) {
+ SIGABBREV(INT);
+ SIGABBREV(ABRT);
+ SIGABBREV(BUS);
+ SIGABBREV(SEGV);
+ SIGABBREV(ALRM);
+ SIGABBREV(CHLD);
+ SIGABBREV(HUP);
+ SIGABBREV(PIPE);
+ SIGABBREV(CONT);
+ SIGABBREV(STOP);
+ SIGABBREV(ILL);
+ SIGABBREV(KILL);
+ SIGABBREV(TERM);
+ }
+#undef SIGABBREV
+
+ return NULL;
+}
+
+#endif /* HAVE_SIGABBREV_NP */
diff --git a/test/pwtest-implementation.h b/test/pwtest-implementation.h
new file mode 100644
index 0000000..1525d20
--- /dev/null
+++ b/test/pwtest-implementation.h
@@ -0,0 +1,137 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PWTEST_IMPLEMENTATION_H
+#define PWTEST_IMPLEMENTATION_H
+
+#include <math.h>
+
+/* This header should never be included on its own, it merely exists to make
+ * the user-visible pwtest.h header more readable */
+
+void
+_pwtest_fail_condition(int exitstatus, const char *file, int line, const char *func,
+ const char *condition, const char *message, ...);
+void
+_pwtest_fail_comparison_int(const char *file, int line, const char *func,
+ const char *operator, int a, int b,
+ const char *astr, const char *bstr);
+void
+_pwtest_fail_comparison_double(const char *file, int line, const char *func,
+ const char *operator, double a, double b,
+ const char *astr, const char *bstr);
+void
+_pwtest_fail_comparison_ptr(const char *file, int line, const char *func,
+ const char *comparison);
+
+void
+_pwtest_fail_comparison_str(const char *file, int line, const char *func,
+ const char *comparison, const char *a, const char *b);
+
+void
+_pwtest_fail_comparison_bool(const char *file, int line, const char *func,
+ const char *operator, bool a, bool b,
+ const char *astr, const char *bstr);
+
+void
+_pwtest_fail_errno(const char *file, int line, const char *func,
+ int expected, int err_no);
+
+#define pwtest_errno_check(r_, errno_) \
+ do { \
+ int _r = r_; \
+ int _e = errno_; \
+ if (_e == 0) { \
+ if (_r == -1) \
+ _pwtest_fail_errno(__FILE__, __LINE__, __func__, _e, errno); \
+ } else { \
+ if (_r != -1 || errno != _e) \
+ _pwtest_fail_errno(__FILE__, __LINE__, __func__, _e, errno); \
+ } \
+ } while(0)
+
+#define pwtest_neg_errno_check(r_, errno_) \
+ do { \
+ int _r = r_; \
+ int _e = errno_; \
+ if (_e == 0) { \
+ if (_r < 0) \
+ _pwtest_fail_errno(__FILE__, __LINE__, __func__, _e, -_r); \
+ } else { \
+ if (_r >= 0 || _r != _e) \
+ _pwtest_fail_errno(__FILE__, __LINE__, __func__, -_e, _r >= 0 ? 0 : -_r); \
+ } \
+ } while(0)
+
+#define pwtest_comparison_bool_(a_, op_, b_) \
+ do { \
+ bool _a = !!(a_); \
+ bool _b = !!(b_); \
+ if (!(_a op_ _b)) \
+ _pwtest_fail_comparison_bool(__FILE__, __LINE__, __func__,\
+ #op_, _a, _b, #a_, #b_); \
+ } while(0)
+
+#define pwtest_comparison_int_(a_, op_, b_) \
+ do { \
+ __typeof__(a_) _a = a_; \
+ __typeof__(b_) _b = b_; \
+ if (trunc(_a) != _a || trunc(_b) != _b) \
+ pwtest_error_with_msg("pwtest_int_* used for non-integer value\n"); \
+ if (!((_a) op_ (_b))) \
+ _pwtest_fail_comparison_int(__FILE__, __LINE__, __func__,\
+ #op_, _a, _b, #a_, #b_); \
+ } while(0)
+
+#define pwtest_comparison_ptr_(a_, op_, b_) \
+ do { \
+ __typeof__(a_) _a = a_; \
+ __typeof__(b_) _b = b_; \
+ if (!((_a) op_ (_b))) \
+ _pwtest_fail_comparison_ptr(__FILE__, __LINE__, __func__,\
+ #a_ " " #op_ " " #b_); \
+ } while(0)
+
+#define pwtest_comparison_double_(a_, op_, b_) \
+ do { \
+ const double EPSILON = 1.0/256; \
+ __typeof__(a_) _a = a_; \
+ __typeof__(b_) _b = b_; \
+ if (!((_a) op_ (_b)) && fabs((_a) - (_b)) > EPSILON) \
+ _pwtest_fail_comparison_double(__FILE__, __LINE__, __func__,\
+ #op_, _a, _b, #a_, #b_); \
+ } while(0)
+
+void _pwtest_add(struct pwtest_context *ctx,
+ struct pwtest_suite *suite,
+ const char *funcname, const void *func,
+ ...) SPA_SENTINEL;
+
+struct pwtest_suite_decl {
+ const char *name;
+ enum pwtest_result (*setup)(struct pwtest_context *, struct pwtest_suite *);
+};
+
+
+#endif /* PWTEST_IMPLEMENTATION_H */
diff --git a/test/pwtest.c b/test/pwtest.c
new file mode 100644
index 0000000..968805b
--- /dev/null
+++ b/test/pwtest.c
@@ -0,0 +1,1486 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <ftw.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#ifdef HAVE_PIDFD_OPEN
+#include <sys/syscall.h>
+#endif
+#ifdef HAVE_LIBCAP
+#include <sys/capability.h>
+#endif
+#include <sys/epoll.h>
+#include <sys/ptrace.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <sys/wait.h>
+#include <time.h>
+
+#include <valgrind/valgrind.h>
+
+#include "spa/utils/ansi.h"
+#include "spa/utils/string.h"
+#include "spa/utils/defs.h"
+#include "spa/utils/list.h"
+#include "spa/support/plugin.h"
+
+#include "pipewire/array.h"
+#include "pipewire/utils.h"
+#include "pipewire/properties.h"
+
+#include "pwtest.h"
+
+#include "pwtest-compat.c"
+
+#define pwtest_log(...) dprintf(testlog_fd, __VA_ARGS__)
+#define pwtest_vlog(format_, args_) vdprintf(testlog_fd, format_, args_)
+
+static bool verbose = false;
+
+/** the global context object */
+static struct pwtest_context *ctx;
+
+/**
+ * The various pwtest_assert() etc. functions write to this fd, collected
+ * separately in the log.
+ */
+static int testlog_fd = STDOUT_FILENO;
+
+enum pwtest_logfds {
+ FD_STDOUT,
+ FD_STDERR,
+ FD_LOG,
+ FD_DAEMON,
+ _FD_LAST,
+};
+
+struct pwtest_test {
+ struct spa_list link;
+ const char *name;
+ enum pwtest_result (*func)(struct pwtest_test *test);
+
+ int iteration;
+
+ /* env vars changed by pwtest. These will be restored after the test
+ * run to get close to the original environment. */
+ struct pw_properties *env;
+
+ /* Arguments during pwtest_add() */
+ struct {
+ int signal;
+ struct {
+ int min, max;
+ } range;
+ struct pw_properties *props;
+ struct pw_properties *env;
+ bool pw_daemon;
+ } args;
+
+ /* Results */
+ enum pwtest_result result;
+ int sig_or_errno;
+ struct pw_array logs[_FD_LAST];
+};
+
+struct pwtest_suite {
+ struct spa_list link;
+ const struct pwtest_suite_decl *decl;
+ enum pwtest_result result;
+
+ struct spa_list tests;
+};
+
+struct pwtest_context {
+ struct spa_list suites;
+ unsigned int timeout;
+ bool no_fork;
+ bool terminate;
+ struct spa_list cleanup_pids;
+
+ const char *test_filter;
+ bool has_iteration_filter;
+ int iteration_filter;
+ char *xdg_dir;
+};
+
+struct cleanup_pid {
+ struct spa_list link;
+ pid_t pid;
+};
+
+struct pwtest_context *pwtest_get_context(struct pwtest_test *t)
+{
+ return ctx;
+}
+
+int pwtest_get_iteration(struct pwtest_test *t)
+{
+ return t->iteration;
+}
+
+struct pw_properties *pwtest_get_props(struct pwtest_test *t)
+{
+ return t->args.props;
+}
+
+static void replace_env(struct pwtest_test *t, const char *prop, const char *value)
+{
+ const char *oldval = getenv(prop);
+
+ pw_properties_set(t->env, prop, oldval ? oldval : "pwtest-null");
+ if (value)
+ setenv(prop, value, 1);
+ else
+ unsetenv(prop);
+}
+
+static void restore_env(struct pwtest_test *t)
+{
+ const char *env;
+ void *state = NULL;
+
+ while ((env = pw_properties_iterate(t->env, &state))) {
+ const char *value = pw_properties_get(t->env, env);
+ if (spa_streq(value, "pwtest-null"))
+ unsetenv(env);
+ else
+ setenv(env, value, 1);
+ }
+}
+
+static int add_cleanup_pid(struct pwtest_context *ctx, pid_t pid)
+{
+ struct cleanup_pid *cpid;
+
+ if (pid == 0)
+ return -EINVAL;
+
+ cpid = calloc(1, sizeof(struct cleanup_pid));
+ if (cpid == NULL)
+ return -errno;
+
+ cpid->pid = pid;
+ spa_list_append(&ctx->cleanup_pids, &cpid->link);
+
+ return 0;
+}
+
+static void remove_cleanup_pid(struct pwtest_context *ctx, pid_t pid)
+{
+ struct cleanup_pid *cpid, *t;
+
+ spa_list_for_each_safe(cpid, t, &ctx->cleanup_pids, link) {
+ if (cpid->pid == pid) {
+ spa_list_remove(&cpid->link);
+ free(cpid);
+ }
+ }
+}
+
+static void terminate_cleanup_pids(struct pwtest_context *ctx)
+{
+ struct cleanup_pid *cpid;
+ spa_list_for_each(cpid, &ctx->cleanup_pids, link) {
+ /* Don't free here, to be signal-safe */
+ if (cpid->pid != 0) {
+ kill(cpid->pid, SIGTERM);
+ cpid->pid = 0;
+ }
+ }
+}
+
+static void free_cleanup_pids(struct pwtest_context *ctx)
+{
+ struct cleanup_pid *cpid;
+ spa_list_consume(cpid, &ctx->cleanup_pids, link) {
+ spa_list_remove(&cpid->link);
+ free(cpid);
+ }
+}
+
+static void pwtest_backtrace(pid_t p)
+{
+#ifdef HAVE_GSTACK
+ char pid[11];
+ pid_t parent, child;
+ int status;
+
+ if (RUNNING_ON_VALGRIND)
+ return;
+
+ parent = p == 0 ? getpid() : p;
+ child = fork();
+ if (child == 0) {
+ assert(testlog_fd > 0);
+ /* gstack writes the backtrace to stdout, we re-route to our
+ * custom fd */
+ dup2(testlog_fd, STDOUT_FILENO);
+
+ spa_scnprintf(pid, sizeof(pid), "%d", (uint32_t)parent);
+ execlp("gstack", "gstack", pid, NULL);
+ exit(errno);
+ }
+
+ /* parent */
+ waitpid(child, &status, 0);
+#endif
+}
+
+SPA_PRINTF_FUNC(6, 7)
+SPA_NORETURN
+void _pwtest_fail_condition(int exitstatus,
+ const char *file, int line, const char *func,
+ const char *condition, const char *message, ...)
+{
+ pwtest_log("FAILED: %s\n", condition);
+
+ if (message) {
+ va_list args;
+ va_start(args, message);
+ pwtest_vlog(message, args);
+ va_end(args);
+ pwtest_log("\n");
+ }
+
+ pwtest_log("in %s() (%s:%d)\n", func, file, line);
+ pwtest_backtrace(0);
+ exit(exitstatus);
+}
+
+SPA_NORETURN
+void _pwtest_fail_comparison_bool(const char *file, int line, const char *func,
+ const char *operator, bool a, bool b,
+ const char *astr, const char *bstr)
+{
+ pwtest_log("FAILED COMPARISON: %s %s %s\n", astr, operator, bstr);
+ pwtest_log("Resolved to: %s %s %s\n", a ? "true" : "false", operator, b ? "true" : "false");
+ pwtest_log("in %s() (%s:%d)\n", func, file, line);
+ pwtest_backtrace(0);
+ exit(PWTEST_FAIL);
+}
+
+SPA_NORETURN
+void _pwtest_fail_errno(const char *file, int line, const char *func,
+ int expected, int err_no)
+{
+ pwtest_log("FAILED ERRNO CHECK: expected %d (%s), got %d (%s)\n",
+ expected, strerror(expected), err_no, strerror(err_no));
+ pwtest_log("in %s() (%s:%d)\n", func, file, line);
+ pwtest_backtrace(0);
+ exit(PWTEST_FAIL);
+}
+
+
+SPA_NORETURN
+void _pwtest_fail_comparison_int(const char *file, int line, const char *func,
+ const char *operator, int a, int b,
+ const char *astr, const char *bstr)
+{
+ pwtest_log("FAILED COMPARISON: %s %s %s\n", astr, operator, bstr);
+ pwtest_log("Resolved to: %d %s %d\n", a, operator, b);
+ pwtest_log("in %s() (%s:%d)\n", func, file, line);
+ pwtest_backtrace(0);
+ exit(PWTEST_FAIL);
+}
+
+SPA_NORETURN
+void _pwtest_fail_comparison_double(const char *file, int line, const char *func,
+ const char *operator, double a, double b,
+ const char *astr, const char *bstr)
+{
+ pwtest_log("FAILED COMPARISON: %s %s %s\n", astr, operator, bstr);
+ pwtest_log("Resolved to: %.3f %s %.3f\n", a, operator, b);
+ pwtest_log("in %s() (%s:%d)\n", func, file, line);
+ pwtest_backtrace(0);
+ exit(PWTEST_FAIL);
+}
+
+SPA_NORETURN
+void _pwtest_fail_comparison_ptr(const char *file, int line, const char *func,
+ const char *comparison)
+{
+ pwtest_log("FAILED COMPARISON: %s\n", comparison);
+ pwtest_log("in %s() (%s:%d)\n", func, file, line);
+ pwtest_backtrace(0);
+ exit(PWTEST_FAIL);
+}
+
+SPA_NORETURN
+void _pwtest_fail_comparison_str(const char *file, int line, const char *func,
+ const char *comparison, const char *a, const char *b)
+{
+ pwtest_log("FAILED COMPARISON: %s, expanded (\"%s\" vs \"%s\")\n", comparison, a, b);
+ pwtest_log("in %s() (%s:%d)\n", func, file, line);
+ pwtest_backtrace(0);
+ exit(PWTEST_FAIL);
+}
+
+struct pwtest_spa_plugin *
+pwtest_spa_plugin_new(void)
+{
+ return calloc(1, sizeof(struct pwtest_spa_plugin));
+}
+
+void
+pwtest_spa_plugin_destroy(struct pwtest_spa_plugin *plugin)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(plugin->handles, hnd) {
+ if (*hnd) {
+ spa_handle_clear(*hnd);
+ free(*hnd);
+ }
+ }
+ SPA_FOR_EACH_ELEMENT_VAR(plugin->dlls, dll) {
+ if (*dll)
+ dlclose(*dll);
+ }
+ free(plugin);
+}
+
+int
+pwtest_spa_plugin_try_load_interface(struct pwtest_spa_plugin *plugin,
+ void **iface_return,
+ const char *libname,
+ const char *factory_name,
+ const char *interface_name,
+ const struct spa_dict *info)
+{
+ char *libdir = getenv("SPA_PLUGIN_DIR");
+ char path[PATH_MAX];
+ void *hnd, *iface;
+ spa_handle_factory_enum_func_t enum_func;
+ const struct spa_handle_factory *factory;
+ uint32_t index = 0;
+ int r;
+ bool found = false;
+ struct spa_handle *handle;
+
+ spa_assert_se(libdir != NULL);
+ spa_scnprintf(path, sizeof(path), "%s/%s.so", libdir, libname);
+
+ hnd = dlopen(path, RTLD_NOW);
+ if (hnd == NULL)
+ return -ENOENT;
+
+ enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME);
+ pwtest_ptr_notnull(enum_func);
+
+ while ((r = enum_func(&factory, &index)) > 0) {
+ pwtest_int_ge(factory->version, 1U);
+ if (spa_streq(factory->name, factory_name)) {
+ found = true;
+ break;
+ }
+ }
+ pwtest_neg_errno_ok(r);
+ if (!found) {
+ dlclose(hnd);
+ return -EINVAL;
+ }
+
+ handle = calloc(1, spa_handle_factory_get_size(factory, info));
+ pwtest_ptr_notnull(handle);
+
+ r = spa_handle_factory_init(factory, handle, info, plugin->support, plugin->nsupport);
+ pwtest_neg_errno_ok(r);
+ if ((r = spa_handle_get_interface(handle, interface_name, &iface)) != 0) {
+ spa_handle_clear(handle);
+ free(handle);
+ dlclose(hnd);
+ return -ENOSYS;
+ }
+
+ plugin->dlls[plugin->ndlls++] = hnd;
+ plugin->handles[plugin->nhandles++] = handle;
+ plugin->support[plugin->nsupport++] = SPA_SUPPORT_INIT(interface_name, iface);
+
+ *iface_return = iface;
+ return 0;
+}
+
+void *
+pwtest_spa_plugin_load_interface(struct pwtest_spa_plugin *plugin,
+ const char *libname,
+ const char *factory_name,
+ const char *interface_name,
+ const struct spa_dict *info)
+{
+ void *iface;
+ int r = pwtest_spa_plugin_try_load_interface(plugin, &iface, libname,
+ factory_name, interface_name, info);
+ pwtest_neg_errno_ok(r);
+ return iface;
+}
+
+void
+pwtest_mkstemp(char path[PATH_MAX])
+{
+ const char *tmpdir = getenv("TMPDIR");
+ int r;
+
+ if (tmpdir == NULL)
+ pwtest_error_with_msg("tmpdir is unset");
+
+ spa_scnprintf(path, PATH_MAX, "%s/%s", tmpdir, "tmp.XXXXXX");
+ r = mkstemp(path);
+ if (r == -1)
+ pwtest_error_with_msg("Unable to create temporary file: %s", strerror(errno));
+}
+
+int
+pwtest_spawn(const char *file, char *const argv[])
+{
+ int r;
+ int status = -1;
+ pid_t pid;
+ const int fail_code = 121;
+
+ pid = fork();
+ if (pid == 0) {
+ /* child process */
+ execvp(file, (char **)argv);
+ exit(fail_code);
+ } else if (pid < 0)
+ pwtest_error_with_msg("Unable to fork: %s", strerror(errno));
+
+ add_cleanup_pid(ctx, pid);
+ r = waitpid(pid, &status, 0);
+ remove_cleanup_pid(ctx, pid);
+ if (r <= 0)
+ pwtest_error_with_msg("waitpid failed: %s", strerror(errno));
+
+ if (WEXITSTATUS(status) == fail_code)
+ pwtest_error_with_msg("exec %s failed", file);
+
+ return status;
+}
+
+void _pwtest_add(struct pwtest_context *ctx, struct pwtest_suite *suite,
+ const char *funcname, const void *func, ...)
+{
+ struct pwtest_test *t;
+ va_list args;
+
+ if (ctx->test_filter != NULL && fnmatch(ctx->test_filter, funcname, 0) != 0)
+ return;
+
+ t = calloc(1, sizeof *t);
+ t->result = PWTEST_SYSTEM_ERROR;
+ t->name = funcname;
+ t->func = func;
+ t->args.range.min = 0;
+ t->args.range.max = 1;
+ t->args.env = pw_properties_new("PWTEST", "1", NULL);
+ t->env = pw_properties_new(NULL, NULL);
+ for (size_t i = 0; i < SPA_N_ELEMENTS(t->logs); i++)
+ pw_array_init(&t->logs[i], 1024);
+
+ va_start(args, func);
+ while (true) {
+ const char *key, *value;
+
+ enum pwtest_arg arg = va_arg(args, enum pwtest_arg);
+ if (!arg)
+ break;
+ switch (arg) {
+ case PWTEST_NOARG:
+ break;
+ case PWTEST_ARG_SIGNAL:
+ if (RUNNING_ON_VALGRIND)
+ t->result = PWTEST_SKIP;
+ t->args.signal = va_arg(args, int);
+ break;
+ case PWTEST_ARG_RANGE:
+ t->args.range.min = va_arg(args, int);
+ t->args.range.max = va_arg(args, int);
+ break;
+ case PWTEST_ARG_PROP:
+ key = va_arg(args, const char *);
+ value = va_arg(args, const char *);
+ if (t->args.props == NULL) {
+ t->args.props = pw_properties_new(key, value, NULL);
+ } else {
+ pw_properties_set(t->args.props, key, value);
+ }
+ break;
+ case PWTEST_ARG_ENV:
+ key = va_arg(args, const char *);
+ value = va_arg(args, const char *);
+ pw_properties_set(t->args.env, key, value);
+ break;
+ case PWTEST_ARG_DAEMON:
+ if (RUNNING_ON_VALGRIND)
+ t->result = PWTEST_SKIP;
+ t->args.pw_daemon = true;
+ break;
+ }
+ }
+ va_end(args);
+
+ spa_list_append(&suite->tests, &t->link);
+}
+
+extern const struct pwtest_suite_decl __start_pwtest_suite_section;
+extern const struct pwtest_suite_decl __stop_pwtest_suite_section;
+
+static void add_suite(struct pwtest_context *ctx,
+ const struct pwtest_suite_decl *decl)
+{
+ struct pwtest_suite *c = calloc(1, sizeof *c);
+
+ c->decl = decl;
+ spa_list_init(&c->tests);
+ spa_list_append(&ctx->suites, &c->link);
+}
+
+static void free_test(struct pwtest_test *t)
+{
+ spa_list_remove(&t->link);
+ for (size_t i = 0; i < SPA_N_ELEMENTS(t->logs); i++)
+ pw_array_clear(&t->logs[i]);
+ pw_properties_free(t->args.props);
+ pw_properties_free(t->args.env);
+ pw_properties_free(t->env);
+ free(t);
+}
+
+static void free_suite(struct pwtest_suite *c)
+{
+ struct pwtest_test *t, *tmp;
+
+ spa_list_for_each_safe(t, tmp, &c->tests, link)
+ free_test(t);
+
+ spa_list_remove(&c->link);
+ free(c);
+}
+
+static void find_suites(struct pwtest_context *ctx, const char *suite_filter)
+{
+ const struct pwtest_suite_decl *c;
+
+ for (c = &__start_pwtest_suite_section; c < &__stop_pwtest_suite_section; c++) {
+ if (suite_filter == NULL || fnmatch(suite_filter, c->name, 0) == 0)
+ add_suite(ctx, c);
+ }
+}
+
+static void add_tests(struct pwtest_context *ctx)
+{
+ struct pwtest_suite *c;
+
+ spa_list_for_each(c, &ctx->suites, link) {
+ c->result = c->decl->setup(ctx, c);
+ spa_assert_se(c->result >= PWTEST_PASS && c->result <= PWTEST_SYSTEM_ERROR);
+ }
+}
+
+static int remove_file(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
+{
+ char *tmpdir = getenv("TMPDIR");
+ int r;
+
+ /* Safety check: bail out if somehow we left TMPDIR */
+ spa_assert_se(tmpdir != NULL);
+ spa_assert_se(spa_strneq(fpath, tmpdir, strlen(tmpdir)));
+
+ r = remove(fpath);
+ if (r)
+ fprintf(stderr, "Failed to remove %s: %m", fpath);
+
+ return r;
+}
+
+static void remove_xdg_runtime_dir(const char *xdg_dir)
+{
+ char *tmpdir = getenv("TMPDIR");
+ char path[PATH_MAX];
+ int r;
+
+ if (xdg_dir == NULL)
+ return;
+
+ /* Safety checks, we really don't want to recursively remove a
+ * random directory due to a bug */
+ spa_assert_se(tmpdir != NULL);
+ spa_assert_se(spa_strneq(xdg_dir, tmpdir, strlen(tmpdir)));
+ r = spa_scnprintf(path, sizeof(path), "%s/pwtest.dir", xdg_dir);
+ spa_assert_se((size_t)r == strlen(xdg_dir) + 11);
+ if (access(path, F_OK) != 0) {
+ fprintf(stderr, "XDG_RUNTIME_DIR changed, cannot clean up\n");
+ return;
+ }
+
+ nftw(xdg_dir, remove_file, 16, FTW_DEPTH | FTW_PHYS);
+}
+
+static void cleanup(struct pwtest_context *ctx)
+{
+ struct pwtest_suite *c, *tmp;
+
+ terminate_cleanup_pids(ctx);
+ free_cleanup_pids(ctx);
+
+ spa_list_for_each_safe(c, tmp, &ctx->suites, link) {
+ free_suite(c);
+ }
+
+ remove_xdg_runtime_dir(ctx->xdg_dir);
+ free(ctx->xdg_dir);
+}
+
+static void sighandler(int signal)
+{
+ struct sigaction act;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = SIG_DFL;
+ sigaction(signal, &act, NULL);
+
+ pwtest_backtrace(0);
+ terminate_cleanup_pids(ctx);
+ raise(signal);
+}
+
+static inline void log_append(struct pw_array *buffer, int fd)
+{
+ int r = 0;
+ const int sz = 65536;
+
+ while (true) {
+ r = pw_array_ensure_size(buffer, sz);
+ spa_assert_se(r == 0);
+ r = read(fd, pw_array_end(buffer), sz);
+ if (r <= 0)
+ break;
+ /* We've read directly into the array's buffer, we just add
+ * now to update the array */
+ pw_array_add(buffer, r);
+ }
+}
+
+static bool collect_child(struct pwtest_test *t, pid_t pid)
+{
+ int r;
+ int status;
+
+ r = waitpid(pid, &status, WNOHANG);
+ if (r <= 0)
+ return false;
+
+ if (WIFEXITED(status)) {
+ t->result = WEXITSTATUS(status);
+ switch (t->result) {
+ case PWTEST_PASS:
+ case PWTEST_SKIP:
+ case PWTEST_FAIL:
+ case PWTEST_TIMEOUT:
+ case PWTEST_SYSTEM_ERROR:
+ break;
+ default:
+ spa_assert_se(!"Invalid test result");
+ break;
+ }
+ return true;
+ }
+
+ if (WIFSIGNALED(status)) {
+ t->sig_or_errno = WTERMSIG(status);
+ t->result = (t->sig_or_errno == t->args.signal) ? PWTEST_PASS : PWTEST_FAIL;
+ } else {
+ t->result = PWTEST_FAIL;
+ }
+ return true;
+}
+
+static pid_t start_pwdaemon(struct pwtest_test *t, int stderr_fd, int log_fd)
+{
+ static unsigned int count;
+ const char *daemon = BUILD_ROOT "/src/daemon/pipewire-uninstalled";
+ pid_t pid;
+ char pw_remote[64];
+ int status;
+ int r;
+
+ spa_scnprintf(pw_remote, sizeof(pw_remote), "pwtest-pw-%u\n", count++);
+ replace_env(t, "PIPEWIRE_REMOTE", pw_remote);
+
+ pid = fork();
+ if (pid == 0) {
+ /* child */
+ setpgid(0, 0);
+
+ setenv("PIPEWIRE_CORE", pw_remote, 1);
+ setenv("PIPEWIRE_DEBUG", "4", 0);
+ setenv("WIREPLUMBER_DEBUG", "4", 0);
+
+ r = dup2(stderr_fd, STDERR_FILENO);
+ spa_assert_se(r != -1);
+ r = dup2(stderr_fd, STDOUT_FILENO);
+ spa_assert_se(r != -1);
+
+ execl(daemon, daemon, (char*)NULL);
+ return -errno;
+
+ } else if (pid < 0) {
+ return pid;
+ }
+
+ add_cleanup_pid(ctx, -pid);
+
+ /* parent */
+ sleep(1); /* FIXME how to wait for pw to be ready? */
+ if (waitpid(pid, &status, WNOHANG) > 0) {
+ if (WIFEXITED(status)) {
+ dprintf(log_fd, "pipewire daemon exited with %d before test started\n", WEXITSTATUS(status));
+ return -ESRCH;
+ } else if (WIFSIGNALED(status)) {
+ dprintf(log_fd, "pipewire daemon terminated with %d (SIG%s) before test started\n", WTERMSIG(status),
+ sigabbrev_np(WTERMSIG(status)));
+ return -EHOSTDOWN;
+ }
+ }
+
+ return pid;
+}
+
+static void make_xdg_runtime_test_dir(char dir[PATH_MAX], const char *prefix)
+{
+ static size_t counter;
+ int r;
+
+ r = spa_scnprintf(dir, PATH_MAX, "%s/%zd", prefix, counter++);
+ spa_assert_se(r >= (int)(strlen(prefix) + 2));
+ r = mkdir(dir, 0777);
+ if (r == -1) {
+ fprintf(stderr, "Failed to make XDG_RUNTIME_DIR %s (%m)\n", dir);
+ spa_assert_se(r != -1);
+ }
+}
+
+static void set_test_env(struct pwtest_context *ctx, struct pwtest_test *t)
+{
+ char xdg_runtime_dir[PATH_MAX];
+
+ make_xdg_runtime_test_dir(xdg_runtime_dir, ctx->xdg_dir);
+ replace_env(t, "XDG_RUNTIME_DIR", xdg_runtime_dir);
+ replace_env(t, "TMPDIR", xdg_runtime_dir);
+
+ replace_env(t, "SPA_PLUGIN_DIR", BUILD_ROOT "/spa/plugins");
+ replace_env(t, "SPA_DATA_DIR", SOURCE_ROOT "/spa/plugins");
+ replace_env(t, "PIPEWIRE_CONFIG_DIR", BUILD_ROOT "/src/daemon");
+ replace_env(t, "PIPEWIRE_MODULE_DIR", BUILD_ROOT "/src/modules");
+ replace_env(t, "ACP_PATHS_DIR", SOURCE_ROOT "/spa/plugins/alsa/mixer/paths");
+ replace_env(t, "ACP_PROFILES_DIR", SOURCE_ROOT "/spa/plugins/alsa/mixer/profile-sets");
+ replace_env(t, "PIPEWIRE_LOG_SYSTEMD", "false");
+}
+
+static void close_pipes(int fds[_FD_LAST])
+{
+ for (int i = 0; i < _FD_LAST; i++) {
+ if (fds[i] >= 0)
+ close(fds[i]);
+ fds[i] = -1;
+ }
+}
+
+static int init_pipes(int read_fds[_FD_LAST], int write_fds[_FD_LAST])
+{
+ int r;
+ int i;
+ int pipe_max_size = 4194304;
+
+ for (i = 0; i < _FD_LAST; i++) {
+ read_fds[i] = -1;
+ write_fds[i] = -1;
+ }
+
+#ifdef __linux__
+ {
+ FILE *f;
+ f = fopen("/proc/sys/fs/pipe-max-size", "re");
+ if (f) {
+ if (fscanf(f, "%d", &r) == 1)
+ pipe_max_size = SPA_MIN(r, pipe_max_size);
+ fclose(f);
+ }
+ }
+#endif
+
+ for (i = 0; i < _FD_LAST; i++) {
+ int pipe[2];
+
+ r = pipe2(pipe, O_CLOEXEC | O_NONBLOCK);
+ if (r < 0)
+ goto error;
+ read_fds[i] = pipe[0];
+ write_fds[i] = pipe[1];
+#ifdef __linux__
+ /* Max pipe buffers, to avoid scrambling if reading lags.
+ * Can't use blocking write fds, since reading too slow
+ * then affects execution.
+ */
+ fcntl(write_fds[i], F_SETPIPE_SZ, pipe_max_size);
+#endif
+ }
+
+ return 0;
+error:
+ r = -errno;
+ close_pipes(read_fds);
+ close_pipes(write_fds);
+ return r;
+}
+
+static void start_test_nofork(struct pwtest_test *t)
+{
+ const char *env;
+ void *state = NULL;
+
+ /* This is going to mess with future tests */
+ while ((env = pw_properties_iterate(t->args.env, &state)))
+ replace_env(t, env, pw_properties_get(t->args.env, env));
+
+ /* The actual test function */
+ t->result = t->func(t);
+}
+
+static int start_test_forked(struct pwtest_test *t, int read_fds[_FD_LAST], int write_fds[_FD_LAST])
+{
+ pid_t pid;
+ enum pwtest_result result;
+ struct sigaction act;
+ const char *env;
+ void *state = NULL;
+ int r;
+
+ pid = fork();
+ if (pid < 0) {
+ r = -errno;
+ close_pipes(read_fds);
+ close_pipes(write_fds);
+ return r;
+ }
+
+ if (pid > 0) { /* parent */
+ close_pipes(write_fds);
+ return pid;
+ }
+
+ /* child */
+
+ close_pipes(read_fds);
+
+ /* Reset cleanup pid list */
+ free_cleanup_pids(ctx);
+
+ /* Catch any crashers so we can insert a backtrace */
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sighandler;
+ sigaction(SIGSEGV, &act, NULL);
+ sigaction(SIGBUS, &act, NULL);
+ sigaction(SIGSEGV, &act, NULL);
+ sigaction(SIGABRT, &act, NULL);
+ /* SIGALARM is used for our timeout */
+ sigaction(SIGALRM, &act, NULL);
+
+ r = dup2(write_fds[FD_STDERR], STDERR_FILENO);
+ spa_assert_se(r != -1);
+ setlinebuf(stderr);
+ r = dup2(write_fds[FD_STDOUT], STDOUT_FILENO);
+ spa_assert_se(r != -1);
+ setlinebuf(stdout);
+
+ /* For convenience in the tests, let this be a global variable. */
+ testlog_fd = write_fds[FD_LOG];
+
+ while ((env = pw_properties_iterate(t->args.env, &state)))
+ setenv(env, pw_properties_get(t->args.env, env), 1);
+
+ /* The actual test function */
+ result = t->func(t);
+
+ for (int i = 0; i < _FD_LAST; i++)
+ fsync(write_fds[i]);
+
+ exit(result);
+}
+
+static int monitor_test_forked(struct pwtest_test *t, pid_t pid, int read_fds[_FD_LAST])
+{
+ int pidfd = -1, timerfd = -1, epollfd = -1;
+ struct epoll_event ev[10];
+ size_t nevents = 0;
+ int r;
+
+#ifdef HAVE_PIDFD_OPEN
+ pidfd = syscall(SYS_pidfd_open, pid, 0);
+#else
+ errno = ENOSYS;
+#endif
+ /* If we don't have pidfd, we use a timerfd to ping us every 20ms */
+ if (pidfd < 0 && errno == ENOSYS) {
+ pidfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (pidfd == -1)
+ goto error;
+ r = timerfd_settime(pidfd, 0,
+ &((struct itimerspec ){
+ .it_interval.tv_nsec = 20 * 1000 * 1000,
+ .it_value.tv_nsec = 20 * 1000 * 1000,
+ }), NULL);
+ if (r < 0)
+ goto error;
+ }
+
+ /* Each test has an epollfd with:
+ * - a timerfd so we can kill() it if it hangs
+ * - a pidfd so we get notified when the test exits
+ * - a pipe for stdout and a pipe for stderr
+ * - a pipe for logging (the various pwtest functions)
+ * - a pipe for the daemon's stdout
+ */
+ timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (timerfd < 0)
+ goto error;
+ timerfd_settime(timerfd, 0, &((struct itimerspec ){ .it_value.tv_sec = ctx->timeout}), NULL);
+
+ epollfd = epoll_create(1);
+ if (epollfd < 0)
+ goto error;
+ ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = pidfd };
+ ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = read_fds[FD_STDOUT] };
+ ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = read_fds[FD_STDERR] };
+ ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = read_fds[FD_LOG] };
+ ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = timerfd };
+ if (t->args.pw_daemon)
+ ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = read_fds[FD_DAEMON] };
+
+ for (size_t i = 0; i < nevents; i++) {
+ r = epoll_ctl(epollfd, EPOLL_CTL_ADD, ev[i].data.fd, &ev[i]);
+ if (r < 0)
+ goto error;
+ }
+
+ while (true) {
+ struct epoll_event e;
+
+ r = epoll_wait(epollfd, &e, 1, (ctx->timeout * 2) * 1000);
+ if (r == 0)
+ break;
+ if (r == -1) {
+ goto error;
+ }
+
+ if (e.data.fd == pidfd) {
+ uint64_t buf;
+ int ignore SPA_UNUSED;
+ ignore = read(pidfd, &buf, sizeof(buf)); /* for timerfd fallback */
+ if (collect_child(t, pid))
+ break;
+ } else if (e.data.fd == timerfd) {
+ /* SIGALARM so we get the backtrace */
+ kill(pid, SIGALRM);
+ t->result = PWTEST_TIMEOUT;
+ waitpid(pid, NULL, 0);
+ break;
+ } else {
+ for (int i = 0; i < _FD_LAST; i++) {
+ if (e.data.fd == read_fds[i]) {
+ log_append(&t->logs[i], e.data.fd);
+ }
+
+ }
+ }
+ }
+ errno = 0;
+error:
+ r = errno;
+ close(epollfd);
+ close(timerfd);
+ close(pidfd);
+
+ return -r;
+}
+
+static void run_test(struct pwtest_context *ctx, struct pwtest_suite *c, struct pwtest_test *t)
+{
+ pid_t pid;
+ pid_t pw_daemon = 0;
+ int read_fds[_FD_LAST], write_fds[_FD_LAST];
+ int r;
+ const char *tmpdir;
+
+ if (t->result == PWTEST_SKIP) {
+ char *buf = pw_array_add(&t->logs[FD_LOG], 64);
+ spa_scnprintf(buf, 64, "pwtest: test skipped by pwtest\n");
+ return;
+ }
+
+ t->result = PWTEST_SYSTEM_ERROR;
+
+ r = init_pipes(read_fds, write_fds);
+ if (r < 0) {
+ t->sig_or_errno = r;
+ return;
+ }
+
+ set_test_env(ctx, t);
+ tmpdir = getenv("TMPDIR");
+ spa_assert_se(tmpdir != NULL);
+ r = chdir(tmpdir);
+ if (r < 0) {
+ char *buf = pw_array_add(&t->logs[FD_LOG], 256);
+ spa_scnprintf(buf, 256, "pwtest: failed to chdir to '%s'\n", tmpdir);
+ t->sig_or_errno = -errno;
+ goto error;
+ }
+
+ if (t->args.pw_daemon) {
+ pw_daemon = start_pwdaemon(t, write_fds[FD_DAEMON], write_fds[FD_LOG]);
+ if (pw_daemon < 0) {
+ errno = -pw_daemon;
+ goto error;
+ }
+ } else {
+ replace_env(t, "PIPEWIRE_REMOTE", "test-has-no-daemon");
+ }
+
+ if (ctx->no_fork) {
+ start_test_nofork(t);
+ } else {
+ pid = start_test_forked(t, read_fds, write_fds);
+ if (pid < 0) {
+ errno = -r;
+ goto error;
+ }
+ add_cleanup_pid(ctx, pid);
+
+ r = monitor_test_forked(t, pid, read_fds);
+ if (r < 0) {
+ errno = -r;
+ goto error;
+ }
+ remove_cleanup_pid(ctx, pid);
+ }
+
+ errno = 0;
+error:
+ if (errno)
+ t->sig_or_errno = -errno;
+
+ if (ctx->terminate) {
+ char *buf = pw_array_add(&t->logs[FD_LOG], 64);
+ spa_scnprintf(buf, 64, "pwtest: tests terminated by signal\n");
+ t->result = PWTEST_SYSTEM_ERROR;
+ }
+
+ for (size_t i = 0; i < SPA_N_ELEMENTS(read_fds); i++) {
+ log_append(&t->logs[i], read_fds[i]);
+ }
+
+ if (pw_daemon > 0) {
+ int status;
+
+ kill(-pw_daemon, SIGTERM);
+ remove_cleanup_pid(ctx, -pw_daemon);
+
+ /* blocking read. the other end closes when done */
+ close_pipes(write_fds);
+ fcntl(read_fds[FD_DAEMON], F_SETFL, O_CLOEXEC);
+ do {
+ log_append(&t->logs[FD_DAEMON], read_fds[FD_DAEMON]);
+ } while ((r = waitpid(pw_daemon, &status, WNOHANG)) == 0);
+
+ if (r > 0) {
+ /* write_fds are closed in the parent process, so we append directly */
+ char *buf = pw_array_add(&t->logs[FD_DAEMON], 64);
+
+ if (WIFEXITED(status)) {
+ spa_scnprintf(buf, 64, "pwtest: pipewire daemon exited with status %d\n",
+ WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ spa_scnprintf(buf, 64, "pwtest: pipewire daemon crashed with signal %d (SIG%s)\n",
+ WTERMSIG(status), sigabbrev_np(WTERMSIG(status)));
+ }
+ }
+ }
+
+ for (size_t i = 0; i < SPA_N_ELEMENTS(t->logs); i++) {
+ char *e = pw_array_add(&t->logs[i], 1);
+ spa_assert_se(e);
+ *e = '\0';
+ }
+
+ close_pipes(read_fds);
+ close_pipes(write_fds);
+
+ restore_env(t);
+}
+
+static inline void print_lines(FILE *fp, const char *log, const char *prefix)
+{
+ const char *state = NULL;
+ const char *s;
+ size_t len;
+
+ while (true) {
+ if ((s = pw_split_walk(log, "\n", &len, &state)) == NULL)
+ break;
+ fprintf(fp, "%s%.*s\n", prefix, (int)len, s);
+ }
+}
+
+static void log_test_result(struct pwtest_test *t)
+{
+ const struct status *s;
+ const struct status {
+ const char *status;
+ const char *color;
+ } statuses[] = {
+ { "PASS", SPA_ANSI_BOLD_GREEN },
+ { "FAIL", SPA_ANSI_BOLD_RED },
+ { "SKIP", SPA_ANSI_BOLD_YELLOW },
+ { "TIMEOUT", SPA_ANSI_BOLD_CYAN },
+ { "ERROR", SPA_ANSI_BOLD_MAGENTA },
+ };
+
+ spa_assert_se(t->result >= PWTEST_PASS);
+ spa_assert_se(t->result <= PWTEST_SYSTEM_ERROR);
+ s = &statuses[t->result - PWTEST_PASS];
+
+ fprintf(stderr, " status: %s%s%s\n",
+ isatty(STDERR_FILENO) ? s->color : "",
+ s->status,
+ isatty(STDERR_FILENO) ? "\x1B[0m" : "");
+
+ switch (t->result) {
+ case PWTEST_PASS:
+ case PWTEST_SKIP:
+ if (!verbose)
+ return;
+ break;
+ default:
+ break;
+ }
+
+ if (t->sig_or_errno > 0)
+ fprintf(stderr, " signal: %d # SIG%s \n", t->sig_or_errno,
+ sigabbrev_np(t->sig_or_errno));
+ else if (t->sig_or_errno < 0)
+ fprintf(stderr, " errno: %d # %s\n", -t->sig_or_errno,
+ strerror(-t->sig_or_errno));
+ if (t->logs[FD_LOG].size) {
+ fprintf(stderr, " log: |\n");
+ print_lines(stderr, t->logs[FD_LOG].data, " ");
+ }
+ if (t->logs[FD_STDOUT].size) {
+ fprintf(stderr, " stdout: |\n");
+ print_lines(stderr, t->logs[FD_STDOUT].data, " ");
+ }
+ if (t->logs[FD_STDERR].size) {
+ fprintf(stderr, " stderr: |\n");
+ print_lines(stderr, t->logs[FD_STDERR].data, " ");
+ }
+ if (t->logs[FD_DAEMON].size) {
+ fprintf(stderr, " daemon: |\n");
+ print_lines(stderr, t->logs[FD_DAEMON].data, " ");
+ }
+}
+
+static char* make_xdg_runtime_dir(void)
+{
+ time_t t = time(NULL);
+ struct tm *tm = localtime(&t);
+ char *dir;
+ char *tmpdir = getenv("TMPDIR");
+ char path[PATH_MAX];
+ FILE *fp;
+
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+ int r = asprintf(&dir, "%s/pwtest-%02d:%02d-XXXXXX", tmpdir, tm->tm_hour, tm->tm_min);
+ spa_assert_se((size_t)r == strlen(tmpdir) + 20); /* rough estimate */
+ spa_assert_se(mkdtemp(dir) != NULL);
+
+ /* Marker file to avoid removing a random directory during cleanup */
+ r = spa_scnprintf(path, sizeof(path), "%s/pwtest.dir", dir);
+ spa_assert_se((size_t)r == strlen(dir) + 11);
+ fp = fopen(path, "we");
+ spa_assert_se(fp);
+ fprintf(fp, "pwtest\n");
+ fclose(fp);
+
+ return dir;
+}
+
+static int run_tests(struct pwtest_context *ctx)
+{
+ int r = EXIT_SUCCESS;
+ struct pwtest_suite *c;
+ struct pwtest_test *t;
+
+ fprintf(stderr, "pwtest:\n");
+ spa_list_for_each(c, &ctx->suites, link) {
+ if (c->result != PWTEST_PASS)
+ continue;
+
+ fprintf(stderr, "- suite: \"%s\"\n", c->decl->name);
+ fprintf(stderr, " tests:\n");
+ spa_list_for_each(t, &c->tests, link) {
+ int min = t->args.range.min,
+ max = t->args.range.max;
+ bool have_range = min != 0 || max != 1;
+
+ for (int iteration = min; iteration < max; iteration++) {
+ if (ctx->has_iteration_filter &&
+ ctx->iteration_filter != iteration)
+ continue;
+
+ fprintf(stderr, " - name: \"%s\"\n", t->name);
+ if (have_range)
+ fprintf(stderr, " iteration: %d # %d - %d\n",
+ iteration, min, max);
+ t->iteration = iteration;
+ run_test(ctx, c, t);
+ log_test_result(t);
+
+ switch (t->result) {
+ case PWTEST_PASS:
+ case PWTEST_SKIP:
+ break;
+ default:
+ r = EXIT_FAILURE;
+ break;
+ }
+
+ if (ctx->terminate) {
+ r = EXIT_FAILURE;
+ return r;
+ }
+ }
+ }
+ }
+ return r;
+}
+
+static void list_tests(struct pwtest_context *ctx)
+{
+ struct pwtest_suite *c;
+ struct pwtest_test *t;
+
+ fprintf(stderr, "pwtest:\n");
+ spa_list_for_each(c, &ctx->suites, link) {
+ fprintf(stderr, "- suite: \"%s\"\n", c->decl->name);
+ fprintf(stderr, " tests:\n");
+ spa_list_for_each(t, &c->tests, link) {
+ fprintf(stderr, " - { name: \"%s\" }\n", t->name);
+ }
+ }
+}
+
+static bool is_debugger_attached(void)
+{
+ bool rc = false;
+#ifdef HAVE_LIBCAP
+ int status;
+ int pid = fork();
+
+ if (pid == -1)
+ return 0;
+
+ if (pid == 0) {
+ int ppid = getppid();
+ cap_t caps = cap_get_pid(ppid);
+ cap_flag_value_t cap_val;
+
+ if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_EFFECTIVE, &cap_val) == -1 ||
+ cap_val != CAP_SET)
+ _exit(false);
+
+ if (ptrace(PTRACE_ATTACH, ppid, NULL, 0) == 0) {
+ waitpid(ppid, NULL, 0);
+ ptrace(PTRACE_CONT, ppid, NULL, 0);
+ ptrace(PTRACE_DETACH, ppid, NULL, 0);
+ rc = false;
+ } else {
+ rc = true;
+ }
+ _exit(rc);
+ } else {
+ waitpid(pid, &status, 0);
+ rc = WEXITSTATUS(status);
+ }
+
+#endif
+ return !!rc;
+}
+
+static void usage(FILE *fp, const char *progname)
+{
+ fprintf(fp, "Usage: %s [OPTIONS]\n"
+ " -h, --help Show this help\n"
+ " --verbose Verbose output\n"
+ " --list List all available suites and tests\n"
+ " --timeout=N Set the test timeout to N seconds (default: 15)\n"
+ " --filter-test=glob Run only tests matching the given glob\n"
+ " --filter-suites=glob Run only suites matching the given glob\n"
+ " --filter-iteration=N Run only iteration N\n"
+ " --no-fork Do not fork for the test (see note below)\n"
+ "\n"
+ "Using --no-fork allows for easy debugging of tests but should only be used.\n"
+ "used with --filter-test. A test that modifies the process state will affect\n"
+ "subsequent tests and invalidate test results.\n",
+ progname);
+}
+
+static void sigterm_handler(int signo)
+{
+ terminate_cleanup_pids(ctx);
+ ctx->terminate = true;
+ if (ctx->no_fork) {
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ raise(signo);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int r = EXIT_SUCCESS;
+ enum {
+ OPT_TIMEOUT = 10,
+ OPT_LIST,
+ OPT_VERBOSE,
+ OPT_FILTER_TEST,
+ OPT_FILTER_SUITE,
+ OPT_FILTER_ITERATION,
+ OPT_NOFORK,
+ };
+ static const struct option opts[] = {
+ { "help", no_argument, 0, 'h' },
+ { "timeout", required_argument, 0, OPT_TIMEOUT },
+ { "list", no_argument, 0, OPT_LIST },
+ { "filter-test", required_argument, 0, OPT_FILTER_TEST },
+ { "filter-suite", required_argument, 0, OPT_FILTER_SUITE },
+ { "filter-iteration", required_argument, 0, OPT_FILTER_ITERATION },
+ { "list", no_argument, 0, OPT_LIST },
+ { "verbose", no_argument, 0, OPT_VERBOSE },
+ { "no-fork", no_argument, 0, OPT_NOFORK },
+ { NULL },
+ };
+ struct pwtest_context test_ctx = {
+ .suites = SPA_LIST_INIT(&test_ctx.suites),
+ .timeout = 15,
+ .has_iteration_filter = false,
+ };
+ enum {
+ MODE_TEST,
+ MODE_LIST,
+ } mode = MODE_TEST;
+ const char *suite_filter = NULL;
+
+ spa_list_init(&test_ctx.cleanup_pids);
+
+ ctx = &test_ctx;
+
+ while (1) {
+ int c;
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "h", opts, &option_index);
+ if (c == -1)
+ break;
+ switch(c) {
+ case 'h':
+ usage(stdout, argv[0]);
+ exit(EXIT_SUCCESS);
+ case OPT_TIMEOUT:
+ ctx->timeout = atoi(optarg);
+ break;
+ case OPT_LIST:
+ mode = MODE_LIST;
+ break;
+ case OPT_VERBOSE:
+ verbose = true;
+ break;
+ case OPT_FILTER_TEST:
+ ctx->test_filter = optarg;
+ break;
+ case OPT_FILTER_SUITE:
+ suite_filter= optarg;
+ break;
+ case OPT_FILTER_ITERATION:
+ ctx->has_iteration_filter = spa_atoi32(optarg, &ctx->iteration_filter, 10);
+ break;
+ case OPT_NOFORK:
+ ctx->no_fork = true;
+ break;
+ default:
+ usage(stderr, argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (RUNNING_ON_VALGRIND || is_debugger_attached())
+ ctx->no_fork = true;
+
+ find_suites(ctx, suite_filter);
+ add_tests(ctx);
+
+ if (getenv("TMPDIR") == NULL)
+ setenv("TMPDIR", "/tmp", 1);
+
+ ctx->xdg_dir = make_xdg_runtime_dir();
+
+ switch (mode) {
+ case MODE_LIST:
+ list_tests(ctx);
+ break;
+ case MODE_TEST:
+ setrlimit(RLIMIT_CORE, &((struct rlimit){0, 0}));
+ signal(SIGTERM, sigterm_handler);
+ signal(SIGINT, sigterm_handler);
+ r = run_tests(ctx);
+ break;
+ }
+
+ cleanup(ctx);
+
+ return r;
+}
diff --git a/test/pwtest.h b/test/pwtest.h
new file mode 100644
index 0000000..6d3070b
--- /dev/null
+++ b/test/pwtest.h
@@ -0,0 +1,577 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#ifndef PWTEST_H
+#define PWTEST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <math.h>
+
+#include <spa/utils/string.h>
+#include <spa/utils/dict.h>
+#include "spa/support/plugin.h"
+
+/**
+ * \defgroup pwtest Test Suite
+ * \brief `pwtest` is a test runner framework for PipeWire.
+ *
+ * It's modelled after other
+ * test suites like [check](https://libcheck.github.io/check/)
+ * and draws a lot of inspiration from the [libinput test
+ * suite](https://wayland.freedesktop.org/libinput/doc/latest/).
+ *
+ * `pwtest` captures logs from the tests (and the pipewire daemon, if
+ * applicable) and collects the output into YAML files printed to `stderr`.
+ *
+ * ## Tests
+ *
+ * A `pwtest` test is declared with the `PWTEST()` macro and must return one of
+ * the `pwtest` status codes. Those codes are:
+ * - \ref PWTEST_PASS for a successful test
+ * - \ref PWTEST_FAIL for a test case failure. Usually you should not return this
+ * value but rely on the `pwtest` macros to handle this case.
+ * - \ref PWTEST_SKIP to skip the current test
+ * - \ref PWTEST_SYSTEM_ERROR in case of an error that would cause the test to not run properly. This is not a test case failure but some required precondition not being met.
+ *
+ * ```c
+ * #include "pwtest.h"
+ *
+ * PWTEST(some_test)
+ * {
+ * int var = 10;
+ * const char *str = "foo";
+ *
+ * if (access("/", R_OK))
+ * pwtest_error_with_message("Oh dear, no root directory?");
+ *
+ * if (today_is_monday)
+ * return PWTEST_SKIP;
+ *
+ * pwtest_int_lt(var, 20);
+ * pwtest_ptr_notnull(&var);
+ * pwtest_str_ne(str, "bar");
+ *
+ * return PWTEST_PASS;
+ * }
+ * ...
+ * ```
+ *
+ * `pwtest` provides comparison macros for most basic data types with the `lt`,
+ * `le`, `eq`, `gt`, `ge` suffixes (`<, <=, ==, >, >=`). Tests usually should not
+ * return `PWTEST_FAIL` directly, use the `pwtest_fail()` macros if .
+ *
+ * By default, a test runs in a forked process, any changes to the
+ * process'environment, etc. are discarded in the next test.
+ *
+ * ## Suites
+ *
+ * Tests are grouped into suites and declared with the PWTEST_SUITE() macro.
+ * Each test must be added with the required arguments, it is acceptable to
+ * add the same test multiple times with different arguments.
+ *
+ * ```c
+ * ...
+ * PWTEST_SUITE(misc)
+ * {
+ * if (today_is_monday)
+ * return PWTEST_SKIP;
+ *
+ * // simple test
+ * pwtest_add(some_test, PWTEST_NOARG);
+ * // starts with its own pipewire daemon instance
+ * pwtest_add(some_test, PWTEST_ARG_DAEMON);
+ *
+ * return PWTEST_SUCCESS;
+ * }
+ * ```
+ * For a list of potential arguments, see \ref pwtest_arg and the
+ * `test-examples.c` file in the source directory.
+ *
+ * Suites are auto-discovered, they do not have to be manually added to a test run.
+ *
+ * ## Running tests
+ *
+ * The `pwtest` framework is built into each test binary, so just execute the
+ * matching binary. See the `--help` output for the full argument list.
+ *
+ * The most useful arguments when running the test suite:
+ * - `--verbose` to enable logs even when tests pass or are skipped
+ * - `--filter-test=glob`, `--filter-suite=glob` uses an `fnmatch()` glob to limit which tests or suites are run
+ * - `--no-fork` - see "Debugging test-case failures"
+ *
+ * ## Debugging test-case failures
+ *
+ * To debug a single test, disable forking and run the test through gdb:
+ *
+ * ```
+ * $ gdb path/to/test
+ * (gdb) break test_function_name
+ * Breakpoint 1 at 0xfffffffffffff: file ../test/test-file.c, line 123
+ * (gdb) r --no-fork --filter-test=test_function_name
+ * ```
+ * Disabling forking makes it easy to debug but should always be used with
+ * `--filter-test`. Any test that modifies its environment will affect
+ * subsequent tests and may invalidate the test results.
+ *
+ * Where a test has multiple iterations, use `--filter-iteration` to only run
+ * one single iteration.
+ */
+
+/**
+ * \addtogroup pwtest
+ * \{
+ */
+
+
+/** \struct pwtest_context */
+struct pwtest_context;
+/** \struct pwtest_suite */
+struct pwtest_suite;
+/** \struct pwtest_test */
+struct pwtest_test;
+
+#include "pwtest-implementation.h"
+
+/**
+ * Result returned from tests or suites.
+ */
+enum pwtest_result {
+ PWTEST_PASS = 75, /**< test successful */
+ PWTEST_FAIL = 76, /**< test failed. Should not be returned directly,
+ Use the pwtest_ macros instead */
+ PWTEST_SKIP = 77, /**< test was skipped */
+ PWTEST_TIMEOUT = 78, /**< test aborted after timeout */
+ PWTEST_SYSTEM_ERROR = 79, /**< unrelated error occurred */
+};
+
+/**
+ * If the test was added with a range (see \ref PWTEST_ARG_RANGE), this
+ * function returns the current iteration within that range. Otherwise, this
+ * function returns zero.
+ */
+int pwtest_get_iteration(struct pwtest_test *t);
+
+/**
+ * If the test had properties set (see \ref PWTEST_ARG_PROP), this function
+ * returns the \ref pw_properties. Otherwise, this function returns NULL.
+ */
+struct pw_properties *pwtest_get_props(struct pwtest_test *t);
+
+struct pwtest_context *pwtest_get_context(struct pwtest_test *t);
+
+/** Fail the current test */
+#define pwtest_fail() \
+ _pwtest_fail_condition(PWTEST_FAIL, __FILE__, __LINE__, __func__, "aborting", "")
+
+/** Same as above but more expressive in the code */
+#define pwtest_fail_if_reached() \
+ _pwtest_fail_condition(PWTEST_FAIL, __FILE__, __LINE__, __func__, "This line is supposed to be unreachable", "")
+
+/** Fail the current test with the given message */
+#define pwtest_fail_with_msg(...) \
+ _pwtest_fail_condition(PWTEST_FAIL, __FILE__, __LINE__, __func__, \
+ "aborting", __VA_ARGS__)
+
+/** Error out of the current test with the given message */
+#define pwtest_error_with_msg(...) \
+ _pwtest_fail_condition(PWTEST_SYSTEM_ERROR, __FILE__, __LINE__, __func__, \
+ "error", __VA_ARGS__)
+
+/** Assert r is not -1 and if it is, print the errno */
+#define pwtest_errno_ok(r_) \
+ pwtest_errno_check(r_, 0);
+
+/** Assert r is -1 and the errno is the given one */
+#define pwtest_errno(r_, errno_) \
+ pwtest_errno_check(r_, errno_);
+
+/** Assert r is not < 0 and if it is assume it's a negative errno */
+#define pwtest_neg_errno_ok(r_) \
+ pwtest_neg_errno_check(r_, 0);
+
+/** Assert r is < 0 and the given negative errno */
+#define pwtest_neg_errno(r_, errno_) \
+ pwtest_neg_errno_check(r_, errno_);
+
+/** Assert boolean (a == b) */
+#define pwtest_bool_eq(a_, b_) \
+ pwtest_comparison_bool_(a_, ==, b_)
+
+/** Assert boolean (a != b) */
+#define pwtest_bool_ne(a_, b_) \
+ pwtest_comparison_bool_(a_, !=, b_)
+
+/** Assert cond to be true. Convenience wrapper for readability */
+#define pwtest_bool_true(cond_) \
+ pwtest_comparison_bool_(cond_, ==, true)
+
+/** Assert cond to be false. Convenience wrapper for readability */
+#define pwtest_bool_false(cond_) \
+ pwtest_comparison_bool_(cond_, ==, false)
+
+/** Assert a == b */
+#define pwtest_int_eq(a_, b_) \
+ pwtest_comparison_int_(a_, ==, b_)
+
+/** Assert a != b */
+#define pwtest_int_ne(a_, b_) \
+ pwtest_comparison_int_(a_, !=, b_)
+
+/** Assert a < b */
+#define pwtest_int_lt(a_, b_) \
+ pwtest_comparison_int_(a_, <, b_)
+
+/** Assert a <= b */
+#define pwtest_int_le(a_, b_) \
+ pwtest_comparison_int_(a_, <=, b_)
+
+/** Assert a >= b */
+#define pwtest_int_ge(a_, b_) \
+ pwtest_comparison_int_(a_, >=, b_)
+
+/** Assert a > b */
+#define pwtest_int_gt(a_, b_) \
+ pwtest_comparison_int_(a_, >, b_)
+
+/** Assert ptr1 == ptr2 */
+#define pwtest_ptr_eq(a_, b_) \
+ pwtest_comparison_ptr_(a_, ==, b_)
+
+/** Assert ptr1 != ptr2 */
+#define pwtest_ptr_ne(a_, b_) \
+ pwtest_comparison_ptr_(a_, !=, b_)
+
+/** Assert ptr == NULL */
+#define pwtest_ptr_null(a_) \
+ pwtest_comparison_ptr_(a_, ==, NULL)
+
+/** Assert ptr != NULL */
+#define pwtest_ptr_notnull(a_) \
+ pwtest_comparison_ptr_(a_, !=, NULL)
+
+/** Assert a == b for a (hardcoded) epsilon */
+#define pwtest_double_eq(a_, b_)\
+ pwtest_comparison_double_((a_), ==, (b_))
+
+/** Assert a != b for a (hardcoded) epsilon */
+#define pwtest_double_ne(a_, b_)\
+ pwtest_comparison_double_((a_), !=, (b_))
+
+/** Assert a < b for a (hardcoded) epsilon */
+#define pwtest_double_lt(a_, b_)\
+ pwtest_comparison_double_((a_), <, (b_))
+
+/** Assert a <= b for a (hardcoded) epsilon */
+#define pwtest_double_le(a_, b_)\
+ pwtest_comparison_double_((a_), <=, (b_))
+
+/** Assert a >= b for a (hardcoded) epsilon */
+#define pwtest_double_ge(a_, b_)\
+ pwtest_comparison_double_((a_), >=, (b_))
+
+/** Assert a > b for a (hardcoded) epsilon */
+#define pwtest_double_gt(a_, b_)\
+ pwtest_comparison_double_((a_), >, (b_))
+
+#define pwtest_int(a_, op_, b_) \
+ pwtest_comparison_int_(a_, op_, b_)
+
+
+/** Assert str1 is equal to str2 */
+#define pwtest_str_eq(a_, b_) \
+ do { \
+ const char *_a = a_; \
+ const char *_b = b_; \
+ if (!spa_streq(_a, _b)) \
+ _pwtest_fail_comparison_str(__FILE__, __LINE__, __func__, \
+ #a_ " equals " #b_, _a, _b); \
+ } while(0)
+
+/** Assert str1 is equal to str2 for l characters */
+#define pwtest_str_eq_n(a_, b_, l_) \
+ do { \
+ const char *_a = a_; \
+ const char *_b = b_; \
+ if (!spa_strneq(_a, _b, l_)) \
+ _pwtest_fail_comparison_str(__FILE__, __LINE__, __func__, \
+ #a_ " equals " #b_ ", len: " #l_, _a, _b); \
+ } while(0)
+
+/** Assert str1 is not equal to str2 */
+#define pwtest_str_ne(a_, b_) \
+ do { \
+ const char *_a = a_; \
+ const char *_b = b_; \
+ if (spa_streq(_a, _b)) \
+ _pwtest_fail_comparison_str(__FILE__, __LINE__, __func__, \
+ #a_ " not equal to " #b_, _a, _b); \
+ } while(0)
+
+/** Assert str1 is not equal to str2 for l characters */
+#define pwtest_str_ne_n(a_, b_, l_) \
+ do { \
+ __typeof__(a_) _a = a_; \
+ __typeof__(b_) _b = b_; \
+ if (spa_strneq(_a, _b, l_)) \
+ _pwtest_fail_comparison_str(__FILE__, __LINE__, __func__, \
+ #a_ " not equal to " #b_ ", len: " #l_, _a, _b); \
+ } while(0)
+
+
+/** Assert haystack contains needle */
+#define pwtest_str_contains(haystack_, needle_) \
+ do { \
+ const char *_h = haystack_; \
+ const char *_n = needle_; \
+ if (!strstr(_h, _n)) \
+ _pwtest_fail_comparison_str(__FILE__, __LINE__, __func__, \
+ #haystack_ " contains " #needle_, _h, _n); \
+ } while(0)
+
+
+/* Needs to be a #define NULL for SPA_SENTINEL */
+enum pwtest_arg {
+ PWTEST_NOARG = 0,
+ /**
+ * The next argument is an int specifying the numerical signal number.
+ * The test is expected to raise that signal. The test fails if none
+ * or any other signal is raised.
+ *
+ * Example:
+ * ```c
+ * pwtest_add(mytest, PWTEST_ARG_SIGNAL, SIGABRT);
+ * ```
+ */
+ PWTEST_ARG_SIGNAL,
+ /**
+ * The next two int arguments are the minimum (inclusive) and
+ * maximum (exclusive) range for this test.
+ *
+ * Example:
+ * ```c
+ * pwtest_add(mytest, PWTEST_ARG_RANGE, -50, 50);
+ * ```
+ * Use pwtest_get_iteration() in the test function to obtain the current iteration.
+ */
+ PWTEST_ARG_RANGE,
+ /**
+ * The next two const char * arguments are the key and value
+ * for a property entry. This argument may be specified multiple times
+ * to add multiple properties.
+ *
+ * Use pwtest_get_props() to get the properties within the test function.
+ *
+ * Example:
+ * ```c
+ * pwtest_add(mytest,
+ * PWTEST_ARG_PROP, "key1", "value1",
+ * PWTEST_ARG_PROP, "key2", "value2");
+ * ```
+ */
+ PWTEST_ARG_PROP,
+ /**
+ * The next two const char * arguments are the key and value
+ * for the environment variable to be set in the test. This argument
+ * may be specified multiple times to add multiple environment
+ * variables.
+ *
+ * Example:
+ * ```c
+ * pwtest_add(mytest,
+ * PWTEST_ARG_ENV, "env1", "value1",
+ * PWTEST_ARG_ENV, "env2", "value2");
+ * ```
+ *
+ * These environment variables are only set for the test itself, a
+ * a pipewire daemon started with \ref PWTEST_ARG_DAEMON does not share
+ * those variables.
+ *
+ */
+ PWTEST_ARG_ENV,
+ /**
+ * Takes no extra arguments. If provided, the test case will start a
+ * pipewire daemon and stop the daemon when finished.
+ *
+ * The `PIPEWIRE_REMOTE` environment variable will be set in the
+ * test to point to this daemon.
+ *
+ * Example:
+ * ```c
+ * pwtest_add(mytest, PWTEST_ARG_DAEMON);
+ * ```
+ *
+ * Environment variables specified with \ref PWTEST_ARG_ENV are
+ * **not** available to the daemon, only to the test itself.
+ */
+ PWTEST_ARG_DAEMON,
+};
+/**
+ * Add function \a func_ to the current test suite.
+ *
+ * This macro should be used within PWTEST_SUITE() to register the test in that suite, for example:
+ *
+ * ```c
+ * PWTEST_SUITE(mysuite)
+ * {
+ * pwtest_add(test1, PWTEST_NOARG);
+ * pwtest_add(test2, PWTEST_ARG_DAEMON);
+ * pwtest_add(test3, PWTEST_ARG_RANGE, 0, 100, PWTEST_ARG_DAEMON);
+ * }
+ *
+ * ```
+ *
+ * If the test matches the given filters and the suite is executed, the test
+ * will be executed with the parameters given to pwtest_add().
+ *
+ * Arguments take a argument-dependent number of extra parameters, see
+ * see the \ref pwtest_arg documentation for details.
+ */
+#define pwtest_add(func_, ...) \
+ _pwtest_add(ctx, suite, #func_, func_, __VA_ARGS__, NULL)
+
+
+/**
+ * Declare a test case. To execute the test, add the test case name with pwtest_add().
+ *
+ * This macro expands so each test has a struct \ref pwtest_test variable
+ * named `current_test` available.
+ *
+ * ```c
+ * PWTEST(mytest)
+ * {
+ * struct pwtest_test *t = current_test;
+ *
+ * ... do stuff ...
+ *
+ * return PWTEST_PASS;
+ * }
+ *
+ * PWTEST_SUITE(mysuite)
+ * {
+ * pwtest_add(mytest);
+ *
+ * return PWTEST_PASS;
+ * }
+ * ```
+ */
+#define PWTEST(tname) \
+ static enum pwtest_result tname(struct pwtest_test *current_test)
+
+/**
+ * Initialize a test suite. A test suite is a group of related
+ * tests that filters and other conditions may apply to.
+ *
+ * Test suites are automatically discovered at build-time.
+ */
+#define PWTEST_SUITE(cname) \
+ static enum pwtest_result (cname##__setup)(struct pwtest_context *ctx, struct pwtest_suite *suite); \
+ __attribute__((used)) \
+ __attribute__((retain)) \
+ __attribute__((section("pwtest_suite_section"))) \
+ __attribute__((aligned(__alignof__(struct pwtest_suite_decl)))) \
+ static const struct pwtest_suite_decl _test_suite = { \
+ .name = #cname, \
+ .setup = cname##__setup, \
+ }; \
+ static enum pwtest_result (cname##__setup)(struct pwtest_context *ctx, struct pwtest_suite *suite)
+
+struct pwtest_spa_plugin {
+#define PWTEST_PLUGIN_MAX 32
+ size_t nsupport;
+ struct spa_support support[PWTEST_PLUGIN_MAX];
+
+ size_t ndlls;
+ void *dlls[PWTEST_PLUGIN_MAX];
+
+ size_t nhandles;
+ struct spa_handle *handles[PWTEST_PLUGIN_MAX];
+};
+
+struct pwtest_spa_plugin* pwtest_spa_plugin_new(void);
+void pwtest_spa_plugin_destroy(struct pwtest_spa_plugin *plugin);
+
+/**
+ * Identical to pwtest_spa_plugin_try_load_interface() but returns the
+ * interface and fails if the interface is NULL.
+ */
+void*
+pwtest_spa_plugin_load_interface(struct pwtest_spa_plugin *plugin,
+ const char *libname,
+ const char *factory_name,
+ const char *interface_name,
+ const struct spa_dict *info);
+
+/**
+ * Load \a interface_name from the factory in \a libname.
+ * If successful, the interface is returned and added to \a plugin's
+ * support items, i.e. subsequent loads of an interface will be able to
+ * make use of previously loaded ones.
+ *
+ * \return 0 on success or a negative errno on error
+ * \retval -ENOENT \a libname does not exist
+ * \retval -EINVAL \a factory_name does not exist in \a libname
+ * \retval -ENOSYS \a interface_name does not exist in \a factory_name
+ */
+int
+pwtest_spa_plugin_try_load_interface(struct pwtest_spa_plugin *plugin,
+ void **iface_return,
+ const char *libname,
+ const char *factory_name,
+ const char *interface_name,
+ const struct spa_dict *info);
+
+
+
+/**
+ * Create a temporary file and copy its full path to \a path. Fails the test
+ * with \ref PWTEST_SYSTEM_ERROR on error.
+ *
+ * This file does not need to be removed by the test, the pwtest framework
+ * will take care of it on exit.
+ */
+void pwtest_mkstemp(char path[PATH_MAX]);
+
+/**
+ * Run a command and wait for it to return.
+ */
+int pwtest_spawn(const char *file, char *const argv[]);
+
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PWTEST_H */
diff --git a/test/test-array.c b/test/test-array.c
new file mode 100644
index 0000000..1d9ea24
--- /dev/null
+++ b/test/test-array.c
@@ -0,0 +1,146 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "config.h"
+
+#include "pwtest.h"
+#include "pipewire/array.h"
+
+PWTEST(array_test_abi)
+{
+ /* array */
+#if defined(__x86_64__) && defined(__LP64__)
+ pwtest_int_eq(sizeof(struct pw_array), 32U);
+ return PWTEST_PASS;
+#else
+ fprintf(stderr, "Unknown arch: pw_array is size %zd\n", sizeof(struct pw_array));
+ return PWTEST_SKIP;
+#endif
+}
+
+PWTEST(array_test)
+{
+ struct pw_array arr;
+ uint32_t *ptr;
+ uint32_t vals[] = { 0, 100, 0x8a, 0 };
+ size_t i;
+
+ pw_array_init(&arr, 64);
+ pwtest_int_eq(SPA_N_ELEMENTS(vals), 4U);
+
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 0U);
+ pwtest_bool_false(pw_array_check_index(&arr, 0, uint32_t));
+ pwtest_ptr_eq(pw_array_first(&arr), pw_array_end(&arr));
+ pw_array_for_each(ptr, &arr)
+ pwtest_fail_if_reached();
+
+ for (i = 0; i < 4; i++) {
+ ptr = (uint32_t*)pw_array_add(&arr, sizeof(uint32_t));
+ *ptr = vals[i];
+ }
+
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 4U);
+ pwtest_bool_true(pw_array_check_index(&arr, 2, uint32_t));
+ pwtest_bool_true(pw_array_check_index(&arr, 3, uint32_t));
+ pwtest_bool_false(pw_array_check_index(&arr, 4, uint32_t));
+
+ i = 0;
+ pw_array_for_each(ptr, &arr) {
+ pwtest_int_eq(*ptr, vals[i++]);
+ }
+
+ /* remove second */
+ ptr = pw_array_get_unchecked(&arr, 2, uint32_t);
+ pwtest_ptr_notnull(ptr);
+ pw_array_remove(&arr, ptr);
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 3U);
+ pwtest_bool_false(pw_array_check_index(&arr, 3, uint32_t));
+ ptr = pw_array_get_unchecked(&arr, 2, uint32_t);
+ pwtest_ptr_notnull(ptr);
+ pwtest_int_eq(*ptr, vals[3]);
+
+ /* remove first */
+ ptr = pw_array_get_unchecked(&arr, 0, uint32_t);
+ pwtest_ptr_notnull(ptr);
+ pw_array_remove(&arr, ptr);
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 2U);
+ ptr = pw_array_get_unchecked(&arr, 0, uint32_t);
+ pwtest_ptr_notnull(ptr);
+ pwtest_int_eq(*ptr, vals[1]);
+
+ /* iterate */
+ ptr = (uint32_t*)pw_array_first(&arr);
+ pwtest_bool_true(pw_array_check(&arr, ptr));
+ pwtest_int_eq(*ptr, vals[1]);
+ ptr++;
+ pwtest_bool_true(pw_array_check(&arr, ptr));
+ pwtest_int_eq(*ptr, vals[3]);
+ ptr++;
+ pwtest_bool_false(pw_array_check(&arr, ptr));
+
+ pw_array_reset(&arr);
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 0U);
+
+ pw_array_clear(&arr);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(array_clear)
+{
+ struct pw_array arr;
+ uint32_t *ptr;
+ uint32_t vals[] = { 0, 100, 0x8a, 0 };
+ size_t i;
+
+ pw_array_init(&arr, 64);
+
+ for (i = 0; i < 4; i++) {
+ ptr = (uint32_t*)pw_array_add(&arr, sizeof(uint32_t));
+ *ptr = vals[i];
+ }
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 4U);
+ pw_array_clear(&arr);
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 0U);
+
+ for (i = 0; i < 4; i++) {
+ ptr = (uint32_t*)pw_array_add(&arr, sizeof(uint32_t));
+ *ptr = vals[i];
+ }
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 4U);
+ pw_array_clear(&arr);
+ pwtest_int_eq(pw_array_get_len(&arr, uint32_t), 0U);
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(pw_array)
+{
+ pwtest_add(array_test_abi, PWTEST_NOARG);
+ pwtest_add(array_test, PWTEST_NOARG);
+ pwtest_add(array_clear, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-client.c b/test/test-client.c
new file mode 100644
index 0000000..f912dc9
--- /dev/null
+++ b/test/test-client.c
@@ -0,0 +1,70 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl-client.h>
+
+#define TEST_FUNC(a,b,func) \
+do { \
+ a.func = b.func; \
+ pwtest_ptr_eq(SPA_PTRDIFF(&a.func, &a), SPA_PTRDIFF(&b.func, &b)); \
+} while(0)
+
+PWTEST(client_abi)
+{
+ static const struct {
+ uint32_t version;
+ void (*destroy) (void *data);
+ void (*free) (void *data);
+ void (*initialized) (void *data);
+ void (*info_changed) (void *data, const struct pw_client_info *info);
+ void (*resource_added) (void *data, struct pw_resource *resource);
+ void (*resource_removed) (void *data, struct pw_resource *resource);
+ void (*busy_changed) (void *data, bool busy);
+ } test = { PW_VERSION_IMPL_CLIENT_EVENTS, NULL };
+
+ struct pw_impl_client_events ev;
+
+ TEST_FUNC(ev, test, destroy);
+ TEST_FUNC(ev, test, free);
+ TEST_FUNC(ev, test, initialized);
+ TEST_FUNC(ev, test, info_changed);
+ TEST_FUNC(ev, test, resource_added);
+ TEST_FUNC(ev, test, resource_removed);
+ TEST_FUNC(ev, test, busy_changed);
+
+ pwtest_int_eq(PW_VERSION_IMPL_CLIENT_EVENTS, 0);
+ pwtest_int_eq(sizeof(ev), sizeof(test));
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(client)
+{
+ pwtest_add(client_abi, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-config.c b/test/test-config.c
new file mode 100644
index 0000000..9ebbc26
--- /dev/null
+++ b/test/test-config.c
@@ -0,0 +1,102 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+
+#include <pipewire/conf.h>
+
+PWTEST(config_load_abspath)
+{
+ char path[PATH_MAX];
+ int r;
+ FILE *fp;
+ struct pw_properties *props;
+ char *basename;
+
+ pwtest_mkstemp(path);
+ fp = fopen(path, "we");
+ fputs("data = x", fp);
+ fclose(fp);
+
+ /* Load with NULL prefix and abs path */
+ props = pw_properties_new("ignore", "me", NULL);
+ r = pw_conf_load_conf(NULL, path, props);
+ pwtest_neg_errno_ok(r);
+ pwtest_str_eq(pw_properties_get(props, "data"), "x");
+ pw_properties_free(props);
+
+#if 0
+ /* Load with non-NULL abs prefix and abs path */
+ props = pw_properties_new("ignore", "me", NULL);
+ r = pw_conf_load_conf("/dummy", path, props);
+ pwtest_neg_errno_ok(r);
+ pwtest_str_eq(pw_properties_get(props, "data"), "x");
+ pw_properties_free(props);
+
+ /* Load with non-NULL relative prefix and abs path */
+ props = pw_properties_new("ignore", "me", NULL);
+ r = pw_conf_load_conf("dummy", path, props);
+ pwtest_neg_errno_ok(r);
+ pwtest_str_eq(pw_properties_get(props, "data"), "x");
+ pw_properties_free(props);
+#endif
+
+ /* Load with non-NULL abs prefix and relative path */
+ basename = rindex(path, '/'); /* basename(3) and dirname(3) are terrible */
+ pwtest_ptr_notnull(basename);
+ *basename = '\0';
+ basename++;
+
+ props = pw_properties_new("ignore", "me", NULL);
+ r = pw_conf_load_conf(path, basename, props);
+ pwtest_neg_errno_ok(r);
+ pwtest_str_eq(pw_properties_get(props, "data"), "x");
+ pw_properties_free(props);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(config_load_nullname)
+{
+ struct pw_properties *props = pw_properties_new("ignore", "me", NULL);
+ int r;
+
+ r = pw_conf_load_conf(NULL, NULL, props);
+ pwtest_neg_errno(r, -EINVAL);
+
+ r = pw_conf_load_conf("/dummy", NULL, props);
+ pwtest_neg_errno(r, -EINVAL);
+
+ pw_properties_free(props);
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(context)
+{
+ pwtest_add(config_load_abspath, PWTEST_NOARG);
+ pwtest_add(config_load_nullname, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-context.c b/test/test-context.c
new file mode 100644
index 0000000..3625d35
--- /dev/null
+++ b/test/test-context.c
@@ -0,0 +1,277 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+
+#include <spa/utils/string.h>
+#include <spa/support/dbus.h>
+#include <spa/support/cpu.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/global.h>
+
+#define TEST_FUNC(a,b,func) \
+do { \
+ a.func = b.func; \
+ pwtest_ptr_eq(SPA_PTRDIFF(&a.func, &a), SPA_PTRDIFF(&b.func, &b)); \
+} while(0)
+
+PWTEST(context_abi)
+{
+ struct pw_context_events ev;
+ struct {
+ uint32_t version;
+ void (*destroy) (void *data);
+ void (*free) (void *data);
+ void (*check_access) (void *data, struct pw_impl_client *client);
+ void (*global_added) (void *data, struct pw_global *global);
+ void (*global_removed) (void *data, struct pw_global *global);
+ } test = { PW_VERSION_CONTEXT_EVENTS, NULL };
+
+ pw_init(0, NULL);
+
+ TEST_FUNC(ev, test, destroy);
+ TEST_FUNC(ev, test, free);
+ TEST_FUNC(ev, test, check_access);
+ TEST_FUNC(ev, test, global_added);
+ TEST_FUNC(ev, test, global_removed);
+
+ pwtest_int_eq(PW_VERSION_CONTEXT_EVENTS, 0);
+ pwtest_int_eq(sizeof(ev), sizeof(test));
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+static void context_destroy_error(void *data)
+{
+ pwtest_fail_if_reached();
+}
+static void context_free_error(void *data)
+{
+ pwtest_fail_if_reached();
+}
+static void context_check_access_error(void *data, struct pw_impl_client *client)
+{
+ pwtest_fail_if_reached();
+}
+static void context_global_added_error(void *data, struct pw_global *global)
+{
+ pwtest_fail_if_reached();
+}
+static void context_global_removed_error(void *data, struct pw_global *global)
+{
+ pwtest_fail_if_reached();
+}
+
+static const struct pw_context_events context_events_error =
+{
+ PW_VERSION_CONTEXT_EVENTS,
+ .destroy = context_destroy_error,
+ .free = context_free_error,
+ .check_access = context_check_access_error,
+ .global_added = context_global_added_error,
+ .global_removed = context_global_removed_error,
+};
+
+static int destroy_count = 0;
+static void context_destroy_count(void *data)
+{
+ destroy_count++;
+}
+static int free_count = 0;
+static void context_free_count(void *data)
+{
+ free_count++;
+}
+static int global_removed_count = 0;
+static void context_global_removed_count(void *data, struct pw_global *global)
+{
+ global_removed_count++;
+}
+static int context_foreach_count = 0;
+static int context_foreach(void *data, struct pw_global *global)
+{
+ context_foreach_count++;
+ return 0;
+}
+static int context_foreach_error(void *data, struct pw_global *global)
+{
+ context_foreach_count++;
+ return -1;
+}
+PWTEST(context_create)
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct spa_hook listener = { { NULL }, };
+ struct pw_context_events context_events = context_events_error;
+ int res;
+
+ pw_init(0, NULL);
+
+ loop = pw_main_loop_new(NULL);
+ pwtest_ptr_notnull(loop);
+
+ context = pw_context_new(pw_main_loop_get_loop(loop),
+ pw_properties_new(
+ PW_KEY_CONFIG_NAME, "null",
+ NULL), 12);
+ pwtest_ptr_notnull(context);
+ pw_context_add_listener(context, &listener, &context_events, context);
+
+ /* check main loop */
+ pwtest_ptr_eq(pw_context_get_main_loop(context), pw_main_loop_get_loop(loop));
+ /* check user data */
+ pwtest_ptr_notnull(pw_context_get_user_data(context));
+
+ /* iterate globals */
+ pwtest_int_eq(context_foreach_count, 0);
+ res = pw_context_for_each_global(context, context_foreach, context);
+ pwtest_int_eq(res, 0);
+ pwtest_int_eq(context_foreach_count, 2);
+ res = pw_context_for_each_global(context, context_foreach_error, context);
+ pwtest_int_eq(res, -1);
+ pwtest_int_eq(context_foreach_count, 3);
+
+ /* check destroy */
+ context_events.destroy = context_destroy_count;
+ context_events.free = context_free_count;
+ context_events.global_removed = context_global_removed_count;
+
+ pwtest_int_eq(destroy_count, 0);
+ pwtest_int_eq(free_count, 0);
+ pwtest_int_eq(global_removed_count, 0);
+ pw_context_destroy(context);
+ pwtest_int_eq(destroy_count, 1);
+ pwtest_int_eq(free_count, 1);
+ pwtest_int_eq(global_removed_count, 2);
+ pw_main_loop_destroy(loop);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+PWTEST(context_properties)
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ const struct pw_properties *props;
+ struct spa_hook listener = { { NULL }, };
+ struct pw_context_events context_events = context_events_error;
+ struct spa_dict_item items[3];
+
+ pw_init(0, NULL);
+
+ loop = pw_main_loop_new(NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop),
+ pw_properties_new("foo", "bar",
+ "biz", "fuzz",
+ NULL),
+ 0);
+ pwtest_ptr_notnull(context);
+ pwtest_ptr_null(pw_context_get_user_data(context));
+ pw_context_add_listener(context, &listener, &context_events, context);
+
+ props = pw_context_get_properties(context);
+ pwtest_ptr_notnull(props);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "biz"), "fuzz");
+ pwtest_str_eq(pw_properties_get(props, "buzz"), NULL);
+
+ /* remove foo */
+ items[0] = SPA_DICT_ITEM_INIT("foo", NULL);
+ /* change biz */
+ items[1] = SPA_DICT_ITEM_INIT("biz", "buzz");
+ /* add buzz */
+ items[2] = SPA_DICT_ITEM_INIT("buzz", "frizz");
+ pw_context_update_properties(context, &SPA_DICT_INIT(items, 3));
+
+ pwtest_ptr_eq(props, pw_context_get_properties(context));
+ pwtest_str_eq(pw_properties_get(props, "foo"), NULL);
+ pwtest_str_eq(pw_properties_get(props, "biz"), "buzz");
+ pwtest_str_eq(pw_properties_get(props, "buzz"), "frizz");
+
+ spa_hook_remove(&listener);
+ pw_context_destroy(context);
+ pw_main_loop_destroy(loop);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+PWTEST(context_support)
+{
+ static const char * const types[] = {
+ SPA_TYPE_INTERFACE_DataSystem,
+ SPA_TYPE_INTERFACE_DataLoop,
+ SPA_TYPE_INTERFACE_System,
+ SPA_TYPE_INTERFACE_Loop,
+ SPA_TYPE_INTERFACE_LoopUtils,
+ SPA_TYPE_INTERFACE_Log,
+#ifdef HAVE_DBUS
+ SPA_TYPE_INTERFACE_DBus,
+#endif
+ SPA_TYPE_INTERFACE_CPU
+ };
+
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ const struct spa_support *support;
+ uint32_t n_support;
+ size_t i;
+
+ pw_init(0, NULL);
+
+ loop = pw_main_loop_new(NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 0);
+
+ support = pw_context_get_support(context, &n_support);
+ pwtest_ptr_notnull(support);
+ pwtest_int_gt(n_support, 0U);
+
+ for (i = 0; i < SPA_N_ELEMENTS(types); i++) {
+ pwtest_ptr_notnull(spa_support_find(support, n_support, types[i]));
+ }
+
+ pw_context_destroy(context);
+ pw_main_loop_destroy(loop);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(context)
+{
+ pwtest_add(context_abi, PWTEST_NOARG);
+ pwtest_add(context_create, PWTEST_NOARG);
+ pwtest_add(context_properties, PWTEST_NOARG);
+ pwtest_add(context_support, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-example.c b/test/test-example.c
new file mode 100644
index 0000000..05f9779
--- /dev/null
+++ b/test/test-example.c
@@ -0,0 +1,265 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+
+#include "config.h"
+
+#include <signal.h>
+#include <unistd.h>
+
+#include <pipewire/pipewire.h>
+
+#include "pwtest.h"
+
+/* The simplest test example (test passes) */
+PWTEST(successful_test)
+{
+ int x = 10, y = 20, z = 10;
+ bool t = true, f = false;
+ const char *a = "foo", *b = "bar", *c = "baz";
+
+ pwtest_int_lt(x, y);
+ pwtest_int_le(x, y);
+ pwtest_int_gt(y, x);
+ pwtest_int_ge(y, x);
+ pwtest_int_eq(x, z);
+ pwtest_int_ne(y, z);
+
+ pwtest_bool_true(t);
+ pwtest_bool_false(f);
+ pwtest_bool_eq(t, !f);
+ pwtest_bool_ne(t, f);
+
+ pwtest_str_eq(a, a);
+ pwtest_str_ne(a, b);
+ pwtest_str_eq_n(b, c, 2);
+ pwtest_str_ne_n(b, c, 3);
+
+ return PWTEST_PASS;
+}
+
+/* Demo failure of an integer comparison (test will fail) */
+PWTEST(failing_test_int)
+{
+ int x = 10, y = 20;
+ pwtest_int_gt(x, y);
+ return PWTEST_PASS;
+}
+
+/* Demo failure of a bool comparison (test will fail) */
+PWTEST(failing_test_bool)
+{
+ bool oops = true;
+ pwtest_bool_false(oops);
+ return PWTEST_PASS;
+}
+
+/* Demo failure of a string comparison (test will fail) */
+PWTEST(failing_test_string)
+{
+ const char *what = "yes";
+ pwtest_str_eq(what, "no");
+ return PWTEST_PASS;
+}
+
+/* Demo custom failure (test will fail) */
+PWTEST(general_fail_test)
+{
+ /* pwtest_fail(); */
+ pwtest_fail_with_msg("Some condition wasn't met");
+ return PWTEST_PASS;
+}
+
+/* Demo failure (test will fail) */
+PWTEST(failing_test_if_reached)
+{
+ pwtest_fail_if_reached();
+ return PWTEST_SYSTEM_ERROR;
+}
+
+/* Demo a system error (test will fail) */
+PWTEST(system_error_test)
+{
+ return PWTEST_SYSTEM_ERROR;
+}
+
+/* Demo signal handling of SIGSEGV (test will fail) */
+PWTEST(catch_segfault_test)
+{
+ int *x = NULL;
+ *x = 20;
+ return PWTEST_PASS;
+}
+
+/* Demo signal handling of abort (test will fail) */
+PWTEST(catch_abort_signal_test)
+{
+ abort();
+ return PWTEST_PASS;
+}
+
+/* Demo a timeout (test will fail with default timeout of 30) */
+PWTEST(timeout_test)
+{
+ /* run with --timeout=1 to make this less annoying */
+ sleep(60);
+ return PWTEST_PASS;
+}
+
+/* Demo a ranged test (test passes, skips the last 2) */
+PWTEST(ranged_test)
+{
+ struct pwtest_test *t = current_test;
+ int iteration = pwtest_get_iteration(t);
+
+ pwtest_int_lt(iteration, 10); /* see pwtest_add */
+
+ printf("Ranged test iteration %d\n", iteration);
+ if (iteration >= 8)
+ return PWTEST_SKIP;
+
+ return PWTEST_PASS;
+}
+
+/* Demo the properties passed to tests (test passes) */
+PWTEST(property_test)
+{
+ struct pwtest_test *t = current_test;
+ struct pw_properties *p = pwtest_get_props(t);
+
+ pwtest_ptr_notnull(p);
+ pwtest_str_eq(pw_properties_get(p, "myprop"), "somevalue");
+ pwtest_str_eq(pw_properties_get(p, "prop2"), "other");
+
+ return PWTEST_PASS;
+}
+
+/* Demo the environment passed to tests (test passes) */
+PWTEST(env_test)
+{
+ pwtest_str_eq(getenv("myenv"), "envval");
+ pwtest_str_eq(getenv("env2"), "val");
+
+ /* Set by pwtest */
+ pwtest_str_eq(getenv("PWTEST"), "1");
+
+ return PWTEST_PASS;
+}
+
+/* Demo the environment passed to tests (test passes) */
+PWTEST(env_reset_test)
+{
+ /* If run after env_test even with --no-fork this test should
+ * succeed */
+ pwtest_str_eq(getenv("myenv"), NULL);
+ pwtest_str_eq(getenv("env2"), NULL);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(default_env_test)
+{
+ /* This one is set automatically */
+ pwtest_str_eq(getenv("PWTEST"), "1");
+ /* Default value */
+ pwtest_str_eq(getenv("PIPEWIRE_REMOTE"), "test-has-no-daemon");
+
+ return PWTEST_PASS;
+}
+
+PWTEST(daemon_test)
+{
+ struct pw_context *ctx;
+ struct pw_core *core;
+ struct pw_loop *loop;
+
+ pwtest_str_eq_n(getenv("PIPEWIRE_REMOTE"), "pwtest-pw-", 10);
+
+ pw_init(0, NULL);
+ loop = pw_loop_new(NULL);
+ ctx = pw_context_new(loop, NULL, 0);
+ pwtest_ptr_notnull(ctx);
+ core = pw_context_connect(ctx, NULL, 0);
+ pwtest_ptr_notnull(core);
+
+ pw_loop_iterate(loop, -1);
+ pw_core_disconnect(core);
+ pw_context_destroy(ctx);
+ pw_loop_destroy(loop);
+
+ return PWTEST_PASS;
+}
+
+/* If not started with a daemon, we can't connect to a daemon (test will fail) */
+PWTEST(daemon_test_without_daemon)
+{
+ struct pw_context *ctx;
+ struct pw_core *core;
+ struct pw_loop *loop;
+
+ pw_init(0, NULL);
+ loop = pw_loop_new(NULL);
+ ctx = pw_context_new(loop, NULL, 0);
+ pwtest_ptr_notnull(ctx);
+ core = pw_context_connect(ctx, NULL, 0);
+
+ pwtest_ptr_notnull(core); /* Expect this to fail because we don't have a daemon */
+
+ pw_loop_iterate(loop, -1);
+ pw_core_disconnect(core);
+ pw_context_destroy(ctx);
+ pw_loop_destroy(loop);
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(example_tests)
+{
+ pwtest_add(successful_test, PWTEST_NOARG);
+ pwtest_add(failing_test_int, PWTEST_NOARG);
+ pwtest_add(failing_test_bool, PWTEST_NOARG);
+ pwtest_add(failing_test_string, PWTEST_NOARG);
+ pwtest_add(failing_test_if_reached, PWTEST_NOARG);
+ pwtest_add(general_fail_test, PWTEST_NOARG);
+ pwtest_add(system_error_test, PWTEST_NOARG);
+ pwtest_add(catch_segfault_test, PWTEST_NOARG);
+ pwtest_add(catch_abort_signal_test, PWTEST_ARG_SIGNAL, SIGABRT);
+ pwtest_add(ranged_test, PWTEST_ARG_RANGE, 0, 10);
+ pwtest_add(property_test,
+ PWTEST_ARG_PROP, "myprop", "somevalue",
+ PWTEST_ARG_PROP, "prop2", "other");
+ pwtest_add(env_test,
+ PWTEST_ARG_ENV, "env2", "val",
+ PWTEST_ARG_ENV, "myenv", "envval");
+ pwtest_add(env_reset_test, PWTEST_NOARG);
+ pwtest_add(default_env_test, PWTEST_NOARG);
+ pwtest_add(daemon_test, PWTEST_ARG_DAEMON);
+ pwtest_add(daemon_test_without_daemon, PWTEST_NOARG);
+
+ /* Run this one last so it doesn't matter if we forget --timeout */
+ pwtest_add(timeout_test, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-functional.c b/test/test-functional.c
new file mode 100644
index 0000000..ae837b4
--- /dev/null
+++ b/test/test-functional.c
@@ -0,0 +1,58 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "config.h"
+
+#include "pwtest.h"
+
+PWTEST(openal_info_test)
+{
+#ifdef OPENAL_INFO_PATH
+ int status = pwtest_spawn(OPENAL_INFO_PATH, (char *[]){ "openal-info", NULL });
+ pwtest_int_eq(WEXITSTATUS(status), 0);
+ return PWTEST_PASS;
+#else
+ return PWTEST_SKIP;
+#endif
+}
+
+PWTEST(pactl_test)
+{
+#ifdef PACTL_PATH
+ int status = pwtest_spawn(PACTL_PATH, (char *[]){ "pactl", "info", NULL });
+ pwtest_int_eq(WEXITSTATUS(status), 0);
+ return PWTEST_PASS;
+#else
+ return PWTEST_SKIP;
+#endif
+}
+
+PWTEST_SUITE(pw_array)
+{
+ pwtest_add(pactl_test, PWTEST_ARG_DAEMON);
+ pwtest_add(openal_info_test, PWTEST_ARG_DAEMON);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-lib.c b/test/test-lib.c
new file mode 100644
index 0000000..c2d73a4
--- /dev/null
+++ b/test/test-lib.c
@@ -0,0 +1,69 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "config.h"
+
+#include "pwtest.h"
+
+#include "pipewire/pipewire.h"
+
+PWTEST(library_version)
+{
+ const char *libversion, *headerversion;
+ char version_expected[64];
+
+ pw_init(0, NULL);
+ libversion = pw_get_library_version();
+ headerversion = pw_get_headers_version();
+
+ spa_scnprintf(version_expected, sizeof(version_expected),
+ "%d.%d.%d", PW_MAJOR, PW_MINOR, PW_MICRO);
+
+ pwtest_str_eq(headerversion, version_expected);
+ pwtest_str_eq(libversion, version_expected);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+PWTEST(init_deinit)
+{
+ pw_init(0, NULL);
+ pw_deinit();
+ pw_init(0, NULL);
+ pw_init(0, NULL);
+ pw_deinit();
+ pw_deinit();
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(properties)
+{
+ pwtest_add(library_version, PWTEST_NOARG);
+ pwtest_add(init_deinit, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-logger.c b/test/test-logger.c
new file mode 100644
index 0000000..b40445a
--- /dev/null
+++ b/test/test-logger.c
@@ -0,0 +1,656 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <spa/utils/ansi.h>
+#include <spa/utils/names.h>
+#include <spa/support/plugin.h>
+#include <spa/support/log.h>
+#include <pipewire/pipewire.h>
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-journal.h>
+#endif
+
+PWTEST(logger_truncate_long_lines)
+{
+ struct pwtest_spa_plugin *plugin;
+ void *iface;
+ char fname[PATH_MAX];
+ struct spa_dict_item item;
+ struct spa_dict info;
+ char buffer[1024];
+ FILE *fp;
+ bool mark_line_found = false;
+
+ pw_init(0, NULL);
+
+ pwtest_mkstemp(fname);
+ item = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_FILE, fname);
+ info = SPA_DICT_INIT(&item, 1);
+ plugin = pwtest_spa_plugin_new();
+ iface = pwtest_spa_plugin_load_interface(plugin, "support/libspa-support",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ &info);
+ pwtest_ptr_notnull(iface);
+
+ /* Print a line expected to be truncated */
+ spa_log_error(iface, "MARK: %1100s", "foo");
+
+ fp = fopen(fname, "re");
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ if (strstr(buffer, "MARK:")) {
+ const char *suffix = ".. (truncated)\n";
+ int len = strlen(buffer);
+ pwtest_str_eq(buffer + len - strlen(suffix), suffix);
+ mark_line_found = true;
+ break;
+ }
+ }
+
+ fclose(fp);
+
+ pwtest_bool_true(mark_line_found);
+ pwtest_spa_plugin_destroy(plugin);
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+PWTEST(logger_no_ansi)
+{
+ struct pwtest_spa_plugin *plugin;
+ void *iface;
+ char fname[PATH_MAX];
+ struct spa_dict_item items[2];
+ struct spa_dict info;
+ char buffer[1024];
+ FILE *fp;
+ bool mark_line_found = false;
+
+ pw_init(0, NULL);
+
+ pwtest_mkstemp(fname);
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_FILE, fname);
+ items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_COLORS, "true");
+ info = SPA_DICT_INIT(items, 2);
+ plugin = pwtest_spa_plugin_new();
+ iface = pwtest_spa_plugin_load_interface(plugin, "support/libspa-support",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ &info);
+ pwtest_ptr_notnull(iface);
+
+ /* Print a line usually containing a color sequence, but we're not a
+ * tty so expect none despite colors being enabled */
+ spa_log_error(iface, "MARK\n");
+
+ fp = fopen(fname, "re");
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ if (strstr(buffer, "MARK")) {
+ mark_line_found = true;
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_RESET));
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_RED));
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_BRIGHT_RED));
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_BOLD_RED));
+ }
+ }
+
+ fclose(fp);
+
+ pwtest_bool_true(mark_line_found);
+ pwtest_spa_plugin_destroy(plugin);
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+static void
+test_log_levels(enum spa_log_level level)
+{
+ char fname[PATH_MAX];
+ char buffer[1024];
+ FILE *fp;
+ bool above_level_found = false;
+ bool below_level_found = false;
+ bool current_level_found = false;
+ char *oldenv = getenv("PIPEWIRE_LOG");
+
+ pwtest_mkstemp(fname);
+ setenv("PIPEWIRE_LOG", fname, 1);
+
+ pw_init(0, NULL);
+
+ /* current level is whatever the iteration is. Log one line
+ * with our level, one with a level above (should never show up)
+ * and one with a level below (should show up).
+ */
+ if (level > SPA_LOG_LEVEL_NONE)
+ pw_log(level, "CURRENT");
+ if (level > SPA_LOG_LEVEL_ERROR)
+ pw_log(level - 1, "BELOW");
+ if (level < SPA_LOG_LEVEL_TRACE)
+ pw_log(level + 1, "ABOVE");
+
+ fp = fopen(fname, "re");
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ if (strstr(buffer, "CURRENT"))
+ current_level_found = true;
+ else if (strstr(buffer, "ABOVE"))
+ above_level_found = true;
+ else if (strstr(buffer, "BELOW"))
+ below_level_found = true;
+ }
+
+ fclose(fp);
+
+ /* Anything on a higher level than ours should never show up */
+ pwtest_bool_false(above_level_found);
+ if (level == SPA_LOG_LEVEL_NONE) {
+ pwtest_bool_false(current_level_found);
+ pwtest_bool_false(below_level_found);
+ } else if (level == SPA_LOG_LEVEL_ERROR) {
+ pwtest_bool_true(current_level_found);
+ pwtest_bool_false(below_level_found);
+ } else {
+ pwtest_bool_true(current_level_found);
+ pwtest_bool_true(below_level_found);
+ }
+ pw_deinit();
+
+ if (oldenv)
+ setenv("PIPEWIRE_LOG", oldenv, 1);
+ else
+ unsetenv("PIPEWIRE_LOG");
+}
+
+PWTEST(logger_levels)
+{
+ enum spa_log_level level = pwtest_get_iteration(current_test);
+ enum spa_log_level default_level = pw_log_level;
+ struct spa_log *default_logger = pw_log_get();
+ char *oldenv = getenv("PIPEWIRE_DEBUG");
+
+ if (oldenv)
+ oldenv = strdup(oldenv);
+ unsetenv("PIPEWIRE_DEBUG");
+
+ pw_log_set_level(level);
+
+ test_log_levels(level);
+
+ if (oldenv) {
+ setenv("PIPEWIRE_DEBUG", oldenv, 1);
+ free(oldenv);
+ }
+
+ pw_log_set(default_logger);
+ pw_log_set_level(default_level);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(logger_debug_env)
+{
+ enum spa_log_level level = pwtest_get_iteration(current_test);
+ enum spa_log_level default_level = pw_log_level;
+ struct spa_log *default_logger = pw_log_get();
+ char lvl[2] = {0};
+ char *oldenv = getenv("PIPEWIRE_DEBUG");
+
+ if (oldenv)
+ oldenv = strdup(oldenv);
+
+ spa_scnprintf(lvl, sizeof(lvl), "%d", level);
+ setenv("PIPEWIRE_DEBUG", lvl, 1);
+
+ /* Disable logging, let PIPEWIRE_DEBUG set the level */
+ pw_log_set_level(SPA_LOG_LEVEL_NONE);
+
+ test_log_levels(level);
+
+ if (oldenv) {
+ setenv("PIPEWIRE_DEBUG", oldenv, 1);
+ free(oldenv);
+ } else {
+ unsetenv("PIPEWIRE_DEBUG");
+ }
+
+ pw_log_set(default_logger);
+ pw_log_set_level(default_level);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(logger_debug_env_alpha)
+{
+ enum spa_log_level level = pwtest_get_iteration(current_test);
+ enum spa_log_level default_level = pw_log_level;
+ struct spa_log *default_logger = pw_log_get();
+ char *lvl = NULL;
+ char *oldenv = getenv("PIPEWIRE_DEBUG");
+
+ if (oldenv)
+ oldenv = strdup(oldenv);
+
+ switch(level) {
+ case SPA_LOG_LEVEL_NONE: lvl = "X"; break;
+ case SPA_LOG_LEVEL_ERROR: lvl = "E"; break;
+ case SPA_LOG_LEVEL_WARN: lvl = "W"; break;
+ case SPA_LOG_LEVEL_INFO: lvl = "I"; break;
+ case SPA_LOG_LEVEL_DEBUG: lvl = "D"; break;
+ case SPA_LOG_LEVEL_TRACE: lvl = "T"; break;
+ default:
+ pwtest_fail_if_reached();
+ break;
+ }
+ setenv("PIPEWIRE_DEBUG", lvl, 1);
+
+ /* Disable logging, let PIPEWIRE_DEBUG set the level */
+ pw_log_set_level(SPA_LOG_LEVEL_NONE);
+
+ test_log_levels(level);
+
+ if (oldenv) {
+ setenv("PIPEWIRE_DEBUG", oldenv, 1);
+ free(oldenv);
+ } else {
+ unsetenv("PIPEWIRE_DEBUG");
+ }
+
+ pw_log_set(default_logger);
+ pw_log_set_level(default_level);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(logger_debug_env_topic_all)
+{
+ enum spa_log_level level = pwtest_get_iteration(current_test);
+ enum spa_log_level default_level = pw_log_level;
+ struct spa_log *default_logger = pw_log_get();
+ char *oldenv = getenv("PIPEWIRE_DEBUG");
+ char lvlstr[32];
+ char *lvl = "X";
+
+ if (oldenv)
+ oldenv = strdup(oldenv);
+
+ switch(level) {
+ case SPA_LOG_LEVEL_NONE: lvl = "X"; break;
+ case SPA_LOG_LEVEL_ERROR: lvl = "E"; break;
+ case SPA_LOG_LEVEL_WARN: lvl = "W"; break;
+ case SPA_LOG_LEVEL_INFO: lvl = "I"; break;
+ case SPA_LOG_LEVEL_DEBUG: lvl = "D"; break;
+ case SPA_LOG_LEVEL_TRACE: lvl = "T"; break;
+ default:
+ pwtest_fail_if_reached();
+ break;
+ }
+
+ /* Check that the * glob works to enable all topics */
+ spa_scnprintf(lvlstr, sizeof(lvlstr), "*:%s", lvl);
+ setenv("PIPEWIRE_DEBUG", lvlstr, 1);
+
+ /* Disable logging, let PIPEWIRE_DEBUG set the level */
+ pw_log_set_level(SPA_LOG_LEVEL_NONE);
+
+ test_log_levels(level);
+
+ if (oldenv) {
+ setenv("PIPEWIRE_DEBUG", oldenv, 1);
+ free(oldenv);
+ } else {
+ unsetenv("PIPEWIRE_DEBUG");
+ }
+
+ pw_log_set(default_logger);
+ pw_log_set_level(default_level);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(logger_debug_env_invalid)
+{
+ enum spa_log_level default_level = pw_log_level;
+ struct spa_log *default_logger = pw_log_get();
+ char *oldenv = getenv("PIPEWIRE_DEBUG");
+ char fname[PATH_MAX];
+ char buf[1024] = {0};
+ int fd;
+ int rc;
+ bool error_message_found = false;
+ long unsigned int which = pwtest_get_iteration(current_test);
+ const char *envvars[] = {
+ "invalid value",
+ "*:5,some invalid value",
+ "*:W,foo.bar:3,invalid:",
+ "*:W,2,foo.bar:Q",
+ "*:W,7,foo.bar:D",
+ "*:W,Q,foo.bar:5",
+ "*:W,D,foo.bar:8",
+ };
+
+ pwtest_int_lt(which, SPA_N_ELEMENTS(envvars));
+
+ if (oldenv)
+ oldenv = strdup(oldenv);
+
+ /* The error message during pw_init() will go to stderr because no
+ * logger has been set up yet. Intercept that in our temp file */
+ pwtest_mkstemp(fname);
+ fd = open(fname, O_RDWR);
+ pwtest_errno_ok(fd);
+ rc = dup2(fd, STDERR_FILENO);
+ setlinebuf(stderr);
+ pwtest_errno_ok(rc);
+
+ setenv("PIPEWIRE_DEBUG", envvars[which], 1);
+ pw_init(0, NULL);
+
+ fsync(STDERR_FILENO);
+ lseek(fd, SEEK_SET, 0);
+ while ((rc = read(fd, buf, sizeof(buf) - 1) > 0)) {
+ if (strstr(buf, "Ignoring invalid format in PIPEWIRE_DEBUG")) {
+ error_message_found = true;
+ break;
+ }
+ }
+ pwtest_errno_ok(rc);
+ close(fd);
+ pwtest_bool_true(error_message_found);
+
+ if (oldenv) {
+ setenv("PIPEWIRE_DEBUG", oldenv, 1);
+ free(oldenv);
+ } else {
+ unsetenv("PIPEWIRE_DEBUG");
+ }
+
+ pw_log_set(default_logger);
+ pw_log_set_level(default_level);
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+PWTEST(logger_topics)
+{
+ struct pwtest_spa_plugin *plugin;
+ void *iface;
+ char fname[PATH_MAX];
+ struct spa_dict_item items[2];
+ struct spa_dict info;
+ char buffer[1024];
+ FILE *fp;
+ bool mark_line_found = false;
+ struct spa_log_topic topic = {
+ .version = 0,
+ .topic = "my topic",
+ .level = SPA_LOG_LEVEL_DEBUG,
+ };
+
+ pw_init(0, NULL);
+
+ pwtest_mkstemp(fname);
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_FILE, fname);
+ items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_COLORS, "true");
+ info = SPA_DICT_INIT(items, 2);
+ plugin = pwtest_spa_plugin_new();
+ iface = pwtest_spa_plugin_load_interface(plugin, "support/libspa-support",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ &info);
+ pwtest_ptr_notnull(iface);
+
+ spa_logt_info(iface, &topic, "MARK\n");
+
+ fp = fopen(fname, "re");
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ if (strstr(buffer, "MARK")) {
+ mark_line_found = true;
+ pwtest_str_contains(buffer, "my topic");
+ }
+ }
+
+ pwtest_bool_true(mark_line_found);
+ pwtest_spa_plugin_destroy(plugin);
+
+ fclose(fp);
+
+ return PWTEST_PASS;
+}
+
+#ifdef HAVE_SYSTEMD
+static enum pwtest_result
+find_in_journal(sd_journal *journal, const char *needle, char *out, size_t out_sz)
+{
+ int rc;
+ int i;
+
+ /* We give ourselves up to a second for our message to appear */
+ for (i = 0; i < 10; i++) {
+ int activity = sd_journal_wait(journal, 100000);
+
+ pwtest_neg_errno_ok(activity);
+ switch (activity) {
+ case SD_JOURNAL_NOP:
+ break;
+ case SD_JOURNAL_INVALIDATE:
+ case SD_JOURNAL_APPEND:
+ while ((rc = sd_journal_next(journal)) > 0) {
+ char buffer[1024] = {0};
+ const char *d;
+ size_t l;
+ int r = sd_journal_get_data(journal, "MESSAGE", (const void **)&d, &l);
+ if (r == -ENOENT || r == -E2BIG || r == -EBADMSG)
+ continue;
+
+ pwtest_neg_errno_ok(r);
+ spa_scnprintf(buffer, sizeof(buffer), "%.*s", (int) l, d);
+ if (strstr(buffer, needle)) {
+ spa_scnprintf(out, out_sz, "%s", buffer);
+ return PWTEST_PASS;
+ }
+ }
+ pwtest_neg_errno_ok(rc);
+ break;
+ default:
+ break;
+ }
+ }
+ return PWTEST_FAIL;
+}
+#endif
+
+PWTEST(logger_journal)
+{
+ enum pwtest_result result = PWTEST_SKIP;
+#ifdef HAVE_SYSTEMD
+ struct pwtest_spa_plugin *plugin;
+ void *iface;
+ struct spa_dict_item items[2];
+ struct spa_dict info;
+ struct spa_log_topic topic = {
+ .version = 0,
+ .topic = "pwtest journal",
+ .level = SPA_LOG_LEVEL_DEBUG,
+ };
+ char buffer[1024] = {0};
+ sd_journal *journal;
+ int rc;
+ char token[64];
+
+ pw_init(0, NULL);
+
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_LEVEL, "4"); /* debug */
+ info = SPA_DICT_INIT(items, 1);
+ plugin = pwtest_spa_plugin_new();
+ iface = pwtest_spa_plugin_load_interface(plugin, "support/libspa-journal",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ &info);
+ pwtest_ptr_notnull(iface);
+
+ rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_CURRENT_USER);
+ pwtest_neg_errno_ok(rc);
+
+ sd_journal_seek_head(journal);
+ if (sd_journal_next(journal) == 0) { /* No entries? We don't have a journal */
+ goto cleanup;
+ }
+
+ sd_journal_seek_tail(journal);
+ sd_journal_next(journal);
+
+ spa_scnprintf(token, sizeof(token), "MARK %s:%d", __func__, __LINE__);
+ spa_logt_info(iface, &topic, "%s", token);
+
+ result = find_in_journal(journal, token, buffer, sizeof(buffer));
+ pwtest_int_eq((int)result, PWTEST_PASS);
+ pwtest_str_contains(buffer, "pwtest journal");
+
+cleanup:
+ sd_journal_close(journal);
+ pwtest_spa_plugin_destroy(plugin);
+ pw_deinit();
+#endif
+ return result;
+}
+
+PWTEST(logger_journal_chain)
+{
+ enum pwtest_result result = PWTEST_SKIP;
+#ifdef HAVE_SYSTEMD
+ struct pwtest_spa_plugin *plugin;
+ void *iface_log;
+ void *iface;
+ char fname[PATH_MAX];
+ char buffer[1024];
+ FILE *fp;
+ struct spa_dict_item items[2];
+ struct spa_dict info;
+ bool mark_line_found = false;
+ struct spa_log_topic topic = {
+ .version = 0,
+ .topic = "pwtest journal",
+ .level = SPA_LOG_LEVEL_DEBUG,
+ };
+ sd_journal *journal;
+ int rc;
+ char token[64];
+
+ pw_init(0, NULL);
+ pwtest_mkstemp(fname);
+
+ /* Load a normal logger interface first, writing to fname */
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_FILE, fname);
+ items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_LEVEL, "4"); /* debug */
+ info = SPA_DICT_INIT(items, 2);
+ plugin = pwtest_spa_plugin_new();
+ iface_log = pwtest_spa_plugin_load_interface(plugin, "support/libspa-support",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ &info);
+ pwtest_ptr_notnull(iface_log);
+
+ /* Load the journal logger, it should chain to the above */
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_LEVEL, "4"); /* debug */
+ info = SPA_DICT_INIT(&items[1], 1);
+ iface = pwtest_spa_plugin_load_interface(plugin, "support/libspa-journal",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ &info);
+ pwtest_ptr_notnull(iface);
+
+ rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY);
+ pwtest_neg_errno_ok(rc);
+ sd_journal_seek_head(journal);
+ if (sd_journal_next(journal) == 0) { /* No entries? We don't have a journal */
+ goto cleanup;
+ }
+
+ sd_journal_seek_tail(journal);
+ sd_journal_next(journal);
+
+ spa_scnprintf(token, sizeof(token), "MARK %s:%d", __func__, __LINE__);
+
+ spa_logt_info(iface, &topic, "%s", token);
+ result = find_in_journal(journal, token, buffer, sizeof(buffer));
+ pwtest_int_eq((int)result, PWTEST_PASS);
+ pwtest_str_contains(buffer, "pwtest journal");
+
+ /* Now check that the line is in the chained file logger too */
+ spa_memzero(buffer, sizeof(buffer));
+ mark_line_found = false;
+ fp = fopen(fname, "re");
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ if (strstr(buffer, token)) {
+ mark_line_found = true;
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_RESET));
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_RED));
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_BRIGHT_RED));
+ pwtest_ptr_null(strstr(buffer, SPA_ANSI_BOLD_RED));
+ }
+ }
+
+ fclose(fp);
+
+ result = PWTEST_PASS;
+ pwtest_bool_true(mark_line_found);
+
+cleanup:
+ sd_journal_close(journal);
+ pwtest_spa_plugin_destroy(plugin);
+ pw_deinit();
+
+#endif
+ return result;
+}
+
+PWTEST_SUITE(logger)
+{
+ pwtest_add(logger_truncate_long_lines, PWTEST_NOARG);
+ pwtest_add(logger_no_ansi, PWTEST_NOARG);
+ pwtest_add(logger_levels,
+ PWTEST_ARG_RANGE, SPA_LOG_LEVEL_NONE, SPA_LOG_LEVEL_TRACE + 1,
+ PWTEST_NOARG);
+ pwtest_add(logger_debug_env,
+ PWTEST_ARG_RANGE, SPA_LOG_LEVEL_NONE, SPA_LOG_LEVEL_TRACE + 1,
+ PWTEST_NOARG);
+ pwtest_add(logger_debug_env_alpha,
+ PWTEST_ARG_RANGE, SPA_LOG_LEVEL_NONE, SPA_LOG_LEVEL_TRACE + 1,
+ PWTEST_NOARG);
+ pwtest_add(logger_debug_env_topic_all,
+ PWTEST_ARG_RANGE, SPA_LOG_LEVEL_NONE, SPA_LOG_LEVEL_TRACE + 1,
+ PWTEST_NOARG);
+ pwtest_add(logger_debug_env_invalid,
+ PWTEST_ARG_RANGE, 0, 7, /* see the test */
+ PWTEST_NOARG);
+ pwtest_add(logger_topics, PWTEST_NOARG);
+ pwtest_add(logger_journal, PWTEST_NOARG);
+ pwtest_add(logger_journal_chain, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-loop.c b/test/test-loop.c
new file mode 100644
index 0000000..bfdb1e4
--- /dev/null
+++ b/test/test-loop.c
@@ -0,0 +1,463 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/eventfd.h>
+
+#include "pwtest.h"
+
+#include <pipewire/pipewire.h>
+
+struct obj {
+ int x;
+ struct spa_source source;
+};
+
+struct data {
+ struct pw_main_loop *ml;
+ struct pw_loop *l;
+ struct obj *a, *b;
+ int count;
+};
+
+static inline void write_eventfd(int evfd)
+{
+ uint64_t value = 1;
+ ssize_t r = write(evfd, &value, sizeof(value));
+ pwtest_errno_ok(r);
+ pwtest_int_eq(r, (ssize_t) sizeof(value));
+}
+
+static inline void read_eventfd(int evfd)
+{
+ uint64_t value = 0;
+ ssize_t r = read(evfd, &value, sizeof(value));
+ pwtest_errno_ok(r);
+ pwtest_int_eq(r, (ssize_t) sizeof(value));
+}
+
+static void on_event(struct spa_source *source)
+{
+ struct data *d = source->data;
+
+ pw_loop_remove_source(d->l, &d->a->source);
+ pw_loop_remove_source(d->l, &d->b->source);
+ close(d->a->source.fd);
+ close(d->b->source.fd);
+ free(d->a);
+ free(d->b);
+
+ pw_main_loop_quit(d->ml);
+}
+
+PWTEST(pwtest_loop_destroy2)
+{
+ struct data data;
+
+ pw_init(0, NULL);
+
+ spa_zero(data);
+ data.ml = pw_main_loop_new(NULL);
+ pwtest_ptr_notnull(data.ml);
+
+ data.l = pw_main_loop_get_loop(data.ml);
+ pwtest_ptr_notnull(data.l);
+
+ data.a = calloc(1, sizeof(*data.a));
+ data.b = calloc(1, sizeof(*data.b));
+
+ data.a->source.func = on_event;
+ data.a->source.fd = eventfd(0, 0);
+ data.a->source.mask = SPA_IO_IN;
+ data.a->source.data = &data;
+ data.b->source.func = on_event;
+ data.b->source.fd = eventfd(0, 0);
+ data.b->source.mask = SPA_IO_IN;
+ data.b->source.data = &data;
+
+ pw_loop_add_source(data.l, &data.a->source);
+ pw_loop_add_source(data.l, &data.b->source);
+
+ write_eventfd(data.a->source.fd);
+ write_eventfd(data.b->source.fd);
+
+ pw_main_loop_run(data.ml);
+ pw_main_loop_destroy(data.ml);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+static void
+on_event_recurse1(struct spa_source *source)
+{
+ static bool first = true;
+ struct data *d = source->data;
+
+ ++d->count;
+ pwtest_int_lt(d->count, 3);
+
+ read_eventfd(source->fd);
+
+ if (first) {
+ first = false;
+ pw_loop_enter(d->l);
+ pw_loop_iterate(d->l, -1);
+ pw_loop_leave(d->l);
+ }
+ pw_main_loop_quit(d->ml);
+}
+
+PWTEST(pwtest_loop_recurse1)
+{
+ struct data data;
+
+ pw_init(0, NULL);
+
+ spa_zero(data);
+ data.ml = pw_main_loop_new(NULL);
+ pwtest_ptr_notnull(data.ml);
+
+ data.l = pw_main_loop_get_loop(data.ml);
+ pwtest_ptr_notnull(data.l);
+
+ data.a = calloc(1, sizeof(*data.a));
+ data.b = calloc(1, sizeof(*data.b));
+
+ data.a->source.func = on_event_recurse1;
+ data.a->source.fd = eventfd(0, 0);
+ data.a->source.mask = SPA_IO_IN;
+ data.a->source.data = &data;
+ data.b->source.func = on_event_recurse1;
+ data.b->source.fd = eventfd(0, 0);
+ data.b->source.mask = SPA_IO_IN;
+ data.b->source.data = &data;
+
+ pw_loop_add_source(data.l, &data.a->source);
+ pw_loop_add_source(data.l, &data.b->source);
+
+ write_eventfd(data.a->source.fd);
+ write_eventfd(data.b->source.fd);
+
+ pw_main_loop_run(data.ml);
+ pw_main_loop_destroy(data.ml);
+
+ pw_deinit();
+
+ free(data.a);
+ free(data.b);
+
+ return PWTEST_PASS;
+}
+
+static void
+on_event_recurse2(struct spa_source *source)
+{
+ static bool first = true;
+ struct data *d = source->data;
+
+ ++d->count;
+ pwtest_int_lt(d->count, 3);
+
+ read_eventfd(source->fd);
+
+ if (first) {
+ first = false;
+ pw_loop_enter(d->l);
+ pw_loop_iterate(d->l, -1);
+ pw_loop_leave(d->l);
+ } else {
+ pw_loop_remove_source(d->l, &d->a->source);
+ pw_loop_remove_source(d->l, &d->b->source);
+ close(d->a->source.fd);
+ close(d->b->source.fd);
+ free(d->a);
+ free(d->b);
+ }
+ pw_main_loop_quit(d->ml);
+}
+
+PWTEST(pwtest_loop_recurse2)
+{
+ struct data data;
+
+ pw_init(0, NULL);
+
+ spa_zero(data);
+ data.ml = pw_main_loop_new(NULL);
+ pwtest_ptr_notnull(data.ml);
+
+ data.l = pw_main_loop_get_loop(data.ml);
+ pwtest_ptr_notnull(data.l);
+
+ data.a = calloc(1, sizeof(*data.a));
+ data.b = calloc(1, sizeof(*data.b));
+
+ data.a->source.func = on_event_recurse2;
+ data.a->source.fd = eventfd(0, 0);
+ data.a->source.mask = SPA_IO_IN;
+ data.a->source.data = &data;
+ data.b->source.func = on_event_recurse2;
+ data.b->source.fd = eventfd(0, 0);
+ data.b->source.mask = SPA_IO_IN;
+ data.b->source.data = &data;
+
+ pw_loop_add_source(data.l, &data.a->source);
+ pw_loop_add_source(data.l, &data.b->source);
+
+ write_eventfd(data.a->source.fd);
+ write_eventfd(data.b->source.fd);
+
+ pw_main_loop_run(data.ml);
+ pw_main_loop_destroy(data.ml);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+static void
+on_event_fail_if_called(void *data, int fd, uint32_t mask)
+{
+ pwtest_fail_if_reached();
+}
+
+struct dmsbd_data {
+ struct pw_loop *l;
+ struct pw_main_loop *ml;
+ struct spa_source *source;
+ struct spa_hook hook;
+};
+
+static void dmsbd_after(void *data)
+{
+ struct dmsbd_data *d = data;
+
+ pw_loop_destroy_source(d->l, d->source);
+ pw_main_loop_quit(d->ml);
+}
+
+static const struct spa_loop_control_hooks dmsbd_hooks = {
+ SPA_VERSION_LOOP_CONTROL_HOOKS,
+ .after = dmsbd_after,
+};
+
+PWTEST(destroy_managed_source_before_dispatch)
+{
+ pw_init(NULL, NULL);
+
+ struct dmsbd_data data = {0};
+
+ data.ml = pw_main_loop_new(NULL);
+ pwtest_ptr_notnull(data.ml);
+
+ data.l = pw_main_loop_get_loop(data.ml);
+ pwtest_ptr_notnull(data.l);
+
+ data.source = pw_loop_add_io(data.l, eventfd(0, 0), SPA_IO_IN, true, on_event_fail_if_called, NULL);
+ pwtest_ptr_notnull(data.source);
+
+ pw_loop_add_hook(data.l, &data.hook, &dmsbd_hooks, &data);
+
+ write_eventfd(data.source->fd);
+
+ pw_main_loop_run(data.ml);
+ pw_main_loop_destroy(data.ml);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+struct dmsbd_recurse_data {
+ struct pw_loop *l;
+ struct pw_main_loop *ml;
+ struct spa_source *a, *b;
+ struct spa_hook hook;
+ bool first;
+};
+
+static void dmsbd_recurse_on_event(void *data, int fd, uint32_t mask)
+{
+ struct dmsbd_recurse_data *d = data;
+
+ read_eventfd(fd);
+
+ pw_loop_enter(d->l);
+ pw_loop_iterate(d->l, 0);
+ pw_loop_leave(d->l);
+
+ pw_main_loop_quit(d->ml);
+}
+
+static void dmswp_recurse_before(void *data)
+{
+ struct dmsbd_recurse_data *d = data;
+
+ if (d->first) {
+ write_eventfd(d->a->fd);
+ write_eventfd(d->b->fd);
+ }
+}
+
+static void dmsbd_recurse_after(void *data)
+{
+ struct dmsbd_recurse_data *d = data;
+
+ if (d->first) {
+ pw_loop_destroy_source(d->l, d->b);
+
+ d->first = false;
+ }
+}
+
+static const struct spa_loop_control_hooks dmsbd_recurse_hooks = {
+ SPA_VERSION_LOOP_CONTROL_HOOKS,
+ .before = dmswp_recurse_before,
+ .after = dmsbd_recurse_after,
+};
+
+PWTEST(destroy_managed_source_before_dispatch_recurse)
+{
+ pw_init(NULL, NULL);
+
+ struct dmsbd_recurse_data data = {
+ .first = true,
+ };
+
+ data.ml = pw_main_loop_new(NULL);
+ pwtest_ptr_notnull(data.ml);
+
+ data.l = pw_main_loop_get_loop(data.ml);
+ pwtest_ptr_notnull(data.l);
+
+ data.l = pw_main_loop_get_loop(data.ml);
+ pwtest_ptr_notnull(data.l);
+
+ data.a = pw_loop_add_io(data.l, eventfd(0, 0), SPA_IO_IN, true, dmsbd_recurse_on_event, &data);
+ data.b = pw_loop_add_io(data.l, eventfd(0, 0), SPA_IO_IN, true, on_event_fail_if_called, NULL);
+ pwtest_ptr_notnull(data.a);
+ pwtest_ptr_notnull(data.b);
+
+ pw_loop_add_hook(data.l, &data.hook, &dmsbd_recurse_hooks, &data);
+
+ pw_main_loop_run(data.ml);
+ pw_main_loop_destroy(data.ml);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+struct ctwd_data {
+ struct spa_source source;
+ int handler_running_barrier;
+};
+
+static void ctwd_event_handler(struct spa_source *source)
+{
+ struct ctwd_data *data = source->data;
+
+ write_eventfd(data->handler_running_barrier);
+
+ for (;;)
+ pause(); /* the purpose of this is to block the loop */
+}
+
+static int ctwd_add_source(struct spa_loop *loop, bool async, uint32_t seq,
+ const void *d, size_t size, void *user_data)
+{
+ struct ctwd_data *data = user_data;
+
+ pwtest_neg_errno_ok(spa_loop_add_source(loop, &data->source));
+
+ return 0;
+}
+
+PWTEST(cancel_thread_while_dispatching)
+{
+ static const struct spa_dict_item data_loop_props_items[] = {
+ { "loop.cancel", "true" },
+ };
+ static const struct spa_dict data_loop_props = SPA_DICT_INIT_ARRAY(data_loop_props_items);
+
+ struct ctwd_data data = {
+ .source = {
+ .data = &data,
+ .func = ctwd_event_handler,
+ .mask = SPA_IO_IN,
+ .fd = eventfd(0, 0),
+ },
+ .handler_running_barrier = eventfd(0, 0),
+ };
+
+ pw_init(NULL, NULL);
+
+ struct pw_data_loop *dl = pw_data_loop_new(&data_loop_props);
+ pwtest_ptr_notnull(dl);
+
+ struct pw_loop *l = pw_data_loop_get_loop(dl);
+ pwtest_ptr_notnull(l);
+
+ pwtest_neg_errno_ok(pw_data_loop_start(dl));
+
+ pw_loop_invoke(l, ctwd_add_source, 0, NULL, 0, true, &data);
+ pwtest_ptr_notnull(data.source.loop);
+
+ write_eventfd(data.source.fd);
+ read_eventfd(data.handler_running_barrier);
+
+ pwtest_neg_errno_ok(pw_data_loop_stop(dl));
+
+ /* these are the important checks */
+ pwtest_ptr_null(data.source.priv);
+ pwtest_int_eq(data.source.rmask, UINT32_C(0));
+
+ pw_loop_remove_source(l, &data.source);
+
+ pw_data_loop_destroy(dl);
+
+ close(data.source.fd);
+ close(data.handler_running_barrier);
+
+ pw_deinit();
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(support)
+{
+ pwtest_add(pwtest_loop_destroy2, PWTEST_NOARG);
+ pwtest_add(pwtest_loop_recurse1, PWTEST_NOARG);
+ pwtest_add(pwtest_loop_recurse2, PWTEST_NOARG);
+ pwtest_add(destroy_managed_source_before_dispatch, PWTEST_NOARG);
+ pwtest_add(destroy_managed_source_before_dispatch_recurse, PWTEST_NOARG);
+ pwtest_add(cancel_thread_while_dispatching, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-map.c b/test/test-map.c
new file mode 100644
index 0000000..b6d7681
--- /dev/null
+++ b/test/test-map.c
@@ -0,0 +1,242 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+
+#include <pipewire/map.h>
+
+
+PWTEST(map_add_remove)
+{
+ struct pw_map map = PW_MAP_INIT(2);
+ int a, b, c;
+ void *p1 = &a, *p2 = &b, *p3 = &c;
+ uint32_t idx1, idx2, idx3;
+
+ idx1 = pw_map_insert_new(&map, p1);
+ idx2 = pw_map_insert_new(&map, p2);
+ idx3 = pw_map_insert_new(&map, p3);
+
+ /* This is implementation-defined behavior and
+ * may change in the future */
+ pwtest_int_eq(idx1, 0U);
+ pwtest_int_eq(idx2, 1U);
+ pwtest_int_eq(idx3, 2U);
+
+ /* public API */
+ pwtest_ptr_eq(p1, pw_map_lookup(&map, idx1));
+ pwtest_ptr_eq(p2, pw_map_lookup(&map, idx2));
+ pwtest_ptr_eq(p3, pw_map_lookup(&map, idx3));
+
+ pw_map_remove(&map, idx1);
+ pwtest_ptr_null(pw_map_lookup(&map, idx1));
+ pwtest_ptr_eq(p2, pw_map_lookup(&map, idx2));
+ pwtest_ptr_eq(p3, pw_map_lookup(&map, idx3));
+
+ pw_map_remove(&map, idx2);
+ pwtest_ptr_null(pw_map_lookup(&map, idx1));
+ pwtest_ptr_null(pw_map_lookup(&map, idx2));
+ pwtest_ptr_eq(p3, pw_map_lookup(&map, idx3));
+
+ pw_map_remove(&map, idx3);
+ pwtest_ptr_null(pw_map_lookup(&map, idx1));
+ pwtest_ptr_null(pw_map_lookup(&map, idx2));
+ pwtest_ptr_null(pw_map_lookup(&map, idx3));
+
+ idx1 = pw_map_insert_new(&map, p1);
+ idx2 = pw_map_insert_new(&map, p2);
+ idx3 = pw_map_insert_new(&map, p3);
+
+ /* This is implementation-defined behavior and
+ * may change in the future */
+ pwtest_int_eq(idx3, 0U);
+ pwtest_int_eq(idx2, 1U);
+ pwtest_int_eq(idx1, 2U);
+
+ pw_map_clear(&map);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(map_insert)
+{
+ struct pw_map map = PW_MAP_INIT(2);
+ int a, b, c, d;
+ void *p1 = &a, *p2 = &b, *p3 = &c, *p4 = &d;
+ uint32_t idx1, idx2, idx3;
+ int rc;
+ size_t sz;
+
+ idx1 = pw_map_insert_new(&map, p1);
+ idx2 = pw_map_insert_new(&map, p2);
+ idx3 = pw_map_insert_new(&map, p3);
+
+ pwtest_ptr_eq(p1, pw_map_lookup(&map, idx1));
+ pwtest_ptr_eq(p2, pw_map_lookup(&map, idx2));
+ pwtest_ptr_eq(p3, pw_map_lookup(&map, idx3));
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 3U);
+
+ /* overwrite */
+ rc = pw_map_insert_at(&map, idx1, p4);
+ pwtest_neg_errno_ok(rc);
+ pwtest_ptr_eq(p4, pw_map_lookup(&map, idx1));
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 3U);
+
+ /* overwrite */
+ rc = pw_map_insert_at(&map, idx2, p4);
+ pwtest_neg_errno_ok(rc);
+ pwtest_ptr_eq(p4, pw_map_lookup(&map, idx2));
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 3U);
+
+ /* out of bounds */
+ rc = pw_map_insert_at(&map, 10000, p4);
+ pwtest_neg_errno(rc, -ENOSPC);
+
+ /* if id is the map size, the item is appended */
+ rc = pw_map_insert_at(&map, idx3 + 1, &p4);
+ pwtest_neg_errno_ok(rc);
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 4U);
+
+ pw_map_clear(&map);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(map_size)
+{
+ struct pw_map map = PW_MAP_INIT(2);
+ int a, b, c;
+ void *p1 = &a, *p2 = &b, *p3 = &c;
+ uint32_t idx1;
+ size_t sz;
+
+ idx1 = pw_map_insert_new(&map, p1);
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 1U);
+ pw_map_insert_new(&map, p2);
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 2U);
+ pw_map_insert_new(&map, p3);
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 3U);
+
+ /* Removing does not alter the size */
+ pw_map_remove(&map, idx1);
+ sz = pw_map_get_size(&map);
+ pwtest_int_eq(sz, 3U);
+
+ pw_map_clear(&map);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(map_double_remove)
+{
+ struct pw_map map = PW_MAP_INIT(2);
+ int a, b, c;
+ void *p1 = &a, *p2 = &b, *p3 = &c;
+
+ uint32_t idx1, idx2, idx3;
+
+ idx1 = pw_map_insert_new(&map, p1);
+ idx2 = pw_map_insert_new(&map, p2);
+ idx3 = pw_map_insert_new(&map, p3);
+
+ pw_map_remove(&map, idx1); /* idx1 in the free list */
+ pw_map_remove(&map, idx2); /* idx1 and 2 in the free list */
+ pw_map_remove(&map, idx2); /* should be a noop */
+ idx1 = pw_map_insert_new(&map, p1);
+ idx2 = pw_map_insert_new(&map, p2);
+
+ pwtest_ptr_eq(p1, pw_map_lookup(&map, idx1));
+ pwtest_ptr_eq(p2, pw_map_lookup(&map, idx2));
+ pwtest_ptr_eq(p3, pw_map_lookup(&map, idx3));
+
+ pw_map_clear(&map);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(map_insert_at_free)
+{
+ struct pw_map map = PW_MAP_INIT(2);
+ int data[3] = {1, 2, 3};
+ int new_data = 4;
+ int *ptr[3] = {&data[0], &data[1], &data[3]};
+ int idx[3];
+ int rc;
+
+ /* Test cases, for an item at idx:
+ * 1. remove at idx, then reinsert
+ * 2. remove at idx, remove another item, reinsert
+ * 3. remove another item, remove at idx, reinsert
+ * 4. remove another item, remove at index, remove another item, reinsert
+ *
+ * The indices are the respective 2 bits from the iteration counter,
+ * we use index 3 to indicate skipping that step to handle cases 1-3.
+ */
+ int iteration = pwtest_get_iteration(current_test);
+ int item_idx = iteration & 0x3;
+ int before_idx = (iteration >> 2) & 0x3;
+ int after_idx = (iteration >> 4) & 0x3;
+ const int SKIP = 3;
+
+ if (item_idx == SKIP)
+ return PWTEST_PASS;
+
+ idx[0] = pw_map_insert_new(&map, ptr[0]);
+ idx[1] = pw_map_insert_new(&map, ptr[1]);
+ idx[2] = pw_map_insert_new(&map, ptr[2]);
+
+ if (before_idx != SKIP) {
+ before_idx = idx[before_idx];
+ pw_map_remove(&map, before_idx);
+ }
+ pw_map_remove(&map, item_idx);
+ if (after_idx != SKIP) {
+ after_idx = idx[after_idx];
+ pw_map_remove(&map, after_idx);
+ }
+
+ rc = pw_map_insert_at(&map, item_idx, &new_data);
+ pwtest_neg_errno(rc, -EINVAL);
+ pw_map_clear(&map);
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(pw_map)
+{
+ pwtest_add(map_add_remove, PWTEST_NOARG);
+ pwtest_add(map_insert, PWTEST_NOARG);
+ pwtest_add(map_size, PWTEST_NOARG);
+ pwtest_add(map_double_remove, PWTEST_NOARG);
+ pwtest_add(map_insert_at_free, PWTEST_ARG_RANGE, 0, 63);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-properties.c b/test/test-properties.c
new file mode 100644
index 0000000..89e66bb
--- /dev/null
+++ b/test/test-properties.c
@@ -0,0 +1,629 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "config.h"
+
+#include "pwtest.h"
+
+#include "pipewire/properties.h"
+
+PWTEST(properties_abi)
+{
+#if defined(__x86_64__) && defined(__LP64__)
+ pwtest_int_eq(sizeof(struct pw_properties), 24U);
+ return PWTEST_PASS;
+#else
+ fprintf(stderr, "%zd\n", sizeof(struct pw_properties));
+ return PWTEST_SKIP;
+#endif
+}
+
+PWTEST(properties_empty)
+{
+ struct pw_properties *props, *copy;
+ void *state = NULL;
+
+ props = pw_properties_new(NULL, NULL);
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+
+ pwtest_int_eq(props->dict.n_items, 0U);
+ pwtest_ptr_null(pw_properties_get(props, NULL));
+ pwtest_ptr_null(pw_properties_get(props, "unknown"));
+ pwtest_ptr_null(pw_properties_iterate(props, &state));
+
+ pw_properties_clear(props);
+ pwtest_int_eq(props->dict.n_items, 0U);
+ pwtest_ptr_null(pw_properties_get(props, NULL));
+ pwtest_ptr_null(pw_properties_get(props, ""));
+ pwtest_ptr_null(pw_properties_get(props, "unknown"));
+ pwtest_ptr_null(pw_properties_iterate(props, &state));
+
+ copy = pw_properties_copy(props);
+ pwtest_ptr_notnull(copy);
+ pw_properties_free(props);
+
+ pwtest_int_eq(copy->dict.n_items, 0U);
+ pwtest_ptr_null(pw_properties_get(copy, NULL));
+ pwtest_ptr_null(pw_properties_get(copy, ""));
+ pwtest_ptr_null(pw_properties_get(copy, "unknown"));
+ pwtest_ptr_null(pw_properties_iterate(copy, &state));
+
+ pw_properties_free(copy);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_set)
+{
+ struct pw_properties *props, *copy;
+ void *state = NULL;
+ const char *str;
+
+ props = pw_properties_new(NULL, NULL);
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+
+ pwtest_int_eq(pw_properties_set(props, "foo", "bar"), 1);
+ pwtest_int_eq(props->dict.n_items, 1U);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_int_eq(pw_properties_set(props, "foo", "bar"), 0);
+ pwtest_int_eq(props->dict.n_items, 1U);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_int_eq(pw_properties_set(props, "foo", "fuz"), 1);
+ pwtest_int_eq(props->dict.n_items, 1U);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "fuz");
+ pwtest_int_eq(pw_properties_set(props, "bar", "foo"), 1);
+ pwtest_int_eq(props->dict.n_items, 2U);
+ pwtest_str_eq(pw_properties_get(props, "bar"), "foo");
+ pwtest_int_eq(pw_properties_set(props, "him", "too"), 1);
+ pwtest_int_eq(props->dict.n_items, 3U);
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+ pwtest_int_eq(pw_properties_set(props, "him", NULL), 1);
+ pwtest_int_eq(props->dict.n_items, 2U);
+ pwtest_ptr_null(pw_properties_get(props, "him"));
+ pwtest_int_eq(pw_properties_set(props, "him", NULL), 0);
+ pwtest_int_eq(props->dict.n_items, 2U);
+ pwtest_ptr_null(pw_properties_get(props, "him"));
+
+ pwtest_int_eq(pw_properties_set(props, "", "invalid"), 0);
+ pwtest_int_eq(pw_properties_set(props, NULL, "invalid"), 0);
+
+ str = pw_properties_iterate(props, &state);
+ pwtest_str_ne(str, NULL);
+ pwtest_bool_true((spa_streq(str, "foo") || spa_streq(str, "bar")));
+ str = pw_properties_iterate(props, &state);
+ pwtest_str_ne(str, NULL);
+ pwtest_bool_true((spa_streq(str, "foo") || spa_streq(str, "bar")));
+ str = pw_properties_iterate(props, &state);
+ pwtest_ptr_null(str);
+
+ pwtest_int_eq(pw_properties_set(props, "foo", NULL), 1);
+ pwtest_int_eq(props->dict.n_items, 1U);
+ pwtest_int_eq(pw_properties_set(props, "bar", NULL), 1);
+ pwtest_int_eq(props->dict.n_items, 0U);
+
+ pwtest_int_eq(pw_properties_set(props, "foo", "bar"), 1);
+ pwtest_int_eq(pw_properties_set(props, "bar", "foo"), 1);
+ pwtest_int_eq(pw_properties_set(props, "him", "too"), 1);
+ pwtest_int_eq(props->dict.n_items, 3U);
+
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "foo");
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+
+ pw_properties_clear(props);
+ pwtest_int_eq(props->dict.n_items, 0U);
+
+ pwtest_int_eq(pw_properties_set(props, "foo", "bar"), 1);
+ pwtest_int_eq(pw_properties_set(props, "bar", "foo"), 1);
+ pwtest_int_eq(pw_properties_set(props, "him", "too"), 1);
+ pwtest_int_eq(props->dict.n_items, 3U);
+
+ copy = pw_properties_copy(props);
+ pwtest_ptr_notnull(copy);
+ pwtest_int_eq(copy->dict.n_items, 3U);
+ pwtest_str_eq(pw_properties_get(copy, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(copy, "bar"), "foo");
+ pwtest_str_eq(pw_properties_get(copy, "him"), "too");
+
+ pwtest_int_eq(pw_properties_set(copy, "bar", NULL), 1);
+ pwtest_int_eq(pw_properties_set(copy, "foo", NULL), 1);
+ pwtest_int_eq(copy->dict.n_items, 1U);
+ pwtest_str_eq(pw_properties_get(copy, "him"), "too");
+
+ pwtest_int_eq(props->dict.n_items, 3U);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "foo");
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+
+ pw_properties_free(props);
+ pw_properties_free(copy);
+
+ return PWTEST_PASS;
+
+}
+
+PWTEST(properties_new)
+{
+ struct pw_properties *p;
+
+ /* Basic initialization */
+ p = pw_properties_new("k1", "v1", "k2", "v2", NULL);
+ pwtest_ptr_notnull(p);
+ pwtest_str_eq(pw_properties_get(p, "k1"), "v1");
+ pwtest_str_eq(pw_properties_get(p, "k2"), "v2");
+ pwtest_ptr_null(pw_properties_get(p, "k3"));
+ pw_properties_free(p);
+
+ /* Empty initialization should be fine */
+ p = pw_properties_new(NULL, NULL);
+ pwtest_ptr_notnull(p);
+ pwtest_ptr_null(pw_properties_get(p, "k1"));
+ pw_properties_free(p); /* sefault/valgrind only check */
+
+ p = pw_properties_new_string("k1=v1 k2 = v2\tk3\t=\tv3\nk4\n=\nv4");
+ pwtest_str_eq(pw_properties_get(p, "k1"), "v1");
+ pwtest_str_eq(pw_properties_get(p, "k2"), "v2");
+ pwtest_str_eq(pw_properties_get(p, "k3"), "v3");
+ pwtest_str_eq(pw_properties_get(p, "k4"), "v4");
+ pw_properties_free(p);
+
+ p = pw_properties_new("foo", "bar", "bar", "baz", "", "invalid", "him", "too", NULL);
+ pwtest_ptr_notnull(p);
+ pwtest_int_eq(p->flags, 0U);
+ pwtest_int_eq(p->dict.n_items, 3U);
+ pw_properties_free(p);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_new_string)
+{
+ struct pw_properties *props;
+
+ props = pw_properties_new_string("foo=bar bar=baz \"#ignore\"=ignore him=too empty=\"\" =gg");
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+ pwtest_int_eq(props->dict.n_items, 5U);
+
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "baz");
+ pwtest_str_eq(pw_properties_get(props, "#ignore"), "ignore");
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+ pwtest_str_eq(pw_properties_get(props, "empty"), "");
+
+ pw_properties_free(props);
+
+ props = pw_properties_new_string("foo=bar bar=baz");
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+ pwtest_int_eq(props->dict.n_items, 2U);
+
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "baz");
+
+ pw_properties_free(props);
+
+ props = pw_properties_new_string("foo=bar bar=\"baz");
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+ pwtest_int_eq(props->dict.n_items, 2U);
+
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "baz");
+
+ pw_properties_free(props);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_free)
+{
+ struct pw_properties *p;
+
+ pw_properties_free(NULL);
+
+ p = pw_properties_new("k1", "v1", "k2", "v2", NULL);
+ pw_properties_clear(p);
+ pw_properties_free(p);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_set_with_alloc)
+{
+ struct pw_properties *p;
+ int nadded;
+
+ /* Set a lot of properties to force reallocation */
+ p = pw_properties_new(NULL, NULL);
+ pwtest_ptr_notnull(p);
+ for (size_t i = 0; i < 1000; i++) {
+ char kbuf[6], vbuf[6];
+ spa_scnprintf(kbuf, sizeof(kbuf), "k%zd", i);
+ spa_scnprintf(vbuf, sizeof(vbuf), "v%zd", i);
+ /* New key, expect 1 */
+ pwtest_int_eq(pw_properties_set(p, kbuf, vbuf), 1);
+ /* No change, expect 0 */
+ pwtest_int_eq(pw_properties_set(p, kbuf, vbuf), 0);
+ pwtest_str_eq(pw_properties_get(p, kbuf), vbuf);
+
+ /* Different value now, expect 1 */
+ pwtest_int_eq(pw_properties_set(p, kbuf, "foo"), 1);
+ pwtest_str_eq(pw_properties_get(p, kbuf), "foo");
+ }
+ pw_properties_free(p);
+
+ /* Adding a NULL value is ignored */
+ p = pw_properties_new(NULL, NULL);
+ nadded = pw_properties_set(p, "key", NULL);
+ pwtest_int_eq(nadded, 0);
+ pw_properties_free(p);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_setf)
+{
+ struct pw_properties *p;
+
+ p = pw_properties_new(NULL, NULL);
+ pwtest_int_eq(pw_properties_setf(p, "foo", "%d.%08x", 657, 0x89342), 1);
+ pwtest_int_eq(p->dict.n_items, 1U);
+ pwtest_str_eq(pw_properties_get(p, "foo"), "657.00089342");
+
+ pwtest_int_eq(pw_properties_setf(p, "", "%f", 189.45f), 0);
+ pwtest_int_eq(pw_properties_setf(p, NULL, "%f", 189.45f), 0);
+ pwtest_int_eq(p->dict.n_items, 1U);
+ pw_properties_free(p);
+
+ /* Set a lot of properties to force reallocation */
+ p = pw_properties_new(NULL, NULL);
+ pwtest_ptr_notnull(p);
+ for (size_t i = 0; i < 1000; i++) {
+ char kbuf[6], vbuf[6];
+ spa_scnprintf(kbuf, sizeof(kbuf), "k%zd", i);
+ spa_scnprintf(vbuf, sizeof(vbuf), "v%zd", i);
+ /* New key, expect 1 */
+ pwtest_int_eq(pw_properties_setf(p, kbuf, "v%zd", i), 1);
+ /* No change, expect 0 */
+ pwtest_int_eq(pw_properties_setf(p, kbuf, "v%zd", i), 0);
+ pwtest_str_eq(pw_properties_get(p, kbuf), vbuf);
+
+ /* Different value now, expect 1 */
+ pwtest_int_eq(pw_properties_set(p, kbuf, "foo"), 1);
+ pwtest_str_eq(pw_properties_get(p, kbuf), "foo");
+ }
+ pw_properties_free(p);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_parse_bool)
+{
+ pwtest_bool_true(pw_properties_parse_bool("true"));
+ pwtest_bool_true(pw_properties_parse_bool("1"));
+ /* only a literal "true" is true */
+ pwtest_bool_false(pw_properties_parse_bool("TRUE"));
+ pwtest_bool_false(pw_properties_parse_bool("True"));
+
+ pwtest_bool_false(pw_properties_parse_bool("false"));
+ pwtest_bool_false(pw_properties_parse_bool("0"));
+ pwtest_bool_false(pw_properties_parse_bool("10"));
+ pwtest_bool_false(pw_properties_parse_bool("-1"));
+ pwtest_bool_false(pw_properties_parse_bool("1x"));
+ pwtest_bool_false(pw_properties_parse_bool("a"));
+
+ return PWTEST_PASS;
+}
+
+
+PWTEST(properties_parse_int)
+{
+ struct test {
+ const char *value;
+ int expected;
+ } tests[] = {
+ { "1", 1 },
+ { "0", 0 },
+ { "-1", -1 },
+ { "+1", +1 },
+ { "0xff", 0xff },
+ { "077", 077 },
+ /* parsing errors */
+ { "x", 0 },
+ { "xk", 0 },
+ { "1k", 0 },
+ { "abc", 0 },
+ { "foo", 0 },
+ { NULL, 0 },
+ { "", 0 },
+ };
+
+ for (size_t i = 0; i < SPA_N_ELEMENTS(tests); i++) {
+ struct test *t = &tests[i];
+ pwtest_int_eq(pw_properties_parse_int(t->value), t->expected);
+ pwtest_int_eq(pw_properties_parse_int64(t->value), t->expected);
+ pwtest_int_eq(pw_properties_parse_uint64(t->value), (uint64_t)t->expected);
+ }
+
+ pwtest_int_eq(pw_properties_parse_int("0xffffffffff"), 0); /* >32 bit */
+ pwtest_int_eq(pw_properties_parse_int64("0xffffffffff"), 0xffffffffff);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_parse_float)
+{
+ struct test {
+ const char *value;
+ double expected;
+ } tests[] = {
+ { "1.0", 1.0 },
+ { "0.0", 0.0 },
+ { "-1.0", -1.0 },
+ { "1.234", 1.234 },
+ /* parsing errors */
+ { "1,0", 0 },
+ { "x", 0 },
+ { "xk", 0 },
+ { "1k", 0 },
+ { "abc", 0 },
+ { "foo", 0 },
+ { NULL, 0 },
+ { "", 0 },
+ };
+
+ for (size_t i = 0; i < SPA_N_ELEMENTS(tests); i++) {
+ struct test *t = &tests[i];
+ pwtest_double_eq(pw_properties_parse_float(t->value), (float)t->expected);
+ pwtest_double_eq(pw_properties_parse_double(t->value), t->expected);
+ }
+
+ return PWTEST_PASS;
+}
+
+
+PWTEST(properties_copy)
+{
+ struct pw_properties *p1, *p2;
+
+ p1 = pw_properties_new("k1", "v1", "k2", "v2", NULL);
+ p2 = pw_properties_copy(p1);
+ pwtest_str_eq(pw_properties_get(p2, "k1"), "v1");
+ pwtest_str_eq(pw_properties_get(p2, "k2"), "v2");
+ pwtest_ptr_null(pw_properties_get(p2, "k3"));
+
+ /* Change in p2, esure p1 remains the same */
+ pw_properties_set(p2, "k1", "changed");
+ pwtest_str_eq(pw_properties_get(p1, "k1"), "v1");
+ pwtest_str_eq(pw_properties_get(p2, "k1"), "changed");
+
+ /* Add to p1, then to p2, check addition is separate */
+ pw_properties_set(p1, "k3", "v3");
+ pwtest_ptr_null(pw_properties_get(p2, "k3"));
+ pw_properties_set(p2, "k3", "new");
+ pwtest_str_eq(pw_properties_get(p2, "k3"), "new");
+
+ pw_properties_free(p1);
+ pw_properties_free(p2);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_update_string)
+{
+ struct pw_properties *p;
+ const char str[] = "k1 = new1 k3 = new3";
+ int nadded;
+
+ p = pw_properties_new("k1", "v1", "k2", "v2", NULL);
+ nadded = pw_properties_update_string(p, str, sizeof(str));
+ pwtest_int_eq(nadded, 2);
+ pwtest_str_eq(pw_properties_get(p, "k1"), "new1");
+ pwtest_str_eq(pw_properties_get(p, "k2"), "v2");
+ pwtest_str_eq(pw_properties_get(p, "k3"), "new3");
+ pw_properties_free(p);
+
+ /* Updating an empty property should be fine */
+ p = pw_properties_new(NULL, NULL);
+ nadded = pw_properties_update_string(p, str, sizeof(str));
+ pwtest_int_eq(nadded, 2);
+ pwtest_str_eq(pw_properties_get(p, "k1"), "new1");
+ pwtest_ptr_null(pw_properties_get(p, "k2"));
+ pwtest_str_eq(pw_properties_get(p, "k3"), "new3");
+ pw_properties_free(p);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_serialize_dict_stack_overflow)
+{
+ char *long_value = NULL;
+ struct spa_dict_item items[2];
+ struct spa_dict dict;
+ const int sz = 8 * 1024 * 1024;
+ char tmpfile[PATH_MAX];
+ FILE *fp;
+ int r;
+
+ /* Alloc a property value long enough to trigger a stack overflow
+ * in any variadic arrays (see * e994949d576e93f8c22)
+ */
+ long_value = calloc(1, sz);
+ if (long_value == 0)
+ return PWTEST_SKIP;
+
+ memset(long_value, 'a', sz - 1);
+ items[0] = SPA_DICT_ITEM_INIT("longval", long_value);
+ items[1] = SPA_DICT_ITEM_INIT(long_value, "longval");
+ dict = SPA_DICT_INIT(items, 2);
+
+ pwtest_mkstemp(tmpfile);
+ fp = fopen(tmpfile, "we");
+ pwtest_ptr_notnull(fp);
+ r = pw_properties_serialize_dict(fp, &dict, 0);
+ pwtest_int_eq(r, 1);
+
+ fclose(fp);
+ free(long_value);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_new_dict)
+{
+ struct pw_properties *props;
+ struct spa_dict_item items[5];
+
+ items[0] = SPA_DICT_ITEM_INIT("foo", "bar");
+ items[1] = SPA_DICT_ITEM_INIT("bar", "baz");
+ items[3] = SPA_DICT_ITEM_INIT("", "invalid");
+ items[4] = SPA_DICT_ITEM_INIT(NULL, "invalid");
+ items[2] = SPA_DICT_ITEM_INIT("him", "too");
+
+ props = pw_properties_new_dict(&SPA_DICT_INIT_ARRAY(items));
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+ pwtest_int_eq(props->dict.n_items, 3U);
+
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "baz");
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+
+ pw_properties_free(props);
+
+ return PWTEST_PASS;
+}
+
+
+PWTEST(properties_new_json)
+{
+ struct pw_properties *props;
+
+ props = pw_properties_new_string("{ \"foo\": \"bar\\n\\t\", \"bar\": 1.8, \"empty\": [ \"foo\", \"bar\" ], \"\": \"gg\"");
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+ pwtest_int_eq(props->dict.n_items, 3U);
+
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar\n\t");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "1.8");
+ pwtest_str_eq(pw_properties_get(props, "empty"),
+ "[ \"foo\", \"bar\" ]");
+
+ pw_properties_free(props);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(properties_update)
+{
+ struct pw_properties *props;
+ struct spa_dict_item items[5];
+
+ props = pw_properties_new(NULL, NULL);
+ pwtest_ptr_notnull(props);
+ pwtest_int_eq(props->flags, 0U);
+ pwtest_int_eq(props->dict.n_items, 0U);
+
+ items[0] = SPA_DICT_ITEM_INIT("foo", "bar");
+ items[1] = SPA_DICT_ITEM_INIT("bar", "baz");
+ items[3] = SPA_DICT_ITEM_INIT("", "invalid");
+ items[4] = SPA_DICT_ITEM_INIT(NULL, "invalid");
+ items[2] = SPA_DICT_ITEM_INIT("him", "too");
+ pwtest_int_eq(pw_properties_update(props, &SPA_DICT_INIT_ARRAY(items)),
+ 3);
+ pwtest_int_eq(props->dict.n_items, 3U);
+
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "baz");
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+
+ items[0] = SPA_DICT_ITEM_INIT("foo", "bar");
+ items[1] = SPA_DICT_ITEM_INIT("bar", "baz");
+ pwtest_int_eq(pw_properties_update(props, &SPA_DICT_INIT(items, 2)),
+ 0);
+ pwtest_int_eq(props->dict.n_items, 3U);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "baz");
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+
+ items[0] = SPA_DICT_ITEM_INIT("bar", "bear");
+ items[1] = SPA_DICT_ITEM_INIT("him", "too");
+ pwtest_int_eq(pw_properties_update(props, &SPA_DICT_INIT(items, 2)),
+ 1);
+ pwtest_int_eq(props->dict.n_items, 3U);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "bear");
+ pwtest_str_eq(pw_properties_get(props, "him"), "too");
+
+ items[0] = SPA_DICT_ITEM_INIT("bar", "bear");
+ items[1] = SPA_DICT_ITEM_INIT("him", NULL);
+ pwtest_int_eq(pw_properties_update(props, &SPA_DICT_INIT(items, 2)),
+ 1);
+ pwtest_int_eq(props->dict.n_items, 2U);
+ pwtest_str_eq(pw_properties_get(props, "foo"), "bar");
+ pwtest_str_eq(pw_properties_get(props, "bar"), "bear");
+ pwtest_ptr_null(pw_properties_get(props, "him"));
+
+ items[0] = SPA_DICT_ITEM_INIT("foo", NULL);
+ items[1] = SPA_DICT_ITEM_INIT("bar", "beer");
+ items[2] = SPA_DICT_ITEM_INIT("him", "her");
+ pwtest_int_eq(pw_properties_update(props, &SPA_DICT_INIT(items, 3)),
+ 3);
+ pwtest_int_eq(props->dict.n_items, 2U);
+ pwtest_ptr_null(pw_properties_get(props, "foo"));
+ pwtest_str_eq(pw_properties_get(props, "bar"), "beer");
+ pwtest_str_eq(pw_properties_get(props, "him"), "her");
+
+ pw_properties_free(props);
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(properties)
+{
+ pwtest_add(properties_abi, PWTEST_NOARG);
+ pwtest_add(properties_empty, PWTEST_NOARG);
+ pwtest_add(properties_new, PWTEST_NOARG);
+ pwtest_add(properties_new_string, PWTEST_NOARG);
+ pwtest_add(properties_free, PWTEST_NOARG);
+ pwtest_add(properties_set, PWTEST_NOARG);
+ pwtest_add(properties_set_with_alloc, PWTEST_NOARG);
+ pwtest_add(properties_setf, PWTEST_NOARG);
+ pwtest_add(properties_parse_bool, PWTEST_NOARG);
+ pwtest_add(properties_parse_int, PWTEST_NOARG);
+ pwtest_add(properties_parse_float, PWTEST_NOARG);
+ pwtest_add(properties_copy, PWTEST_NOARG);
+ pwtest_add(properties_update_string, PWTEST_NOARG);
+ pwtest_add(properties_serialize_dict_stack_overflow, PWTEST_NOARG);
+ pwtest_add(properties_new_dict, PWTEST_NOARG);
+ pwtest_add(properties_new_json, PWTEST_NOARG);
+ pwtest_add(properties_update, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-pwtest.c b/test/test-pwtest.c
new file mode 100644
index 0000000..9242d4c
--- /dev/null
+++ b/test/test-pwtest.c
@@ -0,0 +1,55 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include "pwtest.h"
+
+#include <signal.h>
+
+#include "pwtest-compat.c"
+
+PWTEST(compat_sigabbrev_np)
+{
+#ifndef HAVE_SIGABBREV_NP
+ pwtest_str_eq(sigabbrev_np(SIGABRT), "ABRT");
+ pwtest_str_eq(sigabbrev_np(SIGSEGV), "SEGV");
+ pwtest_str_eq(sigabbrev_np(SIGSTOP), "STOP");
+ pwtest_str_eq(sigabbrev_np(SIGCHLD), "CHLD");
+ pwtest_str_eq(sigabbrev_np(SIGTERM), "TERM");
+ pwtest_str_eq(sigabbrev_np(SIGKILL), "KILL");
+ pwtest_str_eq(sigabbrev_np(12345), NULL);
+
+ return PWTEST_PASS;
+#else
+ return PWTEST_SKIP;
+#endif
+}
+
+PWTEST_SUITE(pwtest)
+{
+ pwtest_add(compat_sigabbrev_np, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-spa-buffer.c b/test/test-spa-buffer.c
new file mode 100644
index 0000000..89bfabd
--- /dev/null
+++ b/test/test-spa-buffer.c
@@ -0,0 +1,150 @@
+/* Simple Plugin API
+ * Copyright © 2018 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+
+#include <spa/buffer/alloc.h>
+#include <spa/buffer/buffer.h>
+#include <spa/buffer/meta.h>
+
+PWTEST(buffer_abi_types)
+{
+ /* buffer */
+ pwtest_int_eq(SPA_DATA_Invalid, 0);
+ pwtest_int_eq(SPA_DATA_MemPtr, 1);
+ pwtest_int_eq(SPA_DATA_MemFd, 2);
+ pwtest_int_eq(SPA_DATA_DmaBuf, 3);
+ pwtest_int_eq(SPA_DATA_MemId, 4);
+ pwtest_int_eq(_SPA_DATA_LAST, 5);
+
+ /* meta */
+ pwtest_int_eq(SPA_META_Invalid, 0);
+ pwtest_int_eq(SPA_META_Header, 1);
+ pwtest_int_eq(SPA_META_VideoCrop, 2);
+ pwtest_int_eq(SPA_META_VideoDamage, 3);
+ pwtest_int_eq(SPA_META_Bitmap, 4);
+ pwtest_int_eq(SPA_META_Cursor, 5);
+ pwtest_int_eq(SPA_META_Control, 6);
+ pwtest_int_eq(SPA_META_Busy, 7);
+ pwtest_int_eq(SPA_META_VideoTransform, 8);
+ pwtest_int_eq(_SPA_META_LAST, 9);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(buffer_abi_sizes)
+{
+#if defined(__x86_64__) && defined(__LP64__)
+ pwtest_int_eq(sizeof(struct spa_chunk), 16U);
+ pwtest_int_eq(sizeof(struct spa_data), 40U);
+ pwtest_int_eq(sizeof(struct spa_buffer), 24U);
+
+ pwtest_int_eq(sizeof(struct spa_meta), 16U);
+ pwtest_int_eq(sizeof(struct spa_meta_header), 32U);
+ pwtest_int_eq(sizeof(struct spa_meta_region), 16U);
+ pwtest_int_eq(sizeof(struct spa_meta_bitmap), 20U);
+ pwtest_int_eq(sizeof(struct spa_meta_cursor), 28U);
+ pwtest_int_eq(sizeof(struct spa_meta_videotransform), 4U);
+
+ return PWTEST_PASS;
+#else
+ fprintf(stderr, "%zd\n", sizeof(struct spa_chunk));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_data));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_buffer));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_meta));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_meta_header));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_meta_region));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_meta_bitmap));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_meta_cursor));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_meta_videotransform));
+ return PWTEST_SKIP;
+#endif
+}
+
+PWTEST(buffer_alloc)
+{
+ struct spa_buffer **buffers;
+ struct spa_meta metas[4];
+ struct spa_data datas[2];
+ uint32_t aligns[2];
+ uint32_t i, j;
+
+ metas[0].type = SPA_META_Header;
+ metas[0].size = sizeof(struct spa_meta_header);
+ metas[1].type = SPA_META_VideoDamage;
+ metas[1].size = sizeof(struct spa_meta_region) * 16;
+#define CURSOR_META_SIZE(w,h,bpp) (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + w * h * bpp)
+ metas[2].type = SPA_META_Cursor;
+ metas[2].size = CURSOR_META_SIZE(64,64,4);
+ metas[3].type = 101;
+ metas[3].size = 11;
+
+ datas[0].maxsize = 4000;
+ datas[1].maxsize = 2011;
+
+ aligns[0] = 32;
+ aligns[1] = 16;
+
+ buffers = spa_buffer_alloc_array(16, 0,
+ SPA_N_ELEMENTS(metas), metas,
+ SPA_N_ELEMENTS(datas), datas, aligns);
+
+ fprintf(stderr, "buffers %p\n", buffers);
+
+ for (i = 0; i < 16; i++) {
+ struct spa_buffer *b = buffers[i];
+ fprintf(stderr, "buffer %d %p\n", i, b);
+
+ pwtest_int_eq(b->n_metas, SPA_N_ELEMENTS(metas));
+ pwtest_int_eq(b->n_datas, SPA_N_ELEMENTS(datas));
+
+ for (j = 0; j < SPA_N_ELEMENTS(metas); j++) {
+ pwtest_int_eq(b->metas[j].type, metas[j].type);
+ pwtest_int_eq(b->metas[j].size, metas[j].size);
+ fprintf(stderr, " meta %d %p\n", j, b->metas[j].data);
+ pwtest_bool_true(SPA_IS_ALIGNED(b->metas[j].data, 8));
+ }
+
+ for (j = 0; j < SPA_N_ELEMENTS(datas); j++) {
+ pwtest_int_eq(b->datas[j].maxsize, datas[j].maxsize);
+ fprintf(stderr, " data %d %p %p\n", j, b->datas[j].chunk, b->datas[j].data);
+ pwtest_bool_true(SPA_IS_ALIGNED(b->datas[j].chunk, 8));
+ pwtest_bool_true(SPA_IS_ALIGNED(b->datas[j].data, aligns[j]));
+ }
+ }
+ free(buffers);
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(spa_buffer)
+{
+ pwtest_add(buffer_abi_types, PWTEST_NOARG);
+ pwtest_add(buffer_abi_sizes, PWTEST_NOARG);
+ pwtest_add(buffer_alloc, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
+
diff --git a/test/test-spa-json.c b/test/test-spa-json.c
new file mode 100644
index 0000000..2a57039
--- /dev/null
+++ b/test/test-spa-json.c
@@ -0,0 +1,333 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2020 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <locale.h>
+
+#include "pwtest.h"
+
+#include <spa/utils/defs.h>
+#include <spa/utils/json.h>
+#include <spa/utils/string.h>
+
+PWTEST(json_abi)
+{
+#if defined(__x86_64__) && defined(__LP64__)
+ pwtest_int_eq(sizeof(struct spa_json), 32U);
+ return PWTEST_PASS;
+#else
+ fprintf(stderr, "%zd\n", sizeof(struct spa_json));
+ return PWTEST_SKIP;
+#endif
+}
+
+#define TYPE_OBJECT 0
+#define TYPE_ARRAY 1
+#define TYPE_STRING 2
+#define TYPE_BOOL 3
+#define TYPE_NULL 4
+#define TYPE_TRUE 5
+#define TYPE_FALSE 6
+#define TYPE_FLOAT 7
+
+static void check_type(int type, const char *value, int len)
+{
+ pwtest_bool_eq(spa_json_is_object(value, len), (type == TYPE_OBJECT));
+ pwtest_bool_eq(spa_json_is_array(value, len), (type == TYPE_ARRAY));
+ pwtest_bool_eq(spa_json_is_string(value, len), (type == TYPE_STRING));
+ pwtest_bool_eq(spa_json_is_bool(value, len),
+ (type == TYPE_BOOL || type == TYPE_TRUE || type == TYPE_FALSE));
+ pwtest_bool_eq(spa_json_is_null(value, len), (type == TYPE_NULL));
+ pwtest_bool_eq(spa_json_is_true(value, len), (type == TYPE_TRUE || type == TYPE_BOOL));
+ pwtest_bool_eq(spa_json_is_false(value, len), (type == TYPE_FALSE || type == TYPE_BOOL));
+ pwtest_bool_eq(spa_json_is_float(value, len), (type == TYPE_FLOAT));
+}
+
+static void expect_type(struct spa_json *it, int type)
+{
+ const char *value;
+ int len;
+ pwtest_int_gt((len = spa_json_next(it, &value)), 0);
+ check_type(type, value, len);
+}
+
+static void expect_string(struct spa_json *it, const char *str)
+{
+ const char *value;
+ int len;
+ char *s;
+ pwtest_int_gt((len = spa_json_next(it, &value)), 0);
+ check_type(TYPE_STRING, value, len);
+ s = alloca(len+1);
+ spa_json_parse_stringn(value, len, s, len+1);
+ pwtest_str_eq(s, str);
+}
+static void expect_float(struct spa_json *it, float val)
+{
+ const char *value;
+ int len;
+ float f = 0.0f;
+ pwtest_int_gt((len = spa_json_next(it, &value)), 0);
+ check_type(TYPE_FLOAT, value, len);
+ pwtest_int_gt(spa_json_parse_float(value, len, &f), 0);
+ pwtest_double_eq(f, val);
+}
+
+PWTEST(json_parse)
+{
+ struct spa_json it[5];
+ const char *json = " { "
+ "\"foo\": \"bar\","
+ "\"foo\\\" \": true, "
+ "\"foo \\n\\r\\t\": false,"
+ " \" arr\": [ true, false, null, 5, 5.7, \"str]\"],"
+ "\"foo 2\": null,"
+ "\"foo 3\": 1,"
+ " \"obj\": { \"ba } z\": false, \"empty\": [], \"foo\": { }, \"1.9\", 1.9 },"
+ "\"foo 4\" : 1.8, "
+ "\"foo 5\": -1.8 , "
+ "\"foo 6\": +2.8 ,"
+ " } ", *value;
+
+ spa_json_init(&it[0], json, strlen(json));
+
+ expect_type(&it[0], TYPE_OBJECT);
+ spa_json_enter(&it[0], &it[1]);
+ expect_string(&it[1], "foo");
+ expect_string(&it[1], "bar");
+ expect_string(&it[1], "foo\" ");
+ expect_type(&it[1], TYPE_TRUE);
+ expect_string(&it[1], "foo \n\r\t");
+ expect_type(&it[1], TYPE_FALSE);
+ expect_string(&it[1], " arr");
+ expect_type(&it[1], TYPE_ARRAY);
+ spa_json_enter(&it[1], &it[2]);
+ expect_string(&it[1], "foo 2");
+ expect_type(&it[1], TYPE_NULL);
+ expect_string(&it[1], "foo 3");
+ expect_float(&it[1], 1.f);
+ expect_string(&it[1], "obj");
+ expect_type(&it[1], TYPE_OBJECT);
+ spa_json_enter(&it[1], &it[3]);
+ expect_string(&it[1], "foo 4");
+ expect_float(&it[1], 1.8f);
+ expect_string(&it[1], "foo 5");
+ expect_float(&it[1], -1.8f);
+ expect_string(&it[1], "foo 6");
+ expect_float(&it[1], +2.8f);
+ /* in the array */
+ expect_type(&it[2], TYPE_TRUE);
+ expect_type(&it[2], TYPE_FALSE);
+ expect_type(&it[2], TYPE_NULL);
+ expect_float(&it[2], 5.f);
+ expect_float(&it[2], 5.7f);
+ expect_string(&it[2], "str]");
+ /* in the object */
+ expect_string(&it[3], "ba } z");
+ expect_type(&it[3], TYPE_FALSE);
+ expect_string(&it[3], "empty");
+ expect_type(&it[3], TYPE_ARRAY);
+ spa_json_enter(&it[3], &it[4]);
+ pwtest_int_eq(spa_json_next(&it[4], &value), 0);
+ expect_string(&it[3], "foo");
+ expect_type(&it[3], TYPE_OBJECT);
+ spa_json_enter(&it[3], &it[4]);
+ expect_string(&it[3], "1.9");
+ expect_float(&it[3], 1.9f);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(json_encode)
+{
+ char dst[128];
+ char dst4[4];
+ char dst6[6];
+ char result[1024];
+ pwtest_int_eq(spa_json_encode_string(dst, sizeof(dst), "test"), 6);
+ pwtest_str_eq(dst, "\"test\"");
+ pwtest_int_eq(spa_json_encode_string(dst4, sizeof(dst4), "test"), 6);
+ pwtest_int_eq(strncmp(dst4, "\"tes", 4), 0);
+ pwtest_int_eq(spa_json_encode_string(dst6, sizeof(dst6), "test"), 6);
+ pwtest_int_eq(strncmp(dst6, "\"test\"", 6), 0);
+ pwtest_int_eq(spa_json_encode_string(dst, sizeof(dst), "test\"\n\r \t\b\f\'"), 20);
+ pwtest_str_eq(dst, "\"test\\\"\\n\\r \\t\\b\\f'\"");
+ pwtest_int_eq(spa_json_encode_string(dst, sizeof(dst), "\x04\x05\x1f\x20\x01\x7f\x90"), 29);
+ pwtest_str_eq(dst, "\"\\u0004\\u0005\\u001f \\u0001\x7f\x90\"");
+ pwtest_int_eq(spa_json_parse_stringn(dst, sizeof(dst), result, sizeof(result)), 1);
+ pwtest_str_eq(result, "\x04\x05\x1f\x20\x01\x7f\x90");
+ strcpy(dst, "\"\\u03b2a\"");
+ pwtest_int_eq(spa_json_parse_stringn(dst, sizeof(dst), result, sizeof(result)), 1);
+ pwtest_str_eq(result, "\316\262a");
+ strcpy(dst, "\"\\u 03b2a \"");
+ pwtest_int_eq(spa_json_parse_stringn(dst, sizeof(dst), result, sizeof(result)), 1);
+ pwtest_str_eq(result, "u 03b2a ");
+
+ return PWTEST_PASS;
+}
+
+static void test_array(char *str, char **vals)
+{
+ struct spa_json it[2];
+ char val[256];
+ int i;
+
+ spa_json_init(&it[0], str, strlen(str));
+ if (spa_json_enter_array(&it[0], &it[1]) <= 0)
+ spa_json_init(&it[1], str, strlen(str));
+ for (i = 0; vals[i]; i++) {
+ pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0);
+ pwtest_str_eq(val, vals[i]);
+ }
+}
+
+PWTEST(json_array)
+{
+ test_array("FL,FR", (char *[]){ "FL", "FR", NULL });
+ test_array(" FL , FR ", (char *[]){ "FL", "FR", NULL });
+ test_array("[ FL , FR ]", (char *[]){ "FL", "FR", NULL });
+ test_array("[FL FR]", (char *[]){ "FL", "FR", NULL });
+ test_array("FL FR", (char *[]){ "FL", "FR", NULL });
+ test_array("[ FL FR ]", (char *[]){ "FL", "FR", NULL });
+
+ return PWTEST_PASS;
+}
+
+PWTEST(json_overflow)
+{
+ struct spa_json it[2];
+ char val[3];
+ const char *str = "[ F, FR, FRC ]";
+
+ spa_json_init(&it[0], str, strlen(str));
+ pwtest_int_gt(spa_json_enter_array(&it[0], &it[1]), 0);
+
+ pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0);
+ pwtest_str_eq(val, "F");
+ pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0);
+ pwtest_str_eq(val, "FR");
+ pwtest_int_lt(spa_json_get_string(&it[1], val, sizeof(val)), 0);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(json_float)
+{
+ struct {
+ const char *str;
+ double val;
+ } val[] = {
+ { "0.0", 0.0f },
+ { ".0", 0.0f },
+ { ".0E0", 0.0E0f },
+ { "1.0", 1.0f },
+ { "1.011", 1.011f },
+ { "176543.123456", 176543.123456f },
+ { "-176543.123456", -176543.123456f },
+ { "-5678.5432E10", -5678.5432E10f },
+ { "-5678.5432e10", -5678.5432e10f },
+ { "-5678.5432e-10", -5678.5432e-10f },
+ { "5678.5432e+10", 5678.5432e+10f },
+ { "00.000100", 00.000100f },
+ { "-0.000100", -0.000100f },
+ };
+ float v;
+ unsigned i;
+ char buf1[128], buf2[128], *b1 = buf1, *b2 = buf2;
+
+ pwtest_int_eq(spa_json_parse_float("", 0, &v), 0);
+
+ setlocale(LC_NUMERIC, "C");
+ for (i = 0; i < SPA_N_ELEMENTS(val); i++) {
+ pwtest_int_gt(spa_json_parse_float(val[i].str, strlen(val[i].str), &v), 0);
+ pwtest_double_eq(v, val[i].val);
+ }
+ setlocale(LC_NUMERIC, "fr_FR");
+ for (i = 0; i < SPA_N_ELEMENTS(val); i++) {
+ pwtest_int_gt(spa_json_parse_float(val[i].str, strlen(val[i].str), &v), 0);
+ pwtest_double_eq(v, val[i].val);
+ }
+ pwtest_ptr_eq(spa_json_format_float(buf1, sizeof(buf1), 0.0f), b1);
+ pwtest_str_eq(buf1, "0.000000");
+ pwtest_ptr_eq(spa_json_format_float(buf1, sizeof(buf1), NAN), b1);
+ pwtest_str_eq(buf1, "0.000000");
+ pwtest_ptr_eq(spa_json_format_float(buf1, sizeof(buf1), INFINITY), b1);
+ pwtest_ptr_eq(spa_json_format_float(buf2, sizeof(buf2), FLT_MAX), b2);
+ pwtest_str_eq(buf1, buf2);
+ pwtest_ptr_eq(spa_json_format_float(buf1, sizeof(buf1), -INFINITY), b1);
+ pwtest_ptr_eq(spa_json_format_float(buf2, sizeof(buf2), FLT_MIN), b2);
+ pwtest_str_eq(buf1, buf2);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(json_float_check)
+{
+ struct {
+ const char *str;
+ int res;
+ } val[] = {
+ { "0.0", 1 },
+ { ".0", 1 },
+ { "+.0E0", 1 },
+ { "-.0e0", 1 },
+
+ { "0,0", 0 },
+ { "0.0.5", 0 },
+ { "0x0", 0 },
+ { "0x0.0", 0 },
+ { "E10", 0 },
+ { "e20", 0 },
+ { " 0.0", 0 },
+ { "0.0 ", 0 },
+ { " 0.0 ", 0 },
+ };
+ unsigned i;
+ float v;
+
+ for (i = 0; i < SPA_N_ELEMENTS(val); i++) {
+ pwtest_int_eq(spa_json_parse_float(val[i].str, strlen(val[i].str), &v), val[i].res);
+ }
+ return PWTEST_PASS;
+}
+
+PWTEST(json_int)
+{
+ int v;
+ pwtest_int_eq(spa_json_parse_int("", 0, &v), 0);
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(spa_json)
+{
+ pwtest_add(json_abi, PWTEST_NOARG);
+ pwtest_add(json_parse, PWTEST_NOARG);
+ pwtest_add(json_encode, PWTEST_NOARG);
+ pwtest_add(json_array, PWTEST_NOARG);
+ pwtest_add(json_overflow, PWTEST_NOARG);
+ pwtest_add(json_float, PWTEST_NOARG);
+ pwtest_add(json_float_check, PWTEST_NOARG);
+ pwtest_add(json_int, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-spa-log.c b/test/test-spa-log.c
new file mode 100644
index 0000000..a90ed73
--- /dev/null
+++ b/test/test-spa-log.c
@@ -0,0 +1,213 @@
+/* Simple Plugin API
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/support/log.h>
+
+#include "pwtest.h"
+
+#define OTHER_ARGS const char *file, int line, const char *func, const char *fmt
+
+struct data {
+ bool invoked;
+ const char *func;
+ const char *msg;
+ const struct spa_log_topic *topic;
+};
+
+
+static void impl_log_log(void *object, enum spa_log_level level, OTHER_ARGS, ...) {
+ struct data *data = object;
+ *data = (struct data) {
+ .func = __func__,
+ .invoked = true,
+ .msg = fmt,
+ .topic = NULL,
+ };
+};
+
+static void impl_log_logv(void *object, enum spa_log_level level, OTHER_ARGS, va_list args) {
+ struct data *data = object;
+ *data = (struct data) {
+ .func = __func__,
+ .invoked = true,
+ .msg = fmt,
+ .topic = NULL,
+ };
+};
+
+static void impl_log_logt(void *object, enum spa_log_level level, const struct spa_log_topic *topic, OTHER_ARGS, ...) {
+ struct data *data = object;
+ *data = (struct data) {
+ .func = __func__,
+ .invoked = true,
+ .msg = fmt,
+ .topic = topic,
+ };
+};
+
+static void impl_log_logtv(void *object, enum spa_log_level level, const struct spa_log_topic *topic, OTHER_ARGS, va_list args) {
+ struct data *data = object;
+ *data = (struct data) {
+ .func = __func__,
+ .invoked = true,
+ .msg = fmt,
+ .topic = topic,
+ };
+};
+
+PWTEST(utils_log_logt)
+{
+ struct spa_log_methods impl_log = {
+ SPA_VERSION_LOG_METHODS,
+ .log = impl_log_log,
+ .logv = impl_log_logv,
+ .logt = impl_log_logt,
+ .logtv = impl_log_logtv,
+ };
+ struct spa_log log;
+ struct data data;
+ struct spa_log_topic topic = {
+ .version = 0,
+ .topic = "log topic",
+ .level = SPA_LOG_LEVEL_DEBUG,
+ };
+
+ log.level = SPA_LOG_LEVEL_DEBUG;
+ log.iface = SPA_INTERFACE_INIT(SPA_TYPE_INTERFACE_Log, 0, &impl_log, &data);
+
+ impl_log.version = 0;
+
+ /* impl_log is v0 so we expect the non-topic function to be called */
+ spa_log_debug(&log, "call v0");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.func, "impl_log_log");
+ pwtest_str_eq(data.msg, "call v0");
+ pwtest_ptr_null(data.topic);
+ data.invoked = false;
+
+ /* impl_log is v0 so we expect the topic to be ignored */
+ spa_logt_debug(&log, &topic, "call v0 logt");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.func, "impl_log_log");
+ pwtest_str_eq(data.msg, "call v0 logt");
+ pwtest_ptr_null(data.topic);
+ data.invoked = false;
+
+ impl_log.version = SPA_VERSION_LOG_METHODS;
+
+ /* impl_log is v1 so we expect logt to be called */
+ spa_log_debug(&log, "call v1");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.func, "impl_log_logt");
+ pwtest_str_eq(data.msg, "call v1");
+ pwtest_ptr_null(data.topic);
+ data.invoked = false;
+
+ /* impl_log is v1 so we expect the topic to be passed through */
+ spa_logt_debug(&log, &topic, "call v1 logt");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.func, "impl_log_logt");
+ pwtest_str_eq(data.msg, "call v1 logt");
+ pwtest_ptr_eq(data.topic, &topic);
+ data.invoked = false;
+
+ /* simulated:
+ * impl_log is v1 but we have an old caller that uses v0, this goes
+ * through to the non-topic log function */
+ spa_interface_call(&log.iface, struct spa_log_methods, log, 0,
+ SPA_LOG_LEVEL_DEBUG, "file", 123, "function", "call from v0");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.func, "impl_log_log");
+ pwtest_str_eq(data.msg, "call from v0");
+ pwtest_ptr_null(data.topic);
+ data.invoked = false;
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_log_logt_levels)
+{
+ struct spa_log_methods impl_log = {
+ SPA_VERSION_LOG_METHODS,
+ .log = impl_log_log,
+ .logv = impl_log_logv,
+ .logt = impl_log_logt,
+ .logtv = impl_log_logtv,
+ };
+ struct spa_log log;
+ struct data data;
+ struct spa_log_topic topic = {
+ .version = 0,
+ .topic = "log topic",
+ .level = SPA_LOG_LEVEL_INFO,
+ .has_custom_level = true,
+ };
+
+ log.level = SPA_LOG_LEVEL_DEBUG;
+ log.iface = SPA_INTERFACE_INIT(SPA_TYPE_INTERFACE_Log, 0, &impl_log, &data);
+
+ /* Topic is NULL for spa_log_*, so expect this to be invoked */
+ spa_log_debug(&log, "spa_log_debug");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.msg, "spa_log_debug");
+ pwtest_ptr_null(data.topic);
+ data.invoked = false;
+
+ spa_log_info(&log, "spa_log_info");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.msg, "spa_log_info");
+ pwtest_ptr_null(data.topic);
+ data.invoked = false;
+
+ spa_log_warn(&log, "spa_log_warn");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.msg, "spa_log_warn");
+ pwtest_ptr_null(data.topic);
+ data.invoked = false;
+
+ spa_logt_debug(&log, &topic, "spa_logt_debug");
+ pwtest_bool_false(data.invoked);
+ data.invoked = false;
+
+ spa_logt_info(&log, &topic, "spa_logt_info");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.msg, "spa_logt_info");
+ pwtest_ptr_eq(data.topic, &topic);
+ data.invoked = false;
+
+ spa_logt_warn(&log, &topic, "spa_logt_warn");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.msg, "spa_logt_warn");
+ pwtest_ptr_eq(data.topic, &topic);
+ data.invoked = false;
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(spa_log)
+{
+ pwtest_add(utils_log_logt, PWTEST_NOARG);
+ pwtest_add(utils_log_logt_levels, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-spa-node.c b/test/test-spa-node.c
new file mode 100644
index 0000000..196bd03
--- /dev/null
+++ b/test/test-spa-node.c
@@ -0,0 +1,251 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/defs.h>
+#include <spa/node/node.h>
+#include <spa/node/io.h>
+#include <spa/node/command.h>
+#include <spa/node/event.h>
+
+#include "pwtest.h"
+
+PWTEST(node_io_abi_sizes)
+{
+#if defined(__x86_64__) && defined(__LP64__)
+ pwtest_int_eq(sizeof(struct spa_io_buffers), 8U);
+ pwtest_int_eq(sizeof(struct spa_io_memory), 16U);
+ pwtest_int_eq(sizeof(struct spa_io_range), 16U);
+ pwtest_int_eq(sizeof(struct spa_io_clock), 160U);
+ pwtest_int_eq(sizeof(struct spa_io_latency), 24U);
+ pwtest_int_eq(sizeof(struct spa_io_sequence), 16U);
+ pwtest_int_eq(sizeof(struct spa_io_segment_bar), 64U);
+ pwtest_int_eq(sizeof(struct spa_io_segment_video), 80U);
+ pwtest_int_eq(sizeof(struct spa_io_segment), 184U);
+
+ pwtest_int_eq(sizeof(struct spa_io_position), 1688U);
+ pwtest_int_eq(sizeof(struct spa_io_rate_match), 48U);
+
+ spa_assert_se(sizeof(struct spa_node_info) == 48);
+ spa_assert_se(sizeof(struct spa_port_info) == 48);
+
+ spa_assert_se(sizeof(struct spa_result_node_error) == 8);
+ spa_assert_se(sizeof(struct spa_result_node_params) == 24);
+
+ return PWTEST_PASS;
+#else
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_buffers));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_memory));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_range));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_clock));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_latency));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_sequence));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_segment_bar));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_segment_video));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_segment));
+
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_position));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_io_rate_match));
+
+ fprintf(stderr, "%zd\n", sizeof(struct spa_node_info));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_port_info));
+
+ fprintf(stderr, "%zd\n", sizeof(struct spa_result_node_error));
+ fprintf(stderr, "%zd\n", sizeof(struct spa_result_node_params));
+
+ return PWTEST_SKIP;
+#endif
+
+}
+
+PWTEST(node_io_abi)
+{
+ /* io */
+ pwtest_int_eq(SPA_IO_Invalid, 0);
+ pwtest_int_eq(SPA_IO_Buffers, 1);
+ pwtest_int_eq(SPA_IO_Range, 2);
+ pwtest_int_eq(SPA_IO_Clock, 3);
+ pwtest_int_eq(SPA_IO_Latency, 4);
+ pwtest_int_eq(SPA_IO_Control, 5);
+ pwtest_int_eq(SPA_IO_Notify, 6);
+ pwtest_int_eq(SPA_IO_Position, 7);
+ pwtest_int_eq(SPA_IO_RateMatch, 8);
+ pwtest_int_eq(SPA_IO_Memory, 9);
+
+ /* position state */
+ pwtest_int_eq(SPA_IO_POSITION_STATE_STOPPED, 0);
+ pwtest_int_eq(SPA_IO_POSITION_STATE_STARTING, 1);
+ pwtest_int_eq(SPA_IO_POSITION_STATE_RUNNING, 2);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(node_command_abi)
+{
+ pwtest_int_eq(SPA_NODE_COMMAND_Suspend, 0);
+ pwtest_int_eq(SPA_NODE_COMMAND_Pause, 1);
+ pwtest_int_eq(SPA_NODE_COMMAND_Start, 2);
+ pwtest_int_eq(SPA_NODE_COMMAND_Enable, 3);
+ pwtest_int_eq(SPA_NODE_COMMAND_Disable, 4);
+ pwtest_int_eq(SPA_NODE_COMMAND_Flush, 5);
+ pwtest_int_eq(SPA_NODE_COMMAND_Drain, 6);
+ pwtest_int_eq(SPA_NODE_COMMAND_Marker, 7);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(node_event_abi)
+{
+ pwtest_int_eq(SPA_NODE_EVENT_Error, 0);
+ pwtest_int_eq(SPA_NODE_EVENT_Buffering, 1);
+ pwtest_int_eq(SPA_NODE_EVENT_RequestRefresh, 2);
+
+ return PWTEST_PASS;
+}
+
+#define TEST_FUNC(a,b,func, id) \
+do { \
+ off_t diff = SPA_PTRDIFF(&a.func, &a); \
+ a.func = b.func; \
+ pwtest_ptr_eq(diff, SPA_PTRDIFF(&b.func, &b)); \
+ pwtest_bool_true(diff == 0 || (diff-1)/sizeof(void*) == id); \
+} while(0)
+
+PWTEST(node_node_abi)
+{
+ struct spa_node_events e;
+ struct spa_node_callbacks c;
+ struct spa_node_methods m;
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct spa_node_info *info);
+ void (*port_info) (void *data,
+ enum spa_direction direction, uint32_t port,
+ const struct spa_port_info *info);
+ void (*result) (void *data, int seq, int res,
+ uint32_t type, const void *result);
+ void (*event) (void *data, const struct spa_event *event);
+ } events = { SPA_VERSION_NODE_EVENTS, };
+ struct {
+ uint32_t version;
+ int (*ready) (void *data, int state);
+ int (*reuse_buffer) (void *data,
+ uint32_t port_id,
+ uint32_t buffer_id);
+ int (*xrun) (void *data, uint64_t trigger, uint64_t delay,
+ struct spa_pod *info);
+ } callbacks = { SPA_VERSION_NODE_CALLBACKS, };
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data);
+ int (*set_callbacks) (void *object,
+ const struct spa_node_callbacks *callbacks,
+ void *data);
+ int (*sync) (void *object, int seq);
+ int (*enum_params) (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t max,
+ const struct spa_pod *filter);
+ int (*set_param) (void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ int (*set_io) (void *object,
+ uint32_t id, void *data, size_t size);
+ int (*send_command) (void *object, const struct spa_command *command);
+ int (*add_port) (void *object,
+ enum spa_direction direction, uint32_t port_id,
+ const struct spa_dict *props);
+ int (*remove_port) (void *object,
+ enum spa_direction direction, uint32_t port_id);
+ int (*port_enum_params) (void *object, int seq,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, uint32_t start, uint32_t max,
+ const struct spa_pod *filter);
+ int (*port_set_param) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ int (*port_use_buffers) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t flags,
+ struct spa_buffer **buffers,
+ uint32_t n_buffers);
+ int (*port_set_io) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id,
+ void *data, size_t size);
+ int (*port_reuse_buffer) (void *object, uint32_t port_id, uint32_t buffer_id);
+ int (*process) (void *object);
+ } methods = { SPA_VERSION_NODE_METHODS, 0 };
+
+ TEST_FUNC(e, events, version, 0);
+ TEST_FUNC(e, events, info, SPA_NODE_EVENT_INFO);
+ TEST_FUNC(e, events, port_info, SPA_NODE_EVENT_PORT_INFO);
+ TEST_FUNC(e, events, result, SPA_NODE_EVENT_RESULT);
+ TEST_FUNC(e, events, event, SPA_NODE_EVENT_EVENT);
+ pwtest_int_eq(SPA_NODE_EVENT_NUM, 4);
+ pwtest_int_eq(sizeof(e), sizeof(events));
+
+ TEST_FUNC(c, callbacks, version, 0);
+ TEST_FUNC(c, callbacks, ready, SPA_NODE_CALLBACK_READY);
+ TEST_FUNC(c, callbacks, reuse_buffer, SPA_NODE_CALLBACK_REUSE_BUFFER);
+ TEST_FUNC(c, callbacks, xrun, SPA_NODE_CALLBACK_XRUN);
+ pwtest_int_eq(SPA_NODE_CALLBACK_NUM, 3);
+ pwtest_int_eq(sizeof(c), sizeof(callbacks));
+
+ TEST_FUNC(m, methods, version, 0);
+ TEST_FUNC(m, methods, add_listener, SPA_NODE_METHOD_ADD_LISTENER);
+ TEST_FUNC(m, methods, set_callbacks, SPA_NODE_METHOD_SET_CALLBACKS);
+ TEST_FUNC(m, methods, sync, SPA_NODE_METHOD_SYNC);
+ TEST_FUNC(m, methods, enum_params, SPA_NODE_METHOD_ENUM_PARAMS);
+ TEST_FUNC(m, methods, set_param, SPA_NODE_METHOD_SET_PARAM);
+ TEST_FUNC(m, methods, set_io, SPA_NODE_METHOD_SET_IO);
+ TEST_FUNC(m, methods, send_command, SPA_NODE_METHOD_SEND_COMMAND);
+ TEST_FUNC(m, methods, add_port, SPA_NODE_METHOD_ADD_PORT);
+ TEST_FUNC(m, methods, remove_port, SPA_NODE_METHOD_REMOVE_PORT);
+ TEST_FUNC(m, methods, port_enum_params, SPA_NODE_METHOD_PORT_ENUM_PARAMS);
+ TEST_FUNC(m, methods, port_use_buffers, SPA_NODE_METHOD_PORT_USE_BUFFERS);
+ TEST_FUNC(m, methods, port_set_io, SPA_NODE_METHOD_PORT_SET_IO);
+ TEST_FUNC(m, methods, port_reuse_buffer, SPA_NODE_METHOD_PORT_REUSE_BUFFER);
+ TEST_FUNC(m, methods, process, SPA_NODE_METHOD_PROCESS);
+ pwtest_int_eq(SPA_NODE_METHOD_NUM, 15);
+ pwtest_int_eq(sizeof(m), sizeof(methods));
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(spa_node)
+{
+ pwtest_add(node_io_abi_sizes, PWTEST_NOARG);
+ pwtest_add(node_io_abi, PWTEST_NOARG);
+ pwtest_add(node_command_abi, PWTEST_NOARG);
+ pwtest_add(node_event_abi, PWTEST_NOARG);
+ pwtest_add(node_node_abi, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-spa-pod.c b/test/test-spa-pod.c
new file mode 100644
index 0000000..24c3303
--- /dev/null
+++ b/test/test-spa-pod.c
@@ -0,0 +1,1706 @@
+/* Simple Plugin API
+ * Copyright © 2019 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/pod/pod.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/command.h>
+#include <spa/pod/event.h>
+#include <spa/pod/iter.h>
+#include <spa/pod/parser.h>
+#include <spa/pod/vararg.h>
+#include <spa/debug/pod.h>
+#include <spa/param/format.h>
+#include <spa/param/video/raw.h>
+#include <spa/utils/string.h>
+
+#include "pwtest.h"
+
+PWTEST(pod_abi_sizes)
+{
+#if defined(__x86_64__) && defined(__LP64__)
+ spa_assert_se(sizeof(struct spa_pod) == 8);
+ spa_assert_se(sizeof(struct spa_pod_bool) == 16);
+ spa_assert_se(sizeof(struct spa_pod_id) == 16);
+ spa_assert_se(sizeof(struct spa_pod_int) == 16);
+ spa_assert_se(sizeof(struct spa_pod_long) == 16);
+ spa_assert_se(sizeof(struct spa_pod_float) == 16);
+ spa_assert_se(sizeof(struct spa_pod_double) == 16);
+ spa_assert_se(sizeof(struct spa_pod_string) == 8);
+ spa_assert_se(sizeof(struct spa_pod_bytes) == 8);
+ spa_assert_se(sizeof(struct spa_pod_rectangle) == 16);
+ spa_assert_se(sizeof(struct spa_pod_fraction) == 16);
+ spa_assert_se(sizeof(struct spa_pod_bitmap) == 8);
+ spa_assert_se(sizeof(struct spa_pod_array_body) == 8);
+ spa_assert_se(sizeof(struct spa_pod_array) == 16);
+
+ spa_assert_se(sizeof(struct spa_pod_choice_body) == 16);
+ spa_assert_se(sizeof(struct spa_pod_choice) == 24);
+ spa_assert_se(sizeof(struct spa_pod_struct) == 8);
+ spa_assert_se(sizeof(struct spa_pod_object_body) == 8);
+ spa_assert_se(sizeof(struct spa_pod_object) == 16);
+ spa_assert_se(sizeof(struct spa_pod_pointer_body) == 16);
+ spa_assert_se(sizeof(struct spa_pod_pointer) == 24);
+ spa_assert_se(sizeof(struct spa_pod_fd) == 16);
+ spa_assert_se(sizeof(struct spa_pod_prop) == 16);
+ spa_assert_se(sizeof(struct spa_pod_control) == 16);
+ spa_assert_se(sizeof(struct spa_pod_sequence_body) == 8);
+ spa_assert_se(sizeof(struct spa_pod_sequence) == 16);
+
+ /* builder */
+ spa_assert_se(sizeof(struct spa_pod_frame) == 24);
+ spa_assert_se(sizeof(struct spa_pod_builder_state) == 16);
+ spa_assert_se(sizeof(struct spa_pod_builder) == 48);
+
+ /* command */
+ spa_assert_se(sizeof(struct spa_command_body) == 8);
+ spa_assert_se(sizeof(struct spa_command) == 16);
+
+ /* event */
+ spa_assert_se(sizeof(struct spa_event_body) == 8);
+ spa_assert_se(sizeof(struct spa_event) == 16);
+
+ /* parser */
+ spa_assert_se(sizeof(struct spa_pod_parser_state) == 16);
+ spa_assert_se(sizeof(struct spa_pod_parser) == 32);
+
+ return PWTEST_PASS;
+#endif
+ return PWTEST_SKIP;
+}
+
+PWTEST(pod_abi)
+{
+ spa_assert_se(SPA_CHOICE_None == 0);
+ spa_assert_se(SPA_CHOICE_Range == 1);
+ spa_assert_se(SPA_CHOICE_Step == 2);
+ spa_assert_se(SPA_CHOICE_Enum == 3);
+ spa_assert_se(SPA_CHOICE_Flags == 4);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_init)
+{
+ {
+ struct spa_pod pod = SPA_POD_INIT(sizeof(int64_t), SPA_TYPE_Long);
+ int32_t val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == sizeof(int64_t) + 8);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == sizeof(int64_t));
+ spa_assert_se(SPA_POD_CONTENTS_SIZE(struct spa_pod, &pod) == sizeof(int64_t));
+ spa_assert_se(spa_pod_is_long(&pod));
+
+ pod = SPA_POD_INIT(sizeof(int32_t), SPA_TYPE_Int);
+ spa_assert_se(SPA_POD_SIZE(&pod) == sizeof(int32_t) + 8);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Int);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == sizeof(int32_t));
+ spa_assert_se(SPA_POD_CONTENTS_SIZE(struct spa_pod, &pod) == sizeof(int32_t));
+ spa_assert_se(spa_pod_is_int(&pod));
+
+ /** too small */
+ pod = SPA_POD_INIT(0, SPA_TYPE_Int);
+ spa_assert_se(!spa_pod_is_int(&pod));
+ spa_assert_se(spa_pod_get_int(&pod, &val) < 0);
+ }
+ {
+ struct spa_pod pod = SPA_POD_INIT_None();
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 8);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_None);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 0);
+ spa_assert_se(SPA_POD_CONTENTS_SIZE(struct spa_pod, &pod) == 0);
+ spa_assert_se(spa_pod_is_none(&pod));
+ }
+ {
+ struct spa_pod_bool pod = SPA_POD_INIT_Bool(true);
+ bool val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Bool);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_bool, &pod) == true);
+ spa_assert_se(spa_pod_is_bool(&pod.pod));
+ spa_assert_se(spa_pod_get_bool(&pod.pod, &val) == 0);
+ spa_assert_se(val == true);
+
+ pod = SPA_POD_INIT_Bool(false);
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Bool);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_bool, &pod) == false);
+ spa_assert_se(spa_pod_is_bool(&pod.pod));
+ spa_assert_se(spa_pod_get_bool(&pod.pod, &val) == 0);
+ spa_assert_se(val == false);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Bool);
+ spa_assert_se(!spa_pod_is_bool(&pod.pod));
+ spa_assert_se(spa_pod_get_bool(&pod.pod, &val) < 0);
+ }
+ {
+ struct spa_pod_id pod = SPA_POD_INIT_Id(SPA_TYPE_Int);
+ uint32_t val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Id);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_id, &pod) == SPA_TYPE_Int);
+ spa_assert_se(spa_pod_is_id(&pod.pod));
+ spa_assert_se(spa_pod_get_id(&pod.pod, &val) == 0);
+ spa_assert_se(val == SPA_TYPE_Int);
+
+ pod = SPA_POD_INIT_Id(SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Id);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_id, &pod) == SPA_TYPE_Long);
+ spa_assert_se(spa_pod_is_id(&pod.pod));
+ spa_assert_se(spa_pod_get_id(&pod.pod, &val) == 0);
+ spa_assert_se(val == SPA_TYPE_Long);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Id);
+ spa_assert_se(!spa_pod_is_id(&pod.pod));
+ spa_assert_se(spa_pod_get_id(&pod.pod, &val) < 0);
+ }
+ {
+ struct spa_pod_int pod = SPA_POD_INIT_Int(23);
+ int32_t val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Int);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_int, &pod) == 23);
+ spa_assert_se(spa_pod_is_int(&pod.pod));
+ spa_assert_se(spa_pod_get_int(&pod.pod, &val) == 0);
+ spa_assert_se(val == 23);
+
+ pod = SPA_POD_INIT_Int(-123);
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Int);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_int, &pod) == -123);
+ spa_assert_se(spa_pod_is_int(&pod.pod));
+ spa_assert_se(spa_pod_get_int(&pod.pod, &val) == 0);
+ spa_assert_se(val == -123);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Int);
+ spa_assert_se(!spa_pod_is_int(&pod.pod));
+ spa_assert_se(spa_pod_get_int(&pod.pod, &val) < 0);
+ }
+ {
+ struct spa_pod_long pod = SPA_POD_INIT_Long(-23);
+ int64_t val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 16);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 8);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_long, &pod) == -23);
+ spa_assert_se(spa_pod_is_long(&pod.pod));
+ spa_assert_se(spa_pod_get_long(&pod.pod, &val) == 0);
+ spa_assert_se(val == -23);
+
+ pod = SPA_POD_INIT_Long(123);
+ spa_assert_se(SPA_POD_SIZE(&pod) == 16);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 8);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_long, &pod) == 123);
+ spa_assert_se(spa_pod_is_long(&pod.pod));
+ spa_assert_se(spa_pod_get_long(&pod.pod, &val) == 0);
+ spa_assert_se(val == 123);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Long);
+ spa_assert_se(!spa_pod_is_long(&pod.pod));
+ spa_assert_se(spa_pod_get_long(&pod.pod, &val) < 0);
+ }
+ {
+ struct spa_pod_float pod = SPA_POD_INIT_Float(0.67f);
+ float val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Float);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_float, &pod) == 0.67f);
+ spa_assert_se(spa_pod_is_float(&pod.pod));
+ spa_assert_se(spa_pod_get_float(&pod.pod, &val) == 0);
+ spa_assert_se(val == 0.67f);
+
+ pod = SPA_POD_INIT_Float(-134.8f);
+ spa_assert_se(SPA_POD_SIZE(&pod) == 12);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Float);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 4);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_float, &pod) == -134.8f);
+ spa_assert_se(spa_pod_is_float(&pod.pod));
+ spa_assert_se(spa_pod_get_float(&pod.pod, &val) == 0);
+ spa_assert_se(val == -134.8f);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Float);
+ spa_assert_se(!spa_pod_is_float(&pod.pod));
+ spa_assert_se(spa_pod_get_float(&pod.pod, &val) < 0);
+ }
+ {
+ struct spa_pod_double pod = SPA_POD_INIT_Double(0.67);
+ double val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 16);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Double);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 8);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_double, &pod) == 0.67);
+ spa_assert_se(spa_pod_is_double(&pod.pod));
+ spa_assert_se(spa_pod_get_double(&pod.pod, &val) == 0);
+ spa_assert_se(val == 0.67);
+
+ pod = SPA_POD_INIT_Double(-134.8);
+ spa_assert_se(SPA_POD_SIZE(&pod) == 16);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Double);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 8);
+ spa_assert_se(SPA_POD_VALUE(struct spa_pod_double, &pod) == -134.8);
+ spa_assert_se(spa_pod_is_double(&pod.pod));
+ spa_assert_se(spa_pod_get_double(&pod.pod, &val) == 0);
+ spa_assert_se(val == -134.8);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Double);
+ spa_assert_se(!spa_pod_is_double(&pod.pod));
+ spa_assert_se(spa_pod_get_double(&pod.pod, &val) < 0);
+ }
+ {
+ struct {
+ struct spa_pod_string pod;
+ char str[9];
+ } pod;
+ char val[12];
+
+ pod.pod = SPA_POD_INIT_String(9);
+ strncpy(pod.str, "test", 9);
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 17);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_String);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 9);
+ spa_assert_se(spa_pod_is_string(&pod.pod.pod));
+ spa_assert_se(spa_pod_copy_string(&pod.pod.pod, sizeof(val), val) == 0);
+ spa_assert_se(spa_streq(pod.str, val));
+
+ pod.pod = SPA_POD_INIT_String(6);
+ memcpy(pod.str, "test123456789", 9);
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 14);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_String);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 6);
+ spa_assert_se(!spa_pod_is_string(&pod.pod.pod));
+ spa_assert_se(spa_pod_copy_string(&pod.pod.pod, sizeof(val), val) < 0);
+ }
+ {
+ struct spa_pod_rectangle pod = SPA_POD_INIT_Rectangle(SPA_RECTANGLE(320,240));
+ struct spa_rectangle val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 16);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Rectangle);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 8);
+ spa_assert_se(memcmp(&SPA_POD_VALUE(struct spa_pod_rectangle, &pod),
+ &SPA_RECTANGLE(320,240), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(spa_pod_is_rectangle(&pod.pod));
+ spa_assert_se(spa_pod_get_rectangle(&pod.pod, &val) == 0);
+ spa_assert_se(memcmp(&val, &SPA_RECTANGLE(320,240), sizeof(struct spa_rectangle)) == 0);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Rectangle);
+ spa_assert_se(!spa_pod_is_rectangle(&pod.pod));
+ spa_assert_se(spa_pod_get_rectangle(&pod.pod, &val) < 0);
+ }
+ {
+ struct spa_pod_fraction pod = SPA_POD_INIT_Fraction(SPA_FRACTION(25,1));
+ struct spa_fraction val;
+
+ spa_assert_se(SPA_POD_SIZE(&pod) == 16);
+ spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_Fraction);
+ spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 8);
+ spa_assert_se(memcmp(&SPA_POD_VALUE(struct spa_pod_fraction, &pod),
+ &SPA_FRACTION(25,1), sizeof(struct spa_fraction)) == 0);
+ spa_assert_se(spa_pod_is_fraction(&pod.pod));
+ spa_assert_se(spa_pod_get_fraction(&pod.pod, &val) == 0);
+ spa_assert_se(memcmp(&val, &SPA_FRACTION(25,1), sizeof(struct spa_fraction)) == 0);
+
+ pod.pod = SPA_POD_INIT(0, SPA_TYPE_Fraction);
+ spa_assert_se(!spa_pod_is_fraction(&pod.pod));
+ spa_assert_se(spa_pod_get_fraction(&pod.pod, &val) < 0);
+ }
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_build)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ struct spa_pod *array, *choice, *head, *pod, *it;
+ const struct spa_pod_prop *prop;
+ struct spa_pod_control *control;
+ int64_t longs[] = { 5, 7, 11, 13, 17 }, *al;
+ uint32_t i, len, yl, *ai;
+ union {
+ bool b;
+ uint32_t I;
+ int32_t i;
+ int64_t l;
+ float f;
+ double d;
+ const char *s;
+ const void *y;
+ const void *p;
+ int64_t h;
+ struct spa_rectangle R;
+ struct spa_fraction F;
+ } val;
+ struct spa_pod_frame f;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(b.data == buffer);
+ spa_assert_se(b.size == sizeof(buffer));
+ spa_assert_se(b.state.offset == 0);
+ spa_assert_se(b.state.flags == 0);
+
+ spa_assert_se(spa_pod_builder_none(&b) == 0);
+ spa_assert_se(b.state.offset == 8);
+ spa_assert_se(spa_pod_builder_bool(&b, true) == 0);
+ spa_assert_se(b.state.offset == 24);
+ spa_assert_se(spa_pod_builder_id(&b, SPA_TYPE_Object) == 0);
+ spa_assert_se(b.state.offset == 40);
+ spa_assert_se(spa_pod_builder_int(&b, 21) == 0);
+ spa_assert_se(b.state.offset == 56);
+ spa_assert_se(spa_pod_builder_float(&b, 0.8f) == 0);
+ spa_assert_se(b.state.offset == 72);
+ spa_assert_se(spa_pod_builder_double(&b, -1.56) == 0);
+ spa_assert_se(b.state.offset == 88);
+ spa_assert_se(spa_pod_builder_string(&b, "test") == 0);
+ spa_assert_se(b.state.offset == 104);
+ spa_assert_se(spa_pod_builder_bytes(&b, "PipeWire", 8) == 0);
+ spa_assert_se(b.state.offset == 120);
+ spa_assert_se(spa_pod_builder_pointer(&b, SPA_TYPE_Object, &b) == 0);
+ spa_assert_se(b.state.offset == 144);
+ spa_assert_se(spa_pod_builder_fd(&b, 4) == 0);
+ spa_assert_se(b.state.offset == 160);
+ spa_assert_se(spa_pod_builder_rectangle(&b, 320, 240) == 0);
+ spa_assert_se(b.state.offset == 176);
+ spa_assert_se(spa_pod_builder_fraction(&b, 25, 1) == 0);
+
+ spa_assert_se(b.state.offset == 192);
+ spa_assert_se(spa_pod_builder_push_array(&b, &f) == 0);
+ spa_assert_se(f.offset == 192);
+ spa_assert_se(b.state.flags == (SPA_POD_BUILDER_FLAG_BODY | SPA_POD_BUILDER_FLAG_FIRST));
+ spa_assert_se(b.state.offset == 200);
+ spa_assert_se(spa_pod_builder_int(&b, 1) == 0);
+ spa_assert_se(b.state.flags == SPA_POD_BUILDER_FLAG_BODY);
+ spa_assert_se(b.state.offset == 212);
+ spa_assert_se(spa_pod_builder_int(&b, 2) == 0);
+ spa_assert_se(b.state.offset == 216);
+ spa_assert_se(spa_pod_builder_int(&b, 3) == 0);
+ array = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(f.pod.size == 20);
+ spa_assert_se(array != NULL);
+ spa_assert_se(SPA_POD_BODY_SIZE(array) == 8 + 12);
+ spa_assert_se(b.state.flags == 0);
+
+ spa_assert_se(b.state.offset == 224);
+ spa_assert_se(spa_pod_builder_array(&b,
+ sizeof(int64_t), SPA_TYPE_Long,
+ SPA_N_ELEMENTS(longs), longs) == 0);
+ spa_assert_se(b.state.flags == 0);
+
+ spa_assert_se(b.state.offset == 280);
+ spa_assert_se(spa_pod_builder_push_choice(&b, &f, SPA_CHOICE_Enum, 0) == 0);
+ spa_assert_se(b.state.flags == (SPA_POD_BUILDER_FLAG_BODY | SPA_POD_BUILDER_FLAG_FIRST));
+ spa_assert_se(b.state.offset == 296);
+ spa_assert_se(spa_pod_builder_long(&b, 1) == 0);
+ spa_assert_se(b.state.flags == SPA_POD_BUILDER_FLAG_BODY);
+ spa_assert_se(b.state.offset == 312);
+ spa_assert_se(spa_pod_builder_long(&b, 2) == 0);
+ spa_assert_se(b.state.offset == 320);
+ spa_assert_se(spa_pod_builder_long(&b, 3) == 0);
+ choice = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(choice != NULL);
+ spa_assert_se(b.state.flags == 0);
+
+ spa_assert_se(b.state.offset == 328);
+ spa_assert_se(spa_pod_builder_push_struct(&b, &f) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 336);
+ spa_assert_se(spa_pod_builder_int(&b, 21) == 0);
+ spa_assert_se(b.state.offset == 352);
+ spa_assert_se(spa_pod_builder_float(&b, 0.8f) == 0);
+ spa_assert_se(b.state.offset == 368);
+ spa_assert_se(spa_pod_builder_double(&b, -1.56) == 0);
+ spa_assert_se(spa_pod_builder_pop(&b, &f) != NULL);
+
+ spa_assert_se(b.state.offset == 384);
+ spa_assert_se(spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, 0) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 400);
+ spa_assert_se(spa_pod_builder_prop(&b, 1, 0) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 408);
+ spa_assert_se(spa_pod_builder_int(&b, 21) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 424);
+ spa_assert_se(spa_pod_builder_prop(&b, 2, 0) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 432);
+ spa_assert_se(spa_pod_builder_long(&b, 42) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 448);
+ spa_assert_se(spa_pod_builder_prop(&b, 3, 0) == 0);
+ spa_assert_se(b.state.offset == 456);
+ spa_assert_se(spa_pod_builder_string(&b, "test123") == 0);
+ spa_assert_se(spa_pod_builder_pop(&b, &f) != NULL);
+ spa_assert_se(b.state.flags == 0);
+
+ spa_assert_se(b.state.offset == 472);
+ spa_assert_se(spa_pod_builder_push_sequence(&b, &f, 0) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 488);
+ spa_assert_se(spa_pod_builder_control(&b, 0, 0) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 496);
+ spa_assert_se(spa_pod_builder_float(&b, 0.667f) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 512);
+ spa_assert_se(spa_pod_builder_control(&b, 12, 0) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(b.state.offset == 520);
+ spa_assert_se(spa_pod_builder_double(&b, 1.22) == 0);
+ spa_assert_se(b.state.flags == 0);
+ spa_assert_se(spa_pod_builder_pop(&b, &f) != NULL);
+ spa_assert_se(b.state.flags == 0);
+
+ spa_assert_se(b.state.offset == 536);
+
+ len = b.state.offset;
+ pod = head = (struct spa_pod *)buffer;
+
+ spa_assert_se(spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_none(pod));
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_bool(pod));
+ spa_assert_se(spa_pod_get_bool(pod, &val.b) == 0);
+ spa_assert_se(val.b == true);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_id(pod));
+ spa_assert_se(spa_pod_get_id(pod, &val.I) == 0);
+ spa_assert_se(val.I == SPA_TYPE_Object);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_int(pod));
+ spa_assert_se(spa_pod_get_int(pod, &val.i) == 0);
+ spa_assert_se(val.i == 21);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_float(pod));
+ spa_assert_se(spa_pod_get_float(pod, &val.f) == 0);
+ spa_assert_se(val.f == 0.8f);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_double(pod));
+ spa_assert_se(spa_pod_get_double(pod, &val.d) == 0);
+ spa_assert_se(val.d == -1.56);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_string(pod));
+ spa_assert_se(spa_pod_get_string(pod, &val.s) == 0);
+ spa_assert_se(spa_streq(val.s, "test"));
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_bytes(pod));
+ spa_assert_se(spa_pod_get_bytes(pod, &val.y, &yl) == 0);
+ spa_assert_se(yl == 8);
+ spa_assert_se(memcmp(val.y, "PipeWire", yl) == 0);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_pointer(pod));
+ spa_assert_se(spa_pod_get_pointer(pod, &yl, &val.p) == 0);
+ spa_assert_se(yl == SPA_TYPE_Object);
+ spa_assert_se(val.p == &b);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_fd(pod));
+ spa_assert_se(spa_pod_get_fd(pod, &val.l) == 0);
+ spa_assert_se(val.l == 4);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_rectangle(pod));
+ spa_assert_se(spa_pod_get_rectangle(pod, &val.R) == 0);
+ spa_assert_se(memcmp(&val.R, &SPA_RECTANGLE(320,240), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_fraction(pod));
+ spa_assert_se(spa_pod_get_fraction(pod, &val.F) == 0);
+ spa_assert_se(memcmp(&val.F, &SPA_FRACTION(25,1), sizeof(struct spa_fraction)) == 0);
+
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_array(pod));
+ spa_assert_se(SPA_POD_ARRAY_VALUE_TYPE(pod) == SPA_TYPE_Int);
+ spa_assert_se(SPA_POD_ARRAY_VALUE_SIZE(pod) == sizeof(int32_t));
+ spa_assert_se(SPA_POD_ARRAY_N_VALUES(pod) == 3);
+ ai = SPA_POD_ARRAY_VALUES(pod);
+ spa_assert_se(ai != NULL);
+ spa_assert_se(SPA_POD_ARRAY_CHILD(pod)->type == SPA_TYPE_Int);
+ spa_assert_se(SPA_POD_ARRAY_CHILD(pod)->size == sizeof(int32_t));
+ spa_assert_se(ai[0] == 1);
+ spa_assert_se(ai[1] == 2);
+ spa_assert_se(ai[2] == 3);
+ i = 1;
+ SPA_POD_ARRAY_FOREACH((struct spa_pod_array*)pod, ai) {
+ spa_assert_se(*ai == i);
+ i++;
+ }
+
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_array(pod));
+ spa_assert_se(SPA_POD_ARRAY_VALUE_TYPE(pod) == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_ARRAY_VALUE_SIZE(pod) == sizeof(int64_t));
+ spa_assert_se(SPA_POD_ARRAY_N_VALUES(pod) == SPA_N_ELEMENTS(longs));
+ al = SPA_POD_ARRAY_VALUES(pod);
+ spa_assert_se(al != NULL);
+ spa_assert_se(SPA_POD_ARRAY_CHILD(pod)->type == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_ARRAY_CHILD(pod)->size == sizeof(int64_t));
+ for (i = 0; i < SPA_N_ELEMENTS(longs); i++)
+ spa_assert_se(al[i] == longs[i]);
+ i = 0;
+ SPA_POD_ARRAY_FOREACH((struct spa_pod_array*)pod, al) {
+ spa_assert_se(*al == longs[i++]);
+ }
+
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_choice(pod));
+ spa_assert_se(SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_Enum);
+ spa_assert_se(SPA_POD_CHOICE_FLAGS(pod) == 0);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_TYPE(pod) == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_SIZE(pod) == sizeof(int64_t));
+ spa_assert_se(SPA_POD_CHOICE_N_VALUES(pod) == 3);
+ al = SPA_POD_CHOICE_VALUES(pod);
+ spa_assert_se(al != NULL);
+ spa_assert_se(SPA_POD_CHOICE_CHILD(pod)->type == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_CHOICE_CHILD(pod)->size == sizeof(int64_t));
+ spa_assert_se(al[0] == 1);
+ spa_assert_se(al[1] == 2);
+ spa_assert_se(al[2] == 3);
+ i = 1;
+ SPA_POD_CHOICE_FOREACH((struct spa_pod_choice*)pod, al) {
+ spa_assert_se(*al == i);
+ i++;
+ }
+
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_struct(pod));
+ i = 0;
+ SPA_POD_STRUCT_FOREACH(pod, it) {
+ switch (i++) {
+ case 0:
+ spa_assert_se(spa_pod_is_int(it));
+ spa_assert_se(spa_pod_get_int(it, &val.i) == 0 && val.i == 21);
+ break;
+ case 1:
+ spa_assert_se(spa_pod_is_float(it));
+ spa_assert_se(spa_pod_get_float(it, &val.f) == 0 && val.f == 0.8f);
+ break;
+ case 2:
+ spa_assert_se(spa_pod_is_double(it));
+ spa_assert_se(spa_pod_get_double(it, &val.d) == 0 && val.d == -1.56);
+ break;
+ default:
+ spa_assert_not_reached();
+ break;
+ }
+ }
+
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_object(pod));
+ spa_assert_se(spa_pod_is_object_type(pod, SPA_TYPE_OBJECT_Props));
+ spa_assert_se(spa_pod_is_object_id(pod, 0));
+ i = 0;
+ SPA_POD_OBJECT_FOREACH((const struct spa_pod_object*)pod, prop) {
+ switch (i++) {
+ case 0:
+ spa_assert_se(prop->key == 1);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_int(&prop->value, &val.i) == 0 && val.i == 21);
+ break;
+ case 1:
+ spa_assert_se(prop->key == 2);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 24);
+ spa_assert_se(spa_pod_get_long(&prop->value, &val.l) == 0 && val.l == 42);
+ break;
+ case 2:
+ spa_assert_se(prop->key == 3);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 24);
+ spa_assert_se(spa_pod_get_string(&prop->value, &val.s) == 0 &&
+ spa_streq(val.s, "test123"));
+ break;
+ default:
+ spa_assert_not_reached();
+ break;
+ }
+ }
+ prop = spa_pod_find_prop(pod, NULL, 3);
+ spa_assert_se(prop != NULL);
+ spa_assert_se(prop->key == 3);
+ spa_assert_se(spa_pod_get_string(&prop->value, &val.s) == 0 &&
+ spa_streq(val.s, "test123"));
+ prop = spa_pod_find_prop(pod, prop, 1);
+ spa_assert_se(prop != NULL);
+ spa_assert_se(prop->key == 1);
+ spa_assert_se(spa_pod_get_int(&prop->value, &val.i) == 0 && val.i == 21);
+ prop = spa_pod_find_prop(pod, prop, 2);
+ spa_assert_se(prop != NULL);
+ spa_assert_se(prop->key == 2);
+ spa_assert_se(spa_pod_get_long(&prop->value, &val.l) == 0 && val.l == 42);
+ prop = spa_pod_find_prop(pod, prop, 5);
+ spa_assert_se(prop == NULL);
+
+ prop = spa_pod_find_prop(pod, NULL, 3);
+ spa_assert_se(prop != NULL);
+ spa_assert_se(prop->key == 3);
+ spa_assert_se(spa_pod_get_string(&prop->value, &val.s) == 0 &&
+ spa_streq(val.s, "test123"));
+
+ spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod));
+ spa_assert_se(spa_pod_is_sequence(pod));
+
+ i = 0;
+ SPA_POD_SEQUENCE_FOREACH((const struct spa_pod_sequence*)pod, control) {
+ switch (i++) {
+ case 0:
+ spa_assert_se(control->offset == 0);
+ spa_assert_se(SPA_POD_CONTROL_SIZE(control) == 20);
+ spa_assert_se(spa_pod_get_float(&control->value, &val.f) == 0 && val.f == 0.667f);
+ break;
+ case 1:
+ spa_assert_se(control->offset == 12);
+ spa_assert_se(SPA_POD_CONTROL_SIZE(control) == 24);
+ spa_assert_se(spa_pod_get_double(&control->value, &val.d) == 0 && val.d == 1.22);
+ break;
+ default:
+ spa_assert_not_reached();
+ break;
+ }
+ }
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_empty)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ struct spa_pod *array, *a2, *choice, *ch2;
+ struct spa_pod_frame f;
+ uint32_t n_vals, ch;
+
+ memset(buffer, 0xab, sizeof(buffer));
+
+ /* create empty arrays */
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(spa_pod_builder_push_array(&b, &f) == 0);
+ spa_assert_se(spa_pod_builder_child(&b, sizeof(uint32_t), SPA_TYPE_Id) == 0);
+ array = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(array != NULL);
+ spa_debug_mem(0, array, 16);
+ spa_assert_se(spa_pod_is_array(array));
+ a2 = spa_pod_get_array(array, &n_vals);
+ spa_assert_se(a2 != NULL);
+ spa_assert_se(n_vals == 0);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(spa_pod_builder_push_array(&b, &f) == 0);
+ array = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(array != NULL);
+ spa_assert_se(spa_pod_is_array(array));
+ a2 = spa_pod_get_array(array, &n_vals);
+ spa_assert_se(a2 != NULL);
+ spa_assert_se(n_vals == 0);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(spa_pod_builder_push_array(&b, &f) == 0);
+ spa_assert_se(spa_pod_builder_none(&b) == 0);
+ array = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(array != NULL);
+ spa_assert_se(spa_pod_is_array(array));
+ a2 = spa_pod_get_array(array, &n_vals);
+ spa_assert_se(a2 != NULL);
+ spa_assert_se(n_vals == 0);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(spa_pod_builder_array(&b, 4, SPA_TYPE_Id, 0, NULL) == 0);
+ array = (struct spa_pod*)buffer;
+ spa_assert_se(spa_pod_is_array(array));
+ a2 = spa_pod_get_array(array, &n_vals);
+ spa_assert_se(a2 != NULL);
+ spa_assert_se(n_vals == 0);
+
+ /* create empty choice */
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(spa_pod_builder_push_choice(&b, &f, 0, 0) == 0);
+ spa_assert_se(spa_pod_builder_child(&b, sizeof(uint32_t), SPA_TYPE_Id) == 0);
+ choice = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(choice != NULL);
+ spa_debug_mem(0, choice, 32);
+ spa_assert_se(spa_pod_is_choice(choice));
+ ch2 = spa_pod_get_values(choice, &n_vals, &ch);
+ spa_assert_se(ch2 != NULL);
+ spa_assert_se(n_vals == 0);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(spa_pod_builder_push_choice(&b, &f, 0, 0) == 0);
+ choice = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(choice != NULL);
+ spa_assert_se(spa_pod_is_choice(choice));
+ ch2 = spa_pod_get_values(choice, &n_vals, &ch);
+ spa_assert_se(ch2 != NULL);
+ spa_assert_se(n_vals == 0);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_assert_se(spa_pod_builder_push_choice(&b, &f, 0, 0) == 0);
+ spa_assert_se(spa_pod_builder_none(&b) == 0);
+ choice = spa_pod_builder_pop(&b, &f);
+ spa_assert_se(choice != NULL);
+ spa_assert_se(spa_pod_is_choice(choice));
+ ch2 = spa_pod_get_values(choice, &n_vals, &ch);
+ spa_assert_se(ch2 != NULL);
+ spa_assert_se(n_vals == 0);
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_varargs)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ struct spa_pod *pod;
+ struct spa_pod_prop *prop;
+ uint32_t i, *aI;
+ union {
+ bool b;
+ uint32_t I;
+ int32_t i;
+ int64_t l;
+ float f;
+ double d;
+ const char *s;
+ const void *y;
+ const void *p;
+ int64_t h;
+ struct spa_rectangle R;
+ struct spa_fraction F;
+ } val;
+ uint32_t media_type, media_subtype, format;
+ int32_t views;
+ struct spa_rectangle *aR, size;
+ struct spa_fraction *aF, framerate;
+ struct spa_pod *Vformat, *Vsize, *Vframerate;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ pod = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Format, 0,
+ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
+ SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3,
+ SPA_VIDEO_FORMAT_I420,
+ SPA_VIDEO_FORMAT_I420,
+ SPA_VIDEO_FORMAT_YUY2),
+ SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(320,242),
+ &SPA_RECTANGLE(1,1),
+ &SPA_RECTANGLE(INT32_MAX,INT32_MAX)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
+ &SPA_FRACTION(25,1),
+ &SPA_FRACTION(0,1),
+ &SPA_FRACTION(INT32_MAX,1)));
+
+ i = 0;
+ SPA_POD_OBJECT_FOREACH((const struct spa_pod_object*)pod, prop) {
+ switch (i++) {
+ case 0:
+ spa_assert_se(prop->key == SPA_FORMAT_mediaType);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_id(&prop->value, &val.I) == 0 && val.I == SPA_MEDIA_TYPE_video);
+ break;
+ case 1:
+ spa_assert_se(prop->key == SPA_FORMAT_mediaSubtype);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_id(&prop->value, &val.I) == 0 && val.I == SPA_MEDIA_SUBTYPE_raw);
+ break;
+ case 2:
+ spa_assert_se(prop->key == SPA_FORMAT_VIDEO_format);
+ spa_assert_se(spa_pod_is_choice(&prop->value));
+ spa_assert_se(SPA_POD_CHOICE_TYPE(&prop->value) == SPA_CHOICE_Enum);
+ spa_assert_se(SPA_POD_CHOICE_N_VALUES(&prop->value) == 3);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_TYPE(&prop->value) == SPA_TYPE_Id);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_SIZE(&prop->value) == sizeof(uint32_t));
+ aI = SPA_POD_CHOICE_VALUES(&prop->value);
+ spa_assert_se(aI != NULL);
+ spa_assert_se(aI[0] == SPA_VIDEO_FORMAT_I420);
+ spa_assert_se(aI[1] == SPA_VIDEO_FORMAT_I420);
+ spa_assert_se(aI[2] == SPA_VIDEO_FORMAT_YUY2);
+ break;
+ case 3:
+ spa_assert_se(prop->key == SPA_FORMAT_VIDEO_size);
+ spa_assert_se(spa_pod_is_choice(&prop->value));
+ spa_assert_se(SPA_POD_CHOICE_TYPE(&prop->value) == SPA_CHOICE_Range);
+ spa_assert_se(SPA_POD_CHOICE_N_VALUES(&prop->value) == 3);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_TYPE(&prop->value) == SPA_TYPE_Rectangle);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_SIZE(&prop->value) == sizeof(struct spa_rectangle));
+ aR = SPA_POD_CHOICE_VALUES(&prop->value);
+ spa_assert_se(aR != NULL);
+ spa_assert_se(memcmp(&aR[0], &SPA_RECTANGLE(320,242), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(memcmp(&aR[1], &SPA_RECTANGLE(1,1), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(memcmp(&aR[2], &SPA_RECTANGLE(INT32_MAX,INT32_MAX), sizeof(struct spa_rectangle)) == 0);
+ break;
+ case 4:
+ spa_assert_se(prop->key == SPA_FORMAT_VIDEO_framerate);
+ spa_assert_se(spa_pod_is_choice(&prop->value));
+ spa_assert_se(SPA_POD_CHOICE_TYPE(&prop->value) == SPA_CHOICE_Range);
+ spa_assert_se(SPA_POD_CHOICE_N_VALUES(&prop->value) == 3);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_TYPE(&prop->value) == SPA_TYPE_Fraction);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_SIZE(&prop->value) == sizeof(struct spa_fraction));
+ aF = SPA_POD_CHOICE_VALUES(&prop->value);
+ spa_assert_se(aF != NULL);
+ spa_assert_se(memcmp(&aF[0], &SPA_FRACTION(25,1), sizeof(struct spa_fraction)) == 0);
+ spa_assert_se(memcmp(&aF[1], &SPA_FRACTION(0,1), sizeof(struct spa_fraction)) == 0);
+ spa_assert_se(memcmp(&aF[2], &SPA_FRACTION(INT32_MAX,1), sizeof(struct spa_fraction)) == 0);
+ break;
+ default:
+ spa_assert_not_reached();
+ break;
+ }
+ }
+
+ spa_assert_se(spa_pod_parse_object(pod,
+ SPA_TYPE_OBJECT_Format, NULL,
+ SPA_FORMAT_mediaType, SPA_POD_Id(&media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(&media_subtype),
+ SPA_FORMAT_VIDEO_format, SPA_POD_PodChoice(&Vformat),
+ SPA_FORMAT_VIDEO_size, SPA_POD_PodChoice(&Vsize),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_PodChoice(&Vframerate)) == 5);
+
+ spa_assert_se(media_type == SPA_MEDIA_TYPE_video);
+ spa_assert_se(media_subtype == SPA_MEDIA_SUBTYPE_raw);
+
+ spa_assert_se(spa_pod_is_choice(Vformat));
+ spa_assert_se(SPA_POD_CHOICE_TYPE(Vformat) == SPA_CHOICE_Enum);
+ spa_assert_se(SPA_POD_CHOICE_N_VALUES(Vformat) == 3);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_TYPE(Vformat) == SPA_TYPE_Id);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_SIZE(Vformat) == sizeof(uint32_t));
+ aI = SPA_POD_CHOICE_VALUES(Vformat);
+ spa_assert_se(aI != NULL);
+ spa_assert_se(aI[0] == SPA_VIDEO_FORMAT_I420);
+ spa_assert_se(aI[1] == SPA_VIDEO_FORMAT_I420);
+ spa_assert_se(aI[2] == SPA_VIDEO_FORMAT_YUY2);
+
+ spa_assert_se(spa_pod_is_choice(Vsize));
+ spa_assert_se(SPA_POD_CHOICE_TYPE(Vsize) == SPA_CHOICE_Range);
+ spa_assert_se(SPA_POD_CHOICE_N_VALUES(Vsize) == 3);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_TYPE(Vsize) == SPA_TYPE_Rectangle);
+ spa_assert_se(SPA_POD_CHOICE_VALUE_SIZE(Vsize) == sizeof(struct spa_rectangle));
+ aR = SPA_POD_CHOICE_VALUES(Vsize);
+ spa_assert_se(aR != NULL);
+ spa_assert_se(memcmp(&aR[0], &SPA_RECTANGLE(320,242), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(memcmp(&aR[1], &SPA_RECTANGLE(1,1), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(memcmp(&aR[2], &SPA_RECTANGLE(INT32_MAX,INT32_MAX), sizeof(struct spa_rectangle)) == 0);
+
+ spa_assert_se(spa_pod_is_choice(Vframerate));
+
+ spa_assert_se(spa_pod_parse_object(pod,
+ SPA_TYPE_OBJECT_Format, NULL,
+ SPA_FORMAT_mediaType, SPA_POD_Id(&media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(&media_subtype),
+ SPA_FORMAT_VIDEO_views, SPA_POD_Int(&views),
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(&format),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerate)) == -ESRCH);
+
+ spa_assert_se(spa_pod_parse_object(pod,
+ SPA_TYPE_OBJECT_Format, NULL,
+ SPA_FORMAT_mediaType, SPA_POD_Id(&media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(&media_subtype),
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(&format),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerate)) == -EPROTO);
+
+ spa_debug_pod(0, NULL, pod);
+ spa_pod_fixate(pod);
+
+ spa_assert_se(spa_pod_parse_object(pod,
+ SPA_TYPE_OBJECT_Format, NULL,
+ SPA_FORMAT_mediaType, SPA_POD_Id(&media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(&media_subtype),
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(&format),
+ SPA_FORMAT_VIDEO_views, SPA_POD_OPT_Int(&views),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerate)) == 5);
+
+ spa_assert_se(media_type == SPA_MEDIA_TYPE_video);
+ spa_assert_se(media_subtype == SPA_MEDIA_SUBTYPE_raw);
+ spa_assert_se(format == SPA_VIDEO_FORMAT_I420);
+ spa_assert_se(memcmp(&size, &SPA_RECTANGLE(320,242), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(memcmp(&framerate, &SPA_FRACTION(25,1), sizeof(struct spa_fraction)) == 0);
+
+ spa_debug_pod(0, NULL, pod);
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_varargs2)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ struct spa_pod *pod;
+ struct spa_pod_prop *prop;
+ uint32_t i, j;
+ struct {
+ bool b;
+ uint32_t I;
+ int32_t i;
+ int64_t l;
+ float f;
+ double d;
+ const char *s;
+ uint32_t yl;
+ const void *y;
+ uint32_t ptype;
+ const void *p;
+ uint32_t asize, atype, anvals;
+ const void *a;
+ int64_t h;
+ struct spa_rectangle R;
+ struct spa_fraction F;
+ struct spa_pod *P;
+ } val;
+ uint8_t bytes[] = { 0x56, 0x00, 0x12, 0xf3, 0xba };
+ int64_t longs[] = { 1002, 5383, 28944, 1237748 }, *al;
+ struct spa_pod_int pi = SPA_POD_INIT_Int(77);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ pod = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Props, 0,
+ 1, SPA_POD_Bool(true),
+ 2, SPA_POD_Id(SPA_TYPE_Id),
+ 3, SPA_POD_Int(3),
+ 4, SPA_POD_Long(4LL),
+ 5, SPA_POD_Float(0.453f),
+ 6, SPA_POD_Double(0.871),
+ 7, SPA_POD_String("test"),
+ 8, SPA_POD_Bytes(bytes, sizeof(bytes)),
+ 9, SPA_POD_Rectangle(&SPA_RECTANGLE(3,4)),
+ 10, SPA_POD_Fraction(&SPA_FRACTION(24,1)),
+ 11, SPA_POD_Array(sizeof(int64_t), SPA_TYPE_Long, SPA_N_ELEMENTS(longs), longs),
+ 12, SPA_POD_Pointer(SPA_TYPE_Object, &b),
+ 13, SPA_POD_Fd(3),
+ 14, SPA_POD_Pod(&pi));
+
+ spa_debug_pod(0, NULL, pod);
+
+ i = 0;
+ SPA_POD_OBJECT_FOREACH((const struct spa_pod_object*)pod, prop) {
+ switch (i++) {
+ case 0:
+ spa_assert_se(prop->key == 1);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_bool(&prop->value, &val.b) == 0 && val.b == true);
+ break;
+ case 1:
+ spa_assert_se(prop->key == 2);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_id(&prop->value, &val.I) == 0 && val.I == SPA_TYPE_Id);
+ break;
+ case 2:
+ spa_assert_se(prop->key == 3);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_int(&prop->value, &val.i) == 0 && val.i == 3);
+ break;
+ case 3:
+ spa_assert_se(prop->key == 4);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 24);
+ spa_assert_se(spa_pod_get_long(&prop->value, &val.l) == 0 && val.l == 4);
+ break;
+ case 4:
+ spa_assert_se(prop->key == 5);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_float(&prop->value, &val.f) == 0 && val.f == 0.453f);
+ break;
+ case 5:
+ spa_assert_se(prop->key == 6);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 24);
+ spa_assert_se(spa_pod_get_double(&prop->value, &val.d) == 0 && val.d == 0.871);
+ break;
+ case 6:
+ spa_assert_se(prop->key == 7);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 21);
+ spa_assert_se(spa_pod_get_string(&prop->value, &val.s) == 0);
+ spa_assert_se(spa_streq(val.s, "test"));
+ break;
+ case 7:
+ spa_assert_se(prop->key == 8);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 21);
+ spa_assert_se(spa_pod_get_bytes(&prop->value, &val.y, &val.yl) == 0);
+ spa_assert_se(val.yl == sizeof(bytes));
+ spa_assert_se(memcmp(val.y, bytes, val.yl) == 0);
+ break;
+ case 8:
+ spa_assert_se(prop->key == 9);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 24);
+ spa_assert_se(spa_pod_get_rectangle(&prop->value, &val.R) == 0);
+ spa_assert_se(memcmp(&val.R, &SPA_RECTANGLE(3,4), sizeof(struct spa_rectangle)) == 0);
+ break;
+ case 9:
+ spa_assert_se(prop->key == 10);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 24);
+ spa_assert_se(spa_pod_get_fraction(&prop->value, &val.F) == 0);
+ spa_assert_se(memcmp(&val.F, &SPA_FRACTION(24,1), sizeof(struct spa_fraction)) == 0);
+ break;
+ case 10:
+ spa_assert_se(prop->key == 11);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 56);
+ spa_assert_se(spa_pod_is_array(&prop->value));
+ spa_assert_se(SPA_POD_ARRAY_VALUE_TYPE(&prop->value) == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_ARRAY_VALUE_SIZE(&prop->value) == sizeof(int64_t));
+ spa_assert_se(SPA_POD_ARRAY_N_VALUES(&prop->value) == SPA_N_ELEMENTS(longs));
+ al = SPA_POD_ARRAY_VALUES(&prop->value);
+ spa_assert_se(al != NULL);
+ spa_assert_se(SPA_POD_ARRAY_CHILD(&prop->value)->type == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_ARRAY_CHILD(&prop->value)->size == sizeof(int64_t));
+ for (j = 0; j < SPA_N_ELEMENTS(longs); j++)
+ spa_assert_se(al[j] == longs[j]);
+ break;
+ case 11:
+ spa_assert_se(prop->key == 12);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == (sizeof(struct spa_pod_prop) +
+ sizeof(struct spa_pod_pointer_body)));
+ spa_assert_se(spa_pod_get_pointer(&prop->value, &val.ptype, &val.p) == 0);
+ spa_assert_se(val.ptype == SPA_TYPE_Object);
+ spa_assert_se(val.p == &b);
+ break;
+ case 12:
+ spa_assert_se(prop->key == 13);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 24);
+ spa_assert_se(spa_pod_get_fd(&prop->value, &val.h) == 0);
+ spa_assert_se(val.h == 3);
+ break;
+ case 13:
+ spa_assert_se(prop->key == 14);
+ spa_assert_se(SPA_POD_PROP_SIZE(prop) == 20);
+ spa_assert_se(spa_pod_get_int(&prop->value, &val.i) == 0);
+ spa_assert_se(val.i == 77);
+ break;
+ default:
+ spa_assert_not_reached();
+ break;
+ }
+ }
+ spa_assert_se(spa_pod_parse_object(pod, SPA_TYPE_OBJECT_Format, NULL) == -EPROTO);
+ spa_assert_se(spa_pod_parse_object(pod, SPA_TYPE_OBJECT_Props, NULL) == 0);
+
+ spa_zero(val);
+ spa_assert_se(spa_pod_parse_object(pod,
+ SPA_TYPE_OBJECT_Props, NULL,
+ 1, SPA_POD_Bool(&val.b),
+ 2, SPA_POD_Id(&val.I),
+ 3, SPA_POD_Int(&val.i),
+ 4, SPA_POD_Long(&val.l),
+ 5, SPA_POD_Float(&val.f),
+ 6, SPA_POD_Double(&val.d),
+ 7, SPA_POD_String(&val.s),
+ 8, SPA_POD_Bytes(&val.y, &val.yl),
+ 9, SPA_POD_Rectangle(&val.R),
+ 10, SPA_POD_Fraction(&val.F),
+ 11, SPA_POD_Array(&val.asize, &val.atype, &val.anvals, &val.a),
+ 12, SPA_POD_Pointer(&val.ptype, &val.p),
+ 13, SPA_POD_Fd(&val.h),
+ 14, SPA_POD_Pod(&val.P)) == 14);
+
+ spa_assert_se(val.b == true);
+ spa_assert_se(val.I == SPA_TYPE_Id);
+ spa_assert_se(val.i == 3);
+ spa_assert_se(val.l == 4);
+ spa_assert_se(val.f == 0.453f);
+ spa_assert_se(val.d == 0.871);
+ spa_assert_se(spa_streq(val.s, "test"));
+ spa_assert_se(val.yl == sizeof(bytes));
+ spa_assert_se(memcmp(val.y, bytes, sizeof(bytes)) == 0);
+ spa_assert_se(memcmp(&val.R, &SPA_RECTANGLE(3, 4), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(memcmp(&val.F, &SPA_FRACTION(24, 1), sizeof(struct spa_fraction)) == 0);
+ spa_assert_se(val.asize == sizeof(int64_t));
+ spa_assert_se(val.atype == SPA_TYPE_Long);
+ spa_assert_se(val.anvals == SPA_N_ELEMENTS(longs));
+ spa_assert_se(memcmp(val.a, longs, val.anvals * val.asize) == 0);
+ spa_assert_se(val.ptype == SPA_TYPE_Object);
+ spa_assert_se(val.p == &b);
+ spa_assert_se(val.h == 3);
+ spa_assert_se(memcmp(val.P, &pi, sizeof(pi)) == 0);
+
+ spa_zero(val);
+ spa_assert_se(spa_pod_parse_object(pod,
+ SPA_TYPE_OBJECT_Props, NULL,
+ 0, SPA_POD_OPT_Bool(&val.b),
+ 0, SPA_POD_OPT_Id(&val.I),
+ 0, SPA_POD_OPT_Int(&val.i),
+ 0, SPA_POD_OPT_Long(&val.l),
+ 0, SPA_POD_OPT_Float(&val.f),
+ 0, SPA_POD_OPT_Double(&val.d),
+ 0, SPA_POD_OPT_String(&val.s),
+ 0, SPA_POD_OPT_Bytes(&val.y, &val.yl),
+ 0, SPA_POD_OPT_Rectangle(&val.R),
+ 0, SPA_POD_OPT_Fraction(&val.F),
+ 0, SPA_POD_OPT_Array(&val.asize, &val.atype, &val.anvals, &val.a),
+ 0, SPA_POD_OPT_Pointer(&val.ptype, &val.p),
+ 0, SPA_POD_OPT_Fd(&val.h),
+ 0, SPA_POD_OPT_Pod(&val.P)) == 0);
+
+ for (i = 1; i < 15; i++) {
+ spa_zero(val);
+ spa_assert_se(spa_pod_parse_object(pod,
+ SPA_TYPE_OBJECT_Props, NULL,
+ i, SPA_POD_OPT_Bool(&val.b),
+ i, SPA_POD_OPT_Id(&val.I),
+ i, SPA_POD_OPT_Int(&val.i),
+ i, SPA_POD_OPT_Long(&val.l),
+ i, SPA_POD_OPT_Float(&val.f),
+ i, SPA_POD_OPT_Double(&val.d),
+ i, SPA_POD_OPT_String(&val.s),
+ i, SPA_POD_OPT_Bytes(&val.y, &val.yl),
+ i, SPA_POD_OPT_Rectangle(&val.R),
+ i, SPA_POD_OPT_Fraction(&val.F),
+ i, SPA_POD_OPT_Array(&val.asize, &val.atype, &val.anvals, &val.a),
+ i, SPA_POD_OPT_Pointer(&val.ptype, &val.p),
+ i, SPA_POD_OPT_Fd(&val.h),
+ i, SPA_POD_OPT_Pod(&val.P)) == 2);
+ }
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_parser)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ struct spa_pod_parser p;
+ struct spa_pod_frame f;
+ struct spa_pod *pod;
+ struct {
+ bool b;
+ uint32_t I;
+ int32_t i;
+ int64_t l;
+ float f;
+ double d;
+ const char *s;
+ uint32_t yl;
+ const void *y;
+ uint32_t ptype;
+ const void *p;
+ uint32_t asize, atype, anvals;
+ const void *a;
+ int64_t h;
+ struct spa_rectangle R;
+ struct spa_fraction F;
+ struct spa_pod *P;
+ } val;
+ uint8_t bytes[] = { 0x56, 0x00, 0x12, 0xf3, 0xba };
+ int64_t longs[] = { 1002, 5383, 28944, 1237748 };
+ struct spa_pod_int pi = SPA_POD_INIT_Int(77);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ pod = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Props, 0,
+ 1, SPA_POD_Bool(true),
+ 2, SPA_POD_Id(SPA_TYPE_Id),
+ 3, SPA_POD_Int(3),
+ 4, SPA_POD_Long(4LL),
+ 5, SPA_POD_Float(0.453f),
+ 6, SPA_POD_Double(0.871),
+ 7, SPA_POD_String("test"),
+ 8, SPA_POD_Bytes(bytes, sizeof(bytes)),
+ 9, SPA_POD_Rectangle(&SPA_RECTANGLE(3,4)),
+ 10, SPA_POD_Fraction(&SPA_FRACTION(24,1)),
+ 11, SPA_POD_Array(sizeof(int64_t), SPA_TYPE_Long, SPA_N_ELEMENTS(longs), longs),
+ 12, SPA_POD_Pointer(SPA_TYPE_Object, &b),
+ 13, SPA_POD_Fd(3),
+ 14, SPA_POD_Pod(&pi));
+
+ spa_debug_pod(0, NULL, pod);
+
+ spa_pod_parser_pod(&p, pod);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_bool(&p, &val.b) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_id(&p, &val.I) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_int(&p, &val.i) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_long(&p, &val.l) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_float(&p, &val.f) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_double(&p, &val.d) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_string(&p, &val.s) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_bytes(&p, &val.y, &val.yl) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_rectangle(&p, &val.R) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_fraction(&p, &val.F) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_pointer(&p, &val.ptype, &val.p) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_fd(&p, &val.h) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_pod(&p, &val.P) == 0);
+ spa_assert_se(p.state.offset == 392);
+ spa_assert_se(spa_pod_is_object(val.P));
+
+ spa_pod_parser_pod(&p, val.P);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_push_struct(&p, &f) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_push_object(&p, &f, SPA_TYPE_OBJECT_Format, NULL) == -EPROTO);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_push_object(&p, &f, SPA_TYPE_OBJECT_Props, NULL) == 0);
+ spa_assert_se(p.state.offset == 392);
+ spa_assert_se(spa_pod_parser_frame(&p, &f) == val.P);
+
+ spa_zero(val);
+ spa_assert_se(spa_pod_parser_get(&p,
+ 1, SPA_POD_OPT_Bool(&val.b),
+ 2, SPA_POD_OPT_Id(&val.I),
+ 3, SPA_POD_OPT_Int(&val.i),
+ 4, SPA_POD_OPT_Long(&val.l),
+ 5, SPA_POD_OPT_Float(&val.f),
+ 6, SPA_POD_OPT_Double(&val.d),
+ 7, SPA_POD_OPT_String(&val.s),
+ 8, SPA_POD_OPT_Bytes(&val.y, &val.yl),
+ 9, SPA_POD_OPT_Rectangle(&val.R),
+ 10, SPA_POD_OPT_Fraction(&val.F),
+ 11, SPA_POD_OPT_Array(&val.asize, &val.atype, &val.anvals, &val.a),
+ 12, SPA_POD_OPT_Pointer(&val.ptype, &val.p),
+ 13, SPA_POD_OPT_Fd(&val.h),
+ 14, SPA_POD_OPT_Pod(&val.P), 0) == 14);
+ spa_pod_parser_pop(&p, &f);
+
+ spa_assert_se(val.b == true);
+ spa_assert_se(val.I == SPA_TYPE_Id);
+ spa_assert_se(val.i == 3);
+ spa_assert_se(val.l == 4);
+ spa_assert_se(val.f == 0.453f);
+ spa_assert_se(val.d == 0.871);
+ spa_assert_se(spa_streq(val.s, "test"));
+ spa_assert_se(val.yl == sizeof(bytes));
+ spa_assert_se(memcmp(val.y, bytes, sizeof(bytes)) == 0);
+ spa_assert_se(memcmp(&val.R, &SPA_RECTANGLE(3, 4), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(memcmp(&val.F, &SPA_FRACTION(24, 1), sizeof(struct spa_fraction)) == 0);
+ spa_assert_se(val.asize == sizeof(int64_t));
+ spa_assert_se(val.atype == SPA_TYPE_Long);
+ spa_assert_se(val.anvals == SPA_N_ELEMENTS(longs));
+ spa_assert_se(memcmp(val.a, longs, val.anvals * val.asize) == 0);
+ spa_assert_se(val.ptype == SPA_TYPE_Object);
+ spa_assert_se(val.p == &b);
+ spa_assert_se(val.h == 3);
+ spa_assert_se(memcmp(val.P, &pi, sizeof(pi)) == 0);
+
+ spa_assert_se(p.state.offset == 392);
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_parser2)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ struct spa_pod_parser p;
+ struct spa_pod_frame f;
+ struct spa_pod *pod;
+ struct {
+ bool b;
+ uint32_t I;
+ int32_t i;
+ int64_t l;
+ float f;
+ double d;
+ const char *s;
+ uint32_t yl;
+ const void *y;
+ uint32_t ptype;
+ const void *p;
+ uint32_t asize, atype, anvals;
+ const void *a;
+ int64_t h;
+ struct spa_rectangle R;
+ struct spa_fraction F;
+ struct spa_pod *P;
+ } val;
+ uint8_t bytes[] = { 0x56, 0x00, 0x12, 0xf3, 0xba };
+ int64_t longs[] = { 1002, 5383, 28944, 1237748 };
+ struct spa_pod_int pi = SPA_POD_INIT_Int(77);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ pod = spa_pod_builder_add_struct(&b,
+ SPA_POD_Bool(true),
+ SPA_POD_Id(SPA_TYPE_Id),
+ SPA_POD_Int(3),
+ SPA_POD_Long(4LL),
+ SPA_POD_Float(0.453f),
+ SPA_POD_Double(0.871),
+ SPA_POD_String("test"),
+ SPA_POD_Bytes(bytes, sizeof(bytes)),
+ SPA_POD_Rectangle(&SPA_RECTANGLE(3,4)),
+ SPA_POD_Fraction(&SPA_FRACTION(24,1)),
+ SPA_POD_Array(sizeof(int64_t), SPA_TYPE_Long, SPA_N_ELEMENTS(longs), longs),
+ SPA_POD_Pointer(SPA_TYPE_Object, &b),
+ SPA_POD_Fd(3),
+ SPA_POD_Pod(&pi));
+
+ spa_debug_pod(0, NULL, pod);
+
+ spa_pod_parser_pod(&p, pod);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_bool(&p, &val.b) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_id(&p, &val.I) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_int(&p, &val.i) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_long(&p, &val.l) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_float(&p, &val.f) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_double(&p, &val.d) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_string(&p, &val.s) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_bytes(&p, &val.y, &val.yl) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_rectangle(&p, &val.R) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_fraction(&p, &val.F) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_pointer(&p, &val.ptype, &val.p) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_fd(&p, &val.h) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_get_pod(&p, &val.P) == 0);
+ spa_assert_se(p.state.offset == 272);
+ spa_assert_se(spa_pod_is_struct(val.P));
+
+ spa_pod_parser_pod(&p, val.P);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_push_object(&p, &f, SPA_TYPE_OBJECT_Format, NULL) == -EINVAL);
+ spa_assert_se(p.state.offset == 0);
+ spa_assert_se(spa_pod_parser_push_struct(&p, &f) == 0);
+ spa_assert_se(f.pod.type == SPA_TYPE_Struct);
+ spa_assert_se(f.pod.size == 264);
+ spa_assert_se(f.offset == 0);
+ spa_assert_se(p.state.frame == &f);
+ spa_assert_se(spa_pod_parser_frame(&p, &f) == val.P);
+ spa_assert_se(p.state.offset == 8);
+ spa_assert_se(spa_pod_parser_get_bool(&p, &val.b) == 0 && val.b == true);
+ spa_assert_se(p.state.offset == 24);
+ spa_assert_se(spa_pod_parser_get_id(&p, &val.I) == 0 && val.I == SPA_TYPE_Id);
+ spa_assert_se(p.state.offset == 40);
+ spa_assert_se(spa_pod_parser_get_int(&p, &val.i) == 0 && val.i == 3);
+ spa_assert_se(p.state.offset == 56);
+ spa_assert_se(spa_pod_parser_get_long(&p, &val.l) == 0 && val.l == 4);
+ spa_assert_se(p.state.offset == 72);
+ spa_assert_se(spa_pod_parser_get_float(&p, &val.f) == 0 && val.f == 0.453f);
+ spa_assert_se(p.state.offset == 88);
+ spa_assert_se(spa_pod_parser_get_double(&p, &val.d) == 0 && val.d == 0.871);
+ spa_assert_se(p.state.offset == 104);
+ spa_assert_se(spa_pod_parser_get_string(&p, &val.s) == 0 && spa_streq(val.s, "test"));
+ spa_assert_se(p.state.offset == 120);
+ spa_assert_se(spa_pod_parser_get_bytes(&p, &val.y, &val.yl) == 0);
+ spa_assert_se(val.yl == sizeof(bytes));
+ spa_assert_se(memcmp(bytes, val.y, sizeof(bytes)) == 0);
+ spa_assert_se(p.state.offset == 136);
+ spa_assert_se(spa_pod_parser_get_rectangle(&p, &val.R) == 0);
+ spa_assert_se(memcmp(&val.R, &SPA_RECTANGLE(3,4), sizeof(struct spa_rectangle)) == 0);
+ spa_assert_se(p.state.offset == 152);
+ spa_assert_se(spa_pod_parser_get_fraction(&p, &val.F) == 0);
+ spa_assert_se(memcmp(&val.F, &SPA_FRACTION(24,1), sizeof(struct spa_fraction)) == 0);
+ spa_assert_se(p.state.offset == 168);
+ val.P = spa_pod_parser_next(&p);
+ spa_assert_se(val.P != NULL);
+ spa_assert_se(spa_pod_is_array(val.P));
+ spa_assert_se(p.state.offset == 216);
+ spa_assert_se(SPA_POD_ARRAY_VALUE_TYPE(val.P) == SPA_TYPE_Long);
+ spa_assert_se(SPA_POD_ARRAY_VALUE_SIZE(val.P) == sizeof(int64_t));
+ spa_assert_se(SPA_POD_ARRAY_N_VALUES(val.P) == SPA_N_ELEMENTS(longs));
+ spa_assert_se(spa_pod_parser_get_pointer(&p, &val.ptype, &val.p) == 0);
+ spa_assert_se(val.ptype == SPA_TYPE_Object);
+ spa_assert_se(val.p == &b);
+ spa_assert_se(p.state.offset == 240);
+ spa_assert_se(spa_pod_parser_get_fd(&p, &val.h) == 0);
+ spa_assert_se(val.h == 3);
+ spa_assert_se(p.state.offset == 256);
+ spa_assert_se(spa_pod_parser_get_pod(&p, &val.P) == 0);
+ spa_assert_se(p.state.offset == 272);
+ spa_assert_se(spa_pod_is_int(val.P));
+ spa_pod_parser_pop(&p, &f);
+ spa_assert_se(p.state.offset == 272);
+ spa_assert_se(p.state.frame == NULL);
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_static)
+{
+ struct _test_format {
+ struct spa_pod_object fmt;
+
+ struct {
+ struct spa_pod_prop prop_media_type SPA_ALIGNED(8);
+ uint32_t media_type;
+
+ struct spa_pod_prop prop_media_subtype SPA_ALIGNED(8);
+ uint32_t media_subtype;
+
+ struct spa_pod_prop prop_format SPA_ALIGNED(8);
+ struct {
+ struct spa_pod_choice_body choice;
+ uint32_t def_format;
+ uint32_t enum_format[2];
+ } format_vals;
+
+ struct spa_pod_prop prop_size SPA_ALIGNED(8);
+ struct {
+ struct spa_pod_choice_body choice;
+ struct spa_rectangle def_size;
+ struct spa_rectangle min_size;
+ struct spa_rectangle max_size;
+ } size_vals;
+
+ struct spa_pod_prop prop_framerate SPA_ALIGNED(8);
+ struct {
+ struct spa_pod_choice_body choice;
+ struct spa_fraction def_framerate;
+ struct spa_fraction min_framerate;
+ struct spa_fraction max_framerate;
+ } framerate_vals;
+ } props;
+ } test_format = {
+ SPA_POD_INIT_Object(sizeof(test_format.props) + sizeof(struct spa_pod_object_body),
+ SPA_TYPE_OBJECT_Format, 0),
+ {
+ SPA_POD_INIT_Prop(SPA_FORMAT_mediaType, 0,
+ sizeof(test_format.props.media_type), SPA_TYPE_Id),
+ SPA_MEDIA_TYPE_video,
+
+ SPA_POD_INIT_Prop(SPA_FORMAT_mediaSubtype, 0,
+ sizeof(test_format.props.media_subtype), SPA_TYPE_Id),
+ SPA_MEDIA_SUBTYPE_raw,
+
+ SPA_POD_INIT_Prop(SPA_FORMAT_VIDEO_format, 0,
+ sizeof(test_format.props.format_vals), SPA_TYPE_Choice),
+ {
+ SPA_POD_INIT_CHOICE_BODY(SPA_CHOICE_Enum, 0,
+ sizeof(uint32_t), SPA_TYPE_Id),
+ SPA_VIDEO_FORMAT_I420,
+ { SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_YUY2 }
+ },
+ SPA_POD_INIT_Prop(SPA_FORMAT_VIDEO_size, 0,
+ sizeof(test_format.props.size_vals), SPA_TYPE_Choice),
+
+ {
+ SPA_POD_INIT_CHOICE_BODY(SPA_CHOICE_Range, 0,
+ sizeof(struct spa_rectangle), SPA_TYPE_Rectangle),
+ SPA_RECTANGLE(320,243),
+ SPA_RECTANGLE(1,1), SPA_RECTANGLE(INT32_MAX, INT32_MAX)
+ },
+ SPA_POD_INIT_Prop(SPA_FORMAT_VIDEO_framerate, 0,
+ sizeof(test_format.props.framerate_vals), SPA_TYPE_Choice),
+ {
+ SPA_POD_INIT_CHOICE_BODY(SPA_CHOICE_Range, 0,
+ sizeof(struct spa_fraction), SPA_TYPE_Fraction),
+ SPA_FRACTION(25,1),
+ SPA_FRACTION(0,1), SPA_FRACTION(INT32_MAX,1)
+ }
+ }
+ };
+ struct {
+ uint32_t media_type;
+ uint32_t media_subtype;
+ uint32_t format;
+ struct spa_rectangle size;
+ struct spa_fraction framerate;
+ } vals;
+ int res;
+
+ spa_debug_pod(0, NULL, &test_format.fmt.pod);
+
+ spa_zero(vals);
+ res = spa_pod_parse_object(&test_format.fmt.pod,
+ SPA_TYPE_OBJECT_Format, NULL,
+ SPA_FORMAT_mediaType, SPA_POD_Id(&vals.media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(&vals.media_subtype),
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(&vals.format),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&vals.size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&vals.framerate));
+
+ spa_assert_se(res == -EPROTO);
+ spa_assert_se(vals.media_type == SPA_MEDIA_TYPE_video);
+ spa_assert_se(vals.media_subtype == SPA_MEDIA_SUBTYPE_raw);
+ spa_assert_se(vals.format == 0);
+ spa_assert_se(vals.size.width == 0 && vals.size.height == 0);
+ spa_assert_se(vals.framerate.num == 0 && vals.framerate.denom == 0);
+
+ spa_pod_fixate(&test_format.fmt.pod);
+
+ spa_zero(vals);
+ res = spa_pod_parse_object(&test_format.fmt.pod,
+ SPA_TYPE_OBJECT_Format, NULL,
+ SPA_FORMAT_mediaType, SPA_POD_Id(&vals.media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(&vals.media_subtype),
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(&vals.format),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&vals.size),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&vals.framerate));
+
+ spa_assert_se(res == 5);
+ spa_assert_se(vals.media_type == SPA_MEDIA_TYPE_video);
+ spa_assert_se(vals.media_subtype == SPA_MEDIA_SUBTYPE_raw);
+ spa_assert_se(vals.format == SPA_VIDEO_FORMAT_I420);
+ spa_assert_se(vals.size.width == 320 && vals.size.height == 243);
+ spa_assert_se(vals.framerate.num == 25 && vals.framerate.denom == 1);
+ return PWTEST_PASS;
+}
+
+PWTEST(pod_overflow)
+{
+ static const char * const labels[] = {
+ "640x480p59", "720x480i29", "720x480p59", "720x576i25", "720x576p50",
+ "1280x720p24", "1280x720p25", "1280x720p30", "1280x720p50", "1280x720p60",
+ "1920x1080p24", "1920x1080p25", "1920x1080p30", "1920x1080i25", "1920x1080p50",
+ "1920x1080i30", "1920x1080p60", "640x350p85", "640x400p85", "720x400p85",
+ "640x480p72", "640x480p75", "640x480p85", "800x600p56", "800x600p60",
+ "800x600p72", "800x600p75", "800x600p85", "800x600p119", "848x480p60",
+ "1024x768i43", "1024x768p60", "1024x768p70", "1024x768p75", "1024x768p84",
+ "1024x768p119", "1152x864p75", "1280x768p59", "1280x768p59", "1280x768p74",
+ "1280x768p84", "1280x768p119", "1280x800p59", "1280x800p59", "1280x800p74",
+ "1280x800p84", "1280x800p119", "1280x960p60", "1280x960p85", "1280x960p119",
+ "1280x1024p60", "1280x1024p75", "1280x1024p85", "1280x1024p119", "1360x768p60",
+ "1360x768p119", "1366x768p59", "1366x768p60", "1400x1050p59", "1400x1050p59",
+ "1400x1050p74", "1400x1050p84", "1400x1050p119", "1440x900p59", "1440x900p59",
+ "1440x900p74", "1440x900p84", "1440x900p119", "1600x900p60", "1600x1200p60",
+ "1600x1200p65", "1600x1200p70", "1600x1200p75", "1600x1200p85", "1600x1200p119",
+ "1680x1050p59", "1680x1050p59", "1680x1050p74", "1680x1050p84", "1680x1050p119",
+ "1792x1344p59", "1792x1344p74", "1792x1344p119", "1856x1392p59", "1856x1392p75",
+ "1856x1392p119", "1920x1200p59", "1920x1200p59", "1920x1200p74", "1920x1200p84",
+ "1920x1200p119", "1920x1440p60", "1920x1440p75", "1920x1440p119", "2048x1152p60",
+ "2560x1600p59", "2560x1600p59", "2560x1600p74", "2560x1600p84", "2560x1600p119",
+ "3840x2160p24", "3840x2160p25", "3840x2160p30", "3840x2160p50", "3840x2160p60",
+ "4096x2160p24", "4096x2160p25", "4096x2160p30", "4096x2160p50", "4096x2160p59",
+ "4096x2160p60", NULL };
+
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ struct spa_pod_builder_state state;
+ struct spa_pod_frame f[2];
+ uint32_t idx;
+ struct spa_pod *pod;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+
+ spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo);
+ spa_pod_builder_add(&b,
+ SPA_PROP_INFO_id, SPA_POD_Id(32567359),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_ENUM_Int(1, 0),
+ SPA_PROP_INFO_description, SPA_POD_String("DV Timings"),
+ 0);
+
+ spa_pod_builder_get_state(&b, &state),
+
+ spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0);
+ spa_pod_builder_push_struct(&b, &f[1]);
+
+ for (idx = 0; labels[idx]; idx++) {
+ spa_pod_builder_int(&b, idx);
+ spa_pod_builder_string(&b, labels[idx]);
+ }
+ spa_assert_se(b.state.offset > sizeof(buffer));
+ pod = spa_pod_builder_pop(&b, &f[1]);
+ spa_assert_se(pod == NULL);
+ spa_pod_builder_reset(&b, &state);
+
+ spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0);
+ spa_pod_builder_push_struct(&b, &f[1]);
+ pod = spa_pod_builder_pop(&b, &f[1]);
+
+ spa_assert_se(b.state.offset < sizeof(buffer));
+ pod = spa_pod_builder_pop(&b, &f[0]);
+ spa_assert_se(pod != NULL);
+
+ spa_debug_pod(0, NULL, pod);
+ return PWTEST_PASS;
+}
+
+static int handle_overflow(void *data, uint32_t size)
+{
+ uint32_t *d = data;
+ (*d)++;
+ return -ENOSPC;
+}
+
+static struct spa_pod_builder_callbacks overflow_cb = {
+ SPA_VERSION_POD_BUILDER_CALLBACKS,
+ .overflow = handle_overflow
+};
+
+PWTEST(pod_overflow2)
+{
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ struct spa_pod_builder_state state;
+ struct spa_pod_frame f[2];
+ uint32_t idx, overflow_count = 0;
+ struct spa_pod *pod;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ spa_pod_builder_set_callbacks(&b, &overflow_cb, &overflow_count);
+
+ spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo);
+ spa_pod_builder_add(&b,
+ SPA_PROP_INFO_id, SPA_POD_Id(32567359),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_ENUM_Int(1, 0),
+ SPA_PROP_INFO_description, SPA_POD_String("DV Timings"),
+ 0);
+
+ spa_pod_builder_get_state(&b, &state),
+
+ spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0);
+ spa_pod_builder_push_struct(&b, &f[1]);
+
+ for (idx = 0; idx < 512; idx++) {
+ spa_pod_builder_int(&b, idx);
+ spa_pod_builder_string(&b, "foo");
+ }
+ spa_assert_se(b.state.offset > sizeof(buffer));
+ pod = spa_pod_builder_pop(&b, &f[1]);
+ spa_assert_se(pod == NULL);
+ spa_assert_se(overflow_count == 1);
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(spa_pod)
+{
+ pwtest_add(pod_abi_sizes, PWTEST_NOARG);
+ pwtest_add(pod_abi, PWTEST_NOARG);
+ pwtest_add(pod_init, PWTEST_NOARG);
+ pwtest_add(pod_empty, PWTEST_NOARG);
+ pwtest_add(pod_build, PWTEST_NOARG);
+ pwtest_add(pod_varargs, PWTEST_NOARG);
+ pwtest_add(pod_varargs2, PWTEST_NOARG);
+ pwtest_add(pod_parser, PWTEST_NOARG);
+ pwtest_add(pod_parser2, PWTEST_NOARG);
+ pwtest_add(pod_static, PWTEST_NOARG);
+ pwtest_add(pod_overflow, PWTEST_NOARG);
+ pwtest_add(pod_overflow2, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-spa-utils.c b/test/test-spa-utils.c
new file mode 100644
index 0000000..4184f43
--- /dev/null
+++ b/test/test-spa-utils.c
@@ -0,0 +1,1043 @@
+/* Simple Plugin API
+ * Copyright © 2018 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <locale.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include <valgrind/valgrind.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/result.h>
+#include <spa/utils/dict.h>
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/utils/string.h>
+#include <spa/utils/type.h>
+#include <spa/utils/ansi.h>
+
+#include "pwtest.h"
+
+PWTEST(utils_abi_sizes)
+{
+#if defined(__x86_64__) && defined(__LP64__)
+ /* dict */
+ pwtest_int_eq(sizeof(struct spa_dict_item), 16U);
+ pwtest_int_eq(sizeof(struct spa_dict), 16U);
+
+ /* hook */
+ pwtest_int_eq(sizeof(struct spa_hook_list), sizeof(struct spa_list));
+ pwtest_int_eq(sizeof(struct spa_hook), 48U);
+
+ /* list */
+ pwtest_int_eq(sizeof(struct spa_list), 16U);
+
+ return PWTEST_PASS;
+#endif
+
+ return PWTEST_SKIP;
+}
+
+PWTEST(utils_abi)
+{
+ /* defs */
+ pwtest_int_eq(SPA_DIRECTION_INPUT, 0);
+ pwtest_int_eq(SPA_DIRECTION_OUTPUT, 1);
+
+ pwtest_int_eq(sizeof(struct spa_rectangle), 8U);
+ pwtest_int_eq(sizeof(struct spa_point), 8U);
+ pwtest_int_eq(sizeof(struct spa_region), 16U);
+ pwtest_int_eq(sizeof(struct spa_fraction), 8U);
+
+ {
+ struct spa_rectangle r = SPA_RECTANGLE(12, 14);
+ pwtest_int_eq(r.width, 12U);
+ pwtest_int_eq(r.height, 14U);
+ }
+ {
+ struct spa_point p = SPA_POINT(8, 34);
+ pwtest_int_eq(p.x, 8);
+ pwtest_int_eq(p.y, 34);
+ }
+ {
+ struct spa_region r = SPA_REGION(4, 5, 12, 13);
+ pwtest_int_eq(r.position.x, 4);
+ pwtest_int_eq(r.position.y, 5);
+ pwtest_int_eq(r.size.width, 12U);
+ pwtest_int_eq(r.size.height, 13U);
+ }
+ {
+ struct spa_fraction f = SPA_FRACTION(56, 125);
+ pwtest_int_eq(f.num, 56U);
+ pwtest_int_eq(f.denom, 125U);
+ }
+
+ /* ringbuffer */
+ pwtest_int_eq(sizeof(struct spa_ringbuffer), 8U);
+
+ /* type */
+ pwtest_int_eq(SPA_TYPE_START, 0);
+ pwtest_int_eq(SPA_TYPE_None, 1);
+ pwtest_int_eq(SPA_TYPE_Bool, 2);
+ pwtest_int_eq(SPA_TYPE_Id, 3);
+ pwtest_int_eq(SPA_TYPE_Int, 4);
+ pwtest_int_eq(SPA_TYPE_Long, 5);
+ pwtest_int_eq(SPA_TYPE_Float, 6);
+ pwtest_int_eq(SPA_TYPE_Double, 7);
+ pwtest_int_eq(SPA_TYPE_String, 8);
+ pwtest_int_eq(SPA_TYPE_Bytes, 9);
+ pwtest_int_eq(SPA_TYPE_Rectangle, 10);
+ pwtest_int_eq(SPA_TYPE_Fraction, 11);
+ pwtest_int_eq(SPA_TYPE_Bitmap, 12);
+ pwtest_int_eq(SPA_TYPE_Array, 13);
+ pwtest_int_eq(SPA_TYPE_Struct, 14);
+ pwtest_int_eq(SPA_TYPE_Object, 15);
+ pwtest_int_eq(SPA_TYPE_Sequence, 16);
+ pwtest_int_eq(SPA_TYPE_Pointer, 17);
+ pwtest_int_eq(SPA_TYPE_Fd, 18);
+ pwtest_int_eq(SPA_TYPE_Choice, 19);
+ pwtest_int_eq(SPA_TYPE_Pod, 20);
+ pwtest_int_eq(_SPA_TYPE_LAST, 21);
+
+ pwtest_int_eq(SPA_TYPE_EVENT_START, 0x20000);
+ pwtest_int_eq(SPA_TYPE_EVENT_Device, 0x20001);
+ pwtest_int_eq(SPA_TYPE_EVENT_Node, 0x20002);
+ pwtest_int_eq(_SPA_TYPE_EVENT_LAST, 0x20003);
+
+ pwtest_int_eq(SPA_TYPE_COMMAND_START, 0x30000);
+ pwtest_int_eq(SPA_TYPE_COMMAND_Device, 0x30001);
+ pwtest_int_eq(SPA_TYPE_COMMAND_Node, 0x30002);
+ pwtest_int_eq(_SPA_TYPE_COMMAND_LAST, 0x30003);
+
+ pwtest_int_eq(SPA_TYPE_OBJECT_START, 0x40000);
+ pwtest_int_eq(SPA_TYPE_OBJECT_PropInfo, 0x40001);
+ pwtest_int_eq(SPA_TYPE_OBJECT_Props, 0x40002);
+ pwtest_int_eq(SPA_TYPE_OBJECT_Format, 0x40003);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamBuffers, 0x40004);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamMeta, 0x40005);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamIO, 0x40006);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamProfile, 0x40007);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamPortConfig, 0x40008);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamRoute, 0x40009);
+ pwtest_int_eq(SPA_TYPE_OBJECT_Profiler, 0x4000a);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamLatency, 0x4000b);
+ pwtest_int_eq(SPA_TYPE_OBJECT_ParamProcessLatency, 0x4000c);
+ pwtest_int_eq(_SPA_TYPE_OBJECT_LAST, 0x4000d);
+
+ pwtest_int_eq(SPA_TYPE_VENDOR_PipeWire, 0x02000000);
+ pwtest_int_eq(SPA_TYPE_VENDOR_Other, 0x7f000000);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_macros)
+{
+ uint8_t ptr[64];
+ uint16_t i16[14];
+ uint32_t i32[10];
+ uint64_t i64[12];
+ unsigned char c[16];
+
+ pwtest_int_eq(SPA_MIN(1, 2), 1);
+ pwtest_int_eq(SPA_MIN(1, -2), -2);
+ pwtest_int_eq(SPA_MAX(1, 2), 2);
+ pwtest_int_eq(SPA_MAX(1, -2), 1);
+ pwtest_int_eq(SPA_CLAMP(23, 1, 16), 16);
+ pwtest_int_eq(SPA_CLAMP(-1, 1, 16), 1);
+ pwtest_int_eq(SPA_CLAMP(8, 1, 16), 8);
+
+ /* SPA_MEMBER exists for backwards compatibility but should no
+ * longer be used, let's make sure it does what we expect it to */
+ pwtest_ptr_eq(SPA_MEMBER(ptr, 4, void), SPA_PTROFF(ptr, 4, void));
+ pwtest_ptr_eq(SPA_MEMBER(ptr, 32, void), SPA_PTROFF(ptr, 32, void));
+ pwtest_ptr_eq(SPA_MEMBER(ptr, 0, void), SPA_PTROFF(ptr, 0, void));
+ pwtest_ptr_eq(SPA_MEMBER_ALIGN(ptr, 0, 4, void), SPA_PTROFF_ALIGN(ptr, 0, 4, void));
+ pwtest_ptr_eq(SPA_MEMBER_ALIGN(ptr, 4, 32, void), SPA_PTROFF_ALIGN(ptr, 4, 32, void));
+
+ pwtest_int_eq(SPA_N_ELEMENTS(ptr), 64U);
+ pwtest_int_eq(SPA_N_ELEMENTS(i32), 10U);
+ pwtest_int_eq(SPA_N_ELEMENTS(i64), 12U);
+ pwtest_int_eq(SPA_N_ELEMENTS(i16), 14U);
+ pwtest_int_eq(SPA_N_ELEMENTS(c), 16U);
+
+#define check_traversal(array_) \
+ { \
+ int count = 0; \
+ SPA_FOR_EACH_ELEMENT_VAR(array_, it) \
+ *it = count++; \
+ for (size_t i = 0; i < SPA_N_ELEMENTS(array_); i++) \
+ pwtest_int_eq(array_[i], i); \
+ }
+ check_traversal(ptr);
+ check_traversal(i64);
+ check_traversal(i32);
+ check_traversal(i16);
+ check_traversal(c);
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_result)
+{
+ int res;
+ pwtest_int_eq(SPA_RESULT_IS_OK(0), true);
+ pwtest_int_eq(SPA_RESULT_IS_OK(1), true);
+ pwtest_int_eq(SPA_RESULT_IS_ERROR(0), false);
+ pwtest_int_eq(SPA_RESULT_IS_ERROR(1), false);
+ pwtest_int_eq(SPA_RESULT_IS_ERROR(-1), true);
+ pwtest_int_eq(SPA_RESULT_IS_ASYNC(-1), false);
+ pwtest_int_eq(SPA_RESULT_IS_ASYNC(0), false);
+ res = SPA_RESULT_RETURN_ASYNC(11);
+ pwtest_int_eq(SPA_RESULT_IS_ASYNC(res), true);
+ pwtest_int_eq(SPA_RESULT_IS_ERROR(res), false);
+ pwtest_int_eq(SPA_RESULT_IS_OK(res), true);
+ pwtest_int_eq(SPA_RESULT_ASYNC_SEQ(res), 11);
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_dict)
+{
+ struct spa_dict_item items[5] = {
+ SPA_DICT_ITEM_INIT("key", "value"),
+ SPA_DICT_ITEM_INIT("pipe", "wire"),
+ SPA_DICT_ITEM_INIT("test", "Works!"),
+ SPA_DICT_ITEM_INIT("123", ""),
+ SPA_DICT_ITEM_INIT("SPA", "Simple Plugin API"),
+ };
+ struct spa_dict dict = SPA_DICT_INIT_ARRAY (items);
+ const struct spa_dict_item *it;
+ int i = 0;
+
+ pwtest_int_eq(dict.n_items, 5U);
+ pwtest_str_eq(spa_dict_lookup(&dict, "pipe"), "wire");
+ pwtest_str_eq(spa_dict_lookup(&dict, "123"), "");
+ pwtest_str_eq(spa_dict_lookup(&dict, "key"), "value");
+ pwtest_str_eq(spa_dict_lookup(&dict, "SPA"), "Simple Plugin API");
+ pwtest_str_eq(spa_dict_lookup(&dict, "test"), "Works!");
+ pwtest_ptr_null(spa_dict_lookup(&dict, "nonexistent"));
+
+ pwtest_ptr_eq(spa_dict_lookup_item(&dict, "123"), &items[3]);
+ pwtest_ptr_null(spa_dict_lookup_item(&dict, "foobar"));
+
+ spa_dict_for_each(it, &dict) {
+ pwtest_ptr_eq(it, &items[i++]);
+ }
+ pwtest_int_eq(i, 5);
+ return PWTEST_PASS;
+}
+
+struct string_list {
+ char string[20];
+ struct spa_list node;
+};
+
+PWTEST(utils_list)
+{
+ struct string_list list;
+ struct spa_list *head = &list.node;
+ struct string_list *e;
+ int i;
+
+ spa_list_init(head);
+ pwtest_bool_true(spa_list_is_empty(head));
+
+ e = malloc(sizeof(struct string_list));
+ strcpy(e->string, "test");
+ spa_list_insert(head, &e->node);
+ pwtest_bool_false(spa_list_is_empty(head));
+ pwtest_ptr_eq(spa_list_first(head, struct string_list, node), e);
+ pwtest_ptr_eq(spa_list_last(head, struct string_list, node), e);
+
+ e = malloc(sizeof(struct string_list));
+ strcpy(e->string, "pipewire!");
+ spa_list_append(head, &e->node);
+ pwtest_bool_false(spa_list_is_empty(head));
+ pwtest_ptr_eq(spa_list_last(head, struct string_list, node), e);
+
+ e = malloc(sizeof(struct string_list));
+ strcpy(e->string, "First element");
+ spa_list_prepend(head, &e->node);
+ pwtest_bool_false(spa_list_is_empty(head));
+ pwtest_ptr_eq(spa_list_first(head, struct string_list, node), e);
+
+ i = 0;
+ spa_list_for_each(e, head, node) {
+ switch (i++) {
+ case 0:
+ pwtest_str_eq(e->string, "First element");
+ break;
+ case 1:
+ pwtest_str_eq(e->string, "test");
+ break;
+ case 2:
+ pwtest_str_eq(e->string, "pipewire!");
+ break;
+ default:
+ pwtest_fail_if_reached();
+ break;
+ }
+ }
+
+ i = 0;
+ spa_list_consume(e, head, node) {
+ spa_list_remove(&e->node);
+ free(e);
+ i++;
+ }
+ pwtest_int_eq(i, 3);
+ pwtest_bool_true(spa_list_is_empty(head));
+
+ return PWTEST_PASS;
+}
+
+
+struct my_hook {
+ int version;
+ void (*invoke) (void *);
+};
+
+struct my_hook_data {
+ bool cb1;
+ bool cb2;
+ bool cb3;
+};
+
+static void test_hook_callback_1(void *data)
+{
+ ((struct my_hook_data *) data)->cb1 = true;
+}
+
+static void test_hook_callback_2(void *data)
+{
+ ((struct my_hook_data *) data)->cb2 = true;
+}
+
+static void test_hook_callback_3(void *data)
+{
+ ((struct my_hook_data *) data)->cb3 = true;
+}
+
+static void test_hook_callback_4(void *data)
+{
+ pwtest_fail_if_reached();
+}
+
+static int hook_free_count = 0;
+
+static void hook_removed_cb(struct spa_hook *h)
+{
+ free(h);
+ hook_free_count++;
+}
+
+PWTEST(utils_hook)
+{
+ const int HOOK_VERSION = 2;
+ struct spa_hook_list hl;
+ struct my_hook callbacks[4] = {
+ {2, test_hook_callback_1},
+ {3, test_hook_callback_2},
+ {2, test_hook_callback_3},
+ /* version 1 should not be called */
+ {1, test_hook_callback_4}
+ };
+ struct my_hook_data data = {0};
+ struct spa_hook *h;
+ int count = 0;
+
+ spa_hook_list_init(&hl);
+
+ h = malloc(sizeof(struct spa_hook));
+ spa_hook_list_append(&hl, h, &callbacks[1], &data);
+ h->removed = hook_removed_cb;
+
+ h = malloc(sizeof(struct spa_hook));
+ spa_hook_list_append(&hl, h, &callbacks[2], &data);
+ h->removed = hook_removed_cb;
+
+ /* iterate with the simple API */
+ spa_hook_list_call_simple(&hl, struct my_hook, invoke, HOOK_VERSION);
+ pwtest_bool_eq(data.cb1, false);
+ pwtest_bool_eq(data.cb2, true);
+ pwtest_bool_eq(data.cb3, true);
+
+ /* reset cb* booleans to false */
+ memset(&data, 0, sizeof(struct my_hook_data));
+
+ h = malloc(sizeof(struct spa_hook));
+ spa_hook_list_prepend(&hl, h, &callbacks[0], &data);
+ h->removed = hook_removed_cb;
+
+ /* call only the first hook - this should be callback_1 */
+ count = spa_hook_list_call_once(&hl, struct my_hook, invoke, HOOK_VERSION);
+ pwtest_int_eq(count, 1);
+ pwtest_bool_eq(data.cb1, true);
+ pwtest_bool_eq(data.cb2, false);
+ pwtest_bool_eq(data.cb3, false);
+
+ /* reset cb* booleans to false */
+ memset(&data, 0, sizeof(struct my_hook_data));
+
+ /* add callback_4 - this is version 1, so it shouldn't be executed */
+ h = malloc(sizeof(struct spa_hook));
+ spa_hook_list_append(&hl, h, &callbacks[3], &data);
+ h->removed = hook_removed_cb;
+
+ count = spa_hook_list_call(&hl, struct my_hook, invoke, HOOK_VERSION);
+ pwtest_int_eq(count, 3);
+ pwtest_bool_eq(data.cb1, true);
+ pwtest_bool_eq(data.cb2, true);
+ pwtest_bool_eq(data.cb3, true);
+
+ count = 0;
+ hook_free_count = 0;
+ spa_list_consume(h, &hl.list, link) {
+ spa_hook_remove(h);
+ count++;
+ }
+ pwtest_int_eq(count, 4);
+ pwtest_int_eq(hook_free_count, 4);
+
+ /* remove a zeroed hook */
+ struct spa_hook hook;
+ spa_zero(hook);
+ spa_hook_remove(&hook);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_ringbuffer)
+{
+ struct spa_ringbuffer rb;
+ char buffer[20];
+ char readbuf[20];
+ uint32_t idx;
+ int32_t fill;
+
+ spa_ringbuffer_init(&rb);
+ fill = spa_ringbuffer_get_write_index(&rb, &idx);
+ pwtest_int_eq(idx, 0U);
+ pwtest_int_eq(fill, 0);
+
+ spa_ringbuffer_write_data(&rb, buffer, 20, idx, "hello pipewire", 14);
+ spa_ringbuffer_write_update(&rb, idx + 14);
+
+ fill = spa_ringbuffer_get_write_index(&rb, &idx);
+ pwtest_int_eq(idx, 14U);
+ pwtest_int_eq(fill, 14);
+ fill = spa_ringbuffer_get_read_index(&rb, &idx);
+ pwtest_int_eq(idx, 0U);
+ pwtest_int_eq(fill, 14);
+
+ spa_ringbuffer_read_data(&rb, buffer, 20, idx, readbuf, 6);
+ spa_ringbuffer_read_update(&rb, idx + 6);
+ pwtest_int_eq(memcmp(readbuf, "hello ", 6), 0);
+
+ fill = spa_ringbuffer_get_read_index(&rb, &idx);
+ pwtest_int_eq(idx, 6U);
+ pwtest_int_eq(fill, 8);
+ fill = spa_ringbuffer_get_write_index(&rb, &idx);
+ pwtest_int_eq(idx, 14U);
+ pwtest_int_eq(fill, 8);
+
+ spa_ringbuffer_write_data(&rb, buffer, 20, idx, " rocks !!!", 10);
+ spa_ringbuffer_write_update(&rb, idx + 10);
+
+ fill = spa_ringbuffer_get_write_index(&rb, &idx);
+ pwtest_int_eq(idx, 24U);
+ pwtest_int_eq(fill, 18);
+ fill = spa_ringbuffer_get_read_index(&rb, &idx);
+ pwtest_int_eq(idx, 6U);
+ pwtest_int_eq(fill, 18);
+
+ spa_ringbuffer_read_data(&rb, buffer, 20, idx, readbuf, 18);
+ spa_ringbuffer_read_update(&rb, idx + 18);
+ pwtest_str_eq_n(readbuf, "pipewire rocks !!!", 18);
+
+ fill = spa_ringbuffer_get_read_index(&rb, &idx);
+ pwtest_int_eq(idx, 24U);
+ pwtest_int_eq(fill, 0);
+ fill = spa_ringbuffer_get_write_index(&rb, &idx);
+ pwtest_int_eq(idx, 24U);
+ pwtest_int_eq(fill, 0);
+
+ /* actual buffer must have wrapped around */
+ pwtest_str_eq_n(buffer, " !!!o pipewire rocks", 20);
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strtol)
+{
+ int32_t v = 0xabcd;
+
+ pwtest_bool_true(spa_atoi32("0", &v, 0)); pwtest_int_eq(v, 0);
+ pwtest_bool_true(spa_atoi32("0", &v, 16)); pwtest_int_eq(v, 0);
+ pwtest_bool_true(spa_atoi32("0", &v, 32)); pwtest_int_eq(v, 0);
+ pwtest_bool_true(spa_atoi32("-1", &v, 0)); pwtest_int_eq(v, -1);
+ pwtest_bool_true(spa_atoi32("-1234", &v, 0)); pwtest_int_eq(v, -1234);
+ pwtest_bool_true(spa_atoi32("-2147483648", &v, 0)); pwtest_int_eq(v, -2147483648);
+ pwtest_bool_true(spa_atoi32("+1", &v, 0)); pwtest_int_eq(v, 1);
+ pwtest_bool_true(spa_atoi32("+1234", &v, 0)); pwtest_int_eq(v, 1234);
+ pwtest_bool_true(spa_atoi32("+2147483647", &v, 0)); pwtest_int_eq(v, 2147483647);
+ pwtest_bool_true(spa_atoi32("65535", &v, 0)); pwtest_int_eq(v, 0xffff);
+ pwtest_bool_true(spa_atoi32("65535", &v, 10)); pwtest_int_eq(v, 0xffff);
+ pwtest_bool_true(spa_atoi32("65535", &v, 16)); pwtest_int_eq(v, 0x65535);
+ pwtest_bool_true(spa_atoi32("0xff", &v, 0)); pwtest_int_eq(v, 0xff);
+ pwtest_bool_true(spa_atoi32("0xff", &v, 16)); pwtest_int_eq(v, 0xff);
+
+ v = 0xabcd;
+ pwtest_bool_false(spa_atoi32("0xff", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("fabc", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("fabc", &v, 0)); pwtest_int_eq(v, 0xabcd);
+
+ pwtest_bool_false(spa_atoi32("124bogus", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("124bogus", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("124bogus", &v, 16)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("0xbogus", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("bogus", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("bogus", &v, 16)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("", &v, 16)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32(" ", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32(" ", &v, 0)); pwtest_int_eq(v, 0xabcd);
+
+ pwtest_bool_false(spa_atoi32("-2147483649", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("2147483648", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("9223372036854775807", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("-9223372036854775808", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32("9223372036854775808999", &v, 0)); pwtest_int_eq(v, 0xabcd);
+
+ pwtest_bool_false(spa_atoi32(NULL, &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32(NULL, &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi32(NULL, &v, 16)); pwtest_int_eq(v, 0xabcd);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strtoul)
+{
+ uint32_t v = 0xabcd;
+
+ pwtest_bool_true(spa_atou32("0", &v, 0)); pwtest_int_eq(v, 0U);
+ pwtest_bool_true(spa_atou32("0", &v, 16)); pwtest_int_eq(v, 0U);
+ pwtest_bool_true(spa_atou32("0", &v, 32)); pwtest_int_eq(v, 0U);
+ pwtest_bool_true(spa_atou32("+1", &v, 0)); pwtest_int_eq(v, 1U);
+ pwtest_bool_true(spa_atou32("+1234", &v, 0)); pwtest_int_eq(v, 1234U);
+ pwtest_bool_true(spa_atou32("+4294967295", &v, 0)); pwtest_int_eq(v, 4294967295U);
+ pwtest_bool_true(spa_atou32("4294967295", &v, 0)); pwtest_int_eq(v, 4294967295U);
+ pwtest_bool_true(spa_atou32("65535", &v, 0)); pwtest_int_eq(v, 0xffffU);
+ pwtest_bool_true(spa_atou32("65535", &v, 10)); pwtest_int_eq(v, 0xffffU);
+ pwtest_bool_true(spa_atou32("65535", &v, 16)); pwtest_int_eq(v, 0x65535U);
+ pwtest_bool_true(spa_atou32("0xff", &v, 0)); pwtest_int_eq(v, 0xffU);
+ pwtest_bool_true(spa_atou32("0xff", &v, 16)); pwtest_int_eq(v, 0xffU);
+
+ v = 0xabcd;
+ pwtest_bool_false(spa_atou32("-1", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("-1234", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("-2147483648", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("0xff", &v, 10)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("fabc", &v, 10)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("fabc", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+
+ pwtest_bool_false(spa_atou32("124bogus", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("124bogus", &v, 10)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("124bogus", &v, 16)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("0xbogus", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("bogus", &v, 10)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("bogus", &v, 16)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("", &v, 10)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("", &v, 16)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32(" ", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32(" ", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+
+ pwtest_bool_false(spa_atou32("-2147483649", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("4294967296", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("9223372036854775807", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("-9223372036854775808", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32("9223372036854775808999", &v, 0)); pwtest_int_eq(v, 0xabcdU);
+
+ pwtest_bool_false(spa_atou32(NULL, &v, 0)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32(NULL, &v, 10)); pwtest_int_eq(v, 0xabcdU);
+ pwtest_bool_false(spa_atou32(NULL, &v, 16)); pwtest_int_eq(v, 0xabcdU);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strtoll)
+{
+ int64_t v = 0xabcd;
+
+ pwtest_bool_true(spa_atoi64("0", &v, 0)); pwtest_int_eq(v, 0);
+ pwtest_bool_true(spa_atoi64("0", &v, 16)); pwtest_int_eq(v, 0);
+ pwtest_bool_true(spa_atoi64("0", &v, 32)); pwtest_int_eq(v, 0);
+ pwtest_bool_true(spa_atoi64("-1", &v, 0)); pwtest_int_eq(v, -1);
+ pwtest_bool_true(spa_atoi64("-1234", &v, 0)); pwtest_int_eq(v, -1234);
+ pwtest_bool_true(spa_atoi64("-2147483648", &v, 0)); pwtest_int_eq(v, -2147483648);
+ pwtest_bool_true(spa_atoi64("+1", &v, 0)); pwtest_int_eq(v, 1);
+ pwtest_bool_true(spa_atoi64("+1234", &v, 0)); pwtest_int_eq(v, 1234);
+ pwtest_bool_true(spa_atoi64("+2147483647", &v, 0)); pwtest_int_eq(v, 2147483647);
+ pwtest_bool_true(spa_atoi64("65535", &v, 0)); pwtest_int_eq(v, 0xffff);
+ pwtest_bool_true(spa_atoi64("65535", &v, 10)); pwtest_int_eq(v, 0xffff);
+ pwtest_bool_true(spa_atoi64("65535", &v, 16)); pwtest_int_eq(v, 0x65535);
+ pwtest_bool_true(spa_atoi64("0xff", &v, 0)); pwtest_int_eq(v, 0xff);
+ pwtest_bool_true(spa_atoi64("0xff", &v, 16)); pwtest_int_eq(v, 0xff);
+ pwtest_bool_true(spa_atoi64("9223372036854775807", &v, 0)); pwtest_int_eq(v, 0x7fffffffffffffff);
+ pwtest_bool_true(spa_atoi64("-9223372036854775808", &v, 0)); pwtest_int_eq((uint64_t)v, 0x8000000000000000);
+
+ v = 0xabcd;
+ pwtest_bool_false(spa_atoi64("0xff", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("fabc", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("fabc", &v, 0)); pwtest_int_eq(v, 0xabcd);
+
+ pwtest_bool_false(spa_atoi64("124bogus", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("124bogus", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("124bogus", &v, 16)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("0xbogus", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("bogus", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("bogus", &v, 16)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("", &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64("", &v, 16)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64(" ", &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64(" ", &v, 0)); pwtest_int_eq(v, 0xabcd);
+
+ pwtest_bool_false(spa_atoi64("9223372036854775808999", &v, 0)); pwtest_int_eq(v, 0xabcd);
+
+ pwtest_bool_false(spa_atoi64(NULL, &v, 0)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64(NULL, &v, 10)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atoi64(NULL, &v, 16)); pwtest_int_eq(v, 0xabcd);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strtof)
+{
+ float v = 0xabcd;
+
+ setlocale(LC_NUMERIC, "C"); /* For decimal number parsing */
+
+ pwtest_bool_true(spa_atof("0", &v)); pwtest_double_eq(v, 0.0f);
+ pwtest_bool_true(spa_atof("0.00", &v)); pwtest_double_eq(v, 0.0f);
+ pwtest_bool_true(spa_atof("1", &v)); pwtest_double_eq(v, 1.0f);
+ pwtest_bool_true(spa_atof("-1", &v)); pwtest_double_eq(v, -1.0f);
+ pwtest_bool_true(spa_atof("0x1", &v)); pwtest_double_eq(v, 1.0f);
+
+ v = 0xabcd;
+ pwtest_bool_false(spa_atof("0,00", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atof("fabc", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atof("1.bogus", &v));pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atof("1.0a", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atof(" ", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atof(" ", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atof("", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atof(NULL, &v)); pwtest_int_eq(v, 0xabcd);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strtod)
+{
+ double v = 0xabcd;
+
+ pwtest_bool_true(spa_atod("0", &v)); pwtest_double_eq(v, 0.0);
+ pwtest_bool_true(spa_atod("0.00", &v)); pwtest_double_eq(v, 0.0);
+ pwtest_bool_true(spa_atod("1", &v)); pwtest_double_eq(v, 1.0);
+ pwtest_bool_true(spa_atod("-1", &v)); pwtest_double_eq(v, -1.0);
+ pwtest_bool_true(spa_atod("0x1", &v)); pwtest_double_eq(v, 1.0);
+
+ v = 0xabcd;
+ pwtest_bool_false(spa_atod("0,00", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atod("fabc", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atod("1.bogus", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atod("1.0a", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atod(" ", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atod(" ", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atod("", &v)); pwtest_int_eq(v, 0xabcd);
+ pwtest_bool_false(spa_atod(NULL, &v)); pwtest_int_eq(v, 0xabcd);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_streq)
+{
+ pwtest_bool_true(spa_streq(NULL, NULL));
+ pwtest_bool_true(spa_streq("", ""));
+ pwtest_bool_true(spa_streq("a", "a"));
+ pwtest_bool_true(spa_streq("abc", "abc"));
+ pwtest_bool_false(spa_streq(NULL, "abc"));
+ pwtest_bool_false(spa_streq("abc", NULL));
+
+ pwtest_bool_true(spa_strneq("abc", "aaa", 1));
+ pwtest_bool_true(spa_strneq("abc", "abc", 7));
+ pwtest_bool_false(spa_strneq("abc", "aaa", 2));
+ pwtest_bool_false(spa_strneq("abc", NULL, 7));
+ pwtest_bool_false(spa_strneq(NULL, "abc", 7));
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strendswith)
+{
+ pwtest_bool_true(spa_strendswith("foo", "o"));
+ pwtest_bool_true(spa_strendswith("foobar", "bar"));
+
+ pwtest_bool_false(spa_strendswith(NULL, "bar"));
+ pwtest_bool_false(spa_strendswith("foo", "f"));
+ pwtest_bool_false(spa_strendswith("foo", "fo"));
+ pwtest_bool_false(spa_strendswith("foo", "foobar"));
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strendswith_null_suffix)
+{
+ spa_strendswith("foo", NULL);
+
+ return PWTEST_FAIL;
+}
+
+PWTEST(utils_atob)
+{
+ pwtest_bool_true(spa_atob("true"));
+ pwtest_bool_true(spa_atob("1"));
+ pwtest_bool_false(spa_atob("0"));
+ pwtest_bool_false(spa_atob("-1"));
+ pwtest_bool_false(spa_atob("10"));
+ pwtest_bool_false(spa_atob("11"));
+ pwtest_bool_false(spa_atob("t"));
+ pwtest_bool_false(spa_atob("yes"));
+ pwtest_bool_false(spa_atob("no"));
+ pwtest_bool_false(spa_atob(NULL));
+ pwtest_bool_false(spa_atob("True")); /* lower-case required */
+ pwtest_bool_false(spa_atob("TRUE"));
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_ansi)
+{
+ /* Visual test only */
+ printf("%sBOLD%s\n", SPA_ANSI_BOLD, SPA_ANSI_RESET);
+ printf("%sUNDERLINE%s\n", SPA_ANSI_UNDERLINE, SPA_ANSI_RESET);
+ printf("%sITALIC%s\n", SPA_ANSI_ITALIC, SPA_ANSI_RESET);
+
+ printf("%sBLACK%s\n", SPA_ANSI_BLACK, SPA_ANSI_RESET);
+ printf("%sBRIGHT_BLACK%s\n", SPA_ANSI_BRIGHT_BLACK, SPA_ANSI_RESET);
+ printf("%sDARK_BLACK%s\n", SPA_ANSI_DARK_BLACK, SPA_ANSI_RESET);
+ printf("%sBOLD_BLACK%s\n", SPA_ANSI_BOLD_BLACK, SPA_ANSI_RESET);
+
+ printf("%sRED%s\n", SPA_ANSI_RED, SPA_ANSI_RESET);
+ printf("%sBRIGHT_RED%s\n", SPA_ANSI_BRIGHT_RED, SPA_ANSI_RESET);
+ printf("%sDARK_RED%s\n", SPA_ANSI_DARK_RED, SPA_ANSI_RESET);
+ printf("%sBOLD_RED%s\n", SPA_ANSI_BOLD_RED, SPA_ANSI_RESET);
+
+ printf("%sGREEN%s\n", SPA_ANSI_GREEN, SPA_ANSI_RESET);
+ printf("%sBRIGHT_GREEN%s\n", SPA_ANSI_BRIGHT_GREEN, SPA_ANSI_RESET);
+ printf("%sDARK_GREEN%s\n", SPA_ANSI_DARK_GREEN, SPA_ANSI_RESET);
+ printf("%sBOLD_GREEN%s\n", SPA_ANSI_BOLD_GREEN, SPA_ANSI_RESET);
+
+ printf("%sYELLOW%s\n", SPA_ANSI_YELLOW, SPA_ANSI_RESET);
+ printf("%sBRIGHT_YELLOW%s\n", SPA_ANSI_BRIGHT_YELLOW, SPA_ANSI_RESET);
+ printf("%sDARK_YELLOW%s\n", SPA_ANSI_DARK_YELLOW, SPA_ANSI_RESET);
+ printf("%sBOLD_YELLOW%s\n", SPA_ANSI_BOLD_YELLOW, SPA_ANSI_RESET);
+
+ printf("%sBLUE%s\n", SPA_ANSI_BLUE, SPA_ANSI_RESET);
+ printf("%sBRIGHT_BLUE%s\n", SPA_ANSI_BRIGHT_BLUE, SPA_ANSI_RESET);
+ printf("%sDARK_BLUE%s\n", SPA_ANSI_DARK_BLUE, SPA_ANSI_RESET);
+ printf("%sBOLD_BLUE%s\n", SPA_ANSI_BOLD_BLUE, SPA_ANSI_RESET);
+
+ printf("%sMAGENTA%s\n", SPA_ANSI_MAGENTA, SPA_ANSI_RESET);
+ printf("%sBRIGHT_MAGENTA%s\n", SPA_ANSI_BRIGHT_MAGENTA, SPA_ANSI_RESET);
+ printf("%sDARK_MAGENTA%s\n", SPA_ANSI_DARK_MAGENTA, SPA_ANSI_RESET);
+ printf("%sBOLD_MAGENTA%s\n", SPA_ANSI_BOLD_MAGENTA, SPA_ANSI_RESET);
+
+ printf("%sCYAN%s\n", SPA_ANSI_CYAN, SPA_ANSI_RESET);
+ printf("%sBRIGHT_CYAN%s\n", SPA_ANSI_BRIGHT_CYAN, SPA_ANSI_RESET);
+ printf("%sDARK_CYAN%s\n", SPA_ANSI_DARK_CYAN, SPA_ANSI_RESET);
+ printf("%sBOLD_CYAN%s\n", SPA_ANSI_BOLD_CYAN, SPA_ANSI_RESET);
+
+ printf("%sWHITE%s\n", SPA_ANSI_WHITE, SPA_ANSI_RESET);
+ printf("%sBRIGHT_WHITE%s\n", SPA_ANSI_BRIGHT_WHITE, SPA_ANSI_RESET);
+ printf("%sDARK_WHITE%s\n", SPA_ANSI_DARK_WHITE, SPA_ANSI_RESET);
+ printf("%sBOLD_WHITE%s\n", SPA_ANSI_BOLD_WHITE, SPA_ANSI_RESET);
+
+
+ /* Background colors */
+
+ printf("%sBG_BLACK%s\n", SPA_ANSI_BG_BLACK, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_BLACK%s\n", SPA_ANSI_BG_BRIGHT_BLACK, SPA_ANSI_RESET);
+
+ printf("%sBG_RED%s\n", SPA_ANSI_BG_RED, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_RED%s\n", SPA_ANSI_BG_BRIGHT_RED, SPA_ANSI_RESET);
+
+ printf("%sBG_GREEN%s\n", SPA_ANSI_BG_GREEN, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_GREEN%s\n", SPA_ANSI_BG_BRIGHT_GREEN, SPA_ANSI_RESET);
+
+ printf("%sBG_YELLOW%s\n", SPA_ANSI_BG_YELLOW, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_YELLOW%s\n", SPA_ANSI_BG_BRIGHT_YELLOW, SPA_ANSI_RESET);
+
+ printf("%sBG_BLUE%s\n", SPA_ANSI_BG_BLUE, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_BLUE%s\n", SPA_ANSI_BG_BRIGHT_BLUE, SPA_ANSI_RESET);
+
+ printf("%sBG_MAGENTA%s\n", SPA_ANSI_BG_MAGENTA, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_MAGENTA%s\n", SPA_ANSI_BG_BRIGHT_MAGENTA, SPA_ANSI_RESET);
+
+ printf("%sBG_CYAN%s\n", SPA_ANSI_BG_CYAN, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_CYAN%s\n", SPA_ANSI_BG_BRIGHT_CYAN, SPA_ANSI_RESET);
+
+ printf("%sBG_WHITE%s\n", SPA_ANSI_BG_WHITE, SPA_ANSI_RESET);
+ printf("%sBG_BRIGHT_WHITE%s\n", SPA_ANSI_BG_BRIGHT_WHITE, SPA_ANSI_RESET);
+
+ /* A combo */
+ printf("normal%s%s%sBG_BLUE,ITALIC,BOLD_YELLOW%snormal\n", SPA_ANSI_BG_BLUE,
+ SPA_ANSI_ITALIC, SPA_ANSI_BOLD_YELLOW, SPA_ANSI_RESET);
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_snprintf)
+{
+ char dest[8];
+ int len;
+
+ /* Basic printf */
+ pwtest_int_eq(spa_scnprintf(dest, sizeof(dest), "foo%d%s", 10, "2"), 6);
+ pwtest_str_eq(dest, "foo102");
+ /* Print a few strings, make sure dest is truncated and return value
+ * is the length of the returned string */
+ pwtest_int_eq(spa_scnprintf(dest, sizeof(dest), "1234567"), 7);
+ pwtest_str_eq(dest, "1234567");
+ pwtest_int_eq(spa_scnprintf(dest, sizeof(dest), "12345678"), 7);
+ pwtest_str_eq(dest, "1234567");
+ pwtest_int_eq(spa_scnprintf(dest, sizeof(dest), "123456789"), 7);
+ pwtest_str_eq(dest, "1234567");
+ /* Same as above, but with printf %s expansion */
+ pwtest_int_eq(spa_scnprintf(dest, sizeof(dest), "%s", "1234567"), 7);
+ pwtest_str_eq(dest, "1234567");
+ pwtest_int_eq(spa_scnprintf(dest, sizeof(dest), "%s", "12345678"), 7);
+ pwtest_str_eq(dest, "1234567");
+ pwtest_int_eq(spa_scnprintf(dest, sizeof(dest), "%s", "123456789"), 7);
+ pwtest_str_eq(dest, "1234567");
+
+ pwtest_int_eq(spa_scnprintf(dest, 2, "1234567"), 1);
+ pwtest_str_eq(dest, "1");
+ pwtest_int_eq(spa_scnprintf(dest, 1, "1234567"), 0);
+ pwtest_str_eq(dest, "");
+
+ /* The "append until buffer is full" use-case */
+ len = 0;
+ while ((size_t)len < sizeof(dest) - 1)
+ len += spa_scnprintf(dest + len, sizeof(dest) - len, "123");
+ /* and once more for good measure, this should print 0 characters */
+ len = spa_scnprintf(dest + len, sizeof(dest) - len, "abc");
+ pwtest_int_eq(len, 0);
+ pwtest_str_eq(dest, "1231231");
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_snprintf_abort_neg_size)
+{
+ size_t size = pwtest_get_iteration(current_test);
+ char dest[8];
+
+ if (RUNNING_ON_VALGRIND)
+ return PWTEST_SKIP;
+
+ spa_scnprintf(dest, size, "1234"); /* expected to abort() */
+
+ return PWTEST_FAIL;
+}
+
+struct cbtest_data {
+ bool invoked;
+ const char *data;
+};
+
+static void cbtest_func(void *object, const char *msg)
+{
+ struct cbtest_data *data = object;
+ data->invoked = true;
+ data->data = msg;
+}
+
+PWTEST(utils_callback)
+{
+ struct cbtest_methods {
+ uint32_t version;
+ void (*func_v0)(void *object, const char *msg);
+ void (*func_v1)(void *object, const char *msg);
+ } methods = { 0, cbtest_func, cbtest_func };
+ struct cbtest {
+ struct spa_interface iface;
+ } cbtest;
+ struct cbtest_data data;
+
+ /* Interface version doesn't matter for this test */
+ cbtest.iface = SPA_INTERFACE_INIT("cbtest type", 0, &methods, &data);
+
+ /* Methods are version 0 */
+ methods.version = 0;
+ data.invoked = false;
+ spa_interface_call(&cbtest.iface,
+ struct cbtest_methods,
+ func_v0, 0, "cbtest v0");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.data, "cbtest v0");
+
+ /* v1 call should be silently filtered */
+ data.invoked = false;
+ spa_interface_call(&cbtest.iface,
+ struct cbtest_methods,
+ func_v1, 1, "cbtest v1");
+ pwtest_bool_false(data.invoked);
+
+ /* Methods are version 1 */
+ methods.version = 1;
+ data.invoked = false;
+ spa_interface_call(&cbtest.iface,
+ struct cbtest_methods,
+ func_v0, 0, "cbtest v0");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.data, "cbtest v0");
+
+ /* v1 call expected to be called */
+ data.invoked = false;
+ spa_interface_call(&cbtest.iface,
+ struct cbtest_methods,
+ func_v1, 1, "cbtest v1");
+ pwtest_bool_true(data.invoked);
+ pwtest_str_eq(data.data, "cbtest v1");
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_callback_func_is_null)
+{
+ struct cbtest_methods {
+ uint32_t version;
+ void (*func_v0)(void *object, const char *msg);
+ void (*func_v1)(void *object, const char *msg);
+ } methods = { 0, NULL, NULL };
+ struct cbtest {
+ struct spa_interface iface;
+ } cbtest;
+ struct cbtest_data data;
+
+ /* Interface version doesn't matter for this test */
+ cbtest.iface = SPA_INTERFACE_INIT("cbtest type", 0, &methods, &data);
+
+ /* Methods are version 0 */
+ methods.version = 0;
+
+ /* func_v0 and func_v1 are NULL so this shouldn't crash */
+ data.invoked = false;
+ spa_interface_call(&cbtest.iface,
+ struct cbtest_methods,
+ func_v0, 0, "cbtest v0");
+ pwtest_bool_false(data.invoked);
+ spa_interface_call(&cbtest.iface,
+ struct cbtest_methods,
+ func_v1, 0, "cbtest v1");
+ pwtest_bool_false(data.invoked);
+
+ /* func_v1 is NULL so this shouldn't crash, though the call should get
+ * filtered anyway due to version mismatch */
+ spa_interface_call(&cbtest.iface,
+ struct cbtest_methods,
+ func_v1, 1, "cbtest v1");
+ pwtest_bool_false(data.invoked);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_callback_version)
+{
+ struct cbtest_methods {
+ uint32_t version;
+ void (*func_v0)(void *object, const char *msg);
+ } methods = { 0, cbtest_func };
+ struct cbtest {
+ struct spa_interface iface;
+ } cbtest;
+ struct cbtest_data data;
+
+ /* Interface version doesn't matter for this test */
+ cbtest.iface = SPA_INTERFACE_INIT("cbtest type", 0, &methods, &data);
+
+ /* Methods are version 0 */
+ methods.version = 0;
+ pwtest_bool_true(spa_interface_callback_version_min(&cbtest.iface,
+ struct cbtest_methods,
+ 0));
+ pwtest_bool_false(spa_interface_callback_version_min(&cbtest.iface,
+ struct cbtest_methods,
+ 1));
+ /* Methods are version 1 */
+ methods.version = 1;
+ pwtest_bool_true(spa_interface_callback_version_min(&cbtest.iface,
+ struct cbtest_methods,
+ 0));
+ pwtest_bool_true(spa_interface_callback_version_min(&cbtest.iface,
+ struct cbtest_methods,
+ 1));
+ pwtest_bool_false(spa_interface_callback_version_min(&cbtest.iface,
+ struct cbtest_methods,
+ 2));
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(spa_utils)
+{
+ pwtest_add(utils_abi_sizes, PWTEST_NOARG);
+ pwtest_add(utils_abi, PWTEST_NOARG);
+ pwtest_add(utils_macros, PWTEST_NOARG);
+ pwtest_add(utils_result, PWTEST_NOARG);
+ pwtest_add(utils_dict, PWTEST_NOARG);
+ pwtest_add(utils_list, PWTEST_NOARG);
+ pwtest_add(utils_hook, PWTEST_NOARG);
+ pwtest_add(utils_ringbuffer, PWTEST_NOARG);
+ pwtest_add(utils_strtol, PWTEST_NOARG);
+ pwtest_add(utils_strtoul, PWTEST_NOARG);
+ pwtest_add(utils_strtoll, PWTEST_NOARG);
+ pwtest_add(utils_strtof, PWTEST_NOARG);
+ pwtest_add(utils_strtod, PWTEST_NOARG);
+ pwtest_add(utils_streq, PWTEST_NOARG);
+ pwtest_add(utils_strendswith, PWTEST_NOARG);
+ pwtest_add(utils_strendswith_null_suffix,
+ PWTEST_ARG_SIGNAL, SIGABRT);
+ pwtest_add(utils_snprintf, PWTEST_NOARG);
+ pwtest_add(utils_snprintf_abort_neg_size,
+ PWTEST_ARG_SIGNAL, SIGABRT,
+ PWTEST_ARG_RANGE, -2, 0);
+ pwtest_add(utils_atob, PWTEST_NOARG);
+ pwtest_add(utils_ansi, PWTEST_NOARG);
+ pwtest_add(utils_callback, PWTEST_NOARG);
+ pwtest_add(utils_callback_func_is_null, PWTEST_NOARG);
+ pwtest_add(utils_callback_version, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-support.c b/test/test-support.c
new file mode 100644
index 0000000..e8a18f0
--- /dev/null
+++ b/test/test-support.c
@@ -0,0 +1,88 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+
+#include <spa/utils/names.h>
+#include <spa/support/plugin.h>
+#include <spa/support/log.h>
+
+PWTEST(pwtest_load_nonexisting)
+{
+ struct pwtest_spa_plugin *plugin;
+ void *iface;
+
+ plugin = pwtest_spa_plugin_new();
+
+ pwtest_neg_errno_check(
+ pwtest_spa_plugin_try_load_interface(plugin, &iface,
+ "support/does_not_exist",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ NULL),
+ -ENOENT);
+
+ pwtest_neg_errno_check(
+ pwtest_spa_plugin_try_load_interface(plugin, &iface,
+ "support/libspa-support",
+ "foo.bar", SPA_TYPE_INTERFACE_Log,
+ NULL),
+ -EINVAL);
+
+ pwtest_neg_errno_check(
+ pwtest_spa_plugin_try_load_interface(plugin, &iface,
+ "support/libspa-support",
+ SPA_NAME_SUPPORT_LOG,
+ "foo", NULL),
+ -ENOSYS);
+
+ pwtest_spa_plugin_destroy(plugin);
+
+ return PWTEST_PASS;
+}
+
+PWTEST(pwtest_load_plugin)
+{
+ struct pwtest_spa_plugin *plugin;
+ void *iface;
+
+ plugin = pwtest_spa_plugin_new();
+
+ pwtest_neg_errno_ok(
+ pwtest_spa_plugin_try_load_interface(plugin, &iface,
+ "support/libspa-support",
+ SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log,
+ NULL)
+ );
+
+ pwtest_spa_plugin_destroy(plugin);
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(support)
+{
+ pwtest_add(pwtest_load_nonexisting, PWTEST_NOARG);
+ pwtest_add(pwtest_load_plugin, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}
diff --git a/test/test-utils.c b/test/test-utils.c
new file mode 100644
index 0000000..84a2fcb
--- /dev/null
+++ b/test/test-utils.c
@@ -0,0 +1,253 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pwtest.h"
+#include <limits.h>
+
+#include <pipewire/utils.h>
+
+#include <spa/utils/string.h>
+
+static void test_destroy(void *object)
+{
+ pwtest_fail_if_reached();
+}
+
+PWTEST(utils_abi)
+{
+ pw_destroy_t f = test_destroy;
+
+ pwtest_ptr_eq(f, &test_destroy);
+
+ return PWTEST_PASS;
+}
+
+static void test__pw_split_walk(void)
+{
+ const struct test_case {
+ const char * const input;
+ const char * const delim;
+ const char * const * const expected;
+ } test_cases[] = {
+ {
+ .input = "a \n test string \n \r ",
+ .delim = " \r\n",
+ .expected = (const char *[]) {
+ "a",
+ "test",
+ "string",
+ NULL
+ },
+ },
+ {
+ .input = "::field1::field2:: field3:::::",
+ .delim = ":",
+ .expected = (const char *[]) {
+ "field1",
+ "field2",
+ " field3",
+ NULL
+ },
+ },
+ {
+ .input = ",,,,,,,,,,,,",
+ .delim = ",",
+ .expected = (const char *[]) {
+ NULL
+ },
+ },
+ {
+ .input = ",;,,,'''':::':::,,,,;",
+ .delim = ",:';",
+ .expected = (const char *[]) {
+ NULL
+ },
+ },
+ {
+ .input = "aaa:bbb,ccc##ddd/#,eee?fff...",
+ .delim = ":,#/?",
+ .expected = (const char *[]) {
+ "aaa",
+ "bbb",
+ "ccc",
+ "ddd",
+ "eee",
+ "fff...",
+ NULL
+ },
+ },
+ {
+ .input = "line 1\na different line\nthe third line\n",
+ .delim = "\n",
+ .expected = (const char *[]) {
+ "line 1",
+ "a different line",
+ "the third line",
+ NULL
+ },
+ },
+ {
+ .input = "no delimiters",
+ .delim = ",:/;",
+ .expected = (const char *[]) {
+ "no delimiters",
+ NULL
+ },
+ },
+ {
+ .input = "delimiter at the end,;",
+ .delim = ",;",
+ .expected = (const char *[]) {
+ "delimiter at the end",
+ NULL
+ },
+ },
+ {
+ .input = "/delimiter on both ends,",
+ .delim = "/,",
+ .expected = (const char *[]) {
+ "delimiter on both ends",
+ NULL
+ },
+ },
+ {
+ .input = ",delimiter at the beginning",
+ .delim = ",",
+ .expected = (const char *[]) {
+ "delimiter at the beginning",
+ NULL
+ },
+ },
+ {
+ .input = "/usr/lib/pipewire-0.3/libpipewire.so",
+ .delim = "/",
+ .expected = (const char *[]) {
+ "usr",
+ "lib",
+ "pipewire-0.3",
+ "libpipewire.so",
+ NULL
+ }
+ },
+ {
+ .input = "/home/x/.ladspa:/usr/lib/ladspa:/usr/local/lib/ladspa",
+ .delim = ":",
+ .expected = (const char *[]) {
+ "/home/x/.ladspa",
+ "/usr/lib/ladspa",
+ "/usr/local/lib/ladspa",
+ NULL
+ }
+ },
+ {
+ .input = "\n field1 \t\n field2 \t \t field3",
+ .delim = " \n\t",
+ .expected = (const char *[]) {
+ "field1",
+ "field2",
+ "field3",
+ NULL
+ }
+ },
+ };
+
+ SPA_FOR_EACH_ELEMENT_VAR(test_cases, tc) {
+ const char *str = tc->input, *s;
+ const char *state = NULL;
+ size_t j = 0, len;
+
+ while ((s = pw_split_walk(str, tc->delim, &len, &state)) != NULL && tc->expected[j] != NULL) {
+ pwtest_int_eq(strlen(tc->expected[j]), len);
+ pwtest_str_eq_n(s, tc->expected[j], len);
+
+ j += 1;
+ }
+
+ pwtest_ptr_null(s);
+ pwtest_ptr_null(tc->expected[j]);
+ }
+}
+
+static void test__pw_split_strv(void)
+{
+ const char *test1 = "a \n test string \n \r ";
+ const char *del = "\n\r ";
+ const char *test2 = "a:";
+ const char *del2 = ":";
+ int n_tokens;
+ char **res;
+
+ res = pw_split_strv(test1, del, INT_MAX, &n_tokens);
+ pwtest_ptr_notnull(res);
+ pwtest_int_eq(n_tokens, 3);
+ pwtest_str_eq(res[0], "a");
+ pwtest_str_eq(res[1], "test");
+ pwtest_str_eq(res[2], "string");
+ pwtest_ptr_null(res[3]);
+ pw_free_strv(res);
+
+ res = pw_split_strv(test1, del, 2, &n_tokens);
+ pwtest_ptr_notnull(res);
+ pwtest_int_eq(n_tokens, 2);
+ pwtest_str_eq(res[0], "a");
+ pwtest_str_eq(res[1], "test string \n \r ");
+ pwtest_ptr_null(res[2]);
+ pw_free_strv(res);
+
+ res = pw_split_strv(test2, del2, 2, &n_tokens);
+ pwtest_ptr_notnull(res);
+ pwtest_int_eq(n_tokens, 1);
+ pwtest_str_eq(res[0], "a");
+ pwtest_ptr_null(res[1]);
+ pw_free_strv(res);
+}
+
+PWTEST(utils_split)
+{
+ test__pw_split_walk();
+ test__pw_split_strv();
+
+ return PWTEST_PASS;
+}
+
+PWTEST(utils_strip)
+{
+ char test1[] = " \n\r \n a test string \n \r ";
+ char test2[] = " \n\r \n \n \r ";
+ char test3[] = "a test string";
+ spa_assert_se(spa_streq(pw_strip(test1, "\n\r "), "a test string"));
+ spa_assert_se(spa_streq(pw_strip(test2, "\n\r "), ""));
+ spa_assert_se(spa_streq(pw_strip(test3, "\n\r "), "a test string"));
+
+ return PWTEST_PASS;
+}
+
+PWTEST_SUITE(utils)
+{
+ pwtest_add(utils_abi, PWTEST_NOARG);
+ pwtest_add(utils_split, PWTEST_NOARG);
+ pwtest_add(utils_strip, PWTEST_NOARG);
+
+ return PWTEST_PASS;
+}