chromium/components/variations/service/variations_field_trial_creator_unittest.cc

// Copyright 2017 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/service/variations_field_trial_creator.h"

#include <stddef.h>

#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/base_switches.h"
#include "base/build_time.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_mock_clock_override.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "components/metrics/clean_exit_beacon.h"
#include "components/metrics/client_info.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/test/test_enabled_state_provider.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/field_trial_config/field_trial_util.h"
#include "components/variations/limited_entropy_mode_gate.h"
#include "components/variations/platform_field_trials.h"
#include "components/variations/pref_names.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "components/variations/service/buildflags.h"
#include "components/variations/service/limited_entropy_synthetic_trial.h"
#include "components/variations/service/safe_seed_manager.h"
#include "components/variations/service/variations_field_trial_creator_base.h"
#include "components/variations/service/variations_service.h"
#include "components/variations/service/variations_service_client.h"
#include "components/variations/synthetic_trial_registry.h"
#include "components/variations/synthetic_trials.h"
#include "components/variations/variations_safe_seed_store_local_state.h"
#include "components/variations/variations_seed_store.h"
#include "components/variations/variations_switches.h"
#include "components/variations/variations_test_utils.h"
#include "components/version_info/channel.h"
#include "components/version_info/version_info.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_ANDROID)
#include "components/variations/seed_response.h"
#endif

namespace variations {
namespace {

_;
Ge;
NiceMock;
Return;

// Constants used to create the test seeds.
const char kTestSeedStudyName[] =;
const char kTestLimitedLayerStudyName[] =;
const char kTestSeedExperimentName[] =;
const char kTestSafeSeedExperimentName[] =;
const int kTestSeedExperimentProbability =;
const char kTestSeedSerialNumber[] =;

// Constants used to mock the serialized seed state.
const char kTestSeedSerializedData[] =;
const char kTestSeedSignature[] =;
const int kTestSeedMilestone =;

struct FetchAndLaunchTimeTestParams {};

std::unique_ptr<VariationsSeedStore> CreateSeedStore(PrefService* local_state) {}

// Returns a seed with simple test data. The seed has a single study,
// "UMA-Uniformity-Trial-10-Percent", which has a single experiment, "abc", with
// probability weight 100.
VariationsSeed CreateTestSeed() {}

// Returns a test seed that contains a single study,
// "UMA-Uniformity-Trial-10-Percent", which has a single experiment, "abc", with
// probability weight 100. The study references the 100% slot of a LIMITED
// entropy layer. The LIMITED layer created will use 0 bit of entropy.
VariationsSeed CreateTestSeedWithLimitedEntropyLayer() {}

VariationsSeed CreateTestSeedWithLimitedEntropyLayerUsingExcessiveEntropy() {}

// Returns a seed with simple test data. The seed has a single study,
// "UMA-Uniformity-Trial-10-Percent", which has a single experiment,
// "abc.safe", with probability weight 100.
//
// Intended to be used when a "safe" seed is needed so that test expectations
// can distinguish between a regular and safe seeds.
VariationsSeed CreateTestSafeSeed() {}

// A base::Time instance representing a time in the distant past. Here, it would
// return the start for epoch in Unix-like system (Jan 1, 1970).
base::Time DistantPast() {}

#if BUILDFLAG(IS_ANDROID)
const char kTestSeedCountry[] = "in";

// Populates |seed| with simple test data, targetting only users in a specific
// country. The resulting seed will contain one study called "test", which
// contains one experiment called "abc" with probability weight 100, restricted
// just to users in |kTestSeedCountry|.
VariationsSeed CreateTestSeedWithCountryFilter() {
  VariationsSeed seed = CreateTestSeed();
  Study* study = seed.mutable_study(0);
  Study::Filter* filter = study->mutable_filter();
  filter->add_country(kTestSeedCountry);
  filter->add_platform(Study::PLATFORM_ANDROID);
  return seed;
}

// Serializes |seed| to protobuf binary format.
std::string SerializeSeed(const VariationsSeed& seed) {
  std::string serialized_seed;
  seed.SerializeToString(&serialized_seed);
  return serialized_seed;
}
#endif  // BUILDFLAG(IS_ANDROID)

class MockSafeSeedManager : public SafeSeedManager {};

class FakeSyntheticTrialObserver : public SyntheticTrialObserver {};

// TODO(crbug.com/40742801): Remove when fake VariationsServiceClient created.
class TestVariationsServiceClient : public VariationsServiceClient {};

class TestLimitedEntropySyntheticTrial : public LimitedEntropySyntheticTrial {};

class MockVariationsServiceClient : public TestVariationsServiceClient {};

class TestVariationsSeedStore : public VariationsSeedStore {};

class TestVariationsFieldTrialCreator : public VariationsFieldTrialCreator {};

}  // namespace

class FieldTrialCreatorTest : public ::testing::Test {};

namespace {

class FieldTrialCreatorFetchAndLaunchTimeTest
    : public FieldTrialCreatorTest,
      public ::testing::WithParamInterface<FetchAndLaunchTimeTestParams> {};

constexpr FetchAndLaunchTimeTestParams kAllFetchAndLaunchTimes[] =;

}  // namespace

INSTANTIATE_TEST_SUITE_P();

// Verify that unexpired seeds are used.
TEST_P(FieldTrialCreatorFetchAndLaunchTimeTest,
       SetUpFieldTrials_ValidSeed_NotExpired) {}

TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_ValidSeed_NoLastFetchTime) {}

// Verify that a regular seed can be used when the milestone with which the seed
// was fetched is unknown. This can happen if the seed was fetched before the
// milestone pref was added.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_ValidSeed_NoMilestone) {}

// Verify that no seed is applied when the seed has expired.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_ExpiredSeed) {}

// Verify that a regular seed is not used when the milestone with which it was
// fetched is greater than the client's milestone.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_FutureMilestone) {}

// Verify that unexpired safe seeds are used.
TEST_P(FieldTrialCreatorFetchAndLaunchTimeTest,
       SetUpFieldTrials_ValidSafeSeed_NewBinaryUsesSeed) {}

// Verify that Chrome does not apply a variations seed when Chrome should run in
// Variations Safe Mode but the safe seed is unloadable.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_UnloadableSafeSeedNotUsed) {}

// Verify that valid safe seeds with missing download times are applied.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_ValidSafeSeed_NoLastFetchTime) {}

// Verify that no seed is applied when (i) safe mode is triggered and (ii) the
// loaded safe seed has expired.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_ExpiredSafeSeed) {}

// Verify that no seed is applied when (i) safe mode is triggered and (ii) the
// loaded safe seed was fetched with a future milestone.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_SafeSeedForFutureMilestone) {}

// Verify that no seed is applied when null seed is triggered.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_NullSeed) {}

TEST_F(FieldTrialCreatorTest, LoadSeedFromTestSeedJsonPath) {}

#if BUILDFLAG(IS_ANDROID)
// This is a regression test for crbug/829527.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrials_LoadsCountryOnFirstRun) {
  // Simulate having received a seed in Java during First Run.
  const base::Time one_day_ago = base::Time::Now() - base::Days(1);
  auto initial_seed = std::make_unique<SeedResponse>();
  initial_seed->data = SerializeSeed(CreateTestSeedWithCountryFilter());
  initial_seed->signature = kTestSeedSignature;
  initial_seed->country = kTestSeedCountry;
  initial_seed->date = one_day_ago;
  initial_seed->is_gzip_compressed = false;

  TestVariationsServiceClient variations_service_client;
  PlatformFieldTrials platform_field_trials;
  NiceMock<MockSafeSeedManager> safe_seed_manager(local_state());

  // Note: Unlike other tests, this test does not mock out the seed store, since
  // the interaction between these two classes is what's being tested.
  auto seed_store = std::make_unique<VariationsSeedStore>(
      local_state(), std::move(initial_seed),
      /*signature_verification_enabled=*/false,
      std::make_unique<VariationsSafeSeedStoreLocalState>(local_state()));
  VariationsFieldTrialCreator field_trial_creator(
      &variations_service_client, std::move(seed_store), UIStringOverrider(),
      /*limited_entropy_synthetic_trial=*/nullptr);

  metrics::TestEnabledStateProvider enabled_state_provider(/*consent=*/true,
                                                           /*enabled=*/true);
  auto metrics_state_manager = metrics::MetricsStateManager::Create(
      local_state(), &enabled_state_provider, std::wstring(), base::FilePath());
  metrics_state_manager->InstantiateFieldTrialList();
  SyntheticTrialRegistry synthetic_trial_registry;

  // Check that field trials are created from the seed. The test seed contains a
  // single study with an experiment targeting 100% of users in India. Since
  // |initial_seed| included the country code for India, this study should be
  // active.
  EXPECT_TRUE(field_trial_creator.SetUpFieldTrials(
      /*variation_ids=*/std::vector<std::string>(),
      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kForceVariationIds),
      std::vector<base::FeatureList::FeatureOverrideInfo>(),
      std::make_unique<base::FeatureList>(), metrics_state_manager.get(),
      &synthetic_trial_registry, &platform_field_trials, &safe_seed_manager,
      /*add_entropy_source_to_variations_ids=*/true));

  EXPECT_EQ(kTestSeedExperimentName,
            base::FieldTrialList::FindFullName(kTestSeedStudyName));
}

// Tests that the hardware class is set on Android.
TEST_F(FieldTrialCreatorTest, ClientFilterableState_HardwareClass) {
  NiceMock<MockSafeSeedManager> safe_seed_manager(local_state());

  TestVariationsServiceClient variations_service_client;
  TestVariationsFieldTrialCreator field_trial_creator(
      local_state(), &variations_service_client, &safe_seed_manager);

  const base::Version& current_version = version_info::GetVersion();
  EXPECT_TRUE(current_version.IsValid());

  std::unique_ptr<ClientFilterableState> client_filterable_state =
      field_trial_creator.GetClientFilterableStateForVersion(current_version);
  EXPECT_NE(client_filterable_state->hardware_class, std::string());
}
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(FIELDTRIAL_TESTING_ENABLED)
// Used to create a TestVariationsFieldTrialCreator with a valid unexpired seed.
std::unique_ptr<TestVariationsFieldTrialCreator>
SetUpFieldTrialCreatorWithValidSeed(
    PrefService* local_state,
    TestVariationsServiceClient* variations_service_client,
    NiceMock<MockSafeSeedManager>* safe_seed_manager) {}

// Verifies that a valid seed is used instead of the testing config when we
// disable it.
TEST_F(FieldTrialCreatorTest, NotSetUpFieldTrialConfig_ValidSeed) {}

// Verifies that field trial testing config is used when enabled, even when
// there is a valid unexpired seed.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrialConfig_ValidSeed) {}

// Verifies that trials from the testing config and the |--force-fieldtrials|
// switch are registered when they are both used (assuming there are no
// conflicts).
TEST_F(FieldTrialCreatorTest, SetUpFieldTrialConfig_ForceFieldTrials) {}

// Verifies that when field trial testing config is used, trials and groups
// specified using |--force-fieldtrials| take precedence if they specify the
// same trials but different groups.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrialConfig_ForceFieldTrialsOverride) {}

// Verifies that when field trial testing config is used, params specified using
// |--force-fieldtrial-params| take precedence if they specify the same trial
// and group.
TEST_F(FieldTrialCreatorTest, SetUpFieldTrialConfig_ForceFieldTrialParams) {}

class FieldTrialCreatorTestWithFeatures
    : public FieldTrialCreatorTest,
      public ::testing::WithParamInterface<const char*> {};

INSTANTIATE_TEST_SUITE_P();

// Verifies that studies from field trial testing config should be ignored
// if they enable/disable features overridden by |--enable-features| or
// |--disable-features|.
TEST_P(FieldTrialCreatorTestWithFeatures,
       SetUpFieldTrialConfig_OverrideFeatures) {}
#endif  // BUILDFLAG(FIELDTRIAL_TESTING_ENABLED)

// Verify that a beacon file is not written when passing an empty user data
// directory path. Some platforms deliberately pass an empty path.
TEST_F(FieldTrialCreatorTest, DoNotWriteBeaconFile) {}

struct StartupVisibilityTestParams {};

class FieldTrialCreatorTestWithStartupVisibility
    : public FieldTrialCreatorTest,
      public ::testing::WithParamInterface<StartupVisibilityTestParams> {};

INSTANTIATE_TEST_SUITE_P();

// Verify that Chrome starts watching for crashes for unknown and foreground
// startup visibilities. Verify that Chrome does not start watching for crashes
// in background sessions.
TEST_P(FieldTrialCreatorTestWithStartupVisibility,
       StartupVisibilityAffectsBrowserCrashMonitoring) {}

// Verify that the beacon file contents are as expected when Chrome starts
// watching for browser crashes before setting up field trials.
TEST_F(FieldTrialCreatorTest, WriteBeaconFile) {}

TEST_F(FieldTrialCreatorTest, GetGoogleGroupsFromPrefsWhenPrefNotPresent) {}

TEST_F(FieldTrialCreatorTest, GetGoogleGroupsFromPrefsWhenEmptyDict) {}

TEST_F(FieldTrialCreatorTest,
       GetGoogleGroupsFromPrefsWhenProfileWithEmptyList) {}

TEST_F(FieldTrialCreatorTest,
       GetGoogleGroupsFromPrefsWhenProfileWithNonEmptyList) {}

TEST_F(FieldTrialCreatorTest,
       GetGoogleGroupsFromPrefsWhenProfileWithNonNumericString) {}

TEST_F(FieldTrialCreatorTest, GetGoogleGroupsFromPrefsClearsDeletedProfiles) {}

namespace {

TestLimitedEntropySyntheticTrial kEnabledTrial(
    kLimitedEntropySyntheticTrialEnabled);
TestLimitedEntropySyntheticTrial kControlTrial(
    kLimitedEntropySyntheticTrialControl);
TestLimitedEntropySyntheticTrial kDefaultTrial(
    kLimitedEntropySyntheticTrialDefault);
const raw_ptr<LimitedEntropySyntheticTrial> kNoLimitedLayerSupport =;

// LERS: Limited Entropy Randomization Source.
struct GenerateLERSTestCase {};

class IsLimitedEntropyRandomizationSourceEnabledTest
    : public FieldTrialCreatorTest,
      public ::testing::WithParamInterface<GenerateLERSTestCase> {};

const GenerateLERSTestCase kGenerateLERSTestCases[] =;

INSTANTIATE_TEST_SUITE_P();

TEST_P(IsLimitedEntropyRandomizationSourceEnabledTest, ) {}

enum class LimitedModeGate {};

struct LimitedEntropyProcessingTestCase {};

std::vector<LimitedEntropyProcessingTestCase> CreateTestCasesForTrials(
    std::string_view test_name,
    LimitedModeGate limited_mode_gate,
    const VariationsSeed& seed,
    const std::vector<raw_ptr<LimitedEntropySyntheticTrial>>& trials,
    bool is_group_registration_expected,
    bool is_seed_rejection_expected,
    bool is_limited_study_active) {}

std::vector<LimitedEntropyProcessingTestCase> FlattenTests(
    const std::vector<std::vector<LimitedEntropyProcessingTestCase>>&
        test_cases) {}

const std::vector<LimitedEntropyProcessingTestCase> kTestCases =;

class LimitedEntropyProcessingTest
    : public FieldTrialCreatorTest,
      public ::testing::WithParamInterface<LimitedEntropyProcessingTestCase> {};

INSTANTIATE_TEST_SUITE_P();

TEST_P(LimitedEntropyProcessingTest,
       RandomizeLimitedEntropyStudyOrRejectTheSeed) {}

// Test feature names prefixed with __ to avoid collision with real features.
BASE_FEATURE();
BASE_FEATURE();
BASE_FEATURE();
BASE_FEATURE();
BASE_FEATURE();
BASE_FEATURE();
BASE_FEATURE();

class FieldTrialCreatorFormFactorTest
    : public FieldTrialCreatorTest,
      public ::testing::WithParamInterface<Study::FormFactor> {};

constexpr Study::FormFactor kAllFormFactors[] =;

// A test seed that enables form-factor specific features across all platforms
// and channels. I.e. the __Desktop feature is enabled only on the Desktop form
// factor, the __Phone feature is enabled only on the Phone form factor, and so
// forth.  The seed applies to all platforms and all channels, except "unknown".
constexpr char kFormFactorTestSeedData[] =;
constexpr char kFormFactorTestSeedSignature[] =;  // Deliberately empty.

}  // namespace

INSTANTIATE_TEST_SUITE_P();

TEST_P(FieldTrialCreatorFormFactorTest, FilterByFormFactor) {}

}  // namespace variations