chromium/chrome/services/sharing/nearby/platform/scheduled_executor_unittest.cc

// Copyright 2020 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/services/sharing/nearby/platform/scheduled_executor.h"

#include <memory>
#include <set>
#include <utility>

#include "base/functional/bind.h"
#include "base/synchronization/lock.h"
#include "base/test/task_environment.h"
#include "base/unguessable_token.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace nearby {
namespace chrome {

namespace {

constexpr base::TimeDelta kDefaultDelayTimeDelta = base::Minutes(10);

}  // namespace

class ScheduledExecutorTest : public testing::Test {
 protected:
  std::shared_ptr<api::Cancelable> PostRunnableWithIdAndDelay(
      base::RunLoop& run_loop,
      const base::UnguessableToken& id,
      base::TimeDelta delay) {
    Runnable runnable = [&] {
      base::AutoLock al(id_set_lock_);
      id_set_.insert(id);

      run_loop.Quit();
    };

    std::shared_ptr<api::Cancelable> cancelable = scheduled_executor_->Schedule(
        std::move(runnable), absl::Microseconds(delay.InMicroseconds()));

    // In order to make thread-safe calls to the API of base::OneShotTimer,
    // schedule() will post a task to an internal base::SequencedTaskRunner that
    // calls Start() on a base::OneShotTimer. Executing RunUntilIdle() simply
    // ensures that the base::OneShotTimer associated with the Runnable has been
    // Start()ed, but offers no guarantee on whether the Runnable has been run()
    // or not.
    task_environment_.RunUntilIdle();

    return cancelable;
  }

  void CancelTaskAndVerifyState(std::shared_ptr<api::Cancelable> cancelable,
                                bool should_expect_success) {
    EXPECT_EQ(should_expect_success, cancelable->Cancel());

    // Ensures that the base::OneShotTimer associated with the given Cancelable
    // has been Stop()ped before this method returns.
    task_environment_.RunUntilIdle();
  }

  void VerifySetContainsId(const base::UnguessableToken& id) {
    base::AutoLock al(id_set_lock_);
    EXPECT_NE(id_set_.end(), id_set_.find(id));
  }

  size_t GetSetSize() {
    base::AutoLock al(id_set_lock_);
    return id_set_.size();
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::unique_ptr<ScheduledExecutor> scheduled_executor_ =
      std::make_unique<ScheduledExecutor>(
          task_environment_.GetMainThreadTaskRunner());

 private:
  base::Lock id_set_lock_;
  std::set<base::UnguessableToken> id_set_;
};

TEST_F(ScheduledExecutorTest, SingleTaskExecutes) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta);
  EXPECT_EQ(1u, GetSetSize());
  VerifySetContainsId(id);
}

TEST_F(ScheduledExecutorTest, StaggeredTasksExecute) {
  base::RunLoop run_loop_1;
  base::UnguessableToken id_1 = base::UnguessableToken::Create();
  PostRunnableWithIdAndDelay(run_loop_1, id_1, kDefaultDelayTimeDelta);
  base::RunLoop run_loop_2;
  base::UnguessableToken id_2 = base::UnguessableToken::Create();
  PostRunnableWithIdAndDelay(run_loop_2, id_2, kDefaultDelayTimeDelta * 2);

  // Only the first scheduled task should run at first.
  task_environment_.FastForwardBy(kDefaultDelayTimeDelta);
  EXPECT_EQ(1u, GetSetSize());
  VerifySetContainsId(id_1);

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta);
  EXPECT_EQ(2u, GetSetSize());
  VerifySetContainsId(id_2);
}

TEST_F(ScheduledExecutorTest, SingleTaskCancels) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);

  CancelTaskAndVerifyState(cancelable, true /* should_expect_success */);
  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  EXPECT_EQ(0u, GetSetSize());
}

TEST_F(ScheduledExecutorTest, FirstTaskCancelsAndSecondTaskExecutes) {
  base::RunLoop run_loop_1;
  base::UnguessableToken id_1 = base::UnguessableToken::Create();
  auto cancelable_1 =
      PostRunnableWithIdAndDelay(run_loop_1, id_1, kDefaultDelayTimeDelta * 2);

  base::RunLoop run_loop_2;
  base::UnguessableToken id_2 = base::UnguessableToken::Create();
  PostRunnableWithIdAndDelay(run_loop_2, id_2, kDefaultDelayTimeDelta * 3);

  CancelTaskAndVerifyState(cancelable_1, true /* should_expect_success */);
  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  EXPECT_EQ(0u, GetSetSize());

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  EXPECT_EQ(1u, GetSetSize());
  VerifySetContainsId(id_2);
}

TEST_F(ScheduledExecutorTest, FailToCancelAfterRun) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
}

TEST_F(ScheduledExecutorTest, FailToRunAfterCancel) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);

  CancelTaskAndVerifyState(cancelable, true /* should_expect_success */);
  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  EXPECT_EQ(0u, GetSetSize());
}

TEST_F(ScheduledExecutorTest, FailToCancelAfterCancel) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);

  // The first call should successfully cancel the task. Subsequent invocations
  // will return false by default, as CancelableTask uses a base::OnceClosure
  // that will be consumed after the first call to cancel().
  CancelTaskAndVerifyState(cancelable, true /* should_expect_success */);
  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
}

TEST_F(ScheduledExecutorTest, FailToCancelAfterExecutorIsDestroyed) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
  scheduled_executor_.reset();

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
}

TEST_F(ScheduledExecutorTest, FailToScheduleAfterShutdown) {
  scheduled_executor_->Shutdown();
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  EXPECT_EQ(0u, GetSetSize());
}

TEST_F(ScheduledExecutorTest, FailToCancelAfterShutdown) {
  scheduled_executor_->Shutdown();
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  CancelTaskAndVerifyState(cancelable, false /* should_expect_success */);
}

TEST_F(ScheduledExecutorTest, ShutdownAllowsExistingTaskToComplete) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
  scheduled_executor_->Shutdown();

  task_environment_.FastForwardBy(kDefaultDelayTimeDelta * 2);
  EXPECT_EQ(1u, GetSetSize());
  VerifySetContainsId(id);
}

TEST_F(ScheduledExecutorTest, DestroyAllowExistingTaskToCompleteImmediately) {
  base::RunLoop run_loop;
  base::UnguessableToken id = base::UnguessableToken::Create();
  auto cancelable =
      PostRunnableWithIdAndDelay(run_loop, id, kDefaultDelayTimeDelta);
  scheduled_executor_.reset();

  run_loop.Run();
  EXPECT_EQ(1u, GetSetSize());
  VerifySetContainsId(id);
}
}  // namespace chrome
}  // namespace nearby