blob: 1461d5bf2ba32186016f53aa346ae23499610d2c [file] [log] [blame]
// Copyright 2020 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/federated_learning/floc_id_provider_impl.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/federated_learning/floc_event_logger.h"
#include "chrome/browser/privacy_sandbox/privacy_sandbox_settings.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/federated_learning/features/features.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/policy/core/common/mock_policy_service.h"
#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/federated_learning/floc.mojom.h"
namespace federated_learning {
namespace {
using ComputeFlocResult = FlocIdProviderImpl::ComputeFlocResult;
using ComputeFlocCompletedCallback =
FlocIdProviderImpl::ComputeFlocCompletedCallback;
using CanComputeFlocCallback = FlocIdProviderImpl::CanComputeFlocCallback;
class MockFlocSortingLshService : public FlocSortingLshClustersService {
public:
using FlocSortingLshClustersService::FlocSortingLshClustersService;
using MappingFunction =
base::RepeatingCallback<base::Optional<uint64_t>(uint64_t)>;
// Configure the version and the mapping function and trigger the file-ready
// event. If |mapping_function| is not provided, it will map any input
// sim-hash to the same number.
void ConfigureSortingLsh(base::Version version,
MappingFunction mapping_function =
base::BindRepeating([](uint64_t sim_hash) {
return base::Optional<uint64_t>(sim_hash);
})) {
version_ = version;
mapping_function_ = mapping_function;
OnSortingLshClustersFileReady(base::FilePath(), version);
}
void ApplySortingLsh(uint64_t sim_hash,
ApplySortingLshCallback callback) override {
DCHECK(mapping_function_);
std::move(callback).Run(mapping_function_.Run(sim_hash), version_);
}
private:
base::Version version_;
MappingFunction mapping_function_;
};
class FakeCookieSettings : public content_settings::CookieSettings {
public:
using content_settings::CookieSettings::CookieSettings;
ContentSetting GetCookieSettingInternal(
const GURL& url,
const GURL& first_party_url,
bool is_third_party_request,
content_settings::SettingSource* source) const override {
return allow_cookies_internal_ ? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK;
}
bool ShouldBlockThirdPartyCookies() const override {
return should_block_third_party_cookies_;
}
void set_should_block_third_party_cookies(
bool should_block_third_party_cookies) {
should_block_third_party_cookies_ = should_block_third_party_cookies;
}
void set_allow_cookies_internal(bool allow_cookies_internal) {
allow_cookies_internal_ = allow_cookies_internal;
}
private:
~FakeCookieSettings() override = default;
bool should_block_third_party_cookies_ = false;
bool allow_cookies_internal_ = true;
};
class MockFlocIdProvider : public FlocIdProviderImpl {
public:
using FlocIdProviderImpl::FlocIdProviderImpl;
void OnComputeFlocCompleted(ComputeFlocResult result) override {
if (should_pause_before_compute_floc_completed_) {
DCHECK(!paused_);
paused_ = true;
paused_result_ = result;
return;
}
++compute_floc_completed_count_;
FlocIdProviderImpl::OnComputeFlocCompleted(result);
}
void ContinueLastOnComputeFlocCompleted() {
DCHECK(paused_);
paused_ = false;
++compute_floc_completed_count_;
FlocIdProviderImpl::OnComputeFlocCompleted(paused_result_);
}
void LogFlocComputedEvent(const ComputeFlocResult& result) override {
++log_event_count_;
last_log_event_result_ = result;
}
size_t compute_floc_completed_count() const {
return compute_floc_completed_count_;
}
void set_should_pause_before_compute_floc_completed(bool should_pause) {
should_pause_before_compute_floc_completed_ = should_pause;
}
ComputeFlocResult paused_result() const {
DCHECK(paused_);
return paused_result_;
}
size_t log_event_count() const { return log_event_count_; }
ComputeFlocResult last_log_event_result() const {
DCHECK_LT(0u, log_event_count_);
return last_log_event_result_;
}
private:
base::OnceCallback<void()> callback_before_compute_floc_completed_;
// Add the support to be able to pause on the OnComputeFlocCompleted
// execution and let it yield to other tasks posted to the same task runner.
bool should_pause_before_compute_floc_completed_ = false;
bool paused_ = false;
ComputeFlocResult paused_result_;
size_t compute_floc_completed_count_ = 0u;
size_t log_event_count_ = 0u;
ComputeFlocResult last_log_event_result_;
};
} // namespace
// Used to create a floc id with non-current finch_config_version or
// compute_time.
class FlocIdTester {
public:
static FlocId Create(base::Optional<uint64_t> id,
base::Time history_begin_time,
base::Time history_end_time,
uint32_t finch_config_version,
uint32_t sorting_lsh_version,
base::Time compute_time) {
return FlocId(id, history_begin_time, history_end_time,
finch_config_version, sorting_lsh_version, compute_time);
}
};
class FlocIdProviderUnitTest : public testing::Test {
public:
FlocIdProviderUnitTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~FlocIdProviderUnitTest() override = default;
void SetUp() override {
FlocId::RegisterPrefs(prefs_.registry());
privacy_sandbox::RegisterProfilePrefs(prefs_.registry());
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
content_settings::ContentSettingsRegistry::GetInstance()->ResetForTest();
content_settings::CookieSettings::RegisterProfilePrefs(prefs_.registry());
HostContentSettingsMap::RegisterProfilePrefs(prefs_.registry());
settings_map_ = new HostContentSettingsMap(
&prefs_, /*is_off_the_record=*/false, /*store_last_modified=*/false,
/*restore_session=*/false);
auto sorting_lsh_service = std::make_unique<MockFlocSortingLshService>();
sorting_lsh_service_ = sorting_lsh_service.get();
TestingBrowserProcess::GetGlobal()->SetFlocSortingLshClustersService(
std::move(sorting_lsh_service));
history_service_ = std::make_unique<history::HistoryService>();
history_service_->Init(
history::TestHistoryDatabaseParamsForPath(temp_dir_.GetPath()));
fake_cookie_settings_ = base::MakeRefCounted<FakeCookieSettings>(
settings_map_.get(), &prefs_, false, "chrome-extension");
privacy_sandbox_settings_ = std::make_unique<PrivacySandboxSettings>(
settings_map_.get(), fake_cookie_settings_.get(), &prefs_,
&mock_policy_service_,
/*sync_service=*/nullptr, /*identity_manager=*/nullptr);
task_environment_.RunUntilIdle();
}
void InitializeFlocIdProvider() {
floc_id_provider_ = std::make_unique<MockFlocIdProvider>(
&prefs_, privacy_sandbox_settings_.get(), history_service_.get(),
nullptr);
}
void InitializeFlocIdProviderAndSortingLsh(
base::Version version,
MockFlocSortingLshService::MappingFunction mapping_function =
base::BindRepeating([](uint64_t sim_hash) {
return base::Optional<uint64_t>(sim_hash);
})) {
InitializeFlocIdProvider();
sorting_lsh_service_->ConfigureSortingLsh(version, mapping_function);
}
void TearDown() override {
TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
settings_map_->ShutdownOnUIThread();
history_service_->RemoveObserver(floc_id_provider_.get());
}
void CheckCanComputeFloc(CanComputeFlocCallback callback) {
floc_id_provider_->CheckCanComputeFloc(std::move(callback));
}
void OnFlocDataAccessibleSinceUpdated() {
floc_id_provider_->OnFlocDataAccessibleSinceUpdated();
}
void OnURLsDeleted(history::HistoryService* history_service,
const history::DeletionInfo& deletion_info) {
floc_id_provider_->OnURLsDeleted(history_service, deletion_info);
}
void OnGetRecentlyVisitedURLsCompleted(history::QueryResults results) {
auto compute_floc_completed_callback =
base::BindOnce(&FlocIdProviderImpl::OnComputeFlocCompleted,
base::Unretained(floc_id_provider_.get()));
floc_id_provider_->OnGetRecentlyVisitedURLsCompleted(
std::move(compute_floc_completed_callback), std::move(results));
}
void AddHistoryEntriesForDomains(const std::vector<std::string>& domains,
base::Time time) {
history::HistoryAddPageArgs add_page_args;
add_page_args.time = time;
add_page_args.context_id = reinterpret_cast<history::ContextID>(1);
for (const std::string& domain : domains) {
static int nav_entry_id = 0;
++nav_entry_id;
add_page_args.url = GURL(base::StrCat({"https://www.", domain}));
add_page_args.nav_entry_id = nav_entry_id;
history_service_->AddPage(add_page_args);
history_service_->SetFlocAllowed(add_page_args.context_id, nav_entry_id,
add_page_args.url);
}
}
void ExpireHistoryBeforeUninclusive(base::Time end_time) {
base::CancelableTaskTracker tracker;
base::RunLoop run_loop;
history_service_->ExpireHistoryBetween(
/*restrict_urls=*/{}, /*begin_time=*/base::Time(), end_time,
/*user_initiated=*/true, run_loop.QuitClosure(), &tracker);
run_loop.Run();
}
FlocId floc_id() const { return floc_id_provider_->floc_id_; }
void set_floc_id(const FlocId& floc_id) {
floc_id_provider_->floc_id_ = floc_id;
}
bool floc_computation_in_progress() const {
return floc_id_provider_->floc_computation_in_progress_;
}
void set_floc_computation_in_progress(bool floc_computation_in_progress) {
floc_id_provider_->floc_computation_in_progress_ =
floc_computation_in_progress;
}
bool floc_computation_scheduled() const {
return floc_id_provider_->compute_floc_timer_.IsRunning();
}
bool need_recompute() { return floc_id_provider_->need_recompute_; }
protected:
base::test::ScopedFeatureList feature_list_;
content::BrowserTaskEnvironment task_environment_;
sync_preferences::TestingPrefServiceSyncable prefs_;
scoped_refptr<HostContentSettingsMap> settings_map_;
std::unique_ptr<history::HistoryService> history_service_;
scoped_refptr<FakeCookieSettings> fake_cookie_settings_;
testing::NiceMock<policy::MockPolicyService> mock_policy_service_;
std::unique_ptr<PrivacySandboxSettings> privacy_sandbox_settings_;
std::unique_ptr<MockFlocIdProvider> floc_id_provider_;
MockFlocSortingLshService* sorting_lsh_service_;
base::ScopedTempDir temp_dir_;
DISALLOW_COPY_AND_ASSIGN(FlocIdProviderUnitTest);
};
TEST_F(FlocIdProviderUnitTest, DefaultSetup_ComputationState) {
// Initializing the floc provider should not trigger an immediate computation,
// as the sorting-lsh file is not ready.
InitializeFlocIdProvider();
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_FALSE(floc_computation_scheduled());
// Configure the sorting-lsh service to to trigger the 1st floc computation.
sorting_lsh_service_->ConfigureSortingLsh(base::Version("2.0.0"));
EXPECT_TRUE(floc_computation_in_progress());
EXPECT_FALSE(floc_computation_scheduled());
// Finish any outstanding history queries.
task_environment_.RunUntilIdle();
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
// Advance the clock by 7 days. Expect another computation.
task_environment_.FastForwardBy(base::TimeDelta::FromDays(7));
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
EXPECT_EQ(2u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
}
TEST_F(FlocIdProviderUnitTest, DefaultSetup_BelowMinimumHistoryDomainSize) {
const base::Time kSevenDaysBeforeStart =
base::Time::Now() - base::TimeDelta::FromDays(7);
AddHistoryEntriesForDomains({"foo.com", "bar.com"}, kSevenDaysBeforeStart);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed with an invalid floc, due
// to insufficient history domains.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_FALSE(floc_id().IsValid());
}
TEST_F(FlocIdProviderUnitTest, DefaultSetup_MinimumHistoryDomainSize) {
const base::Time kSevenDaysBeforeStart =
base::Time::Now() - base::TimeDelta::FromDays(7);
AddHistoryEntriesForDomains({"foo.com", "bar.com", "baz.com"},
kSevenDaysBeforeStart);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed with the expected floc.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com", "bar.com", "baz.com"}),
kSevenDaysBeforeStart, kSevenDaysBeforeStart, 2),
floc_id());
}
TEST_F(FlocIdProviderUnitTest, DefaultSetup_ScheduledUpdateInterval) {
const base::Time kSevenDaysBeforeStart =
base::Time::Now() - base::TimeDelta::FromDays(7);
AddHistoryEntriesForDomains({"foo.com", "bar.com", "baz.com"},
kSevenDaysBeforeStart);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com", "bar.com", "baz.com"}),
kSevenDaysBeforeStart, kSevenDaysBeforeStart, 2),
floc_id());
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
// Advance the clock by 6 days.
task_environment_.FastForwardBy(base::TimeDelta::FromDays(6));
// Add 3 history entries with a new set of domains.
const base::Time kSixDaysAfterStart = base::Time::Now();
AddHistoryEntriesForDomains({"bar.com", "baz.com", "qux.com"},
kSixDaysAfterStart);
task_environment_.RunUntilIdle();
// Advance the clock by 23 hours. Expect no more computation, as the floc id
// refresh interval is 7 days.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(23));
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
// Advance the clock by 1 hour. Expect one more computation.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
EXPECT_EQ(2u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"bar.com", "baz.com", "qux.com"}),
kSixDaysAfterStart, kSixDaysAfterStart, 2),
floc_id());
}
class FlocIdProviderSimpleFeatureParamUnitTest : public FlocIdProviderUnitTest {
public:
FlocIdProviderSimpleFeatureParamUnitTest() {
feature_list_.Reset();
feature_list_.InitAndEnableFeatureWithParameters(
kFederatedLearningOfCohorts,
{{"update_interval", "24h"},
{"minimum_history_domain_size_required", "1"}});
}
};
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, QualifiedInitialHistory) {
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(7);
AddHistoryEntriesForDomains({"foo.com"}, kTime);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com"}), kTime, kTime, 2),
floc_id());
EXPECT_TRUE(floc_id_provider_->last_log_event_result().sim_hash_computed);
EXPECT_EQ(FlocId::SimHashHistory({"foo.com"}),
floc_id_provider_->last_log_event_result().sim_hash);
EXPECT_EQ(base::Time::Now(),
floc_id_provider_->last_log_event_result().floc_id.compute_time());
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
// Advance the clock by 1 day. Expect a computation, as there's no history in
// the last 7 days so the id has been reset to empty.
task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
EXPECT_EQ(2u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
EXPECT_FALSE(floc_id().IsValid());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, UnqualifiedInitialHistory) {
AddHistoryEntriesForDomains({"foo.com"},
base::Time::Now() - base::TimeDelta::FromDays(8));
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(6);
AddHistoryEntriesForDomains({"foo.com"}, kTime);
// Advance the clock by 23 hours. Expect no more computation, as the id
// refresh interval is 24 hours.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(23));
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
// Advance the clock by 1 hour. Expect one more computation, as the refresh
// time is reached and there's a valid history entry in the last 7 days.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
EXPECT_EQ(2u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com"}), kTime, kTime, 2),
floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
HistoryQueryBoundedByFlocAccessibleSince) {
const base::Time kStartTime = base::Time::Now();
const base::Time kSevenDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(7);
const base::Time kSixDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(6);
prefs_.SetTime(prefs::kPrivacySandboxFlocDataAccessibleSince,
kSixDaysBeforeStart);
AddHistoryEntriesForDomains({"foo.com"}, kSevenDaysBeforeStart);
AddHistoryEntriesForDomains({"bar.com"}, kSixDaysBeforeStart);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
// Expected that floc is calculated from only "bar.com".
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"bar.com"}), kSixDaysBeforeStart,
kSixDaysBeforeStart, 2),
floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
FlocAccessibleSinceViolationOnStartup) {
const base::Time kStartTime = base::Time::Now();
const base::Time kSevenDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(7);
const base::Time kSixDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(6);
const base::Time kTwelveHoursBeforeStart =
kStartTime - base::TimeDelta::FromHours(12);
FlocId floc_id_in_prefs_before_start =
FlocIdTester::Create(123, kSevenDaysBeforeStart, kSixDaysBeforeStart, 1,
0, kTwelveHoursBeforeStart);
floc_id_in_prefs_before_start.SaveToPrefs(&prefs_);
prefs_.SetTime(prefs::kPrivacySandboxFlocDataAccessibleSince,
kSixDaysBeforeStart);
AddHistoryEntriesForDomains({"foo.com"}, kSevenDaysBeforeStart);
AddHistoryEntriesForDomains({"bar.com"}, kSixDaysBeforeStart);
// Initializing the floc provider and sorting-lsh service should invalidate
// the previous floc but should not trigger an immediate computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
EXPECT_FALSE(floc_id().IsValid());
EXPECT_FALSE(FlocId::ReadFromPrefs(&prefs_).IsValid());
// Fast forward by 12 hours. This should trigger a scheduled update.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(12));
// Expect a completed computation and an update to the local prefs.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
EXPECT_EQ(floc_id(), FlocId(FlocId::SimHashHistory({"bar.com"}),
kSixDaysBeforeStart, kSixDaysBeforeStart, 2));
EXPECT_EQ(floc_id(), FlocId::ReadFromPrefs(&prefs_));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
HistoryDeleteAndScheduledUpdate) {
const base::Time kStartTime = base::Time::Now();
const base::Time kSevenDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(7);
const base::Time kSixDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(6);
AddHistoryEntriesForDomains({"foo.com"}, kSevenDaysBeforeStart);
AddHistoryEntriesForDomains({"bar.com"}, kSixDaysBeforeStart);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com", "bar.com"}),
kSevenDaysBeforeStart, kSixDaysBeforeStart, 2),
floc_id());
// Advance the clock by 12 hours. Expect no more computation.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(12));
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
// Expire the oldest history entry.
ExpireHistoryBeforeUninclusive(kSixDaysBeforeStart);
task_environment_.RunUntilIdle();
// Expect that the floc has been invalidated. Expect no more floc computation,
// but one more logging.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
EXPECT_FALSE(floc_id().IsValid());
// Advance the clock by 12 hours. Expect one more computation, which implies
// the timer didn't get reset due to the history invalidation. Expect that
// the floc is derived from "bar.com".
task_environment_.FastForwardBy(base::TimeDelta::FromHours(12));
EXPECT_EQ(2u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(3u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"bar.com"}), kSixDaysBeforeStart,
kSixDaysBeforeStart, 2),
floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, ScheduledUpdateSameFloc) {
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(2);
AddHistoryEntriesForDomains({"foo.com"}, kTime);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com"}), kTime, kTime, 2),
floc_id());
// Advance the clock by 1 day. Expect one more computation, but the floc
// didn't change.
task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
EXPECT_EQ(2u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com"}), kTime, kTime, 2),
floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
CheckCanComputeFloc_Default_Success) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
base::OnceCallback<void(bool)> cb = base::BindOnce(
[](bool can_compute_floc) { EXPECT_TRUE(can_compute_floc); });
CheckCanComputeFloc(std::move(cb));
task_environment_.RunUntilIdle();
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
CheckCanComputeFloc_Failure_BlockThirdPartyCookies) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
fake_cookie_settings_->set_should_block_third_party_cookies(true);
base::OnceCallback<void(bool)> cb = base::BindOnce(
[](bool can_compute_floc) { EXPECT_FALSE(can_compute_floc); });
CheckCanComputeFloc(std::move(cb));
task_environment_.RunUntilIdle();
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
OnFlocDataAccessibleSinceUpdated_TimeRangeNotFullyCovered) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
set_floc_id(FlocId(123, kTime1, kTime2, 2));
prefs_.SetTime(prefs::kPrivacySandboxFlocDataAccessibleSince, kTime2);
OnFlocDataAccessibleSinceUpdated();
EXPECT_FALSE(floc_id().IsValid());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
OnFlocDataAccessibleSinceUpdated_TimeRangeFullyCovered) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
set_floc_id(FlocId(123, kTime1, kTime2, 2));
prefs_.SetTime(prefs::kPrivacySandboxFlocDataAccessibleSince, kTime1);
OnFlocDataAccessibleSinceUpdated();
EXPECT_TRUE(floc_id().IsValid());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, HistoryDelete_AllHistory) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
set_floc_id(FlocId(123, kTime1, kTime2, 2));
OnURLsDeleted(history_service_.get(), history::DeletionInfo::ForAllHistory());
EXPECT_FALSE(floc_id().IsValid());
// Check the logged event for history-delete.
EXPECT_FALSE(floc_id_provider_->last_log_event_result().sim_hash_computed);
EXPECT_EQ(0u, floc_id_provider_->last_log_event_result().sim_hash);
EXPECT_EQ(base::Time::Now(),
floc_id_provider_->last_log_event_result().floc_id.compute_time());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
HistoryDelete_InvalidTimeRange) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
GURL url_a = GURL("https://a.test");
history::URLResult url_result(url_a, kTime1);
url_result.set_content_annotations(
{history::VisitContentAnnotationFlag::kFlocEligibleRelaxed,
/*model_annotations=*/{}});
history::QueryResults query_results;
query_results.SetURLResults({url_result});
const FlocId expected_floc =
FlocId(FlocId::SimHashHistory({"a.test"}), kTime1, kTime2, 2);
set_floc_id(expected_floc);
OnURLsDeleted(history_service_.get(),
history::DeletionInfo::ForUrls(
{history::URLResult(url_a, kTime1)}, /*favicon_urls=*/{}));
EXPECT_EQ(expected_floc, floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
HistoryDelete_TimeRangeNoOverlap) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
const base::Time kTime3 = base::Time::FromTimeT(3);
const base::Time kTime4 = base::Time::FromTimeT(4);
const FlocId expected_floc =
FlocId(FlocId::SimHashHistory({"a.test"}), kTime1, kTime2, 2);
set_floc_id(expected_floc);
history::DeletionInfo deletion_info(
history::DeletionTimeRange(kTime3, kTime4),
/*is_from_expiration=*/false, /*deleted_rows=*/{}, /*favicon_urls=*/{},
/*restrict_urls=*/base::nullopt);
OnURLsDeleted(history_service_.get(), deletion_info);
EXPECT_EQ(expected_floc, floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
HistoryDelete_TimeRangePartialOverlap) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
const base::Time kTime3 = base::Time::FromTimeT(3);
const FlocId expected_floc =
FlocId(FlocId::SimHashHistory({"a.test"}), kTime1, kTime2, 2);
set_floc_id(expected_floc);
history::DeletionInfo deletion_info(
history::DeletionTimeRange(kTime2, kTime3),
/*is_from_expiration=*/false, /*deleted_rows=*/{}, /*favicon_urls=*/{},
/*restrict_urls=*/base::nullopt);
OnURLsDeleted(history_service_.get(), deletion_info);
EXPECT_FALSE(floc_id().IsValid());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
HistoryDelete_TimeRangeFullOverlap) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
const FlocId expected_floc =
FlocId(FlocId::SimHashHistory({"a.test"}), kTime1, kTime2, 2);
set_floc_id(expected_floc);
history::DeletionInfo deletion_info(
history::DeletionTimeRange(kTime1, kTime2),
/*is_from_expiration=*/false, /*deleted_rows=*/{}, /*favicon_urls=*/{},
/*restrict_urls=*/base::nullopt);
OnURLsDeleted(history_service_.get(), deletion_info);
EXPECT_FALSE(floc_id().IsValid());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, FlocIneligibleHistoryEntries) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
history::QueryResults query_results;
query_results.SetURLResults(
{history::URLResult(GURL("https://a.test"),
base::Time::Now() - base::TimeDelta::FromDays(1))});
set_floc_computation_in_progress(true);
OnGetRecentlyVisitedURLsCompleted(std::move(query_results));
EXPECT_FALSE(floc_id().IsValid());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, MultipleHistoryEntries) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
const base::Time kTime3 = base::Time::FromTimeT(3);
history::URLResult url_result_a(GURL("https://a.test"), kTime1);
url_result_a.set_content_annotations(
{history::VisitContentAnnotationFlag::kFlocEligibleRelaxed,
/*model_annotations=*/{}});
history::URLResult url_result_b(GURL("https://b.test"), kTime2);
url_result_b.set_content_annotations(
{history::VisitContentAnnotationFlag::kFlocEligibleRelaxed,
/*model_annotations=*/{}});
history::URLResult url_result_c(GURL("https://c.test"), kTime3);
std::vector<history::URLResult> url_results{url_result_a, url_result_b,
url_result_c};
history::QueryResults query_results;
query_results.SetURLResults(std::move(url_results));
set_floc_computation_in_progress(true);
OnGetRecentlyVisitedURLsCompleted(std::move(query_results));
EXPECT_EQ(
FlocId(FlocId::SimHashHistory({"a.test", "b.test"}), kTime1, kTime2, 2),
floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
MaybeRecordFlocToUkmMethod_FilteredRecording) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
InitializeFlocIdProvider();
floc_id_provider_->MaybeRecordFlocToUkm(1);
// Initially the |need_ukm_recording_| is false. The recording attempt should
// have been filtered. Expect no events.
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::FlocPageLoad::kEntryName);
EXPECT_EQ(0u, entries.size());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
MaybeRecordFlocToUkmMethod_RecordInvalidFloc) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
floc_id_provider_->OnComputeFlocCompleted(ComputeFlocResult());
floc_id_provider_->MaybeRecordFlocToUkm(1);
// Expect an event with a missing metric, meaning the floc id is invalid.
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::FlocPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
EXPECT_FALSE(ukm_recorder.EntryHasMetric(
entries.back(), ukm::builders::FlocPageLoad::kFlocIdName));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
MaybeRecordFlocToUkmMethod_RecordValidFloc) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
floc_id_provider_->OnComputeFlocCompleted(ComputeFlocResult(
/*sim_hash=*/123, FlocIdTester::Create(123, base::Time::FromTimeT(4),
base::Time::FromTimeT(5), 6, 7,
base::Time::FromTimeT(8))));
floc_id_provider_->MaybeRecordFlocToUkm(1);
// Expect an event with a metric having the expected floc value.
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::FlocPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntryMetric(entries.back(),
ukm::builders::FlocPageLoad::kFlocIdName,
/*expected_value=*/123);
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
MaybeRecordFlocToUkmMethod_MultipleRecordings) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
floc_id_provider_->OnComputeFlocCompleted(ComputeFlocResult(
/*sim_hash=*/123, FlocIdTester::Create(123, base::Time::FromTimeT(4),
base::Time::FromTimeT(5), 6, 7,
base::Time::FromTimeT(8))));
floc_id_provider_->MaybeRecordFlocToUkm(1);
auto entries =
ukm_recorder.GetEntriesByName(ukm::builders::FlocPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
// Subsequent recoding attempt will be filtered as the last recording has set
// |need_ukm_recording_| to false.
floc_id_provider_->MaybeRecordFlocToUkm(1);
entries =
ukm_recorder.GetEntriesByName(ukm::builders::FlocPageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
// Trigger a new floc computation completion.
set_floc_computation_in_progress(true);
floc_id_provider_->OnComputeFlocCompleted(ComputeFlocResult(
/*sim_hash=*/456, FlocIdTester::Create(456, base::Time::FromTimeT(4),
base::Time::FromTimeT(5), 6, 7,
base::Time::FromTimeT(8))));
floc_id_provider_->MaybeRecordFlocToUkm(1);
// The new recording attempt should have succeeded.
entries =
ukm_recorder.GetEntriesByName(ukm::builders::FlocPageLoad::kEntryName);
EXPECT_EQ(2u, entries.size());
ukm_recorder.ExpectEntryMetric(entries.back(),
ukm::builders::FlocPageLoad::kFlocIdName,
/*expected_value=*/456);
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
GetInterestCohortForJsApiMethod) {
InitializeFlocIdProviderAndSortingLsh(base::Version("999.0.0"));
task_environment_.RunUntilIdle();
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(1);
const FlocId expected_floc = FlocId(123, kTime, kTime, 999);
set_floc_id(expected_floc);
EXPECT_EQ(expected_floc.ToInterestCohortForJsApi(),
floc_id_provider_->GetInterestCohortForJsApi(
/*requesting_origin=*/{}, /*site_for_cookies=*/{}));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
GetInterestCohortForJsApiMethod_ThirdPartyCookiesDisabled) {
InitializeFlocIdProviderAndSortingLsh(base::Version("999.0.0"));
task_environment_.RunUntilIdle();
fake_cookie_settings_->set_should_block_third_party_cookies(true);
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(1);
set_floc_id(FlocId(123, kTime, kTime, 999));
EXPECT_EQ(blink::mojom::InterestCohort::New(),
floc_id_provider_->GetInterestCohortForJsApi(
/*requesting_origin=*/{}, /*site_for_cookies=*/{}));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
GetInterestCohortForJsApiMethod_CookiesContentSettingsDisallowed) {
InitializeFlocIdProviderAndSortingLsh(base::Version("999.0.0"));
task_environment_.RunUntilIdle();
fake_cookie_settings_->set_allow_cookies_internal(false);
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(1);
set_floc_id(FlocId(123, kTime, kTime, 999));
EXPECT_EQ(blink::mojom::InterestCohort::New(),
floc_id_provider_->GetInterestCohortForJsApi(
/*requesting_origin=*/{}, /*site_for_cookies=*/{}));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
GetInterestCohortForJsApiMethod_FlocUnavailable) {
InitializeFlocIdProviderAndSortingLsh(base::Version("2.0.0"));
task_environment_.RunUntilIdle();
EXPECT_EQ(blink::mojom::InterestCohort::New(),
floc_id_provider_->GetInterestCohortForJsApi(
/*requesting_origin=*/{}, /*site_for_cookies=*/{}));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
HistoryDeleteDuringInProgressComputation) {
const base::Time kStartTime = base::Time::Now();
const base::Time kSevenDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(7);
const base::Time kSixDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(6);
const base::Time kFiveDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(5);
AddHistoryEntriesForDomains({"foo.com"}, kSevenDaysBeforeStart);
AddHistoryEntriesForDomains({"bar.com"}, kSixDaysBeforeStart);
AddHistoryEntriesForDomains({"baz.com"}, kFiveDaysBeforeStart);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("999.0.0"));
task_environment_.RunUntilIdle();
// Expect that the 1st computation has completed.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_TRUE(floc_id().IsValid());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com", "bar.com", "baz.com"}),
kSevenDaysBeforeStart, kFiveDaysBeforeStart, 999),
floc_id());
base::Time time_before_advancing = base::Time::Now();
// Advance the clock by 1 day. The "foo.com" should expire. However, we pause
// before the computation completes.
floc_id_provider_->set_should_pause_before_compute_floc_completed(true);
task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
EXPECT_TRUE(floc_computation_in_progress());
EXPECT_FALSE(need_recompute());
EXPECT_EQ(FlocIdTester::Create(
FlocId::SimHashHistory({"foo.com", "bar.com", "baz.com"}),
kSevenDaysBeforeStart, kFiveDaysBeforeStart, 1, 999,
time_before_advancing),
floc_id());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"bar.com", "baz.com"}),
kSixDaysBeforeStart, kFiveDaysBeforeStart, 999),
floc_id_provider_->paused_result().floc_id);
// Expire the "bar.com" history entry right before the floc computation
// completes. Since the computation is still considered to be in-progress, we
// will recompute right after this computation completes.
ExpireHistoryBeforeUninclusive(kFiveDaysBeforeStart);
EXPECT_TRUE(need_recompute());
floc_id_provider_->set_should_pause_before_compute_floc_completed(false);
floc_id_provider_->ContinueLastOnComputeFlocCompleted();
task_environment_.RunUntilIdle();
// Expect 2 more compute completion events and 1 more log event. This is
// because we won't send log event if there's a recompute event scheduled.
// The compute trigger should be the original trigger (i.e. kScheduledUpdate),
// rather than kHistoryDelete.
EXPECT_EQ(3u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
EXPECT_FALSE(need_recompute());
// The final floc should be derived from "baz.com".
EXPECT_TRUE(floc_id().IsValid());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"baz.com"}), kFiveDaysBeforeStart,
kFiveDaysBeforeStart, 999),
floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, NonDefaultSortingLshMapping) {
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(7);
AddHistoryEntriesForDomains({"foo.com"}, kTime);
// Map the sim-hash to 2
InitializeFlocIdProviderAndSortingLsh(
base::Version("99.0.0"), base::BindRepeating([](uint64_t sim_hash) {
if (sim_hash == FlocId::SimHashHistory({"foo.com"}))
return base::Optional<uint64_t>(2);
return base::Optional<uint64_t>();
}));
task_environment_.RunUntilIdle();
EXPECT_EQ(FlocId(2, kTime, kTime, 99), floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
NonDefaultSortingLshMapping_Blocked) {
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(7);
AddHistoryEntriesForDomains({"foo.com"}, kTime);
// Block the sim-hash.
InitializeFlocIdProviderAndSortingLsh(
base::Version("999.0.0"), base::BindRepeating([](uint64_t sim_hash) {
return base::Optional<uint64_t>();
}));
task_environment_.RunUntilIdle();
EXPECT_FALSE(floc_id().IsValid());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest, MultipleSortingLshUpdate) {
const base::Time kTime = base::Time::Now() - base::TimeDelta::FromDays(1);
AddHistoryEntriesForDomains({"foo.com"}, kTime);
// Initializing the floc provider and sorting-lsh service should trigger the
// 1st floc computation.
InitializeFlocIdProviderAndSortingLsh(base::Version("99.0.0"));
task_environment_.RunUntilIdle();
// Expect a computation. The floc should be equal to the sim-hash.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(1u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(FlocId::SimHashHistory({"foo.com"}), kTime, kTime, 99),
floc_id());
// Configure the |sorting_lsh_service_| to block any input sim-hash.
sorting_lsh_service_->ConfigureSortingLsh(
base::Version("3.4.5"), base::BindRepeating([](uint64_t sim_hash) {
return base::Optional<uint64_t>();
}));
task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
// Expect one more computation, where the result contains a valid sim_hash and
// an invalid floc_id, as it was blocked. The internal floc is set to the
// invalid one.
EXPECT_EQ(2u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(2u, floc_id_provider_->log_event_count());
EXPECT_TRUE(floc_id_provider_->last_log_event_result().sim_hash_computed);
EXPECT_EQ(floc_id_provider_->last_log_event_result().sim_hash,
FlocId::SimHashHistory({"foo.com"}));
EXPECT_FALSE(floc_id_provider_->last_log_event_result().floc_id.IsValid());
EXPECT_FALSE(floc_id().IsValid());
// Configure the |sorting_lsh_service_| to map sim-hash to 6789.
sorting_lsh_service_->ConfigureSortingLsh(
base::Version("999.0"), base::BindRepeating([](uint64_t sim_hash) {
if (sim_hash == FlocId::SimHashHistory({"foo.com"}))
return base::Optional<uint64_t>(6789);
return base::Optional<uint64_t>();
}));
task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
// Expect one more computation. The floc should be equal to 6789.
EXPECT_EQ(3u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(3u, floc_id_provider_->log_event_count());
EXPECT_EQ(FlocId(6789, kTime, kTime, 999), floc_id());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
LastFlocUnexpired_NextScheduledUpdate) {
// Setups before session start.
const base::Time kStartTime = base::Time::Now();
const base::Time kFourDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(4);
const base::Time kThreeDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(3);
const base::Time kLastComputeTime =
kStartTime - base::TimeDelta::FromHours(12);
FlocId floc_id_in_prefs_before_start =
FlocIdTester::Create(123, kFourDaysBeforeStart, kThreeDaysBeforeStart, 1,
999, kLastComputeTime);
floc_id_in_prefs_before_start.SaveToPrefs(&prefs_);
AddHistoryEntriesForDomains({"domain1.com"}, kFourDaysBeforeStart);
AddHistoryEntriesForDomains({"domain2.com"}, kThreeDaysBeforeStart);
// Start of session.
InitializeFlocIdProvider();
// Initially the floc is set to the entry from the prefs. No computation has
// occurred for this session.
EXPECT_EQ(floc_id(), floc_id_in_prefs_before_start);
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
EXPECT_EQ(0u, floc_id_provider_->compute_floc_completed_count());
// Finish any outstanding history queries.
task_environment_.RunUntilIdle();
// Expect that the floc prefs hasn't changed at this stage.
EXPECT_EQ(floc_id(), FlocId::ReadFromPrefs(&prefs_));
// Set up the sorting-lsh service so that the next computation will compute a
// valid floc.
sorting_lsh_service_->ConfigureSortingLsh(base::Version("99.0"));
// Fast forward by 12 hours. This should trigger a scheduled update.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(12));
// Expect a completed computation and an update to the local prefs.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(floc_id(),
FlocId(FlocId::SimHashHistory({"domain1.com", "domain2.com"}),
kFourDaysBeforeStart, kThreeDaysBeforeStart, 99));
EXPECT_EQ(floc_id(), FlocId::ReadFromPrefs(&prefs_));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
LastFlocUnexpired_HistoryDelete) {
// Setups before session start.
const base::Time kStartTime = base::Time::Now();
const base::Time kFourDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(4);
const base::Time kThreeDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(3);
const base::Time kLastComputeTime =
kStartTime - base::TimeDelta::FromHours(12);
FlocId floc_id_in_prefs_before_start =
FlocIdTester::Create(123, kFourDaysBeforeStart, kThreeDaysBeforeStart, 1,
999, kLastComputeTime);
floc_id_in_prefs_before_start.SaveToPrefs(&prefs_);
AddHistoryEntriesForDomains({"domain1.com"}, kFourDaysBeforeStart);
AddHistoryEntriesForDomains({"domain2.com"}, kThreeDaysBeforeStart);
// Start of session.
InitializeFlocIdProvider();
// Initially the floc is set to the entry from the prefs. No computation has
// occurred for this session.
EXPECT_EQ(floc_id(), floc_id_in_prefs_before_start);
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
EXPECT_EQ(0u, floc_id_provider_->compute_floc_completed_count());
// Expire all previous history.
ExpireHistoryBeforeUninclusive(base::Time::Now());
// Expect no explicit recompute, but the floc has been invalidated and is
// written to the local prefs. The last floc compute time in the pref hasn't
// changed.
EXPECT_EQ(0u, floc_id_provider_->compute_floc_completed_count());
EXPECT_FALSE(floc_id().IsValid());
FlocId floc_id_in_prefs = FlocId::ReadFromPrefs(&prefs_);
EXPECT_FALSE(floc_id_in_prefs.IsValid());
EXPECT_EQ(kLastComputeTime, floc_id_in_prefs.compute_time());
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
LastFlocExpired_ImmediateCompute) {
// Setups before session start.
const base::Time kStartTime = base::Time::Now();
const base::Time kTwentyDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(20);
const base::Time kNineteenDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(19);
const base::Time kTwoDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(2);
const base::Time kLastComputeTime =
kStartTime - base::TimeDelta::FromHours(25);
FlocId floc_id_in_prefs_before_start =
FlocIdTester::Create(123, kTwentyDaysBeforeStart,
kNineteenDaysBeforeStart, 1, 888, kLastComputeTime);
floc_id_in_prefs_before_start.SaveToPrefs(&prefs_);
AddHistoryEntriesForDomains({"domain1.com"}, kTwentyDaysBeforeStart);
AddHistoryEntriesForDomains({"domain2.com"}, kNineteenDaysBeforeStart);
AddHistoryEntriesForDomains({"foo.com"}, kTwoDaysBeforeStart);
// Start of session.
InitializeFlocIdProvider();
FlocId initial_invalid_floc_id =
FlocIdTester::Create(base::nullopt, kTwentyDaysBeforeStart,
kNineteenDaysBeforeStart, 1, 888, kLastComputeTime);
// Initially the floc is invalidated as the last floc has expired, but other
// fields remains unchanged. The invalidation is also written to the prefs.
// Expect no immediate computation as the sorting-lsh file is not ready.
EXPECT_EQ(floc_id(), initial_invalid_floc_id);
EXPECT_EQ(FlocId::ReadFromPrefs(&prefs_), initial_invalid_floc_id);
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_FALSE(floc_computation_scheduled());
EXPECT_EQ(0u, floc_id_provider_->compute_floc_completed_count());
// Set up the sorting-lsh service to trigger the 1st floc computation.
sorting_lsh_service_->ConfigureSortingLsh(base::Version("99.0"));
task_environment_.RunUntilIdle();
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
// Expect a completed computation and an update to the local prefs.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(floc_id(), FlocId(FlocId::SimHashHistory({"foo.com"}),
kTwoDaysBeforeStart, kTwoDaysBeforeStart, 99));
EXPECT_EQ(floc_id(), FlocId::ReadFromPrefs(&prefs_));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
NextComputeDelayTooBig_ImmediateCompute) {
// Setups before session start.
const base::Time kStartTime = base::Time::Now();
const base::Time kFourDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(4);
const base::Time kThreeDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(3);
const base::Time kTwoDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(2);
const base::Time kLastComputeTime = kStartTime + base::TimeDelta::FromDays(1);
// Configure the last compute time to be 1 day after the start time, that
// emulates the situation when the machine time has changed.
FlocId floc_id_in_prefs_before_start =
FlocIdTester::Create(123, kFourDaysBeforeStart, kThreeDaysBeforeStart, 1,
999, kLastComputeTime);
floc_id_in_prefs_before_start.SaveToPrefs(&prefs_);
AddHistoryEntriesForDomains({"foo.com"}, kTwoDaysBeforeStart);
// Start of session.
InitializeFlocIdProvider();
FlocId initial_invalid_floc_id =
FlocIdTester::Create(base::nullopt, kFourDaysBeforeStart,
kThreeDaysBeforeStart, 1, 999, kLastComputeTime);
// Initially the floc is invalidated as the "presumed next computation delay"
// >= "2 x the scheduled update interval", implying the machine time has
// changed. Other fields should remain unchanged. The invalidation is also
// written to the prefs. Expect no immediate computation as the sorting-lsh
// file is not ready.
EXPECT_EQ(floc_id(), initial_invalid_floc_id);
EXPECT_EQ(FlocId::ReadFromPrefs(&prefs_), initial_invalid_floc_id);
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_FALSE(floc_computation_scheduled());
EXPECT_EQ(0u, floc_id_provider_->compute_floc_completed_count());
// Set up the sorting-lsh service to trigger the 1st floc computation.
sorting_lsh_service_->ConfigureSortingLsh(base::Version("99.0"));
task_environment_.RunUntilIdle();
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
// Expect a completed computation and an update to the local prefs.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(floc_id(), FlocId(FlocId::SimHashHistory({"foo.com"}),
kTwoDaysBeforeStart, kTwoDaysBeforeStart, 99));
EXPECT_EQ(floc_id(), FlocId::ReadFromPrefs(&prefs_));
}
TEST_F(FlocIdProviderSimpleFeatureParamUnitTest,
LastFlocVersionMismatch_ImmediateCompute) {
// Setups before session start.
const base::Time kStartTime = base::Time::Now();
const base::Time kFourDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(4);
const base::Time kThreeDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(3);
const base::Time kTwoDaysBeforeStart =
kStartTime - base::TimeDelta::FromDays(2);
const base::Time kLastComputeTime =
kStartTime - base::TimeDelta::FromHours(12);
// Configure a floc with version finch_config_version 0, that is different
// from the current version 1.
FlocId floc_id_in_prefs_before_start =
FlocIdTester::Create(123, kFourDaysBeforeStart, kThreeDaysBeforeStart, 0,
999, kLastComputeTime);
floc_id_in_prefs_before_start.SaveToPrefs(&prefs_);
AddHistoryEntriesForDomains({"foo.com"}, kTwoDaysBeforeStart);
// Start of session.
InitializeFlocIdProvider();
FlocId initial_invalid_floc_id =
FlocIdTester::Create(base::nullopt, kFourDaysBeforeStart,
kThreeDaysBeforeStart, 0, 999, kLastComputeTime);
// Initially the floc is invalidated as the version mismatches, but other
// fields remains unchanged. The invalidation is also written to the prefs.
// Expect no immediate computation as the sorting-lsh file is not ready.
EXPECT_EQ(floc_id(), initial_invalid_floc_id);
EXPECT_EQ(FlocId::ReadFromPrefs(&prefs_), initial_invalid_floc_id);
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_FALSE(floc_computation_scheduled());
EXPECT_EQ(0u, floc_id_provider_->compute_floc_completed_count());
// Set up the sorting-lsh service to trigger the 1st floc computation.
sorting_lsh_service_->ConfigureSortingLsh(base::Version("99.0"));
task_environment_.RunUntilIdle();
EXPECT_FALSE(floc_computation_in_progress());
EXPECT_TRUE(floc_computation_scheduled());
// Expect a completed computation and an update to the local prefs.
EXPECT_EQ(1u, floc_id_provider_->compute_floc_completed_count());
EXPECT_EQ(floc_id(), FlocId(FlocId::SimHashHistory({"foo.com"}),
kTwoDaysBeforeStart, kTwoDaysBeforeStart, 99));
EXPECT_EQ(floc_id(), FlocId::ReadFromPrefs(&prefs_));
}
} // namespace federated_learning