// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


#include "components/variations/variations_seed_processor.h"

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_list_including_low_anonymity.h"
#include "base/metrics/field_trial_params.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "components/metrics/entropy_state.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/processed_study.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/study_filtering.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_layers.h"
#include "components/variations/variations_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::ElementsAre;
using testing::IsEmpty;

namespace variations {
namespace {

// Constants for testing associating command line flags with trial groups.
const char kFlagStudyName[] = "flag_test_trial";
const char kFlagGroup1Name[] = "flag_group1";
const char kFlagGroup2Name[] = "flag_group2";
const char kNonFlagGroupName[] = "non_flag_group";
const char kOtherGroupName[] = "other_group";
const char kForcingFlag1[] = "flag_test1";
const char kForcingFlag2[] = "flag_test2";

const VariationID kExperimentId = 123;

// Adds an experiment to |study| with the specified |name| and |probability|.
Study::Experiment* AddExperiment(const std::string& name,
                                 int probability,
                                 std::optional<int> google_web_experiment_id,
                                 Study* study) {
  Study::Experiment* experiment = study->add_experiment();
  experiment->set_name(name);
  experiment->set_probability_weight(probability);
  if (google_web_experiment_id.has_value()) {
    experiment->set_google_web_visibility(Study::ANY);
    experiment->set_google_web_experiment_id(google_web_experiment_id.value());
  }
  return experiment;
}

// Adds an experiment to |study| with the specified |name| and |probability|.
Study::Experiment* AddExperiment(const std::string& name,
                                 int probability,
                                 Study* study) {
  return AddExperiment(name, probability, std::nullopt, study);
}

// Adds a Study to |seed| and populates it with test data associating command
// line flags with trials groups. The study will contain three groups, a
// default group that isn't associated with a flag, and two other groups, both
// associated with different flags.
Study* CreateStudyWithFlagGroups(int default_group_probability,
                                 int flag_group1_probability,
                                 int flag_group2_probability,
                                 VariationsSeed* seed) {
  DCHECK_GE(default_group_probability, 0);
  DCHECK_GE(flag_group1_probability, 0);
  DCHECK_GE(flag_group2_probability, 0);
  Study* study = seed->add_study();
  study->set_name(kFlagStudyName);
  study->set_default_experiment_name(kNonFlagGroupName);

  AddExperiment(kNonFlagGroupName, default_group_probability, study);
  AddExperiment(kFlagGroup1Name, flag_group1_probability, study)
      ->set_forcing_flag(kForcingFlag1);
  AddExperiment(kFlagGroup2Name, flag_group2_probability, study)
      ->set_forcing_flag(kForcingFlag2);

  return study;
}

BASE_FEATURE(kDisabled, base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kEnabled, base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kRepeated, base::FEATURE_DISABLED_BY_DEFAULT);

// Gets the group name of the study associated with a feature or empty string.
std::string AssociatedStudyGroup(const base::Feature& feature) {
  auto* trial = base::FeatureList::GetFieldTrial(feature);
  return trial ? trial->group_name() : "";
}

// Create a filterable state for use in these tests.
// This differs from |CreateDummyClientFilterableState()| by setting membership
// of a specific google group (which some tests rely on).
constexpr uint64_t kExampleGoogleGroup = 123456;
constexpr char kExampleEnterpriseGroup[] = "included_customers";
std::unique_ptr<ClientFilterableState> CreateTestClientFilterableState() {
  auto client_state = std::make_unique<ClientFilterableState>(
      base::BindOnce([] { return false; }), base::BindOnce([] {
        return base::flat_set<uint64_t>({kExampleGoogleGroup});
      }),
      base::BindOnce([] {
        return base::flat_set<std::string>({kExampleEnterpriseGroup});
      }));
  client_state->locale = "en-CA";
  client_state->reference_date = base::Time::Now();
  client_state->version = base::Version("20.0.0.0");
  client_state->channel = Study::STABLE;
  client_state->form_factor = Study::PHONE;
  return client_state;
}

// Add a filter to |study| that filters on a Google group which matches the
// client filterable state.
void AddGoogleGroupFilter(Study& study) {
  Study::Filter* filter = study.mutable_filter();
  filter->add_google_group(kExampleGoogleGroup);
  // Also add a platform filter that matches both the environments we're
  // testing in the typed tests.
  filter->add_platform(Study::PLATFORM_ANDROID);
  filter->add_platform(Study::PLATFORM_ANDROID_WEBVIEW);
}

}  // namespace

// ChromeEnvironment calls CreateTrialsFromSeed with arguments similar to
// chrome.
class ChromeEnvironment {
 public:
  ChromeEnvironment() : sticky_activation_manager_(/*local_state=*/nullptr) {}

  bool HasHighEntropy() { return true; }
  bool HasLimitedEntropy() { return true; }

  void CreateTrialsFromSeed(const VariationsSeed& seed,
                            base::FeatureList* feature_list) {
    auto client_state = CreateTestClientFilterableState();
    client_state->platform = Study::PLATFORM_ANDROID;

    MockEntropyProviders entropy_providers({
        .low_entropy = kAlwaysUseLastGroup,
        .high_entropy = kAlwaysUseFirstGroup,
        .limited_entropy = kAlwaysUseFirstGroup,
    });

    VariationsLayers layers(seed, entropy_providers);
    // This should mimic the call through SetUpFieldTrials from
    // components/variations/service/variations_service.cc
    VariationsSeedProcessor(sticky_activation_manager_)
        .CreateTrialsFromSeed(seed, *client_state, entropy_providers, layers,
                              feature_list);
  }

 private:
  StickyActivationManager sticky_activation_manager_;
};

// WebViewEnvironment calls CreateTrialsFromSeed with arguments similar to
// WebView.
class WebViewEnvironment {
 public:
  WebViewEnvironment() : sticky_activation_manager_(/*local_state=*/nullptr) {}

  bool HasHighEntropy() { return false; }
  bool HasLimitedEntropy() { return false; }

  void CreateTrialsFromSeed(const VariationsSeed& seed,
                            base::FeatureList* feature_list) {
    auto client_state = CreateTestClientFilterableState();
    client_state->platform = Study::PLATFORM_ANDROID_WEBVIEW;

    MockEntropyProviders entropy_providers({
        .low_entropy = kAlwaysUseLastGroup,
    });

    VariationsLayers layers(seed, entropy_providers);
    // This should mimic the call through SetUpFieldTrials from
    // android_webview/browser/aw_feature_list_creator.cc
    VariationsSeedProcessor(sticky_activation_manager_)
        .CreateTrialsFromSeed(seed, *client_state, entropy_providers, layers,
                              feature_list);
  }

 private:
  StickyActivationManager sticky_activation_manager_;
};

template <typename Environment>
class VariationsSeedProcessorTest : public ::testing::Test {
 public:
  VariationsSeedProcessorTest() = default;
  VariationsSeedProcessorTest(const VariationsSeedProcessorTest&) = delete;
  VariationsSeedProcessorTest& operator=(const VariationsSeedProcessorTest&) =
      delete;

  ~VariationsSeedProcessorTest() override {
    // Ensure that the maps are cleared between tests, since they are stored as
    // process singletons.
    test::ClearAllVariationIDs();
    test::ClearAllVariationParams();
  }

  void CreateTrialsFromSeed(const VariationsSeed& seed) {
    base::FeatureList feature_list;
    env.CreateTrialsFromSeed(seed, &feature_list);
  }

  void CreateTrialsFromSeed(const VariationsSeed& seed,
                            base::FeatureList* feature_list) {
    env.CreateTrialsFromSeed(seed, feature_list);
  }

  scoped_refptr<base::FieldTrial> CreateTrialFromStudy(
      VariationsSeedProcessor& seed_processor,
      const ProcessedStudy& processed_study,
      const EntropyProviders& entropy_providers,
      const VariationsLayers& layers,
      base::FeatureList* feature_list,
      bool simulated = false) {
    return seed_processor.CreateTrialFromStudyImpl(
        processed_study, entropy_providers, layers, feature_list, simulated);
  }

 protected:
  Environment env;
};

using EnvironmentTypes =
    ::testing::Types<ChromeEnvironment, WebViewEnvironment>;
TYPED_TEST_SUITE(VariationsSeedProcessorTest, EnvironmentTypes);

TYPED_TEST(VariationsSeedProcessorTest, EmitStudyCountMetric) {
  struct StudyCountMetricTestParams {
    VariationsSeed seed;
    int expected_study_count;
  };

  VariationsSeed zero_study_seed;
  VariationsSeed one_study_seed;
  Study* study = one_study_seed.add_study();
  study->set_name("MyStudy");
  AddExperiment("Enabled", 1, study);
  std::vector<StudyCountMetricTestParams> test_cases = {
      {.seed = zero_study_seed, .expected_study_count = 0},
      {.seed = one_study_seed, .expected_study_count = 1}};

  for (const StudyCountMetricTestParams& test_case : test_cases) {
    base::HistogramTester histogram_tester;
    this->CreateTrialsFromSeed(test_case.seed);
    histogram_tester.ExpectUniqueSample("Variations.AppliedSeed.StudyCount",
                                        test_case.expected_study_count, 1);
  }
}

TYPED_TEST(VariationsSeedProcessorTest, IgnoreExpiryDateStudy) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);

  VariationsSeed seed;
  Study* study = CreateStudyWithFlagGroups(100, 0, 0, &seed);
  // Set an expiry far in the future.
  study->set_expiry_date(std::numeric_limits<int64_t>::max());

  this->CreateTrialsFromSeed(seed);
  // No trial should be created, since expiry_date is not supported.
  EXPECT_EQ("", base::FieldTrialList::FindFullName(kFlagStudyName));
}

TYPED_TEST(VariationsSeedProcessorTest, AllowForceGroupAndVariationId) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);

  VariationsSeed seed;
  Study* study = CreateStudyWithFlagGroups(100, 0, 0, &seed);
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  study->mutable_experiment(1)->set_google_web_experiment_id(kExperimentId);

  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kFlagGroup1Name,
            base::FieldTrialList::FindFullName(kFlagStudyName));

  VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT,
                                        kFlagStudyName, kFlagGroup1Name);
  EXPECT_EQ(kExperimentId, id);
}

TYPED_TEST(VariationsSeedProcessorTest,
           AllowForceGroupAndVariationId_FirstParty) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);

  VariationsSeed seed;
  Study* study = CreateStudyWithFlagGroups(100, 0, 0, &seed);
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  Study::Experiment* experiment1 = study->mutable_experiment(1);
  experiment1->set_google_web_experiment_id(kExperimentId);
  experiment1->set_google_web_visibility(Study::FIRST_PARTY);

  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kFlagGroup1Name,
            base::FieldTrialList::FindFullName(kFlagStudyName));

  VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_FIRST_PARTY,
                                        kFlagStudyName, kFlagGroup1Name);
  EXPECT_EQ(kExperimentId, id);
}

// Test that the group for kForcingFlag1 is forced.
TYPED_TEST(VariationsSeedProcessorTest, ForceGroupWithFlag1) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);

  VariationsSeed seed;
  CreateStudyWithFlagGroups(100, 0, 0, &seed);
  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kFlagGroup1Name,
            base::FieldTrialList::FindFullName(kFlagStudyName));
}

// Test that the group for kForcingFlag1 is forced.
TYPED_TEST(VariationsSeedProcessorTest, ForceGroupWithFlag1_LowAnonymity) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);

  VariationsSeed seed;
  Study* study = CreateStudyWithFlagGroups(100, 0, 0, &seed);
  AddGoogleGroupFilter(*study);
  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kFlagGroup1Name,
            base::FieldTrialList::FindFullName(kFlagStudyName));

  // This study should be marked as low anonymity, and therefore only returned
  // by |FieldTrialListIncludingLowAnonymity|.
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  EXPECT_EQ(active_groups.size(), 0u);

  base::FieldTrial::ActiveGroups active_groups_including_low_anonymity;
  base::FieldTrialListIncludingLowAnonymity::
      GetActiveFieldTrialGroupsForTesting(
          &active_groups_including_low_anonymity);
  EXPECT_EQ(active_groups_including_low_anonymity.size(), 1u);
}

// Test that the group for kForcingFlag2 is forced.
TYPED_TEST(VariationsSeedProcessorTest, ForceGroupWithFlag2) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);

  VariationsSeed seed;
  CreateStudyWithFlagGroups(100, 0, 0, &seed);
  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kFlagGroup2Name,
            base::FieldTrialList::FindFullName(kFlagStudyName));
}

TYPED_TEST(VariationsSeedProcessorTest, FieldTrialOverride) {
  struct Case {
    std::string name;
    std::optional<int> experiment_id;
    std::optional<int> triggering_experiment_id;
    bool overridden = false;

    int expected_experiment_id = 0;
    int expected_triggering_id = 0;
  };

  std::vector<Case> cases = {
      {
          .name = "Override Enabled with experiment id",
          .experiment_id = kExperimentId,
          .overridden = true,
          .expected_experiment_id = 0,
          .expected_triggering_id = 0,
      },
      {
          .name = "Enabled with experiment id",
          .experiment_id = kExperimentId,
          .overridden = false,
          .expected_experiment_id = kExperimentId,
          .expected_triggering_id = 0,
      },
      {
          .name = "Override Enabled with triggering id",
          .triggering_experiment_id = kExperimentId,
          .overridden = true,
          .expected_experiment_id = 0,
          .expected_triggering_id = kExperimentId,
      },
      {
          .name = "Enabled with triggering id",
          .triggering_experiment_id = kExperimentId,
          .overridden = false,
          .expected_experiment_id = 0,
          .expected_triggering_id = kExperimentId,
      },
  };

  for (auto& c : cases) {
    SCOPED_TRACE(c.name);
    base::test::ScopedFeatureList empty_state;
    empty_state.InitWithEmptyFeatureAndFieldTrialLists();

    VariationsSeed seed;
    Study* study = seed.add_study();
    study->set_name(kRepeated.name);
    study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
    Study::Experiment* experiment = AddExperiment("Enabled", 1, study);
    experiment->mutable_feature_association()->add_enable_feature(
        kRepeated.name);
    if (c.experiment_id) {
      experiment->set_google_web_experiment_id(*c.experiment_id);
    }
    if (c.triggering_experiment_id) {
      experiment->set_google_web_trigger_experiment_id(
          *c.triggering_experiment_id);
    }

    base::FieldTrialList::CreateFieldTrial(
        "Repeated", "Enabled", /*is_low_anonymity=*/false, c.overridden);

    auto feature_list = std::make_unique<base::FeatureList>();
    this->CreateTrialsFromSeed(seed, feature_list.get());
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitWithFeatureList(std::move(feature_list));

    EXPECT_EQ(c.expected_experiment_id,
              GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT,
                                   "Repeated", "Enabled"));
    EXPECT_EQ(c.expected_triggering_id,
              GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER_ANY_CONTEXT,
                                   "Repeated", "Enabled"));
    EXPECT_TRUE(base::FeatureList::IsEnabled(kRepeated));

    test::ClearAllVariationIDs();
  }
}

TYPED_TEST(VariationsSeedProcessorTest, ForceGroup_ChooseFirstGroupWithFlag) {
  // Add the flag to the command line arguments so the flag group is forced.
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);

  VariationsSeed seed;
  CreateStudyWithFlagGroups(100, 0, 0, &seed);
  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kFlagGroup1Name,
            base::FieldTrialList::FindFullName(kFlagStudyName));
}

TYPED_TEST(VariationsSeedProcessorTest, ForceGroup_DontChooseGroupWithFlag) {
  // The two flag groups are given high probability, which would normally make
  // them very likely to be chosen. They won't be chosen since flag groups are
  // never chosen when their flag isn't present.
  VariationsSeed seed;
  CreateStudyWithFlagGroups(1, 999, 999, &seed);
  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kNonFlagGroupName,
            base::FieldTrialList::FindFullName(kFlagStudyName));
}

TYPED_TEST(VariationsSeedProcessorTest, CreateTrialForRegisteredGroup) {
  base::FieldTrialList::CreateFieldTrial(kFlagStudyName, kOtherGroupName);

  // Create an arbitrary study that does not have group named |kOtherGroupName|.
  VariationsSeed seed;
  CreateStudyWithFlagGroups(100, 0, 0, &seed);
  // Creating the trial should not crash.
  this->CreateTrialsFromSeed(seed);
  // And the previous group should still be selected.
  EXPECT_EQ(kOtherGroupName,
            base::FieldTrialList::FindFullName(kFlagStudyName));
}

TYPED_TEST(VariationsSeedProcessorTest, VariationParams) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_default_experiment_name("B");

  Study::Experiment* experiment1 = AddExperiment("A", 1, study);
  Study::Experiment::Param* param = experiment1->add_param();
  param->set_name("x");
  param->set_value("y");

  Study::Experiment* experiment2 = AddExperiment("B", 0, study);

  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ("y", base::GetFieldTrialParamValue("Study1", "x"));

  study->set_name("Study2");
  experiment1->set_probability_weight(0);
  experiment2->set_probability_weight(1);
  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(std::string(), base::GetFieldTrialParamValue("Study2", "x"));
}

TYPED_TEST(VariationsSeedProcessorTest, VariationParamsWithForcingFlag) {
  VariationsSeed seed;
  Study* study = CreateStudyWithFlagGroups(100, 0, 0, &seed);
  ASSERT_EQ(kForcingFlag1, study->experiment(1).forcing_flag());
  Study::Experiment::Param* param = study->mutable_experiment(1)->add_param();
  param->set_name("x");
  param->set_value("y");

  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
  this->CreateTrialsFromSeed(seed);
  EXPECT_EQ(kFlagGroup1Name, base::FieldTrialList::FindFullName(study->name()));
  EXPECT_EQ("y", base::GetFieldTrialParamValue(study->name(), "x"));
}

TYPED_TEST(VariationsSeedProcessorTest, StartsActive) {
  VariationsSeed seed;
  Study* study1 = seed.add_study();
  study1->set_name("A");
  study1->set_default_experiment_name("Default");
  AddExperiment("AA", 100, study1);
  AddExperiment("Default", 0, study1);

  Study* study2 = seed.add_study();
  study2->set_name("B");
  study2->set_default_experiment_name("Default");
  AddExperiment("BB", 100, study2);
  AddExperiment("Default", 0, study2);
  study2->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  Study* study3 = seed.add_study();
  study3->set_name("C");
  study3->set_default_experiment_name("Default");
  AddExperiment("CC", 100, study3);
  AddExperiment("Default", 0, study3);
  study3->set_activation_type(Study::ACTIVATE_ON_QUERY);

  StickyActivationManager sticky_activation_manager(/*local_state=*/nullptr);
  VariationsSeedProcessor seed_processor(sticky_activation_manager);
  this->CreateTrialsFromSeed(seed);

  // Non-specified and ACTIVATE_ON_QUERY should not start active, but
  // ACTIVATE_ON_STARTUP should.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive("A"));
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive("B"));
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive("C"));

  EXPECT_EQ("AA", base::FieldTrialList::FindFullName("A"));
  EXPECT_EQ("BB", base::FieldTrialList::FindFullName("B"));
  EXPECT_EQ("CC", base::FieldTrialList::FindFullName("C"));

  // Now, all studies should be active.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive("A"));
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive("B"));
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive("C"));
}

TYPED_TEST(VariationsSeedProcessorTest, StartsActiveWithFlag) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);

  VariationsSeed seed;
  Study* study = CreateStudyWithFlagGroups(100, 0, 0, &seed);
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  this->CreateTrialsFromSeed(seed);
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kFlagStudyName));

  EXPECT_EQ(kFlagGroup1Name,
            base::FieldTrialList::FindFullName(kFlagStudyName));
}

TYPED_TEST(VariationsSeedProcessorTest, ForcingFlagAlreadyForced) {
  VariationsSeed seed;
  Study* study = CreateStudyWithFlagGroups(100, 0, 0, &seed);
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  ASSERT_EQ(kNonFlagGroupName, study->experiment(0).name());
  Study::Experiment::Param* param = study->mutable_experiment(0)->add_param();
  param->set_name("x");
  param->set_value("y");
  study->mutable_experiment(0)->set_google_web_experiment_id(kExperimentId);

  base::FieldTrialList::CreateFieldTrial(kFlagStudyName, kNonFlagGroupName);

  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
  this->CreateTrialsFromSeed(seed);
  // The previously forced experiment should still hold.
  EXPECT_EQ(kNonFlagGroupName,
            base::FieldTrialList::FindFullName(study->name()));

  // Check that params and experiment ids correspond.
  EXPECT_EQ("y", base::GetFieldTrialParamValue(study->name(), "x"));
  VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT,
                                        kFlagStudyName, kNonFlagGroupName);
  EXPECT_EQ(kExperimentId, id);
}

TYPED_TEST(VariationsSeedProcessorTest, FeatureEnabledOrDisableByTrial) {
  static BASE_FEATURE(kFeatureOffByDefault, "kOff",
                      base::FEATURE_DISABLED_BY_DEFAULT);
  static BASE_FEATURE(kFeatureOnByDefault, "kOn",
                      base::FEATURE_ENABLED_BY_DEFAULT);
  static BASE_FEATURE(kUnrelatedFeature, "kUnrelated",
                      base::FEATURE_DISABLED_BY_DEFAULT);

  struct {
    const char* enable_feature;
    const char* disable_feature;
    bool expected_feature_off_state;
    bool expected_feature_on_state;
  } test_cases_raw[] = {
      {nullptr, nullptr, false, true},
      {kFeatureOnByDefault.name, nullptr, false, true},
      {kFeatureOffByDefault.name, nullptr, true, true},
      {nullptr, kFeatureOnByDefault.name, false, false},
      {nullptr, kFeatureOffByDefault.name, false, true},
  };
  const auto test_cases = base::span(test_cases_raw);

  for (size_t i = 0; i < test_cases.size(); i++) {
    const auto& test_case = test_cases[i];
    SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]", i));

    // Needed for base::FeatureList::GetInstance() when creating field trials.
    base::test::ScopedFeatureList base_scoped_feature_list;
    base_scoped_feature_list.Init();

    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);

    VariationsSeed seed;
    Study* study = seed.add_study();
    study->set_name("Study1");
    study->set_default_experiment_name("B");
    AddExperiment("B", 0, study);

    Study::Experiment* experiment = AddExperiment("A", 1, study);
    Study::Experiment::FeatureAssociation* association =
        experiment->mutable_feature_association();
    if (test_case.enable_feature)
      association->add_enable_feature(test_case.enable_feature);
    else if (test_case.disable_feature)
      association->add_disable_feature(test_case.disable_feature);

    this->CreateTrialsFromSeed(seed, feature_list.get());
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitWithFeatureList(std::move(feature_list));

    // |kUnrelatedFeature| should not be affected.
    EXPECT_FALSE(base::FeatureList::IsEnabled(kUnrelatedFeature));

    // Before the associated feature is queried, the trial shouldn't be active.
    EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));

    EXPECT_EQ(test_case.expected_feature_off_state,
              base::FeatureList::IsEnabled(kFeatureOffByDefault));
    EXPECT_EQ(test_case.expected_feature_on_state,
              base::FeatureList::IsEnabled(kFeatureOnByDefault));

    // The field trial should get activated if it had a feature association.
    const bool expected_field_trial_active =
        test_case.enable_feature || test_case.disable_feature;
    EXPECT_EQ(expected_field_trial_active,
              base::FieldTrialList::IsTrialActive(study->name()));
  }
}

TYPED_TEST(VariationsSeedProcessorTest, FeatureAssociationAndForcing) {
  static BASE_FEATURE(kFeatureOffByDefault, "kFeatureOffByDefault",
                      base::FEATURE_DISABLED_BY_DEFAULT);
  static BASE_FEATURE(kFeatureOnByDefault, "kFeatureOnByDefault",
                      base::FEATURE_ENABLED_BY_DEFAULT);

  enum OneHundredPercentGroup {
    DEFAULT_GROUP,
    ENABLE_GROUP,
    DISABLE_GROUP,
  };

  const char kTestDefaultGroup[] = "Default";
  const char kEnabledGroup[] = "Enabled";
  const char kDisabledGroup[] = "Disabled";
  const char kForcedOnGroup[] = "ForcedOn";
  const char kForcedOffGroup[] = "ForcedOff";

  struct {
    const raw_ref<const base::Feature> feature;
    const char* enable_features_command_line;
    const char* disable_features_command_line;
    OneHundredPercentGroup one_hundred_percent_group;

    const char* expected_group;
    bool expected_feature_state;
    bool expected_trial_activated;
  } test_cases_raw[] = {
      // Check what happens without and command-line forcing flags - that the
      // |one_hundred_percent_group| gets correctly selected and does the right
      // thing w.r.t. to affecting the feature / activating the trial.
      {ToRawRef(kFeatureOffByDefault), "", "", DEFAULT_GROUP, kTestDefaultGroup,
       false, true},
      {ToRawRef(kFeatureOffByDefault), "", "", ENABLE_GROUP, kEnabledGroup,
       true, true},
      {ToRawRef(kFeatureOffByDefault), "", "", DISABLE_GROUP, kDisabledGroup,
       false, true},

      // Do the same as above, but for kFeatureOnByDefault feature.
      {ToRawRef(kFeatureOnByDefault), "", "", DEFAULT_GROUP, kTestDefaultGroup,
       true, true},
      {ToRawRef(kFeatureOnByDefault), "", "", ENABLE_GROUP, kEnabledGroup, true,
       true},
      {ToRawRef(kFeatureOnByDefault), "", "", DISABLE_GROUP, kDisabledGroup,
       false, true},

      // Test forcing each feature on and off through the command-line and that
      // the correct associated experiment gets chosen.
      {ToRawRef(kFeatureOffByDefault), kFeatureOffByDefault.name, "",
       DEFAULT_GROUP, kForcedOnGroup, true, true},
      {ToRawRef(kFeatureOffByDefault), "", kFeatureOffByDefault.name,
       DEFAULT_GROUP, kForcedOffGroup, false, true},
      {ToRawRef(kFeatureOnByDefault), kFeatureOnByDefault.name, "",
       DEFAULT_GROUP, kForcedOnGroup, true, true},
      {ToRawRef(kFeatureOnByDefault), "", kFeatureOnByDefault.name,
       DEFAULT_GROUP, kForcedOffGroup, false, true},

      // Check that even if a feature should be enabled or disabled based on the
      // the experiment probability weights, the forcing flag association still
      // takes precedence. This is 4 cases as above, but with different values
      // for |one_hundred_percent_group|.
      {ToRawRef(kFeatureOffByDefault), kFeatureOffByDefault.name, "",
       ENABLE_GROUP, kForcedOnGroup, true, true},
      {ToRawRef(kFeatureOffByDefault), "", kFeatureOffByDefault.name,
       ENABLE_GROUP, kForcedOffGroup, false, true},
      {ToRawRef(kFeatureOnByDefault), kFeatureOnByDefault.name, "",
       ENABLE_GROUP, kForcedOnGroup, true, true},
      {ToRawRef(kFeatureOnByDefault), "", kFeatureOnByDefault.name,
       ENABLE_GROUP, kForcedOffGroup, false, true},
      {ToRawRef(kFeatureOffByDefault), kFeatureOffByDefault.name, "",
       DISABLE_GROUP, kForcedOnGroup, true, true},
      {ToRawRef(kFeatureOffByDefault), "", kFeatureOffByDefault.name,
       DISABLE_GROUP, kForcedOffGroup, false, true},
      {ToRawRef(kFeatureOnByDefault), kFeatureOnByDefault.name, "",
       DISABLE_GROUP, kForcedOnGroup, true, true},
      {ToRawRef(kFeatureOnByDefault), "", kFeatureOnByDefault.name,
       DISABLE_GROUP, kForcedOffGroup, false, true},
  };
  const auto test_cases = base::span(test_cases_raw);

  for (size_t i = 0; i < test_cases.size(); i++) {
    const auto& test_case = test_cases[i];
    const int group = test_case.one_hundred_percent_group;
    SCOPED_TRACE(base::StringPrintf(
        "Test[%" PRIuS "]: %s [%s] [%s] %d", i, test_case.feature->name,
        test_case.enable_features_command_line,
        test_case.disable_features_command_line, static_cast<int>(group)));

    // Needed for base::FeatureList::GetInstance() when creating field trials.
    base::test::ScopedFeatureList base_scoped_feature_list;
    base_scoped_feature_list.Init();

    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
    feature_list->InitFromCommandLine(test_case.enable_features_command_line,
                                      test_case.disable_features_command_line);

    VariationsSeed seed;
    Study* study = seed.add_study();
    study->set_name("Study1");
    study->set_default_experiment_name(kTestDefaultGroup);
    AddExperiment(kTestDefaultGroup, group == DEFAULT_GROUP ? 1 : 0, study);

    Study::Experiment* feature_enable =
        AddExperiment(kEnabledGroup, group == ENABLE_GROUP ? 1 : 0, study);
    feature_enable->mutable_feature_association()->add_enable_feature(
        test_case.feature->name);

    Study::Experiment* feature_disable =
        AddExperiment(kDisabledGroup, group == DISABLE_GROUP ? 1 : 0, study);
    feature_disable->mutable_feature_association()->add_disable_feature(
        test_case.feature->name);

    AddExperiment(kForcedOnGroup, 0, study)
        ->mutable_feature_association()
        ->set_forcing_feature_on(test_case.feature->name);
    AddExperiment(kForcedOffGroup, 0, study)
        ->mutable_feature_association()
        ->set_forcing_feature_off(test_case.feature->name);

    this->CreateTrialsFromSeed(seed, feature_list.get());
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitWithFeatureList(std::move(feature_list));

    // Trial should not be activated initially, but later might get activated
    // depending on the expected values.
    EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
    EXPECT_EQ(test_case.expected_feature_state,
              base::FeatureList::IsEnabled(*test_case.feature));
    EXPECT_EQ(test_case.expected_trial_activated,
              base::FieldTrialList::IsTrialActive(study->name()));
  }
}

TYPED_TEST(VariationsSeedProcessorTest, DefaultAssociatedFeatures) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("Study1");
  {
    auto* feature_association =
        AddExperiment("NotSelected1", 0, study)->mutable_feature_association();
    feature_association->add_disable_feature(kEnabled.name);
    feature_association->add_enable_feature(kDisabled.name);
    feature_association->add_disable_feature(kRepeated.name);
  }
  {
    auto* feature_association =
        AddExperiment("NotSelected2", 0, study)->mutable_feature_association();
    feature_association->add_enable_feature(kRepeated.name);
  }
  AddExperiment("Expected", 100, study);

  std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
  this->CreateTrialsFromSeed(seed, feature_list.get());
  base::test::ScopedFeatureList base_scoped_feature_list;
  base_scoped_feature_list.InitWithFeatureList(std::move(feature_list));

  // All features should be associated with the group with no features, but
  // none should have their state changed.
  EXPECT_FALSE(base::FeatureList::IsEnabled(kDisabled));
  EXPECT_EQ(AssociatedStudyGroup(kDisabled), "Expected");
  EXPECT_TRUE(base::FeatureList::IsEnabled(kEnabled));
  EXPECT_EQ(AssociatedStudyGroup(kEnabled), "Expected");
  EXPECT_FALSE(base::FeatureList::IsEnabled(kRepeated));
  EXPECT_EQ(AssociatedStudyGroup(kRepeated), "Expected");
}

TYPED_TEST(VariationsSeedProcessorTest, NonDefaultAssociatedFeatures) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("Study1");
  {
    auto* feature_association =
        AddExperiment("NotSelected1", 0, study)->mutable_feature_association();
    feature_association->add_disable_feature(kEnabled.name);
    feature_association->add_enable_feature(kDisabled.name);
    feature_association->add_disable_feature(kRepeated.name);
  }
  {
    auto* feature_association =
        AddExperiment("Expected", 100, study)->mutable_feature_association();
    feature_association->add_enable_feature(kRepeated.name);
  }
  AddExperiment("Default", 0, study);

  std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
  this->CreateTrialsFromSeed(seed, feature_list.get());
  base::test::ScopedFeatureList base_scoped_feature_list;
  base_scoped_feature_list.InitWithFeatureList(std::move(feature_list));

  // Only the feature explicitly associated with the group should be enabled
  // or have it's state changed.
  EXPECT_FALSE(base::FeatureList::IsEnabled(kDisabled));
  EXPECT_EQ(AssociatedStudyGroup(kDisabled), "");
  EXPECT_TRUE(base::FeatureList::IsEnabled(kEnabled));
  EXPECT_EQ(AssociatedStudyGroup(kEnabled), "");
  EXPECT_TRUE(base::FeatureList::IsEnabled(kRepeated));
  EXPECT_EQ(AssociatedStudyGroup(kRepeated), "Expected");
}

TYPED_TEST(VariationsSeedProcessorTest, DefaultAssociatedFeaturesOnStartup) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  {
    auto* feature_association =
        AddExperiment("NotSelected1", 0, study)->mutable_feature_association();
    feature_association->add_disable_feature(kEnabled.name);
    feature_association->add_enable_feature(kDisabled.name);
    feature_association->add_disable_feature(kRepeated.name);
  }
  {
    auto* feature_association =
        AddExperiment("NotSelected2", 0, study)->mutable_feature_association();
    feature_association->add_enable_feature(kRepeated.name);
  }
  AddExperiment("Expected", 100, study);

  std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
  this->CreateTrialsFromSeed(seed, feature_list.get());
  base::test::ScopedFeatureList base_scoped_feature_list;
  base_scoped_feature_list.InitWithFeatureList(std::move(feature_list));

  // Nothing should be associated with the default group for an
  // ACTIVATE_ON_STARTUP trial.
  EXPECT_FALSE(base::FeatureList::IsEnabled(kDisabled));
  EXPECT_EQ(AssociatedStudyGroup(kDisabled), "");
  EXPECT_TRUE(base::FeatureList::IsEnabled(kEnabled));
  EXPECT_EQ(AssociatedStudyGroup(kEnabled), "");
  EXPECT_FALSE(base::FeatureList::IsEnabled(kRepeated));
  EXPECT_EQ(AssociatedStudyGroup(kRepeated), "");
}

TYPED_TEST(VariationsSeedProcessorTest, LowEntropyStudyTest) {
  const std::string kTrial1Name = "A";
  const std::string kTrial2Name = "B";
  const std::string kGroup1Name = "AA";
  const std::string kDefaultName = "Default";

  VariationsSeed seed;
  Study* study1 = seed.add_study();
  study1->set_name(kTrial1Name);
  study1->set_consistency(Study::PERMANENT);
  study1->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  study1->set_default_experiment_name(kDefaultName);
  AddExperiment(kGroup1Name, 50, study1);
  AddExperiment(kDefaultName, 50, study1);
  Study* study2 = seed.add_study();
  study2->set_name(kTrial2Name);
  study2->set_consistency(Study::PERMANENT);
  study2->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  study2->set_default_experiment_name(kDefaultName);
  AddExperiment(kGroup1Name, 50, study2);
  AddExperiment(kDefaultName, 50, study2);
  study2->mutable_experiment(0)->set_google_web_experiment_id(kExperimentId);

  this->CreateTrialsFromSeed(seed);

  // The environment will create a low entropy source that always picks the last
  // group, and if it creates a high entropy provider will create one that
  // always uses the first group.

  // Since no experiment in study1 sends experiment IDs, it will use the high
  // entropy provider when available, which selects the non-default group.
  if (this->env.HasHighEntropy()) {
    EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrial1Name));
  } else {
    EXPECT_EQ(kDefaultName, base::FieldTrialList::FindFullName(kTrial1Name));
  }

  // Since an experiment in study2 has google_web_experiment_id set, it will use
  // the low entropy provider, which selects the default group.
  EXPECT_EQ(kDefaultName, base::FieldTrialList::FindFullName(kTrial2Name));
}

TYPED_TEST(VariationsSeedProcessorTest, LimitedEntropyStudyTest) {
  VariationsSeed seed;
  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(100);
  layer->set_entropy_mode(Layer::LIMITED);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* slot = member->add_slots();
  slot->set_start(0);
  slot->set_end(99);

  const base::Time today = base::Time::Now();
  const base::Time tomorrow = today + base::Days(1);
  const base::Time in_two_days = today + base::Days(2);

  Study* study = seed.add_study();
  study->set_name("MyStudy");
  study->set_consistency(Study::PERMANENT);
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  study->set_default_experiment_name("Default");
  study->set_google_web_visibility_start_date(
      static_cast<int64_t>(tomorrow.InSecondsFSinceUnixEpoch()));
  study->set_google_web_visibility_end_date(
      static_cast<int64_t>(in_two_days.InSecondsFSinceUnixEpoch()));

  AddExperiment("Group1", 50, kExperimentId, study);
  AddExperiment(study->default_experiment_name(), 50, study);
  LayerMemberReference* layer_member_reference = study->mutable_layer();
  layer_member_reference->set_layer_id(layer->id());
  layer_member_reference->add_layer_member_ids(member->id());

  this->CreateTrialsFromSeed(seed);

  if (this->env.HasLimitedEntropy()) {
    // Expect the first group to be selected when using the limited entropy
    // provider from the setup (`kAlwaysUseFirstGroup`).
    EXPECT_EQ("Group1", base::FieldTrialList::FindFullName(study->name()));

    // Validate that the time box for the experiment has been properly
    // associated with the study and group. Query for the variation ID before
    // during, and after the visibility window.
    EXPECT_EQ(EMPTY_ID, GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT,
                                             study->name(), "Group1",
                                             today + base::Hours(1)));
    EXPECT_EQ(
        kExperimentId,
        GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, study->name(),
                             "Group1", tomorrow + base::Hours(1)));
    EXPECT_EQ(EMPTY_ID, GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT,
                                             study->name(), "Group1",
                                             in_two_days + base::Hours(1)));
  } else {
    // The study should be dropped on clients without a limited entropy
    // provider.
    EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
  }
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithInvalidLayer) {
  VariationsSeed seed;

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer = study->mutable_layer();
  layer->set_layer_id(42);
  layer->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // Since the studies references a layer which doesn't exist, it should
  // select the default group.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithInvalidLayerMember) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(1);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(2);
  Layer::LayerMember::SlotRange* slot = member->add_slots();
  slot->set_start(0);
  slot->set_end(0);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(88);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // Since the studies references a layer member which doesn't exist, it should
  // not be active.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLayerSelected) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(1);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* slot = member->add_slots();
  slot->set_start(0);
  slot->set_end(0);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // The layer only has the single member, which is what should be chosen.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLegacyLayerMemberReference) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(1);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* slot = member->add_slots();
  slot->set_start(0);
  slot->set_end(0);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  // `layer_member_id` is a legacy field that should still be considered.
  // TODO(crbug.com/TBA): remove `layer_member_id` after it's fully deprecated.
  layer_membership->set_layer_member_id(82);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
}

// TODO(b/260609574): Add a test for handling layers with unknown fields.

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLayerMemberWithNoSlots) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // The layer member referenced by the study is missing slots, and should
  // never be chosen.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLayerMemberWithUnsetSlots) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  // Add one SlotRange, with no start/end unset. This should be equivalent
  // to specifying start/end = 0, which includes slot 0 only.
  member->add_slots();

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  if (this->env.HasHighEntropy()) {
    // high entropy should select slot 0, which activates the study.
    EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
  } else {
    // low entropy should select slot 9, which does not activate the study.
    EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
  }
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLayerWithDuplicateSlots) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(1);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* first_slot = member->add_slots();
  first_slot->set_start(0);
  first_slot->set_end(0);

  // A second overlapping slot.
  Layer::LayerMember::SlotRange* second_slot = member->add_slots();
  second_slot->set_start(0);
  second_slot->set_end(0);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  // The layer should be rejected due to duplicated slot bounds.
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kInvalidSlotBounds,
                                      1);

  // The layer only has the single member, which is what should be chosen.
  // Having two duplicate slot ranges within that member should not crash.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest,
           StudyWithLayerMemberWithOutOfRangeSlots) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* overshooting_slot = member->add_slots();
  overshooting_slot->set_start(20);
  overshooting_slot->set_end(50);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  // The layer should be rejected due to invalid slot bounds.
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kInvalidSlotBounds,
                                      1);

  // The layer member referenced by the study is missing slots, and should
  // never be chosen.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLayerMemberWithReversedSlots) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* overshooting_slot = member->add_slots();
  overshooting_slot->set_start(8);
  overshooting_slot->set_end(2);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  // The layer should be rejected due to invalid slot bounds.
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kInvalidSlotBounds,
                                      1);

  // The layer member referenced by the study is has its slots in the wrong
  // order (end < start) which should cause the slot to never be chosen
  // (and not crash).
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest,
           StudyWithLayerMemberWithOutOfOrderSlots) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  {
    Layer::LayerMember::SlotRange* range = member->add_slots();
    range->set_start(8);
    range->set_end(9);
  }
  // Add a second range that is not increasing from the first one.
  {
    Layer::LayerMember::SlotRange* range = member->add_slots();
    range->set_start(1);
    range->set_end(2);
  }

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  // The layer should be rejected due to out of order slots.
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kInvalidSlotBounds,
                                      1);

  // The layer should be rejected, so the study should not be active.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithInterleavedLayerMember) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  {
    Layer::LayerMember* member = layer->add_members();
    member->set_id(82);
    {
      Layer::LayerMember::SlotRange* range = member->add_slots();
      range->set_start(0);
      range->set_end(2);
    }
    {
      Layer::LayerMember::SlotRange* range = member->add_slots();
      range->set_start(8);
      range->set_end(9);
    }
  }
  // Add a second member that is interleaved with the first one.
  {
    Layer::LayerMember* member = layer->add_members();
    member->set_id(100);
    {
      Layer::LayerMember::SlotRange* range = member->add_slots();
      range->set_start(4);
      range->set_end(5);
    }
  }

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // high entropy should select slot 0, and low entropy should select
  // slot 9, which both activate the study.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyReferencingMultipleLayerMember) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  Layer::LayerMember* member_1 = layer->add_members();
  member_1->set_id(82);
  {
    Layer::LayerMember::SlotRange* range = member_1->add_slots();
    range->set_start(0);
    range->set_end(4);
  }
  Layer::LayerMember* member_2 = layer->add_members();
  member_2->set_id(83);
  {
    Layer::LayerMember::SlotRange* range = member_2->add_slots();
    range->set_start(5);
    range->set_end(9);
  }

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(layer->id());
  layer_membership->add_layer_member_ids(member_1->id());
  layer_membership->add_layer_member_ids(member_2->id());
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // The layer members with IDs 0 and 1 cover 100% of the population. By
  // referencing both of the layer members the study must be active all the
  // time.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest,
           MultipleLayerMember_NoChangeToExistingClients_RemainderEntropy) {
  VariationsSeed seed;

  // Add a low entropy layer into the seed:
  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(10);
  layer->set_entropy_mode(Layer::LOW);

  // Populate the layer with two members covering all of the 10 slots:
  Layer::LayerMember* member_1 = layer->add_members();
  Layer::LayerMember* member_2 = layer->add_members();
  member_1->set_id(82);
  member_2->set_id(83);
  {
    Layer::LayerMember::SlotRange* range = member_1->add_slots();
    range->set_start(0);
    range->set_end(4);
  }
  {
    Layer::LayerMember::SlotRange* range = member_2->add_slots();
    range->set_start(5);
    range->set_end(9);
  }

  // Add a permanently consistent, starts-active study, and constrained it to
  // the layer member #2 in the layer:
  Study* study = seed.add_study();
  study->set_name("MyStudy");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  study->set_consistency(Study_Consistency_PERMANENT);
  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(layer->id());
  layer_membership->add_layer_member_ids(member_2->id());

  // Add two experiments with google_web_experiment_id. This setup forces the
  // study to be randomized using remainder entropy from the slot randomization.
  // See VariationsLayers::SelectEntropyProviderForStudy().
  AddExperiment("A", 1, study);
  AddExperiment("B", 1, study);
  study->mutable_experiment(0)->set_google_web_experiment_id(1001);
  study->mutable_experiment(1)->set_google_web_experiment_id(1002);

  this->CreateTrialsFromSeed(seed);

  // Verify that the study is active, with group A selected:
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
  EXPECT_EQ(base::FieldTrialList::Find(study->name())->group_name(), "A");

  // Clear field trial states:
  test::ClearAllVariationIDs();
  test::ClearAllVariationParams();

  // Give this study a new layer member constraint, and randomize it again:
  layer_membership->add_layer_member_ids(member_1->id());
  this->CreateTrialsFromSeed(seed);

  // The randomization of this exact same client is not affected, and it will
  // still randomize to group A. This verifies that existing clients using
  // remainder entropy will not be re-shuffled if the study is constrained to
  // another layer member.
  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
  EXPECT_EQ(base::FieldTrialList::Find(study->name())->group_name(), "A");
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLayerNotSelected) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(8000);
  // Setting this forces the provided entropy provider to be used when
  // calling CreateTrialsFromSeed.
  layer->set_entropy_mode(Layer::LOW);

  // Member with most slots, but won't be chosen due to the entropy provided.
  {
    Layer::LayerMember* member = layer->add_members();
    member->set_id(0xDEAD);
    Layer::LayerMember::SlotRange* slot = member->add_slots();
    slot->set_start(0);
    slot->set_end(7900);
  }

  // Member with few slots, but will be chosen.
  {
    Layer::LayerMember* member = layer->add_members();
    member->set_id(0xBEEF);
    Layer::LayerMember::SlotRange* slot = member->add_slots();
    slot->set_start(7901);
    slot->set_end(7999);
  }

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(0xDEAD);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // Low entropy should select slot 7999, which should not select layer 0xDEAD,
  // and the study should not be activated.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, LayerWithDefaultEntropy) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(8000);

  // Member which should get chosen by the default high entropy source
  // (which defaults to half of the num_slots in tests).
  {
    Layer::LayerMember* member = layer->add_members();
    member->set_id(0xDEAD);
    Layer::LayerMember::SlotRange* slot = member->add_slots();
    slot->set_start(0);
    slot->set_end(7900);
  }

  // Member with few slots,
  {
    Layer::LayerMember* member = layer->add_members();
    member->set_id(0xBEEF);
    Layer::LayerMember::SlotRange* slot = member->add_slots();
    slot->set_start(7901);
    slot->set_end(7999);
  }

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(0xDEAD);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  if (this->env.HasHighEntropy()) {
    // The high entropy source should select slot 0, which should select
    // the member 0xDEAD and activate the study.
    EXPECT_TRUE(base::FieldTrialList::IsTrialActive(study->name()));
  } else {
    // The low entropy source should select slot 7999, which should NOT select
    // the member 0xDEAD, so the study will be inactive.
    EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
  }
}

TYPED_TEST(VariationsSeedProcessorTest, LayerWithNoMembers) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(1);
  layer->set_num_slots(1);
  layer->set_salt(0xBEEF);

  // Layer should be rejected and not crash.
  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kNoMembers, 1);
}

TYPED_TEST(VariationsSeedProcessorTest, LayerWithNoSlots) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(1);
  layer->set_salt(0xBEEF);

  // Layer should be rejected and not crash.
  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kNoSlots, 1);
}

TYPED_TEST(VariationsSeedProcessorTest, LayerWithNoID) {
  VariationsSeed seed;
  Layer* layer = seed.add_layers();
  layer->set_salt(0xBEEF);

  // Layer should be rejected and not crash.
  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kInvalidId, 1);
}

TYPED_TEST(VariationsSeedProcessorTest, EmptyLayer) {
  VariationsSeed seed;
  seed.add_layers();

  // Layer should be rejected and not crash.
  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kInvalidId, 1);
}

TYPED_TEST(VariationsSeedProcessorTest, LayersWithDuplicateID) {
  VariationsSeed seed;

  {
    Layer* layer = seed.add_layers();
    layer->set_id(1);
    layer->set_salt(0xBEEF);
    layer->set_num_slots(1);
    Layer::LayerMember* member = layer->add_members();
    member->set_id(82);
    Layer::LayerMember::SlotRange* slot = member->add_slots();
    slot->set_start(0);
    slot->set_end(0);
  }

  {
    Layer* layer = seed.add_layers();
    layer->set_id(1);
    layer->set_salt(0xBEEF);
    layer->set_num_slots(1);
    Layer::LayerMember* member = layer->add_members();
    member->set_id(82);
    Layer::LayerMember::SlotRange* slot = member->add_slots();
    slot->set_start(0);
    slot->set_end(0);
  }

  // The duplicate layer should be rejected and not crash.
  this->CreateTrialsFromSeed(seed);
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLayerMemberWithoutID) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(1);
  Layer::LayerMember* member = layer->add_members();
  Layer::LayerMember::SlotRange* slot = member->add_slots();
  slot->set_start(0);
  slot->set_end(0);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  AddExperiment("A", 1, study);

  this->CreateTrialsFromSeed(seed);

  // The layer only has the single member but that member has no
  // ID set. The LayerMembership also has no member_id set. The study
  // should then *not* be chosen (i.e. a default initialized ID of 0
  // should not be seen as valid.)
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudyWithLowerEntropyThanLayer) {
  VariationsSeed seed;

  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(1);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* slot = member->add_slots();
  slot->set_start(0);
  slot->set_end(0);

  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);
  study->mutable_experiment(0)->set_google_web_experiment_id(kExperimentId);

  this->CreateTrialsFromSeed(seed);

  // Since the study will use the low entropy source and the layer the default
  // one, the study should be rejected.
  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study->name()));
}

TYPED_TEST(VariationsSeedProcessorTest, StudiesWithOverlappingEnabledFeatures) {
  static BASE_FEATURE(kFeature, "FeatureName",
                      base::FEATURE_ENABLED_BY_DEFAULT);

  VariationsSeed seed;

  // Create two studies that enable |kFeature|.
  Study* flags_study = seed.add_study();
  flags_study->set_name("FlagsStudy");
  flags_study->set_default_experiment_name("A");
  flags_study->set_activation_type(Study_ActivationType_ACTIVATE_ON_STARTUP);
  Study::Experiment* experiment =
      AddExperiment("A", /*probability=*/1, flags_study);
  experiment->mutable_feature_association()->add_enable_feature(kFeature.name);

  Study* server_side_study = seed.add_study();
  server_side_study->set_name("ServerSideStudy");
  server_side_study->set_default_experiment_name("A");
  server_side_study->set_activation_type(
      Study_ActivationType_ACTIVATE_ON_STARTUP);
  AddGoogleGroupFilter(*server_side_study);
  Study::Experiment* experiment2 =
      AddExperiment("A", /*probability=*/1, server_side_study);
  experiment2->mutable_feature_association()->add_enable_feature(kFeature.name);

  this->CreateTrialsFromSeed(seed);

  // Verify that FlagsStudy was created and activated, and that the "A"
  // experiment group was selected.
  ASSERT_TRUE(base::FieldTrialList::IsTrialActive(flags_study->name()));
  EXPECT_EQ(base::FieldTrialList::Find(flags_study->name())->group_name(), "A");

  // Verify that ServerSideStudy was created and activated, but that the
  // |kFeatureConflictGroupName| experiment group was forcibly selected due to
  // the study being associated with |kFeature| (which is already associated
  // with trial FlagsStudy).
  ASSERT_TRUE(base::FieldTrialList::IsTrialActive(server_side_study->name()));
  EXPECT_EQ(base::FieldTrialList::Find(server_side_study->name())->group_name(),
            internal::kFeatureConflictGroupName);

  // Only one of the studies is returned by the default field trial list (as
  // the second is low-anonymity).
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  EXPECT_EQ(active_groups.size(), 1u);

  // Both studies are returned by in the full list including low anonymity.
  base::FieldTrial::ActiveGroups active_groups_including_low_anonymity;
  base::FieldTrialListIncludingLowAnonymity::
      GetActiveFieldTrialGroupsForTesting(
          &active_groups_including_low_anonymity);
  EXPECT_EQ(active_groups_including_low_anonymity.size(), 2u);
}

TYPED_TEST(VariationsSeedProcessorTest,
           StudiesWithOverlappingDisabledFeatures) {
  static BASE_FEATURE(kFeature, "FeatureName",
                      base::FEATURE_ENABLED_BY_DEFAULT);

  VariationsSeed seed;

  // Create two studies that disable |kFeature|.
  Study* flags_study = seed.add_study();
  flags_study->set_name("FlagsStudy");
  flags_study->set_default_experiment_name("A");
  flags_study->set_activation_type(Study_ActivationType_ACTIVATE_ON_STARTUP);
  Study::Experiment* experiment =
      AddExperiment("A", /*probability=*/1, flags_study);
  experiment->mutable_feature_association()->add_disable_feature(kFeature.name);

  Study* server_side_study = seed.add_study();
  server_side_study->set_name("ServerSideStudy");
  server_side_study->set_default_experiment_name("A");
  server_side_study->set_activation_type(
      Study_ActivationType_ACTIVATE_ON_STARTUP);
  AddGoogleGroupFilter(*server_side_study);
  Study::Experiment* experiment2 =
      AddExperiment("A", /*probability=*/1, server_side_study);
  experiment2->mutable_feature_association()->add_disable_feature(
      kFeature.name);

  this->CreateTrialsFromSeed(seed);

  // Verify that FlagsStudy was created and activated, and that the "A"
  // experiment group was selected.
  ASSERT_TRUE(base::FieldTrialList::IsTrialActive(flags_study->name()));
  EXPECT_EQ(base::FieldTrialList::Find(flags_study->name())->group_name(), "A");

  // Verify that ServerSideStudy was created and activated, but that the
  // |kFeatureConflictGroupName| experiment group was forcibly selected due to
  // the study being associated with |kFeature| (which is already associated
  // with trial FlagsStudy).
  ASSERT_TRUE(base::FieldTrialList::IsTrialActive(server_side_study->name()));
  EXPECT_EQ(base::FieldTrialList::Find(server_side_study->name())->group_name(),
            internal::kFeatureConflictGroupName);

  // Only one of the studies is returned by the default field trial list (as
  // the second is low-anonymity).
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  EXPECT_EQ(active_groups.size(), 1u);

  // Both studies are returned by in the full list including low anonymity.
  base::FieldTrial::ActiveGroups active_groups_including_low_anonymity;
  base::FieldTrialListIncludingLowAnonymity::
      GetActiveFieldTrialGroupsForTesting(
          &active_groups_including_low_anonymity);
  EXPECT_EQ(active_groups_including_low_anonymity.size(), 2u);
}

TYPED_TEST(VariationsSeedProcessorTest, OutOfBoundsLayer) {
  VariationsSeed seed;
  // Define an invalid layer with out of bounds slots.
  Layer* layer = seed.add_layers();
  layer->set_id(42);
  layer->set_num_slots(8000);
  Layer::LayerMember* member = layer->add_members();
  member->set_id(82);
  Layer::LayerMember::SlotRange* slot = member->add_slots();
  slot->set_start(0);
  slot->set_end(0x7fffffff);

  // Add a study that uses it with remainder entropy.
  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  LayerMemberReference* layer_membership = study->mutable_layer();
  layer_membership->set_layer_id(42);
  layer_membership->add_layer_member_ids(82);
  AddExperiment("A", 1, study);
  study->mutable_experiment(0)->set_google_web_experiment_id(kExperimentId);
  AddExperiment("B", 1, study);

  // Layer should be rejected and not crash or timeout.
  base::HistogramTester histogram_tester;
  this->CreateTrialsFromSeed(seed);
  histogram_tester.ExpectUniqueSample("Variations.InvalidLayerReason",
                                      InvalidLayerReason::kInvalidSlotBounds,
                                      1);
}

TYPED_TEST(VariationsSeedProcessorTest,
           StudyWithGoogleGroupFilterIsLowAnonymity) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("A");
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("AA", 100, study);
  AddExperiment("Default", 0, study);
  AddGoogleGroupFilter(*study);

  this->CreateTrialsFromSeed(seed);

  // This study should be marked as low anonymity, and therefore only returned
  // by |FieldTrialListIncludingLowAnonymity|.
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  EXPECT_EQ(active_groups.size(), 0u);

  base::FieldTrial::ActiveGroups active_groups_including_low_anonymity;
  base::FieldTrialListIncludingLowAnonymity::
      GetActiveFieldTrialGroupsForTesting(
          &active_groups_including_low_anonymity);
  EXPECT_EQ(active_groups_including_low_anonymity.size(), 1u);
}

// Tests that studies with filters with a `google_groups` parameter generate a
// field trial parameter that contains the Google Groups ids for that study.
TYPED_TEST(VariationsSeedProcessorTest,
           StudyWithGoogleGroupFilterGeneratesFieldTrialParam) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("A");
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("AA", 100, study);
  AddExperiment("Default", 0, study);
  AddGoogleGroupFilter(*study);

  this->CreateTrialsFromSeed(seed);

  EXPECT_EQ(base::NumberToString(kExampleGoogleGroup),
            base::GetFieldTrialParamValue(
                "A", internal::kGoogleGroupFeatureParamName));
}

TYPED_TEST(VariationsSeedProcessorTest,
           StudyWithExcludeGoogleGroupFilterIsNotLowAnonymity) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("A");
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("AA", 100, study);
  AddExperiment("Default", 0, study);

  // Add a study filter that excludes a Google group, which this client is not
  // a member of (i.e. the client does select this study).
  Study::Filter* filter = study->mutable_filter();
  filter->add_exclude_google_group(987654);
  // Also add a platform filter that matches both the environments we're
  // testing in the typed tests.
  filter->add_platform(Study::PLATFORM_ANDROID);
  filter->add_platform(Study::PLATFORM_ANDROID_WEBVIEW);

  this->CreateTrialsFromSeed(seed);

  // This study should not be marked as low anonymity, and therefore is returned
  // by both APIs.
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  EXPECT_EQ(active_groups.size(), 1u);

  base::FieldTrial::ActiveGroups active_groups_including_low_anonymity;
  base::FieldTrialListIncludingLowAnonymity::
      GetActiveFieldTrialGroupsForTesting(
          &active_groups_including_low_anonymity);
  EXPECT_EQ(active_groups_including_low_anonymity.size(), 1u);
}

TYPED_TEST(VariationsSeedProcessorTest,
           StudyWithIncludeEnterpriseGroupFilterIsLowAnonymity) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("A");
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("AA", 100, study);
  AddExperiment("Default", 0, study);

  Study::Filter* filter = study->mutable_filter();
  filter->add_enterprise_group(kExampleEnterpriseGroup);
  filter->add_platform(Study::PLATFORM_ANDROID);
  filter->add_platform(Study::PLATFORM_ANDROID_WEBVIEW);

  this->CreateTrialsFromSeed(seed);

  // This study should be marked as low anonymity, and therefore only returned
  // by |FieldTrialListIncludingLowAnonymity|.
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  EXPECT_EQ(active_groups.size(), 0u);

  base::FieldTrial::ActiveGroups active_groups_including_low_anonymity;
  base::FieldTrialListIncludingLowAnonymity::
      GetActiveFieldTrialGroupsForTesting(
          &active_groups_including_low_anonymity);
  EXPECT_EQ(active_groups_including_low_anonymity.size(), 1u);
}

TYPED_TEST(VariationsSeedProcessorTest,
           StudyWithExcludeEnterpriseGroupFilterIsNotLowAnonymity) {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("A");
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("AA", 100, study);
  AddExperiment("Default", 0, study);

  Study::Filter* filter = study->mutable_filter();
  filter->add_exclude_enterprise_group("excluded_customers");
  filter->add_platform(Study::PLATFORM_ANDROID);
  filter->add_platform(Study::PLATFORM_ANDROID_WEBVIEW);

  this->CreateTrialsFromSeed(seed);

  // This study should not be marked as low anonymity, and therefore is returned
  // by both APIs.
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  EXPECT_EQ(active_groups.size(), 1u);

  base::FieldTrial::ActiveGroups active_groups_including_low_anonymity;
  base::FieldTrialListIncludingLowAnonymity::
      GetActiveFieldTrialGroupsForTesting(
          &active_groups_including_low_anonymity);
  EXPECT_EQ(active_groups_including_low_anonymity.size(), 1u);
}

TYPED_TEST(VariationsSeedProcessorTest, SimulateCreateTrialFromStudy_Basic) {
  // Create a seed with a single study, which in turn has 100 Enabled groups
  // (Enabled0, Enabled1, ..., Enabled99).
  VariationsSeed seed;
  Study* study = seed.add_study();
  const char kStudyName[] = "SimulationStudy";
  study->set_name(kStudyName);
  study->set_consistency(Study_Consistency_PERMANENT);
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("Default", 0, study);
  for (int i = 0; i < 100; ++i) {
    Study::Experiment* enabled_exp = AddExperiment(
        base::StrCat({"Enabled", base::NumberToString(i)}), 1, study);
    enabled_exp->set_google_web_experiment_id(i);
    Study::Experiment::Param* param = enabled_exp->add_param();
    param->set_name("param");
    param->set_value(base::NumberToString(i));
    enabled_exp->mutable_feature_association()->add_enable_feature(
        kDisabled.name);
  }

  std::unique_ptr<base::FeatureList> feature_list =
      std::make_unique<base::FeatureList>();
  EntropyProviders entropy_providers(
      /*high_entropy_source=*/"client_id",
      /*low_entropy_source=*/{123, metrics::EntropyState::kMaxLowEntropySize},
      /*limited_entropy_source=*/"limited");
  VariationsLayers layers(seed, entropy_providers);
  ProcessedStudy processed_study;
  ASSERT_TRUE(processed_study.Init(study));

  StickyActivationManager sticky_activation_manager(/*local_state=*/nullptr);
  VariationsSeedProcessor seed_processor(sticky_activation_manager);

  // Run the simulation.
  scoped_refptr<base::FieldTrial> simulated_trial =
      this->CreateTrialFromStudy(seed_processor, processed_study,
                                 entropy_providers, layers, feature_list.get(),
                                 /*simulated=*/true);
  ASSERT_TRUE(simulated_trial);
  EXPECT_EQ(simulated_trial->trial_name(), kStudyName);
  std::string simulated_group_name =
      simulated_trial->GetGroupNameWithoutActivation();
  EXPECT_EQ(simulated_group_name, "Enabled11");

  // Verify NO side effects:
  // 1. Not registered in field trial list.
  EXPECT_EQ(base::FieldTrialList::Find(kStudyName), nullptr);
  // 2. No variation IDs registered.
  EXPECT_EQ(GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, kStudyName,
                                 simulated_group_name),
            EMPTY_ID);
  // 3. No params registered.
  EXPECT_EQ(base::GetFieldTrialParamValue(kStudyName, "param"), "");
  // 4. Feature overrides are not registered/active.
  {
    base::test::ScopedFeatureList base_scoped_feature_list;
    base_scoped_feature_list.InitWithFeatureList(std::move(feature_list));
    EXPECT_FALSE(base::FeatureList::IsEnabled(kDisabled));
  }

  // Now, do the trial assignment for real -- we should end up in the same group
  // we simulated.
  feature_list = std::make_unique<base::FeatureList>();
  this->CreateTrialFromStudy(seed_processor, processed_study, entropy_providers,
                             layers, feature_list.get());
  // 1. Registered in field trial list.
  ASSERT_TRUE(base::FieldTrialList::Find(kStudyName));
  EXPECT_EQ(base::FieldTrialList::Find(kStudyName)->group_name(),
            simulated_group_name);
  // 2. Variation IDs registered.
  EXPECT_EQ(GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, kStudyName,
                                 simulated_group_name),
            11);
  // 3. Params registered.
  EXPECT_EQ(base::GetFieldTrialParamValue(kStudyName, "param"), "11");
  // 4. Feature overrides are registered/active.
  {
    base::test::ScopedFeatureList base_scoped_feature_list;
    base::FeatureList* feature_list_ptr = feature_list.get();
    base_scoped_feature_list.InitWithFeatureList(std::move(feature_list));
    EXPECT_TRUE(base::FeatureList::IsEnabled(kDisabled));

    // For good measure, also try a simulation with a finalized FeatureList.
    // As simulations have no side-effects, the FeatureList should not be
    // modified (DCHECKs would be triggered if it were).
    simulated_trial = this->CreateTrialFromStudy(
        seed_processor, processed_study, entropy_providers, layers,
        feature_list_ptr, /*simulated=*/true);
    ASSERT_TRUE(simulated_trial);
    EXPECT_EQ(simulated_trial->GetGroupNameWithoutActivation(),
              simulated_group_name);
  }
}

TYPED_TEST(VariationsSeedProcessorTest,
           SimulateCreateTrialFromStudy_ExistingTrial) {
  // Create a seed with a single study, which in turn has a single Enabled
  // group.
  VariationsSeed seed;
  Study* study = seed.add_study();
  const char kStudyName[] = "SimulationStudy";
  study->set_name(kStudyName);
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("Default", 0, study);
  Study::Experiment* enabled_exp = AddExperiment("Enabled", 100, study);
  enabled_exp->set_google_web_experiment_id(kExperimentId);
  Study::Experiment::Param* param = enabled_exp->add_param();
  param->set_name("x");
  param->set_value("y");
  enabled_exp->mutable_feature_association()->add_enable_feature(
      kDisabled.name);

  std::unique_ptr<base::FeatureList> feature_list =
      std::make_unique<base::FeatureList>();
  EntropyProviders entropy_providers(
      /*high_entropy_source=*/"client_id",
      /*low_entropy_source=*/{123, metrics::EntropyState::kMaxLowEntropySize},
      /*limited_entropy_source=*/"limited");
  VariationsLayers layers(seed, entropy_providers);
  ProcessedStudy processed_study;
  ASSERT_TRUE(processed_study.Init(study));

  StickyActivationManager sticky_activation_manager(/*local_state=*/nullptr);
  VariationsSeedProcessor seed_processor(sticky_activation_manager);

  // Do a real trial assignment for the study.
  this->CreateTrialFromStudy(seed_processor, processed_study, entropy_providers,
                             layers, feature_list.get());
  // 1. Registered in field trial list.
  ASSERT_TRUE(base::FieldTrialList::Find(kStudyName));
  std::string group_name = base::FieldTrialList::Find(kStudyName)->group_name();
  EXPECT_EQ(group_name, "Enabled");
  // 2. Variation IDs registered.
  EXPECT_EQ(GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, kStudyName,
                                 group_name),
            kExperimentId);
  // 3. Params registered.
  EXPECT_EQ(base::GetFieldTrialParamValue(kStudyName, "x"), "y");
  // 4. Feature overrides are registered/active.
  base::test::ScopedFeatureList base_scoped_feature_list;
  base::FeatureList* feature_list_ptr = feature_list.get();
  base_scoped_feature_list.InitWithFeatureList(std::move(feature_list));
  EXPECT_TRUE(base::FeatureList::IsEnabled(kDisabled));

  // Create a new seed with the the same study, but this time with only a
  // Disabled group. Simulate a trial assignment, and verify that the existing
  // trial is not modified and does not influence the simulation.
  VariationsSeed seed2;
  Study* study2 = seed2.add_study();
  study2->set_name(kStudyName);
  study2->set_consistency(Study_Consistency_PERMANENT);
  study2->set_default_experiment_name("Default");
  study2->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("Default", 0, study2);
  Study::Experiment* disabled_exp = AddExperiment("Disabled", 100, study2);
  disabled_exp->set_google_web_experiment_id(kExperimentId + 1);
  Study::Experiment::Param* param2 = disabled_exp->add_param();
  param2->set_name("x");
  param2->set_value("z");
  Study::Experiment::Param* param3 = disabled_exp->add_param();
  param3->set_name("y");
  param3->set_value("z");
  disabled_exp->mutable_feature_association()->add_disable_feature(
      kDisabled.name);

  ProcessedStudy processed_study2;
  ASSERT_TRUE(processed_study2.Init(study2));
  scoped_refptr<base::FieldTrial> simulated_trial =
      this->CreateTrialFromStudy(seed_processor, processed_study2,
                                 entropy_providers, layers, feature_list_ptr,
                                 /*simulated=*/true);

  ASSERT_TRUE(simulated_trial);
  EXPECT_EQ(simulated_trial->trial_name(), kStudyName);
  std::string simulated_group_name =
      simulated_trial->GetGroupNameWithoutActivation();
  EXPECT_EQ(simulated_group_name, "Disabled");

  // Verify that simulation did NOT register new study params, variation IDs, or
  // feature overrides to the existing trial.
  EXPECT_EQ(base::FieldTrialList::Find(kStudyName)->group_name(), group_name);
  EXPECT_EQ(GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, kStudyName,
                                 group_name),
            kExperimentId);
  EXPECT_EQ(GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, kStudyName,
                                 simulated_group_name),
            EMPTY_ID);
  EXPECT_EQ(base::GetFieldTrialParamValue(kStudyName, "x"), "y");
  EXPECT_EQ(base::GetFieldTrialParamValue(kStudyName, "y"), "");
  EXPECT_TRUE(base::FeatureList::IsEnabled(kDisabled));
}

TYPED_TEST(VariationsSeedProcessorTest,
           SimulateCreateTrialFromStudy_ForcingFeatureOrFlag) {
  // Force via switch.
  const char kForcingSwitch[] = "simulation-forcing-flag";
  base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingSwitch);

  VariationsSeed seed;
  Study* study = seed.add_study();
  const char kStudyName[] = "SimulationStudy";
  study->set_name(kStudyName);
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);

  // Create a default experiment group with 100% probability.
  AddExperiment("Default", 100, study);

  // Create a forcing group with 0% probability. Under normal circumstances,
  // this group would never be selected. However, as we specify the forcing
  // switch in the command line, this group should be forcibly selected.
  Study::Experiment* forced_exp = AddExperiment("ForcedGroup", 0, study);
  forced_exp->set_forcing_flag(kForcingSwitch);
  forced_exp->set_google_web_experiment_id(kExperimentId);
  Study::Experiment::Param* param = forced_exp->add_param();
  param->set_name("x");
  param->set_value("y");

  std::unique_ptr<base::FeatureList> feature_list =
      std::make_unique<base::FeatureList>();
  EntropyProviders entropy_providers(
      /*high_entropy_source=*/"client_id",
      /*low_entropy_source=*/{123, metrics::EntropyState::kMaxLowEntropySize},
      /*limited_entropy_source=*/"limited");
  VariationsLayers layers(seed, entropy_providers);
  ProcessedStudy processed_study;
  ASSERT_TRUE(processed_study.Init(study));

  StickyActivationManager sticky_activation_manager(/*local_state=*/nullptr);
  VariationsSeedProcessor seed_processor(sticky_activation_manager);

  // Run simulation.
  scoped_refptr<base::FieldTrial> simulated_trial =
      this->CreateTrialFromStudy(seed_processor, processed_study,
                                 entropy_providers, layers, feature_list.get(),
                                 /*simulated=*/true);
  ASSERT_TRUE(simulated_trial);
  EXPECT_EQ(simulated_trial->trial_name(), kStudyName);
  std::string simulated_group_name =
      simulated_trial->GetGroupNameWithoutActivation();
  EXPECT_EQ(simulated_trial->GetGroupNameWithoutActivation(), "ForcedGroup");

  // Verify NO side effects.
  EXPECT_EQ(base::FieldTrialList::Find(kStudyName), nullptr);
  EXPECT_EQ(GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, kStudyName,
                                 simulated_group_name),
            EMPTY_ID);
  EXPECT_EQ(base::GetFieldTrialParamValue(kStudyName, "x"), "");
}

TYPED_TEST(VariationsSeedProcessorTest,
           CreateTrialsFromSeed_SimulatesAndValidates) {
  base::MetricsSubSampler::ScopedAlwaysSampleForTesting always_sample;
  base::HistogramTester histogram_tester;

  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("Default", 100, study);

  this->CreateTrialsFromSeed(seed);

  histogram_tester.ExpectUniqueSample(
      "Variations.CreateTrial.SimulationMatches", true, 1);
}

TYPED_TEST(VariationsSeedProcessorTest,
           CreateTrialsFromSeed_SimulationNotSampled) {
  base::MetricsSubSampler::ScopedNeverSampleForTesting never_sample;
  base::HistogramTester histogram_tester;

  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name("Study1");
  study->set_default_experiment_name("Default");
  study->set_activation_type(Study::ACTIVATE_ON_STARTUP);
  AddExperiment("Default", 100, study);

  this->CreateTrialsFromSeed(seed);

  histogram_tester.ExpectTotalCount("Variations.CreateTrial.SimulationMatches",
                                    0);
}

}  // namespace variations
