blob: 7ad575f50b5e0ac7e1eb272a6f839aaf139cab44 [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/privacy_budget/identifiability_study_state.h"
#include <math.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/containers/flat_tree.h"
#include "chrome/browser/privacy_budget/inspectable_identifiability_study_state.h"
#include "chrome/browser/privacy_budget/privacy_budget_prefs.h"
#include "chrome/common/privacy_budget/field_trial_param_conversions.h"
#include "chrome/common/privacy_budget/privacy_budget_features.h"
#include "chrome/common/privacy_budget/scoped_privacy_budget_config.h"
#include "chrome/common/privacy_budget/types.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
namespace {
// Matcher that takes an IdentifiableSurface() as a parameter and checks if the
// argument is the corresponding RepresentativeSurface.
//
// Use as:
//
// const auto kSurface = blink::IdentifiableSurface::(...);
// const auto RepresentativeSurface representative_surface(...);
//
// EXPECT_THAT(representative_surface, Represents(kSurface));
MATCHER_P(Represents, surface, "") {
return arg.value() == surface;
}
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
using ::testing::Not;
using ::testing::UnorderedElementsAre;
// Constants used to set up the test configuration.
constexpr int kTestingStudyGeneration = 58;
constexpr auto kRegularTypeId =
blink::IdentifiableSurface::Type::kGenericFontLookup;
constexpr auto kBlockedSurface1 =
blink::IdentifiableSurface::FromTypeAndToken(kRegularTypeId, 1);
constexpr auto kBlockedType1 =
blink::IdentifiableSurface::Type::kCanvasReadback;
constexpr auto kTestingActiveSurfaceBudget = 40u;
constexpr auto kTestingExpectedSurfaceCount = 1u;
// Sample surfaces. "Regular" means that the surface is not blocked and its type
// is not blocked.
constexpr auto kRegularSurface1 =
blink::IdentifiableSurface::FromTypeAndToken(kRegularTypeId, 3);
constexpr auto kRegularSurface2 =
blink::IdentifiableSurface::FromTypeAndToken(kRegularTypeId, 4);
constexpr auto kRegularSurface3 =
blink::IdentifiableSurface::FromTypeAndToken(kRegularTypeId, 5);
constexpr auto kBlockedTypeSurface1 =
blink::IdentifiableSurface::FromTypeAndToken(kBlockedType1, 1); // = 258
// Invokes ShouldRecordSurface() on `state` for `surface_count` distinct
// surfaces. All the surfaces belong to the type kRegularTypeId with inputs
// in [0, surface_count).
int SimulateSurfaceSelectionRound(IdentifiabilityStudyState& state,
int surface_count) {
int selected_surface_count = 0;
for (int i = 0; i < surface_count; ++i) {
auto surface =
blink::IdentifiableSurface::FromTypeAndToken(kRegularTypeId, i);
if (state.ShouldRecordSurface(surface))
++selected_surface_count;
}
return selected_surface_count;
}
// Creates a list of distinct IdentifiableSurfaces with type kWebFeature and
// inputs in the range [offset, offset+count).
IdentifiableSurfaceList CreateSurfaceList(size_t count, size_t offset = 0) {
IdentifiableSurfaceList list;
list.reserve(count);
for (auto token = 0u; token < count; ++token) {
list.push_back(blink::IdentifiableSurface::FromTypeAndToken(
blink::IdentifiableSurface::Type::kWebFeature, token + offset));
}
return list;
}
} // namespace
class IdentifiabilityStudyStateTest : public ::testing::Test {
public:
IdentifiabilityStudyStateTest() {
config_parameters_.generation = kTestingStudyGeneration;
config_parameters_.blocked_surfaces.push_back(kBlockedSurface1);
config_parameters_.blocked_types.push_back(kBlockedType1);
config_parameters_.active_surface_budget = kTestingActiveSurfaceBudget;
config_parameters_.expected_surface_count = kTestingExpectedSurfaceCount;
config_.Apply(config_parameters_);
prefs::RegisterPrivacyBudgetPrefs(pref_service_.registry());
pref_service()->SetInteger(prefs::kPrivacyBudgetGeneration,
kTestingStudyGeneration);
}
TestingPrefServiceSimple* pref_service() { return &pref_service_; }
private:
TestingPrefServiceSimple pref_service_;
test::ScopedPrivacyBudgetConfig::Parameters config_parameters_;
test::ScopedPrivacyBudgetConfig config_;
};
TEST(IdentifiabilityStudyStateStandaloneTest, InstantiateAndInitialize) {
test::ScopedPrivacyBudgetConfig::Parameters parameters;
parameters.enabled = true;
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
// Default is zero.
ASSERT_EQ(0, pref_service.GetInteger(prefs::kPrivacyBudgetGeneration));
auto settings = std::make_unique<IdentifiabilityStudyState>(&pref_service);
// Successful initialization should result in setting the generation number.
EXPECT_EQ(parameters.generation,
pref_service.GetInteger(prefs::kPrivacyBudgetGeneration));
// There should be at least one offset selected.
EXPECT_FALSE(
pref_service.GetString(prefs::kPrivacyBudgetSelectedOffsets).empty());
// But there should be no seen surfaces.
EXPECT_TRUE(
pref_service.GetString(prefs::kPrivacyBudgetSeenSurfaces).empty());
}
TEST_F(IdentifiabilityStudyStateTest, ReInitializesWhenGenerationChanges) {
pref_service()->SetInteger(prefs::kPrivacyBudgetGeneration,
kTestingStudyGeneration - 1);
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(IdentifiableSurfaceList{
blink::IdentifiableSurface::FromMetricHash(4),
blink::IdentifiableSurface::FromMetricHash(5),
blink::IdentifiableSurface::FromMetricHash(6)}));
auto settings = std::make_unique<IdentifiabilityStudyState>(pref_service());
EXPECT_EQ(kTestingStudyGeneration,
pref_service()->GetInteger(prefs::kPrivacyBudgetGeneration));
EXPECT_THAT(pref_service()->GetString(prefs::kPrivacyBudgetSelectedOffsets),
Not(IsEmpty()));
EXPECT_THAT(pref_service()->GetString(prefs::kPrivacyBudgetSeenSurfaces),
IsEmpty());
}
TEST_F(IdentifiabilityStudyStateTest,
LoadsSeenSurfacesAndSelectedOffsetsFromPrefs) {
auto kSurfaces = IdentifiableSurfaceList{kRegularSurface1, kRegularSurface2};
pref_service()->SetString(prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(kSurfaces));
auto kOffsets = std::vector<IdentifiabilityStudyState::OffsetType>{0, 1};
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets,
EncodeIdentifiabilityFieldTrialParam(kOffsets));
auto state =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_THAT(state->active_surfaces(),
UnorderedElementsAre(Represents(kRegularSurface1),
Represents(kRegularSurface2)));
EXPECT_THAT(state->seen_surfaces().AsList(), ElementsAreArray(kSurfaces));
EXPECT_THAT(state->selected_offsets(), IsSupersetOf(kOffsets));
EXPECT_EQ(kTestingStudyGeneration, state->generation());
}
// Valid offsets, as decided by IsValidOffset() go from 0 thru
// kMaxSelectedSurfaceOffset. Encountering invalid ones is a good sign that the
// prefs are inconsistent.
TEST_F(IdentifiabilityStudyStateTest, RecoversFromInvalidOffsets) {
const std::vector<unsigned int> kBadOffsets = {
5, 5, 6, 3, IdentifiabilityStudyState::kMaxSelectedSurfaceOffset + 1,
2, 0, 5};
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets,
EncodeIdentifiabilityFieldTrialParam(kBadOffsets));
const IdentifiableSurfaceList kSeenSurfaces = {
kRegularSurface1, kRegularSurface2, kRegularSurface3};
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(kSeenSurfaces));
auto state =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
// The seen surfaces should've been dumped due to having invalid persisted
// state.
EXPECT_TRUE(state->seen_surfaces().empty());
// Upon re-initialization there should be at least one selected offset to get
// started with.
EXPECT_FALSE(state->selected_offsets().empty());
}
// Any blocked surfaces in the seen surface list should be dropped. Selected
// offsets should be adjusted correspondingly.
TEST_F(IdentifiabilityStudyStateTest, DropsBlockedSurfaces_Suffix) {
// The suffix of the seen surfaces list consists of blocked surfaces.
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(IdentifiableSurfaceList{
kRegularSurface1, kRegularSurface2, kRegularSurface3,
kBlockedSurface1, kBlockedTypeSurface1}));
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets, "0,1,3,4");
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_THAT(settings->selected_offsets(),
UnorderedElementsAre(IdentifiabilityStudyState::OffsetType{0},
IdentifiabilityStudyState::OffsetType{1}));
EXPECT_THAT(
settings->active_surfaces(),
ElementsAre(Represents(kRegularSurface1), Represents(kRegularSurface2)));
}
// Any blocked surfaces in the seen surface list should be dropped. Selected
// offsets should be adjusted correspondingly.
TEST_F(IdentifiabilityStudyStateTest, DropsBlockedSurfaces_Prefix) {
// The prefix of the seen surfaces list consists of blocked surfaces.
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(
IdentifiableSurfaceList{kBlockedSurface1, kRegularSurface1,
kRegularSurface2, kRegularSurface3}));
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets, "1,3");
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_THAT(settings->selected_offsets(), UnorderedElementsAre(0, 2));
EXPECT_THAT(
settings->active_surfaces(),
ElementsAre(Represents(kRegularSurface1), Represents(kRegularSurface3)));
}
// Any blocked surfaces in the seen surface list should be dropped. Selected
// offsets should be adjusted correspondingly.
TEST_F(IdentifiabilityStudyStateTest, DropsBlockedSurfaces_Infix) {
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(
IdentifiableSurfaceList{kRegularSurface1, kBlockedSurface1,
kRegularSurface2, kRegularSurface3}));
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets,
"0,1,2,3,4,5");
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_THAT(settings->selected_offsets(),
UnorderedElementsAre(0, 1, 2, 3, 4));
EXPECT_THAT(
settings->active_surfaces(),
ElementsAre(Represents(kRegularSurface1), Represents(kRegularSurface2),
Represents(kRegularSurface3)));
}
TEST_F(IdentifiabilityStudyStateTest, DropsBlockedTypes) {
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(
IdentifiableSurfaceList{kRegularSurface1, kBlockedTypeSurface1,
kRegularSurface2, kRegularSurface3}));
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets,
"0,1,2,3,4,5");
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_THAT(
settings->active_surfaces(),
ElementsAre(Represents(kRegularSurface1), Represents(kRegularSurface2),
Represents(kRegularSurface3)));
EXPECT_THAT(settings->selected_offsets(),
AllOf(Contains(0u), Contains(1u), Contains(2u), Contains(3u),
Contains(4u)));
}
TEST_F(IdentifiabilityStudyStateTest, AllowsValidPersistedSurfaces) {
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(IdentifiableSurfaceList{
kRegularSurface1, kRegularSurface2, kRegularSurface3}));
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets,
"0,1,2,3,4,5");
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_TRUE(settings->ShouldRecordSurface(kRegularSurface1));
EXPECT_TRUE(settings->ShouldRecordSurface(kRegularSurface2));
EXPECT_TRUE(settings->ShouldRecordSurface(kRegularSurface3));
EXPECT_THAT(settings->active_surfaces(),
::testing::ElementsAre(Represents(kRegularSurface1),
Represents(kRegularSurface2),
Represents(kRegularSurface3)));
}
// Different from the DropsBlockedSurfaces* tests in that the behavior being
// verified is that of ShouldRecordSurface().
TEST_F(IdentifiabilityStudyStateTest, DisallowsBlockedSurfaces) {
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(IdentifiableSurfaceList{
kRegularSurface1, kRegularSurface2, kRegularSurface3}));
pref_service()->SetString(prefs::kPrivacyBudgetSelectedOffsets,
"0,1,2,3,4,5");
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_FALSE(settings->ShouldRecordSurface(kBlockedSurface1));
EXPECT_FALSE(settings->ShouldRecordSurface(kBlockedTypeSurface1));
}
TEST_F(IdentifiabilityStudyStateTest, UpdatesSeenSurfaces) {
// The list of seen surfaces should be updated when a new surface was
// observed.
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
settings->SelectAllOffsetsForTesting();
EXPECT_TRUE(settings->active_surfaces().Empty());
EXPECT_THAT(settings->seen_surfaces(), IsEmpty());
EXPECT_TRUE(settings->ShouldRecordSurface(kRegularSurface1));
EXPECT_THAT(settings->active_surfaces().Container(),
ElementsAre(Represents(kRegularSurface1)));
EXPECT_THAT(settings->seen_surfaces().AsList(),
ElementsAre(kRegularSurface1));
EXPECT_EQ(pref_service()->GetString(prefs::kPrivacyBudgetSeenSurfaces),
std::string("772"));
}
// If there are duplicate `seen` surfaces, then IdentifiabilityStudyState
// should drop the existing persisted state and re-initialize the study. An
// inconsistency in the persisted state should be considered irrecoverable.
TEST_F(IdentifiabilityStudyStateTest, DropsInvalidPrefs_DuplicateSurfaces) {
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(
IdentifiableSurfaceList{kRegularSurface1, kRegularSurface1}));
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_TRUE(settings->seen_surfaces().empty());
}
// If there are `seen` surfaces that are internal metadata types, then
// IdentifiabilityStudyState should drop the existing persisted state and
// re-initialize the study. An inconsistency in the persisted state should be
// considered irrecoverable.
TEST_F(IdentifiabilityStudyStateTest, DropsInvalidPrefs_InternalTypes) {
pref_service()->SetString(
prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(
IdentifiableSurfaceList{blink::IdentifiableSurface::FromTypeAndToken(
blink::IdentifiableSurface::Type::kReservedInternal, 1)}));
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
pref_service());
EXPECT_TRUE(settings->seen_surfaces().empty());
}
// If there's not enough offsets selected, IdentifiabilityStudyState should
// select more. If any of the newly selected offsets are within the range of
// the seen surfaces, those seen surfaces should be automatically promoted to
// active surfaces.
TEST(IdentifiabilityStudyStateStandaloneTest,
OffsetSelectionPromotesSeenToActiveSurfaces) {
auto params = test::ScopedPrivacyBudgetConfig::Parameters{};
params.active_surface_budget = 40u;
params.expected_surface_count = params.active_surface_budget * 2;
test::ScopedPrivacyBudgetConfig config(params);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
IdentifiableSurfaceList seen_surfaces;
constexpr unsigned kTargetOffsetCount =
test_utils::InspectableIdentifiabilityStudyState::
kMaxSelectedSurfaceOffset +
1;
for (uint64_t i = 0; i < kTargetOffsetCount; ++i) {
seen_surfaces.push_back(blink::IdentifiableSurface::FromTypeAndToken(
blink::IdentifiableSurface::Type::kCanvasReadback, i));
}
pref_service.SetString(prefs::kPrivacyBudgetSeenSurfaces,
EncodeIdentifiabilityFieldTrialParam(seen_surfaces));
pref_service.SetInteger(prefs::kPrivacyBudgetGeneration, params.generation);
auto settings =
std::make_unique<test_utils::InspectableIdentifiabilityStudyState>(
&pref_service);
// The default costing model counts each surface as a single unit. So
// params.active_surface_budget is also the maximum number of surfaces that
// could be selected.
EXPECT_EQ(static_cast<unsigned>(params.active_surface_budget),
settings->active_surfaces().Size());
EXPECT_EQ(kTargetOffsetCount, settings->seen_surfaces().size());
EXPECT_EQ(static_cast<unsigned>(params.active_surface_budget),
settings->selected_offsets().size());
}
// Verify that the study parameters don't overflow.
TEST(IdentifiabilityStudyStateStandaloneTest, HighClamps) {
auto params = test::ScopedPrivacyBudgetConfig::Parameters{};
params.active_surface_budget =
features::kMaxIdentifiabilityStudyActiveSurfaceBudget + 1;
params.expected_surface_count =
features::kMaxIdentifiabilityStudyExpectedSurfaceCount + 1;
test::ScopedPrivacyBudgetConfig config(params);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState settings(&pref_service);
EXPECT_EQ(features::kMaxIdentifiabilityStudyActiveSurfaceBudget,
settings.active_surface_budget());
}
// Verify that the study parameters don't underflow.
TEST(IdentifiabilityStudyStateStandaloneTest, LowClamps) {
auto params = test::ScopedPrivacyBudgetConfig::Parameters{};
params.active_surface_budget = -1;
params.expected_surface_count = -1;
test::ScopedPrivacyBudgetConfig config(params);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState settings(&pref_service);
EXPECT_EQ(0, settings.active_surface_budget());
}
TEST(IdentifiabilityStudyStateStandaloneTest, Disabled) {
auto params = test::ScopedPrivacyBudgetConfig::Parameters{};
params.enabled = false;
test::ScopedPrivacyBudgetConfig config(params);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState settings(&pref_service);
// The specific surface doesn't matter.
EXPECT_FALSE(settings.ShouldRecordSurface(kRegularSurface1));
}
TEST(IdentifiabilityStudyStateStandaloneTest, ClearsPrefsIfStudyIsDisabled) {
test::ScopedPrivacyBudgetConfig::Parameters parameters;
parameters.enabled = false;
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
pref_service.SetInteger(prefs::kPrivacyBudgetGeneration,
parameters.generation);
pref_service.SetString(prefs::kPrivacyBudgetSeenSurfaces, "1,2,3");
pref_service.SetString(prefs::kPrivacyBudgetSelectedOffsets, "100,200,300");
IdentifiabilityStudyState state(&pref_service);
EXPECT_EQ(0, pref_service.GetInteger(prefs::kPrivacyBudgetGeneration));
EXPECT_THAT(pref_service.GetString(prefs::kPrivacyBudgetSeenSurfaces),
IsEmpty());
EXPECT_THAT(pref_service.GetString(prefs::kPrivacyBudgetSelectedOffsets),
IsEmpty());
}
TEST_F(IdentifiabilityStudyStateTest, StripDisallowedSurfaces_Empty) {
test_utils::InspectableIdentifiabilityStudyState::
InitializeGlobalStudySettings();
std::vector<IdentifiabilityStudyState::OffsetType> dropped;
IdentifiableSurfaceList surfaces;
EXPECT_TRUE(
test_utils::InspectableIdentifiabilityStudyState::StripDisallowedSurfaces(
surfaces, dropped));
EXPECT_THAT(dropped, IsEmpty());
EXPECT_THAT(surfaces, IsEmpty());
}
TEST_F(IdentifiabilityStudyStateTest, StripDisallowedSurfaces_SingleBad) {
test_utils::InspectableIdentifiabilityStudyState::
InitializeGlobalStudySettings();
std::vector<IdentifiabilityStudyState::OffsetType> dropped;
IdentifiableSurfaceList surfaces{kBlockedSurface1, kRegularSurface1};
EXPECT_TRUE(
test_utils::InspectableIdentifiabilityStudyState::StripDisallowedSurfaces(
surfaces, dropped));
EXPECT_THAT(dropped, ElementsAre(0));
EXPECT_THAT(surfaces, ElementsAre(kRegularSurface1));
}
TEST_F(IdentifiabilityStudyStateTest, StripDisallowedSurfaces_BadSuffix) {
test_utils::InspectableIdentifiabilityStudyState::
InitializeGlobalStudySettings();
std::vector<IdentifiabilityStudyState::OffsetType> dropped;
IdentifiableSurfaceList surfaces{kRegularSurface1, kBlockedSurface1};
EXPECT_TRUE(
test_utils::InspectableIdentifiabilityStudyState::StripDisallowedSurfaces(
surfaces, dropped));
EXPECT_THAT(dropped, ElementsAre(1));
EXPECT_THAT(surfaces, ElementsAre(kRegularSurface1));
}
TEST_F(IdentifiabilityStudyStateTest, StripDisallowedSurfaces_Duplicates) {
test_utils::InspectableIdentifiabilityStudyState::
InitializeGlobalStudySettings();
std::vector<IdentifiabilityStudyState::OffsetType> dropped;
IdentifiableSurfaceList surfaces{kRegularSurface1, kRegularSurface1};
EXPECT_FALSE(
test_utils::InspectableIdentifiabilityStudyState::StripDisallowedSurfaces(
surfaces, dropped));
}
TEST_F(IdentifiabilityStudyStateTest, StripDisallowedSurfaces_InternalSurface) {
test_utils::InspectableIdentifiabilityStudyState::
InitializeGlobalStudySettings();
std::vector<IdentifiabilityStudyState::OffsetType> dropped;
IdentifiableSurfaceList surfaces{
kRegularSurface1,
blink::IdentifiableSurface::FromTypeAndToken(
blink::IdentifiableSurface::Type::kReservedInternal, 1)};
EXPECT_FALSE(
test_utils::InspectableIdentifiabilityStudyState::StripDisallowedSurfaces(
surfaces, dropped));
}
TEST(IdentifiabilityStudyStateStandaloneTest, AdjustForDroppedOffsets_Empty) {
std::vector<IdentifiabilityStudyState::OffsetType> dropped;
std::vector<IdentifiabilityStudyState::OffsetType> original;
auto result =
test_utils::InspectableIdentifiabilityStudyState::AdjustForDroppedOffsets(
dropped, original);
EXPECT_THAT(result, IsEmpty());
}
TEST(IdentifiabilityStudyStateStandaloneTest,
AdjustForDroppedOffsets_NoDropped) {
std::vector<IdentifiabilityStudyState::OffsetType> dropped;
std::vector<IdentifiabilityStudyState::OffsetType> original{1, 5, 7, 9};
auto result =
test_utils::InspectableIdentifiabilityStudyState::AdjustForDroppedOffsets(
dropped, original);
EXPECT_THAT(result, ElementsAreArray(original));
}
TEST(IdentifiabilityStudyStateStandaloneTest,
AdjustForDroppedOffsets_DroppedZero) {
std::vector<IdentifiabilityStudyState::OffsetType> dropped{0};
std::vector<IdentifiabilityStudyState::OffsetType> original{1, 5, 7, 9};
auto result =
test_utils::InspectableIdentifiabilityStudyState::AdjustForDroppedOffsets(
dropped, original);
// All offsets in `original` should be decreased by 1.
EXPECT_THAT(result, ElementsAre(0, 4, 6, 8));
}
TEST(IdentifiabilityStudyStateStandaloneTest,
AdjustForDroppedOffsets_DroppedOne) {
std::vector<IdentifiabilityStudyState::OffsetType> dropped{1};
std::vector<IdentifiabilityStudyState::OffsetType> original{1, 5, 7, 9};
auto result =
test_utils::InspectableIdentifiabilityStudyState::AdjustForDroppedOffsets(
dropped, original);
EXPECT_THAT(result, ElementsAre(4, 6, 8));
}
TEST(IdentifiabilityStudyStateStandaloneTest,
AdjustForDroppedOffsets_DroppedMultiple) {
std::vector<IdentifiabilityStudyState::OffsetType> dropped{2, 3, 5};
std::vector<IdentifiabilityStudyState::OffsetType> original{1, 2, 4, 6};
auto result =
test_utils::InspectableIdentifiabilityStudyState::AdjustForDroppedOffsets(
dropped, original);
EXPECT_THAT(result, ElementsAre(1, 2, 3));
}
TEST(IdentifiabilityStudyStateStandaloneTest, OffsetUsesUpAllTheBudget) {
test::ScopedPrivacyBudgetConfig::Parameters parameters;
parameters.active_surface_budget = kTestingActiveSurfaceBudget;
// kRegularSurface2 costs the entire budget.
parameters.per_surface_cost = {
{kRegularSurface1, 1.0},
{kRegularSurface2, parameters.active_surface_budget - 1},
{kRegularSurface3, 1.0}};
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
pref_service.SetInteger(prefs::kPrivacyBudgetGeneration,
parameters.generation);
pref_service.SetString(prefs::kPrivacyBudgetSelectedOffsets, "0,1,2");
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
EXPECT_TRUE(state.ShouldRecordSurface(kRegularSurface1));
EXPECT_TRUE(state.ShouldRecordSurface(kRegularSurface2));
EXPECT_FALSE(state.ShouldRecordSurface(kRegularSurface3));
// kRegularSurface3 doesn't end up in the seen_surface_sequence because we've
// saturated the active surface budget and are no longer interested in new
// surfaces.
EXPECT_THAT(state.seen_surfaces(),
ElementsAre(kRegularSurface1, kRegularSurface2));
}
TEST(IdentifiabilityStudyStateStandaloneTest, NextOffsetIsTooExpensive) {
test::ScopedPrivacyBudgetConfig::Parameters parameters;
parameters.active_surface_budget = kTestingActiveSurfaceBudget;
// kRegularSurface1 costs the entire budget.
parameters.per_surface_cost = {
{kRegularSurface1, parameters.active_surface_budget + 1}};
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
pref_service.SetInteger(prefs::kPrivacyBudgetGeneration,
parameters.generation);
pref_service.SetString(prefs::kPrivacyBudgetSelectedOffsets, "0");
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
// Selected offset `0` is from prefs.
EXPECT_THAT(state.selected_offsets(), Contains(0));
// This surface is too expensive, which should result in `state` dropping the
// corresponding offset.
EXPECT_FALSE(state.ShouldRecordSurface(kRegularSurface1));
EXPECT_THAT(state.selected_offsets(), Not(Contains(0)));
EXPECT_THAT(state.seen_surfaces(), ElementsAre(kRegularSurface1));
}
TEST(IdentifiabilityStudyStateStandaloneTest, ReachesPivotPoint) {
test::ScopedPrivacyBudgetConfig::Parameters parameters;
parameters.active_surface_budget = kTestingActiveSurfaceBudget;
// Try out 10x the number of surfaces we want to select.
constexpr auto kExpectedSurfaceCount = kTestingActiveSurfaceBudget * 10;
parameters.expected_surface_count = kExpectedSurfaceCount;
test::ScopedPrivacyBudgetConfig config(parameters);
int active_surface_count = 0;
constexpr int kTrials = 10;
for (auto trials = 0; trials < kTrials; ++trials) {
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
active_surface_count +=
SimulateSurfaceSelectionRound(state, kExpectedSurfaceCount);
}
// If we see `expected_surface_count` many surfaces, then we *should* consume
// roughly `active_surface_budget` * `kMesaDistributionRatio` surfaces. (the
// budget that's allocated for the ordinal space between 0 and the pivot
// point.)
constexpr double kExpectedActiveSurfaceCount =
kTestingActiveSurfaceBudget *
IdentifiabilityStudyState::kMesaDistributionRatio;
const double kEpsilon =
kExpectedActiveSurfaceCount * 0.05 /* 5% margin of error */;
EXPECT_NEAR(kExpectedActiveSurfaceCount, active_surface_count * 1.0 / kTrials,
kEpsilon);
}
TEST(IdentifiabilityStudyStateStandaloneTest, CheapSurfaces) {
// For this test, we are going to use surfaces that cost
// 1/kSurfacesPerCostUnit units. The objective is to ensure that the code
// correctly handles the case where the number of surfaces that saturate the
// budget is substantially larger than the budget itself.
constexpr auto kSurfacesPerCostUnit = 4;
// The number of surfaces that are selected must not exceed this value.
constexpr auto kMaxSelectedSurfaces =
kTestingActiveSurfaceBudget * kSurfacesPerCostUnit + 1 -
kSurfacesPerCostUnit;
// Expect a substantially larger number of surfaces than the number we expect
// to select.
constexpr auto kExpectedSurfaceCount = kMaxSelectedSurfaces * 10;
test::ScopedPrivacyBudgetConfig::Parameters parameters;
parameters.active_surface_budget = kTestingActiveSurfaceBudget;
parameters.expected_surface_count = kExpectedSurfaceCount;
parameters.per_type_cost = {
{kRegularTypeId, 1.0 / kSurfacesPerCostUnit},
};
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
// Invoking ShouldRecordSurface() for kExpectedSurfaceCount surfaces should
// result in at least
unsigned selected_surfaces =
SimulateSurfaceSelectionRound(state, kExpectedSurfaceCount);
ASSERT_LE(selected_surfaces, kMaxSelectedSurfaces);
EXPECT_GT(selected_surfaces, kTestingActiveSurfaceBudget);
EXPECT_LE(selected_surfaces, kMaxSelectedSurfaces);
}
TEST(IdentifiabilityStudyStateStandaloneTest, SomeSetOfGroups) {
// Number of test groups. Arbitrary.
constexpr unsigned kTestGroupCount = 80;
// Number of surfaces in a single group. The groups don't need to be the same
// size.
constexpr unsigned kSurfacesInGroup = 40;
test::ScopedPrivacyBudgetConfig::Parameters parameters;
for (unsigned group_index = 0; group_index < kTestGroupCount; ++group_index) {
parameters.blocks.emplace_back(
CreateSurfaceList(kSurfacesInGroup, kSurfacesInGroup * group_index));
}
parameters.block_weights.assign(kTestGroupCount, 1.0);
parameters.active_surface_budget = kSurfacesInGroup;
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
// Any single selected group contributes kSurfacesInGroup surfaces.
EXPECT_EQ(kSurfacesInGroup, state.active_surfaces().Size());
EXPECT_EQ(0u, state.seen_surfaces().size());
}
// Regression test for the problem solved by https://crrev.com/c/3211286
TEST(IdentifiabilityStudyStateStandaloneTest,
SomeSetOfGroupsWithRhoEqualToZero) {
// Number of test groups. Arbitrary.
constexpr unsigned kTestGroupCount = 80;
// Number of surfaces in a single group. The groups don't need to be the same
// size.
constexpr unsigned kSurfacesInGroup = 40;
test::ScopedPrivacyBudgetConfig::Parameters parameters;
for (unsigned group_index = 0; group_index < kTestGroupCount; ++group_index) {
parameters.blocks.emplace_back(
CreateSurfaceList(kSurfacesInGroup, kSurfacesInGroup * group_index));
}
parameters.block_weights.assign(kTestGroupCount, 1.0);
parameters.active_surface_budget = kSurfacesInGroup;
parameters.expected_surface_count = 0;
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
EXPECT_TRUE(state.is_using_assigned_block_sampling());
// Any single selected group contributes kSurfacesInGroup surfaces.
EXPECT_EQ(kSurfacesInGroup, state.active_surfaces().Size());
}
TEST(IdentifiabilityStudyStateStandaloneTest, GroupsWithWeights) {
constexpr unsigned kTestGroupCount = 80;
constexpr unsigned kSurfacesInGroup = 40;
test::ScopedPrivacyBudgetConfig::Parameters parameters;
for (unsigned group_index = 0; group_index < kTestGroupCount; ++group_index) {
parameters.blocks.emplace_back(
CreateSurfaceList(kSurfacesInGroup, kSurfacesInGroup * group_index));
}
parameters.block_weights.assign(kTestGroupCount, 1.0);
parameters.active_surface_budget = kSurfacesInGroup;
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
int surfaces_to_record = 0;
for (const IdentifiableSurfaceList& block : parameters.blocks) {
if (state.ShouldRecordSurface(block[0]))
surfaces_to_record++;
}
// Should record surfaces from exactly one group.
EXPECT_EQ(1, surfaces_to_record);
}
TEST(IdentifiabilityStudyStateStandaloneTest, GroupSelectionPersists) {
constexpr unsigned kTestGroupCount = 80;
constexpr unsigned kSurfacesInGroup = 40;
test::ScopedPrivacyBudgetConfig::Parameters parameters;
for (unsigned group_index = 0; group_index < kTestGroupCount; ++group_index) {
parameters.blocks.emplace_back(
CreateSurfaceList(kSurfacesInGroup, kSurfacesInGroup * group_index));
}
parameters.block_weights.assign(kTestGroupCount, 1.0);
parameters.active_surface_budget = kSurfacesInGroup;
// Runs two scoped test. The selection from the first one should persist to
// the next.
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
int selected_block_offset = -1;
{
test::ScopedPrivacyBudgetConfig config(parameters);
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
selected_block_offset = state.selected_block_offset();
ASSERT_GE(selected_block_offset, 0);
ASSERT_LT(static_cast<unsigned>(selected_block_offset),
parameters.blocks.size());
EXPECT_TRUE(
state.ShouldRecordSurface(parameters.blocks[selected_block_offset][0]));
}
{
test::ScopedPrivacyBudgetConfig config(parameters);
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
EXPECT_TRUE(
state.ShouldRecordSurface(parameters.blocks[selected_block_offset][0]));
}
}
TEST(IdentifiabilityStudyStateStandaloneTest, GroupOverflowsExperimentBudget) {
constexpr unsigned kSurfacesInGroup = 40;
// One unit short of accommodating the surface set.
constexpr unsigned kActiveSurfaceBudget = kSurfacesInGroup - 1;
test::ScopedPrivacyBudgetConfig::Parameters parameters;
parameters.blocks.emplace_back(CreateSurfaceList(kSurfacesInGroup));
parameters.active_surface_budget = kActiveSurfaceBudget;
test::ScopedPrivacyBudgetConfig config(parameters);
TestingPrefServiceSimple pref_service;
prefs::RegisterPrivacyBudgetPrefs(pref_service.registry());
test_utils::InspectableIdentifiabilityStudyState state(&pref_service);
// Experiment is active, but there are no active surfaces.
EXPECT_TRUE(state.active_surfaces().Empty());
}