blob: 948060dc4c77d00015c19e23e485875e4b716149 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/device.h"
#include <memory>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/callback.h>
#include <base/time/time.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "metrics/fake_metrics_library.h"
#include "shill/event_dispatcher.h"
#include "shill/metrics.h"
#include "shill/mock_control.h"
#include "shill/mock_device_info.h"
#include "shill/mock_manager.h"
#include "shill/network/mock_network.h"
#include "shill/portal_detector.h"
#include "shill/routing_table.h"
#include "shill/service_under_test.h"
#include "shill/technology.h"
#include "shill/test_event_dispatcher.h"
#include "shill/testing.h"
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::ReturnRefOfCopy;
// This file contains Device unit tests focused on portal detection and
// integration with the PortalDetector class. These tests minimize the use of
// mocks, relying instead on a test PortalDetector implementation and a test
// Device implementation to provide the test PortalDetector.
// The primary advantage to this pattern, other than increased readability,
// is that it is much easier to test the Device state machine from
// UpdatePortalDetector() through completion, including multiple attempts.
// This will be especially helpful for ensuring that UMA metrics are properly
// measured.
namespace shill {
namespace {
const char kDeviceName[] = "testdevice";
const char kDeviceAddress[] = "00:01:02:03:04:05";
const int kDeviceInterfaceIndex = 1;
const char kRedirectUrl[] = "http://www.redirect.com/signin";
// Portal detection is technology agnostic, use 'unknown'.
const Technology kTestTechnology = Technology::kUnknown;
class TestPortalDetector : public PortalDetector {
public:
TestPortalDetector(EventDispatcher* dispatcher,
base::RepeatingCallback<void(const Result&)> callback)
: PortalDetector(dispatcher, callback) {}
~TestPortalDetector() override = default;
TestPortalDetector(const TestPortalDetector&) = delete;
TestPortalDetector& operator=(const TestPortalDetector&) = delete;
// PortalDetector overrides
bool Start(const ManagerProperties& props,
const std::string& ifname,
const IPAddress& src_address,
const std::vector<std::string>& dns_list,
const std::string& logging_tag,
base::TimeDelta delay = base::TimeDelta()) override {
if (delay == base::TimeDelta()) {
started_ = true;
num_attempts_++;
} else {
delayed_ = true;
}
return true;
}
void Stop() override { started_ = false; }
bool IsInProgress() override { return started_; };
base::TimeDelta GetNextAttemptDelay() override {
return base::Milliseconds(1);
}
void SetDNSResult(PortalDetector::Status status) {
result_ = PortalDetector::Result();
result_.http_phase = PortalDetector::Phase::kDNS;
result_.http_status = status;
result_.https_phase = PortalDetector::Phase::kDNS;
result_.https_status = status;
}
void SetRedirectResult(const std::string& redirect_url) {
result_ = PortalDetector::Result();
result_.http_phase = PortalDetector::Phase::kContent;
result_.http_status = PortalDetector::Status::kRedirect;
result_.http_status_code = 302;
result_.redirect_url_string = redirect_url;
result_.https_phase = PortalDetector::Phase::kContent;
result_.https_status = PortalDetector::Status::kSuccess;
}
void SetHTTPSFailureResult() {
result_ = PortalDetector::Result();
result_.http_phase = PortalDetector::Phase::kContent;
result_.http_status = PortalDetector::Status::kSuccess;
result_.http_status_code = 204;
result_.https_phase = PortalDetector::Phase::kContent;
result_.https_status = PortalDetector::Status::kFailure;
}
void SetOnlineResult() {
result_ = PortalDetector::Result();
result_.http_phase = PortalDetector::Phase::kContent;
result_.http_status = PortalDetector::Status::kSuccess;
result_.http_status_code = 204;
result_.https_phase = PortalDetector::Phase::kContent;
result_.https_status = PortalDetector::Status::kSuccess;
}
void Continue() {
if (delayed_) {
started_ = true;
num_attempts_++;
delayed_ = false;
}
}
void Complete() {
if (delayed_)
Continue();
started_ = false;
// The callback might delete |this| so copy |result_|.
PortalDetector::Result result = result_;
result.num_attempts = num_attempts_;
portal_result_callback().Run(result);
}
const PortalDetector::Result& result() const { return result_; }
int num_attempts() { return num_attempts_; }
private:
PortalDetector::Result result_;
bool started_ = false;
bool delayed_ = false;
int num_attempts_ = 0;
base::WeakPtrFactory<TestPortalDetector> test_weak_ptr_factory_{this};
};
class TestDevice : public Device {
public:
TestDevice(Manager* manager,
const std::string& link_name,
const std::string& address,
int interface_index,
Technology technology)
: Device(manager, link_name, address, interface_index, technology) {}
~TestDevice() override = default;
// Device overrides
void Start(const EnabledStateChangedCallback& callback) override {
callback.Run(Error(Error::kSuccess));
}
void Stop(const EnabledStateChangedCallback& callback) override {
callback.Run(Error(Error::kSuccess));
}
void StartConnectionDiagnosticsAfterPortalDetection() override {}
std::unique_ptr<PortalDetector> CreatePortalDetector() override {
return std::make_unique<TestPortalDetector>(
dispatcher(),
base::BindRepeating(&TestDevice::TestPortalDetectorCallback,
test_weak_ptr_factory_.GetWeakPtr()));
}
// A protected Device method can not be bound directly so use a wrapper.
void TestPortalDetectorCallback(const PortalDetector::Result& result) {
PortalDetectorCallback(result);
}
TestPortalDetector* test_portal_detector() {
return static_cast<TestPortalDetector*>(portal_detector());
}
private:
base::WeakPtrFactory<TestDevice> test_weak_ptr_factory_{this};
};
class TestService : public ServiceUnderTest {
public:
explicit TestService(Manager* manager) : ServiceUnderTest(manager) {}
~TestService() override = default;
protected:
// Service
void OnConnect(Error* /*error*/) override { SetState(kStateConnected); }
void OnDisconnect(Error* /*error*/, const char* /*reason*/) override {
SetState(kStateIdle);
}
};
} // namespace
class DevicePortalDetectorTest : public testing::Test {
public:
DevicePortalDetectorTest()
: manager_(&control_interface_, &dispatcher_, &metrics_),
device_info_(&manager_) {
metrics_.SetLibraryForTesting(&fake_metrics_library_);
manager_.set_mock_device_info(&device_info_);
device_ = new TestDevice(&manager_, kDeviceName, kDeviceAddress,
kDeviceInterfaceIndex, kTestTechnology);
}
~DevicePortalDetectorTest() override = default;
void SetUp() override {
RoutingTable::GetInstance()->Start();
ManagerProperties props = GetManagerPortalProperties();
EXPECT_CALL(manager_, GetProperties()).WillRepeatedly(ReturnRef(props));
auto network = std::make_unique<NiceMock<MockNetwork>>(
kDeviceInterfaceIndex, kDeviceName, Technology::kUnknown);
ON_CALL(*network, local()).WillByDefault(Return(IPAddress("192.168.86.2")));
ON_CALL(*network, dns_servers())
.WillByDefault(Return(std::vector<std::string>{"8.8.8.8", "8.8.4.4"}));
ON_CALL(*network, IsConnected()).WillByDefault(Return(true));
device_->set_network_for_testing(std::move(network));
// Set up a connected test Service for the Device.
service_ = new TestService(&manager_);
service_->SetState(Service::kStateConnected);
SetServiceCheckPortal(true);
device_->SelectService(service_);
}
void PortalDetectorCallback(const PortalDetector::Result& result) {
device_->PortalDetectorCallback(result);
}
TestPortalDetector* UpdatePortalDetector(bool restart = true) {
device_->UpdatePortalDetector(restart);
// This will be nullptr if UpdatePortalDetector() did not start detection.
return device_->test_portal_detector();
}
TestPortalDetector* GetPortalDetector() {
return device_->test_portal_detector();
}
void SetServiceCheckPortal(bool check_portal) {
service_->SetCheckPortal(
check_portal ? Service::kCheckPortalTrue : Service::kCheckPortalFalse,
/*error=*/nullptr);
}
std::string GetServiceProbeUrlString() { return service_->probe_url_string_; }
int NumHistogramCalls(
const Metrics::HistogramMetric<Metrics::NameByTechnology>& metric) {
return fake_metrics_library_.NumCalls(Metrics::GetFullMetricName(
metric.n.name, kTestTechnology, metric.n.location));
}
int NumEnumMetricsCalls(
const Metrics::EnumMetric<Metrics::NameByTechnology>& metric) {
return fake_metrics_library_.NumCalls(Metrics::GetFullMetricName(
metric.n.name, kTestTechnology, metric.n.location));
}
std::vector<int> MetricsHistogramCalls(
const Metrics::HistogramMetric<Metrics::NameByTechnology>& metric) {
return fake_metrics_library_.GetCalls(Metrics::GetFullMetricName(
metric.n.name, kTestTechnology, metric.n.location));
}
std::vector<int> MetricsEnumCalls(
const Metrics::EnumMetric<Metrics::NameByTechnology>& metric) {
return fake_metrics_library_.GetCalls(Metrics::GetFullMetricName(
metric.n.name, kTestTechnology, metric.n.location));
}
protected:
ManagerProperties GetManagerPortalProperties() {
ManagerProperties props;
props.portal_http_url = PortalDetector::kDefaultHttpUrl;
props.portal_https_url = PortalDetector::kDefaultHttpsUrl;
props.portal_fallback_http_urls = std::vector<std::string>(
PortalDetector::kDefaultFallbackHttpUrls.begin(),
PortalDetector::kDefaultFallbackHttpUrls.end());
return props;
}
NiceMock<MockControl> control_interface_;
EventDispatcherForTest dispatcher_;
Metrics metrics_;
FakeMetricsLibrary fake_metrics_library_;
NiceMock<MockManager> manager_;
NiceMock<MockDeviceInfo> device_info_;
scoped_refptr<TestDevice> device_;
scoped_refptr<TestService> service_;
};
TEST_F(DevicePortalDetectorTest, Disabled) {
SetServiceCheckPortal(false);
TestPortalDetector* portal_detector = UpdatePortalDetector();
EXPECT_FALSE(portal_detector);
}
TEST_F(DevicePortalDetectorTest, DNSFailure) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
portal_detector->SetDNSResult(PortalDetector::Status::kFailure);
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateNoConnectivity);
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultDNSFailure));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultDNSFailure));
EXPECT_EQ(NumEnumMetricsCalls(Metrics::kPortalDetectorRetryResult), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kMetricPortalAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
// Portal detection should be started again.
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_FALSE(portal_detector->IsInProgress());
portal_detector->Continue();
EXPECT_TRUE(portal_detector->IsInProgress());
EXPECT_EQ(portal_detector->num_attempts(), 2);
}
TEST_F(DevicePortalDetectorTest, DNSTimeout) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
portal_detector->SetDNSResult(PortalDetector::Status::kTimeout);
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateNoConnectivity);
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultDNSTimeout));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultDNSTimeout));
EXPECT_EQ(NumEnumMetricsCalls(Metrics::kPortalDetectorRetryResult), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kMetricPortalAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
// Portal detection should still be active.
EXPECT_TRUE(GetPortalDetector());
}
TEST_F(DevicePortalDetectorTest, RedirectFound) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
portal_detector->SetRedirectResult(kRedirectUrl);
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateRedirectFound);
EXPECT_EQ(GetServiceProbeUrlString(),
portal_detector->result().probe_url_string);
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultContentRedirect));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultRedirectFound));
EXPECT_EQ(NumEnumMetricsCalls(Metrics::kPortalDetectorRetryResult), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kMetricPortalAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline), 0);
EXPECT_THAT(
MetricsHistogramCalls(Metrics::kPortalDetectorAttemptsToRedirectFound),
ElementsAre(1));
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
// Portal detection should still be active.
EXPECT_TRUE(GetPortalDetector());
}
TEST_F(DevicePortalDetectorTest, RedirectFoundNoUrl) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
// Redirect result with an empty redirect URL -> PortalSuspected state.
portal_detector->SetRedirectResult("");
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStatePortalSuspected);
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultContentRedirect));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultRedirectNoUrl));
EXPECT_EQ(NumEnumMetricsCalls(Metrics::kPortalDetectorRetryResult), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kMetricPortalAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToRedirectFound),
0);
// Portal detection should still be active.
EXPECT_TRUE(GetPortalDetector());
}
TEST_F(DevicePortalDetectorTest, RedirectFoundThenOnline) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
portal_detector->SetRedirectResult(kRedirectUrl);
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateRedirectFound);
// Portal detection should be started again.
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
portal_detector->Continue();
EXPECT_EQ(portal_detector->num_attempts(), 2);
// Completion with an 'online' result should set the Service state to online.
portal_detector->SetOnlineResult();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateOnline);
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultContentRedirect,
Metrics::kPortalResultSuccess));
EXPECT_THAT(MetricsHistogramCalls(Metrics::kMetricPortalAttemptsToOnline),
ElementsAre(2));
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
// Portal detection should be completed and the PortalDetector destroyed.
EXPECT_FALSE(GetPortalDetector());
}
TEST_F(DevicePortalDetectorTest, PortalSuspected) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
portal_detector->SetHTTPSFailureResult();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStatePortalSuspected);
// NOTE: Since we only report on the HTTP phase, a portal-suspected result
// reports 'success'. This will be addressed when the metrics are updated.
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultSuccess));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
EXPECT_EQ(NumEnumMetricsCalls(Metrics::kPortalDetectorRetryResult), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kMetricPortalAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToRedirectFound),
0);
// Portal detection should still be active.
EXPECT_TRUE(GetPortalDetector());
}
TEST_F(DevicePortalDetectorTest, PortalSuspectedThenRedirectFound) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
// Multiple portal-suspected results.
portal_detector->SetHTTPSFailureResult();
portal_detector->Complete();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStatePortalSuspected);
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultSuccess,
Metrics::kPortalResultSuccess));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorRetryResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
// Portal detection should be started again.
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_FALSE(portal_detector->IsInProgress());
portal_detector->Continue();
EXPECT_TRUE(portal_detector->IsInProgress());
EXPECT_EQ(portal_detector->num_attempts(), 3);
// Completion with a 'redirect-found' result should set the Service state
// to redirect-found and record the number of attempts..
portal_detector->SetRedirectResult(kRedirectUrl);
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateRedirectFound);
EXPECT_THAT(
MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultSuccess, Metrics::kPortalResultSuccess,
Metrics::kPortalResultContentRedirect));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorRetryResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure,
Metrics::kPortalDetectorResultRedirectFound));
EXPECT_EQ(NumHistogramCalls(Metrics::kMetricPortalAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
EXPECT_THAT(
MetricsHistogramCalls(Metrics::kPortalDetectorAttemptsToRedirectFound),
ElementsAre(3));
// Portal detection should be started again.
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
portal_detector->Continue();
EXPECT_EQ(portal_detector->num_attempts(), 4);
}
TEST_F(DevicePortalDetectorTest, PortalSuspectedThenOnline) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
portal_detector->SetHTTPSFailureResult();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStatePortalSuspected);
// Portal detection should be started again.
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
portal_detector->Continue();
EXPECT_EQ(portal_detector->num_attempts(), 2);
// Completion with an 'online' result should set the Service state to online.
portal_detector->SetOnlineResult();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateOnline);
// NOTE: Since we only report on the HTTP phase, a portal-suspected result
// reports 'success'. This will be addressed when the metrics are updated.
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultSuccess,
Metrics::kPortalResultSuccess));
EXPECT_THAT(MetricsHistogramCalls(Metrics::kMetricPortalAttemptsToOnline),
ElementsAre(2));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorRetryResult),
ElementsAre(Metrics::kPortalDetectorResultOnline));
EXPECT_THAT(MetricsHistogramCalls(Metrics::kMetricPortalAttemptsToOnline),
ElementsAre(2));
EXPECT_THAT(MetricsHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline),
ElementsAre(2));
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
// Portal detection should be completed and the PortalDetector destroyed.
EXPECT_FALSE(GetPortalDetector());
}
TEST_F(DevicePortalDetectorTest, PortalSuspectedThenDisconnect) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
// Multiple portal-suspected results
portal_detector->SetHTTPSFailureResult();
portal_detector->Complete();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStatePortalSuspected);
// Portal detection should be started again.
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
portal_detector->Continue();
EXPECT_EQ(portal_detector->num_attempts(), 3);
// Disconnect should not record an UMA result.
service_->Disconnect(/*error=*/nullptr, /*reason=*/"test");
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateIdle);
EXPECT_THAT(
MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultSuccess, Metrics::kPortalResultSuccess,
Metrics::kPortalResultSuccess));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorRetryResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
EXPECT_EQ(NumHistogramCalls(Metrics::kMetricPortalAttemptsToOnline), 0);
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline), 0);
// Histogram records the number of failed attempts *before* a disconnect.
EXPECT_THAT(
MetricsHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect),
ElementsAre(2));
}
TEST_F(DevicePortalDetectorTest, Online) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
portal_detector->SetOnlineResult();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateOnline);
EXPECT_THAT(MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultSuccess));
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultOnline));
EXPECT_EQ(NumEnumMetricsCalls(Metrics::kPortalDetectorRetryResult), 0);
EXPECT_THAT(MetricsHistogramCalls(Metrics::kMetricPortalAttemptsToOnline),
ElementsAre(1));
EXPECT_THAT(MetricsHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline),
ElementsAre(1));
EXPECT_EQ(NumHistogramCalls(Metrics::kPortalDetectorAttemptsToDisconnect), 0);
// Portal detection should be completed and the PortalDetector destroyed.
EXPECT_FALSE(GetPortalDetector());
}
TEST_F(DevicePortalDetectorTest, RestartPortalDetection) {
TestPortalDetector* portal_detector = UpdatePortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
// Run portal detection 3 times.
portal_detector->SetHTTPSFailureResult();
portal_detector->Complete();
portal_detector->Complete();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStatePortalSuspected);
// Portal detection should be started again.
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
portal_detector->Continue();
EXPECT_TRUE(portal_detector->IsInProgress());
// UpdatePortalDetector(true) will reset the current portal detector and
// start a new one.
device_->UpdatePortalDetector(/*restart=*/true);
portal_detector = GetPortalDetector();
ASSERT_TRUE(portal_detector);
EXPECT_TRUE(portal_detector->IsInProgress());
// Complete will run portal detection 1 more time with an 'online' result.
portal_detector->SetOnlineResult();
portal_detector->Complete();
EXPECT_EQ(service_->state(), Service::kStateOnline);
// Old result metric gets called 4 times, with a final result of 'online'.
// NOTE: Since we only report on the HTTP phase, a portal-suspected result
// reports 'success'. This will be addressed when the metrics are updated.
EXPECT_THAT(
MetricsEnumCalls(Metrics::kMetricPortalResult),
ElementsAre(Metrics::kPortalResultSuccess, Metrics::kPortalResultSuccess,
Metrics::kPortalResultSuccess,
Metrics::kPortalResultSuccess));
// New initial result metric gets called once with an HTTPS failure.
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorInitialResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure));
// New retry result metric gets called three times, ending with 'online'
EXPECT_THAT(MetricsEnumCalls(Metrics::kPortalDetectorRetryResult),
ElementsAre(Metrics::kPortalDetectorResultHTTPSFailure,
Metrics::kPortalDetectorResultHTTPSFailure,
Metrics::kPortalDetectorResultOnline));
// Old attempts-to-online metric gets called once with a value of 1.
EXPECT_THAT(MetricsHistogramCalls(Metrics::kMetricPortalAttemptsToOnline),
ElementsAre(1));
// New attempts-to-online metric gets called once with a value of 3+1 = 4.
EXPECT_THAT(MetricsHistogramCalls(Metrics::kPortalDetectorAttemptsToOnline),
ElementsAre(4));
// Portal detection should be completed and the PortalDetector destroyed.
EXPECT_FALSE(GetPortalDetector());
}
} // namespace shill