blob: 159be8321afdef94b893ff8326dcc4d150ecb681 [file] [log] [blame]
// 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