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

#include <string>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/field_trial.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/test/mock_entropy_provider.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/variations/entropy_provider.h"
#include "content/public/test/browser_test.h"

namespace {

const char kEnabledGroupName[] = "Enabled";
const char kDisabledGroupName[] = "Disabled";

// Create 20 OneTimeRandomization test trials to make sure they persist their
// state correctly between runs. We use 20 trials instead of just a couple so
// that if there's a failure, it likely won't be too flaky since the chance
// of all 20 randomly getting the same state is very low.
const int kNumTestTrials = 20;

}  // namespace

// This test verifies the functionality of the --force-fieldtrials flag.
// It spans multiple browser launches (using the PRE_ construct) and checks
// that forcing two trials affects only those trials, but not other ones. For
// the other trials, it checks that they get the same state between sessions
// as they use OneTimeRandomization.
class ForceFieldTrialsBrowserTest : public InProcessBrowserTest,
                                    public testing::WithParamInterface<bool> {
 public:
  ForceFieldTrialsBrowserTest() : metrics_consent_(GetParam()) {
    // Force the first trial to "Enabled" and the second to "Disabled".
    base::FieldTrialList::CreateFieldTrial(GetTestTrialName(1), "Enabled");
    base::FieldTrialList::CreateFieldTrial(GetTestTrialName(2), "Disabled");
  }

  ForceFieldTrialsBrowserTest(const ForceFieldTrialsBrowserTest&) = delete;
  ForceFieldTrialsBrowserTest& operator=(const ForceFieldTrialsBrowserTest&) =
      delete;

  ~ForceFieldTrialsBrowserTest() override = default;

  std::string GetTestTrialName(int trial_number) {
    return base::StringPrintf("_TestTrial_%d", trial_number);
  }

  // Creates a 50/50 trial with OneTimeRandomization consistency.
  void CreateFiftyPercentTrial(const std::string& trial_name) {
    variations::SHA1EntropyProvider entropy_provider("fake_client_id");
    auto* trial = base::FieldTrialList::FactoryGetFieldTrial(
        trial_name, 100, "Default", entropy_provider);
    trial->AppendGroup(kEnabledGroupName, 50);
    trial->AppendGroup(kDisabledGroupName, 50);
  }

  base::FilePath GetTestFilePath(int trial_number) {
    base::FilePath user_data_dir;
    EXPECT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
    std::string file_name =
        "ForceFieldTrialsBrowserTest" + GetTestTrialName(trial_number);
    return user_data_dir.AppendASCII(file_name);
  }

  void SetUp() override {
    // Make metrics reporting work same as in Chrome branded builds, for test
    // consistency between Chromium and Chrome builds.
    ChromeMetricsServiceAccessor::SetForceIsMetricsReportingEnabledPrefLookup(
        true);
    // Based on GetParam(), either enable or disable metrics reporting.
    ChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
        &metrics_consent_);
    InProcessBrowserTest::SetUp();
  }

 private:
  bool metrics_consent_;
};

IN_PROC_BROWSER_TEST_P(ForceFieldTrialsBrowserTest, PRE_PRE_ForceTrials) {
  // Empty test to bypass first-run provisional client id logic in
  // metrics_state_manager.cc before running the other two tests.
}

// The "PRE_" prefix makes this test run before the corresponding named test.
// This allows us to do a test spanning two Chrome launches and check the
// consistency of behavior between them.
IN_PROC_BROWSER_TEST_P(ForceFieldTrialsBrowserTest, PRE_ForceTrials) {
  // Create twenty one-time randomized field trials. Note: Loop is 1-indexed so
  // that we get trial names from "_TestTrial_1" to "_TestTrial_20".
  for (int i = 1; i <= kNumTestTrials; i++)
    CreateFiftyPercentTrial(GetTestTrialName(i));

  // The first two trials has been forced via SetUpCommandLine() above.
  EXPECT_EQ(kEnabledGroupName,
            base::FieldTrialList::FindFullName(GetTestTrialName(1)));
  EXPECT_EQ(kDisabledGroupName,
            base::FieldTrialList::FindFullName(GetTestTrialName(2)));

  // Save the other trials' group names to separate files, so we can check them
  // in the ForceTrials test below.
  for (int i = 3; i <= kNumTestTrials; i++) {
    const std::string trial_group =
        base::FieldTrialList::FindFullName(GetTestTrialName(i));
    EXPECT_TRUE(trial_group == kEnabledGroupName ||
                trial_group == kDisabledGroupName);

    base::ScopedAllowBlockingForTesting allow_blocking;
    ASSERT_TRUE(base::WriteFile(GetTestFilePath(i), trial_group));
  }
}

IN_PROC_BROWSER_TEST_P(ForceFieldTrialsBrowserTest, ForceTrials) {
#if BUILDFLAG(IS_CHROMEOS)
  // TODO(asvitkine): This test fails on Linux Chrome OS bots. Since it passes
  // on proper Chrome OS bots, and Linux Chrome OS is not an end user
  // configuration, disable there for now. Would be good to understand the
  // problem at some point, though. crbug.com/41449818
  if (!base::SysInfo::IsRunningOnChromeOS())
    return;
#endif  // BUILDFLAG(IS_CHROMEOS)

  // Create twenty one-time randomized field trials. Note: Loop is 1-indexed so
  // that we get trial names from "_TestTrial_1" to "_TestTrial_20". Since they
  // are one-time randomized and there was a PRE_ test that created them earlier
  // they should get the same groups as before.
  for (int i = 1; i <= kNumTestTrials; i++)
    CreateFiftyPercentTrial(GetTestTrialName(i));

  // The first two trials has been forced via SetUpCommandLine() above.
  EXPECT_EQ(kEnabledGroupName,
            base::FieldTrialList::FindFullName(GetTestTrialName(1)));
  EXPECT_EQ(kDisabledGroupName,
            base::FieldTrialList::FindFullName(GetTestTrialName(2)));

  // Check that the other trials have expected state.
  for (int i = 3; i <= kNumTestTrials; i++) {
    const std::string trial_group =
        base::FieldTrialList::FindFullName(GetTestTrialName(i));
    EXPECT_TRUE(trial_group == kEnabledGroupName ||
                trial_group == kDisabledGroupName);

    // Read the trial group name that was saved by PRE_ForceTrials from the
    // corresponding test file.
    base::ScopedAllowBlockingForTesting allow_blocking;
    char data[256];
    int bytes_read = ReadFile(GetTestFilePath(i), data, sizeof(data));
    ASSERT_NE(-1, bytes_read);

    // Verify that the trial has the same group as last run and was unaffected
    // by the --force-fieldtrials= switch.
    EXPECT_EQ(std::string(data, bytes_read), trial_group)
        << GetTestTrialName(i);
  }
}

INSTANTIATE_TEST_SUITE_P(ForceFieldTrialsBrowserTests,
                         ForceFieldTrialsBrowserTest,
                         testing::Bool());
