Added cross-platform shared library tests

This commit is contained in:
Pijus Kamandulis
2025-11-27 00:17:24 +02:00
parent 46c446c273
commit cae6fda95c
6 changed files with 158 additions and 10 deletions

View File

@@ -7,6 +7,9 @@ jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact: shared-libraries
steps:
- name: Checkout code
uses: actions/checkout@v3
@@ -29,3 +32,58 @@ jobs:
with:
name: shared-libraries
path: dist/*
test:
needs: build
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
lib_ext: amd64.so
- os: windows-latest
lib_ext: amd64.dll
- os: macos-latest
lib_ext: arm64.dylib
steps:
- uses: actions/checkout@v3
- name: Download shared libraries
uses: actions/download-artifact@v4
with:
name: shared-libraries
path: libs
- name: Install MinGW (GCC) on Windows
if: runner.os == 'Windows'
run: choco install mingw --no-progress
- name: Build test loader (Windows)
if: runner.os == 'Windows'
run: |
mkdir build
gcc -Wall -o build/test_loader.exe sharedlibrary/tests/*.c
- name: Build test loader (Unix)
if: runner.os != 'Windows'
run: |
mkdir build
gcc -Wall -ldl -o build/test_loader sharedlibrary/tests/*.c
- name: Run test (Unix)
if: runner.os != 'Windows'
run: |
LIB=$(ls libs/*${{ matrix.lib_ext }} | head -n 1)
echo "Testing library: $LIB"
chmod +x build/test_loader
./build/test_loader "$LIB"
- name: Run test (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$lib = Get-ChildItem "libs/*${{ matrix.lib_ext }}" | Select-Object -First 1
Write-Host "Testing library: $($lib.FullName)"
.\build\test_loader.exe $lib.FullName

View File

@@ -7,6 +7,9 @@ int test_Databases();
int main(int argc, char *argv[])
{
/* Disable stdout buffering for CI environments without a real terminal */
setvbuf(stdout, NULL, _IONBF, 0);
if (argc < 2)
{
fprintf(stderr, "Usage: %s <path_to_shared_library>\n", argv[0]);
@@ -14,13 +17,20 @@ int main(int argc, char *argv[])
}
const char *libPath = argv[1];
handle = dlopen(libPath, RTLD_LAZY);
handle = load_library(libPath);
if (!handle)
{
fprintf(stderr, "Failed to load shared library: %s\n", dlerror());
fprintf(stderr, "Failed to load shared library: %s\n", get_load_error());
return EXIT_FAILURE;
}
/* give the loaded library a short time to initialize */
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
printf("Running tests for library: %s\n", libPath);
int results[] = {
test_CreateServerInstance(),
@@ -41,6 +51,15 @@ int main(int argc, char *argv[])
printf("Tests passed: %d/%d\n", numPassed, numTests);
dlclose(handle);
return EXIT_SUCCESS;
/* Exit explicitly before unloading the library.
Go runtime cleanup during FreeLibrary can set a non-zero exit code on Windows. */
int exitCode = (numPassed == numTests) ? EXIT_SUCCESS : EXIT_FAILURE;
#ifdef _WIN32
ExitProcess(exitCode);
#else
close_library(handle);
#endif
return exitCode;
}

View File

@@ -1,13 +1,50 @@
#include "shared.h"
void *handle = NULL;
lib_handle_t handle = NULL;
char *get_load_error(void)
{
#ifdef _WIN32
DWORD error = GetLastError();
static char buf[256];
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
buf, sizeof(buf), NULL);
return buf;
#else
return dlerror();
#endif
}
lib_handle_t load_library(const char *path)
{
#ifdef _WIN32
return LoadLibraryA(path);
#else
return dlopen(path, RTLD_LAZY);
#endif
}
void close_library(lib_handle_t handle)
{
#ifdef _WIN32
FreeLibrary(handle);
#else
dlclose(handle);
#endif
}
void *load_function(const char *func_name)
{
#ifdef _WIN32
void *func = (void *)GetProcAddress(handle, func_name);
#else
void *func = dlsym(handle, func_name);
#endif
if (!func)
{
fprintf(stderr, "Failed to load function %s: %s\n", func_name, dlerror());
fprintf(stderr, "Failed to load function %s: %s\n", func_name, get_load_error());
}
return func;
}

View File

@@ -3,13 +3,24 @@
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#include <ctype.h>
extern void *handle;
#ifdef _WIN32
#include <unistd.h>
#include <windows.h>
typedef HMODULE lib_handle_t;
#else
#include <dlfcn.h>
typedef void* lib_handle_t;
#endif
extern lib_handle_t handle;
void *load_function(const char *func_name);
char *compact_json(const char *json);
char *get_load_error(void);
lib_handle_t load_library(const char *path);
void close_library(lib_handle_t handle);
#endif

View File

@@ -2,6 +2,16 @@
int test_Databases()
{
/* Load FreeMemory function - must use this to free memory allocated by the DLL
because the DLL may use a different C runtime heap than the test loader */
typedef void (*FreeMemoryFn)(char *);
FreeMemoryFn FreeMemory = (FreeMemoryFn)load_function("FreeMemory");
if (!FreeMemory)
{
fprintf(stderr, "Failed to find FreeMemory function\n");
return 0;
}
typedef int (*CreateDatabaseFn)(char *, char *);
CreateDatabaseFn CreateDatabase = (CreateDatabaseFn)load_function("CreateDatabase");
if (!CreateDatabase)
@@ -36,6 +46,7 @@ int test_Databases()
if (database)
{
printf("GetDatabase: SUCCESS (database = %s)\n", database);
FreeMemory(database);
}
else
{

View File

@@ -2,6 +2,16 @@
int test_ServerInstanceStateMethods()
{
/* Load FreeMemory function - must use this to free memory allocated by the DLL
because the DLL may use a different C runtime heap than the test loader */
typedef void (*FreeMemoryFn)(char *);
FreeMemoryFn FreeMemory = (FreeMemoryFn)load_function("FreeMemory");
if (!FreeMemory)
{
fprintf(stderr, "Failed to find FreeMemory function\n");
return 0;
}
typedef int (*LoadServerInstanceStateFn)(char *, char *);
LoadServerInstanceStateFn LoadServerInstanceState = (LoadServerInstanceStateFn)load_function("LoadServerInstanceState");
if (!LoadServerInstanceState)
@@ -46,7 +56,7 @@ int test_ServerInstanceStateMethods()
char *compact_state = compact_json(state);
if (!compact_state)
{
free(state);
FreeMemory(state);
return 0;
}
@@ -59,10 +69,12 @@ int test_ServerInstanceStateMethods()
printf("GetServerInstanceState: State does not match expected value.\n");
printf("Expected: %s\n", expected_state);
printf("Actual: %s\n", compact_state);
FreeMemory(state);
free(compact_state);
return 0;
}
free(state);
FreeMemory(state);
free(compact_state);
return 1;
}