blob: 3db73df2f7920015468d194ede1d1114078bd2c4 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/network/cellular_inhibitor.h"
#include <memory>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/shill/fake_shill_device_client.h"
#include "chromeos/dbus/shill/shill_clients.h"
#include "chromeos/dbus/shill/shill_manager_client.h"
#include "chromeos/network/network_device_handler_impl.h"
#include "chromeos/network/network_handler_callbacks.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/network/network_state_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace chromeos {
namespace {
const char kDefaultCellularDevicePath[] = "stub_cellular_device";
const char kInhibitOperationResultHistogram[] =
"Network.Cellular.InhibitResult";
constexpr base::TimeDelta kScanningChangeTimeout = base::Seconds(120);
enum class GetInhibitedPropertyResult { kTrue, kFalse, kOperationFailed };
class TestObserver : public CellularInhibitor::Observer {
public:
TestObserver() = default;
~TestObserver() override = default;
size_t num_observer_events() const { return num_observer_events_; }
private:
// CellularInhibitor::Observer:
void OnInhibitStateChanged() override { ++num_observer_events_; }
size_t num_observer_events_ = 0u;
};
} // namespace
class CellularInhibitorTest : public testing::Test {
protected:
CellularInhibitorTest()
: task_environment_(
base::test::SingleThreadTaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::TimeSource::MOCK_TIME),
helper_(/*use_default_devices_and_services=*/false) {}
~CellularInhibitorTest() override = default;
// testing::Test:
void SetUp() override {
// Disable inhibit scanning simulation since this test tests the
// intermediate state where the scanning is set to true but not set to false
// yet.
helper_.device_test()->SetSimulateInhibitScanning(false);
helper_.device_test()->ClearDevices();
cellular_inhibitor_.Init(helper_.network_state_handler(),
helper_.network_device_handler());
cellular_inhibitor_.AddObserver(&observer_);
}
void TearDown() override {
cellular_inhibitor_.RemoveObserver(&observer_);
helper_.device_test()->SetPropertyChangeDelay(absl::nullopt);
}
void AddCellularDevice() {
helper_.device_test()->AddDevice(kDefaultCellularDevicePath,
shill::kTypeCellular, "cellular1");
base::RunLoop().RunUntilIdle();
}
std::unique_ptr<CellularInhibitor::InhibitLock> InhibitCellularScanningSync(
CellularInhibitor::InhibitReason inhibit_reason) {
base::RunLoop run_loop;
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock;
cellular_inhibitor_.InhibitCellularScanning(
inhibit_reason,
base::BindLambdaForTesting(
[&](std::unique_ptr<CellularInhibitor::InhibitLock> result) {
// Shill will start scanning before setting inhibit
SetScanning(/*is_scanning=*/true);
inhibit_lock = std::move(result);
run_loop.Quit();
}));
run_loop.Run();
return inhibit_lock;
}
void InhibitCellularScanning(
CellularInhibitor::InhibitReason inhibit_reason,
std::unique_ptr<CellularInhibitor::InhibitLock>& lock) {
cellular_inhibitor_.InhibitCellularScanning(
inhibit_reason,
base::BindLambdaForTesting(
[&](std::unique_ptr<CellularInhibitor::InhibitLock> result) {
// Shill will start scanning before setting inhibit
SetScanning(/*is_scanning=*/true);
lock = std::move(result);
}));
}
void SetScanning(bool is_scanning) {
helper_.network_device_handler()->SetDeviceProperty(
kDefaultCellularDevicePath, shill::kScanningProperty,
base::Value(is_scanning), base::DoNothing(), base::DoNothing());
base::RunLoop().RunUntilIdle();
}
GetInhibitedPropertyResult GetInhibitedProperty() {
properties_.reset();
helper_.network_device_handler()->GetDeviceProperties(
kDefaultCellularDevicePath,
base::BindOnce(&CellularInhibitorTest::GetPropertiesCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
if (!properties_)
return GetInhibitedPropertyResult::kOperationFailed;
absl::optional<bool> inhibited =
properties_->FindBoolKey(shill::kInhibitedProperty);
EXPECT_TRUE(inhibited.has_value());
return inhibited.value() ? GetInhibitedPropertyResult::kTrue
: GetInhibitedPropertyResult::kFalse;
}
absl::optional<CellularInhibitor::InhibitReason> GetInhibitReason() const {
return cellular_inhibitor_.GetInhibitReason();
}
void SetDevicePropertyChangeDelay() {
helper_.device_test()->SetPropertyChangeDelay(
CellularInhibitor::kInhibitPropertyChangeTimeout);
}
void FastForwardInhibitPropertyChangeTimeout() {
task_environment_.FastForwardBy(
CellularInhibitor::kInhibitPropertyChangeTimeout);
}
void FastForwardScanningChangeTimeout() {
task_environment_.FastForwardBy(kScanningChangeTimeout);
}
size_t GetNumObserverEvents() const {
return observer_.num_observer_events();
}
base::HistogramTester& histogram_tester() { return histogram_tester_; }
private:
void GetPropertiesCallback(const std::string& device_path,
absl::optional<base::Value> properties) {
if (!properties) {
properties_.reset();
return;
}
properties_ = base::DictionaryValue::From(
std::make_unique<base::Value>(std::move(*properties)));
}
base::test::SingleThreadTaskEnvironment task_environment_;
base::HistogramTester histogram_tester_;
NetworkStateTestHelper helper_;
CellularInhibitor cellular_inhibitor_;
TestObserver observer_;
std::unique_ptr<base::DictionaryValue> properties_;
};
TEST_F(CellularInhibitorTest, SuccessSingleRequest) {
AddCellularDevice();
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock =
InhibitCellularScanningSync(
CellularInhibitor::InhibitReason::kInstallingProfile);
// Ensure that a valid lock is returned and Inhibit property is set on
// Cellular device.
EXPECT_TRUE(inhibit_lock);
EXPECT_EQ(1u, GetNumObserverEvents());
EXPECT_EQ(CellularInhibitor::InhibitReason::kInstallingProfile,
GetInhibitReason());
EXPECT_EQ(GetInhibitedPropertyResult::kTrue, GetInhibitedProperty());
// Ensure that deleting lock uninhibits the Cellular device.
inhibit_lock.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetInhibitedPropertyResult::kFalse, GetInhibitedProperty());
// At this point, we are still in the inhibited state, since scanning has not
// yet started and stopped.
EXPECT_EQ(1u, GetNumObserverEvents());
EXPECT_EQ(CellularInhibitor::InhibitReason::kInstallingProfile,
GetInhibitReason());
// Just stop scanning; this should alert observers that the inhibit
// operation has completed.
SetScanning(/*is_scanning=*/false);
EXPECT_EQ(2u, GetNumObserverEvents());
EXPECT_FALSE(GetInhibitReason().has_value());
histogram_tester().ExpectBucketCount(
kInhibitOperationResultHistogram,
CellularInhibitor::InhibitOperationResult::kSuccess,
/*expected_count=*/1);
}
TEST_F(CellularInhibitorTest, SuccessMultipleRequests) {
AddCellularDevice();
// Make two inhibit requests in parallel.
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock;
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock2;
InhibitCellularScanning(CellularInhibitor::InhibitReason::kInstallingProfile,
inhibit_lock);
InhibitCellularScanning(CellularInhibitor::InhibitReason::kRemovingProfile,
inhibit_lock2);
base::RunLoop().RunUntilIdle();
// Ensure that the only one inhibit lock has been granted.
EXPECT_TRUE(inhibit_lock);
EXPECT_FALSE(inhibit_lock2);
EXPECT_EQ(1u, GetNumObserverEvents());
EXPECT_EQ(CellularInhibitor::InhibitReason::kInstallingProfile,
GetInhibitReason());
EXPECT_EQ(GetInhibitedPropertyResult::kTrue, GetInhibitedProperty());
// Release the first lock; though this causes the Inhibited property to false,
// it should not yet start the next (queued) Inhibit flow, since the scanning
// flow has not yet started.
inhibit_lock.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, GetNumObserverEvents());
EXPECT_EQ(CellularInhibitor::InhibitReason::kInstallingProfile,
GetInhibitReason());
EXPECT_EQ(GetInhibitedPropertyResult::kFalse, GetInhibitedProperty());
EXPECT_FALSE(inhibit_lock2);
// Change scanning back to false, which should trigger the second lock being
// set.
SetScanning(/*is_scanning=*/false);
histogram_tester().ExpectBucketCount(
kInhibitOperationResultHistogram,
CellularInhibitor::InhibitOperationResult::kSuccess,
/*expected_count=*/1);
EXPECT_TRUE(inhibit_lock2);
EXPECT_EQ(3u, GetNumObserverEvents());
EXPECT_EQ(CellularInhibitor::InhibitReason::kRemovingProfile,
GetInhibitReason());
EXPECT_EQ(GetInhibitedPropertyResult::kTrue, GetInhibitedProperty());
// Ensure that inhibited property is set to false when all locks are deleted.
inhibit_lock2.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(3u, GetNumObserverEvents());
EXPECT_EQ(CellularInhibitor::InhibitReason::kRemovingProfile,
GetInhibitReason());
EXPECT_EQ(GetInhibitedPropertyResult::kFalse, GetInhibitedProperty());
SetScanning(/*is_scanning=*/false);
base::RunLoop().RunUntilIdle();
histogram_tester().ExpectBucketCount(
kInhibitOperationResultHistogram,
CellularInhibitor::InhibitOperationResult::kSuccess,
/*expected_count=*/2);
}
TEST_F(CellularInhibitorTest, Failure) {
// Do not add a Cellular device. This should cause commands below to fail,
// since the device cannot be inhibited if it does not exist.
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock =
InhibitCellularScanningSync(
CellularInhibitor::InhibitReason::kInstallingProfile);
EXPECT_EQ(GetInhibitedPropertyResult::kOperationFailed,
GetInhibitedProperty());
EXPECT_FALSE(inhibit_lock);
histogram_tester().ExpectBucketCount(
kInhibitOperationResultHistogram,
CellularInhibitor::InhibitOperationResult::kSetInhibitNoDevice,
/*expected_count=*/1);
}
TEST_F(CellularInhibitorTest, FailurePropertySetTimeout) {
AddCellularDevice();
SetDevicePropertyChangeDelay();
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock;
InhibitCellularScanning(CellularInhibitor::InhibitReason::kInstallingProfile,
inhibit_lock);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(inhibit_lock);
FastForwardInhibitPropertyChangeTimeout();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(inhibit_lock);
histogram_tester().ExpectBucketCount(
kInhibitOperationResultHistogram,
CellularInhibitor::InhibitOperationResult::kSetInhibitTimeout,
/*expected_count=*/1);
}
TEST_F(CellularInhibitorTest, FailureScanningChangeTimeout) {
AddCellularDevice();
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock =
InhibitCellularScanningSync(
CellularInhibitor::InhibitReason::kInstallingProfile);
// Ensure that a valid lock is returned and inhibit reason is returned.
EXPECT_TRUE(inhibit_lock);
EXPECT_EQ(CellularInhibitor::InhibitReason::kInstallingProfile,
GetInhibitReason());
// Delete lock to start uninhibiting and run till uninhibit is waiting for
// scanning state change.
inhibit_lock.reset();
base::RunLoop().RunUntilIdle();
// Verify that no inhibit reason is returned after timeout.
EXPECT_EQ(CellularInhibitor::InhibitReason::kInstallingProfile,
GetInhibitReason());
FastForwardScanningChangeTimeout();
EXPECT_FALSE(GetInhibitReason().has_value());
histogram_tester().ExpectBucketCount(
kInhibitOperationResultHistogram,
CellularInhibitor::InhibitOperationResult::kUninhibitTimeout,
/*expected_count=*/1);
}
} // namespace chromeos