diff options
Diffstat (limited to 'chromium/media/gpu/android/avda_codec_allocator_unittest.cc')
-rw-r--r-- | chromium/media/gpu/android/avda_codec_allocator_unittest.cc | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/chromium/media/gpu/android/avda_codec_allocator_unittest.cc b/chromium/media/gpu/android/avda_codec_allocator_unittest.cc new file mode 100644 index 00000000000..6e7fe85599f --- /dev/null +++ b/chromium/media/gpu/android/avda_codec_allocator_unittest.cc @@ -0,0 +1,384 @@ +// 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 "media/gpu/android/avda_codec_allocator.h" + +#include <stdint.h> + +#include <memory> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/test/scoped_task_environment.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/tick_clock.h" +#include "media/base/android/mock_android_overlay.h" +#include "media/base/android/mock_media_codec_bridge.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::Invoke; +using testing::NiceMock; +using testing::ReturnRef; +using testing::_; + +namespace media { +namespace { +template <typename ReturnType> +void RunAndSignalTask(base::WaitableEvent* event, + ReturnType* return_value, + const base::Callback<ReturnType(void)>& cb) { + *return_value = cb.Run(); + event->Signal(); +} + +void WaitUntilRestarted(base::WaitableEvent* about_to_wait_event, + base::WaitableEvent* wait_event) { + // Notify somebody that we've started. + if (about_to_wait_event) + about_to_wait_event->Signal(); + wait_event->Wait(); +} + +void SignalImmediately(base::WaitableEvent* event) { + event->Signal(); +} +} // namespace + +class MockClient : public AVDACodecAllocatorClient { + public: + MockClient() + : codec_arrived_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED), + weak_factory_(this) {} + + // Gmock doesn't let us mock methods taking move-only types. + MOCK_METHOD1(OnCodecConfiguredMock, void(MediaCodecBridge* media_codec)); + void OnCodecConfigured( + std::unique_ptr<MediaCodecBridge> media_codec, + scoped_refptr<AVDASurfaceBundle> surface_bundle) override { + media_codec_ = std::move(media_codec); + OnCodecConfiguredMock(media_codec.get()); + codec_arrived_event_.Signal(); + } + + base::WeakPtr<AVDACodecAllocatorClient> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + // Most recently provided codec. + std::unique_ptr<MediaCodecBridge> media_codec_; + + base::WaitableEvent codec_arrived_event_; + + base::WeakPtrFactory<AVDACodecAllocatorClient> weak_factory_; +}; + +class AVDACodecAllocatorTest : public testing::Test { + public: + AVDACodecAllocatorTest() + : allocator_thread_("AllocatorThread"), + stop_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED) { + // Don't start the clock at null. + tick_clock_.Advance(base::TimeDelta::FromSeconds(1)); + } + + ~AVDACodecAllocatorTest() override {} + + // Utility fn to test out threading. + void AllocateCodec() { + allocator_->StartThread(avda1_); + scoped_refptr<CodecConfig> codec_config(new CodecConfig); + codec_config->surface_bundle = surface_bundle_; + EXPECT_CALL(*avda1_, OnCodecConfiguredMock(_)); + allocator_->CreateMediaCodecAsync(avda1_->GetWeakPtr(), codec_config); + } + + void DestroyCodec() { + // Make sure that we got a codec. + ASSERT_NE(avda1_->media_codec_, nullptr); + base::WaitableEvent destruction_event( + base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED); + static_cast<MockMediaCodecBridge*>(avda1_->media_codec_.get()) + ->SetCodecDestroyedEvent(&destruction_event); + allocator_->ReleaseMediaCodec(std::move(avda1_->media_codec_), + surface_bundle_); + + // This won't wait for the threads to stop, which means that the release + // might not have completed yet. Even once we are signalled that the codec + // has been destroyed, we can't be sure that OnMediaCodecReleased has run + // on the allocator thread. To get around this, one should wait on + // |stop_event_|, but not here. If we're run on the allocator's thread, + // then that's where |stop_event_| will be signalled from. + allocator_->StopThread(avda1_); + // The codec destruction should be async with respect to us. + destruction_event.Wait(); + + // Important: we don't know that OnMediaCodecReleased has completed. + // If we clean up the test and post the allocator's destruction to the + // allocator thread, before the "and reply" posts the codec release, then + // the codec release will be run on a destructed allocator. Either we + // should synchronize on that, or quit using base::Unretained(). + // Waiting for |stop_event_| and then for |allocator_thread_| should be + // sufficent to avoid this. + // Waiting for the overlay to be released is probably also enough, since + // that happens to be run on OnMediaCodecReleased also. + } + + void WaitForSurfaceDestruction() { + // This may be called from any thread. + PostAndWait(FROM_HERE, + base::Bind( + [](AVDACodecAllocator* allocator, AndroidOverlay* overlay) { + allocator->WaitForPendingRelease(overlay); + return true; + }, + allocator_, surface_bundle_->overlay.get())); + } + + protected: + void SetUp() override { + // Start the main thread for the allocator. This would normally be the GPU + // main thread. + ASSERT_TRUE(allocator_thread_.Start()); + + AVDACodecAllocator::CodecFactoryCB factory_cb( + base::BindRepeating(&MockMediaCodecBridge::CreateVideoDecoder)); + + // Create the first allocator on the allocator thread. + allocator_ = PostAndWait( + FROM_HERE, base::Bind( + [](AVDACodecAllocator::CodecFactoryCB factory_cb, + scoped_refptr<base::SequencedTaskRunner> task_runner, + base::TickClock* clock, base::WaitableEvent* event) { + return new AVDACodecAllocator(factory_cb, task_runner, + clock, event); + }, + factory_cb, allocator_thread_.task_runner(), + &tick_clock_, &stop_event_)); + allocator2_ = new AVDACodecAllocator( + factory_cb, base::SequencedTaskRunnerHandle::Get()); + + // Create a SurfaceBundle that provides an overlay. It will provide a null + // java ref if requested. + std::unique_ptr<MockAndroidOverlay> overlay = + base::MakeUnique<NiceMock<MockAndroidOverlay>>(); + scoped_refptr<CodecConfig> codec_config(new CodecConfig); + ON_CALL(*overlay, GetJavaSurface()) + .WillByDefault(ReturnRef(null_java_ref_)); + surface_bundle_ = new AVDASurfaceBundle(std::move(overlay)); + } + + void TearDown() override { + // Don't leave any threads hung, or this will hang too. + // It would be nice if we could let a unique ptr handle this, but the + // destructor is private. We also have to destroy it on the right thread. + PostAndWait(FROM_HERE, base::Bind( + [](AVDACodecAllocator* allocator) { + delete allocator; + return true; + }, + allocator_)); + + allocator_thread_.Stop(); + delete allocator2_; + } + + protected: + // Start / stop the threads for |avda| on the right thread. + void StartThread(AVDACodecAllocatorClient* avda) { + PostAndWait(FROM_HERE, base::Bind( + [](AVDACodecAllocator* allocator, + AVDACodecAllocatorClient* avda) { + allocator->StartThread(avda); + return true; // void won't work. + }, + allocator_, avda)); + } + + void StopThread(AVDACodecAllocatorClient* avda) { + // Note that we also wait for the stop event, so that we know that the + // stop has completed. It's async with respect to the allocator thread. + PostAndWait(FROM_HERE, base::Bind( + [](AVDACodecAllocator* allocator, + AVDACodecAllocatorClient* avda) { + allocator->StopThread(avda); + return true; + }, + allocator_, avda)); + // Note that we don't do this on the allocator thread, since that's the + // thread that will signal it. + stop_event_.Wait(); + } + + // Return the running state of |task_type|, doing the necessary thread hops. + bool IsThreadRunning(TaskType task_type) { + return PostAndWait( + FROM_HERE, + base::Bind( + [](AVDACodecAllocator* allocator, TaskType task_type) { + return allocator->GetThreadForTesting(task_type).IsRunning(); + }, + allocator_, task_type)); + } + + base::Optional<TaskType> TaskTypeForAllocation( + bool software_codec_forbidden) { + return PostAndWait( + FROM_HERE, + base::Bind(&AVDACodecAllocator::TaskTypeForAllocation, + base::Unretained(allocator_), software_codec_forbidden)); + } + + scoped_refptr<base::SingleThreadTaskRunner> TaskRunnerFor( + TaskType task_type) { + return PostAndWait(FROM_HERE, + base::Bind(&AVDACodecAllocator::TaskRunnerFor, + base::Unretained(allocator_), task_type)); + } + + // Post |cb| to the allocator thread, and wait for a response. Note that we + // don't have a specialization for void, and void won't work as written. So, + // be sure to return something. + template <typename ReturnType> + ReturnType PostAndWait(const base::Location& from_here, + const base::Callback<ReturnType(void)>& cb) { + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + ReturnType return_value = ReturnType(); + allocator_thread_.task_runner()->PostTask( + from_here, + base::Bind(&RunAndSignalTask<ReturnType>, &event, &return_value, cb)); + event.Wait(); + return return_value; + } + + // So that we can get the thread's task runner. + base::test::ScopedTaskEnvironment scoped_task_environment_; + + base::Thread allocator_thread_; + + // The test params for |allocator_|. + base::SimpleTestTickClock tick_clock_; + base::WaitableEvent stop_event_; + + // Allocators that we own. The first is intialized to be used on the allocator + // thread and the second one is initialized on the test thread. Each test + // should only be using one of the two. They are not unique_ptrs because the + // destructor is private and they need to be destructed on the right thread. + AVDACodecAllocator* allocator_ = nullptr; + AVDACodecAllocator* allocator2_ = nullptr; + + NiceMock<MockClient> client1_, client2_, client3_; + NiceMock<MockClient>* avda1_ = &client1_; + NiceMock<MockClient>* avda2_ = &client2_; + NiceMock<MockClient>* avda3_ = &client3_; + + // Surface bundle that has an overlay. + scoped_refptr<AVDASurfaceBundle> surface_bundle_; + base::android::JavaRef<jobject> null_java_ref_; +}; + +TEST_F(AVDACodecAllocatorTest, ThreadsStartWhenClientsStart) { + ASSERT_FALSE(IsThreadRunning(AUTO_CODEC)); + ASSERT_FALSE(IsThreadRunning(SW_CODEC)); + StartThread(avda1_); + ASSERT_TRUE(IsThreadRunning(AUTO_CODEC)); + ASSERT_TRUE(IsThreadRunning(SW_CODEC)); +} + +TEST_F(AVDACodecAllocatorTest, ThreadsStopAfterAllClientsStop) { + StartThread(avda1_); + StartThread(avda2_); + StopThread(avda1_); + ASSERT_TRUE(IsThreadRunning(AUTO_CODEC)); + StopThread(avda2_); + ASSERT_FALSE(IsThreadRunning(AUTO_CODEC)); + ASSERT_FALSE(IsThreadRunning(SW_CODEC)); +} + +TEST_F(AVDACodecAllocatorTest, TestHangThread) { + StartThread(avda1_); + ASSERT_EQ(AUTO_CODEC, TaskTypeForAllocation(false)); + + // Hang the AUTO_CODEC thread. + base::WaitableEvent about_to_wait_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + TaskRunnerFor(AUTO_CODEC) + ->PostTask(FROM_HERE, base::Bind(&WaitUntilRestarted, + &about_to_wait_event, &wait_event)); + // Wait until the task starts, so that |allocator_| starts the hang timer. + about_to_wait_event.Wait(); + + // Verify that we've failed over after a long time has passed. + tick_clock_.Advance(base::TimeDelta::FromSeconds(1)); + ASSERT_EQ(SW_CODEC, TaskTypeForAllocation(false)); + + // Un-hang the thread and wait for it to let another task run. This will + // notify |allocator_| that the thread is no longer hung. + base::WaitableEvent done_waiting_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + TaskRunnerFor(AUTO_CODEC) + ->PostTask(FROM_HERE, + base::Bind(&SignalImmediately, &done_waiting_event)); + wait_event.Signal(); + done_waiting_event.Wait(); + + // Verify that we've un-failed over. + ASSERT_EQ(AUTO_CODEC, TaskTypeForAllocation(false)); +} + +TEST_F(AVDACodecAllocatorTest, AllocateAndDestroyCodecOnAllocatorThread) { + // Make sure that allocating / freeing a codec on the allocator's thread + // completes, and doesn't DCHECK. + allocator_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&AVDACodecAllocatorTest::AllocateCodec, + base::Unretained(this))); + + // Wait for the codec on this thread, rather than the allocator thread, since + // that's where the codec will be posted. + avda1_->codec_arrived_event_.Wait(); + + allocator_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&AVDACodecAllocatorTest::DestroyCodec, + base::Unretained(this))); + + // Note that TearDown will join |allocator_thread_|. + WaitForSurfaceDestruction(); + + // Wait for threads to stop, now that we're not on the allocator thread. + stop_event_.Wait(); +} + +TEST_F(AVDACodecAllocatorTest, AllocateAndDestroyCodecOnNewThread) { + // Make sure that allocating / freeing a codec on a random thread completes, + // and doesn't DCHECK. + base::Thread new_thread("NewThreadForTesting"); + ASSERT_TRUE(new_thread.Start()); + new_thread.task_runner()->PostTask( + FROM_HERE, base::Bind(&AVDACodecAllocatorTest::AllocateCodec, + base::Unretained(this))); + + // Wait for the codec on this thread, rather than |new_thread|, since that's + // where the codec will be posted. + avda1_->codec_arrived_event_.Wait(); + + new_thread.task_runner()->PostTask( + FROM_HERE, base::Bind(&AVDACodecAllocatorTest::DestroyCodec, + base::Unretained(this))); + new_thread.Stop(); + WaitForSurfaceDestruction(); + stop_event_.Wait(); +} + +} // namespace media |