Link the pinned LiveKit C++ SDK into the core library

Adds cmake/LiveKitSDK.cmake, adapted from
livekit-examples/cpp-example-collection's helper of the same name, with the
VERSION="latest" GitHub-API resolution path removed: this project pins an
exact client-sdk-cpp release (1.10.1, the newest tag as of today), and the
pin should not be silently bypassable. The module also now exports the
runtime shared libraries so packaging can stage liblivekit/liblivekit_ffi
next to the plugin module later.

core/ links LiveKit::livekit PUBLIC. A new smoke test proves the SDK is not
just linked but loadable and callable: livekit::initialize()/shutdown()
round-trip in-process, a second initialize() reports "already initialized",
the log level round-trips, and the SDK's generated LIVEKIT_BUILD_VERSION is
asserted equal to the version CMake pinned (so a stale extracted SDK
directory fails loudly rather than being silently reused).

Also adds core/tests/test_util.h, a dependency-free assertion harness that
keeps running after a failure and prints a pass/fail count, so CI output says
how much actually ran instead of dying on the first bare assert().

cmake_minimum_required goes 3.16 -> 3.19 (file(ARCHIVE_EXTRACT)).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RL8abRmgFXkVASHkkqiJbE
This commit is contained in:
2026-09-06 21:22:14 -07:00
co-authored by Claude Sonnet 5
parent 304bc3ceba
commit e595173049
6 changed files with 417 additions and 16 deletions
+5
View File
@@ -13,6 +13,11 @@ target_include_directories(stplugin_core
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(stplugin_core
PUBLIC
LiveKit::livekit
)
set_target_properties(stplugin_core PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
+13 -2
View File
@@ -1,7 +1,18 @@
add_executable(stplugin_core_tests
test_core.cpp
)
target_link_libraries(stplugin_core_tests PRIVATE stplugin_core)
add_test(NAME stplugin_core_tests COMMAND stplugin_core_tests)
# Smoke test for the LiveKit SDK link: initialize()/shutdown() must succeed
# in-process. This is the cheapest possible proof that LiveKit::livekit is
# not just linked but loadable and callable (it dlopen-chains into
# liblivekit_ffi, which is where a broken RPATH would show up).
add_executable(stplugin_livekit_smoke
test_livekit_smoke.cpp
)
target_link_libraries(stplugin_livekit_smoke PRIVATE stplugin_core)
target_compile_definitions(stplugin_livekit_smoke PRIVATE
STPLUGIN_EXPECTED_LIVEKIT_VERSION="${LIVEKIT_SDK_VERSION_RESOLVED}"
)
add_test(NAME stplugin_livekit_smoke COMMAND stplugin_livekit_smoke)
+64
View File
@@ -0,0 +1,64 @@
/*
streamer-tools OBS Camera Plugin - LiveKit SDK link smoke test
Copyright (C) 2026 CyberCoveLLC <jknapp85@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
// Proves the pinned client-sdk-cpp release is genuinely linked and callable:
// the SDK's own global init/shutdown runs in-process without crashing, and
// the header-reported build version matches the version CMake pinned.
//
// This does NOT touch the network -- livekit::initialize() only sets up
// global SDK state and log routing.
#include <cstdio>
#include <cstring>
#include <livekit/build.h>
#include <livekit/livekit.h>
#include <livekit/logging.h>
#include "test_util.h"
int main()
{
// The pin CMake resolved is passed in as a define; the SDK's own
// generated build.h reports what was actually unpacked. A mismatch means
// a stale extracted SDK directory is being reused.
ST_ASSERT_EQ(std::string(LIVEKIT_BUILD_VERSION), std::string(STPLUGIN_EXPECTED_LIVEKIT_VERSION));
// initialize() returns true when this call performed the init, false if
// the SDK was already initialized. Either way it must not crash, and a
// second call must report "already initialized".
bool first = livekit::initialize(livekit::LogLevel::Error);
ST_ASSERT(first);
bool second = livekit::initialize(livekit::LogLevel::Error);
ST_ASSERT(!second);
// Log level round-trips through the SDK's global state.
livekit::setLogLevel(livekit::LogLevel::Warn);
ST_ASSERT(livekit::getLogLevel() == livekit::LogLevel::Warn);
livekit::shutdown();
// The SDK documents that initialize() may be called again after
// shutdown(); exercise that so a half-torn-down global state would show
// up here rather than in OBS.
ST_ASSERT(livekit::initialize(livekit::LogLevel::Error));
livekit::shutdown();
return st_test_report("livekit_smoke");
}
+91
View File
@@ -0,0 +1,91 @@
/*
streamer-tools OBS Camera Plugin - minimal test harness
Copyright (C) 2026 CyberCoveLLC <jknapp85@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#pragma once
// Deliberately dependency-free (no gtest/catch2) so the core library's test
// targets add no package-manager or network step to any of the three CI
// platforms. Unlike bare assert(), this keeps running after a failure and
// prints a real pass/fail count, so CI output says how much actually ran.
#include <cstdio>
#include <string>
namespace st_test_detail {
inline int &checks_run()
{
static int n = 0;
return n;
}
inline int &checks_failed()
{
static int n = 0;
return n;
}
inline std::string to_display(const std::string &v)
{
return "\"" + v + "\"";
}
inline std::string to_display(const char *v)
{
return v ? ("\"" + std::string(v) + "\"") : std::string("(null)");
}
inline std::string to_display(bool v)
{
return v ? "true" : "false";
}
template<typename T> inline std::string to_display(const T &v)
{
return std::to_string(v);
}
} // namespace st_test_detail
#define ST_ASSERT(expr) \
do { \
++st_test_detail::checks_run(); \
if (!(expr)) { \
++st_test_detail::checks_failed(); \
std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #expr); \
} \
} while (0)
#define ST_ASSERT_EQ(actual, expected) \
do { \
++st_test_detail::checks_run(); \
auto _st_a = (actual); \
auto _st_e = (expected); \
if (!(_st_a == _st_e)) { \
++st_test_detail::checks_failed(); \
std::fprintf(stderr, "FAIL %s:%d: %s\n actual: %s\n expected: %s\n", \
__FILE__, __LINE__, #actual " == " #expected, \
st_test_detail::to_display(_st_a).c_str(), \
st_test_detail::to_display(_st_e).c_str()); \
} \
} while (0)
inline int st_test_report(const char *suite)
{
const int run = st_test_detail::checks_run();
const int failed = st_test_detail::checks_failed();
if (failed == 0) {
std::printf("%s: %d checks passed\n", suite, run);
return 0;
}
std::printf("%s: %d/%d checks FAILED\n", suite, failed, run);
return 1;
}