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

#include <memory>

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/message_loop/message_loop.h"
#include "base/values.h"
#include "content/browser/tracing/background_tracing_config_impl.h"
#include "content/browser/tracing/background_tracing_rule.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

class BackgroundTracingConfigTest : public testing::Test {
 public:
  BackgroundTracingConfigTest()
      : ui_thread_(BrowserThread::UI, &message_loop_) {}

 protected:
  base::MessageLoop message_loop_;
  TestBrowserThread ui_thread_;
};

std::unique_ptr<BackgroundTracingConfigImpl> ReadFromJSONString(
    const std::string& json_text) {
  std::unique_ptr<base::Value> json_value(base::JSONReader::Read(json_text));

  base::DictionaryValue* dict = nullptr;
  if (json_value)
    json_value->GetAsDictionary(&dict);

  std::unique_ptr<BackgroundTracingConfigImpl> config(
      static_cast<BackgroundTracingConfigImpl*>(
          BackgroundTracingConfig::FromDict(dict).release()));
  return config;
}

std::string ConfigToString(const BackgroundTracingConfig* config) {
  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());

  config->IntoDict(dict.get());

  std::string results;
  if (base::JSONWriter::Write(*dict.get(), &results))
    return results;
  return "";
}

std::string RuleToString(const std::unique_ptr<BackgroundTracingRule>& rule) {
  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());

  rule->IntoDict(dict.get());

  std::string results;
  if (base::JSONWriter::Write(*dict.get(), &results))
    return results;
  return "";
}

TEST_F(BackgroundTracingConfigTest, ConfigFromInvalidString) {
  // Missing or invalid mode
  EXPECT_FALSE(ReadFromJSONString("{}"));
  EXPECT_FALSE(ReadFromJSONString("{\"mode\":\"invalid\"}"));
}

TEST_F(BackgroundTracingConfigTest, PreemptiveConfigFromInvalidString) {
  // Missing or invalid category
  EXPECT_FALSE(ReadFromJSONString("{\"mode\":\"preemptive\"}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"invalid\"}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"invalid\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\", \"trigger_name\":\"foo\"}]}"));

  // Missing rules.
  EXPECT_FALSE(
      ReadFromJSONString("{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
                         "\"BENCHMARK\",\"configs\": []}"));

  // Missing or invalid configs
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\"}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\","
      "\"configs\": \"\"}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\","
      "\"configs\": {}}"));

  // Invalid config entries
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\","
      "\"configs\": [{}]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\","
      "\"configs\": [\"invalid\"]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\","
      "\"configs\": [[]]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\","
      "\"configs\": [{\"rule\": \"invalid\"}]}"));

  // Missing or invalid keys for a named trigger.
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"preemptive\", \"category\": \"benchmark\","
      "\"configs\": [{\"rule\": \"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\"}]}"));
}

TEST_F(BackgroundTracingConfigTest, ReactiveConfigFromInvalidString) {
  // Missing or invalid configs
  EXPECT_FALSE(ReadFromJSONString("{\"mode\":\"reactive\"}"));
  EXPECT_FALSE(
      ReadFromJSONString("{\"mode\":\"reactive\", \"configs\": \"invalid\"}"));
  EXPECT_FALSE(ReadFromJSONString("{\"mode\":\"reactive\", \"configs\": {}}"));

  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"REACTIVE_TRACING_MODE\", \"configs\": []}"));

  // Invalid config entries
  EXPECT_FALSE(
      ReadFromJSONString("{\"mode\":\"reactive\", \"configs\": [{}]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\", \"configs\": [\"invalid\"]}"));

  // Invalid tracing rule type
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\","
      "\"configs\": [{\"rule\": []}]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\","
      "\"configs\": [{\"rule\": \"\"}]}"));
  EXPECT_FALSE(
      ReadFromJSONString("{\"mode\":\"reactive\","
                         "\"configs\": [{\"rule\": "
                         "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\"}]}"));

  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\","
      "\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", \"category\": "
      "[]}]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\","
      "\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", \"category\": "
      "\"\"}]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\","
      "\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", \"category\": "
      "\"benchmark\"}]}"));

  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\","
      "\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", \"category\": "
      "\"benchmark\", \"trigger_name\": []}]}"));
  EXPECT_FALSE(ReadFromJSONString(
      "{\"mode\":\"reactive\","
      "\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", \"category\": "
      "\"benchmark\", \"trigger_name\": 0}]}"));
}

TEST_F(BackgroundTracingConfigTest, PreemptiveConfigFromValidString) {
  std::unique_ptr<BackgroundTracingConfigImpl> config;

  config = ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"BENCHMARK\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\", \"trigger_name\":\"foo\"}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::PREEMPTIVE);
  EXPECT_EQ(config->category_preset(), BackgroundTracingConfigImpl::BENCHMARK);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"rule\":\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\","
            "\"trigger_name\":\"foo\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"BENCHMARK\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\", "
      "\"histogram_name\":\"foo\", \"histogram_value\": 1}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::PREEMPTIVE);
  EXPECT_EQ(config->category_preset(), BackgroundTracingConfigImpl::BENCHMARK);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"histogram_lower_value\":1,\"histogram_name\":\"foo\","
            "\"histogram_repeat\":true,\"histogram_upper_value\":2147483647,"
            "\"rule\":\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"BENCHMARK\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\", "
      "\"histogram_name\":\"foo\", \"histogram_value\": 1, "
      "\"histogram_repeat\":false}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::PREEMPTIVE);
  EXPECT_EQ(config->category_preset(), BackgroundTracingConfigImpl::BENCHMARK);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"histogram_lower_value\":1,\"histogram_name\":\"foo\","
            "\"histogram_repeat\":false,\"histogram_upper_value\":2147483647,"
            "\"rule\":\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"BENCHMARK\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\", "
      "\"histogram_name\":\"foo\", \"histogram_lower_value\": 1, "
      "\"histogram_upper_value\": 2}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::PREEMPTIVE);
  EXPECT_EQ(config->category_preset(), BackgroundTracingConfigImpl::BENCHMARK);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"histogram_lower_value\":1,\"histogram_name\":\"foo\","
            "\"histogram_repeat\":true,\"histogram_upper_value\":2,\"rule\":"
            "\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"BENCHMARK\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\", "
      "\"histogram_name\":\"foo\", \"histogram_lower_value\": 1, "
      "\"histogram_upper_value\": 2, \"histogram_repeat\":false}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::PREEMPTIVE);
  EXPECT_EQ(config->category_preset(), BackgroundTracingConfigImpl::BENCHMARK);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"histogram_lower_value\":1,\"histogram_name\":\"foo\","
            "\"histogram_repeat\":false,\"histogram_upper_value\":2,\"rule\":"
            "\"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"BENCHMARK\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\", \"trigger_name\":\"foo1\"}, "
      "{\"rule\": \"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\", "
      "\"trigger_name\":\"foo2\"}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::PREEMPTIVE);
  EXPECT_EQ(config->category_preset(), BackgroundTracingConfigImpl::BENCHMARK);
  EXPECT_EQ(config->rules().size(), 2u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"rule\":\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\","
            "\"trigger_name\":\"foo1\"}");
  EXPECT_EQ(RuleToString(config->rules()[1]),
            "{\"rule\":\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\","
            "\"trigger_name\":\"foo2\"}");

  config = ReadFromJSONString(
      "{\"category\":\"BENCHMARK_DEEP\",\"configs\":[{\"rule\":"
      "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\",\"trigger_name\":"
      "\"foo1\"}],\"disable_blink_features\":\"SlowerWeb1,SlowerWeb2\","
      "\"enable_blink_features\":\"FasterWeb1,FasterWeb2\","
      "\"mode\":\"PREEMPTIVE_TRACING_MODE\","
      "\"scenario_name\":\"my_awesome_experiment\"}");
  EXPECT_EQ(config->enable_blink_features(), "FasterWeb1,FasterWeb2");
  EXPECT_EQ(config->disable_blink_features(), "SlowerWeb1,SlowerWeb2");
  EXPECT_EQ(config->scenario_name(), "my_awesome_experiment");
}

TEST_F(BackgroundTracingConfigTest, ValidPreemptiveCategoryToString) {
  std::unique_ptr<BackgroundTracingConfigImpl> config = ReadFromJSONString(
      "{\"mode\":\"PREEMPTIVE_TRACING_MODE\", \"category\": "
      "\"BENCHMARK\",\"configs\": [{\"rule\": "
      "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\", \"trigger_name\":\"foo\"}]}");

  BackgroundTracingConfigImpl::CategoryPreset categories[] = {
      BackgroundTracingConfigImpl::BENCHMARK,
      BackgroundTracingConfigImpl::BENCHMARK_DEEP,
      BackgroundTracingConfigImpl::BENCHMARK_GPU,
      BackgroundTracingConfigImpl::BENCHMARK_IPC,
      BackgroundTracingConfigImpl::BENCHMARK_STARTUP,
      BackgroundTracingConfigImpl::BENCHMARK_BLINK_GC,
      BackgroundTracingConfigImpl::BENCHMARK_MEMORY_HEAVY,
      BackgroundTracingConfigImpl::BENCHMARK_MEMORY_LIGHT,
      BackgroundTracingConfigImpl::BENCHMARK_EXECUTION_METRIC,
      BackgroundTracingConfigImpl::BENCHMARK_NAVIGATION,
      BackgroundTracingConfigImpl::BLINK_STYLE,
  };

  const char* category_strings[] = {"BENCHMARK",
                                    "BENCHMARK_DEEP",
                                    "BENCHMARK_GPU",
                                    "BENCHMARK_IPC",
                                    "BENCHMARK_STARTUP",
                                    "BENCHMARK_BLINK_GC",
                                    "BENCHMARK_MEMORY_HEAVY",
                                    "BENCHMARK_MEMORY_LIGHT",
                                    "BENCHMARK_EXECUTION_METRIC",
                                    "BENCHMARK_NAVIGATION",
                                    "BLINK_STYLE"};
  for (size_t i = 0;
       i <
       sizeof(categories) / sizeof(BackgroundTracingConfigImpl::CategoryPreset);
       i++) {
    config->set_category_preset(categories[i]);
    std::string expected =
        std::string("{\"category\":\"") + category_strings[i] +
        std::string(
            "\",\"configs\":[{\"rule\":"
            "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\",\"trigger_name\":"
            "\"foo\"}],\"mode\":\"PREEMPTIVE_TRACING_MODE\"}");
    EXPECT_EQ(ConfigToString(config.get()), expected.c_str());
    std::unique_ptr<BackgroundTracingConfigImpl> config2 =
        ReadFromJSONString(expected);
    EXPECT_EQ(config->category_preset(), config2->category_preset());
  }
}

TEST_F(BackgroundTracingConfigTest, ReactiveConfigFromValidString) {
  std::unique_ptr<BackgroundTracingConfigImpl> config;

  config = ReadFromJSONString(
      "{\"mode\":\"REACTIVE_TRACING_MODE\",\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", "
      "\"category\": \"BENCHMARK\",\"trigger_delay\":30,"
      "\"trigger_name\": \"foo\"}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::REACTIVE);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"category\":\"BENCHMARK\","
            "\"rule\":\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\","
            "\"trigger_delay\":30,\"trigger_name\":\"foo\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"REACTIVE_TRACING_MODE\",\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", "
      "\"category\": \"BENCHMARK_DEEP\", \"trigger_delay\":30, "
      "\"trigger_name\": \"foo\"}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::REACTIVE);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"category\":\"BENCHMARK_DEEP\","
            "\"rule\":\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\","
            "\"trigger_delay\":30,\"trigger_name\":\"foo\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"REACTIVE_TRACING_MODE\",\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", "
      "\"category\": \"BENCHMARK_DEEP\",\"trigger_delay\":30,"
      "\"trigger_name\": \"foo\",\"trigger_delay\":30,"
      "\"trigger_chance\": 0.5}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::REACTIVE);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"category\":\"BENCHMARK_DEEP\","
            "\"rule\":\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\","
            "\"trigger_chance\":0.5,\"trigger_delay\":30,"
            "\"trigger_name\":\"foo\"}");

  config = ReadFromJSONString(
      "{\"mode\":\"REACTIVE_TRACING_MODE\",\"configs\": [{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", "
      "\"category\": \"BENCHMARK_DEEP\", \"trigger_name\": "
      "\"foo1\"},{\"rule\": "
      "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\", "
      "\"category\": \"BENCHMARK_DEEP\", \"trigger_name\": \"foo2\"}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::REACTIVE);
  EXPECT_EQ(config->rules().size(), 2u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"category\":\"BENCHMARK_DEEP\","
            "\"rule\":\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\","
            "\"trigger_delay\":30,\"trigger_name\":\"foo1\"}");
  EXPECT_EQ(RuleToString(config->rules()[1]),
            "{\"category\":\"BENCHMARK_DEEP\","
            "\"rule\":\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\","
            "\"trigger_delay\":30,\"trigger_name\":\"foo2\"}");
  config = ReadFromJSONString(
      "{\"mode\":\"REACTIVE_TRACING_MODE\",\"configs\": [{\"rule\": "
      "\"TRACE_AT_RANDOM_INTERVALS\","
      "\"stop_tracing_on_repeated_reactive\": true,"
      "\"category\": \"BENCHMARK_DEEP\","
      "\"timeout_min\":10, \"timeout_max\":20}]}");
  EXPECT_TRUE(config);
  EXPECT_EQ(config->tracing_mode(), BackgroundTracingConfig::REACTIVE);
  EXPECT_EQ(config->rules().size(), 1u);
  EXPECT_EQ(RuleToString(config->rules()[0]),
            "{\"category\":\"BENCHMARK_DEEP\",\"rule\":\"TRACE_AT_RANDOM_"
            "INTERVALS\",\"stop_tracing_on_repeated_reactive\":true,"
            "\"timeout_max\":20,\"timeout_min\":10}");
}

TEST_F(BackgroundTracingConfigTest, ValidPreemptiveConfigToString) {
  std::unique_ptr<BackgroundTracingConfigImpl> config(
      new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));

  // Default values
  EXPECT_EQ(ConfigToString(config.get()),
            "{\"category\":\"BENCHMARK\",\"configs\":[],\"mode\":\"PREEMPTIVE_"
            "TRACING_MODE\"}");

  // Change category_preset
  config->set_category_preset(BackgroundTracingConfigImpl::BENCHMARK_DEEP);
  EXPECT_EQ(ConfigToString(config.get()),
            "{\"category\":\"BENCHMARK_DEEP\",\"configs\":[],\"mode\":"
            "\"PREEMPTIVE_TRACING_MODE\"}");

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));
    config->set_category_preset(BackgroundTracingConfigImpl::BENCHMARK_DEEP);

    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("rule", "MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED");
    dict->SetString("trigger_name", "foo");
    config->AddPreemptiveRule(dict.get());

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK_DEEP\",\"configs\":[{\"rule\":"
              "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\",\"trigger_name\":"
              "\"foo\"}],\"mode\":\"PREEMPTIVE_TRACING_MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));
    config->set_category_preset(BackgroundTracingConfigImpl::BENCHMARK_DEEP);

    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("rule", "MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED");
    dict->SetString("trigger_name", "foo");
    dict->SetDouble("trigger_chance", 0.5);
    config->AddPreemptiveRule(dict.get());

    EXPECT_EQ(
        ConfigToString(config.get()),
        "{\"category\":\"BENCHMARK_DEEP\",\"configs\":[{\"rule\":"
        "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\",\"trigger_chance\":0.5,"
        "\"trigger_name\":\"foo\"}],\"mode\":\"PREEMPTIVE_TRACING_MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));
    config->set_category_preset(BackgroundTracingConfigImpl::BENCHMARK_DEEP);

    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("rule", "MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED");
    dict->SetString("trigger_name", "foo1");
    config->AddPreemptiveRule(dict.get());

    dict->SetString("trigger_name", "foo2");
    config->AddPreemptiveRule(dict.get());

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK_DEEP\",\"configs\":[{\"rule\":"
              "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\",\"trigger_name\":"
              "\"foo1\"},{\"rule\":\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\","
              "\"trigger_name\":\"foo2\"}],\"mode\":\"PREEMPTIVE_TRACING_"
              "MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));

    std::unique_ptr<base::DictionaryValue> second_dict(
        new base::DictionaryValue());
    second_dict->SetString(
        "rule", "MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE");
    second_dict->SetString("histogram_name", "foo");
    second_dict->SetInteger("histogram_lower_value", 1);
    second_dict->SetInteger("histogram_upper_value", 2);
    config->AddPreemptiveRule(second_dict.get());

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK\",\"configs\":[{\"histogram_lower_"
              "value\":1,\"histogram_name\":\"foo\",\"histogram_repeat\":true,"
              "\"histogram_upper_value\":2,\"rule\":\"MONITOR_AND_DUMP_WHEN_"
              "SPECIFIC_HISTOGRAM_AND_VALUE\"}],\"mode\":\"PREEMPTIVE_TRACING_"
              "MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));

    std::unique_ptr<base::DictionaryValue> second_dict(
        new base::DictionaryValue());
    second_dict->SetString(
        "rule", "MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE");
    second_dict->SetString("histogram_name", "foo");
    second_dict->SetInteger("histogram_lower_value", 1);
    second_dict->SetInteger("histogram_upper_value", 2);
    second_dict->SetInteger("trigger_delay", 10);
    config->AddPreemptiveRule(second_dict.get());

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK\",\"configs\":[{\"histogram_lower_"
              "value\":1,\"histogram_name\":\"foo\",\"histogram_repeat\":true,"
              "\"histogram_upper_value\":2,\"rule\":\"MONITOR_AND_DUMP_WHEN_"
              "SPECIFIC_HISTOGRAM_AND_VALUE\",\"trigger_delay\":10}],\"mode\":"
              "\"PREEMPTIVE_TRACING_MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));
    config->set_category_preset(BackgroundTracingConfigImpl::BENCHMARK_DEEP);

    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("rule", "MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED");
    dict->SetString("trigger_name", "foo1");
    config->AddPreemptiveRule(dict.get());

    config->scenario_name_ = "my_awesome_experiment";
    config->enable_blink_features_ = "FasterWeb1,FasterWeb2";
    config->disable_blink_features_ = "SlowerWeb1,SlowerWeb2";

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK_DEEP\",\"configs\":[{\"rule\":"
              "\"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED\",\"trigger_name\":"
              "\"foo1\"}],\"disable_blink_features\":\"SlowerWeb1,SlowerWeb2\","
              "\"enable_blink_features\":\"FasterWeb1,FasterWeb2\","
              "\"mode\":\"PREEMPTIVE_TRACING_MODE\","
              "\"scenario_name\":\"my_awesome_experiment\"}");
  }
}

TEST_F(BackgroundTracingConfigTest, InvalidPreemptiveConfigToString) {
  std::unique_ptr<BackgroundTracingConfigImpl> config;

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));

    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("rule", "MONITOR_AND_DUMP_WHEN_BROWSER_STARTUP_COMPLETE");
    config->AddPreemptiveRule(dict.get());

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK\",\"configs\":[],\"mode\":"
              "\"PREEMPTIVE_TRACING_MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));

    std::unique_ptr<base::DictionaryValue> second_dict(
        new base::DictionaryValue());
    second_dict->SetString(
        "rule", "MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE");
    second_dict->SetString("histogram_name", "foo");
    second_dict->SetInteger("histogram_lower_value", 1);

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK\",\"configs\":[],\"mode\":"
              "\"PREEMPTIVE_TRACING_MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::PREEMPTIVE));

    std::unique_ptr<base::DictionaryValue> second_dict(
        new base::DictionaryValue());
    second_dict->SetString(
        "rule", "MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE");
    second_dict->SetString("histogram_name", "foo");
    second_dict->SetInteger("histogram_lower_value", 1);
    second_dict->SetInteger("histogram_upper_value", 1);

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"category\":\"BENCHMARK\",\"configs\":[],\"mode\":"
              "\"PREEMPTIVE_TRACING_MODE\"}");
  }
}

TEST_F(BackgroundTracingConfigTest, ValidReactiveConfigToString) {
  std::unique_ptr<BackgroundTracingConfigImpl> config(
      new BackgroundTracingConfigImpl(BackgroundTracingConfig::REACTIVE));

  // Default values
  EXPECT_EQ(ConfigToString(config.get()),
            "{\"configs\":[],\"mode\":\"REACTIVE_TRACING_MODE\"}");

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::REACTIVE));

    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("rule", "TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL");
    dict->SetString("trigger_name", "foo");
    config->AddReactiveRule(dict.get(),
                            BackgroundTracingConfigImpl::BENCHMARK_DEEP);

    EXPECT_EQ(ConfigToString(config.get()),
              "{\"configs\":[{\"category\":\"BENCHMARK_DEEP\",\"rule\":\"TRACE_"
              "ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\",\"trigger_delay\":30,"
              "\"trigger_name\":\"foo\"}],\"mode\":\"REACTIVE_TRACING_MODE\"}");
  }

  {
    config.reset(
        new BackgroundTracingConfigImpl(BackgroundTracingConfig::REACTIVE));

    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("rule", "TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL");
    dict->SetString("trigger_name", "foo1");
    config->AddReactiveRule(dict.get(),
                            BackgroundTracingConfigImpl::BENCHMARK_DEEP);

    dict->SetString("trigger_name", "foo2");
    config->AddReactiveRule(dict.get(),
                            BackgroundTracingConfigImpl::BENCHMARK_DEEP);

    EXPECT_EQ(
        ConfigToString(config.get()),
        "{\"configs\":[{\"category\":\"BENCHMARK_DEEP\",\"rule\":\"TRACE_"
        "ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\",\"trigger_delay\":30,"
        "\"trigger_name\":\"foo1\"},{\"category\":\"BENCHMARK_DEEP\",\"rule\":"
        "\"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL\",\"trigger_delay\":30,"
        "\"trigger_name\":\"foo2\"}],\"mode\":\"REACTIVE_TRACING_MODE\"}");
  }
}

}  // namespace content
