blob: 5f382c9a5a2e85f74276f04e6fc5ca972846f5f3 [file] [log] [blame]
// Copyright 2017 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/components/tether/master_host_scan_cache.h"
#include <memory>
#include <unordered_map>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/timer/mock_timer.h"
#include "chromeos/components/tether/device_id_tether_network_guid_map.h"
#include "chromeos/components/tether/fake_active_host.h"
#include "chromeos/components/tether/fake_host_scan_cache.h"
#include "chromeos/components/tether/host_scan_test_util.h"
#include "chromeos/components/tether/persistent_host_scan_cache.h"
#include "chromeos/components/tether/timer_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace tether {
namespace {
class FakePersistentHostScanCache : public FakeHostScanCache,
public PersistentHostScanCache {
public:
FakePersistentHostScanCache() = default;
~FakePersistentHostScanCache() override = default;
// PersistentHostScanCache:
std::unordered_map<std::string, HostScanCacheEntry> GetStoredCacheEntries()
override {
return cache();
}
};
// MockTimer which invokes a callback in its destructor.
class ExtendedMockTimer : public base::MockOneShotTimer {
public:
explicit ExtendedMockTimer(const base::Closure& destructor_callback)
: destructor_callback_(destructor_callback) {}
~ExtendedMockTimer() override { destructor_callback_.Run(); }
private:
base::Closure destructor_callback_;
};
class TestTimerFactory : public TimerFactory {
public:
TestTimerFactory() = default;
~TestTimerFactory() override = default;
std::unordered_map<std::string, ExtendedMockTimer*>&
tether_network_guid_to_timer_map() {
return tether_network_guid_to_timer_map_;
}
void set_tether_network_guid_for_next_timer(
const std::string& tether_network_guid_for_next_timer) {
tether_network_guids_for_upcoming_timers_.push_back(
tether_network_guid_for_next_timer);
}
// TimerFactory:
std::unique_ptr<base::OneShotTimer> CreateOneShotTimer() override {
EXPECT_TRUE(!tether_network_guids_for_upcoming_timers_.empty());
// Pop the first GUID off the list of upcoming GUIDs.
std::string guid_for_timer =
tether_network_guids_for_upcoming_timers_.front();
EXPECT_FALSE(guid_for_timer.empty());
tether_network_guids_for_upcoming_timers_.erase(
tether_network_guids_for_upcoming_timers_.begin());
ExtendedMockTimer* mock_timer = new ExtendedMockTimer(
base::Bind(&TestTimerFactory::OnActiveTimerDestructor,
base::Unretained(this), guid_for_timer));
tether_network_guid_to_timer_map_[guid_for_timer] = mock_timer;
return base::WrapUnique(mock_timer);
}
private:
void OnActiveTimerDestructor(const std::string& tether_network_guid) {
tether_network_guid_to_timer_map_.erase(
tether_network_guid_to_timer_map_.find(tether_network_guid));
}
std::vector<std::string> tether_network_guids_for_upcoming_timers_;
std::unordered_map<std::string, ExtendedMockTimer*>
tether_network_guid_to_timer_map_;
};
} // namespace
// TODO(khorimoto): The test uses a FakeHostScanCache to keep an in-memory
// cache of expected values. This has the potential to be confusing, since this
// is the test for MasterHostScanCache. Clean this up to avoid using
// FakeHostScanCache if possible.
class MasterHostScanCacheTest : public testing::Test {
protected:
MasterHostScanCacheTest()
: test_entries_(host_scan_test_util::CreateTestEntries()) {}
void SetUp() override {
test_timer_factory_ = new TestTimerFactory();
fake_active_host_ = std::make_unique<FakeActiveHost>();
fake_network_host_scan_cache_ = std::make_unique<FakeHostScanCache>();
fake_persistent_host_scan_cache_ =
base::WrapUnique(new FakePersistentHostScanCache());
host_scan_cache_ = std::make_unique<MasterHostScanCache>(
base::WrapUnique(test_timer_factory_), fake_active_host_.get(),
fake_network_host_scan_cache_.get(),
fake_persistent_host_scan_cache_.get());
// To track what is expected to be contained in the cache, maintain a
// FakeHostScanCache in memory and update it alongside |host_scan_cache_|.
// Use a std::vector to track which device IDs correspond to devices whose
// Tether networks' HasConnectedToHost fields are expected to be set.
expected_cache_ = std::make_unique<FakeHostScanCache>();
device_id_tether_network_guid_map_ =
std::make_unique<DeviceIdTetherNetworkGuidMap>();
}
void FireTimer(const std::string& tether_network_guid) {
ExtendedMockTimer* timer =
test_timer_factory_
->tether_network_guid_to_timer_map()[tether_network_guid];
ASSERT_TRUE(timer);
timer->Fire();
// If the device whose correlated timer has fired is not the active host, it
// is expected to be removed from the cache.
if (fake_active_host_->GetTetherNetworkGuid() != tether_network_guid) {
expected_cache_->RemoveHostScanResult(tether_network_guid);
}
}
void SetActiveHost(const std::string& tether_network_guid) {
if (tether_network_guid.empty()) {
fake_active_host_->SetActiveHostDisconnected();
} else {
fake_active_host_->SetActiveHostConnected(
device_id_tether_network_guid_map_->GetDeviceIdForTetherNetworkGuid(
tether_network_guid),
tether_network_guid, "wifiNetworkGuid");
}
}
void SetHostScanResult(const HostScanCacheEntry& entry) {
test_timer_factory_->set_tether_network_guid_for_next_timer(
entry.tether_network_guid);
host_scan_cache_->SetHostScanResult(entry);
expected_cache_->SetHostScanResult(entry);
}
void RemoveHostScanResult(const std::string& tether_network_guid) {
host_scan_cache_->RemoveHostScanResult(tether_network_guid);
if (fake_active_host_->GetTetherNetworkGuid() != tether_network_guid)
expected_cache_->RemoveHostScanResult(tether_network_guid);
}
// Verifies that the information present in |expected_cache_| mirrors what
// |host_scan_cache_| has stored.
void VerifyCacheContainsExpectedContents(size_t expected_size) {
EXPECT_EQ(expected_size, expected_cache_->size());
EXPECT_EQ(expected_size, fake_network_host_scan_cache_->size());
EXPECT_EQ(expected_size, fake_persistent_host_scan_cache_->size());
EXPECT_EQ(expected_cache_->GetTetherGuidsInCache(),
host_scan_cache_->GetTetherGuidsInCache());
for (auto& it : expected_cache_->cache()) {
const std::string tether_network_guid = it.first;
const HostScanCacheEntry& expected_entry = it.second;
const HostScanCacheEntry* network_entry =
fake_network_host_scan_cache_->GetCacheEntry(tether_network_guid);
ASSERT_TRUE(network_entry);
const HostScanCacheEntry* persistent_entry =
fake_persistent_host_scan_cache_->GetCacheEntry(tether_network_guid);
ASSERT_TRUE(persistent_entry);
EXPECT_EQ(expected_entry.device_name, network_entry->device_name);
EXPECT_EQ(expected_entry.device_name, persistent_entry->device_name);
EXPECT_EQ(expected_entry.carrier, network_entry->carrier);
EXPECT_EQ(expected_entry.carrier, persistent_entry->carrier);
EXPECT_EQ(expected_entry.battery_percentage,
network_entry->battery_percentage);
EXPECT_EQ(expected_entry.battery_percentage,
persistent_entry->battery_percentage);
EXPECT_EQ(expected_entry.signal_strength, network_entry->signal_strength);
EXPECT_EQ(expected_entry.signal_strength,
persistent_entry->signal_strength);
EXPECT_EQ(expected_entry.setup_required, network_entry->setup_required);
EXPECT_EQ(expected_entry.setup_required,
persistent_entry->setup_required);
// Ensure that each entry has an actively-running Timer.
ExtendedMockTimer* timer =
test_timer_factory_
->tether_network_guid_to_timer_map()[tether_network_guid];
ASSERT_TRUE(timer);
EXPECT_TRUE(timer->IsRunning());
}
}
const std::unordered_map<std::string, HostScanCacheEntry> test_entries_;
TestTimerFactory* test_timer_factory_;
std::unique_ptr<FakeActiveHost> fake_active_host_;
std::unique_ptr<FakeHostScanCache> fake_network_host_scan_cache_;
std::unique_ptr<FakePersistentHostScanCache> fake_persistent_host_scan_cache_;
std::unique_ptr<FakeHostScanCache> expected_cache_;
// TODO(hansberry): Use a fake for this when a real mapping scheme is created.
std::unique_ptr<DeviceIdTetherNetworkGuidMap>
device_id_tether_network_guid_map_;
std::unique_ptr<MasterHostScanCache> host_scan_cache_;
private:
DISALLOW_COPY_AND_ASSIGN(MasterHostScanCacheTest);
};
TEST_F(MasterHostScanCacheTest, TestSetScanResultsAndLetThemExpire) {
SetHostScanResult(test_entries_.at(host_scan_test_util::kTetherGuid0));
VerifyCacheContainsExpectedContents(1u /* expected_size */);
SetHostScanResult(test_entries_.at(host_scan_test_util::kTetherGuid1));
VerifyCacheContainsExpectedContents(2u /* expected_size */);
SetHostScanResult(test_entries_.at(host_scan_test_util::kTetherGuid2));
VerifyCacheContainsExpectedContents(3u /* expected_size */);
SetHostScanResult(test_entries_.at(host_scan_test_util::kTetherGuid3));
VerifyCacheContainsExpectedContents(4u /* expected_size */);
FireTimer(host_scan_test_util::kTetherGuid0);
VerifyCacheContainsExpectedContents(3u /* expected_size */);
FireTimer(host_scan_test_util::kTetherGuid1);
VerifyCacheContainsExpectedContents(2u /* expected_size */);
FireTimer(host_scan_test_util::kTetherGuid2);
VerifyCacheContainsExpectedContents(1u /* expected_size */);
FireTimer(host_scan_test_util::kTetherGuid3);
VerifyCacheContainsExpectedContents(0 /* expected_size */);
}
TEST_F(MasterHostScanCacheTest, TestSetScanResultThenUpdateAndRemove) {
SetHostScanResult(test_entries_.at(host_scan_test_util::kTetherGuid0));
VerifyCacheContainsExpectedContents(1u /* expected_size */);
// Change the fields for tether network with GUID |kTetherGuid0| to the
// fields corresponding to |kTetherGuid1|.
SetHostScanResult(
*HostScanCacheEntry::Builder()
.SetTetherNetworkGuid(host_scan_test_util::kTetherGuid0)
.SetDeviceName(host_scan_test_util::kTetherDeviceName0)
.SetCarrier(host_scan_test_util::kTetherCarrier1)
.SetBatteryPercentage(host_scan_test_util::kTetherBatteryPercentage1)
.SetSignalStrength(host_scan_test_util::kTetherSignalStrength1)
.SetSetupRequired(host_scan_test_util::kTetherSetupRequired1)
.Build());
VerifyCacheContainsExpectedContents(1u /* expected_size */);
// Now, remove that result.
RemoveHostScanResult(host_scan_test_util::kTetherGuid0);
VerifyCacheContainsExpectedContents(0 /* expected_size */);
}
TEST_F(MasterHostScanCacheTest, TestSetScanResult_SetActiveHost) {
SetHostScanResult(test_entries_.at(host_scan_test_util::kTetherGuid0));
VerifyCacheContainsExpectedContents(1u /* expected_size */);
// Now, set the active host to be the device 0.
SetActiveHost(host_scan_test_util::kTetherGuid0);
// Attempt to remove the active host. This operation should fail since
// removing the active host from the cache is not allowed.
RemoveHostScanResult(host_scan_test_util::kTetherGuid0);
VerifyCacheContainsExpectedContents(1u /* expected_size */);
// Fire the timer for the active host. Likewise, this should not result in the
// cache entry being removed.
FireTimer(host_scan_test_util::kTetherGuid0);
VerifyCacheContainsExpectedContents(1u /* expected_size */);
// Now, unset the active host.
SetActiveHost("");
// Removing the device should now succeed.
RemoveHostScanResult(host_scan_test_util::kTetherGuid0);
EXPECT_TRUE(expected_cache_->empty());
VerifyCacheContainsExpectedContents(0 /* expected_size */);
}
TEST_F(MasterHostScanCacheTest, TestRecoversFromCrashAndCleansUpWhenDeleted) {
// Delete the cache that was initialized in SetUp(). This test requires extra
// setup before initialization.
host_scan_cache_.reset();
// Add results for GUIDs 0 and 1 to the persistent cache.
fake_persistent_host_scan_cache_->SetHostScanResult(
test_entries_.at(host_scan_test_util::kTetherGuid0));
fake_persistent_host_scan_cache_->SetHostScanResult(
test_entries_.at(host_scan_test_util::kTetherGuid1));
// These results are expected to be in the master cache after it is created.
expected_cache_->SetHostScanResult(
test_entries_.at(host_scan_test_util::kTetherGuid0));
expected_cache_->SetHostScanResult(
test_entries_.at(host_scan_test_util::kTetherGuid1));
// Alert the timer factory that these GUIDs will be added. To ensure that the
// timer GUID is set in the correct order, iterate through the stored cache
// entries to mimic the iteration order performed in MasterHostScanCache.
// See crbug.com/750342.
test_timer_factory_ = new TestTimerFactory();
std::unordered_map<std::string, HostScanCacheEntry> persisted_entries =
fake_persistent_host_scan_cache_->GetStoredCacheEntries();
for (const auto& it : persisted_entries)
test_timer_factory_->set_tether_network_guid_for_next_timer(it.first);
// Create the master cache. It should have automatically picked up the
// persisted scan results, even though they were not explicitly added.
host_scan_cache_ = std::make_unique<MasterHostScanCache>(
base::WrapUnique(test_timer_factory_), fake_active_host_.get(),
fake_network_host_scan_cache_.get(),
fake_persistent_host_scan_cache_.get());
VerifyCacheContainsExpectedContents(2u /* expected_size */);
// Test that the timers are still valid for the scan results that were added
// at startup by firing the timer for GUID 0.
FireTimer(host_scan_test_util::kTetherGuid0);
VerifyCacheContainsExpectedContents(1u /* expected_size */);
// Now, only GUID 1 should be present.
EXPECT_TRUE(
host_scan_cache_->ExistsInCache(host_scan_test_util::kTetherGuid1));
// Now, delete the master cache. It should result in the sub-caches being
// cleared. This verifies that the persistent cache is cleared when logging
// out (i.e., when the Tether component is shut down without a crash).
host_scan_cache_.reset();
EXPECT_TRUE(fake_network_host_scan_cache_->empty());
EXPECT_TRUE(fake_persistent_host_scan_cache_->empty());
}
} // namespace tether
} // namespace chromeos