| // 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 |