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

#include "chrome/browser/ash/arc/tracing/overview_tracing_handler.h"

#include "ash/constants/ash_switches.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/test/test_file_util.h"
#include "chrome/browser/ash/app_list/arc/arc_app_test.h"
#include "chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h"
#include "chrome/browser/ash/arc/tracing/test/overview_tracing_test_base.h"
#include "chrome/browser/ash/arc/tracing/test/overview_tracing_test_handler.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/experiences/arc/arc_prefs.h"
#include "chromeos/ash/experiences/arc/test/arc_task_window_builder.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"

namespace arc {

using EventType = arc::ArcTracingGraphicsModel::EventType;

namespace {

class OverviewTracingHandlerTest : public OverviewTracingTestBase {
 public:
  OverviewTracingHandlerTest() : weak_ptr_factory_(this) {}

  ~OverviewTracingHandlerTest() override = default;

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

  void SetUp() override {
    OverviewTracingTestBase::SetUp();

    handler_ = std::make_unique<OverviewTracingTestHandler>(
        base::BindRepeating(&OverviewTracingHandlerTest::OnArcWindowFocusChange,
                            weak_ptr_factory_.GetWeakPtr()));
    handler_->set_start_build_model_cb(
        base::BindRepeating(&OverviewTracingHandlerTest::OnStartBuildModel,
                            weak_ptr_factory_.GetWeakPtr()));
    handler_->set_graphics_model_ready_cb(
        base::BindRepeating(&OverviewTracingHandlerTest::OnGraphicsModelReady,
                            weak_ptr_factory_.GetWeakPtr()));
  }

  void TearDown() override {
    handler_.reset();

    OverviewTracingTestBase::TearDown();
  }

 protected:
  void OnArcWindowFocusChange(aura::Window* window) {
    if (window) {
      events_.push_back("new ARC window focused");
    } else {
      events_.push_back("ARC window loses focus");
    }
  }

  void OnStartBuildModel() { events_.push_back("start building model"); }
  void OnGraphicsModelReady(std::unique_ptr<OverviewTracingResult> results) {
    events_.push_back(
        base::StrCat({"graphics model status: ", results->status}));
    model_ = std::move(results->model);
  }

  std::unique_ptr<OverviewTracingTestHandler> handler_;
  std::vector<std::string> events_;
  base::Value model_;

  base::WeakPtrFactory<OverviewTracingHandlerTest> weak_ptr_factory_;
};

TEST_F(OverviewTracingHandlerTest, ModelName) {
  base::FilePath download_path = base::FilePath::FromASCII("/mnt/downloads");
  base::Time timestamp = base::Time::UnixEpoch() + base::Seconds(1);

  std::unique_ptr<icu::TimeZone> tz;
  SetTimeZone("America/Chicago");
  EXPECT_EQ("overview_tracing_test_title_1_1969-12-31_18-00-01.json",
            handler_->GetModelBaseNameFromTitle("Test Title #:1", timestamp));
  SetTimeZone("Indian/Maldives");
  EXPECT_EQ(
      "overview_tracing_0123456789012345678901234567890_"
      "1970-01-01_05-00-01.json",
      handler_->GetModelBaseNameFromTitle(
          "0123456789012345678901234567890123456789", timestamp));

  SetTimeZone("Etc/UTC");
  timestamp = base::Time::UnixEpoch() + base::Days(50);
  EXPECT_EQ("overview_tracing_xyztitle_1970-02-20_00-00-00.json",
            handler_->GetModelBaseNameFromTitle("xyztitle", timestamp));

  SetTimeZone("Japan");
  EXPECT_EQ("overview_tracing_secret_app_1970-02-20_09-00-00.json",
            handler_->GetModelBaseNameFromTitle("Secret App", timestamp));
}

TEST_F(OverviewTracingHandlerTest, FilterSystemTraceByTimestamp) {
  handler_->set_now(
      base::Time::FromMillisecondsSinceUnixEpoch(1'500'088'880'000));
  handler_->set_trace_time_base(
      base::Time::FromMillisecondsSinceUnixEpoch(1'500'000'000'000));

  exo::Surface s;
  auto arc_widget = arc::ArcTaskWindowBuilder()
                        .SetShellRootSurface(&s)
                        .BuildOwnsNativeWidget();

  arc_widget->Show();
  auto max_time = base::Seconds(5);
  handler_->StartTracing(download_path_, max_time);
  handler_->StartTracingOnControllerRespond();

  // Fast forward past the max tracing interval.
  FastForwardClockAndTaskQueue(handler_.get(),
                               max_time + base::Milliseconds(500));

  // Pass results from trace controller to handler. First and last events should
  // not be in the model.
  handler_->StopTracingOnControllerRespond(std::make_unique<std::string>(
      R"(
{
    "traceEvents": [],
    "systemTraceEvents":
"          <idle>-0     [000] d..0 88879.800000: sched_wakeup: comm=foo pid=99 prio=115 target_cpu=000
          <idle>-0     [000] d..0 88882.000001: cpu_idle: state=0 cpu_id=0
          <idle>-0     [000] dn.0 88883.000002: cpu_idle: state=4294967295 cpu_id=0
          <idle>-0     [000] dnh3 88884.000003: sched_wakeup: comm=foo pid=25821 prio=115 target_cpu=000
          <idle>-0     [000] d..3 88884.500004: sched_switch: prev_comm=bar prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=baz next_pid=25891 next_prio=115
          <idle>-0     [000] d..3 88885.500004: sched_switch: prev_comm=baz prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=foo next_pid=33921 next_prio=115
"
})"));

  ASSERT_EQ(("new ARC window focused\n"
             "start building model"),
            base::JoinString(events_, "\n"));

  task_environment()->RunUntilIdle();

  ASSERT_EQ(("new ARC window focused\n"
             "start building model\n"
             "graphics model status: Tracing model is ready"),
            base::JoinString(events_, "\n"));
  {
    const auto& dict = model_.GetDict();
    const auto* events_by_cpu = dict.FindListByDottedPath("system.cpu");
    ASSERT_TRUE(events_by_cpu);
    // Only one CPU in log.
    ASSERT_EQ(1UL, events_by_cpu->size());

    const auto& cpu_events = (*events_by_cpu)[0].GetList();
    ASSERT_EQ(4UL, cpu_events.size()) << cpu_events;

    EXPECT_EQ(25821.0, cpu_events[2].GetList()[2].GetDouble());
    EXPECT_EQ(25891.0, cpu_events[3].GetList()[2].GetDouble());
  }
}

TEST_F(OverviewTracingHandlerTest, SwitchWindowDuringModelBuild) {
  handler_->set_now(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'044'440'000));
  handler_->set_trace_time_base(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'000'000'000));

  exo::Surface s;
  auto arc_widget = arc::ArcTaskWindowBuilder()
                        .SetTaskId(22)
                        .SetPackageName("org.funstuff.client")
                        .SetShellRootSurface(&s)
                        .BuildOwnsNativeWidget();

  auto other_arc_widget = arc::ArcTaskWindowBuilder()
                              .SetTaskId(88)
                              .SetPackageName("net.differentapp")
                              .SetShellRootSurface(&s)
                              .BuildOwnsNativeWidget();

  arc_widget->Show();
  other_arc_widget->ShowInactive();
  auto max_time = base::Seconds(5);
  handler_->StartTracing(download_path_, max_time);
  handler_->StartTracingOnControllerRespond();

  // Fast forward past the max tracing interval. This will stop the trace at the
  // end of the fast-forward, which is 400ms after the timeout.
  FastForwardClockAndTaskQueue(handler_.get(),
                               max_time + base::Milliseconds(400));

  // While model is being built, switch to the ARC window to change
  // min_tracing_time_. This sets the min trace time to 300ms after the end of
  // the trace.
  FastForwardClockAndTaskQueue(handler_.get(), base::Milliseconds(300));
  other_arc_widget->Activate();

  // Pass results from trace controller to handler.
  handler_->StopTracingOnControllerRespond(
      std::make_unique<std::string>(kBasicSystrace));

  task_environment()->RunUntilIdle();

  {
    const auto& dict = model_.GetDict();
    const auto* events_by_cpu = dict.FindListByDottedPath("system.cpu");
    ASSERT_TRUE(events_by_cpu);
    ASSERT_EQ(4UL, events_by_cpu->size());

    const auto& cpu_events = (*events_by_cpu)[3].GetList();
    ASSERT_EQ(1UL, cpu_events.size()) << cpu_events;
  }
}

TEST_F(OverviewTracingHandlerTest, SwitchWindowDuringTrace) {
  handler_->set_now(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'044'440'000));
  handler_->set_trace_time_base(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'000'000'000));

  exo::Surface s;
  auto arc_widget = arc::ArcTaskWindowBuilder()
                        .SetTaskId(22)
                        .SetPackageName("org.funstuff.client")
                        .SetShellRootSurface(&s)
                        .SetTitle("the first app")
                        .BuildOwnsNativeWidget();

  auto other_arc_widget = arc::ArcTaskWindowBuilder()
                              .SetTaskId(88)
                              .SetPackageName("net.differentapp")
                              .SetShellRootSurface(&s)
                              .SetTitle("another ARC app")
                              .BuildOwnsNativeWidget();

  arc_widget->Show();
  other_arc_widget->ShowInactive();
  auto max_time = base::Seconds(5);
  handler_->StartTracing(download_path_, max_time);
  handler_->StartTracingOnControllerRespond();

  FastForwardClockAndTaskQueue(handler_.get(), base::Seconds(1));
  other_arc_widget->Activate();
  FastForwardClockAndTaskQueue(handler_.get(), base::Seconds(1));
  task_environment()->RunUntilIdle();

  // Fast forward past the max tracing interval. This will stop the trace at the
  // end of the fast-forward, which is 400ms after the timeout.
  FastForwardClockAndTaskQueue(handler_.get(), base::Milliseconds(4500));

  // Pass results from trace controller to handler.
  handler_->StopTracingOnControllerRespond(
      std::make_unique<std::string>(kBasicSystrace));

  task_environment()->RunUntilIdle();

  // The current behavior when activating a separate window during a trace is to
  // stop the trace and switch to the web UI. This behavior may change, but this
  // test was originally added to avoid a DCHECK failure upon switching the
  // active window. NOTE even if the below asserts are no longer valid, this
  // test is still important for what has occurred before this line.
  const auto& dict = model_.GetDict();
  EXPECT_EQ(*dict.FindStringByDottedPath("information.title"), "the first app");
}

TEST_F(OverviewTracingHandlerTest, SwitchWindowBeforeTraceStart) {
  handler_->set_now(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'044'440'000));
  handler_->set_trace_time_base(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'000'000'000));

  exo::Surface s1, s2;
  auto arc_widget = arc::ArcTaskWindowBuilder()
                        .SetTaskId(22)
                        .SetPackageName("org.funstuff.client")
                        .SetShellRootSurface(&s1)
                        .BuildOwnsNativeWidget();

  auto other_arc_widget = arc::ArcTaskWindowBuilder()
                              .SetTaskId(88)
                              .SetPackageName("net.differentapp")
                              .SetTitle("i will be traced")
                              .SetShellRootSurface(&s2)
                              .BuildOwnsNativeWidget();

  arc_widget->Show();
  other_arc_widget->ShowInactive();
  handler_->StartTracing(download_path_, base::Seconds(5));
  other_arc_widget->Activate();

  handler_->StartTracingOnControllerRespond();
  FastForwardClockAndTaskQueue(handler_.get(), base::Seconds(6));
  handler_->VerifyNoUnrespondedCallback();

  // We should be able to do a trace now - no trace state should be lingering.
  // The web UI will be active - switch back to an ARC app.
  other_arc_widget->Activate();
  handler_->StartTracing(download_path_, base::Seconds(5));
  handler_->StartTracingOnControllerRespond();
  FastForwardClockAndTaskQueue(handler_.get(), base::Seconds(6));
  handler_->StopTracingOnControllerRespond(
      std::make_unique<std::string>(kBasicSystrace));
  task_environment()->RunUntilIdle();

  const auto& dict = model_.GetDict();
  EXPECT_EQ(*dict.FindStringByDottedPath("information.title"),
            "i will be traced");
}

TEST_F(OverviewTracingHandlerTest, CommitAndPresentTimestampsInModel) {
  handler_->set_now(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'044'440'000));
  handler_->set_trace_time_base(
      base::Time::FromMillisecondsSinceUnixEpoch(1'600'000'000'000));

  exo::Surface s;
  auto arc_widget = arc::ArcTaskWindowBuilder()
                        .SetTaskId(22)
                        .SetPackageName("org.funstuff.client")
                        .SetShellRootSurface(&s)
                        .SetTitle("the first app")
                        .BuildOwnsNativeWidget();

  arc_widget->Show();
  handler_->StartTracing(download_path_, base::Seconds(5));
  handler_->StartTracingOnControllerRespond();

  FastForwardClockAndTaskQueue(handler_.get(), base::Seconds(1));

  CommitAndPresentFrames(handler_.get(), &s, 5, base::Milliseconds(42));

  FastForwardClockAndTaskQueue(handler_.get(), base::Seconds(5));

  handler_->StopTracingOnControllerRespond(
      std::make_unique<std::string>(kBasicSystrace));

  task_environment()->RunUntilIdle();

  const auto& dict = model_.GetDict();

  LOG(INFO) << "checking JSON model: " << dict;

  auto validate_events = [&](EventType type, const base::ListValue* events) {
    ASSERT_TRUE(events);
    std::queue<double> expected_times({0, 42000, 84000, 126000, 168000});
    ASSERT_EQ(events->size(), expected_times.size());
    for (const auto& event : *events) {
      const auto& list = event.GetList();
      ASSERT_EQ(list.size(), 2ul);
      ASSERT_EQ(list[0].GetInt(), static_cast<int>(type));
      ASSERT_EQ(list[1].GetDouble(), expected_times.front());
      expected_times.pop();
    }
  };

  validate_events(EventType::kChromeOSPresentationDone,
                  dict.FindListByDottedPath("chrome.global_events"));
  const auto* events_by_buffer =
      (*dict.FindListByDottedPath("views"))[0].GetDict().FindListByDottedPath(
          "buffers");

  ASSERT_TRUE(events_by_buffer);
  ASSERT_EQ(events_by_buffer->size(), 1ul);
  validate_events(EventType::kExoSurfaceCommit,
                  (*events_by_buffer)[0].GetIfList());
}

}  // namespace

}  // namespace arc
