Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
df684d0
First pass at unit tests in CI
alan-george-lk Apr 9, 2026
a931038
Separate unit tests
alan-george-lk Apr 9, 2026
21bab19
Fix unit tests on all platforms
alan-george-lk Apr 9, 2026
5911b65
Move all gtest discovery to "PRE_TEST" preventing invocation automati…
alan-george-lk Apr 9, 2026
5716889
Try fixing test stages
alan-george-lk Apr 9, 2026
abbdba4
Move unit tests to unit folder
alan-george-lk Apr 9, 2026
7df6650
Separate quality stage
alan-george-lk Apr 9, 2026
a7e5f0f
unify test stage
alan-george-lk Apr 10, 2026
b98cb97
Possibly fix windows
alan-george-lk Apr 10, 2026
34efb22
Merge branch 'main' into feature/ci_unit_tests
alan-george-lk Apr 10, 2026
e3362e9
Use full sha on actions, fixes to windows build
alan-george-lk Apr 10, 2026
498ea01
Fix integration stage build on Windows
alan-george-lk Apr 10, 2026
75eef85
Put back smoke tests
alan-george-lk Apr 13, 2026
30018a2
LFS support in CI, adjust attenuation test to fail if file fails
alan-george-lk Apr 13, 2026
53fbc04
Fix wav path
alan-george-lk Apr 13, 2026
6a4119d
Better pulling of LFS
alan-george-lk Apr 13, 2026
2692866
Remove TODO/add context if file fails to read
alan-george-lk Apr 13, 2026
7cf0366
docker: base and sdk images
stephen-derosa Apr 13, 2026
00779dd
Try without absl::base bit
alan-george-lk Apr 13, 2026
917f5b2
More cmake cleanup
alan-george-lk Apr 13, 2026
89afc25
Merge branch 'main' of github.com:livekit/client-sdk-cpp into feature…
alan-george-lk Apr 13, 2026
d6936cb
Revert glob
alan-george-lk Apr 13, 2026
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
38 changes: 32 additions & 6 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,23 @@ jobs:
include:
- os: ubuntu-latest
name: linux-x64
build_cmd: ./build.sh release-examples
build_cmd: ./build.sh release-tests && ./build.sh release-examples
build_dir: build-release
- os: ubuntu-24.04-arm
name: linux-arm64
build_cmd: ./build.sh release-examples
build_cmd: ./build.sh release-tests && ./build.sh release-examples
build_dir: build-release
- os: macos-latest
name: macos-arm64
build_cmd: ./build.sh release-examples
build_cmd: ./build.sh release-tests && ./build.sh release-examples
build_dir: build-release
- os: macos-latest
name: macos-x64
build_cmd: ./build.sh release-examples --macos-arch x86_64
build_cmd: ./build.sh release-tests && ./build.sh release-examples --macos-arch x86_64
build_dir: build-release
- os: windows-latest
name: windows-x64
build_cmd: .\build.cmd release-examples
build_cmd: .\build.cmd release-tests && .\build.cmd release-examples
build_dir: build-release

name: Build (${{ matrix.name }})
Expand All @@ -90,7 +90,10 @@ jobs:
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
submodules: recursive
fetch-depth: 0
fetch-depth: 1

- name: Pull LFS files
run: git lfs pull
Comment thread
alan-george-lk marked this conversation as resolved.

# ---------- vcpkg caching for Windows ----------
- name: Export GitHub Actions cache environment variables
Expand Down Expand Up @@ -283,6 +286,29 @@ jobs:
}
if ($failed) { exit 1 } else { exit 0 }

# ---------- Run unit tests ----------
- name: Run unit tests (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
${{ matrix.build_dir }}/bin/livekit_unit_tests \
--gtest_output=xml:${{ matrix.build_dir }}/unit-test-results.xml

- name: Run unit tests (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
& "${{ matrix.build_dir }}/bin/livekit_unit_tests.exe" `
--gtest_output=xml:${{ matrix.build_dir }}/unit-test-results.xml

- name: Upload test results
Comment thread
alan-george-lk marked this conversation as resolved.
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-results-${{ matrix.name }}
path: ${{ matrix.build_dir }}/unit-test-results.xml
retention-days: 7

# ---------- Upload artifacts ----------
- name: Upload build artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ git clone --recurse-submodules https://github.com/livekit/client-sdk-cpp.git
git clone https://github.com/livekit/client-sdk-cpp.git
cd client-sdk-cpp
git submodule update --init --recursive

# Note: If running tests, pull Git LFS to bring in test data:
git lfs pull
```

## ⚙️ BUILD
Expand Down
23 changes: 17 additions & 6 deletions bridge/include/livekit_bridge/rpc_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@

#include <string>

#ifdef _WIN32
#ifdef livekit_bridge_EXPORTS
#define LIVEKIT_BRIDGE_API __declspec(dllexport)
#else
#define LIVEKIT_BRIDGE_API __declspec(dllimport)
#endif
#else
#define LIVEKIT_BRIDGE_API
#endif

namespace livekit_bridge {
namespace rpc {

Expand All @@ -34,20 +44,21 @@ namespace track_control {
enum class Action { kActionMute, kActionUnmute };

/// RPC method name registered by the bridge for remote track control.
extern const char *const kMethod;
LIVEKIT_BRIDGE_API extern const char *const kMethod;

/// Payload action strings.
extern const char *const kActionMute;
extern const char *const kActionUnmute;
LIVEKIT_BRIDGE_API extern const char *const kActionMute;
LIVEKIT_BRIDGE_API extern const char *const kActionUnmute;

/// Delimiter between action and track name in the payload (e.g. "mute:cam").
extern const char kDelimiter;
LIVEKIT_BRIDGE_API extern const char kDelimiter;

/// Response payload returned on success.
extern const char *const kResponseOk;
LIVEKIT_BRIDGE_API extern const char *const kResponseOk;

/// Build a track-control RPC payload: "<action>:<track_name>".
std::string formatPayload(const char *action, const std::string &track_name);
LIVEKIT_BRIDGE_API std::string formatPayload(const char *action,
const std::string &track_name);

} // namespace track_control
} // namespace rpc
Expand Down
1 change: 1 addition & 0 deletions bridge/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ if(BRIDGE_TEST_SOURCES)
# Register tests with CTest
gtest_discover_tests(livekit_bridge_tests
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
DISCOVERY_MODE PRE_TEST
PROPERTIES
LABELS "bridge_unit"
)
Expand Down
95 changes: 95 additions & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,81 @@ FetchContent_MakeAvailable(googletest)
enable_testing()
include(GoogleTest)

# ============================================================================
# Unit Tests
# ============================================================================

file(GLOB UNIT_TEST_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/unit/*.cpp"
)

if(UNIT_TEST_SOURCES)
add_executable(livekit_unit_tests
${UNIT_TEST_SOURCES}
)

target_link_libraries(livekit_unit_tests
PRIVATE
livekit
spdlog::spdlog
GTest::gtest_main
GTest::gmock
)

target_include_directories(livekit_unit_tests
PRIVATE
${LIVEKIT_ROOT_DIR}/include
${LIVEKIT_ROOT_DIR}/src
)

target_compile_definitions(livekit_unit_tests
PRIVATE
LIVEKIT_TEST_ACCESS
LIVEKIT_ROOT_DIR="${LIVEKIT_ROOT_DIR}"
SPDLOG_ACTIVE_LEVEL=${_SPDLOG_ACTIVE_LEVEL}
$<$<PLATFORM_ID:Windows>:_USE_MATH_DEFINES>
)

if(WIN32)
add_custom_command(TARGET livekit_unit_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:livekit>
$<TARGET_FILE_DIR:livekit_unit_tests>
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE_DIR:livekit>/livekit_ffi.dll"
$<TARGET_FILE_DIR:livekit_unit_tests>
COMMENT "Copying DLLs to unit test directory"
)
elseif(APPLE)
add_custom_command(TARGET livekit_unit_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:livekit>
$<TARGET_FILE_DIR:livekit_unit_tests>
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE_DIR:livekit>/liblivekit_ffi.dylib"
$<TARGET_FILE_DIR:livekit_unit_tests>
COMMENT "Copying dylibs to unit test directory"
)
else()
add_custom_command(TARGET livekit_unit_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:livekit>
$<TARGET_FILE_DIR:livekit_unit_tests>
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE_DIR:livekit>/liblivekit_ffi.so"
$<TARGET_FILE_DIR:livekit_unit_tests>
COMMENT "Copying shared libraries to unit test directory"
)
endif()

gtest_discover_tests(livekit_unit_tests
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
DISCOVERY_MODE PRE_TEST
PROPERTIES
LABELS "unit"
)
endif()

# ============================================================================
# Integration Tests
# ============================================================================
Expand All @@ -38,10 +113,18 @@ if(INTEGRATION_TEST_SOURCES)
${INTEGRATION_TEST_SOURCES}
)

# On Windows, protobuf default-instance symbols (constinit globals) are not
# auto-exported from livekit.dll by WINDOWS_EXPORT_ALL_SYMBOLS. Link the
# proto object library directly so the test binary has its own copy.
if(WIN32 AND TARGET livekit_proto)
target_sources(livekit_integration_tests PRIVATE $<TARGET_OBJECTS:livekit_proto>)
endif()

target_link_libraries(livekit_integration_tests
PRIVATE
livekit
spdlog::spdlog
$<$<PLATFORM_ID:Windows>:${LIVEKIT_PROTOBUF_TARGET}>
GTest::gtest_main
GTest::gmock
)
Expand All @@ -67,6 +150,7 @@ if(INTEGRATION_TEST_SOURCES)
LIVEKIT_TEST_ACCESS
LIVEKIT_ROOT_DIR="${LIVEKIT_ROOT_DIR}"
SPDLOG_ACTIVE_LEVEL=${_SPDLOG_ACTIVE_LEVEL}
$<$<PLATFORM_ID:Windows>:_USE_MATH_DEFINES>
)

# Copy shared libraries to test executable directory
Expand Down Expand Up @@ -105,6 +189,7 @@ if(INTEGRATION_TEST_SOURCES)
# Register tests with CTest
gtest_discover_tests(livekit_integration_tests
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
DISCOVERY_MODE PRE_TEST
PROPERTIES
LABELS "integration"
)
Expand Down Expand Up @@ -137,6 +222,11 @@ if(STRESS_TEST_SOURCES)
${LIVEKIT_ROOT_DIR}/src
)

target_compile_definitions(livekit_stress_tests
PRIVATE
$<$<PLATFORM_ID:Windows>:_USE_MATH_DEFINES>
)

# Copy shared libraries to test executable directory
if(WIN32)
add_custom_command(TARGET livekit_stress_tests POST_BUILD
Expand Down Expand Up @@ -173,6 +263,7 @@ if(STRESS_TEST_SOURCES)
# Register tests with CTest (longer timeout for stress tests)
gtest_discover_tests(livekit_stress_tests
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
DISCOVERY_MODE PRE_TEST
PROPERTIES
LABELS "stress"
TIMEOUT 300
Expand All @@ -189,6 +280,10 @@ add_custom_target(run_all_tests
COMMENT "Running all tests"
)

if(TARGET livekit_unit_tests)
add_dependencies(run_all_tests livekit_unit_tests)
endif()

if(TARGET livekit_integration_tests)
add_dependencies(run_all_tests livekit_integration_tests)
endif()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -802,9 +802,7 @@ TEST_F(AudioProcessingModuleTest, AGCAttenuatesLoudSpeech) {
int num_channels = 0;

std::string wav_path = std::string(LIVEKIT_ROOT_DIR) + "/data/welcome.wav";
if (!readWavFile(wav_path, original_samples, sample_rate, num_channels)) {
GTEST_SKIP() << "Could not read " << wav_path;
}
ASSERT_TRUE(readWavFile(wav_path, original_samples, sample_rate, num_channels)) << "Could not read " << wav_path << " (is Git LFS pulled?)";

std::cout << "[AGC-LoudSpeech] Loaded " << original_samples.size()
<< " samples, " << sample_rate << " Hz, " << num_channels
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ TEST_F(RoomCallbackTest, ConcurrentRegistrationDoesNotCrash) {
threads.reserve(kThreads);

for (int t = 0; t < kThreads; ++t) {
threads.emplace_back([&room, t]() {
threads.emplace_back([&room, t, kIterations]() {
for (int i = 0; i < kIterations; ++i) {
const std::string id = "participant-" + std::to_string(t);
room.setOnAudioFrameCallback(id, TrackSource::SOURCE_MICROPHONE,
Expand All @@ -228,7 +228,7 @@ TEST_F(RoomCallbackTest, ConcurrentMixedRegistrationDoesNotCrash) {
threads.reserve(kThreads);

for (int t = 0; t < kThreads; ++t) {
threads.emplace_back([&room, t]() {
threads.emplace_back([&room, t, kIterations]() {
const std::string id = "p-" + std::to_string(t);
for (int i = 0; i < kIterations; ++i) {
room.setOnAudioFrameCallback(id, TrackSource::SOURCE_MICROPHONE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ TEST_F(SubscriptionThreadDispatcherTest, ConcurrentRegistrationDoesNotCrash) {
threads.reserve(kThreads);

for (int t = 0; t < kThreads; ++t) {
threads.emplace_back([&dispatcher, t]() {
threads.emplace_back([&dispatcher, t, kIterations]() {
for (int i = 0; i < kIterations; ++i) {
std::string id = "participant-" + std::to_string(t);
dispatcher.setOnAudioFrameCallback(id, TrackSource::SOURCE_MICROPHONE,
Expand Down Expand Up @@ -435,7 +435,7 @@ TEST_F(SubscriptionThreadDispatcherTest,
std::vector<std::thread> threads;

for (int t = 0; t < kThreads; ++t) {
threads.emplace_back([&dispatcher, t]() {
threads.emplace_back([&dispatcher, t, kIterations]() {
std::string id = "p-" + std::to_string(t);
for (int i = 0; i < kIterations; ++i) {
dispatcher.setOnAudioFrameCallback(id, TrackSource::SOURCE_MICROPHONE,
Expand Down Expand Up @@ -714,7 +714,7 @@ TEST_F(SubscriptionThreadDispatcherTest,
threads.reserve(kThreads);

for (int t = 0; t < kThreads; ++t) {
threads.emplace_back([&dispatcher, t]() {
threads.emplace_back([&dispatcher, t, kIterations]() {
for (int i = 0; i < kIterations; ++i) {
auto id = dispatcher.addOnDataFrameCallback(
"participant-" + std::to_string(t), "track",
Expand Down
Loading