| // Copyright (c) 2018 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 "chromeos/components/nearby/submittable_executor_base.h" |
| |
| #include <memory> |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_forward.h" |
| #include "base/synchronization/lock.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/unguessable_token.h" |
| #include "chromeos/components/nearby/library/callable.h" |
| #include "chromeos/components/nearby/library/exception.h" |
| #include "chromeos/components/nearby/library/future.h" |
| #include "chromeos/components/nearby/library/multi_thread_executor.h" |
| #include "chromeos/components/nearby/library/runnable.h" |
| #include "chromeos/components/nearby/library/single_thread_executor.h" |
| #include "chromeos/components/nearby/multi_thread_executor_impl.h" |
| #include "chromeos/components/nearby/single_thread_executor_impl.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| |
| namespace nearby { |
| |
| namespace { |
| |
| class SimpleRunnable : public location::nearby::Runnable { |
| public: |
| explicit SimpleRunnable(base::OnceClosure closure) |
| : closure_(std::move(closure)) {} |
| ~SimpleRunnable() override = default; |
| |
| void run() override { |
| EXPECT_FALSE(closure_.is_null()); |
| std::move(closure_).Run(); |
| } |
| |
| private: |
| base::OnceClosure closure_; |
| }; |
| |
| class SimpleCallable : public location::nearby::Callable<bool> { |
| public: |
| SimpleCallable() = default; |
| ~SimpleCallable() override = default; |
| |
| location::nearby::ExceptionOr<bool> call() override { |
| return location::nearby::ExceptionOr<bool>(true); |
| } |
| }; |
| |
| } // namespace |
| |
| // SubmittableExecutorBase is tested via both the SingleThreadExecutorImpl and |
| // MultiThreadExecutorImpl interfaces. There are two tests for each test case, |
| // one for each implementation. |
| // |
| // To test execute(), which has no return value, each task is assigned a unique |
| // ID. This ID is added to |executed_tasks_| when the task is Run(). Thus, the |
| // presence of an ID within |executed_tasks_| means the associated task has |
| // completed execution. |
| class SubmittableExecutorBaseTest : public testing::Test { |
| protected: |
| SubmittableExecutorBaseTest() |
| : scoped_task_environment_( |
| base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT, |
| base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED) {} |
| |
| ~SubmittableExecutorBaseTest() override = default; |
| |
| void InitSingleThreadExecutor() { |
| single_thread_executor_ = std::make_unique<SingleThreadExecutorImpl>(); |
| } |
| |
| void InitMultiThreadExecutor() { |
| multi_thread_executor_ = std::make_unique<MultiThreadExecutorImpl>(); |
| } |
| |
| location::nearby::SingleThreadExecutor<SingleThreadExecutorImpl>* |
| single_thread_executor() { |
| EXPECT_TRUE(single_thread_executor_); |
| return single_thread_executor_.get(); |
| } |
| |
| location::nearby::MultiThreadExecutor<MultiThreadExecutorImpl>* |
| multi_thread_executor() { |
| EXPECT_TRUE(multi_thread_executor_); |
| return multi_thread_executor_.get(); |
| } |
| |
| void ExecuteRunnableWithId(const base::UnguessableToken& task_id, |
| bool single_thread) { |
| single_thread ? single_thread_executor_->execute(CreateRunnable(task_id)) |
| : multi_thread_executor_->execute(CreateRunnable(task_id)); |
| |
| // Ensures the Runnable will complete execution before this method returns. |
| scoped_task_environment_.RunUntilIdle(); |
| } |
| |
| std::shared_ptr<location::nearby::Future<bool>> SubmitCallable( |
| bool single_thread) { |
| std::shared_ptr<location::nearby::Future<bool>> future = |
| single_thread |
| ? single_thread_executor_->submit( |
| static_cast< |
| std::shared_ptr<location::nearby::Callable<bool>>>( |
| std::make_shared<SimpleCallable>())) |
| : multi_thread_executor_->submit( |
| static_cast< |
| std::shared_ptr<location::nearby::Callable<bool>>>( |
| std::make_shared<SimpleCallable>())); |
| |
| // Ensures the Callable will complete execution before this method returns. |
| scoped_task_environment_.RunUntilIdle(); |
| |
| return future; |
| } |
| |
| bool HasTaskExecuted(const base::UnguessableToken& task_id) { |
| base::AutoLock al(executed_tasks_lock_); |
| return executed_tasks_.find(task_id) != executed_tasks_.end(); |
| } |
| |
| size_t GetNumExecutedTasks() { |
| base::AutoLock al(executed_tasks_lock_); |
| return executed_tasks_.size(); |
| } |
| |
| std::shared_ptr<location::nearby::Callable<bool>> CreateCallable() { |
| return std::make_shared<SimpleCallable>(); |
| } |
| |
| std::shared_ptr<location::nearby::Runnable> CreateRunnable( |
| const base::UnguessableToken& task_id) { |
| return std::make_shared<SimpleRunnable>( |
| base::BindRepeating(&SubmittableExecutorBaseTest::OnTaskRun, |
| base::Unretained(this), task_id)); |
| } |
| |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| |
| private: |
| void OnTaskRun(const base::UnguessableToken& task_id) { |
| base::AutoLock al(executed_tasks_lock_); |
| executed_tasks_.insert(task_id); |
| } |
| |
| std::unique_ptr< |
| location::nearby::SingleThreadExecutor<SingleThreadExecutorImpl>> |
| single_thread_executor_; |
| std::unique_ptr< |
| location::nearby::MultiThreadExecutor<MultiThreadExecutorImpl>> |
| multi_thread_executor_; |
| base::Lock executed_tasks_lock_; |
| std::set<base::UnguessableToken> executed_tasks_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SubmittableExecutorBaseTest); |
| }; |
| |
| TEST_F(SubmittableExecutorBaseTest, SingleThread_Submit) { |
| InitSingleThreadExecutor(); |
| std::shared_ptr<location::nearby::Future<bool>> future = |
| SubmitCallable(true /* single_thread */); |
| |
| EXPECT_TRUE(future->get().ok()); |
| EXPECT_TRUE(future->get().result()); |
| } |
| |
| TEST_F(SubmittableExecutorBaseTest, MultiThread_Submit) { |
| InitMultiThreadExecutor(); |
| std::shared_ptr<location::nearby::Future<bool>> future = |
| SubmitCallable(false /* single_thread */); |
| |
| EXPECT_TRUE(future->get().ok()); |
| EXPECT_TRUE(future->get().result()); |
| } |
| |
| TEST_F(SubmittableExecutorBaseTest, SingleThread_Execute) { |
| InitSingleThreadExecutor(); |
| base::UnguessableToken task_id = base::UnguessableToken::Create(); |
| ExecuteRunnableWithId(task_id, true /* single_thread */); |
| |
| EXPECT_EQ(1u, GetNumExecutedTasks()); |
| EXPECT_TRUE(HasTaskExecuted(task_id)); |
| } |
| |
| TEST_F(SubmittableExecutorBaseTest, MultiThread_Execute) { |
| InitMultiThreadExecutor(); |
| base::UnguessableToken task_id = base::UnguessableToken::Create(); |
| ExecuteRunnableWithId(task_id, false /* single_thread */); |
| |
| EXPECT_EQ(1u, GetNumExecutedTasks()); |
| EXPECT_TRUE(HasTaskExecuted(task_id)); |
| } |
| |
| TEST_F(SubmittableExecutorBaseTest, SingleThread_ShutdownPreventsFurtherTasks) { |
| InitSingleThreadExecutor(); |
| single_thread_executor()->shutdown(); |
| base::UnguessableToken task_id = base::UnguessableToken::Create(); |
| ExecuteRunnableWithId(task_id, true /* single_thread */); |
| std::shared_ptr<location::nearby::Future<bool>> future = |
| SubmitCallable(true /*single_thread */); |
| |
| EXPECT_EQ(0u, GetNumExecutedTasks()); |
| EXPECT_FALSE(future->get().ok()); |
| EXPECT_EQ(location::nearby::Exception::INTERRUPTED, |
| future->get().exception()); |
| } |
| |
| TEST_F(SubmittableExecutorBaseTest, MultiThread_ShutdownPreventsFurtherTasks) { |
| InitMultiThreadExecutor(); |
| multi_thread_executor()->shutdown(); |
| base::UnguessableToken task_id = base::UnguessableToken::Create(); |
| ExecuteRunnableWithId(task_id, false /* single_thread */); |
| std::shared_ptr<location::nearby::Future<bool>> future = |
| SubmitCallable(false /* single_thread */); |
| |
| EXPECT_EQ(0u, GetNumExecutedTasks()); |
| EXPECT_FALSE(future->get().ok()); |
| EXPECT_EQ(location::nearby::Exception::INTERRUPTED, |
| future->get().exception()); |
| } |
| |
| TEST_F(SubmittableExecutorBaseTest, |
| SingleThread_ShutdownAllowsExistingTaskToComplete) { |
| InitSingleThreadExecutor(); |
| base::UnguessableToken task_id = base::UnguessableToken::Create(); |
| single_thread_executor()->execute(CreateRunnable(task_id)); |
| single_thread_executor()->shutdown(); |
| scoped_task_environment_.RunUntilIdle(); |
| |
| EXPECT_EQ(1u, GetNumExecutedTasks()); |
| EXPECT_TRUE(HasTaskExecuted(task_id)); |
| } |
| |
| TEST_F(SubmittableExecutorBaseTest, |
| MultiThread_ShutdownAllowsExistingTaskToComplete) { |
| InitMultiThreadExecutor(); |
| base::UnguessableToken task_id = base::UnguessableToken::Create(); |
| multi_thread_executor()->execute(CreateRunnable(task_id)); |
| multi_thread_executor()->shutdown(); |
| scoped_task_environment_.RunUntilIdle(); |
| |
| EXPECT_EQ(1u, GetNumExecutedTasks()); |
| EXPECT_TRUE(HasTaskExecuted(task_id)); |
| } |
| |
| } // namespace nearby |
| |
| } // namespace chromeos |