Chromium Code Reviews| Index: content/renderer/media/audio_renderer_impl_unittest.cc |
| =================================================================== |
| --- content/renderer/media/audio_renderer_impl_unittest.cc (revision 92002) |
| +++ content/renderer/media/audio_renderer_impl_unittest.cc (working copy) |
| @@ -2,9 +2,19 @@ |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| +#include "base/message_loop.h" |
| #include "base/process_util.h" |
| +#include "base/synchronization/waitable_event.h" |
| +#include "base/test/test_timeouts.h" |
| +#include "base/time.h" |
| +#include "content/common/child_process.h" |
| +#include "content/common/child_thread.h" |
| #include "content/common/media/audio_messages.h" |
| #include "content/renderer/media/audio_renderer_impl.h" |
| +#include "content/renderer/mock_content_renderer_client.h" |
| +#include "content/renderer/render_process.h" |
| +#include "content/renderer/render_thread.h" |
| +#include "ipc/ipc_channel.h" |
| #include "media/base/data_buffer.h" |
| #include "media/base/mock_callback.h" |
| #include "media/base/mock_filter_host.h" |
| @@ -13,36 +23,125 @@ |
| using ::testing::Return; |
| -// Class we would be tesing. The only difference between it and "real" one |
| +namespace { |
| +// This class is a mock of the child process singleton which is needed |
| +// to be able to create a RenderThread object. |
| +class MockRenderProcess : public RenderProcess { |
| + public: |
| + MockRenderProcess() {} |
| + virtual ~MockRenderProcess() {} |
| + |
| + // RenderProcess implementation. |
| + virtual skia::PlatformCanvas* GetDrawingCanvas(TransportDIB** memory, |
| + const gfx::Rect& rect) { return NULL; } |
|
tommi (sloooow) - chröme
2011/07/11 19:46:41
nit: fix indentation
henrika_dont_use
2011/07/12 07:12:36
Done.
|
| + virtual void ReleaseTransportDIB(TransportDIB* memory) {} |
| + virtual bool UseInProcessPlugins() const { return false; } |
| + virtual bool HasInitializedMediaLibrary() const { return false; } |
| + |
| + DISALLOW_COPY_AND_ASSIGN(MockRenderProcess); |
|
tommi (sloooow) - chröme
2011/07/11 19:46:41
this needs to be in a private section:
private:
henrika_dont_use
2011/07/12 07:12:36
Done.
|
| +}; |
| +} |
| + |
| +// This class defines a set of methods which will be used in combination |
| +// with NewRunnableMethod to form tasks which will be posted on the |
| +// IO thread. All methods emulate AudioMessageFilter::Delegate calls. |
| +class DelegateCaller : public base::RefCountedThreadSafe<DelegateCaller> { |
| + public: |
| + explicit DelegateCaller(AudioRendererImpl* renderer) |
| + : renderer_(renderer) {} |
| + |
| + void OnCreated(base::SharedMemoryHandle handle, uint32 length) { |
| + if (renderer_->latency_type() == AudioRendererImpl::kHighLatency) { |
| + renderer_->OnCreated(handle, length); |
| + } else { |
| + renderer_->OnLowLatencyCreated(handle, 0, length); |
| + } |
| + } |
| + void OnStateChanged(AudioStreamState state) { |
| + renderer_->OnStateChanged(state); |
| + } |
| + void OnRequestPacket(AudioBuffersState buffers_state) { |
| + renderer_->OnRequestPacket(buffers_state); |
| + } |
| + void OnVolume(double volume) { |
| + renderer_->OnVolume(volume); |
| + } |
| + void DestroyCurrentMessageLoop() { |
| + renderer_->WillDestroyCurrentMessageLoop(); |
| + } |
| + private: |
| + friend class base::RefCountedThreadSafe<DelegateCaller>; |
| + virtual ~DelegateCaller() {} |
| + |
| + scoped_refptr<AudioRendererImpl> renderer_; |
| + DISALLOW_COPY_AND_ASSIGN(DelegateCaller); |
| +}; |
| + |
| +// This task can be posted on the IO thread and will signal an event when |
| +// done. The caller can then wait for this signal to ensure that no |
| +// additional tasks remain in the task queue. |
| +class WaitTask : public Task { |
| + public: |
| + explicit WaitTask(base::WaitableEvent* event) |
| + : event_(event) {} |
| + virtual ~WaitTask() {} |
| + virtual void Run() { |
| + event_->Signal(); |
| + } |
| + |
| + private: |
| + base::WaitableEvent* event_; |
| + DISALLOW_COPY_AND_ASSIGN(WaitTask); |
| +}; |
| + |
| +// Class we would be testing. The only difference between it and "real" one |
| // is that test class does not open sockets and launch audio thread. |
| class TestAudioRendererImpl : public AudioRendererImpl { |
| public: |
| - explicit TestAudioRendererImpl(AudioMessageFilter* filter) |
| - : AudioRendererImpl(filter) { |
| + explicit TestAudioRendererImpl() |
| + : AudioRendererImpl() { |
| } |
| private: |
| virtual void CreateSocket(base::SyncSocket::Handle socket_handle) {} |
| virtual void CreateAudioThread() {} |
| }; |
| -class AudioRendererImplTest : public ::testing::Test { |
| +class AudioRendererImplTest |
| + : public ::testing::Test, |
| + public IPC::Channel::Listener { |
| public: |
| - static const int kRouteId = 0; |
| - static const int kSize = 1024; |
| - |
| static void SetUpTestCase() { |
| // Set low latency mode, as it soon would be on by default. |
| AudioRendererImpl::set_latency_type(AudioRendererImpl::kLowLatency); |
| } |
| - AudioRendererImplTest() { |
| - message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO)); |
| + // IPC::Channel::Listener implementation. |
| + virtual bool OnMessageReceived(const IPC::Message& message) { |
| + NOTIMPLEMENTED(); |
| + return true; |
| + } |
| - // TODO(scherkus): use gmock with AudioMessageFilter to verify |
| - // AudioRendererImpl calls or doesn't call Send(). |
| - filter_ = new AudioMessageFilter(kRouteId); |
| - filter_->message_loop_ = message_loop_.get(); |
| + static const int kSize; |
| + AudioRendererImplTest() {} |
| + virtual ~AudioRendererImplTest() {} |
| + |
| + virtual void SetUp() { |
| + // This part sets up a RenderThread environment to ensure that |
| + // RenderThread::current() (<=> TLS pointer) is valid. |
| + // Main parts are inspired by the RenderViewFakeResourcesTest. |
| + // Note that, the IPC part is not utilized in this test. |
| + content::GetContentClient()->set_renderer(&mock_content_renderer_client_); |
| + |
| + static const char kThreadName[] = "RenderThread"; |
| + channel_.reset(new IPC::Channel(kThreadName, |
| + IPC::Channel::MODE_SERVER, this)); |
| + ASSERT_TRUE(channel_->Connect()); |
| + |
| + mock_process_.reset(new MockRenderProcess); |
| + render_thread_ = new RenderThread(kThreadName); |
| + mock_process_->set_main_thread(render_thread_); |
| + |
| // Create temporary shared memory. |
| CHECK(shared_mem_.CreateAnonymous(kSize)); |
| @@ -50,93 +149,145 @@ |
| decoder_ = new media::MockAudioDecoder(); |
| ON_CALL(*decoder_, config()) |
| - .WillByDefault(Return(media::AudioDecoderConfig(16, |
| - CHANNEL_LAYOUT_MONO, |
| - 44100))); |
| + .WillByDefault(Return(media::AudioDecoderConfig(16, |
| + CHANNEL_LAYOUT_MONO, |
| + 44100))); |
| - // Create and initialize audio renderer. |
| - renderer_ = new TestAudioRendererImpl(filter_); |
| + // Create and initialize the audio renderer. |
| + renderer_ = new TestAudioRendererImpl(); |
| renderer_->set_host(&host_); |
| renderer_->Initialize(decoder_, media::NewExpectedCallback()); |
| - // Run pending tasks and simulate responding with a created audio stream. |
| - message_loop_->RunAllPending(); |
| + // Wraps delegate calls into tasks. |
| + delegate_caller_ = new DelegateCaller(renderer_); |
| + // We need an event to verify that all tasks are done before leaving |
| + // our tests. |
| + event_.reset(new base::WaitableEvent(false, false)); |
| + |
| // Duplicate the shared memory handle so both the test and the callee can |
| // close their copy. |
| base::SharedMemoryHandle duplicated_handle; |
| EXPECT_TRUE(shared_mem_.ShareToProcess(base::GetCurrentProcessHandle(), |
| - &duplicated_handle)); |
| - CallOnCreated(duplicated_handle); |
| + &duplicated_handle)); |
| + |
| + // Set things up and ensure that the call comes from the IO thread |
| + // as all AudioMessageFilter::Delegate methods. |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::OnCreated, duplicated_handle, kSize)); |
| + WaitForIOThreadCompletion(); |
| } |
| - virtual ~AudioRendererImplTest() { |
| + virtual void TearDown() { |
| + mock_process_.reset(); |
| } |
| - void CallOnCreated(base::SharedMemoryHandle handle) { |
| - if (renderer_->latency_type() == AudioRendererImpl::kHighLatency) { |
| - renderer_->OnCreated(handle, kSize); |
| - } else { |
| - renderer_->OnLowLatencyCreated(handle, 0, kSize); |
| - } |
| + protected: |
| + // Posts a final task to the IO message loop and waits for completion. |
| + void WaitForIOThreadCompletion() { |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, new WaitTask(event_.get())); |
| + EXPECT_TRUE(event_->TimedWait( |
| + base::TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms()))); |
| } |
| - protected: |
| - // Fixtures. |
| - scoped_ptr<MessageLoop> message_loop_; |
| - scoped_refptr<AudioMessageFilter> filter_; |
| + MessageLoopForIO message_loop_; |
| + content::MockContentRendererClient mock_content_renderer_client_; |
| + scoped_ptr<IPC::Channel> channel_; |
| + RenderThread* render_thread_; // owned by mock_process_ |
| + scoped_ptr<MockRenderProcess> mock_process_; |
| base::SharedMemory shared_mem_; |
| media::MockFilterHost host_; |
| scoped_refptr<media::MockAudioDecoder> decoder_; |
| scoped_refptr<AudioRendererImpl> renderer_; |
| + scoped_ptr<base::WaitableEvent> event_; |
| + scoped_refptr<DelegateCaller> delegate_caller_; |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AudioRendererImplTest); |
| }; |
| +const int AudioRendererImplTest::kSize = 1024; |
| + |
| TEST_F(AudioRendererImplTest, SetPlaybackRate) { |
| - // Execute SetPlaybackRate() codepath to create an IPC message. |
| - |
| - // Toggle play/pause to generate some IPC messages. |
| + // Execute SetPlaybackRate() codepath by toggling play/pause. |
| + // These methods will be called on the pipeline thread but calling from |
| + // here is fine for this test. Tasks will be posted internally on |
| + // the IO thread. |
| renderer_->SetPlaybackRate(0.0f); |
| renderer_->SetPlaybackRate(1.0f); |
| renderer_->SetPlaybackRate(0.0f); |
| renderer_->Stop(media::NewExpectedCallback()); |
| - message_loop_->RunAllPending(); |
| + WaitForIOThreadCompletion(); |
| } |
| TEST_F(AudioRendererImplTest, SetVolume) { |
| - // Execute SetVolume() codepath to create an IPC message. |
| + // Execute SetVolume() codepath. |
| + // This method will be called on the pipeline thread IRL. |
| + // Tasks will be posted internally on the IO thread. |
| renderer_->SetVolume(0.5f); |
| + |
| renderer_->Stop(media::NewExpectedCallback()); |
| - message_loop_->RunAllPending(); |
| + WaitForIOThreadCompletion(); |
| } |
| TEST_F(AudioRendererImplTest, Stop) { |
| - // Execute Stop() codepath to create an IPC message. |
| + // Execute Stop() codepath. |
| + // Tasks will be posted internally on the IO thread. |
| renderer_->Stop(media::NewExpectedCallback()); |
| - message_loop_->RunAllPending(); |
| // Run AudioMessageFilter::Delegate methods, which can be executed after being |
| - // stopped. AudioRendererImpl shouldn't create any messages. |
| + // stopped. AudioRendererImpl shouldn't create any messages in this state. |
| + // All delegate method calls are posted on the IO thread since it is |
| + // a requirement. |
| if (renderer_->latency_type() == AudioRendererImpl::kHighLatency) { |
| - renderer_->OnRequestPacket(AudioBuffersState(kSize, 0)); |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::OnRequestPacket, AudioBuffersState(kSize, 0))); |
| } |
| - renderer_->OnStateChanged(kAudioStreamError); |
| - renderer_->OnStateChanged(kAudioStreamPlaying); |
| - renderer_->OnStateChanged(kAudioStreamPaused); |
| - CallOnCreated(shared_mem_.handle()); |
| - renderer_->OnVolume(0.5); |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::OnStateChanged, kAudioStreamError)); |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::OnStateChanged, kAudioStreamPlaying)); |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::OnStateChanged, kAudioStreamPaused)); |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::OnCreated, shared_mem_.handle(), kSize)); |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::OnVolume, 0.5)); |
| + WaitForIOThreadCompletion(); |
| + |
| // It's possible that the upstream decoder replies right after being stopped. |
| scoped_refptr<media::Buffer> buffer(new media::DataBuffer(kSize)); |
| renderer_->ConsumeAudioSamples(buffer); |
| } |
| TEST_F(AudioRendererImplTest, DestroyedMessageLoop_SetPlaybackRate) { |
| - // Kill the message loop and verify SetPlaybackRate() still works. |
| - message_loop_.reset(); |
| + // Emulate "killing the message loop" and verify that SetPlaybackRate() |
| + // still works. |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::DestroyCurrentMessageLoop)); |
| + WaitForIOThreadCompletion(); |
| + |
| + // No tasks will be posted on the IO thread here since we are in |
| + // a "stopped" state. |
| renderer_->SetPlaybackRate(0.0f); |
| renderer_->SetPlaybackRate(1.0f); |
| renderer_->SetPlaybackRate(0.0f); |
| @@ -144,15 +295,31 @@ |
| } |
| TEST_F(AudioRendererImplTest, DestroyedMessageLoop_SetVolume) { |
| - // Kill the message loop and verify SetVolume() still works. |
| - message_loop_.reset(); |
| + // Emulate "killing the message loop" and verify that SetVolume() |
| + // still works. |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::DestroyCurrentMessageLoop)); |
| + WaitForIOThreadCompletion(); |
| + |
| + // No tasks will be posted on the IO thread here since we are in |
| + // a "stopped" state. |
| renderer_->SetVolume(0.5f); |
| renderer_->Stop(media::NewExpectedCallback()); |
| } |
| TEST_F(AudioRendererImplTest, DestroyedMessageLoop_ConsumeAudioSamples) { |
| - // Kill the message loop and verify OnReadComplete() still works. |
| - message_loop_.reset(); |
| + // Emulate "killing the message loop" and verify thata OnReadComplete() |
| + // still works. |
| + ChildProcess::current()->io_message_loop()->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(delegate_caller_.get(), |
| + &DelegateCaller::DestroyCurrentMessageLoop)); |
| + WaitForIOThreadCompletion(); |
| + |
| + // No tasks will be posted on the IO thread here since we are in |
| + // a "stopped" state. |
| scoped_refptr<media::Buffer> buffer(new media::DataBuffer(kSize)); |
| renderer_->ConsumeAudioSamples(buffer); |
| renderer_->Stop(media::NewExpectedCallback()); |