blob: 5f0d5e274993b82aada834ea176a5be02ace0275 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <string_view>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/metrics/field_trial.h"
#include "base/path_service.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/variations/pref_names.h"
#include "components/variations/proto/layer.pb.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/variations_switches.h"
#include "components/variations/variations_test_utils.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/google/compression_utils.h"
namespace variations {
namespace {
constexpr std::string_view kGwsVisibleStudyName = "GwsVisibleStudy";
constexpr std::string_view kNonGwsVisibleStudyName = "NonGwsVisibleStudy";
// Returns a seed with the following:
// * A 100-slot limited layer with a single layer member containing all slots.
// * A GWS-visible A/B study constrained to the limited layer.
// * A non-GWS-visible A/B study constrained to the limited layer.
//
// Each study has 50% of clients in Group1 and 50% of clients in Group2. Because
// the studies have permanent consistency, have the same group ordering, and
// specify the same randomization seed, they are randomized in the same way.
// Therefore, the Group1 clients in one study are also the Group1 clients in the
// other study. Ditto for Group2.
VariationsSeed CreateTestSeedWithLimitedEntropyLayer() {
VariationsSeed seed;
auto* layer = seed.add_layers();
layer->set_id(123);
layer->set_num_slots(100);
layer->set_entropy_mode(Layer::LIMITED);
auto* layer_member = layer->add_members();
layer_member->set_id(1);
auto* slot_range = layer_member->add_slots();
slot_range->set_start(0);
slot_range->set_end(99);
Study base_study;
base_study.set_randomization_seed(500);
base_study.set_consistency(Study::PERMANENT);
auto* filter = base_study.mutable_filter();
filter->add_channel(Study::CANARY);
filter->add_channel(Study::BETA);
filter->add_channel(Study::DEV);
filter->add_channel(Study::STABLE);
filter->add_channel(Study::UNKNOWN);
filter->add_platform(Study::PLATFORM_WINDOWS);
filter->add_platform(Study::PLATFORM_MAC);
filter->add_platform(Study::PLATFORM_LINUX);
filter->add_platform(Study::PLATFORM_CHROMEOS);
filter->add_platform(Study::PLATFORM_ANDROID);
auto* layer_member_reference = base_study.mutable_layer();
layer_member_reference->set_layer_id(123);
layer_member_reference->add_layer_member_ids(1);
Study gws_study = base_study;
gws_study.set_name(kGwsVisibleStudyName);
auto* group = gws_study.add_experiment();
group->set_name("Group1");
group->set_probability_weight(1);
group->set_google_web_experiment_id(44);
group = gws_study.add_experiment();
group->set_name("Group2");
group->set_probability_weight(1);
group->set_google_web_experiment_id(55);
Study non_gws_study = base_study;
non_gws_study.set_name(kNonGwsVisibleStudyName);
group = non_gws_study.add_experiment();
group->set_name("Group1");
group->set_probability_weight(1);
group = non_gws_study.add_experiment();
group->set_name("Group2");
group->set_probability_weight(1);
*seed.add_study() = gws_study;
*seed.add_study() = non_gws_study;
return seed;
}
class LimitedEntropyRandomizationBrowserTest : public PlatformBrowserTest {
public:
LimitedEntropyRandomizationBrowserTest() = default;
~LimitedEntropyRandomizationBrowserTest() override = default;
protected:
// BrowserTestBase:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kAcceptEmptySeedSignatureForTesting);
DisableTestingConfig();
}
// PlatformBrowserTest:
bool SetUpUserDataDirectory() override {
const base::FilePath user_data_dir =
base::PathService::CheckedGet(chrome::DIR_USER_DATA);
const base::FilePath seed_file_path =
user_data_dir.AppendASCII("VariationsSeedV1");
const base::FilePath local_state_path =
user_data_dir.Append(chrome::kLocalStateFilename);
std::string serialized_seed =
CreateTestSeedWithLimitedEntropyLayer().SerializeAsString();
std::string compressed_seed;
compression::GzipCompress(serialized_seed, &compressed_seed);
// Write the seed for the seed file experiment's treatment-group clients.
CHECK(base::WriteFile(seed_file_path, compressed_seed));
// Write the seed for the seed file experiment's control-group clients.
base::Value::Dict local_state;
local_state.SetByDottedPath(prefs::kVariationsCompressedSeed,
base::Base64Encode(compressed_seed));
CHECK(JSONFileValueSerializer(local_state_path).Serialize(local_state));
return true;
}
PrefService* local_state() { return g_browser_process->local_state(); }
};
// Verifies the following:
// * The client generates a limited entropy randomization source value.
// * FieldTrials are created from the limited-layer-constrained studies in the
// seed produced by `CreateTestSeedWithLimitedEntropyLayer()`.
// * Randomization is the same for limited-layer-constrained studies that are
// identical except for (A) unused study names (unused because the studies
// set randomization seed) and (B) the presence or absence of Google
// experiment IDs in their respective groups.
IN_PROC_BROWSER_TEST_F(LimitedEntropyRandomizationBrowserTest,
UseLimitedEntropyRandomization) {
EXPECT_FALSE(
local_state()
->FindPreference(
metrics::prefs::kMetricsLimitedEntropyRandomizationSource)
->IsDefaultValue());
EXPECT_TRUE(base::FieldTrialList::TrialExists(kGwsVisibleStudyName));
EXPECT_TRUE(base::FieldTrialList::TrialExists(kNonGwsVisibleStudyName));
// As a reminder, `CreateTestSeedWithLimitedEntropyLayer()` returns a seed
// with two two-arm studies whose groups have the same names, order, and
// weight. The expectation is that all clients in the first group of one study
// are the same clients in the first group of the other study. Ditto for the
// second group.
//
// Verify that the studies were randomized in the same way for this client by
// checking that the client is in either the first group of each study or the
// second.
EXPECT_EQ(base::FieldTrialList::FindFullName(kGwsVisibleStudyName),
base::FieldTrialList::FindFullName(kNonGwsVisibleStudyName));
}
} // namespace
} // namespace variations