blob: b09925f638d786b1ccf6e4d5a6a19e8e40241a2d [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/segmentation_platform/internal/signals/ukm_observer.h"
#include <memory>
#include "base/test/task_environment.h"
#include "components/prefs/testing_pref_service.h"
#include "components/segmentation_platform/internal/constants.h"
#include "components/segmentation_platform/internal/database/mock_ukm_database.h"
#include "components/segmentation_platform/internal/signals/ukm_config.h"
#include "components/segmentation_platform/internal/signals/url_signal_handler.h"
#include "components/segmentation_platform/internal/ukm_data_manager_impl.h"
#include "components/segmentation_platform/public/local_state_helper.h"
#include "components/segmentation_platform/public/segmentation_platform_service.h"
#include "components/ukm/test_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace segmentation_platform {
namespace {
using testing::_;
using testing::Invoke;
using testing::UnorderedElementsAre;
using ukm::builders::PageLoad;
using ukm::builders::PaintPreviewCapture;
constexpr ukm::SourceId kSourceId = 10;
ukm::mojom::UkmEntryPtr GetSamplePageLoadEntry(
ukm::SourceId source_id = kSourceId) {
ukm::mojom::UkmEntryPtr entry = ukm::mojom::UkmEntry::New();
entry->source_id = source_id;
entry->event_hash = PageLoad::kEntryNameHash;
entry->metrics[PageLoad::kCpuTimeNameHash] = 10;
entry->metrics[PageLoad::kIsNewBookmarkNameHash] = 20;
entry->metrics[PageLoad::kIsNTPCustomLinkNameHash] = 30;
return entry;
}
ukm::mojom::UkmEntryPtr GetSamplePaintPreviewEntry(
ukm::SourceId source_id = kSourceId) {
ukm::mojom::UkmEntryPtr entry = ukm::mojom::UkmEntry::New();
entry->source_id = source_id;
entry->event_hash = PaintPreviewCapture::kEntryNameHash;
entry->metrics[PaintPreviewCapture::kBlinkCaptureTimeNameHash] = 5;
entry->metrics[PaintPreviewCapture::kCompressedOnDiskSizeNameHash] = 15;
return entry;
}
UkmEventHash TestEvent(uint64_t hash) {
return UkmEventHash::FromUnsafeValue(hash);
}
UkmMetricHash TestMetric(uint64_t hash) {
return UkmMetricHash::FromUnsafeValue(hash);
}
} // namespace
class UkmObserverTest : public testing::Test {
public:
UkmObserverTest() = default;
~UkmObserverTest() override = default;
void SetUp() override {
SegmentationPlatformService::RegisterLocalStatePrefs(prefs_.registry());
LocalStateHelper::GetInstance().Initialize(&prefs_);
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
InitializeUkmObserver({ukm::MSBB} /*consent_state*/);
}
void TearDown() override {
ukm_observer_.reset();
ukm_recorder_.reset();
}
void ExpectUkmEventFromRecorder(ukm::mojom::UkmEntryPtr entry) {
uint64_t event_hash = entry->event_hash;
base::RunLoop wait_for_record;
EXPECT_CALL(ukm_database(), StoreUkmEntry(_))
.WillOnce(
[&wait_for_record, &event_hash](ukm::mojom::UkmEntryPtr ukm_entry) {
EXPECT_EQ(ukm_entry->event_hash, event_hash);
wait_for_record.QuitClosure().Run();
});
ukm_recorder_->AddEntry(std::move(entry));
wait_for_record.Run();
}
void InitializeUkmObserver(ukm::UkmConsentState consent_state) {
ukm_observer_ = std::make_unique<UkmObserver>(ukm_recorder_.get());
ukm_observer_->OnUkmAllowedStateChanged(consent_state);
auto ukm_database = std::make_unique<MockUkmDatabase>();
ukm_database_ = ukm_database.get();
ukm_data_manager_ = std::make_unique<UkmDataManagerImpl>();
ukm_data_manager_->InitializeForTesting(std::move(ukm_database),
ukm_observer_.get());
}
UkmObserver& ukm_observer() { return *ukm_observer_; }
ukm::TestUkmRecorder& ukm_recorder() { return *ukm_recorder_; }
MockUkmDatabase& ukm_database() { return *ukm_database_; }
UkmDataManagerImpl& ukm_data_manager() { return *ukm_data_manager_; }
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<ukm::TestUkmRecorder> ukm_recorder_;
std::unique_ptr<UkmObserver> ukm_observer_;
raw_ptr<MockUkmDatabase, DanglingUntriaged> ukm_database_;
std::unique_ptr<UkmDataManagerImpl> ukm_data_manager_;
TestingPrefServiceSimple prefs_;
};
TEST_F(UkmObserverTest, EmptyConfig) {
UkmObserver& observer = ukm_observer();
// Empty config should not add anything to database.
observer.StartObserving(UkmConfig());
EXPECT_CALL(ukm_database(), StoreUkmEntry(_)).Times(0);
observer.OnEntryAdded(GetSamplePageLoadEntry());
observer.OnEntryAdded(GetSamplePaintPreviewEntry());
// Add page load config with no metrics.
UkmConfig config;
config.AddEvent(TestEvent(PageLoad::kEntryNameHash), {});
observer.StartObserving(UkmConfig());
observer.OnEntryAdded(GetSamplePageLoadEntry());
}
TEST_F(UkmObserverTest, FilterEventsAndMetrics) {
UkmObserver& observer = ukm_observer();
// Add PageLoad to the config.
UkmConfig config1;
config1.AddEvent(TestEvent(PageLoad::kEntryNameHash),
{TestMetric(PageLoad::kCpuTimeNameHash),
TestMetric(PageLoad::kNet_CacheBytes2NameHash),
TestMetric(PageLoad::kIsNewBookmarkNameHash)});
observer.StartObserving(config1);
EXPECT_CALL(ukm_database(), StoreUkmEntry(_))
.WillOnce(Invoke([](ukm::mojom::UkmEntryPtr entry) {
EXPECT_EQ(entry->event_hash, PageLoad::kEntryNameHash);
EXPECT_THAT(entry->metrics,
UnorderedElementsAre(
std::make_pair(PageLoad::kCpuTimeNameHash, 10),
std::make_pair(PageLoad::kIsNewBookmarkNameHash, 20)));
}));
observer.OnEntryAdded(GetSamplePaintPreviewEntry());
observer.OnEntryAdded(GetSamplePageLoadEntry());
// Add Paint preview config to the same observer, also add metrics with wrong
// event hashes, which should not match.
UkmConfig config2;
config2.AddEvent(
TestEvent(PaintPreviewCapture::kEntryNameHash),
{TestMetric(PaintPreviewCapture::kBlinkCaptureTimeNameHash)});
config2.AddEvent(
UkmEventHash(),
{TestMetric(PaintPreviewCapture::kCompressedOnDiskSizeNameHash)});
config2.AddEvent(
TestEvent(PageLoad::kEntryNameHash),
{TestMetric(PaintPreviewCapture::kCompressedOnDiskSizeNameHash)});
// In addition to already added metrics.
observer.StartObserving(config2);
EXPECT_CALL(ukm_database(), StoreUkmEntry(_))
.WillOnce(Invoke([](ukm::mojom::UkmEntryPtr entry) {
EXPECT_EQ(entry->event_hash, PaintPreviewCapture::kEntryNameHash);
EXPECT_THAT(entry->metrics,
UnorderedElementsAre(std::make_pair(
PaintPreviewCapture::kBlinkCaptureTimeNameHash, 5)));
}));
observer.OnEntryAdded(GetSamplePaintPreviewEntry());
EXPECT_CALL(ukm_database(), StoreUkmEntry(_))
.WillOnce(Invoke([](ukm::mojom::UkmEntryPtr entry) {
EXPECT_EQ(entry->event_hash, PageLoad::kEntryNameHash);
EXPECT_THAT(entry->metrics,
UnorderedElementsAre(
std::make_pair(PageLoad::kCpuTimeNameHash, 10),
std::make_pair(PageLoad::kIsNewBookmarkNameHash, 20)));
}));
observer.OnEntryAdded(GetSamplePageLoadEntry());
}
TEST_F(UkmObserverTest, PauseObservation) {
UkmObserver& observer = ukm_observer();
// Add PageLoad to the config.
UkmConfig config;
config.AddEvent(TestEvent(PageLoad::kEntryNameHash),
{TestMetric(PageLoad::kCpuTimeNameHash),
TestMetric(PageLoad::kNet_CacheBytes2NameHash),
TestMetric(PageLoad::kIsNewBookmarkNameHash)});
observer.StartObserving(config);
EXPECT_CALL(ukm_database(), StoreUkmEntry(_)).Times(0);
EXPECT_CALL(ukm_database(), UpdateUrlForUkmSource(_, _, _)).Times(0);
observer.PauseOrResumeObservation(true);
const GURL kUrl1("https://www.url1.com");
observer.OnEntryAdded(GetSamplePageLoadEntry());
observer.OnUpdateSourceURL(kSourceId, {kUrl1});
}
TEST_F(UkmObserverTest, ObservationFromRecorder) {
UkmObserver& observer = ukm_observer();
ukm::TestUkmRecorder& recorder = ukm_recorder();
// Add PageLoad to the config.
UkmConfig config;
config.AddEvent(TestEvent(PageLoad::kEntryNameHash),
{TestMetric(PageLoad::kCpuTimeNameHash),
TestMetric(PageLoad::kIsNewBookmarkNameHash)});
ukm_data_manager().StartObservingUkm(config);
ExpectUkmEventFromRecorder(GetSamplePageLoadEntry());
const GURL kUrl1("https://www.url1.com");
base::RunLoop wait_for_source;
EXPECT_CALL(ukm_database(),
UpdateUrlForUkmSource(kSourceId, kUrl1, /*is_validated=*/false))
.WillOnce([&wait_for_source](ukm::SourceId, const GURL&, bool) {
wait_for_source.QuitClosure().Run();
});
recorder.UpdateSourceURL(kSourceId, {kUrl1});
wait_for_source.Run();
// Update the Config to include more events.
UkmConfig config2;
config2.AddEvent(
TestEvent(PaintPreviewCapture::kEntryNameHash),
{TestMetric(PaintPreviewCapture::kBlinkCaptureTimeNameHash)});
observer.StartObserving(config2);
ExpectUkmEventFromRecorder(GetSamplePageLoadEntry());
ExpectUkmEventFromRecorder(GetSamplePaintPreviewEntry());
}
// Tests that the most recent time for UKM allowed state is correctly set and
// read.
TEST_F(UkmObserverTest, GetUkmMostRecentAllowedTime) {
LocalStateHelper& local_state_helper = LocalStateHelper::GetInstance();
// Without pref entry, the |is_ukm_allowed| param passed to UkmObserver ctor
// will determine the value to be returned.
EXPECT_LE(
local_state_helper.GetPrefTime(kSegmentationUkmMostRecentAllowedTimeKey),
base::Time::Now());
InitializeUkmObserver(ukm::UkmConsentState() /*consent_state*/);
EXPECT_EQ(base::Time::Max(), local_state_helper.GetPrefTime(
kSegmentationUkmMostRecentAllowedTimeKey));
ukm_observer().OnUkmAllowedStateChanged({ukm::MSBB});
EXPECT_LE(
local_state_helper.GetPrefTime(kSegmentationUkmMostRecentAllowedTimeKey),
base::Time::Now());
// Change the allowed state to false, the start time should now be set to
// Time::Max().
ukm_recorder().OnUkmAllowedStateChanged(ukm::UkmConsentState());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(base::Time::Max(), local_state_helper.GetPrefTime(
kSegmentationUkmMostRecentAllowedTimeKey));
// Change the allowed state to true, the new start time should be close to
// now.
base::Time now = base::Time::Now();
ukm_recorder().OnUkmAllowedStateChanged({ukm::MSBB});
base::RunLoop().RunUntilIdle();
EXPECT_LE(now, local_state_helper.GetPrefTime(
kSegmentationUkmMostRecentAllowedTimeKey));
EXPECT_LE(
local_state_helper.GetPrefTime(kSegmentationUkmMostRecentAllowedTimeKey),
base::Time::Now());
}
} // namespace segmentation_platform