webrtc/p2p/base/wrapping_active_ice_controller_unittest.cc
Sameer Vijaykar f0e65bab0e Accept ArrayView in ICE agent interface where feasible.
Ownership does not need to cross the interface boundary, so ArrayView can be safely accepted in ForgetStateForConnections and PruneConnections.

Bug: webrtc:14131
Change-Id: I18a739aea1dc47976d17925e9bca3461225bf803
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/278629
Commit-Queue: Sameer Vijaykar <samvi@google.com>
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38418}
2022-10-17 15:03:33 +00:00

315 lines
12 KiB
C++

/*
* Copyright 2009 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 "p2p/base/wrapping_active_ice_controller.h"
#include <memory>
#include <utility>
#include <vector>
#include "p2p/base/connection.h"
#include "p2p/base/mock_ice_agent.h"
#include "p2p/base/mock_ice_controller.h"
#include "rtc_base/fake_clock.h"
#include "rtc_base/gunit.h"
#include "rtc_base/thread.h"
namespace {
using ::cricket::Connection;
using ::cricket::IceConfig;
using ::cricket::IceControllerFactoryArgs;
using ::cricket::IceControllerInterface;
using ::cricket::IceMode;
using ::cricket::IceRecheckEvent;
using ::cricket::IceSwitchReason;
using ::cricket::MockIceAgent;
using ::cricket::MockIceController;
using ::cricket::MockIceControllerFactory;
using ::cricket::NominationMode;
using ::cricket::WrappingActiveIceController;
using ::testing::_;
using ::testing::ElementsAreArray;
using ::testing::IsEmpty;
using ::testing::NiceMock;
using ::testing::Ref;
using ::testing::Return;
using ::testing::Sequence;
using ::rtc::AutoThread;
using ::rtc::Event;
using ::rtc::ScopedFakeClock;
using ::webrtc::TimeDelta;
using NiceMockIceController = NiceMock<MockIceController>;
static const Connection* kConnection =
reinterpret_cast<const Connection*>(0xabcd);
static const Connection* kConnectionTwo =
reinterpret_cast<const Connection*>(0xbcde);
static const Connection* kConnectionThree =
reinterpret_cast<const Connection*>(0xcdef);
static const std::vector<const Connection*> kEmptyConnsList =
std::vector<const Connection*>();
static const TimeDelta kTick = TimeDelta::Millis(1);
TEST(WrappingActiveIceControllerTest, CreateLegacyIceControllerFromFactory) {
AutoThread main;
MockIceAgent agent;
IceControllerFactoryArgs args;
MockIceControllerFactory legacy_controller_factory;
EXPECT_CALL(legacy_controller_factory, RecordIceControllerCreated()).Times(1);
WrappingActiveIceController controller(&agent, &legacy_controller_factory,
args);
}
TEST(WrappingActiveIceControllerTest, PassthroughIceControllerInterface) {
AutoThread main;
MockIceAgent agent;
std::unique_ptr<MockIceController> will_move =
std::make_unique<MockIceController>(IceControllerFactoryArgs{});
MockIceController* wrapped = will_move.get();
WrappingActiveIceController controller(&agent, std::move(will_move));
IceConfig config{};
EXPECT_CALL(*wrapped, SetIceConfig(Ref(config)));
controller.SetIceConfig(config);
EXPECT_CALL(*wrapped,
GetUseCandidateAttr(kConnection, NominationMode::AGGRESSIVE,
IceMode::ICEMODE_LITE))
.WillOnce(Return(true));
EXPECT_TRUE(controller.GetUseCandidateAttribute(
kConnection, NominationMode::AGGRESSIVE, IceMode::ICEMODE_LITE));
EXPECT_CALL(*wrapped, AddConnection(kConnection));
controller.OnConnectionAdded(kConnection);
EXPECT_CALL(*wrapped, OnConnectionDestroyed(kConnection));
controller.OnConnectionDestroyed(kConnection);
EXPECT_CALL(*wrapped, SetSelectedConnection(kConnection));
controller.OnConnectionSwitched(kConnection);
EXPECT_CALL(*wrapped, MarkConnectionPinged(kConnection));
controller.OnConnectionPinged(kConnection);
EXPECT_CALL(*wrapped, FindNextPingableConnection())
.WillOnce(Return(kConnection));
EXPECT_EQ(controller.FindNextPingableConnection(), kConnection);
}
TEST(WrappingActiveIceControllerTest, HandlesImmediateSwitchRequest) {
AutoThread main;
ScopedFakeClock clock;
NiceMock<MockIceAgent> agent;
std::unique_ptr<NiceMockIceController> will_move =
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
NiceMockIceController* wrapped = will_move.get();
WrappingActiveIceController controller(&agent, std::move(will_move));
IceSwitchReason reason = IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE;
std::vector<const Connection*> conns_to_forget{kConnectionTwo};
int recheck_delay_ms = 10;
IceControllerInterface::SwitchResult switch_result{
kConnection,
IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
recheck_delay_ms),
conns_to_forget};
// ICE controller should switch to given connection immediately.
Sequence check_then_switch;
EXPECT_CALL(*wrapped, ShouldSwitchConnection(reason, kConnection))
.InSequence(check_then_switch)
.WillOnce(Return(switch_result));
EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
.InSequence(check_then_switch);
EXPECT_CALL(agent, ForgetLearnedStateForConnections(
ElementsAreArray(conns_to_forget)));
EXPECT_TRUE(controller.OnImmediateSwitchRequest(reason, kConnection));
// No rechecks before recheck delay.
clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms - 1));
// ICE controller should recheck for best connection after the recheck delay.
Sequence recheck_sort;
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(recheck_sort);
EXPECT_CALL(*wrapped,
SortAndSwitchConnection(IceSwitchReason::ICE_CONTROLLER_RECHECK))
.InSequence(recheck_sort)
.WillOnce(Return(IceControllerInterface::SwitchResult{}));
EXPECT_CALL(agent, ForgetLearnedStateForConnections(IsEmpty()));
clock.AdvanceTime(kTick);
}
TEST(WrappingActiveIceControllerTest, HandlesImmediateSortAndSwitchRequest) {
AutoThread main;
ScopedFakeClock clock;
NiceMock<MockIceAgent> agent;
std::unique_ptr<NiceMockIceController> will_move =
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
NiceMockIceController* wrapped = will_move.get();
WrappingActiveIceController controller(&agent, std::move(will_move));
IceSwitchReason reason = IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE;
std::vector<const Connection*> conns_to_forget{kConnectionTwo};
std::vector<const Connection*> conns_to_prune{kConnectionThree};
int recheck_delay_ms = 10;
IceControllerInterface::SwitchResult switch_result{
kConnection,
IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
recheck_delay_ms),
conns_to_forget};
Sequence sort_and_switch;
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(sort_and_switch);
EXPECT_CALL(*wrapped, SortAndSwitchConnection(reason))
.InSequence(sort_and_switch)
.WillOnce(Return(switch_result));
EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
.InSequence(sort_and_switch);
EXPECT_CALL(*wrapped, PruneConnections())
.InSequence(sort_and_switch)
.WillOnce(Return(conns_to_prune));
EXPECT_CALL(agent, PruneConnections(ElementsAreArray(conns_to_prune)))
.InSequence(sort_and_switch);
controller.OnImmediateSortAndSwitchRequest(reason);
// No rechecks before recheck delay.
clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms - 1));
// ICE controller should recheck for best connection after the recheck delay.
Sequence recheck_sort;
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(recheck_sort);
EXPECT_CALL(*wrapped,
SortAndSwitchConnection(IceSwitchReason::ICE_CONTROLLER_RECHECK))
.InSequence(recheck_sort)
.WillOnce(Return(IceControllerInterface::SwitchResult{}));
EXPECT_CALL(*wrapped, PruneConnections())
.InSequence(recheck_sort)
.WillOnce(Return(kEmptyConnsList));
EXPECT_CALL(agent, PruneConnections(IsEmpty())).InSequence(recheck_sort);
clock.AdvanceTime(kTick);
}
TEST(WrappingActiveIceControllerTest, HandlesSortAndSwitchRequest) {
AutoThread main;
ScopedFakeClock clock;
// Block the main task queue until ready.
Event init;
TimeDelta init_delay = TimeDelta::Millis(10);
main.PostTask([&init, &init_delay] { init.Wait(init_delay); });
NiceMock<MockIceAgent> agent;
std::unique_ptr<NiceMockIceController> will_move =
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
NiceMockIceController* wrapped = will_move.get();
WrappingActiveIceController controller(&agent, std::move(will_move));
IceSwitchReason reason = IceSwitchReason::NETWORK_PREFERENCE_CHANGE;
// No action should occur immediately
EXPECT_CALL(agent, UpdateConnectionStates()).Times(0);
EXPECT_CALL(*wrapped, SortAndSwitchConnection(_)).Times(0);
EXPECT_CALL(agent, SwitchSelectedConnection(_, _)).Times(0);
controller.OnSortAndSwitchRequest(reason);
std::vector<const Connection*> conns_to_forget{kConnectionTwo};
int recheck_delay_ms = 10;
IceControllerInterface::SwitchResult switch_result{
kConnection,
IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
recheck_delay_ms),
conns_to_forget};
// Sort and switch should take place as the subsequent task.
Sequence sort_and_switch;
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(sort_and_switch);
EXPECT_CALL(*wrapped, SortAndSwitchConnection(reason))
.InSequence(sort_and_switch)
.WillOnce(Return(switch_result));
EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
.InSequence(sort_and_switch);
// Unblock the init task.
clock.AdvanceTime(init_delay);
}
TEST(WrappingActiveIceControllerTest, StartPingingAfterSortAndSwitch) {
AutoThread main;
ScopedFakeClock clock;
// Block the main task queue until ready.
Event init;
TimeDelta init_delay = TimeDelta::Millis(10);
main.PostTask([&init, &init_delay] { init.Wait(init_delay); });
NiceMock<MockIceAgent> agent;
std::unique_ptr<NiceMockIceController> will_move =
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
NiceMockIceController* wrapped = will_move.get();
WrappingActiveIceController controller(&agent, std::move(will_move));
// Pinging does not start automatically, unless triggered through a sort.
EXPECT_CALL(*wrapped, HasPingableConnection()).Times(0);
EXPECT_CALL(*wrapped, SelectConnectionToPing(_)).Times(0);
EXPECT_CALL(agent, OnStartedPinging()).Times(0);
controller.OnSortAndSwitchRequest(IceSwitchReason::DATA_RECEIVED);
// Pinging does not start if no pingable connection.
EXPECT_CALL(*wrapped, HasPingableConnection()).WillOnce(Return(false));
EXPECT_CALL(*wrapped, SelectConnectionToPing(_)).Times(0);
EXPECT_CALL(agent, OnStartedPinging()).Times(0);
// Unblock the init task.
clock.AdvanceTime(init_delay);
int recheck_delay_ms = 10;
IceControllerInterface::PingResult ping_result(kConnection, recheck_delay_ms);
// Pinging starts when there is a pingable connection.
Sequence start_pinging;
EXPECT_CALL(*wrapped, HasPingableConnection())
.InSequence(start_pinging)
.WillOnce(Return(true));
EXPECT_CALL(agent, OnStartedPinging()).InSequence(start_pinging);
EXPECT_CALL(agent, GetLastPingSentMs())
.InSequence(start_pinging)
.WillOnce(Return(123));
EXPECT_CALL(*wrapped, SelectConnectionToPing(123))
.InSequence(start_pinging)
.WillOnce(Return(ping_result));
EXPECT_CALL(agent, SendPingRequest(kConnection)).InSequence(start_pinging);
controller.OnSortAndSwitchRequest(IceSwitchReason::DATA_RECEIVED);
clock.AdvanceTime(kTick);
// ICE controller should recheck and ping after the recheck delay.
// No ping should be sent if no connection selected to ping.
EXPECT_CALL(agent, GetLastPingSentMs()).WillOnce(Return(456));
EXPECT_CALL(*wrapped, SelectConnectionToPing(456))
.WillOnce(Return(IceControllerInterface::PingResult(
/* connection= */ nullptr, recheck_delay_ms)));
EXPECT_CALL(agent, SendPingRequest(kConnection)).Times(0);
clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms));
}
} // namespace