blob: 324e003925c3cfad7894f3d221a2c612042d3542 [file]
// Copyright 2025 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/moqt/relay_namespace_tree.h"
#include <memory>
#include <utility>
#include "quiche/quic/moqt/moqt_fetch_task.h"
#include "quiche/quic/moqt/moqt_names.h"
#include "quiche/quic/moqt/test_tools/mock_moqt_session.h"
#include "quiche/common/platform/api/quiche_expect_bug.h"
#include "quiche/common/platform/api/quiche_test.h"
namespace moqt {
namespace test {
class TestRelayNamespaceTree : public RelayNamespaceTree {
public:
using RelayNamespaceTree::NumNamespaces;
};
using ::testing::_;
class RelayNamespaceTreeTest : public quiche::test::QuicheTest {
public:
RelayNamespaceTreeTest() : session_(std::make_unique<MockMoqtSession>()) {}
TestRelayNamespaceTree tree_;
TrackNamespace a_{"a"}, ab_{"a", "b"}, abc_{"a", "b", "c"};
std::unique_ptr<MockMoqtSession> session_;
int objects_available_ = 0;
ObjectsAvailableCallback callback_ = [&]() { ++objects_available_; };
void CheckNextSuffix(MoqtNamespaceTask* task, TrackNamespace& full,
bool add = true) {
TrackNamespace suffix;
TransactionType type;
EXPECT_EQ(task->GetNextSuffix(suffix, type), GetNextResult::kSuccess);
EXPECT_EQ(*task->prefix().AddSuffix(suffix), full);
EXPECT_EQ(type, add ? TransactionType::kAdd : TransactionType::kDelete);
}
};
TEST_F(RelayNamespaceTreeTest, AddGetRemovePublisher) {
EXPECT_EQ(tree_.NumNamespaces(), 0u);
EXPECT_EQ(tree_.GetValidPublisher(ab_), nullptr);
tree_.AddPublisher(ab_, session_.get());
EXPECT_EQ(tree_.NumNamespaces(), 3u);
EXPECT_EQ(tree_.GetValidPublisher(a_), nullptr);
EXPECT_EQ(tree_.GetValidPublisher(ab_), session_.get());
EXPECT_EQ(tree_.GetValidPublisher(abc_), session_.get());
tree_.RemovePublisher(ab_, session_.get());
EXPECT_EQ(tree_.NumNamespaces(), 0u);
EXPECT_EQ(tree_.GetValidPublisher(ab_), nullptr);
}
TEST_F(RelayNamespaceTreeTest, AddGetRemoveListener) {
// Add a listener to a namespace that has no publishers.
EXPECT_EQ(tree_.NumNamespaces(), 0u);
std::unique_ptr<MoqtNamespaceTask> task =
tree_.AddSubscriber(ab_, session_.get());
task->SetObjectsAvailableCallback(std::move(callback_));
EXPECT_EQ(tree_.NumNamespaces(), 3u);
tree_.AddPublisher(a_, session_.get());
EXPECT_EQ(objects_available_, 0);
tree_.AddPublisher(ab_, session_.get());
EXPECT_EQ(objects_available_, 1);
CheckNextSuffix(task.get(), ab_);
tree_.AddPublisher(abc_, session_.get());
EXPECT_EQ(objects_available_, 2);
CheckNextSuffix(task.get(), abc_);
EXPECT_EQ(tree_.NumNamespaces(), 4u);
// Second publisher creates no new notifications, and delays OnNamespaceDone.
auto session2 = std::make_unique<MockMoqtSession>();
tree_.AddPublisher(ab_, session2.get());
EXPECT_EQ(objects_available_, 2);
tree_.RemovePublisher(ab_, session_.get());
EXPECT_EQ(objects_available_, 2);
tree_.RemovePublisher(ab_, session2.get());
EXPECT_EQ(objects_available_, 3);
CheckNextSuffix(task.get(), ab_, /*add=*/false);
// Removing the listener disables notifications.
task.reset();
tree_.AddPublisher(ab_, session2.get());
EXPECT_EQ(objects_available_, 3);
}
TEST_F(RelayNamespaceTreeTest, SessionDestroyed) {
std::unique_ptr<MoqtNamespaceTask> task =
tree_.AddSubscriber(ab_, session_.get());
task->SetObjectsAvailableCallback(std::move(callback_));
tree_.AddPublisher(ab_, session_.get());
EXPECT_EQ(objects_available_, 1);
CheckNextSuffix(task.get(), ab_);
EXPECT_NE(tree_.GetValidPublisher(ab_), nullptr);
// First session dies. In real life, it would have destroyed the stream and
// therefore the task, removing the entry. But verify that the WeakPtr works.
session_.reset();
EXPECT_EQ(tree_.GetValidPublisher(ab_), nullptr);
}
TEST_F(RelayNamespaceTreeTest, AddListenerToExistingPublisher) {
tree_.AddPublisher(a_, session_.get());
tree_.AddPublisher(ab_, session_.get());
tree_.AddPublisher(abc_, session_.get());
std::unique_ptr<MoqtNamespaceTask> task =
tree_.AddSubscriber(ab_, session_.get());
task->SetObjectsAvailableCallback(std::move(callback_));
EXPECT_EQ(objects_available_, 2);
CheckNextSuffix(task.get(), ab_);
CheckNextSuffix(task.get(), abc_);
}
TEST_F(RelayNamespaceTreeTest, MaxSizeNamespace) {
std::unique_ptr<MoqtNamespaceTask> task =
tree_.AddSubscriber(a_, session_.get());
task->SetObjectsAvailableCallback(std::move(callback_));
TrackNamespace big_namespace{"a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k", "l", "m", "n", "o", "p",
"q", "r", "s", "t", "u", "v", "w", "x",
"y", "z", "1", "2", "3", "4", "5", "6"};
tree_.AddPublisher(big_namespace, session_.get());
EXPECT_EQ(objects_available_, 1);
TrackNamespace suffix;
TransactionType type;
CheckNextSuffix(task.get(), big_namespace);
EXPECT_EQ(task->GetNextSuffix(suffix, type), GetNextResult::kPending);
}
// TODO(martinduke): Add tests for published tracks.
} // namespace test
} // namespace moqt