From 94286cb25c3e90f7ab5bb90dbaf0d814ba1e5408 Mon Sep 17 00:00:00 2001 From: Steve Anton Date: Tue, 26 Sep 2017 16:20:19 -0700 Subject: [PATCH] Add base fixture and PeerConnection wrapper for unit tests This lays the groundwork for splitting up the PeerConnectionInterface unit tests into multiple files so that the tests can be organized better. The intent is for each unit test file to declare a test fixture which subclasses PeerConnectionUnitTestFixture and creates PeerConnectionWrappers to write assertions against. Bug: webrtc:8222 Change-Id: I21175b1e1828a6cd5012305a8a27faaf4eecf81c Reviewed-on: https://webrtc-review.googlesource.com/1120 Commit-Queue: Steve Anton Reviewed-by: Taylor Brandstetter Cr-Commit-Position: refs/heads/master@{#20004} --- pc/BUILD.gn | 2 + pc/peerconnection_integrationtest.cc | 2 + pc/peerconnectioninterface_unittest.cc | 110 +-------------- pc/peerconnectionwrapper.cc | 178 +++++++++++++++++++++++++ pc/peerconnectionwrapper.h | 108 +++++++++++++++ pc/test/mockpeerconnectionobservers.h | 109 +++++++++++++++ 6 files changed, 402 insertions(+), 107 deletions(-) create mode 100644 pc/peerconnectionwrapper.cc create mode 100644 pc/peerconnectionwrapper.h diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 6339ffbfbb..fa182fa811 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -387,6 +387,8 @@ if (rtc_include_tests) { "peerconnectionendtoend_unittest.cc", "peerconnectionfactory_unittest.cc", "peerconnectioninterface_unittest.cc", + "peerconnectionwrapper.cc", + "peerconnectionwrapper.h", "proxy_unittest.cc", "rtcstats_integrationtest.cc", "rtcstatscollector_unittest.cc", diff --git a/pc/peerconnection_integrationtest.cc b/pc/peerconnection_integrationtest.cc index 641b2fa7a5..5ebf0315fa 100644 --- a/pc/peerconnection_integrationtest.cc +++ b/pc/peerconnection_integrationtest.cc @@ -158,6 +158,8 @@ class MockRtpReceiverObserver : public webrtc::RtpReceiverObserverInterface { // Uses a fake network, fake A/V capture, and optionally fake // encoders/decoders, though they aren't used by default since they don't // advertise support of any codecs. +// TODO(steveanton): See how this could become a subclass of +// PeerConnectionWrapper defined in peerconnectionwrapper.h . class PeerConnectionWrapper : public webrtc::PeerConnectionObserver, public SignalingMessageReceiver, public ObserverInterface { diff --git a/pc/peerconnectioninterface_unittest.cc b/pc/peerconnectioninterface_unittest.cc index b8bf8b8435..cc063b4f74 100644 --- a/pc/peerconnectioninterface_unittest.cc +++ b/pc/peerconnectioninterface_unittest.cc @@ -336,6 +336,7 @@ using webrtc::MediaStreamInterface; using webrtc::MediaStreamTrackInterface; using webrtc::MockCreateSessionDescriptionObserver; using webrtc::MockDataChannelObserver; +using webrtc::MockPeerConnectionObserver; using webrtc::MockSetSessionDescriptionObserver; using webrtc::MockStatsObserver; using webrtc::NotifierInterface; @@ -527,113 +528,6 @@ class MockTrackObserver : public ObserverInterface { NotifierInterface* notifier_; }; -class MockPeerConnectionObserver : public PeerConnectionObserver { - public: - MockPeerConnectionObserver() : remote_streams_(StreamCollection::Create()) {} - virtual ~MockPeerConnectionObserver() { - } - void SetPeerConnectionInterface(PeerConnectionInterface* pc) { - pc_ = pc; - if (pc) { - state_ = pc_->signaling_state(); - } - } - void OnSignalingChange( - PeerConnectionInterface::SignalingState new_state) override { - EXPECT_EQ(pc_->signaling_state(), new_state); - state_ = new_state; - } - - MediaStreamInterface* RemoteStream(const std::string& label) { - return remote_streams_->find(label); - } - StreamCollectionInterface* remote_streams() const { return remote_streams_; } - void OnAddStream(rtc::scoped_refptr stream) override { - last_added_stream_ = stream; - remote_streams_->AddStream(stream); - } - void OnRemoveStream( - rtc::scoped_refptr stream) override { - last_removed_stream_ = stream; - remote_streams_->RemoveStream(stream); - } - void OnRenegotiationNeeded() override { renegotiation_needed_ = true; } - void OnDataChannel( - rtc::scoped_refptr data_channel) override { - last_datachannel_ = data_channel; - } - - void OnIceConnectionChange( - PeerConnectionInterface::IceConnectionState new_state) override { - EXPECT_EQ(pc_->ice_connection_state(), new_state); - callback_triggered_ = true; - } - void OnIceGatheringChange( - PeerConnectionInterface::IceGatheringState new_state) override { - EXPECT_EQ(pc_->ice_gathering_state(), new_state); - ice_complete_ = new_state == PeerConnectionInterface::kIceGatheringComplete; - callback_triggered_ = true; - } - void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override { - EXPECT_NE(PeerConnectionInterface::kIceGatheringNew, - pc_->ice_gathering_state()); - - std::string sdp; - EXPECT_TRUE(candidate->ToString(&sdp)); - EXPECT_LT(0u, sdp.size()); - last_candidate_.reset(webrtc::CreateIceCandidate(candidate->sdp_mid(), - candidate->sdp_mline_index(), sdp, NULL)); - EXPECT_TRUE(last_candidate_.get() != NULL); - callback_triggered_ = true; - } - - void OnIceCandidatesRemoved( - const std::vector& candidates) override { - callback_triggered_ = true; - } - - void OnIceConnectionReceivingChange(bool receiving) override { - callback_triggered_ = true; - } - - void OnAddTrack( - rtc::scoped_refptr receiver, - const std::vector>& - streams) override { - EXPECT_TRUE(receiver != nullptr); - num_added_tracks_++; - last_added_track_label_ = receiver->id(); - } - - // Returns the label of the last added stream. - // Empty string if no stream have been added. - std::string GetLastAddedStreamLabel() { - if (last_added_stream_.get()) - return last_added_stream_->label(); - return ""; - } - std::string GetLastRemovedStreamLabel() { - if (last_removed_stream_.get()) - return last_removed_stream_->label(); - return ""; - } - - rtc::scoped_refptr pc_; - PeerConnectionInterface::SignalingState state_; - std::unique_ptr last_candidate_; - rtc::scoped_refptr last_datachannel_; - rtc::scoped_refptr remote_streams_; - bool renegotiation_needed_ = false; - bool ice_complete_ = false; - bool callback_triggered_ = false; - int num_added_tracks_ = 0; - std::string last_added_track_label_; - - private: - rtc::scoped_refptr last_added_stream_; - rtc::scoped_refptr last_removed_stream_; -}; - } // namespace // The PeerConnectionMediaConfig tests below verify that configuration and @@ -691,6 +585,8 @@ class PeerConnectionFactoryForTest : public webrtc::PeerConnectionFactory { cricket::TransportController* transport_controller; }; +// TODO(steveanton): Convert to use the new PeerConnectionUnitTestFixture and +// PeerConnectionWrapper. class PeerConnectionInterfaceTest : public testing::Test { protected: PeerConnectionInterfaceTest() diff --git a/pc/peerconnectionwrapper.cc b/pc/peerconnectionwrapper.cc new file mode 100644 index 0000000000..01710d9cb2 --- /dev/null +++ b/pc/peerconnectionwrapper.cc @@ -0,0 +1,178 @@ +/* + * Copyright 2017 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 "pc/peerconnectionwrapper.h" + +#include +#include +#include + +#include "api/jsepsessiondescription.h" +#include "media/base/fakevideocapturer.h" +#include "rtc_base/gunit.h" +#include "rtc_base/ptr_util.h" + +namespace webrtc { + +namespace { +const uint32_t kWaitTimeout = 10000U; +} + +PeerConnectionWrapper::PeerConnectionWrapper( + rtc::scoped_refptr pc_factory, + rtc::scoped_refptr pc, + std::unique_ptr observer) + : pc_factory_(pc_factory), pc_(pc), observer_(std::move(observer)) { + RTC_DCHECK(pc_factory_); + RTC_DCHECK(pc_); + RTC_DCHECK(observer_); + observer_->SetPeerConnectionInterface(pc_.get()); +} + +PeerConnectionWrapper::~PeerConnectionWrapper() = default; + +PeerConnectionFactoryInterface* PeerConnectionWrapper::pc_factory() { + return pc_factory_.get(); +} + +PeerConnectionInterface* PeerConnectionWrapper::pc() { + return pc_.get(); +} + +MockPeerConnectionObserver* PeerConnectionWrapper::observer() { + return observer_.get(); +} + +std::unique_ptr +PeerConnectionWrapper::CreateOffer() { + return CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions()); +} + +std::unique_ptr PeerConnectionWrapper::CreateOffer( + const PeerConnectionInterface::RTCOfferAnswerOptions& options) { + return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { + pc()->CreateOffer(observer, options); + }); +} + +std::unique_ptr +PeerConnectionWrapper::CreateOfferAndSetAsLocal() { + auto offer = CreateOffer(); + if (!offer) { + return nullptr; + } + EXPECT_TRUE(SetLocalDescription(CloneSessionDescription(offer.get()))); + return offer; +} + +std::unique_ptr +PeerConnectionWrapper::CreateAnswer() { + return CreateAnswer(PeerConnectionInterface::RTCOfferAnswerOptions()); +} + +std::unique_ptr +PeerConnectionWrapper::CreateAnswer( + const PeerConnectionInterface::RTCOfferAnswerOptions& options) { + return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { + pc()->CreateAnswer(observer, options); + }); +} + +std::unique_ptr +PeerConnectionWrapper::CreateAnswerAndSetAsLocal() { + auto answer = CreateAnswer(); + if (!answer) { + return nullptr; + } + EXPECT_TRUE(SetLocalDescription(CloneSessionDescription(answer.get()))); + return answer; +} + +std::unique_ptr PeerConnectionWrapper::CreateSdp( + std::function fn) { + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + fn(observer); + EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); + return observer->MoveDescription(); +} + +bool PeerConnectionWrapper::SetLocalDescription( + std::unique_ptr desc) { + return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { + pc()->SetLocalDescription(observer, desc.release()); + }); +} + +bool PeerConnectionWrapper::SetRemoteDescription( + std::unique_ptr desc) { + return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { + pc()->SetRemoteDescription(observer, desc.release()); + }); +} + +bool PeerConnectionWrapper::SetSdp( + std::function fn) { + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + fn(observer); + if (pc()->signaling_state() != PeerConnectionInterface::kClosed) { + EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); + } + return observer->result(); +} + +std::unique_ptr +PeerConnectionWrapper::CloneSessionDescription( + const SessionDescriptionInterface* desc) { + std::unique_ptr clone( + new JsepSessionDescription(desc->type())); + RTC_DCHECK(clone->Initialize(desc->description()->Copy(), desc->session_id(), + desc->session_version())); + return clone; +} + +void PeerConnectionWrapper::AddAudioStream(const std::string& stream_label, + const std::string& track_label) { + auto stream = pc_factory()->CreateLocalMediaStream(stream_label); + auto audio_track = pc_factory()->CreateAudioTrack(track_label, nullptr); + EXPECT_TRUE(pc()->AddTrack(audio_track, {stream})); + EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); + observer()->renegotiation_needed_ = false; +} + +void PeerConnectionWrapper::AddVideoStream(const std::string& stream_label, + const std::string& track_label) { + auto stream = pc_factory()->CreateLocalMediaStream(stream_label); + auto video_source = pc_factory()->CreateVideoSource( + rtc::MakeUnique()); + auto video_track = pc_factory()->CreateVideoTrack(track_label, video_source); + EXPECT_TRUE(pc()->AddTrack(video_track, {stream})); + EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); + observer()->renegotiation_needed_ = false; +} + +void PeerConnectionWrapper::AddAudioVideoStream( + const std::string& stream_label, + const std::string& audio_track_label, + const std::string& video_track_label) { + auto stream = pc_factory()->CreateLocalMediaStream(stream_label); + auto audio_track = pc_factory()->CreateAudioTrack(audio_track_label, nullptr); + EXPECT_TRUE(pc()->AddTrack(audio_track, {stream})); + auto video_source = pc_factory()->CreateVideoSource( + rtc::MakeUnique()); + auto video_track = + pc_factory()->CreateVideoTrack(video_track_label, video_source); + EXPECT_TRUE(pc()->AddTrack(video_track, {stream})); + EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); + observer()->renegotiation_needed_ = false; +} + +} // namespace webrtc diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h new file mode 100644 index 0000000000..83b81865ab --- /dev/null +++ b/pc/peerconnectionwrapper.h @@ -0,0 +1,108 @@ +/* + * Copyright 2017 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. + */ + +#ifndef PC_PEERCONNECTIONWRAPPER_H_ +#define PC_PEERCONNECTIONWRAPPER_H_ + +#include +#include +#include + +#include "api/peerconnectioninterface.h" +#include "pc/test/mockpeerconnectionobservers.h" + +namespace webrtc { + +// Class that wraps a PeerConnection so that it is easier to use in unit tests. +// Namely, gives a synchronous API for the event-callback-based API of +// PeerConnection and provides an observer object that stores information from +// PeerConnectionObserver callbacks. +// +// This is intended to be subclassed if additional information needs to be +// stored with the PeerConnection (e.g., fake PeerConnection parameters so that +// tests can be written against those interactions). The base +// PeerConnectionWrapper should only have helper methods that are broadly +// useful. More specific helper methods should be created in the test-specific +// subclass. +// +// The wrapper is intended to be constructed by specialized factory methods on +// a test fixture class then used as a local variable in each test case. +class PeerConnectionWrapper { + public: + // Constructs a PeerConnectionWrapper from the given PeerConnection. + // The given PeerConnectionFactory should be the factory that created the + // PeerConnection and the MockPeerConnectionObserver should be the observer + // that is watching the PeerConnection. + PeerConnectionWrapper( + rtc::scoped_refptr pc_factory, + rtc::scoped_refptr pc, + std::unique_ptr observer); + virtual ~PeerConnectionWrapper(); + + PeerConnectionFactoryInterface* pc_factory(); + PeerConnectionInterface* pc(); + MockPeerConnectionObserver* observer(); + + // Calls the underlying PeerConnection's CreateOffer method and returns the + // resulting SessionDescription once it is available. If the method call + // failed, null is returned. + std::unique_ptr CreateOffer( + const PeerConnectionInterface::RTCOfferAnswerOptions& options); + // Calls CreateOffer with default options. + std::unique_ptr CreateOffer(); + // Calls CreateOffer and sets a copy of the offer as the local description. + std::unique_ptr CreateOfferAndSetAsLocal(); + + // Calls the underlying PeerConnection's CreateAnswer method and returns the + // resulting SessionDescription once it is available. If the method call + // failed, null is returned. + std::unique_ptr CreateAnswer( + const PeerConnectionInterface::RTCOfferAnswerOptions& options); + // Calls CreateAnswer with the default options. + std::unique_ptr CreateAnswer(); + // Calls CreateAnswer and sets a copy of the offer as the local description. + std::unique_ptr CreateAnswerAndSetAsLocal(); + + // Calls the underlying PeerConnection's SetLocalDescription method with the + // given session description and waits for the success/failure response. + // Returns true if the description was successfully set. + bool SetLocalDescription(std::unique_ptr desc); + // Calls the underlying PeerConnection's SetRemoteDescription method with the + // given session description and waits for the success/failure response. + // Returns true if the description was successfully set. + bool SetRemoteDescription(std::unique_ptr desc); + + // Adds a new stream with one audio track to the underlying PeerConnection. + void AddAudioStream(const std::string& stream_label, + const std::string& track_label); + // Adds a new stream with one video track to the underlying PeerConnection. + void AddVideoStream(const std::string& stream_label, + const std::string& track_label); + // Adds a new stream with one audio and one video track to the underlying + // PeerConnection. + void AddAudioVideoStream(const std::string& stream_label, + const std::string& audio_track_label, + const std::string& video_track_label); + + private: + std::unique_ptr CreateSdp( + std::function fn); + bool SetSdp(std::function fn); + std::unique_ptr CloneSessionDescription( + const SessionDescriptionInterface* desc); + + rtc::scoped_refptr pc_factory_; + rtc::scoped_refptr pc_; + std::unique_ptr observer_; +}; + +} // namespace webrtc + +#endif // PC_PEERCONNECTIONWRAPPER_H_ diff --git a/pc/test/mockpeerconnectionobservers.h b/pc/test/mockpeerconnectionobservers.h index e8e78ad7cb..64f84b6b8e 100644 --- a/pc/test/mockpeerconnectionobservers.h +++ b/pc/test/mockpeerconnectionobservers.h @@ -9,6 +9,7 @@ */ // This file contains mock implementations of observers used in PeerConnection. +// TODO(steveanton): These aren't really mocks and should be renamed. #ifndef PC_TEST_MOCKPEERCONNECTIONOBSERVERS_H_ #define PC_TEST_MOCKPEERCONNECTIONOBSERVERS_H_ @@ -17,10 +18,118 @@ #include #include "api/datachannelinterface.h" +#include "pc/streamcollection.h" #include "rtc_base/checks.h" namespace webrtc { +class MockPeerConnectionObserver : public PeerConnectionObserver { + public: + MockPeerConnectionObserver() : remote_streams_(StreamCollection::Create()) {} + virtual ~MockPeerConnectionObserver() {} + void SetPeerConnectionInterface(PeerConnectionInterface* pc) { + pc_ = pc; + if (pc) { + state_ = pc_->signaling_state(); + } + } + void OnSignalingChange( + PeerConnectionInterface::SignalingState new_state) override { + RTC_DCHECK(pc_->signaling_state() == new_state); + state_ = new_state; + } + + MediaStreamInterface* RemoteStream(const std::string& label) { + return remote_streams_->find(label); + } + StreamCollectionInterface* remote_streams() const { return remote_streams_; } + void OnAddStream(rtc::scoped_refptr stream) override { + last_added_stream_ = stream; + remote_streams_->AddStream(stream); + } + void OnRemoveStream( + rtc::scoped_refptr stream) override { + last_removed_stream_ = stream; + remote_streams_->RemoveStream(stream); + } + void OnRenegotiationNeeded() override { renegotiation_needed_ = true; } + void OnDataChannel( + rtc::scoped_refptr data_channel) override { + last_datachannel_ = data_channel; + } + + void OnIceConnectionChange( + PeerConnectionInterface::IceConnectionState new_state) override { + RTC_DCHECK(pc_->ice_connection_state() == new_state); + callback_triggered_ = true; + } + void OnIceGatheringChange( + PeerConnectionInterface::IceGatheringState new_state) override { + RTC_DCHECK(pc_->ice_gathering_state() == new_state); + ice_complete_ = new_state == PeerConnectionInterface::kIceGatheringComplete; + callback_triggered_ = true; + } + void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override { + RTC_DCHECK(PeerConnectionInterface::kIceGatheringNew != + pc_->ice_gathering_state()); + + std::string sdp; + bool success = candidate->ToString(&sdp); + RTC_DCHECK(success); + RTC_DCHECK(!sdp.empty()); + last_candidate_.reset(webrtc::CreateIceCandidate( + candidate->sdp_mid(), candidate->sdp_mline_index(), sdp, NULL)); + RTC_DCHECK(last_candidate_); + callback_triggered_ = true; + } + + void OnIceCandidatesRemoved( + const std::vector& candidates) override { + callback_triggered_ = true; + } + + void OnIceConnectionReceivingChange(bool receiving) override { + callback_triggered_ = true; + } + + void OnAddTrack( + rtc::scoped_refptr receiver, + const std::vector>& + streams) override { + RTC_DCHECK(receiver); + num_added_tracks_++; + last_added_track_label_ = receiver->id(); + } + + // Returns the label of the last added stream. + // Empty string if no stream have been added. + std::string GetLastAddedStreamLabel() { + if (last_added_stream_.get()) + return last_added_stream_->label(); + return ""; + } + std::string GetLastRemovedStreamLabel() { + if (last_removed_stream_.get()) + return last_removed_stream_->label(); + return ""; + } + + rtc::scoped_refptr pc_; + PeerConnectionInterface::SignalingState state_; + std::unique_ptr last_candidate_; + rtc::scoped_refptr last_datachannel_; + rtc::scoped_refptr remote_streams_; + bool renegotiation_needed_ = false; + bool ice_complete_ = false; + bool callback_triggered_ = false; + int num_added_tracks_ = 0; + std::string last_added_track_label_; + + private: + rtc::scoped_refptr last_added_stream_; + rtc::scoped_refptr last_removed_stream_; +}; + class MockCreateSessionDescriptionObserver : public webrtc::CreateSessionDescriptionObserver { public: