Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,15 @@ tests_option = get_option('tests')
docs_option = get_option('documentation')
# crc32_variant_option already defined earlier before compiler settings

io_plugin_dlopen_option = get_option('io_plugin_dlopen')

message('wirelog ' + project_version)
message(' Platform: ' + platform)
message(' Embedded: @0@'.format(is_embedded))
message(' IPC support: ' + ipc_option)
message(' Threading: ' + threads_option)
message(' CRC-32 variant: ' + crc32_variant_option)
message(' Plugin loader (dlopen): ' + io_plugin_dlopen_option)

#== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
#Dependencies
Expand Down Expand Up @@ -277,15 +280,31 @@ pkg.generate(
#CLI Driver
#== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==

# Optional plugin loader via dlopen (Issue #461)
# Excluded from Windows builds (no dlopen); Android/iOS excluded via option default.
cli_plugin_src = []
cli_plugin_deps = []
cli_plugin_args = []
if io_plugin_dlopen_option == 'enabled' and not is_windows
_dl_dep = cc.find_library('dl', required: false)
if _dl_dep.found()
cli_plugin_deps += [_dl_dep]
endif
cli_plugin_src = files('wirelog/cli/plugin_loader.c')
cli_plugin_args = ['-DWL_HAVE_PLUGIN_LOADER']
endif

#Build wirelog-cli executable (wirelog_cli_src defined in wirelog/meson.build)
#Include all wirelog sources to ensure proper linking with LTO
wirelog_cli = executable(
'wirelog_cli',
wirelog_cli_src,
cli_plugin_src,
wirelog_sources,
config_h_file,
include_directories: [wirelog_inc, wirelog_src_inc],
dependencies: [threads_dep, zlib_dep, nanoarrow_dep, xxhash_dep, mbedtls_dep, math_dep],
c_args: cli_plugin_args,
dependencies: [threads_dep, zlib_dep, nanoarrow_dep, xxhash_dep, mbedtls_dep, math_dep] + cli_plugin_deps,
install: true,
)

Expand Down
8 changes: 8 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ option(
description: 'Default CRC-32 variant: ethernet (standard) or castagnoli (iSCSI)'
)

option(
'io_plugin_dlopen',
type: 'combo',
choices: ['disabled', 'enabled'],
value: 'disabled',
description: 'Enable optional CLI plugin loader via dlopen (Issue #461). Excluded from Android/iOS.'
)

option(
'mbedTLS',
type: 'combo',
Expand Down
45 changes: 45 additions & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2495,3 +2495,48 @@ test_io_adapter_asan_exe = executable(
install: false,
)
test('io_adapter_asan', test_io_adapter_asan_exe)

# ======================================================================
# Plugin loader tests (Issue #461)
# Only built when io_plugin_dlopen is enabled and not on Windows
# ======================================================================
if io_plugin_dlopen_option == 'enabled' and not is_windows

# Mock plugin shared library (valid adapter)
mock_plugin_lib = shared_library(
'mock_plugin_adapter',
files('mock_plugin_adapter.c'),
include_directories: [wirelog_inc, wirelog_src_inc],
install: false,
)

# Mock plugin shared library (ABI mismatch — always returns NULL)
mock_plugin_bad_abi_lib = shared_library(
'mock_plugin_adapter_bad_abi',
files('mock_plugin_adapter_bad_abi.c'),
include_directories: [wirelog_inc, wirelog_src_inc],
install: false,
)

_dl_test_dep = cc.find_library('dl', required: false)
test_plugin_loader_exe = executable(
'test_plugin_loader',
files('test_plugin_loader.c',
'../wirelog/cli/plugin_loader.c',
'../wirelog/io/io_adapter.c',
'../wirelog/io/csv_adapter.c',
'../wirelog/io/csv_reader.c',
'../wirelog/io/io_ctx.c',
'../wirelog/intern.c'),
thread_src,
include_directories: [wirelog_inc, wirelog_src_inc],
c_args: ['-DWIRELOG_BUILDING', '-DWL_HAVE_PLUGIN_LOADER'],
dependencies: [threads_dep] + (_dl_test_dep.found() ? [_dl_test_dep] : []),
install: false,
)
test('plugin_loader', test_plugin_loader_exe,
args: [mock_plugin_lib.full_path(), mock_plugin_bad_abi_lib.full_path()],
depends: [mock_plugin_lib, mock_plugin_bad_abi_lib],
)

endif
46 changes: 46 additions & 0 deletions tests/mock_plugin_adapter.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* mock_plugin_adapter.c - Mock adapter plugin for test_plugin_loader
*
* Built as a shared library (libmock_plugin_adapter.so) that exports
* the wl_io_plugin_entry symbol. Used by test_plugin_loader.c to
* exercise the plugin load + register + dispatch path.
*/

#include "wirelog/io/io_adapter.h"

#include <stddef.h>
#include <stdlib.h>

static int
mock_read(wl_io_ctx_t *ctx, int64_t **out_data, uint32_t *out_nrows,
void *user_data)
{
(void)ctx;
(void)user_data;
*out_data = NULL;
*out_nrows = 0;
return 0;
}

static const wl_io_adapter_t mock_adapter = {
.abi_version = WL_IO_ABI_VERSION,
.scheme = "mock_plugin",
.description = "mock plugin adapter for testing",
.read = mock_read,
.validate = NULL,
.user_data = NULL,
};

static const wl_io_adapter_t *const adapter_list[] = {&mock_adapter};

WL_IO_PLUGIN_EXPORT
const wl_io_adapter_t *const *
wl_io_plugin_entry(uint32_t *n_out, uint32_t abi_ver)
{
if (abi_ver != WL_IO_ABI_VERSION) {
*n_out = 0;
return NULL;
}
*n_out = 1;
return adapter_list;
}
18 changes: 18 additions & 0 deletions tests/mock_plugin_adapter_bad_abi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* mock_plugin_adapter_bad_abi.c - ABI mismatch mock for test_plugin_loader
*
* Always returns NULL to simulate an ABI version mismatch.
*/

#include "wirelog/io/io_adapter.h"

#include <stddef.h>

WL_IO_PLUGIN_EXPORT
const wl_io_adapter_t *const *
wl_io_plugin_entry(uint32_t *n_out, uint32_t abi_ver)
{
(void)abi_ver;
*n_out = 0;
return NULL;
}
132 changes: 132 additions & 0 deletions tests/test_plugin_loader.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* test_plugin_loader.c - Plugin loader integration tests (Issue #461)
*
* Tests: load + register + find + unload, ABI mismatch error path,
* missing symbol error path, NULL path error path.
*
* Requires -Dio_plugin_dlopen=enabled to build.
*/

#include "wirelog/io/io_adapter.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* We test the plugin loader by calling it directly */
extern int wl_plugin_load(const char *path);
extern void wl_plugin_unload_all(void);

#define TEST(name) do { printf(" [TEST] %-50s ", name); } while (0)
#define PASS() do { printf("PASS\n"); passed++; } while (0)
#define FAIL(msg) do { printf("FAIL: %s\n", msg); failed++; } while (0)

static int passed = 0, failed = 0;

static void
test_load_valid_plugin(const char *mock_path)
{
TEST("load valid plugin");

int rc = wl_plugin_load(mock_path);
if (rc != 0) {
FAIL("wl_plugin_load returned non-zero");
return;
}

const wl_io_adapter_t *found = wl_io_find_adapter("mock_plugin");
if (!found) {
FAIL("adapter 'mock_plugin' not found after load");
return;
}

if (strcmp(found->scheme, "mock_plugin") != 0) {
FAIL("scheme mismatch");
return;
}

PASS();
}

static void
test_unload_cleans_registry(void)
{
TEST("unload cleans registry");

wl_plugin_unload_all();

const wl_io_adapter_t *found = wl_io_find_adapter("mock_plugin");
if (found) {
FAIL("adapter 'mock_plugin' still found after unload");
return;
}

PASS();
}

static void
test_abi_mismatch(const char *bad_abi_path)
{
TEST("ABI mismatch returns error");

int rc = wl_plugin_load(bad_abi_path);
if (rc == 0) {
FAIL("expected failure for ABI mismatch plugin");
wl_plugin_unload_all();
return;
}

PASS();
}

static void
test_missing_file(void)
{
TEST("missing file returns error");

int rc = wl_plugin_load("/nonexistent/path/libfoo.so");
if (rc == 0) {
FAIL("expected failure for missing file");
return;
}

PASS();
}

static void
test_null_path(void)
{
TEST("NULL path returns error");

int rc = wl_plugin_load(NULL);
if (rc == 0) {
FAIL("expected failure for NULL path");
return;
}

PASS();
}

int
main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr,
"usage: %s <mock_plugin.so> <mock_bad_abi.so>\n", argv[0]);
return 1;
}

const char *mock_path = argv[1];
const char *bad_abi_path = argv[2];

printf("test_plugin_loader\n");

test_null_path();
test_missing_file();
test_load_valid_plugin(mock_path);
test_unload_cleans_registry();
test_abi_mismatch(bad_abi_path);

printf("\n Results: %d passed, %d failed\n", passed, failed);
return failed > 0 ? 1 : 0;
}
Loading
Loading