// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/tether/host_scan_scheduler_impl.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "chromeos/ash/components/tether/fake_host_scanner.h"
#include "chromeos/ash/services/device_sync/cryptauth_device_manager.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/session_manager/core/session_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace tether {
namespace {
const char kEthernetServiceGuid[] = "ethernetServiceGuid";
const char kWifiServiceGuid[] = "wifiServiceGuid";
const char kTetherGuid[] = "tetherGuid";
std::string CreateConfigurationJsonString(const std::string& guid,
const std::string& type,
const std::string& state) {
std::stringstream ss;
ss << "{"
<< " \"GUID\": \"" << guid << "\","
<< " \"Type\": \"" << type << "\","
<< " \"State\": \"" << state << "\""
<< "}";
return ss.str();
}
} // namespace
class HostScanSchedulerImplTest : public testing::Test {
protected:
void SetUp() override {
helper_ = std::make_unique<NetworkStateTestHelper>(
/*use_default_devices_and_services=*/true);
histogram_tester_ = std::make_unique<base::HistogramTester>();
helper_->network_state_handler()->SetTetherTechnologyState(
NetworkStateHandler::TECHNOLOGY_ENABLED);
fake_host_scanner_ = std::make_unique<FakeHostScanner>();
session_manager_ = std::make_unique<session_manager::SessionManager>();
host_scan_scheduler_ = std::make_unique<HostScanSchedulerImpl>(
helper_->network_state_handler(), fake_host_scanner_.get(),
session_manager_.get());
mock_host_scan_batch_timer_ = new base::MockOneShotTimer();
// Advance the clock by an arbitrary value to ensure that when Now() is
// called, the Unix epoch will not be returned.
test_clock_.Advance(base::Seconds(10));
test_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();
host_scan_scheduler_->SetTestDoubles(
base::WrapUnique(mock_host_scan_batch_timer_.get()), &test_clock_,
test_task_runner_);
}
void TearDown() override {
host_scan_scheduler_.reset();
helper_.reset();
}
void RequestScan(const NetworkTypePattern& type) {
host_scan_scheduler_->ScanRequested(type);
}
void InitializeEthernet(bool is_initially_connected) {
std::string state =
is_initially_connected ? shill::kStateReady : shill::kStateIdle;
ethernet_service_path_ =
helper_->ConfigureService(CreateConfigurationJsonString(
kEthernetServiceGuid, shill::kTypeEthernet, state));
helper_->manager_test()->SetManagerProperty(
shill::kDefaultServiceProperty, base::Value(ethernet_service_path_));
base::RunLoop().RunUntilIdle();
test_task_runner_->RunUntilIdle();
}
// Disconnects the Ethernet network and manually sets the default network to
// |new_default_service_path|. If |new_default_service_path| is empty then no
// default network is set.
void SetEthernetNetworkDisconnected(
const std::string& new_default_service_path) {
helper_->SetServiceProperty(ethernet_service_path_,
std::string(shill::kStateProperty),
base::Value(shill::kStateIdle));
helper_->manager_test()->SetManagerProperty(
shill::kDefaultServiceProperty, base::Value(new_default_service_path));
base::RunLoop().RunUntilIdle();
test_task_runner_->RunUntilIdle();
}
void SetEthernetNetworkConnecting() {
helper_->SetServiceProperty(ethernet_service_path_,
std::string(shill::kStateProperty),
base::Value(shill::kStateAssociation));
// Ethernet does not become the default network until it connects.
base::RunLoop().RunUntilIdle();
test_task_runner_->RunUntilIdle();
}
void SetEthernetNetworkConnected() {
helper_->SetServiceProperty(ethernet_service_path_,
std::string(shill::kStateProperty),
base::Value(shill::kStateReady));
helper_->manager_test()->SetManagerProperty(
shill::kDefaultServiceProperty, base::Value(ethernet_service_path_));
base::RunLoop().RunUntilIdle();
test_task_runner_->RunUntilIdle();
}
// Adds a Tether network state, adds a Wifi network to be used as the Wifi
// hotspot, and associates the two networks. Returns the service path of the
// Wifi network.
std::string AddTetherNetworkState() {
helper_->network_state_handler()->AddTetherNetworkState(
kTetherGuid, "name", "carrier", 100 /* battery_percentage */,
100 /* signal strength */, false /* has_connected_to_host */);
std::string wifi_service_path =
helper_->ConfigureService(CreateConfigurationJsonString(
kWifiServiceGuid, shill::kTypeWifi, shill::kStateReady));
helper_->network_state_handler()
->AssociateTetherNetworkStateWithWifiNetwork(kTetherGuid,
kWifiServiceGuid);
return wifi_service_path;
}
void SetScreenLockedState(bool is_locked) {
session_manager_->SetSessionState(
is_locked ? session_manager::SessionState::LOCKED
: session_manager::SessionState::LOGIN_PRIMARY);
}
void VerifyScanDuration(size_t expected_num_seconds) {
histogram_tester_->ExpectTimeBucketCount(
"InstantTethering.HostScanBatchDuration",
base::Seconds(expected_num_seconds), 1u);
}
NetworkStateHandler* network_state_handler() {
return helper_->network_state_handler();
}
base::test::TaskEnvironment task_environment_;
std::string ethernet_service_path_;
std::unique_ptr<NetworkStateTestHelper> helper_;
std::unique_ptr<FakeHostScanner> fake_host_scanner_;
std::unique_ptr<session_manager::SessionManager> session_manager_;
raw_ptr<base::MockOneShotTimer, DanglingUntriaged>
mock_host_scan_batch_timer_;
base::SimpleTestClock test_clock_;
scoped_refptr<base::TestSimpleTaskRunner> test_task_runner_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<HostScanSchedulerImpl> host_scan_scheduler_;
};
TEST_F(HostScanSchedulerImplTest, AttemptScanIfOffline) {
host_scan_scheduler_->AttemptScanIfOffline();
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_TRUE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
test_clock_.Advance(base::Seconds(5));
fake_host_scanner_->StopScan();
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
// Fire the timer; the duration should be recorded.
mock_host_scan_batch_timer_->Fire();
VerifyScanDuration(5u /* expected_num_sections */);
}
TEST_F(HostScanSchedulerImplTest, TestDeviceLockAndUnlock_Offline) {
// Lock the screen. This should never trigger a scan.
SetScreenLockedState(true /* is_locked */);
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
// Try to start a scan. Because the screen is locked, this should not
// cause a scan to be started.
host_scan_scheduler_->AttemptScanIfOffline();
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
// Unlock the screen. Because the device is offline, a new scan should have
// started.
SetScreenLockedState(false /* is_locked */);
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
}
TEST_F(HostScanSchedulerImplTest, TestDeviceLockAndUnlock_Online) {
// Simulate the device being online.
InitializeEthernet(true /* is_initially_connected */);
// Lock the screen. This should never trigger a scan.
SetScreenLockedState(true /* is_locked */);
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
// Try to start a scan. Because the screen is locked, this should not
// cause a scan to be started.
host_scan_scheduler_->AttemptScanIfOffline();
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
// Unlock the screen. Because the device is online, a new scan should not have
// started.
SetScreenLockedState(false /* is_locked */);
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
}
TEST_F(HostScanSchedulerImplTest, ScanRequested) {
// Begin scanning.
RequestScan(NetworkTypePattern::Tether());
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_TRUE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
// Should not begin a new scan while a scan is active.
RequestScan(NetworkTypePattern::Tether());
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_TRUE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
test_clock_.Advance(base::Seconds(5));
fake_host_scanner_->StopScan();
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
mock_host_scan_batch_timer_->Fire();
VerifyScanDuration(5u /* expected_num_sections */);
// A new scan should be allowed once a scan is not active.
RequestScan(NetworkTypePattern::Tether());
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_TRUE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
}
TEST_F(HostScanSchedulerImplTest, ScanRequested_NonMatchingNetworkTypePattern) {
RequestScan(NetworkTypePattern::WiFi());
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
}
TEST_F(HostScanSchedulerImplTest, HostScanSchedulerDestroyed) {
host_scan_scheduler_->AttemptScanIfOffline();
EXPECT_TRUE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
test_clock_.Advance(base::Seconds(5));
// Delete |host_scan_scheduler_|, which should cause the metric to be logged.
host_scan_scheduler_.reset();
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
VerifyScanDuration(5u /* expected_num_sections */);
}
TEST_F(HostScanSchedulerImplTest, HostScanBatchMetric) {
// The first scan takes 5 seconds. After stopping, the timer should be
// running.
host_scan_scheduler_->AttemptScanIfOffline();
test_clock_.Advance(base::Seconds(5));
fake_host_scanner_->StopScan();
EXPECT_TRUE(mock_host_scan_batch_timer_->IsRunning());
// Advance the clock by 1 second and start another scan. The timer should have
// been stopped.
test_clock_.Advance(base::Seconds(1));
EXPECT_LT(base::Seconds(1), mock_host_scan_batch_timer_->GetCurrentDelay());
host_scan_scheduler_->AttemptScanIfOffline();
EXPECT_FALSE(mock_host_scan_batch_timer_->IsRunning());
// Stop the scan; the duration should not have been recorded, and the timer
// should be running again.
test_clock_.Advance(base::Seconds(5));
fake_host_scanner_->StopScan();
EXPECT_TRUE(mock_host_scan_batch_timer_->IsRunning());
// Advance the clock by 59 seconds and start another scan. The timer should
// have been stopped.
test_clock_.Advance(base::Seconds(59));
EXPECT_LT(base::Seconds(59), mock_host_scan_batch_timer_->GetCurrentDelay());
host_scan_scheduler_->AttemptScanIfOffline();
EXPECT_FALSE(mock_host_scan_batch_timer_->IsRunning());
// Stop the scan; the duration should not have been recorded, and the timer
// should be running again.
test_clock_.Advance(base::Seconds(5));
fake_host_scanner_->StopScan();
EXPECT_TRUE(mock_host_scan_batch_timer_->IsRunning());
// Advance the clock by 60 seconds, which should be equal to the timer's
// delay. Since this is a MockTimer, we need to manually fire the timer.
test_clock_.Advance(base::Seconds(60));
EXPECT_EQ(base::Seconds(60), mock_host_scan_batch_timer_->GetCurrentDelay());
mock_host_scan_batch_timer_->Fire();
// The scan duration should be equal to the three 5-second scans as well as
// the 1-second and 59-second breaks between the three scans.
VerifyScanDuration(5u + 1u + 5u + 59u + 5u /* expected_num_sections */);
// Now, start a new 5-second scan, then wait for the timer to fire. A new
// batch duration should have been logged to metrics.
host_scan_scheduler_->AttemptScanIfOffline();
test_clock_.Advance(base::Seconds(5));
fake_host_scanner_->StopScan();
EXPECT_TRUE(mock_host_scan_batch_timer_->IsRunning());
test_clock_.Advance(base::Seconds(60));
EXPECT_EQ(base::Seconds(60), mock_host_scan_batch_timer_->GetCurrentDelay());
mock_host_scan_batch_timer_->Fire();
VerifyScanDuration(5u /* expected_num_sections */);
}
TEST_F(HostScanSchedulerImplTest, DefaultNetworkChanged) {
InitializeEthernet(false /* is_initially_connected */);
// When no Tether network is present, a scan should start when the default
// network is disconnected.
SetEthernetNetworkConnecting();
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkConnected();
EXPECT_EQ(0u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkDisconnected(std::string() /* default_service_path */);
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_TRUE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
fake_host_scanner_->StopScan();
// When Tether is present but disconnected, a scan should start when the
// default network is disconnected.
std::string tether_service_path = AddTetherNetworkState();
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkConnecting();
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkConnected();
EXPECT_EQ(1u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkDisconnected(std::string() /* default_service_path */);
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_TRUE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
fake_host_scanner_->StopScan();
// When Tether is present and connecting, no scan should start when an
// Ethernet network becomes the default network and then disconnects.
network_state_handler()->SetTetherNetworkStateConnecting(kTetherGuid);
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkConnecting();
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkConnected();
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkDisconnected(tether_service_path);
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
fake_host_scanner_->StopScan();
// When Tether is present and connected, no scan should start when an Ethernet
// network becomes the default network and then disconnects.
base::RunLoop().RunUntilIdle();
network_state_handler()->SetTetherNetworkStateConnected(kTetherGuid);
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkConnecting();
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkConnected();
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
SetEthernetNetworkDisconnected(tether_service_path);
EXPECT_EQ(2u, fake_host_scanner_->num_scans_started());
EXPECT_FALSE(
network_state_handler()->GetScanningByType(NetworkTypePattern::Tether()));
}
} // namespace tether
} // namespace ash