Source code

Revision control

Copy as Markdown

Other Tools

/*
* Copyright 2026 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/create_modular_peer_connection_factory.h"
#include "api/data_channel_interface.h"
#include "api/jsep.h"
#include "api/make_ref_counted.h"
#include "api/peer_connection_interface.h"
#include "api/peer_connection_tracer_interface.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "pc/peer_connection.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/test/enable_fake_media.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/thread.h"
#include "rtc_base/virtual_socket_server.h"
#include "test/create_test_environment.h"
#include "test/gtest.h"
#include "test/run_loop.h"
#include "test/wait_until.h"
namespace webrtc {
namespace {
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
// Records how many times each tracer method has been invoked. Tests retain a
// raw pointer to the installed tracer (the PC owns it) and assert against the
// counts after driving operations.
class CountingTracer : public PeerConnectionTracerInterface {
public:
struct Counts {
int create_offer = 0;
int create_offer_success = 0;
int create_offer_failure = 0;
int create_answer = 0;
int create_answer_success = 0;
int create_answer_failure = 0;
int set_local_description = 0;
int set_local_description_implicit = 0;
int set_local_description_success = 0;
int set_local_description_success_with_sdp = 0;
int set_local_description_failure = 0;
int set_remote_description = 0;
int set_remote_description_success = 0;
int set_remote_description_failure = 0;
int set_configuration = 0;
int close = 0;
int ice_candidate = 0;
int add_ice_candidate = 0;
int ice_candidate_error = 0;
int data_channel_created_local = 0;
int data_channel_created_remote = 0;
int signaling_state_changed = 0;
int ice_connection_state_changed = 0;
int connection_state_changed = 0;
int ice_gathering_state_changed = 0;
int negotiation_needed_event = 0;
};
const Counts& counts() const { return counts_; }
void OnCreateOffer(
const PeerConnectionInterface::RTCOfferAnswerOptions&) override {
counts_.create_offer++;
}
void OnCreateOfferSuccess(const SessionDescriptionInterface*) override {
counts_.create_offer_success++;
}
void OnCreateOfferFailure(const RTCError&) override {
counts_.create_offer_failure++;
}
void OnCreateAnswer(
const PeerConnectionInterface::RTCOfferAnswerOptions&) override {
counts_.create_answer++;
}
void OnCreateAnswerSuccess(const SessionDescriptionInterface*) override {
counts_.create_answer_success++;
}
void OnCreateAnswerFailure(const RTCError&) override {
counts_.create_answer_failure++;
}
void OnSetLocalDescription(
const SessionDescriptionInterface* description) override {
counts_.set_local_description++;
if (description == nullptr) {
counts_.set_local_description_implicit++;
}
}
void OnSetLocalDescriptionSuccess(
const SessionDescriptionInterface* description) override {
counts_.set_local_description_success++;
if (description != nullptr) {
counts_.set_local_description_success_with_sdp++;
}
}
void OnSetLocalDescriptionFailure(const RTCError&) override {
counts_.set_local_description_failure++;
}
void OnSetRemoteDescription(const SessionDescriptionInterface*) override {
counts_.set_remote_description++;
}
void OnSetRemoteDescriptionSuccess() override {
counts_.set_remote_description_success++;
}
void OnSetRemoteDescriptionFailure(const RTCError&) override {
counts_.set_remote_description_failure++;
}
void OnSetConfiguration(
const PeerConnectionInterface::RTCConfiguration&) override {
counts_.set_configuration++;
}
void OnClose() override { counts_.close++; }
void OnIceCandidate(const IceCandidate&) override { counts_.ice_candidate++; }
void OnAddIceCandidate(const IceCandidate&, bool) override {
counts_.add_ice_candidate++;
}
void OnIceCandidateError(absl::string_view,
int,
absl::string_view,
int,
absl::string_view) override {
counts_.ice_candidate_error++;
}
void OnCreateDataChannel(const DataChannelInterface&) override {
counts_.data_channel_created_local++;
}
void OnDataChannel(const DataChannelInterface&) override {
counts_.data_channel_created_remote++;
}
void OnSignalingStateChanged(
PeerConnectionInterface::SignalingState) override {
counts_.signaling_state_changed++;
}
void OnIceConnectionStateChanged(
PeerConnectionInterface::IceConnectionState) override {
counts_.ice_connection_state_changed++;
}
void OnConnectionStateChanged(
PeerConnectionInterface::PeerConnectionState) override {
counts_.connection_state_changed++;
}
void OnIceGatheringStateChanged(
PeerConnectionInterface::IceGatheringState) override {
counts_.ice_gathering_state_changed++;
}
void OnNegotiationNeededEvent() override {
counts_.negotiation_needed_event++;
}
private:
Counts counts_;
};
PeerConnectionFactoryDependencies CreateFactoryDependencies() {
PeerConnectionFactoryDependencies deps;
deps.env = CreateTestEnvironment();
deps.network_thread = Thread::Current();
deps.worker_thread = Thread::Current();
deps.signaling_thread = Thread::Current();
EnableFakeMedia(deps);
return deps;
}
class PeerConnectionTracerTest : public ::testing::Test {
protected:
PeerConnectionTracerTest()
: vss_(std::make_unique<VirtualSocketServer>()), main_(vss_.get()) {
pc_factory_ =
CreateModularPeerConnectionFactory(CreateFactoryDependencies());
}
// Creates a PC with a freshly attached CountingTracer. The tracer is owned
// by the PC; tests recover it via Counts() (or by hand through
// pc->GetInternalPeerConnection()->tracer()).
std::unique_ptr<PeerConnectionWrapper> CreatePeerConnection() {
auto observer = std::make_unique<MockPeerConnectionObserver>();
PeerConnectionDependencies deps(observer.get());
deps.tracer = std::make_unique<CountingTracer>();
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
auto result =
pc_factory_->CreatePeerConnectionOrError(config, std::move(deps));
if (!result.ok()) {
return nullptr;
}
observer->SetPeerConnectionInterface(result.value().get());
return std::make_unique<PeerConnectionWrapper>(
pc_factory_, result.MoveValue(), std::move(observer));
}
// Returns the CountingTracer attached to `pc`, recovered through the
// tracer() accessor on PeerConnection.
const CountingTracer::Counts& Counts(const PeerConnectionWrapper& pc) {
return static_cast<const CountingTracer*>(
pc.GetInternalPeerConnection()->tracer())
->counts();
}
std::unique_ptr<VirtualSocketServer> vss_;
test::RunLoop main_;
scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
};
// At construction time the tracer should see exactly one
// OnSetConfiguration (for the initial configuration) and nothing else.
TEST_F(PeerConnectionTracerTest, FiresOnConstruction) {
auto pc = CreatePeerConnection();
ASSERT_TRUE(pc);
EXPECT_EQ(Counts(*pc).set_configuration, 1);
EXPECT_EQ(Counts(*pc).create_offer, 0);
EXPECT_EQ(Counts(*pc).close, 0);
}
// CreateOffer should fire OnCreateOffer once and OnCreateOfferSuccess once
// when the operation resolves successfully.
TEST_F(PeerConnectionTracerTest, FiresOnCreateOffer) {
auto pc = CreatePeerConnection();
ASSERT_TRUE(pc);
pc->AddAudioTrack("a");
auto offer = pc->CreateOffer();
ASSERT_TRUE(offer);
EXPECT_EQ(Counts(*pc).create_offer, 1);
EXPECT_EQ(Counts(*pc).create_offer_success, 1);
EXPECT_EQ(Counts(*pc).create_offer_failure, 0);
}
// SetLocalDescription with an explicit description should fire
// OnSetLocalDescription with that description and report success.
TEST_F(PeerConnectionTracerTest, FiresOnSetLocalDescription) {
auto pc = CreatePeerConnection();
ASSERT_TRUE(pc);
pc->AddAudioTrack("a");
auto offer = pc->CreateOffer();
ASSERT_TRUE(offer);
ASSERT_TRUE(pc->SetLocalDescription(std::move(offer)));
EXPECT_EQ(Counts(*pc).set_local_description, 1);
EXPECT_EQ(Counts(*pc).set_local_description_implicit, 0);
EXPECT_EQ(Counts(*pc).set_local_description_success, 1);
EXPECT_EQ(Counts(*pc).set_local_description_success_with_sdp, 1);
EXPECT_EQ(Counts(*pc).set_local_description_failure, 0);
}
// CreateDataChannel should fire OnCreateDataChannel.
TEST_F(PeerConnectionTracerTest, FiresOnCreateDataChannel) {
auto pc = CreatePeerConnection();
ASSERT_TRUE(pc);
auto channel = pc->CreateDataChannel("dc");
ASSERT_TRUE(channel);
EXPECT_EQ(Counts(*pc).data_channel_created_local, 1);
EXPECT_EQ(Counts(*pc).data_channel_created_remote, 0);
}
// SetConfiguration should fire OnSetConfiguration in addition to the one
// fired at construction.
TEST_F(PeerConnectionTracerTest, FiresOnSetConfiguration) {
auto pc = CreatePeerConnection();
ASSERT_TRUE(pc);
ASSERT_EQ(Counts(*pc).set_configuration, 1);
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
ASSERT_TRUE(pc->pc()->SetConfiguration(config).ok());
EXPECT_EQ(Counts(*pc).set_configuration, 2);
}
// Close should fire OnClose once and at least one signaling-state transition
// (to "closed").
TEST_F(PeerConnectionTracerTest, FiresOnClose) {
auto pc = CreatePeerConnection();
ASSERT_TRUE(pc);
pc->pc()->Close();
EXPECT_EQ(Counts(*pc).close, 1);
EXPECT_GE(Counts(*pc).signaling_state_changed, 1);
// Calling Close again should be a no-op for the tracer.
pc->pc()->Close();
EXPECT_EQ(Counts(*pc).close, 1);
}
// The implicit SetLocalDescription overload (no SDP arg) should fire
// OnSetLocalDescription with a null description; the SDP the PeerConnection
// generated is reported on success instead.
TEST_F(PeerConnectionTracerTest, FiresOnSetLocalDescriptionImplicit) {
auto pc = CreatePeerConnection();
ASSERT_TRUE(pc);
pc->AddAudioTrack("a");
auto observer = make_ref_counted<MockSetSessionDescriptionObserver>();
pc->pc()->SetLocalDescription(observer.get());
EXPECT_EQ(Counts(*pc).set_local_description, 1);
EXPECT_EQ(Counts(*pc).set_local_description_implicit, 1);
EXPECT_TRUE(WaitUntil([&] { return observer->called(); }));
EXPECT_EQ(Counts(*pc).set_local_description_success, 1);
EXPECT_EQ(Counts(*pc).set_local_description_success_with_sdp, 1);
}
// OnSetRemoteDescription should fire when the application calls
// SetRemoteDescription, not when the operation reaches the front of the chain.
TEST_F(PeerConnectionTracerTest, FiresOnSetRemoteDescription) {
auto caller = CreatePeerConnection();
ASSERT_TRUE(caller);
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee);
caller->AddAudioTrack("a");
auto offer = caller->CreateOffer();
ASSERT_TRUE(offer);
// Keep the callee's operations chain busy.
auto create_observer =
make_ref_counted<MockCreateSessionDescriptionObserver>();
callee->pc()->CreateOffer(create_observer.get(),
PeerConnectionInterface::RTCOfferAnswerOptions());
ASSERT_FALSE(create_observer->called());
auto set_observer = make_ref_counted<MockSetSessionDescriptionObserver>();
callee->pc()->SetRemoteDescription(set_observer.get(), offer.release());
EXPECT_EQ(Counts(*callee).set_remote_description, 1);
EXPECT_EQ(Counts(*callee).set_remote_description_success, 0);
EXPECT_TRUE(WaitUntil([&] { return set_observer->called(); }));
EXPECT_EQ(Counts(*callee).set_remote_description_success, 1);
EXPECT_EQ(Counts(*callee).set_remote_description_failure, 0);
}
} // namespace
} // namespace webrtc