| Index: content/renderer/media/audio_renderer_sink_cache_unittest.cc
|
| diff --git a/content/renderer/media/audio_renderer_sink_cache_unittest.cc b/content/renderer/media/audio_renderer_sink_cache_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..57a8566ce1d77a4fe22e448e3b7d2c83dbad427b
|
| --- /dev/null
|
| +++ b/content/renderer/media/audio_renderer_sink_cache_unittest.cc
|
| @@ -0,0 +1,316 @@
|
| +// 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 "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/logging.h"
|
| +#include "base/test/test_simple_task_runner.h"
|
| +#include "content/renderer/media/audio_renderer_sink_cache_impl.h"
|
| +#include "media/audio/audio_device_description.h"
|
| +#include "media/base/audio_parameters.h"
|
| +#include "media/base/mock_audio_renderer_sink.h"
|
| +#include "media/base/test_helpers.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "url/gurl.h"
|
| +
|
| +namespace content {
|
| +
|
| +namespace {
|
| +static const char* const kDefaultDeviceId =
|
| + media::AudioDeviceDescription::kDefaultDeviceId;
|
| +static const char kAnotherDeviceId[] = "another-device-id";
|
| +static const int kRenderFrameId = 124;
|
| +static const int kDeleteTimeoutMs = 1000;
|
| +} // namespace
|
| +
|
| +class AudioRendererSinkCacheTest : public testing::Test {
|
| + public:
|
| + AudioRendererSinkCacheTest()
|
| + : cache_(new AudioRendererSinkCacheImpl(
|
| + message_loop_.task_runner(),
|
| + base::Bind(&AudioRendererSinkCacheTest::CreateSink,
|
| + base::Unretained(this)),
|
| + kDeleteTimeoutMs)) {}
|
| +
|
| + void GetSink(int render_frame_id,
|
| + const std::string& device_id,
|
| + const url::Origin& security_origin,
|
| + media::AudioRendererSink** sink) {
|
| + *sink = cache_->GetSink(render_frame_id, device_id, security_origin).get();
|
| + }
|
| +
|
| + protected:
|
| + int sink_count() {
|
| + DCHECK(message_loop_.task_runner()->BelongsToCurrentThread());
|
| + return cache_->GetCacheSizeForTesting();
|
| + }
|
| +
|
| + scoped_refptr<media::AudioRendererSink> CreateSink(
|
| + int render_frame_id,
|
| + int session_id,
|
| + const std::string& device_id,
|
| + const url::Origin& security_origin) {
|
| + return new media::MockAudioRendererSink(device_id,
|
| + media::OUTPUT_DEVICE_STATUS_OK);
|
| + }
|
| +
|
| + void ExpectToStop(media::AudioRendererSink* sink) {
|
| + // The sink must be stoped before deletion.
|
| + EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop())
|
| + .Times(1);
|
| + }
|
| +
|
| + void ExpectNotToStop(media::AudioRendererSink* sink) {
|
| + // The sink must be stoped before deletion.
|
| + EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop())
|
| + .Times(0);
|
| + }
|
| +
|
| + // Posts the task to the specified thread and runs current message loop until
|
| + // the task is completed.
|
| + void PostAndRunUntilDone(const base::Thread& thread,
|
| + const base::Closure& task) {
|
| + media::WaitableMessageLoopEvent event;
|
| + thread.task_runner()->PostTaskAndReply(FROM_HERE, task, event.GetClosure());
|
| + // Runs the loop and waits for the thread to call event's closure.
|
| + event.RunAndWait();
|
| + }
|
| +
|
| + void WaitOnAnotherThread(const base::Thread& thread, int timeout_ms) {
|
| + PostAndRunUntilDone(
|
| + thread, base::Bind(base::IgnoreResult(&base::PlatformThread::Sleep),
|
| + base::TimeDelta::FromMilliseconds(timeout_ms)));
|
| + }
|
| +
|
| + base::MessageLoop message_loop_;
|
| + std::unique_ptr<AudioRendererSinkCacheImpl> cache_;
|
| +
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(AudioRendererSinkCacheTest);
|
| +};
|
| +
|
| +// Verify that normal get/release sink sequence works.
|
| +TEST_F(AudioRendererSinkCacheTest, GetReleaseSink) {
|
| + // Verify that a new sink is successfully created.
|
| + EXPECT_EQ(0, sink_count());
|
| + media::AudioRendererSink* sink =
|
| + cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
|
| + ExpectNotToStop(sink); // Cache should not stop sinks marked as used.
|
| + EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // Verify that another sink with the same key is successfully created
|
| + media::AudioRendererSink* another_sink =
|
| + cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
|
| + ExpectNotToStop(another_sink);
|
| + EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
|
| + EXPECT_EQ(2, sink_count());
|
| + EXPECT_NE(sink, another_sink);
|
| +
|
| + // Verify that another sink with a different kay is successfully created.
|
| + media::AudioRendererSink* yet_another_sink =
|
| + cache_->GetSink(kRenderFrameId, kAnotherDeviceId, url::Origin()).get();
|
| + ExpectNotToStop(yet_another_sink);
|
| + EXPECT_EQ(kAnotherDeviceId,
|
| + yet_another_sink->GetOutputDeviceInfo().device_id());
|
| + EXPECT_EQ(3, sink_count());
|
| + EXPECT_NE(sink, yet_another_sink);
|
| + EXPECT_NE(another_sink, yet_another_sink);
|
| +
|
| + // Verify that the first sink is successfully deleted.
|
| + cache_->ReleaseSink(sink);
|
| + EXPECT_EQ(2, sink_count());
|
| + sink = nullptr;
|
| +
|
| + // Make sure we deleted the right sink, and the memory for the rest is not
|
| + // corrupted.
|
| + EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
|
| + EXPECT_EQ(kAnotherDeviceId,
|
| + yet_another_sink->GetOutputDeviceInfo().device_id());
|
| +
|
| + // Verify that the second sink is successfully deleted.
|
| + cache_->ReleaseSink(another_sink);
|
| + EXPECT_EQ(1, sink_count());
|
| + EXPECT_EQ(kAnotherDeviceId,
|
| + yet_another_sink->GetOutputDeviceInfo().device_id());
|
| +
|
| + cache_->ReleaseSink(yet_another_sink);
|
| + EXPECT_EQ(0, sink_count());
|
| +}
|
| +
|
| +// Verify that the sink created with GetSinkInfo() is reused when possible.
|
| +TEST_F(AudioRendererSinkCacheTest, GetDeviceInfo) {
|
| + EXPECT_EQ(0, sink_count());
|
| + media::OutputDeviceInfo device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // The info on the same device is requested, so no new sink is created.
|
| + media::OutputDeviceInfo one_more_device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
|
| + EXPECT_EQ(1, sink_count());
|
| + EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
|
| +
|
| + // Aquire the sink that was created on GetSinkInfo().
|
| + media::AudioRendererSink* sink =
|
| + cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
|
| + EXPECT_EQ(1, sink_count());
|
| + EXPECT_EQ(device_info.device_id(), sink->GetOutputDeviceInfo().device_id());
|
| +
|
| + // Now the sink is in used, but we can still get the device info out of it, no
|
| + // new sink is created.
|
| + one_more_device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
|
| + EXPECT_EQ(1, sink_count());
|
| + EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
|
| +
|
| + // Request sink for the same device. The first sink is in use, so a new one
|
| + // should be created.
|
| + media::AudioRendererSink* another_sink =
|
| + cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
|
| + EXPECT_EQ(2, sink_count());
|
| + EXPECT_EQ(device_info.device_id(),
|
| + another_sink->GetOutputDeviceInfo().device_id());
|
| +}
|
| +
|
| +// Verify that the sink created with GetSinkInfo() is deleted if unused.
|
| +// The test produces 2 "Uninteresting mock" warnings for
|
| +// MockAudioRendererSink::Stop().
|
| +TEST_F(AudioRendererSinkCacheTest, GarbageCollection) {
|
| + EXPECT_EQ(0, sink_count());
|
| + media::OutputDeviceInfo device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + media::OutputDeviceInfo another_device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin());
|
| + EXPECT_EQ(2, sink_count());
|
| +
|
| + base::Thread thread("timeout_thread");
|
| + thread.Start();
|
| +
|
| + // 100 ms more than garbage collection timeout.
|
| + WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100);
|
| +
|
| + // All the sinks should be garbage-collected by now.
|
| + EXPECT_EQ(0, sink_count());
|
| +}
|
| +
|
| +// Verify that the sink created with GetSinkInfo() is not deleted if used within
|
| +// the timeout.
|
| +TEST_F(AudioRendererSinkCacheTest, NoGarbageCollectionForUsedSink) {
|
| + EXPECT_EQ(0, sink_count());
|
| + media::OutputDeviceInfo device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + base::Thread thread("timeout_thread");
|
| + thread.Start();
|
| +
|
| + // Wait significantly less than grabage collection timeout.
|
| + int wait_a_bit = 100;
|
| + DCHECK_GT(kDeleteTimeoutMs, wait_a_bit * 2);
|
| + WaitOnAnotherThread(thread, wait_a_bit);
|
| +
|
| + // Sink is not deleted yet.
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // Request it:
|
| + media::AudioRendererSink* sink =
|
| + cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
|
| + EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // Wait more to hit garbage collection timeout.
|
| + WaitOnAnotherThread(thread, kDeleteTimeoutMs);
|
| +
|
| + // The sink is still in place.
|
| + EXPECT_EQ(1, sink_count());
|
| +}
|
| +
|
| +// Verify that cache works fine if a sink scheduled for delettion is aquired and
|
| +// released before deletion timeout elapses.
|
| +// The test produces one "Uninteresting mock" warning for
|
| +// MockAudioRendererSink::Stop().
|
| +TEST_F(AudioRendererSinkCacheTest, ReleaseSinkBeforeScheduledDeletion) {
|
| + EXPECT_EQ(0, sink_count());
|
| +
|
| + media::OutputDeviceInfo device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
|
| + EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now.
|
| +
|
| + // Request it:
|
| + media::AudioRendererSink* sink =
|
| + cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
|
| + ExpectNotToStop(sink);
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // Release it:
|
| + cache_->ReleaseSink(sink);
|
| + EXPECT_EQ(0, sink_count());
|
| +
|
| + media::OutputDeviceInfo another_device_info =
|
| + cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin());
|
| + EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now.
|
| +
|
| + base::Thread thread("timeout_thread");
|
| + thread.Start();
|
| +
|
| + // 100 ms more than garbage collection timeout.
|
| + WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100);
|
| +
|
| + // Nothing crashed and the second sink deleted on schedule.
|
| + EXPECT_EQ(0, sink_count());
|
| +}
|
| +
|
| +// Check that a sink created on one thread in response to GetSinkInfo can be
|
| +// used on another thread.
|
| +TEST_F(AudioRendererSinkCacheTest, MultithreadedAccess) {
|
| + EXPECT_EQ(0, sink_count());
|
| +
|
| + base::Thread thread1("thread1");
|
| + thread1.Start();
|
| +
|
| + base::Thread thread2("thread2");
|
| + thread2.Start();
|
| +
|
| + // Request device information on the first thread.
|
| + PostAndRunUntilDone(
|
| + thread1,
|
| + base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo),
|
| + base::Unretained(cache_.get()), kRenderFrameId, 0,
|
| + kDefaultDeviceId, url::Origin()));
|
| +
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // Request the sink on the second thread.
|
| + media::AudioRendererSink* sink = nullptr;
|
| +
|
| + PostAndRunUntilDone(
|
| + thread2,
|
| + base::Bind(&AudioRendererSinkCacheTest::GetSink, base::Unretained(this),
|
| + kRenderFrameId, kDefaultDeviceId, url::Origin(), &sink));
|
| +
|
| + EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
|
| + ExpectNotToStop(sink);
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // Request device information on the first thread again.
|
| + PostAndRunUntilDone(
|
| + thread1,
|
| + base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo),
|
| + base::Unretained(cache_.get()), kRenderFrameId, 0,
|
| + kDefaultDeviceId, url::Origin()));
|
| + EXPECT_EQ(1, sink_count());
|
| +
|
| + // Release the sink on the second thread.
|
| + PostAndRunUntilDone(thread2,
|
| + base::Bind(&AudioRendererSinkCache::ReleaseSink,
|
| + base::Unretained(cache_.get()), sink));
|
| +
|
| + EXPECT_EQ(0, sink_count());
|
| +}
|
| +
|
| +} // namespace content
|
|
|