| // Copyright 2018 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 "chrome/browser/resource_coordinator/local_site_characteristics_data_impl.h" |
| |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "chrome/browser/resource_coordinator/local_site_characteristics_data_unittest_utils.h" |
| #include "chrome/browser/resource_coordinator/time.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace resource_coordinator { |
| namespace internal { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kInitialTimeSinceEpoch = |
| base::TimeDelta::FromSeconds(1); |
| |
| class TestLocalSiteCharacteristicsDataImpl |
| : public LocalSiteCharacteristicsDataImpl { |
| public: |
| using LocalSiteCharacteristicsDataImpl::FeatureObservationDuration; |
| using LocalSiteCharacteristicsDataImpl::OnDestroyDelegate; |
| using LocalSiteCharacteristicsDataImpl::site_characteristics_for_testing; |
| using LocalSiteCharacteristicsDataImpl::TimeDeltaToInternalRepresentation; |
| |
| explicit TestLocalSiteCharacteristicsDataImpl( |
| const url::Origin& origin, |
| LocalSiteCharacteristicsDataImpl::OnDestroyDelegate* delegate, |
| LocalSiteCharacteristicsDatabase* database) |
| : LocalSiteCharacteristicsDataImpl(origin, delegate, database) {} |
| |
| base::TimeDelta FeatureObservationTimestamp( |
| const SiteCharacteristicsFeatureProto& feature_proto) { |
| return InternalRepresentationToTimeDelta(feature_proto.use_timestamp()); |
| } |
| |
| protected: |
| ~TestLocalSiteCharacteristicsDataImpl() override {} |
| }; |
| |
| class MockLocalSiteCharacteristicsDatabase |
| : public testing::NoopLocalSiteCharacteristicsDatabase { |
| public: |
| MockLocalSiteCharacteristicsDatabase() = default; |
| ~MockLocalSiteCharacteristicsDatabase() = default; |
| |
| // Note: As move-only parameters (e.g. OnceCallback) aren't supported by mock |
| // methods, add On... methods to pass a non-const reference to OnceCallback. |
| void ReadSiteCharacteristicsFromDB( |
| const url::Origin& origin, |
| LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback |
| callback) override { |
| OnReadSiteCharacteristicsFromDB(origin, callback); |
| } |
| MOCK_METHOD2(OnReadSiteCharacteristicsFromDB, |
| void(const url::Origin&, |
| LocalSiteCharacteristicsDatabase:: |
| ReadSiteCharacteristicsFromDBCallback&)); |
| |
| MOCK_METHOD2(WriteSiteCharacteristicsIntoDB, |
| void(const url::Origin&, const SiteCharacteristicsProto&)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockLocalSiteCharacteristicsDatabase); |
| }; |
| |
| // Returns a SiteCharacteristicsFeatureProto that indicates that a feature |
| // hasn't been used. |
| SiteCharacteristicsFeatureProto GetUnusedFeatureProto() { |
| SiteCharacteristicsFeatureProto unused_feature_proto; |
| unused_feature_proto.set_observation_duration(1U); |
| unused_feature_proto.set_use_timestamp(0U); |
| return unused_feature_proto; |
| } |
| |
| // Returns a SiteCharacteristicsFeatureProto that indicates that a feature |
| // has been used. |
| SiteCharacteristicsFeatureProto GetUsedFeatureProto() { |
| SiteCharacteristicsFeatureProto used_feature_proto; |
| used_feature_proto.set_observation_duration(0U); |
| used_feature_proto.set_use_timestamp(1U); |
| return used_feature_proto; |
| } |
| |
| } // namespace |
| |
| class LocalSiteCharacteristicsDataImplTest : public ::testing::Test { |
| public: |
| LocalSiteCharacteristicsDataImplTest() |
| : scoped_set_tick_clock_for_testing_(&test_clock_) {} |
| |
| void SetUp() override { |
| test_clock_.SetNowTicks(base::TimeTicks::UnixEpoch()); |
| // Advance the test clock by a small delay, as some tests will fail if the |
| // current time is equal to Epoch. |
| test_clock_.Advance(kInitialTimeSinceEpoch); |
| } |
| |
| protected: |
| scoped_refptr<TestLocalSiteCharacteristicsDataImpl> GetDataImpl( |
| const url::Origin& origin, |
| LocalSiteCharacteristicsDataImpl::OnDestroyDelegate* destroy_delegate, |
| LocalSiteCharacteristicsDatabase* database) { |
| return base::MakeRefCounted<TestLocalSiteCharacteristicsDataImpl>( |
| origin, destroy_delegate, database); |
| } |
| |
| // Use a mock database to intercept the initialization callback and save it |
| // locally so it can be run later. |
| scoped_refptr<TestLocalSiteCharacteristicsDataImpl> |
| GetDataImplAndInterceptReadCallback( |
| const url::Origin& origin, |
| LocalSiteCharacteristicsDataImpl::OnDestroyDelegate* destroy_delegate, |
| MockLocalSiteCharacteristicsDatabase* mock_db, |
| LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback* |
| read_cb) { |
| auto read_from_db_mock_impl = |
| [&](const url::Origin& origin, |
| LocalSiteCharacteristicsDatabase:: |
| ReadSiteCharacteristicsFromDBCallback& callback) { |
| *read_cb = std::move(callback); |
| }; |
| |
| EXPECT_CALL(*mock_db, |
| OnReadSiteCharacteristicsFromDB(::testing::_, ::testing::_)) |
| .WillOnce(::testing::Invoke(read_from_db_mock_impl)); |
| auto local_site_data = GetDataImpl(origin, &destroy_delegate_, mock_db); |
| ::testing::Mock::VerifyAndClear(mock_db); |
| return local_site_data; |
| } |
| |
| const url::Origin kDummyOrigin = url::Origin::Create(GURL("foo.com")); |
| const url::Origin kDummyOrigin2 = url::Origin::Create(GURL("bar.com")); |
| |
| base::SimpleTestTickClock test_clock_; |
| ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing_; |
| // Use a NiceMock as there's no need to add expectations in these tests, |
| // there's a dedicated test that ensure that the delegate works as expected. |
| ::testing::NiceMock< |
| testing::MockLocalSiteCharacteristicsDataImplOnDestroyDelegate> |
| destroy_delegate_; |
| |
| testing::NoopLocalSiteCharacteristicsDatabase database_; |
| }; |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, BasicTestEndToEnd) { |
| auto local_site_data = |
| GetDataImpl(kDummyOrigin, &destroy_delegate_, &database_); |
| |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| |
| // Initially the feature usage should be reported as unknown. |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UsesAudioInBackground()); |
| |
| // Advance the clock by a time lower than the miniumum observation time for |
| // the audio feature. |
| test_clock_.Advance(GetStaticSiteCharacteristicsDatabaseParams() |
| .audio_usage_observation_window - |
| base::TimeDelta::FromSeconds(1)); |
| |
| // The audio feature usage is still unknown as the observation window hasn't |
| // expired. |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UsesAudioInBackground()); |
| |
| // Report that the audio feature has been used. |
| local_site_data->NotifyUsesAudioInBackground(); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesAudioInBackground()); |
| |
| // When a feature is in use it's expected that its recorded observation |
| // timestamp is equal to the time delta since Unix Epoch when the observation |
| // has been made. |
| EXPECT_EQ(local_site_data->FeatureObservationTimestamp( |
| local_site_data->site_characteristics_for_testing() |
| .uses_audio_in_background()), |
| (test_clock_.NowTicks() - base::TimeTicks::UnixEpoch())); |
| EXPECT_EQ(local_site_data->FeatureObservationDuration( |
| local_site_data->site_characteristics_for_testing() |
| .uses_audio_in_background()), |
| base::TimeDelta()); |
| |
| // Advance the clock and make sure that notifications feature gets |
| // reported as unused. |
| test_clock_.Advance(GetStaticSiteCharacteristicsDatabaseParams() |
| .notifications_usage_observation_window); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureNotInUse, |
| local_site_data->UsesNotificationsInBackground()); |
| |
| // Observating that a feature has been used after its observation window has |
| // expired should still be recorded, the feature should then be reported as |
| // used. |
| local_site_data->NotifyUsesNotificationsInBackground(); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesNotificationsInBackground()); |
| |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, LastLoadedTime) { |
| auto local_site_data = |
| GetDataImpl(kDummyOrigin, &destroy_delegate_, &database_); |
| |
| // Create a second instance of this object, simulates having several tab |
| // owning it. |
| auto local_site_data2(local_site_data); |
| |
| local_site_data->NotifySiteLoaded(); |
| base::TimeDelta last_loaded_time = |
| local_site_data->last_loaded_time_for_testing(); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| |
| // Loading the site a second time shouldn't change the last loaded time. |
| local_site_data2->NotifySiteLoaded(); |
| EXPECT_EQ(last_loaded_time, local_site_data2->last_loaded_time_for_testing()); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| |
| // Unloading the site shouldn't update the last loaded time as there's still |
| // a loaded instance. |
| local_site_data2->NotifySiteUnloaded(TabVisibility::kForeground); |
| EXPECT_EQ(last_loaded_time, local_site_data->last_loaded_time_for_testing()); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| |
| local_site_data->NotifySiteUnloaded(TabVisibility::kForeground); |
| EXPECT_NE(last_loaded_time, local_site_data->last_loaded_time_for_testing()); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, GetFeatureUsageForUnloadedSite) { |
| auto local_site_data = |
| GetDataImpl(kDummyOrigin, &destroy_delegate_, &database_); |
| |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| local_site_data->NotifyUsesAudioInBackground(); |
| |
| test_clock_.Advance(GetStaticSiteCharacteristicsDatabaseParams() |
| .notifications_usage_observation_window - |
| base::TimeDelta::FromSeconds(1)); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesAudioInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UsesNotificationsInBackground()); |
| |
| const base::TimeDelta observation_duration_before_unload = |
| local_site_data->FeatureObservationDuration( |
| local_site_data->site_characteristics_for_testing() |
| .uses_notifications_in_background()); |
| |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| |
| // Once unloaded the feature observations should still be accessible. |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesAudioInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UsesNotificationsInBackground()); |
| |
| // Advancing the clock shouldn't affect the observation duration for this |
| // feature. |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| EXPECT_EQ(observation_duration_before_unload, |
| local_site_data->FeatureObservationDuration( |
| local_site_data->site_characteristics_for_testing() |
| .uses_notifications_in_background())); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UsesNotificationsInBackground()); |
| |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesAudioInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureNotInUse, |
| local_site_data->UsesNotificationsInBackground()); |
| |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, AllDurationGetSavedOnUnload) { |
| // This test helps making sure that the observation/timestamp fields get saved |
| // for all the features being tracked. |
| auto local_site_data = |
| GetDataImpl(kDummyOrigin, &destroy_delegate_, &database_); |
| |
| const base::TimeDelta kInterval = base::TimeDelta::FromSeconds(1); |
| const auto kIntervalInternalRepresentation = |
| TestLocalSiteCharacteristicsDataImpl::TimeDeltaToInternalRepresentation( |
| kInterval); |
| const auto kZeroIntervalInternalRepresentation = |
| TestLocalSiteCharacteristicsDataImpl::TimeDeltaToInternalRepresentation( |
| base::TimeDelta()); |
| |
| // The internal representation of a zero interval is expected to be equal to |
| // zero as the protobuf use variable size integers and so storing zero values |
| // is really efficient (uses only one bit). |
| EXPECT_EQ(0U, kZeroIntervalInternalRepresentation); |
| |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| test_clock_.Advance(kInterval); |
| // Makes use of a feature to make sure that the observation timestamps get |
| // saved. |
| local_site_data->NotifyUsesAudioInBackground(); |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| |
| SiteCharacteristicsProto expected_proto; |
| |
| auto expected_last_loaded_time = |
| TestLocalSiteCharacteristicsDataImpl::TimeDeltaToInternalRepresentation( |
| kInterval + kInitialTimeSinceEpoch); |
| |
| expected_proto.set_last_loaded(expected_last_loaded_time); |
| |
| // Features that haven't been used should have an observation duration of |
| // |kIntervalInternalRepresentation| and an observation timestamp equal to |
| // zero. |
| SiteCharacteristicsFeatureProto unused_feature_proto; |
| unused_feature_proto.set_observation_duration( |
| kIntervalInternalRepresentation); |
| |
| expected_proto.mutable_updates_favicon_in_background()->CopyFrom( |
| unused_feature_proto); |
| expected_proto.mutable_updates_title_in_background()->CopyFrom( |
| unused_feature_proto); |
| expected_proto.mutable_uses_notifications_in_background()->CopyFrom( |
| unused_feature_proto); |
| |
| // The audio feature has been used, so its observation duration value should |
| // be equal to zero, and its observation timestamp should be equal to the last |
| // loaded time in this case (as this feature has been used right before |
| // unloading). |
| SiteCharacteristicsFeatureProto used_feature_proto; |
| used_feature_proto.set_use_timestamp(expected_last_loaded_time); |
| expected_proto.mutable_uses_audio_in_background()->CopyFrom( |
| used_feature_proto); |
| |
| EXPECT_EQ( |
| expected_proto.SerializeAsString(), |
| local_site_data->site_characteristics_for_testing().SerializeAsString()); |
| } |
| |
| // Verify that the OnDestroyDelegate gets notified when a |
| // LocalSiteCharacteristicsDataImpl object gets destroyed. |
| TEST_F(LocalSiteCharacteristicsDataImplTest, DestroyNotifiesDelegate) { |
| ::testing::StrictMock< |
| testing::MockLocalSiteCharacteristicsDataImplOnDestroyDelegate> |
| strict_delegate; |
| { |
| auto local_site_data = |
| GetDataImpl(kDummyOrigin, &strict_delegate, &database_); |
| EXPECT_CALL(strict_delegate, OnLocalSiteCharacteristicsDataImplDestroyed( |
| local_site_data.get())); |
| } |
| ::testing::Mock::VerifyAndClear(&strict_delegate); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, |
| OnInitCallbackMergePreviousObservations) { |
| // Use a mock database to intercept the initialization callback and save it |
| // locally so it can be run later. This simulates an asynchronous |
| // initialization of this object and is used to test that the observations |
| // made between the time this object has been created and the callback is |
| // called get properly merged. |
| ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase> mock_db; |
| LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback |
| read_cb; |
| |
| auto local_site_data = GetDataImplAndInterceptReadCallback( |
| kDummyOrigin, &destroy_delegate_, &mock_db, &read_cb); |
| |
| // Simulates audio in background usage before the callback gets called. |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| local_site_data->NotifyUsesAudioInBackground(); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesAudioInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UsesNotificationsInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UpdatesFaviconInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UpdatesTitleInBackground()); |
| |
| // Unload the site and save the last loaded time to make sure the |
| // initialization doesn't overwrite it. |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| auto last_loaded = local_site_data->last_loaded_time_for_testing(); |
| |
| // Add a couple of performance samples. |
| local_site_data->NotifyLoadTimePerformanceMeasurement( |
| base::TimeDelta::FromMicroseconds(100), |
| base::TimeDelta::FromMicroseconds(1000), 2000u); |
| local_site_data->NotifyLoadTimePerformanceMeasurement( |
| base::TimeDelta::FromMicroseconds(200), |
| base::TimeDelta::FromMicroseconds(500), 1000u); |
| |
| // Make sure the local performance samples are averaged as expected. |
| EXPECT_EQ(2U, local_site_data->load_duration().num_datums()); |
| EXPECT_EQ(150, local_site_data->load_duration().value()); |
| |
| EXPECT_EQ(2U, local_site_data->cpu_usage_estimate().num_datums()); |
| EXPECT_EQ(750.0, local_site_data->cpu_usage_estimate().value()); |
| |
| EXPECT_EQ(2U, local_site_data->private_footprint_kb_estimate().num_datums()); |
| EXPECT_EQ(1500.0, local_site_data->private_footprint_kb_estimate().value()); |
| |
| // This protobuf should have a valid |last_loaded| field and valid observation |
| // durations for each features, but the |use_timestamp| field shouldn't have |
| // been initialized for the features that haven't been used. |
| EXPECT_TRUE( |
| local_site_data->site_characteristics_for_testing().has_last_loaded()); |
| EXPECT_TRUE(local_site_data->site_characteristics_for_testing() |
| .uses_audio_in_background() |
| .has_use_timestamp()); |
| EXPECT_FALSE(local_site_data->site_characteristics_for_testing() |
| .uses_notifications_in_background() |
| .has_use_timestamp()); |
| EXPECT_TRUE(local_site_data->site_characteristics_for_testing() |
| .uses_notifications_in_background() |
| .has_observation_duration()); |
| EXPECT_FALSE(local_site_data->site_characteristics_for_testing() |
| .has_load_time_estimates()); |
| |
| // Initialize a fake protobuf that indicates that this site updates its title |
| // while in background and set a fake last loaded time (this should be |
| // overriden once the callback runs). |
| base::Optional<SiteCharacteristicsProto> test_proto = |
| SiteCharacteristicsProto(); |
| SiteCharacteristicsFeatureProto unused_feature_proto = |
| GetUnusedFeatureProto(); |
| test_proto->mutable_updates_title_in_background()->CopyFrom( |
| GetUsedFeatureProto()); |
| test_proto->mutable_updates_favicon_in_background()->CopyFrom( |
| unused_feature_proto); |
| test_proto->mutable_uses_audio_in_background()->CopyFrom( |
| unused_feature_proto); |
| test_proto->mutable_uses_notifications_in_background()->CopyFrom( |
| unused_feature_proto); |
| test_proto->set_last_loaded(42); |
| |
| // Set the previously saved performance averages. |
| auto* estimates = test_proto->mutable_load_time_estimates(); |
| estimates->set_avg_load_duration_us(50); |
| estimates->set_avg_cpu_usage_us(250); |
| estimates->set_avg_footprint_kb(500); |
| |
| // Run the callback to indicate that the initialization has completed. |
| std::move(read_cb).Run(test_proto); |
| |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesAudioInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UpdatesTitleInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UpdatesFaviconInBackground()); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown, |
| local_site_data->UsesNotificationsInBackground()); |
| EXPECT_EQ(last_loaded, local_site_data->last_loaded_time_for_testing()); |
| |
| // Make sure the local performance samples have been updated with the previous |
| // averages. |
| EXPECT_EQ(3U, local_site_data->load_duration().num_datums()); |
| EXPECT_EQ(137.5, local_site_data->load_duration().value()); |
| |
| EXPECT_EQ(3U, local_site_data->cpu_usage_estimate().num_datums()); |
| EXPECT_EQ(562.5, local_site_data->cpu_usage_estimate().value()); |
| |
| EXPECT_EQ(3U, local_site_data->private_footprint_kb_estimate().num_datums()); |
| EXPECT_EQ(1125, local_site_data->private_footprint_kb_estimate().value()); |
| |
| |
| // Verify that the in-memory data is flushed to the protobuffer on write. |
| EXPECT_CALL(mock_db, |
| WriteSiteCharacteristicsIntoDB(::testing::_, ::testing::_)) |
| .WillOnce(::testing::Invoke( |
| [](const url::Origin& origin, const SiteCharacteristicsProto& proto) { |
| ASSERT_TRUE(proto.has_load_time_estimates()); |
| const auto& estimates = proto.load_time_estimates(); |
| ASSERT_TRUE(estimates.has_avg_load_duration_us()); |
| EXPECT_EQ(137.5, estimates.avg_load_duration_us()); |
| ASSERT_TRUE(estimates.has_avg_cpu_usage_us()); |
| EXPECT_EQ(562.5, estimates.avg_cpu_usage_us()); |
| ASSERT_TRUE(estimates.has_avg_footprint_kb()); |
| EXPECT_EQ(1125, estimates.avg_footprint_kb()); |
| })); |
| |
| local_site_data = nullptr; |
| ::testing::Mock::VerifyAndClear(&mock_db); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, LateAsyncReadDoesntEraseData) { |
| // Ensure that no historical data get lost if an asynchronous read from the |
| // database finishes after the last reference to a |
| // LocalSiteCharacteristicsDataImpl gets destroyed. |
| |
| ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase> mock_db; |
| LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback |
| read_cb; |
| |
| auto local_site_data_writer = GetDataImplAndInterceptReadCallback( |
| kDummyOrigin, &destroy_delegate_, &mock_db, &read_cb); |
| |
| local_site_data_writer->NotifySiteLoaded(); |
| local_site_data_writer->NotifyLoadedSiteBackgrounded(); |
| local_site_data_writer->NotifyUsesAudioInBackground(); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data_writer->UsesAudioInBackground()); |
| |
| local_site_data_writer->NotifySiteUnloaded(TabVisibility::kBackground); |
| |
| // Releasing |local_site_data_writer| should cause this object to get |
| // destroyed but there shouldn't be any write operation as the read hasn't |
| // completed. |
| EXPECT_CALL(destroy_delegate_, |
| OnLocalSiteCharacteristicsDataImplDestroyed(::testing::_)); |
| EXPECT_CALL(mock_db, |
| WriteSiteCharacteristicsIntoDB(::testing::_, ::testing::_)) |
| .Times(0); |
| local_site_data_writer = nullptr; |
| ::testing::Mock::VerifyAndClear(&destroy_delegate_); |
| ::testing::Mock::VerifyAndClear(&mock_db); |
| |
| EXPECT_TRUE(read_cb.IsCancelled()); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, |
| LateAsyncReadDoesntBypassClearEvent) { |
| ::testing::NiceMock<MockLocalSiteCharacteristicsDatabase> mock_db; |
| LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback |
| read_cb; |
| |
| auto local_site_data = GetDataImplAndInterceptReadCallback( |
| kDummyOrigin, &destroy_delegate_, &mock_db, &read_cb); |
| |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| local_site_data->NotifyUsesAudioInBackground(); |
| EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse, |
| local_site_data->UsesAudioInBackground()); |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| |
| // TODO(sebmarchand): Test that data is cleared here. |
| local_site_data->ClearObservationsAndInvalidateReadOperation(); |
| |
| EXPECT_TRUE(read_cb.IsCancelled()); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, BackgroundedCountTests) { |
| auto local_site_data = |
| GetDataImpl(kDummyOrigin, &destroy_delegate_, &database_); |
| |
| // By default the tabs are expected to be foregrounded. |
| EXPECT_EQ(0U, local_site_data->loaded_tabs_in_background_count_for_testing()); |
| |
| local_site_data->NotifySiteLoaded(); |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| |
| auto background_session_begin = |
| local_site_data->background_session_begin_for_testing(); |
| EXPECT_EQ(test_clock_.NowTicks(), background_session_begin); |
| |
| EXPECT_EQ(1U, local_site_data->loaded_tabs_in_background_count_for_testing()); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| |
| // Add a second instance of this object, this one pretending to be in |
| // foreground. |
| auto local_site_data_copy(local_site_data); |
| local_site_data_copy->NotifySiteLoaded(); |
| EXPECT_EQ(1U, local_site_data->loaded_tabs_in_background_count_for_testing()); |
| |
| EXPECT_EQ(background_session_begin, |
| local_site_data->background_session_begin_for_testing()); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| |
| local_site_data->NotifyLoadedSiteForegrounded(); |
| EXPECT_EQ(0U, local_site_data->loaded_tabs_in_background_count_for_testing()); |
| |
| auto expected_observation_duration = |
| test_clock_.NowTicks() - background_session_begin; |
| |
| auto observed_observation_duration = |
| local_site_data->FeatureObservationDuration( |
| local_site_data->site_characteristics_for_testing() |
| .uses_notifications_in_background()); |
| |
| EXPECT_EQ(expected_observation_duration, observed_observation_duration); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| EXPECT_EQ(1U, local_site_data->loaded_tabs_in_background_count_for_testing()); |
| background_session_begin = |
| local_site_data->background_session_begin_for_testing(); |
| EXPECT_EQ(test_clock_.NowTicks(), background_session_begin); |
| |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| local_site_data_copy->NotifySiteUnloaded(TabVisibility::kForeground); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, |
| OptionalFieldsNotPopulatedWhenClean) { |
| ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase> mock_db; |
| LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback |
| read_cb; |
| |
| auto local_site_data = GetDataImplAndInterceptReadCallback( |
| kDummyOrigin, &destroy_delegate_, &mock_db, &read_cb); |
| |
| EXPECT_EQ(0u, local_site_data->cpu_usage_estimate().num_datums()); |
| EXPECT_EQ(0u, local_site_data->private_footprint_kb_estimate().num_datums()); |
| |
| base::Optional<SiteCharacteristicsProto> test_proto = |
| SiteCharacteristicsProto(); |
| |
| // Run the callback to indicate that the initialization has completed. |
| std::move(read_cb).Run(test_proto); |
| |
| // There still should be no perf data. |
| EXPECT_EQ(0u, local_site_data->cpu_usage_estimate().num_datums()); |
| EXPECT_EQ(0u, local_site_data->private_footprint_kb_estimate().num_datums()); |
| |
| // Dirty the record to force a write. |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| local_site_data->NotifyUsesAudioInBackground(); |
| |
| // Verify that the saved protobuffer isn't populated with the perf fields. |
| EXPECT_CALL(mock_db, |
| WriteSiteCharacteristicsIntoDB(::testing::_, ::testing::_)) |
| .WillOnce(::testing::Invoke( |
| [](const url::Origin& origin, const SiteCharacteristicsProto& proto) { |
| ASSERT_FALSE(proto.has_load_time_estimates()); |
| })); |
| |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| local_site_data = nullptr; |
| ::testing::Mock::VerifyAndClear(&mock_db); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, |
| FlushingStateToProtoDoesntAffectData) { |
| // Create 2 DataImpl object and do the same operations on them, ensures that |
| // calling FlushStateToProto doesn't affect the data that gets recorded. |
| |
| auto local_site_data = |
| GetDataImpl(kDummyOrigin, &destroy_delegate_, &database_); |
| auto local_site_data_ref = |
| GetDataImpl(kDummyOrigin2, &destroy_delegate_, &database_); |
| |
| local_site_data->NotifySiteLoaded(); |
| local_site_data->NotifyLoadedSiteBackgrounded(); |
| local_site_data_ref->NotifySiteLoaded(); |
| local_site_data_ref->NotifyLoadedSiteBackgrounded(); |
| |
| test_clock_.Advance(base::TimeDelta::FromSeconds(15)); |
| local_site_data->FlushStateToProto(); |
| test_clock_.Advance(base::TimeDelta::FromSeconds(15)); |
| |
| local_site_data->NotifyUsesAudioInBackground(); |
| local_site_data_ref->NotifyUsesAudioInBackground(); |
| |
| local_site_data->FlushStateToProto(); |
| |
| EXPECT_EQ(local_site_data->FeatureObservationTimestamp( |
| local_site_data->site_characteristics_for_testing() |
| .uses_audio_in_background()), |
| local_site_data_ref->FeatureObservationTimestamp( |
| local_site_data_ref->site_characteristics_for_testing() |
| .uses_audio_in_background())); |
| |
| EXPECT_EQ(local_site_data->FeatureObservationDuration( |
| local_site_data->site_characteristics_for_testing() |
| .updates_title_in_background()), |
| local_site_data_ref->FeatureObservationDuration( |
| local_site_data_ref->site_characteristics_for_testing() |
| .updates_title_in_background())); |
| |
| local_site_data->NotifySiteUnloaded(TabVisibility::kBackground); |
| local_site_data_ref->NotifySiteUnloaded(TabVisibility::kBackground); |
| } |
| |
| TEST_F(LocalSiteCharacteristicsDataImplTest, DataLoadedCallbackInvoked) { |
| ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase> mock_db; |
| LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback |
| read_cb; |
| |
| auto local_site_data = GetDataImplAndInterceptReadCallback( |
| kDummyOrigin, &destroy_delegate_, &mock_db, &read_cb); |
| |
| EXPECT_FALSE(local_site_data->DataLoaded()); |
| |
| bool callback_invoked = false; |
| local_site_data->RegisterDataLoadedCallback( |
| base::BindLambdaForTesting([&]() { callback_invoked = true; })); |
| |
| // Run the callback to indicate that the initialization has completed. |
| base::Optional<SiteCharacteristicsProto> test_proto = |
| SiteCharacteristicsProto(); |
| std::move(read_cb).Run(test_proto); |
| |
| EXPECT_TRUE(callback_invoked); |
| EXPECT_TRUE(local_site_data->DataLoaded()); |
| } |
| |
| } // namespace internal |
| } // namespace resource_coordinator |