diff --git a/src/config/Config.h b/src/config/Config.h index 1f45b18..8efeccf 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -196,6 +196,7 @@ struct Config { std::optional admin; std::string relayID; // always set: from config or randomly generated uint32_t threads{1}; + bool useRelayThread{true}; bool mvfstBpfSteering{true}; }; diff --git a/src/config/ConfigResolver.cpp b/src/config/ConfigResolver.cpp index 33d7a8e..c97e288 100644 --- a/src/config/ConfigResolver.cpp +++ b/src/config/ConfigResolver.cpp @@ -822,8 +822,11 @@ folly::Expected resolveConfig(const ParsedConfig& c const uint32_t threads = config.threads.value().value_or(1); if (threads == 0) { errors.push_back("threads must be >= 1"); - } else if (threads > 1) { - errors.push_back("threads > 1 is not yet supported"); + } + + const bool useRelayThread = config.use_relay_thread.value().value_or(true); + if (threads > 1 && !useRelayThread) { + errors.push_back("use_relay_thread must be true when threads > 1"); } const bool mvfstBpfSteering = config.mvfst_bpf_steering.value().value_or(true); @@ -877,6 +880,7 @@ folly::Expected resolveConfig(const ParsedConfig& c .admin = std::move(adminConfig), .relayID = std::move(relayID), .threads = threads, + .useRelayThread = useRelayThread, .mvfstBpfSteering = mvfstBpfSteering, }, .warnings = std::move(warnings), diff --git a/src/config/loader/ParsedConfig.h b/src/config/loader/ParsedConfig.h index 178d6a2..b075ff3 100644 --- a/src/config/loader/ParsedConfig.h +++ b/src/config/loader/ParsedConfig.h @@ -387,6 +387,11 @@ struct ParsedConfig { std::optional> listener_defaults; rfl::Description<"Number of IO worker threads (default: 1)", std::optional> threads; + rfl::Description< + "Dedicate one relay thread per service for relay state isolation (default: true). " + "Disable for baseline performance comparison.", + std::optional> + use_relay_thread; rfl::Description< "Attach a classic BPF reuseport filter to steer QUIC packets to the correct mvfst worker " "based on the connection ID's workerId field (Linux only, mvfst stack only, default: true). " diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bc86edc..dd48015 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable(moqx_relay_test MoqxRelayTrackStatusTests.cpp MoqxRelayNGRTests.cpp MoqxRelayPeerTests.cpp + MoqxRelayTestModes.cpp ) target_link_libraries(moqx_relay_test PRIVATE moqx_test_fixture diff --git a/test/MoqxRelayDataPlaneTests.cpp b/test/MoqxRelayDataPlaneTests.cpp index 2aa0606..e1daf0a 100644 --- a/test/MoqxRelayDataPlaneTests.cpp +++ b/test/MoqxRelayDataPlaneTests.cpp @@ -14,7 +14,7 @@ namespace moxygen::test { // new ones. // Sequence: publish, 2 subscribers, beginSubgroup, beginSubgroup again -> // first consumers get reset, both subscribers get new consumers. -TEST_F(MoQRelayTest, DuplicateSubgroupReplacesActiveConsumers) { +TEST_P(MoQRelayTest, DuplicateSubgroupReplacesActiveConsumers) { auto publisherSession = createMockSession(); auto sub1 = createMockSession(); auto sub2 = createMockSession(); @@ -75,7 +75,7 @@ TEST_F(MoQRelayTest, DuplicateSubgroupReplacesActiveConsumers) { // Test: Duplicate beginSubgroup after all subscribers have stop_sending'd // returns CANCELLED to propagate the signal back to the publisher. -TEST_F(MoQRelayTest, DuplicateSubgroupCancelledWhenNoActiveConsumers) { +TEST_P(MoQRelayTest, DuplicateSubgroupCancelledWhenNoActiveConsumers) { auto publisherSession = createMockSession(); auto subscriber = createMockSession(); @@ -114,7 +114,7 @@ TEST_F(MoQRelayTest, DuplicateSubgroupCancelledWhenNoActiveConsumers) { // Test: Duplicate beginSubgroup with partial stop_sending - active subscriber // gets reset and new consumer; tombstoned subscriber is skipped. -TEST_F(MoQRelayTest, DuplicateSubgroupSkipsTombstonedSubscriber) { +TEST_P(MoQRelayTest, DuplicateSubgroupSkipsTombstonedSubscriber) { auto publisherSession = createMockSession(); auto subA = createMockSession(); auto subB = createMockSession(); diff --git a/test/MoqxRelayFetchTests.cpp b/test/MoqxRelayFetchTests.cpp index b7f488c..948bbf6 100644 --- a/test/MoqxRelayFetchTests.cpp +++ b/test/MoqxRelayFetchTests.cpp @@ -14,7 +14,7 @@ namespace moxygen::test { // crash. When findPublishNamespaceSession returns null (no publishNamespace), // fetch falls back to subscriptions_. After onPublishDone, upstream is null // but the subscription entry remains if the forwarder has subscribers. -TEST_F(MoQRelayTest, FetchAfterPublisherTermination) { +TEST_P(MoQRelayTest, FetchAfterPublisherTermination) { auto publisherSession = createMockSession(); auto subSession = createMockSession(); auto fetchSession = createMockSession(); diff --git a/test/MoqxRelayNGRTests.cpp b/test/MoqxRelayNGRTests.cpp index 67a45b5..827fb86 100644 --- a/test/MoqxRelayNGRTests.cpp +++ b/test/MoqxRelayNGRTests.cpp @@ -12,7 +12,7 @@ namespace moxygen::test { // Test: relay PUBLISH path – dynamic groups from PublishRequest extensions // is stored in the forwarder and forwarded to every downstream subscriber -TEST_F(MoQRelayTest, RelayPublishPropagatesDynamicGroupsToSubscribers) { +TEST_P(MoQRelayTest, RelayPublishPropagatesDynamicGroupsToSubscribers) { auto publisherSession = createMockSession(); auto subscriberSession = createMockSession(); @@ -45,7 +45,7 @@ TEST_F(MoQRelayTest, RelayPublishPropagatesDynamicGroupsToSubscribers) { // Test: relay SUBSCRIBE path – dynamic groups from the upstream SubscribeOk is // stored in the forwarder and forwarded to both the first and late-joining // downstream subscribers -TEST_F(MoQRelayTest, RelaySubscribePropagatesDynamicGroupsToAllSubscribers) { +TEST_P(MoQRelayTest, RelaySubscribePropagatesDynamicGroupsToAllSubscribers) { auto publisherSession = createMockSession(); auto subscriber1 = createMockSession(); auto subscriber2 = createMockSession(); @@ -92,7 +92,7 @@ TEST_F(MoQRelayTest, RelaySubscribePropagatesDynamicGroupsToAllSubscribers) { // Relay test: When a late-joining subscriber sends NEW_GROUP_REQUEST in its // SUBSCRIBE, the relay forwards it upstream via REQUEST_UPDATE -TEST_F(MoQRelayTest, RelaySubscribeLateJoinerNGRForwardedUpstream) { +TEST_P(MoQRelayTest, RelaySubscribeLateJoinerNGRForwardedUpstream) { auto publisherSession = createMockSession(); auto subscriber1 = createMockSession(); auto subscriber2 = createMockSession(); @@ -163,7 +163,7 @@ TEST_F(MoQRelayTest, RelaySubscribeLateJoinerNGRForwardedUpstream) { // Relay test: A downstream subscriber sending REQUEST_UPDATE with // NEW_GROUP_REQUEST causes the relay to cascade the NGR upstream -TEST_F(MoQRelayTest, RelayRequestUpdateNGRCascadedUpstream) { +TEST_P(MoQRelayTest, RelayRequestUpdateNGRCascadedUpstream) { auto publisherSession = createMockSession(); auto subscriberSession = createMockSession(); @@ -218,7 +218,7 @@ TEST_F(MoQRelayTest, RelayRequestUpdateNGRCascadedUpstream) { // Relay test: downstream subscriber returns PublishOk carrying NEW_GROUP_REQUEST; // relay cascades NGR to the publisher handle upstream via REQUEST_UPDATE -TEST_F(MoQRelayTest, PublishOkNewNGRForwardedUpstream) { +TEST_P(MoQRelayTest, PublishOkNewNGRForwardedUpstream) { auto publisherSession = createMockSession(); auto subscriberSession = createMockSession(); @@ -279,7 +279,7 @@ TEST_F(MoQRelayTest, PublishOkNewNGRForwardedUpstream) { // Relay test: a second subscriber returning the same NEW_GROUP_REQUEST value in // its PublishOk is deduplicated; the upstream handle receives exactly one // REQUEST_UPDATE -TEST_F(MoQRelayTest, PublishOkDuplicateNGRNotForwardedUpstream) { +TEST_P(MoQRelayTest, PublishOkDuplicateNGRNotForwardedUpstream) { auto publisherSession = createMockSession(); auto subscriber1 = createMockSession(); auto subscriber2 = createMockSession(); diff --git a/test/MoqxRelayPeerTests.cpp b/test/MoqxRelayPeerTests.cpp index dbbc576..086a0d0 100644 --- a/test/MoqxRelayPeerTests.cpp +++ b/test/MoqxRelayPeerTests.cpp @@ -12,7 +12,7 @@ namespace moxygen::test { // Test: makeNamespaceBridgeHandle routes namespaceMsg to doPublishNamespace -TEST_F(MoQRelayTest, NamespaceBridgeHandleForwardsNamespaceMsg) { +TEST_P(MoQRelayTest, NamespaceBridgeHandleForwardsNamespaceMsg) { auto peerSession = createMockSession(); // Bridge handle routes NAMESPACE messages from peerSession into the relay. @@ -32,7 +32,7 @@ TEST_F(MoQRelayTest, NamespaceBridgeHandleForwardsNamespaceMsg) { } // Test: makeNamespaceBridgeHandle routes namespaceDoneMsg to doPublishNamespaceDone -TEST_F(MoQRelayTest, NamespaceBridgeHandleForwardsDoneMsg) { +TEST_P(MoQRelayTest, NamespaceBridgeHandleForwardsDoneMsg) { auto peerSession = createMockSession(); auto handle = makeNamespaceBridgeHandle(relay_, peerSession); @@ -65,7 +65,7 @@ TEST_F(MoQRelayTest, NamespaceBridgeHandleForwardsDoneMsg) { // // Production relays negotiate draft-16 (empty prefix allowed). The delivery // path for draft-16 is synchronous: namespacePublishHandle->namespaceMsg(). -TEST_F(MoQRelayTest, PeerNamespaceNotEchoedBackOnReconnect) { +TEST_P(MoQRelayTest, PeerNamespaceNotEchoedBackOnReconnect) { // Relay must have a relayID for peer detection to activate. resetRelay(std::make_shared(config::CacheConfig{.maxCachedTracks = 0}, "sg-sin-2-1")); relay_->setAllowedNamespacePrefix(kAllowedPrefix); @@ -107,7 +107,7 @@ TEST_F(MoQRelayTest, PeerNamespaceNotEchoedBackOnReconnect) { // Complement: namespaces from LOCAL publishers (not from the peer) must still // be delivered when that peer subscribes. -TEST_F(MoQRelayTest, LocalNamespaceDeliveredToPeerOnReconnect) { +TEST_P(MoQRelayTest, LocalNamespaceDeliveredToPeerOnReconnect) { resetRelay(std::make_shared(config::CacheConfig{.maxCachedTracks = 0}, "sg-sin-2-1")); relay_->setAllowedNamespacePrefix(kAllowedPrefix); @@ -183,7 +183,7 @@ class PeerAnnounceSession : public NiceMock { // directly via makeNamespaceBridgeHandle), this test goes through the full // publisherInterface()->subscribeNamespace() production path so the bug in the call-site is // exercised. -TEST_F(MoQRelayTest, PeerNamespaceNotEchoedBack_FullProductionPath) { +TEST_P(MoQRelayTest, PeerNamespaceNotEchoedBack_FullProductionPath) { resetRelay(std::make_shared(config::CacheConfig{.maxCachedTracks = 0}, "sg-sin-2-1")); relay_->setAllowedNamespacePrefix(kAllowedPrefix); @@ -237,7 +237,7 @@ TEST_F(MoQRelayTest, PeerNamespaceNotEchoedBack_FullProductionPath) { // without graceful namespaceDoneMsg calls, tree entries it created must be // cleaned up so stale sourceSession shared_ptrs don't keep dead session objects // alive and downstream subscribers receive NAMESPACE_DONE. -TEST_F(MoQRelayTest, BridgeHandleDestructorCleansUpNamespaces) { +TEST_P(MoQRelayTest, BridgeHandleDestructorCleansUpNamespaces) { auto upstreamSession = createMockSession(); // Simulate the bridge path: create a handle and announce a namespace through @@ -264,7 +264,7 @@ TEST_F(MoQRelayTest, BridgeHandleDestructorCleansUpNamespaces) { // Verify that when a new publisher takes over a namespace before the old // bridge handle is destroyed, the stale handle's destructor does NOT evict // the new publisher's entry (doPublishNamespaceDone guards on sourceSession). -TEST_F(MoQRelayTest, BridgeHandleDestructorDoesNotEvictNewPublisher) { +TEST_P(MoQRelayTest, BridgeHandleDestructorDoesNotEvictNewPublisher) { auto session1 = createMockSession(); auto session2 = createMockSession(); diff --git a/test/MoqxRelayPublishTests.cpp b/test/MoqxRelayPublishTests.cpp index a93c13a..ef85712 100644 --- a/test/MoqxRelayPublishTests.cpp +++ b/test/MoqxRelayPublishTests.cpp @@ -11,7 +11,7 @@ namespace moxygen::test { // Test: Verify allowed namespace prefix is set correctly -TEST_F(MoQRelayTest, AllowedNamespacePrefix) { +TEST_P(MoQRelayTest, AllowedNamespacePrefix) { // This just verifies the relay can be constructed with a namespace prefix // More detailed testing requires full session setup auto relay2 = std::make_shared(config::CacheConfig{ @@ -23,7 +23,7 @@ TEST_F(MoQRelayTest, AllowedNamespacePrefix) { } // Test: Publish a track through the relay -TEST_F(MoQRelayTest, PublishSuccess) { +TEST_P(MoQRelayTest, PublishSuccess) { auto publisherSession = createMockSession(); // Publish the namespace @@ -48,7 +48,7 @@ TEST_F(MoQRelayTest, PublishSuccess) { // Test: Extensions from publish are forwarded to subscribers via // subscribeNamespace -TEST_F(MoQRelayTest, PublishExtensionsForwardedToSubscribers) { +TEST_P(MoQRelayTest, PublishExtensionsForwardedToSubscribers) { auto publisherSession = createMockSession(); auto subscriber = createMockSession(); @@ -107,7 +107,7 @@ TEST_F(MoQRelayTest, PublishExtensionsForwardedToSubscribers) { // ============================================================ // Test: Extensions from publish are forwarded to late-joining subscribers -TEST_F(MoQRelayTest, PublishExtensionsForwardedToLateJoiners) { +TEST_P(MoQRelayTest, PublishExtensionsForwardedToLateJoiners) { auto publisherSession = createMockSession(); auto subscriber1 = createMockSession(); auto subscriber2 = createMockSession(); @@ -199,7 +199,7 @@ TEST_F(MoQRelayTest, PublishExtensionsForwardedToLateJoiners) { // 4. Session A reconnects and re-publishes the same track. The multipublisher // check finds the surviving entry and calls it->second.handle->unsubscribe() // — null-pointer dereference, SIGSEGV. -TEST_F(MoQRelayTest, PublisherReconnectWithOpenSubgroupNoSegfault) { +TEST_P(MoQRelayTest, PublisherReconnectWithOpenSubgroupNoSegfault) { auto publisherSession = createMockSession(); auto subscriberSession = createMockSession(); @@ -260,7 +260,7 @@ TEST_F(MoQRelayTest, PublisherReconnectWithOpenSubgroupNoSegfault) { // old forwarder's subscribers must receive publishDone, and the new // publish-path subscription must be fully functional (accepting data from the // new publisher). -TEST_F(MoQRelayTest, PublishReplacesSubscribeDrainsOldAndServesNew) { +TEST_P(MoQRelayTest, PublishReplacesSubscribeDrainsOldAndServesNew) { auto publisherSession = createMockSession(); auto subscriberSession = createMockSession(); @@ -334,7 +334,7 @@ TEST_F(MoQRelayTest, PublishReplacesSubscribeDrainsOldAndServesNew) { // ScopeGuardImplBase::terminate() → std::terminate (exit code 139). // // Without the fix: crashes. With the fix: subscribe returns an error cleanly. -TEST_F(MoQRelayTest, PublishReconnectDuringSubscribeScopeGuardCrash) { +TEST_P(MoQRelayTest, PublishReconnectDuringSubscribeScopeGuardCrash) { auto publisherSession1 = createMockSession(); auto publisherSession2 = createMockSession(); auto subscriberSession = createMockSession(); @@ -423,7 +423,7 @@ TEST_F(MoQRelayTest, PublishReconnectDuringSubscribeScopeGuardCrash) { // entry (promise already satisfied), and rsub.promise.setValue() throws // PromiseAlreadySatisfied, which propagates as an unhandled coroutine exception. // With the fix: subscribe returns SUBSCRIBE_ERROR "publisher reconnected". -TEST_F(MoQRelayTest, PublishReconnectDuringSubscribeSuccessPathCrash) { +TEST_P(MoQRelayTest, PublishReconnectDuringSubscribeSuccessPathCrash) { auto publisherSession1 = createMockSession(); auto publisherSession2 = createMockSession(); auto subscriberSession = createMockSession(); @@ -498,7 +498,7 @@ TEST_F(MoQRelayTest, PublishReconnectDuringSubscribeSuccessPathCrash) { // Regression: after publishDone the namespace-tree node must be pruned when // the track was the only remaining content. (Was a bug before unpublishTrack // gained a NodeMutationGuard; kept as a regression guard.) -TEST_F(MoQRelayTest, PublishDonePrunesNamespaceTreeNode) { +TEST_P(MoQRelayTest, PublishDonePrunesNamespaceTreeNode) { auto publisher = createMockSession(); doPublishNamespace(publisher, kTestNamespace); @@ -541,7 +541,7 @@ TEST_F(MoQRelayTest, PublishDonePrunesNamespaceTreeNode) { } // Empty namespace: publishNamespace with an empty TrackNamespace must not crash. -TEST_F(MoQRelayTest, EmptyNamespacePublishNamespaceDone) { +TEST_P(MoQRelayTest, EmptyNamespacePublishNamespaceDone) { auto publisher = createMockSession(); TrackNamespace emptyNs{{}}; diff --git a/test/MoqxRelaySubNsTests.cpp b/test/MoqxRelaySubNsTests.cpp index bf63e11..c89d2cf 100644 --- a/test/MoqxRelaySubNsTests.cpp +++ b/test/MoqxRelaySubNsTests.cpp @@ -10,7 +10,7 @@ namespace moxygen::test { -TEST_F(MoQRelayTest, SubscribeNamespaceDoesntAddDrainingPublish) { +TEST_P(MoQRelayTest, SubscribeNamespaceDoesntAddDrainingPublish) { auto publisherSession = createMockSession(); auto subscriber1 = createMockSession(); auto subscriber2 = createMockSession(); @@ -89,7 +89,7 @@ TEST_F(MoQRelayTest, SubscribeNamespaceDoesntAddDrainingPublish) { removeSession(subscriber2); } -TEST_F(MoQRelayTest, SubscribeNamespaceEmptyPrefixRejectedPreV16) { +TEST_P(MoQRelayTest, SubscribeNamespaceEmptyPrefixRejectedPreV16) { // Default session uses kVersionDraftCurrent (draft-14, which is < 16) auto session = createMockSession(); @@ -109,7 +109,7 @@ TEST_F(MoQRelayTest, SubscribeNamespaceEmptyPrefixRejectedPreV16) { removeSession(session); } -TEST_F(MoQRelayTest, SubscribeNamespaceEmptyPrefixAllowedV16) { +TEST_P(MoQRelayTest, SubscribeNamespaceEmptyPrefixAllowedV16) { auto session = createMockSession(); // Override the negotiated version to draft-16 ON_CALL(*session, getNegotiatedVersion()) @@ -121,7 +121,7 @@ TEST_F(MoQRelayTest, SubscribeNamespaceEmptyPrefixAllowedV16) { removeSession(session); } -TEST_F(MoQRelayTest, ExactNamespaceSubscriberReceivesPublishNamespace) { +TEST_P(MoQRelayTest, ExactNamespaceSubscriberReceivesPublishNamespace) { auto subscriber = createMockSession(); auto publisher = createMockSession(); @@ -156,7 +156,7 @@ TEST_F(MoQRelayTest, ExactNamespaceSubscriberReceivesPublishNamespace) { // forwarder is empty, the relay fires REQUEST_UPDATE twice — once explicitly // at the if(forwarder->empty()) site and once via forwardChanged() when // addSubscriber() increments numForwardingSubscribers from 0 to 1. -TEST_F(MoQRelayTest, SubscribeNs_ForwardTrue_EmptyForwarder_SingleRequestUpdate) { +TEST_P(MoQRelayTest, SubscribeNs_ForwardTrue_EmptyForwarder_SingleRequestUpdate) { auto pubSession = createMockSession(); doPublishNamespace(pubSession, kTestNamespace); auto mockHandle = makePublishHandle(); @@ -192,7 +192,7 @@ TEST_F(MoQRelayTest, SubscribeNs_ForwardTrue_EmptyForwarder_SingleRequestUpdate) // forwarder is empty, the relay fires a spurious REQUEST_UPDATE(forward=false) // at the if(forwarder->empty()) site — even though the upstream is already at // forward=false (set by publish() which found no subscribers). -TEST_F(MoQRelayTest, SubscribeNs_ForwardFalse_EmptyForwarder_NoRequestUpdate) { +TEST_P(MoQRelayTest, SubscribeNs_ForwardFalse_EmptyForwarder_NoRequestUpdate) { auto pubSession = createMockSession(); doPublishNamespace(pubSession, kTestNamespace); auto mockHandle = makePublishHandle(); diff --git a/test/MoqxRelaySubscribeTests.cpp b/test/MoqxRelaySubscribeTests.cpp index cb2075a..816b19c 100644 --- a/test/MoqxRelaySubscribeTests.cpp +++ b/test/MoqxRelaySubscribeTests.cpp @@ -14,7 +14,7 @@ namespace moxygen::test { // terminated (onPublishDone clears handle/upstream). We trigger forwardChanged // via Subscriber::requestUpdate changing forward from true→false (1→0 // transition). The subscriber survives drain because it has an open subgroup. -TEST_F(MoQRelayTest, ForwardChangedAfterPublisherTermination) { +TEST_P(MoQRelayTest, ForwardChangedAfterPublisherTermination) { auto publisherSession = createMockSession(); auto subSession = createMockSession(); @@ -68,7 +68,7 @@ TEST_F(MoQRelayTest, ForwardChangedAfterPublisherTermination) { // twice — once via forwardChanged() (which fires synchronously inside addSubscriber // via addForwardingSubscriber) and once via the explicit block at the end of the // subscribe() else-branch. Analogous to the subscribeNamespace bug fixed in this PR. -TEST_F(MoQRelayTest, Subscribe_SecondForwardingSubscriber_SingleRequestUpdate) { +TEST_P(MoQRelayTest, Subscribe_SecondForwardingSubscriber_SingleRequestUpdate) { auto pubSession = createMockSession(); doPublishNamespace(pubSession, kTestNamespace); auto mockHandle = makePublishHandle(); diff --git a/test/MoqxRelayTestFixture.h b/test/MoqxRelayTestFixture.h index 187c612..245a1cb 100644 --- a/test/MoqxRelayTestFixture.h +++ b/test/MoqxRelayTestFixture.h @@ -29,6 +29,18 @@ using namespace openmoq::moqx; namespace moxygen::test { +enum class RelayMode { + SingleThread, +}; + +inline void PrintTo(RelayMode mode, std::ostream* os) { + switch (mode) { + case RelayMode::SingleThread: + *os << "SingleThread"; + return; + } +} + inline const TrackNamespace kTestNamespace{{"test", "namespace"}}; inline const TrackNamespace kAllowedPrefix{{"test"}}; inline const FullTrackName kTestTrackName{kTestNamespace, "track1"}; @@ -48,8 +60,10 @@ class TestMoQExecutor : public MoQFollyExecutorImpl, public folly::DrivableExecu }; // Test fixture for MoqxRelay and NamespaceTree tests. -class MoQRelayTest : public ::testing::Test { +class MoQRelayTest : public ::testing::TestWithParam { protected: + virtual RelayMode relayMode() const { return GetParam(); } + void SetUp() override; void TearDown() override; diff --git a/test/MoqxRelayTestModes.cpp b/test/MoqxRelayTestModes.cpp new file mode 100644 index 0000000..38faea8 --- /dev/null +++ b/test/MoqxRelayTestModes.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) OpenMOQ contributors. + */ + +#include "MoqxRelayTestFixture.h" + +namespace moxygen::test { + +INSTANTIATE_TEST_SUITE_P( + AllModes, + MoQRelayTest, + ::testing::Values(RelayMode::SingleThread), + [](const ::testing::TestParamInfo& info) -> std::string { + switch (info.param) { + case RelayMode::SingleThread: + return "SingleThread"; + } + return "Unknown"; + } +); + +} // namespace moxygen::test diff --git a/test/MoqxRelayTrackStatusTests.cpp b/test/MoqxRelayTrackStatusTests.cpp index c4dc751..b222a10 100644 --- a/test/MoqxRelayTrackStatusTests.cpp +++ b/test/MoqxRelayTrackStatusTests.cpp @@ -11,7 +11,7 @@ namespace moxygen::test { // Test: TrackStatus on non-existent track -TEST_F(MoQRelayTest, TrackStatusNonExistentTrack) { +TEST_P(MoQRelayTest, TrackStatusNonExistentTrack) { auto clientSession = createMockSession(); // Request trackStatus for a track that doesn't exist @@ -34,7 +34,7 @@ TEST_F(MoQRelayTest, TrackStatusNonExistentTrack) { // Test: TrackStatus on existing track - returns forwarder state (no upstream // call) -TEST_F(MoQRelayTest, TrackStatusSuccessfulForward) { +TEST_P(MoQRelayTest, TrackStatusSuccessfulForward) { auto publisherSession = createMockSession(); auto clientSession = createMockSession(); @@ -67,7 +67,7 @@ TEST_F(MoQRelayTest, TrackStatusSuccessfulForward) { // Verifies that when there's no exact subscription but a publisher has // published a matching namespace prefix, the relay correctly routes // TRACK_STATUS upstream using prefix matching -TEST_F(MoQRelayTest, TrackStatusViaPrefixMatching) { +TEST_P(MoQRelayTest, TrackStatusViaPrefixMatching) { auto publisher = createMockSession(); auto requester = createMockSession(); diff --git a/test/MoqxTrackFilterTest.cpp b/test/MoqxTrackFilterTest.cpp index 50d6f69..443fae1 100644 --- a/test/MoqxTrackFilterTest.cpp +++ b/test/MoqxTrackFilterTest.cpp @@ -31,6 +31,8 @@ constexpr uint64_t kPropType = 0x100; // audio level property type class MoqxTrackFilterTest : public moxygen::test::MoQRelayTest { protected: + RelayMode relayMode() const override { return RelayMode::SingleThread; } + void SetUp() override { MoQRelayTest::SetUp(); relay_ = std::make_shared( diff --git a/test/config/ConfigResolverTest.cpp b/test/config/ConfigResolverTest.cpp index ac08fc7..37e8d82 100644 --- a/test/config/ConfigResolverTest.cpp +++ b/test/config/ConfigResolverTest.cpp @@ -920,12 +920,29 @@ TEST(ResolveConfig, ThreadsZeroRejected) { EXPECT_THAT(result.error(), HasSubstr("threads must be >= 1")); } -TEST(ResolveConfig, ThreadsGreaterThanOneRejected) { +TEST(ResolveConfig, ThreadsGreaterThanOneAccepted) { auto cfg = makeMinimalInsecureConfig(); cfg.threads = std::optional{2}; auto result = resolveConfig(cfg); + ASSERT_TRUE(result.hasValue()); + EXPECT_EQ(result.value().config.threads, 2u); +} + +TEST(ResolveConfig, UseRelayThreadFalseWithOneThreadAccepted) { + auto cfg = makeMinimalInsecureConfig(); + cfg.use_relay_thread = std::optional{false}; + auto result = resolveConfig(cfg); + ASSERT_TRUE(result.hasValue()); + EXPECT_FALSE(result.value().config.useRelayThread); +} + +TEST(ResolveConfig, UseRelayThreadFalseWithMultipleThreadsRejected) { + auto cfg = makeMinimalInsecureConfig(); + cfg.threads = std::optional{2}; + cfg.use_relay_thread = std::optional{false}; + auto result = resolveConfig(cfg); ASSERT_TRUE(result.hasError()); - EXPECT_THAT(result.error(), HasSubstr("threads > 1 is not yet supported")); + EXPECT_THAT(result.error(), HasSubstr("use_relay_thread must be true when threads > 1")); } // --- multiple listeners tests ---