Skip to content

Commit 2ce72ea

Browse files
more E2E tests
1 parent 90788da commit 2ce72ea

6 files changed

Lines changed: 566 additions & 0 deletions

File tree

include/livekit/remote_data_track.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ class RemoteDataTrack {
9595
#ifdef LIVEKIT_TEST_ACCESS
9696
/// Test-only accessor for exercising lower-level FFI subscription paths.
9797
uintptr_t testFfiHandleId() const noexcept { return ffiHandleId(); }
98+
99+
/// @brief Test-only factory that builds a RemoteDataTrack with no live FFI
100+
/// handle, used to exercise dispatcher routing without a real subscription.
101+
/// @param info Metadata (name, sid, e2ee) to expose via info().
102+
/// @param publisher_identity Identity to expose via publisherIdentity().
103+
/// @return A RemoteDataTrack whose handle is invalid, so subscribe() fails
104+
/// fast without touching the FFI.
105+
static std::shared_ptr<RemoteDataTrack> createForTest(DataTrackInfo info, std::string publisher_identity) {
106+
return std::shared_ptr<RemoteDataTrack>(new RemoteDataTrack(std::move(info), std::move(publisher_identity)));
107+
}
98108
#endif
99109

100110
/// Subscribe to this remote data track.
@@ -109,6 +119,12 @@ class RemoteDataTrack {
109119

110120
explicit RemoteDataTrack(const proto::OwnedRemoteDataTrack& owned);
111121

122+
#ifdef LIVEKIT_TEST_ACCESS
123+
/// Test-only constructor that bypasses the FFI, leaving an invalid handle.
124+
RemoteDataTrack(DataTrackInfo info, std::string publisher_identity)
125+
: handle_(0), info_(std::move(info)), publisher_identity_(std::move(publisher_identity)) {}
126+
#endif
127+
112128
uintptr_t ffiHandleId() const noexcept { return handle_.get(); }
113129
/// RAII wrapper for the Rust-owned FFI resource.
114130
FfiHandle handle_;

include/livekit/subscription_thread_dispatcher.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#pragma once
1818

1919
#include <atomic>
20+
#include <cstddef>
2021
#include <cstdint>
2122
#include <functional>
2223
#include <memory>
@@ -233,6 +234,14 @@ class LIVEKIT_API SubscriptionThreadDispatcher {
233234
/// thread survives beyond the lifetime of the owning @ref Room.
234235
void stopAll();
235236

237+
/// @brief Number of active audio/video reader threads. Intended for tests.
238+
/// @return Count of currently active audio and video reader slots.
239+
LIVEKIT_INTERNAL_API std::size_t activeReaderCount() const;
240+
241+
/// @brief Number of active data track reader threads. Intended for tests.
242+
/// @return Count of currently active data reader slots.
243+
LIVEKIT_INTERNAL_API std::size_t activeDataReaderCount() const;
244+
236245
private:
237246
friend class SubscriptionThreadDispatcherTest;
238247

src/subscription_thread_dispatcher.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,16 @@ void SubscriptionThreadDispatcher::stopAll() {
339339
LK_LOG_DEBUG("Stopped {} subscription reader threads", threads.size());
340340
}
341341

342+
std::size_t SubscriptionThreadDispatcher::activeReaderCount() const {
343+
const std::scoped_lock<std::mutex> lock(lock_);
344+
return active_readers_.size();
345+
}
346+
347+
std::size_t SubscriptionThreadDispatcher::activeDataReaderCount() const {
348+
const std::scoped_lock<std::mutex> lock(lock_);
349+
return active_data_readers_.size();
350+
}
351+
342352
std::thread SubscriptionThreadDispatcher::extractReaderThreadLocked(const CallbackKey& key) {
343353
auto it = active_readers_.find(key);
344354
if (it == active_readers_.end()) {

src/tests/integration/test_data_track.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,101 @@ TEST_F(DataTrackE2ETest, UnpublishUpdatesPublishedStateEndToEnd) {
468468
<< "Remote track did not report unpublished state";
469469
}
470470

471+
// Verifies that an auto-wired data callback (Room::addOnDataFrameCallback)
472+
// follows a republished track: after unpublish + republish under the same
473+
// (participant, track name) but a new SID, the previous reader is torn down and
474+
// a fresh reader delivers frames from the new publication.
475+
TEST_F(DataTrackE2ETest, RepublishRewiresDataCallbackToNewPublication) {
476+
const auto track_name = makeTrackName("republish");
477+
478+
std::vector<TestRoomConnectionOptions> room_configs(2);
479+
room_configs[0].room_options.single_peer_connection = false;
480+
room_configs[1].room_options.single_peer_connection = false;
481+
482+
DataTrackPublishedDelegate subscriber_delegate;
483+
room_configs[1].delegate = &subscriber_delegate;
484+
485+
auto rooms = testRooms(room_configs);
486+
auto& publisher_room = rooms[0];
487+
auto& subscriber_room = rooms[1];
488+
const auto publisher_identity = lockLocalParticipant(*publisher_room)->identity();
489+
490+
std::atomic<int> frames_received{0};
491+
std::mutex payload_mutex;
492+
std::vector<std::uint8_t> last_payload;
493+
subscriber_room->addOnDataFrameCallback(publisher_identity, track_name,
494+
[&](const std::vector<std::uint8_t>& payload, std::optional<std::uint64_t>) {
495+
{
496+
const std::scoped_lock<std::mutex> lock(payload_mutex);
497+
last_payload = payload;
498+
}
499+
frames_received.fetch_add(1);
500+
});
501+
502+
auto publish_with_retry = [&](const std::string& name) -> std::shared_ptr<LocalDataTrack> {
503+
std::shared_ptr<LocalDataTrack> track;
504+
waitForCondition(
505+
[&]() {
506+
auto result = lockLocalParticipant(*publisher_room)->publishDataTrack(name);
507+
if (result) {
508+
track = result.value();
509+
return true;
510+
}
511+
return false;
512+
},
513+
kTrackWaitTimeout);
514+
return track;
515+
};
516+
517+
// First publication.
518+
auto first_track = publish_with_retry(track_name);
519+
ASSERT_NE(first_track, nullptr) << "Failed to publish first data track";
520+
auto first_remote = subscriber_delegate.waitForTrack(kTrackWaitTimeout);
521+
ASSERT_NE(first_remote, nullptr) << "Timed out waiting for first remote data track";
522+
const std::string first_sid = first_remote->info().sid;
523+
524+
DataTrackFrame first_frame;
525+
first_frame.payload.assign(64, 0xA1);
526+
ASSERT_TRUE(waitForCondition(
527+
[&]() {
528+
requirePushSuccess(first_track->tryPush(first_frame), "Failed to push first-publication frame");
529+
return frames_received.load() > 0;
530+
},
531+
kTransportFrameTimeout))
532+
<< "Auto-wired callback never received a frame from the first publication";
533+
534+
// Unpublish: the reader for the first publication must be torn down.
535+
first_track->unpublishDataTrack();
536+
ASSERT_TRUE(waitForCondition([&]() { return !first_remote->isPublished(); }, kTrackWaitTimeout))
537+
<< "First remote track did not report unpublished state";
538+
const int frames_before_republish = frames_received.load();
539+
540+
// Republish under the same name; the server assigns a new SID.
541+
auto second_track = publish_with_retry(track_name);
542+
ASSERT_NE(second_track, nullptr) << "Failed to republish data track";
543+
auto remotes = subscriber_delegate.waitForTracks(2, kTrackWaitTimeout);
544+
ASSERT_EQ(remotes.size(), 2u) << "Timed out waiting for republished remote data track";
545+
const std::string second_sid = remotes.back()->info().sid;
546+
EXPECT_NE(first_sid, second_sid) << "Republish should produce a new SID";
547+
548+
DataTrackFrame second_frame;
549+
second_frame.payload.assign(64, 0xB2);
550+
ASSERT_TRUE(waitForCondition(
551+
[&]() {
552+
requirePushSuccess(second_track->tryPush(second_frame), "Failed to push republished frame");
553+
return frames_received.load() > frames_before_republish;
554+
},
555+
kTransportFrameTimeout))
556+
<< "Auto-wired callback did not re-wire to the republished track";
557+
558+
{
559+
const std::scoped_lock<std::mutex> lock(payload_mutex);
560+
EXPECT_EQ(last_payload, second_frame.payload) << "Callback delivered stale payload after republish";
561+
}
562+
563+
second_track->unpublishDataTrack();
564+
}
565+
471566
TEST_F(DataTrackE2ETest, SubscribeAfterUnpublishReportsTerminalError) {
472567
const auto track_name = makeTrackName("subscribe_after_unpublish");
473568

0 commit comments

Comments
 (0)