diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
commit | 7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch) | |
tree | d483300dab478b994fe199a5d19d18d74153718a /test | |
parent | Initial commit. (diff) | |
download | pipewire-0bfb2679f751193be0325ef92c84c3863d22ac84.tar.xz pipewire-0bfb2679f751193be0325ef92c84c3863d22ac84.zip |
Adding upstream version 0.3.65.upstream/0.3.65upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test')
-rw-r--r-- | test/meson.build | 156 | ||||
-rw-r--r-- | test/pwtest-compat.c | 55 | ||||
-rw-r--r-- | test/pwtest-implementation.h | 137 | ||||
-rw-r--r-- | test/pwtest.c | 1486 | ||||
-rw-r--r-- | test/pwtest.h | 577 | ||||
-rw-r--r-- | test/test-array.c | 146 | ||||
-rw-r--r-- | test/test-client.c | 70 | ||||
-rw-r--r-- | test/test-config.c | 102 | ||||
-rw-r--r-- | test/test-context.c | 277 | ||||
-rw-r--r-- | test/test-example.c | 265 | ||||
-rw-r--r-- | test/test-functional.c | 58 | ||||
-rw-r--r-- | test/test-lib.c | 69 | ||||
-rw-r--r-- | test/test-logger.c | 656 | ||||
-rw-r--r-- | test/test-loop.c | 463 | ||||
-rw-r--r-- | test/test-map.c | 242 | ||||
-rw-r--r-- | test/test-properties.c | 629 | ||||
-rw-r--r-- | test/test-pwtest.c | 55 | ||||
-rw-r--r-- | test/test-spa-buffer.c | 150 | ||||
-rw-r--r-- | test/test-spa-json.c | 333 | ||||
-rw-r--r-- | test/test-spa-log.c | 213 | ||||
-rw-r--r-- | test/test-spa-node.c | 251 | ||||
-rw-r--r-- | test/test-spa-pod.c | 1706 | ||||
-rw-r--r-- | test/test-spa-utils.c | 1043 | ||||
-rw-r--r-- | test/test-support.c | 88 | ||||
-rw-r--r-- | test/test-utils.c | 253 |
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; +} |