// Copyright 2013 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include "base/bind.h" #include "base/debug/stack_trace.h" #include "base/run_loop.h" #include "base/task/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 runner_; // Used to enforce calling stack guarantees of the API. bool inside_start_; // Tracks whether the i'th bound function was called. std::vector 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