Chromium Code Reviews| Index: content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc |
| diff --git a/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..014f2aa5d9bc23ed4a71ea167c4d8a2593efe5e2 |
| --- /dev/null |
| +++ b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc |
| @@ -0,0 +1,297 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
|
DaleCurtis
2017/03/24 17:47:12
Don't see a test which utilizes the RemoveStream p
Max Morin
2017/03/27 14:40:51
I added RenderFrameAudioOutputStreamFactoryTest.{C
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/renderer_host/media/render_frame_audio_output_stream_factory.h" |
| + |
| +#include <cmath> |
| +#include <limits> |
| +#include <utility> |
| + |
| +#include "base/bind.h" |
| +#include "base/memory/shared_memory.h" |
| +#include "base/memory/shared_memory_handle.h" |
| +#include "base/run_loop.h" |
| +#include "base/sync_socket.h" |
| +#include "content/browser/audio_manager_thread.h" |
| +#include "content/browser/renderer_host/media/media_stream_manager.h" |
| +#include "content/browser/renderer_host/media/renderer_audio_output_stream_factory_context.h" |
| +#include "content/common/media/renderer_audio_output_stream_factory.mojom.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "content/public/test/mock_render_process_host.h" |
| +#include "content/public/test/test_browser_context.h" |
| +#include "content/public/test/test_browser_thread_bundle.h" |
| +#include "media/audio/audio_output_controller.h" |
| +#include "media/audio/fake_audio_log_factory.h" |
| +#include "media/audio/fake_audio_manager.h" |
| +#include "media/base/audio_parameters.h" |
| +#include "media/base/media_switches.h" |
| +#include "mojo/public/cpp/bindings/binding.h" |
| +#include "mojo/public/cpp/system/platform_handle.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace content { |
| + |
| +namespace { |
| + |
| +using testing::Test; |
| +using AudioOutputStreamFactory = mojom::RendererAudioOutputStreamFactory; |
| +using AudioOutputStreamFactoryPtr = |
| + mojo::InterfacePtr<AudioOutputStreamFactory>; |
| +using AudioOutputStreamFactoryRequest = |
| + mojo::InterfaceRequest<AudioOutputStreamFactory>; |
| +using AudioOutputStream = media::mojom::AudioOutputStream; |
| +using AudioOutputStreamPtr = mojo::InterfacePtr<AudioOutputStream>; |
| +using AudioOutputStreamRequest = mojo::InterfaceRequest<AudioOutputStream>; |
| +using AudioOutputStreamProvider = media::mojom::AudioOutputStreamProvider; |
| +using AudioOutputStreamProviderPtr = |
| + mojo::InterfacePtr<AudioOutputStreamProvider>; |
| +using AudioOutputStreamProviderRequest = |
| + mojo::InterfaceRequest<AudioOutputStreamProvider>; |
| + |
| +const int kStreamId = 0; |
| +const int kNoSessionId = 0; |
| +const int kRenderProcessId = 42; |
| +const int kRenderFrameId = 24; |
| +const char kSecurityOrigin[] = "http://localhost"; |
| +const int kSampleFrequency = 44100; |
| +const int kBitsPerSample = 16; |
| +const int kSamplesPerBuffer = kSampleFrequency / 100; |
| +const char kSalt[] = "salt"; |
| + |
| +media::AudioParameters GetTestAudioParameters() { |
| + return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| + media::CHANNEL_LAYOUT_MONO, kSampleFrequency, |
| + kBitsPerSample, kSamplesPerBuffer); |
| +} |
| + |
| +class MockAudioOutputDelegate : public media::AudioOutputDelegate { |
| + MOCK_CONST_METHOD0(GetController, |
| + scoped_refptr<media::AudioOutputController>()); |
| + MOCK_CONST_METHOD0(GetStreamId, int()); |
| + MOCK_METHOD0(OnPlayStream, void()); |
| + MOCK_METHOD0(OnPauseStream, void()); |
| + MOCK_METHOD1(OnSetVolume, void(double)); |
| +}; |
| + |
| +class MockContext : public RendererAudioOutputStreamFactoryContext { |
| + public: |
| + explicit MockContext(bool auth_ok, int render_process_id = kRenderProcessId) |
| + : render_process_id_(render_process_id), |
| + salt_(kSalt), |
| + auth_ok_(auth_ok) {} |
| + |
| + ~MockContext() override { EXPECT_EQ(nullptr, delegate_); } |
| + |
| + int GetRenderProcessId() const override { return render_process_id_; } |
| + |
| + std::string GetHMACForDeviceId( |
| + const url::Origin& origin, |
| + const std::string& raw_device_id) const override { |
| + return MediaStreamManager::GetHMACForMediaDeviceID(salt_, origin, |
| + raw_device_id); |
| + } |
| + |
| + void RequestDeviceAuthorization( |
| + int render_frame_id, |
| + int session_id, |
| + const std::string& device_id, |
| + const url::Origin& security_origin, |
| + AuthorizationCompletedCallback cb) const override { |
| + EXPECT_EQ(render_frame_id, kRenderFrameId); |
| + EXPECT_EQ(session_id, 0); |
| + if (auth_ok_) { |
| + base::ThreadTaskRunnerHandle::Get()->PostTask( |
| + FROM_HERE, |
| + base::Bind(cb, media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK, |
| + false, GetTestAudioParameters(), "default")); |
| + return; |
| + } |
| + base::ThreadTaskRunnerHandle::Get()->PostTask( |
| + FROM_HERE, |
| + base::Bind(cb, |
| + media::OutputDeviceStatus:: |
| + OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED, |
| + false, media::AudioParameters::UnavailableDeviceParams(), |
| + "")); |
| + } |
| + |
| + // The event handler for the delegate will be stored at |
| + // |*event_handler_location| when the delegate is created. |
| + void PrepareDelegateForCreation( |
| + std::unique_ptr<media::AudioOutputDelegate> delegate, |
| + media::AudioOutputDelegate::EventHandler** event_handler_location) { |
| + EXPECT_EQ(nullptr, delegate_); |
| + EXPECT_EQ(nullptr, delegate_event_handler_location_); |
| + delegate_ = std::move(delegate); |
| + delegate_event_handler_location_ = event_handler_location; |
| + } |
| + |
| + std::unique_ptr<media::AudioOutputDelegate> CreateDelegate( |
| + const std::string& unique_device_id, |
| + int render_frame_id, |
| + const media::AudioParameters& params, |
| + media::AudioOutputDelegate::EventHandler* handler) override { |
| + EXPECT_NE(nullptr, delegate_); |
| + EXPECT_NE(nullptr, delegate_event_handler_location_); |
| + *delegate_event_handler_location_ = handler; |
| + delegate_event_handler_location_ = nullptr; |
| + return std::move(delegate_); |
| + } |
| + |
| + AudioOutputStreamFactoryPtr CreateFactory() { |
| + DCHECK(!factory_); |
| + AudioOutputStreamFactoryPtr ret; |
| + factory_ = base::MakeUnique<RenderFrameAudioOutputStreamFactory>( |
| + this, kRenderFrameId); |
| + factory_binding_ = base::MakeUnique< |
| + mojo::Binding<mojom::RendererAudioOutputStreamFactory>>(factory_.get(), |
| + &ret); |
| + return ret; |
| + } |
| + |
| + private: |
| + int render_process_id_ = kRenderProcessId; |
| + const std::string salt_; |
| + const bool auth_ok_; |
| + std::unique_ptr<RenderFrameAudioOutputStreamFactory> factory_; |
| + std::unique_ptr<mojo::Binding<mojom::RendererAudioOutputStreamFactory>> |
| + factory_binding_; |
| + std::unique_ptr<media::AudioOutputDelegate> delegate_; |
| + media::AudioOutputDelegate::EventHandler** delegate_event_handler_location_ = |
| + nullptr; |
| +}; |
| + |
| +class MockClient { |
| + public: |
| + MockClient() {} |
| + ~MockClient() {} |
| + |
| + void StreamCreated(mojo::ScopedSharedBufferHandle handle1, |
| + mojo::ScopedHandle handle2) { |
| + was_called_ = true; |
| + } |
| + |
| + bool was_called() { return was_called_; } |
| + |
| + private: |
| + bool was_called_ = false; |
| +}; |
| + |
| +void AuthCallback(media::OutputDeviceStatus* status_out, |
| + media::AudioParameters* params_out, |
| + std::string* id_out, |
| + media::OutputDeviceStatus status, |
| + const media::AudioParameters& params, |
| + const std::string& id) { |
| + *status_out = status; |
| + *params_out = params; |
| + *id_out = id; |
| +} |
| + |
| +} // namespace |
| + |
| +class RenderFrameAudioOutputStreamFactoryTest : public Test { |
| + public: |
| + RenderFrameAudioOutputStreamFactoryTest() {} |
| + ~RenderFrameAudioOutputStreamFactoryTest() override {} |
| + |
| + private: |
| + TestBrowserThreadBundle thread_bundle_; |
| +}; |
| + |
| +// This test authorizes and creates a stream, and checks that |
| +// 1. the authorization callback is called with appropriate parameters. |
| +// 2. the AudioOutputDelegate is created. |
| +// 3. when the delegate calls OnStreamCreated, this is propagated to the client. |
| +TEST_F(RenderFrameAudioOutputStreamFactoryTest, CreateStream) { |
| + AudioOutputStreamProviderPtr provider; |
| + AudioOutputStreamPtr output_stream; |
| + MockClient client; |
| + media::AudioOutputDelegate::EventHandler* event_handler = nullptr; |
| + auto factory_context = base::MakeUnique<MockContext>(true); |
| + factory_context->PrepareDelegateForCreation( |
| + base::MakeUnique<MockAudioOutputDelegate>(), &event_handler); |
| + AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory(); |
| + |
| + media::OutputDeviceStatus status; |
| + media::AudioParameters params; |
| + std::string id; |
| + factory_ptr->RequestDeviceAuthorization( |
| + mojo::MakeRequest(&provider), kNoSessionId, "default", |
| + url::Origin(GURL(kSecurityOrigin)), |
| + base::Bind(&AuthCallback, base::Unretained(&status), |
| + base::Unretained(¶ms), base::Unretained(&id))); |
| + base::RunLoop().RunUntilIdle(); |
| + EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK); |
| + EXPECT_EQ(params.AsHumanReadableString(), |
| + GetTestAudioParameters().AsHumanReadableString()); |
| + EXPECT_TRUE(id.empty()); |
| + |
| + provider->Acquire( |
| + mojo::MakeRequest<AudioOutputStream>(&output_stream), params, |
| + base::Bind(&MockClient::StreamCreated, base::Unretained(&client))); |
| + base::RunLoop().RunUntilIdle(); |
| + ASSERT_NE(event_handler, nullptr); |
| + |
| + base::SharedMemory shared_memory; |
| + ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(100)); |
| + |
| + base::CancelableSyncSocket local, remote; |
| + ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&local, &remote)); |
| + event_handler->OnStreamCreated(kStreamId, &shared_memory, &remote); |
| + |
| + base::RunLoop().RunUntilIdle(); |
| + // Make sure we got callback from creating stream. |
| + EXPECT_TRUE(client.was_called()); |
| +} |
| + |
| +TEST_F(RenderFrameAudioOutputStreamFactoryTest, NotAuthorized_Denied) { |
| + AudioOutputStreamProviderPtr output_provider; |
| + auto factory_context = base::MakeUnique<MockContext>(false); |
| + AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory(); |
| + |
| + media::OutputDeviceStatus status; |
| + media::AudioParameters params; |
| + std::string id; |
| + factory_ptr->RequestDeviceAuthorization( |
| + mojo::MakeRequest(&output_provider), kNoSessionId, "default", |
| + url::Origin(GURL(kSecurityOrigin)), |
| + base::Bind(&AuthCallback, base::Unretained(&status), |
| + base::Unretained(¶ms), base::Unretained(&id))); |
| + base::RunLoop().RunUntilIdle(); |
| + EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED); |
| + EXPECT_TRUE(id.empty()); |
| +} |
| + |
| +TEST_F(RenderFrameAudioOutputStreamFactoryTest, |
| + OutOfRangeSessionId_BadMessage) { |
| + // This test checks that we get a bad message if session_id is too large |
| + // to fit in an integer. This ensures that we don't overflow when casting the |
| + // int64_t to an int |
| + if (sizeof(int) >= sizeof(int64_t)) { |
| + // In this case, any int64_t would fit in an int, and the case we are |
| + // checking for is impossible. |
| + return; |
| + } |
| + |
| + TestBrowserContext browser_context; |
| + MockRenderProcessHost rph(&browser_context); |
| + |
| + AudioOutputStreamProviderPtr output_provider; |
| + auto factory_context = base::MakeUnique<MockContext>(true, rph.GetID()); |
| + AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory(); |
| + |
| + int64_t session_id = std::numeric_limits<int>::max(); |
| + ++session_id; |
| + factory_ptr->RequestDeviceAuthorization( |
| + mojo::MakeRequest(&output_provider), session_id, "default", |
| + url::Origin(GURL(kSecurityOrigin)), |
| + base::Bind([](media::OutputDeviceStatus, const media::AudioParameters&, |
| + const std::string&) {})); |
| + base::RunLoop().RunUntilIdle(); |
| + EXPECT_EQ(1, rph.bad_msg_count()); |
| +} |
| + |
| +} // namespace content |