From b3320ac64a3d3dd2257d147294a7050b227f355b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sat, 6 Jul 2024 18:29:49 +0200 Subject: [PATCH] test/libmpv_lifetime: add test to test libmpv ability to reinit itself This test: - Checks if libmpv can be loaded dynamically. - Checks for leaks after mpv context destroy. - Checks if libmpv can be reloads after dlclose() --- test/libmpv_lifetime.c | 135 +++++++++++++++++++++++++++++++++++++++++ test/meson.build | 12 ++++ 2 files changed, 147 insertions(+) create mode 100644 test/libmpv_lifetime.c diff --git a/test/libmpv_lifetime.c b/test/libmpv_lifetime.c new file mode 100644 index 0000000000..1a97edb025 --- /dev/null +++ b/test/libmpv_lifetime.c @@ -0,0 +1,135 @@ +#include +#include +#include +#include + +#include + +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define __SANITIZE_ADDRESS__ +#endif +#endif + +#ifdef __SANITIZE_ADDRESS__ +#include +#endif + +// Check only the first iteration, before dlclose() happens. LSAN does not track +// unloaded modules, so reports are not very readable and require manual processing. +// Shared libraries often don't fully clean up after themselves. Ideally, these +// cases should be investigated at some point. +#define LSAN_IGNORE_DLCLOSE + +#ifdef _WIN32 +#include + +#define LOAD_LIB() HMODULE lib = LoadLibraryW(L"libmpv-2.dll") +#define CLOSE_LIB() FreeLibrary(lib) +#define GET_SYM GetProcAddress +#else +#include + +#ifdef __APPLE__ +#define LIB_NAME "libmpv.2.dylib" +#else +#define LIB_NAME "libmpv.so" +#endif +#define LOAD_LIB() void *lib = dlopen(LIB_NAME, RTLD_NOW | RTLD_LOCAL) +#define CLOSE_LIB() dlclose(lib) +#define GET_SYM dlsym +#endif + +#define INIT_SYM(name) __typeof__(&mpv_##name) name = (void *) GET_SYM(lib, "mpv_" #name); \ + if (!name) exit(1) + +#define REPEAT 2 + +static void exit_log(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + exit(1); +} + +#define check_error(status) check_error_(status, error_string) +static inline void check_error_(int status, __typeof__(&mpv_error_string) error_string) +{ + if (status < 0) + exit_log("mpv API error: %s\n", error_string(status)); +} + +int main(void) +{ +#ifdef __APPLE__ + // FIXME: Hangs after libplacebo initialization + return 77; +#endif + + // Skip this test when run through a wrapper like Wine. It is well-tested on + // different configurations. Meson does not set PATH and WINEPATH when + // libmpv is not directly linked, and doing it manually would be annoying. + if (getenv("MESON_EXE_WRAPPER")) + return 77; + + for (int i = 0; i < REPEAT; ++i) { + LOAD_LIB(); + if (!lib) + exit_log("Failed to load libmpv!\n"); + + INIT_SYM(command); + INIT_SYM(create); + INIT_SYM(error_string); + INIT_SYM(initialize); + INIT_SYM(set_option_string); + INIT_SYM(terminate_destroy); + INIT_SYM(wait_event); + + for (int j = 0; j < REPEAT; ++j) { + mpv_handle *ctx = create(); + if (!ctx) + exit_log("Failed to create mpv context!\n"); + + set_option_string(ctx, "msg-level", "all=trace"); + set_option_string(ctx, "terminal", "yes"); + + check_error(initialize(ctx)); + + for (int k = 0; k < REPEAT; ++k) { + check_error(command(ctx, (const char *[]){"loadfile", + "av://lavfi:yuvtestsrc=d=0.1", + NULL})); + bool loaded = false; + while (true) { + mpv_event *event = wait_event(ctx, -1); + if (event->event_id == MPV_EVENT_START_FILE) + loaded = true; + if (loaded && event->event_id == MPV_EVENT_IDLE) + break; + } + } + + terminate_destroy(ctx); + +#ifdef __SANITIZE_ADDRESS__ +#ifdef LSAN_IGNORE_DLCLOSE + __lsan_do_leak_check(); +#else + if (__lsan_do_recoverable_leak_check()) + exit_log("Detected memory leaks after terminate_destroy!\n"); +#endif +#endif + } + + CLOSE_LIB(); + +#if defined(__SANITIZE_ADDRESS__) && !defined(LSAN_IGNORE_DLCLOSE) + if (__lsan_do_recoverable_leak_check()) + exit_log("Detected memory leaks after dlclose!\n"); +#endif + } + + return 0; +} diff --git a/test/meson.build b/test/meson.build index 2d27169422..b14f2ef661 100644 --- a/test/meson.build +++ b/test/meson.build @@ -121,6 +121,18 @@ if get_option('libmpv') exe = executable('libmpv-encode', 'libmpv_encode.c', include_directories: incdir, link_with: libmpv) test('libmpv-encode', exe, timeout: 30) + + mpvlib = libmpv + shared = get_option('default_library') == 'shared' + if get_option('default_library') == 'both' + mpvlib = libmpv.get_shared_lib() + shared = true + endif + if shared + exe = executable('libmpv-lifetime', sources: 'libmpv_lifetime.c', + include_directories: incdir) + test('libmpv-lifetime', exe, depends: mpvlib) + endif endif # Supported libavutil versions that work with these tests.