| // Copyright 2013 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 <stddef.h> |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/debug/stack_trace.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/task_environment.h" |
| #include "media/base/pipeline_status.h" |
| #include "media/base/serial_runner.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace media { |
| |
| class SerialRunnerTest : public ::testing::Test { |
| public: |
| SerialRunnerTest() |
| : inside_start_(false), done_called_(false), done_status_(PIPELINE_OK) {} |
| |
| SerialRunnerTest(const SerialRunnerTest&) = delete; |
| SerialRunnerTest& operator=(const SerialRunnerTest&) = delete; |
| |
| ~SerialRunnerTest() override = default; |
| |
| void RunSerialRunner() { |
| task_environment_.GetMainThreadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&SerialRunnerTest::StartRunnerInternal, |
| base::Unretained(this), std::move(bound_fns_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Pushes a bound function to the queue that will run its callback with |
| // |status|. called(i) returns whether the i'th bound function pushed to the |
| // queue was called while running the SerialRunner. |
| void PushBoundFunction(PipelineStatus status) { |
| bound_fns_.Push(base::BindOnce(&SerialRunnerTest::RunBoundFunction, |
| base::Unretained(this), status, |
| called_.size())); |
| called_.push_back(false); |
| } |
| |
| void PushBoundClosure() { |
| bound_fns_.Push(base::BindOnce(&SerialRunnerTest::RunBoundClosure, |
| base::Unretained(this), called_.size())); |
| called_.push_back(false); |
| } |
| |
| void PushClosure() { |
| bound_fns_.Push(base::BindOnce(&SerialRunnerTest::RunClosure, |
| base::Unretained(this), called_.size())); |
| called_.push_back(false); |
| } |
| |
| // Push a bound function to the queue that will delete the SerialRunner, |
| // which should cancel all remaining queued work. |
| void PushCancellation() { |
| bound_fns_.Push(base::BindOnce(&SerialRunnerTest::CancelSerialRunner, |
| base::Unretained(this))); |
| } |
| |
| // Queries final status of pushed functions and done callback. Valid only |
| // after calling RunSerialRunner(). |
| bool called(size_t index) { return called_[index]; } |
| bool done_called() { return done_called_; } |
| PipelineStatus done_status() { return done_status_; } |
| |
| private: |
| void RunBoundFunction(PipelineStatus status, |
| size_t index, |
| PipelineStatusCallback status_cb) { |
| EXPECT_EQ(index == 0u, inside_start_) |
| << "First bound function should run on same stack as " |
| << "SerialRunner::Run() while all others should not\n" |
| << base::debug::StackTrace().ToString(); |
| |
| called_[index] = true; |
| std::move(status_cb).Run(status); |
| } |
| |
| void RunBoundClosure(size_t index, base::OnceClosure done_cb) { |
| EXPECT_EQ(index == 0u, inside_start_) |
| << "First bound function should run on same stack as " |
| << "SerialRunner::Run() while all others should not\n" |
| << base::debug::StackTrace().ToString(); |
| |
| called_[index] = true; |
| std::move(done_cb).Run(); |
| } |
| |
| void RunClosure(size_t index) { |
| EXPECT_EQ(index == 0u, inside_start_) |
| << "First bound function should run on same stack as " |
| << "SerialRunner::Run() while all others should not\n" |
| << base::debug::StackTrace().ToString(); |
| |
| called_[index] = true; |
| } |
| |
| void StartRunnerInternal(SerialRunner::Queue bound_fns) { |
| inside_start_ = true; |
| runner_ = SerialRunner::Run(std::move(bound_fns), |
| base::BindOnce(&SerialRunnerTest::DoneCallback, |
| base::Unretained(this))); |
| inside_start_ = false; |
| } |
| |
| void DoneCallback(PipelineStatus status) { |
| EXPECT_FALSE(inside_start_) |
| << "Done callback should not run on same stack as SerialRunner::Run()\n" |
| << base::debug::StackTrace().ToString(); |
| |
| done_called_ = true; |
| done_status_ = status; |
| } |
| |
| void CancelSerialRunner(PipelineStatusCallback status_cb) { |
| // Tasks run by |runner_| shouldn't reset it, hence we post a task to do so. |
| task_environment_.GetMainThreadTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(&SerialRunnerTest::ResetSerialRunner, |
| base::Unretained(this))); |
| std::move(status_cb).Run(PIPELINE_OK); |
| } |
| |
| void ResetSerialRunner() { |
| runner_.reset(); |
| } |
| |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| SerialRunner::Queue bound_fns_; |
| std::unique_ptr<SerialRunner> runner_; |
| |
| // Used to enforce calling stack guarantees of the API. |
| bool inside_start_; |
| |
| // Tracks whether the i'th bound function was called. |
| std::vector<bool> called_; |
| |
| // Tracks whether the final done callback was called + resulting status. |
| bool done_called_; |
| PipelineStatus done_status_; |
| }; |
| |
| TEST_F(SerialRunnerTest, Empty) { |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(done_called()); |
| EXPECT_EQ(PIPELINE_OK, done_status()); |
| } |
| |
| TEST_F(SerialRunnerTest, Single) { |
| PushBoundFunction(PIPELINE_OK); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_TRUE(done_called()); |
| EXPECT_EQ(PIPELINE_OK, done_status()); |
| } |
| |
| TEST_F(SerialRunnerTest, Single_Error) { |
| PushBoundFunction(PIPELINE_ERROR_ABORT); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_TRUE(done_called()); |
| EXPECT_EQ(PIPELINE_ERROR_ABORT, done_status()); |
| } |
| |
| TEST_F(SerialRunnerTest, Single_Cancel) { |
| PushBoundFunction(PIPELINE_OK); |
| PushCancellation(); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_FALSE(done_called()); |
| } |
| |
| TEST_F(SerialRunnerTest, Multiple) { |
| PushBoundFunction(PIPELINE_OK); |
| PushBoundFunction(PIPELINE_OK); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_TRUE(called(1)); |
| EXPECT_TRUE(done_called()); |
| EXPECT_EQ(PIPELINE_OK, done_status()); |
| } |
| |
| TEST_F(SerialRunnerTest, Multiple_Error) { |
| PushBoundFunction(PIPELINE_ERROR_ABORT); |
| PushBoundFunction(PIPELINE_OK); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_FALSE(called(1)); // A bad status cancels remaining work. |
| EXPECT_TRUE(done_called()); |
| EXPECT_EQ(PIPELINE_ERROR_ABORT, done_status()); |
| } |
| |
| TEST_F(SerialRunnerTest, Multiple_Cancel) { |
| PushBoundFunction(PIPELINE_OK); |
| PushCancellation(); |
| PushBoundFunction(PIPELINE_OK); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_FALSE(called(1)); |
| EXPECT_FALSE(done_called()); |
| } |
| |
| TEST_F(SerialRunnerTest, BoundClosure) { |
| PushBoundClosure(); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_TRUE(done_called()); |
| EXPECT_EQ(PIPELINE_OK, done_status()); |
| } |
| |
| TEST_F(SerialRunnerTest, Closure) { |
| PushClosure(); |
| RunSerialRunner(); |
| |
| EXPECT_TRUE(called(0)); |
| EXPECT_TRUE(done_called()); |
| EXPECT_EQ(PIPELINE_OK, done_status()); |
| } |
| |
| } // namespace media |