// Copyright 2016 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 "components/subresource_filter/core/common/scoped_timers.h"

#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "components/subresource_filter/core/common/time_measurements.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

class MockExportFunctor {
 public:
  int number_of_calls() const { return number_of_calls_; }
  void operator()(base::TimeDelta) { ++number_of_calls_; }

 private:
  int number_of_calls_ = 0;
};

template <typename TimerFactory>
void ExpectFunctorIsCalledOnceOnDestruction() {
  MockExportFunctor export_functor;
  {
    auto scoped_timer = TimerFactory::Start(export_functor);
    EXPECT_EQ(0, export_functor.number_of_calls());
  }
  const int expected_number_of_calls = TimerFactory::IsSupported() ? 1 : 0;
  EXPECT_EQ(expected_number_of_calls, export_functor.number_of_calls());
}

template <typename TimerFactory>
void ExpectStoredLambdaIsInvokedOnceOnDestruction() {
  bool export_is_called = false;
  auto export_functor = [&export_is_called](base::TimeDelta) {
    EXPECT_FALSE(export_is_called);
    export_is_called = true;
  };

  {
    auto scoped_timer = TimerFactory::Start(export_functor);
    EXPECT_FALSE(export_is_called);
  }
  EXPECT_EQ(TimerFactory::IsSupported(), export_is_called);
}

template <typename TimerFactory>
void ExpectInlineLambdaIsInvokedOnceOnDestruction() {
  bool export_is_called = false;
  {
    auto scoped_timer =
        TimerFactory::Start([&export_is_called](base::TimeDelta) {
          EXPECT_FALSE(export_is_called);
          export_is_called = true;
        });
    EXPECT_FALSE(export_is_called);
  }
  EXPECT_EQ(TimerFactory::IsSupported(), export_is_called);
}

template <typename TimerFactory>
void ExpectWellBehavedStartIf(bool condition) {
  bool export_is_called = false;
  auto export_functor = [&export_is_called](base::TimeDelta) {
    EXPECT_FALSE(export_is_called);
    export_is_called = true;
  };

  {
    auto scoped_timer = TimerFactory::StartIf(condition, export_functor);
    EXPECT_FALSE(export_is_called);
  }
  EXPECT_EQ(condition && TimerFactory::IsSupported(), export_is_called);
}

template <typename TimerFactory>
void ExpectWellBehavedMoveContructor() {
  MockExportFunctor export_functor;
  const int expected_number_of_calls = TimerFactory::IsSupported() ? 1 : 0;
  {
    auto scoped_timer = TimerFactory::Start(export_functor);
    EXPECT_EQ(0, export_functor.number_of_calls());
    {
      auto another_scoped_timer = std::move(scoped_timer);
      EXPECT_EQ(0, export_functor.number_of_calls());
    }
    // |another_scoped_timer| should have called |export_functor|.
    EXPECT_EQ(expected_number_of_calls, export_functor.number_of_calls());
  }
  // But |scoped_timer| should have not since then.
  EXPECT_EQ(expected_number_of_calls, export_functor.number_of_calls());
}

template <typename TimerFactory>
void ExpectWellBehavedMoveAssignment() {
  MockExportFunctor export_functor;
  const int expected_number_of_calls = TimerFactory::IsSupported() ? 1 : 0;
  {
    auto scoped_timer = TimerFactory::Start(export_functor);
    EXPECT_EQ(0, export_functor.number_of_calls());
    {
      auto another_scoped_timer = std::move(scoped_timer);
      scoped_timer = std::move(another_scoped_timer);
      EXPECT_EQ(0, export_functor.number_of_calls());
    }
    // |another_scoped_timer| shouldn't have called |export_functor|.
    EXPECT_EQ(0, export_functor.number_of_calls());
  }
  // But |scoped_timer| should have because it owns the measurement.
  EXPECT_EQ(expected_number_of_calls, export_functor.number_of_calls());
}

}  // namespace

namespace subresource_filter {

TEST(ScopedTimersTest, CallsFunctor) {
  ExpectFunctorIsCalledOnceOnDestruction<ScopedTimers>();
  ExpectFunctorIsCalledOnceOnDestruction<ScopedThreadTimers>();
}

TEST(ScopedTimersTest, CallsStoredLambdaFunctor) {
  ExpectStoredLambdaIsInvokedOnceOnDestruction<ScopedTimers>();
  ExpectStoredLambdaIsInvokedOnceOnDestruction<ScopedThreadTimers>();
}

TEST(ScopedTimersTest, CallsInlineLambdaFunctor) {
  ExpectInlineLambdaIsInvokedOnceOnDestruction<ScopedTimers>();
  ExpectInlineLambdaIsInvokedOnceOnDestruction<ScopedThreadTimers>();
}

TEST(ScopedTimersTest, StartIf) {
  ExpectWellBehavedStartIf<ScopedTimers>(false);
  ExpectWellBehavedStartIf<ScopedTimers>(true);

  ExpectWellBehavedStartIf<ScopedThreadTimers>(false);
  ExpectWellBehavedStartIf<ScopedThreadTimers>(true);
}

TEST(ScopedTimersTest, MoveConstructTimer) {
  ExpectWellBehavedMoveContructor<ScopedTimers>();
  ExpectWellBehavedMoveContructor<ScopedThreadTimers>();
}

TEST(ScopedTimersTest, MoveAssignTimer) {
  ExpectWellBehavedMoveAssignment<ScopedTimers>();
  ExpectWellBehavedMoveAssignment<ScopedThreadTimers>();
}

// Below are tests for macros in "time_measurements.h" -------------------------
// TODO(pkalinnikov): Move these to a separate file?

TEST(ScopedTimersTest, ScopedUmaHistogramMacros) {
  base::HistogramTester tester;
  {
    SCOPED_UMA_HISTOGRAM_THREAD_TIMER("ScopedTimers.ThreadTimer");
    SCOPED_UMA_HISTOGRAM_MICRO_THREAD_TIMER("ScopedTimers.MicroThreadTimer");
    SCOPED_UMA_HISTOGRAM_MICRO_TIMER("ScopedTimers.MicroTimer");

    tester.ExpectTotalCount("ScopedTimers.ThreadTimer", 0);
    tester.ExpectTotalCount("ScopedTimers.MicroThreadTimer", 0);
    tester.ExpectTotalCount("ScopedTimers.MicroTimer", 0);
  }

  int expected_count = ScopedTimers::IsSupported() ? 1 : 0;
  tester.ExpectTotalCount("ScopedTimers.MicroTimer", expected_count);

  expected_count = ScopedThreadTimers::IsSupported() ? 1 : 0;
  tester.ExpectTotalCount("ScopedTimers.ThreadTimer", expected_count);
  tester.ExpectTotalCount("ScopedTimers.MicroThreadTimer", expected_count);
}

TEST(ScopedTimersTest, UmaHistogramMicroTimesFromExportFunctor) {
  base::HistogramTester tester;
  auto export_functor = [](base::TimeDelta delta) {
    UMA_HISTOGRAM_MICRO_TIMES("ScopedTimers.MicroTimes", delta);
  };
  {
    auto scoped_timer = ScopedTimers::Start(export_functor);
    tester.ExpectTotalCount("ScopedTimers.MicroTimes", 0);
  }
  tester.ExpectTotalCount("ScopedTimers.MicroTimes", 1);
}

}  // namespace subresource_filter
