// Copyright (c) 2010 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/environment.h" #include "base/message_loop.h" #include "base/process_util.h" #include "base/scoped_ptr.h" #include "base/sync_socket.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/renderer_host/audio_renderer_host.h" #include "chrome/common/render_messages.h" #include "chrome/common/render_messages_params.h" #include "ipc/ipc_message_utils.h" #include "media/audio/audio_manager.h" #include "media/audio/fake_audio_output_stream.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::_; using ::testing::DoAll; using ::testing::InSequence; using ::testing::InvokeWithoutArgs; using ::testing::Return; using ::testing::SaveArg; using ::testing::SetArgumentPointee; static const int kInvalidId = -1; static const int kRouteId = 200; static const int kStreamId = 50; static bool IsRunningHeadless() { scoped_ptr env(base::Environment::Create()); if (env->HasVar("CHROME_HEADLESS")) return true; return false; } class MockAudioRendererHost : public AudioRendererHost { public: MockAudioRendererHost() : shared_memory_length_(0) { } virtual ~MockAudioRendererHost() { } // A list of mock methods. MOCK_METHOD3(OnRequestPacket, void(int routing_id, int stream_id, AudioBuffersState buffers_state)); MOCK_METHOD3(OnStreamCreated, void(int routing_id, int stream_id, int length)); MOCK_METHOD3(OnLowLatencyStreamCreated, void(int routing_id, int stream_id, int length)); MOCK_METHOD2(OnStreamPlaying, void(int routing_id, int stream_id)); MOCK_METHOD2(OnStreamPaused, void(int routing_id, int stream_id)); MOCK_METHOD2(OnStreamError, void(int routing_id, int stream_id)); MOCK_METHOD3(OnStreamVolume, void(int routing_id, int stream_id, double volume)); base::SharedMemory* shared_memory() { return shared_memory_.get(); } uint32 shared_memory_length() { return shared_memory_length_; } base::SyncSocket* sync_socket() { return sync_socket_.get(); } private: // This method is used to dispatch IPC messages to the renderer. We intercept // these messages here and dispatch to our mock methods to verify the // conversation between this object and the renderer. virtual void SendMessage(IPC::Message* message) { CHECK(message); // In this method we dispatch the messages to the according handlers as if // we are the renderer. bool handled = true; IPC_BEGIN_MESSAGE_MAP(MockAudioRendererHost, *message) IPC_MESSAGE_HANDLER(ViewMsg_RequestAudioPacket, OnRequestPacket) IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamCreated, OnStreamCreated) IPC_MESSAGE_HANDLER(ViewMsg_NotifyLowLatencyAudioStreamCreated, OnLowLatencyStreamCreated) IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamStateChanged, OnStreamStateChanged) IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamVolume, OnStreamVolume) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() EXPECT_TRUE(handled); delete message; } // These handler methods do minimal things and delegate to the mock methods. void OnRequestPacket(const IPC::Message& msg, int stream_id, AudioBuffersState buffers_state) { OnRequestPacket(msg.routing_id(), stream_id, buffers_state); } void OnStreamCreated(const IPC::Message& msg, int stream_id, base::SharedMemoryHandle handle, uint32 length) { // Maps the shared memory. shared_memory_.reset(new base::SharedMemory(handle, false)); ASSERT_TRUE(shared_memory_->Map(length)); ASSERT_TRUE(shared_memory_->memory()); shared_memory_length_ = length; // And then delegate the call to the mock method. OnStreamCreated(msg.routing_id(), stream_id, length); } void OnLowLatencyStreamCreated(const IPC::Message& msg, int stream_id, base::SharedMemoryHandle handle, #if defined(OS_WIN) base::SyncSocket::Handle socket_handle, #else base::FileDescriptor socket_descriptor, #endif uint32 length) { // Maps the shared memory. shared_memory_.reset(new base::SharedMemory(handle, false)); CHECK(shared_memory_->Map(length)); CHECK(shared_memory_->memory()); shared_memory_length_ = length; // Create the SyncSocket using the handle. base::SyncSocket::Handle sync_socket_handle; #if defined(OS_WIN) sync_socket_handle = socket_handle; #else sync_socket_handle = socket_descriptor.fd; #endif sync_socket_.reset(new base::SyncSocket(sync_socket_handle)); // And then delegate the call to the mock method. OnLowLatencyStreamCreated(msg.routing_id(), stream_id, length); } void OnStreamStateChanged(const IPC::Message& msg, int stream_id, const ViewMsg_AudioStreamState_Params& params) { if (params.state == ViewMsg_AudioStreamState_Params::kPlaying) { OnStreamPlaying(msg.routing_id(), stream_id); } else if (params.state == ViewMsg_AudioStreamState_Params::kPaused) { OnStreamPaused(msg.routing_id(), stream_id); } else if (params.state == ViewMsg_AudioStreamState_Params::kError) { OnStreamError(msg.routing_id(), stream_id); } else { FAIL() << "Unknown stream state"; } } void OnStreamVolume(const IPC::Message& msg, int stream_id, double volume) { OnStreamVolume(msg.routing_id(), stream_id, volume); } scoped_ptr shared_memory_; scoped_ptr sync_socket_; uint32 shared_memory_length_; DISALLOW_COPY_AND_ASSIGN(MockAudioRendererHost); }; ACTION_P(QuitMessageLoop, message_loop) { message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); } class AudioRendererHostTest : public testing::Test { public: AudioRendererHostTest() : mock_stream_(true) { } protected: virtual void SetUp() { // Create a message loop so AudioRendererHost can use it. message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO)); io_thread_.reset(new ChromeThread(ChromeThread::IO, message_loop_.get())); host_ = new MockAudioRendererHost(); // Simulate IPC channel connected. host_->IPCChannelConnected(base::GetCurrentProcId(), base::GetCurrentProcessHandle(), NULL); } virtual void TearDown() { // Simulate closing the IPC channel. host_->IPCChannelClosing(); // This task post a task to message_loop_ to do internal destruction on // message_loop_. host_->Destroy(); // Release the reference to the mock object. host_ = NULL; // We need to continue running message_loop_ to complete all destructions. SyncWithAudioThread(); io_thread_.reset(); } void Create() { InSequence s; // 1. We will first receive a OnStreamCreated() signal. EXPECT_CALL(*host_, OnStreamCreated(kRouteId, kStreamId, _)); // 2. First packet request will arrive. EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _)) .WillOnce(QuitMessageLoop(message_loop_.get())); IPC::Message msg; msg.set_routing_id(kRouteId); ViewHostMsg_Audio_CreateStream_Params params; if (mock_stream_) params.params.format = AudioParameters::AUDIO_MOCK; else params.params.format = AudioParameters::AUDIO_PCM_LINEAR; params.params.channels = 2; params.params.sample_rate = AudioParameters::kAudioCDSampleRate; params.params.bits_per_sample = 16; params.packet_size = 0; // Send a create stream message to the audio output stream and wait until // we receive the created message. host_->OnCreateStream(msg, kStreamId, params, false); message_loop_->Run(); } void CreateLowLatency() { InSequence s; // We will first receive a OnLowLatencyStreamCreated() signal. EXPECT_CALL(*host_, OnLowLatencyStreamCreated(kRouteId, kStreamId, _)) .WillOnce(QuitMessageLoop(message_loop_.get())); IPC::Message msg; msg.set_routing_id(kRouteId); ViewHostMsg_Audio_CreateStream_Params params; if (mock_stream_) params.params.format = AudioParameters::AUDIO_MOCK; else params.params.format = AudioParameters::AUDIO_PCM_LINEAR; params.params.channels = 2; params.params.sample_rate = AudioParameters::kAudioCDSampleRate; params.params.bits_per_sample = 16; params.packet_size = 0; // Send a create stream message to the audio output stream and wait until // we receive the created message. host_->OnCreateStream(msg, kStreamId, params, true); message_loop_->Run(); } void Close() { // Send a message to AudioRendererHost to tell it we want to close the // stream. IPC::Message msg; msg.set_routing_id(kRouteId); host_->OnCloseStream(msg, kStreamId); message_loop_->RunAllPending(); } void Play() { EXPECT_CALL(*host_, OnStreamPlaying(kRouteId, kStreamId)) .WillOnce(QuitMessageLoop(message_loop_.get())); IPC::Message msg; msg.set_routing_id(kRouteId); host_->OnPlayStream(msg, kStreamId); message_loop_->Run(); } void Pause() { EXPECT_CALL(*host_, OnStreamPaused(kRouteId, kStreamId)) .WillOnce(QuitMessageLoop(message_loop_.get())); IPC::Message msg; msg.set_routing_id(kRouteId); host_->OnPauseStream(msg, kStreamId); message_loop_->Run(); } void SetVolume(double volume) { IPC::Message msg; msg.set_routing_id(kRouteId); host_->OnSetVolume(msg, kStreamId, volume); message_loop_->RunAllPending(); } void NotifyPacketReady() { EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _)) .WillOnce(QuitMessageLoop(message_loop_.get())); IPC::Message msg; msg.set_routing_id(kRouteId); memset(host_->shared_memory()->memory(), 0, host_->shared_memory_length()); host_->OnNotifyPacketReady(msg, kStreamId, host_->shared_memory_length()); message_loop_->Run(); } void SimulateError() { // Find the first AudioOutputController in the AudioRendererHost. CHECK(host_->audio_entries_.size()) << "Calls Create() before calling this method"; media::AudioOutputController* controller = host_->audio_entries_.begin()->second->controller; CHECK(controller) << "AudioOutputController not found"; // Expect an error signal sent through IPC. EXPECT_CALL(*host_, OnStreamError(kRouteId, kStreamId)); // Simulate an error sent from the audio device. host_->OnError(controller, 0); SyncWithAudioThread(); // Expect the audio stream record is removed. EXPECT_EQ(0u, host_->audio_entries_.size()); } // Called on the audio thread. static void PostQuitMessageLoop(MessageLoop* message_loop) { message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); } // Called on the main thread. static void PostQuitOnAudioThread(MessageLoop* message_loop) { AudioManager::GetAudioManager()->GetMessageLoop()->PostTask( FROM_HERE, NewRunnableFunction(&PostQuitMessageLoop, message_loop)); } // SyncWithAudioThread() waits until all pending tasks on the audio thread // are executed while also processing pending task in message_loop_ on the // current thread. It is used to synchronize with the audio thread when we are // closing an audio stream. void SyncWithAudioThread() { message_loop_->PostTask( FROM_HERE, NewRunnableFunction(&PostQuitOnAudioThread, message_loop_.get())); message_loop_->Run(); } MessageLoop* message_loop() { return message_loop_.get(); } MockAudioRendererHost* host() { return host_; } void EnableRealDevice() { mock_stream_ = false; } private: bool mock_stream_; scoped_refptr host_; scoped_ptr message_loop_; scoped_ptr io_thread_; DISALLOW_COPY_AND_ASSIGN(AudioRendererHostTest); }; TEST_F(AudioRendererHostTest, CreateAndClose) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); Close(); } TEST_F(AudioRendererHostTest, CreatePlayAndClose) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); Play(); Close(); } TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); Play(); Pause(); Close(); } TEST_F(AudioRendererHostTest, SetVolume) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); SetVolume(0.5); Play(); Pause(); Close(); // Expect the volume is set. if (IsRunningHeadless()) { EXPECT_EQ(0.5, FakeAudioOutputStream::GetLastFakeStream()->volume()); } } // Simulate the case where a stream is not properly closed. TEST_F(AudioRendererHostTest, CreateAndShutdown) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); } // Simulate the case where a stream is not properly closed. TEST_F(AudioRendererHostTest, CreatePlayAndShutdown) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); Play(); } // Simulate the case where a stream is not properly closed. TEST_F(AudioRendererHostTest, CreatePlayPauseAndShutdown) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); Play(); Pause(); } TEST_F(AudioRendererHostTest, DataConversationMockStream) { Create(); // Note that we only do notify three times because the buffer capacity is // triple of one packet size. NotifyPacketReady(); NotifyPacketReady(); NotifyPacketReady(); Close(); } TEST_F(AudioRendererHostTest, DataConversationRealStream) { if (IsRunningHeadless()) return; EnableRealDevice(); Create(); Play(); // If this is a real audio device, the data conversation is not limited // to the buffer capacity of AudioOutputController. So we do 5 exchanges // before we close the device. for (int i = 0; i < 5; ++i) { NotifyPacketReady(); } Close(); } TEST_F(AudioRendererHostTest, SimulateError) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); Play(); SimulateError(); } // Simulate the case when an error is generated on the browser process, // the audio device is closed but the render process try to close the // audio stream again. TEST_F(AudioRendererHostTest, SimulateErrorAndClose) { if (!IsRunningHeadless()) EnableRealDevice(); Create(); Play(); SimulateError(); Close(); } TEST_F(AudioRendererHostTest, CreateLowLatencyAndClose) { if (!IsRunningHeadless()) EnableRealDevice(); CreateLowLatency(); Close(); } // Simulate the case where a stream is not properly closed. TEST_F(AudioRendererHostTest, CreateLowLatencyAndShutdown) { if (!IsRunningHeadless()) EnableRealDevice(); CreateLowLatency(); } // TODO(hclam): Add tests for data conversation in low latency mode.