Index: content/browser/renderer_host/media/render_frame_audio_output_service_unittest.cc |
diff --git a/content/browser/renderer_host/media/render_frame_audio_output_service_unittest.cc b/content/browser/renderer_host/media/render_frame_audio_output_service_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3e73719cbe181bca31cb79a0b005a0278c906538 |
--- /dev/null |
+++ b/content/browser/renderer_host/media/render_frame_audio_output_service_unittest.cc |
@@ -0,0 +1,255 @@ |
+// 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 "content/browser/renderer_host/media/render_frame_audio_output_service.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/audio_output_service_context_impl.h" |
+#include "content/browser/renderer_host/media/media_stream_manager.h" |
+#include "content/common/media/audio_output.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::_; |
+using testing::StrictMock; |
+using testing::Return; |
+using testing::Test; |
+using AudioOutputService = mojom::RendererAudioOutputService; |
+using AudioOutputServicePtr = mojo::InterfacePtr<AudioOutputService>; |
+using AudioOutputServiceRequest = mojo::InterfaceRequest<AudioOutputService>; |
+using AudioOutput = mojom::AudioOutput; |
+using AudioOutputPtr = mojo::InterfacePtr<AudioOutput>; |
+using AudioOutputRequest = mojo::InterfaceRequest<AudioOutput>; |
+ |
+const int kRenderProcessId = 42; |
+const int kRenderFrameId = 24; |
+const char kSecurityOrigin[] = "http://localhost"; |
+const char kSalt[] = "salt"; |
+const media::AudioParameters params( |
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
+ media::CHANNEL_LAYOUT_MONO, |
+ 44100 /*sample frequency*/, |
+ 16 /*bits per sample*/, |
+ 441 /*10 ms buffers*/); |
+ |
+class MockAudioOutputDelegate : public 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 AudioOutputServiceContext { |
+ public: |
+ explicit MockContext(bool auth_ok, int render_process_id = kRenderProcessId) |
+ : render_process_id_(render_process_id), |
+ salt_(kSalt), |
+ auth_ok_(auth_ok) {} |
+ |
+ ~MockContext() { EXPECT_EQ(nullptr, delegate_); } |
+ |
+ int GetRenderProcessId() const override { return render_process_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, media::AudioParameters::UnavailableDeviceParams(), |
+ "default")); |
+ return; |
+ } |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(cb, media::OutputDeviceStatus:: |
+ OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED, |
+ false, media::AudioParameters::UnavailableDeviceParams(), |
+ "default")); |
+ } |
+ |
+ const std::string& GetSalt() const override { return salt_; } |
+ |
+ void PrepareDelegateForCreation( |
+ std::unique_ptr<AudioOutputDelegate> delegate) { |
+ EXPECT_EQ(nullptr, delegate_); |
+ delegate_ = std::move(delegate); |
+ } |
+ |
+ std::unique_ptr<AudioOutputDelegate> CreateDelegate( |
+ const std::string& unique_device_id, |
+ int render_frame_id, |
+ AudioOutputDelegate::EventHandler* handler, |
+ const media::AudioParameters& params) override { |
+ EXPECT_NE(nullptr, delegate_); |
+ return std::move(delegate_); |
+ } |
+ |
+ MOCK_METHOD1(OnServiceFinished, |
+ void(mojom::RendererAudioOutputService* service)); |
+ |
+ private: |
+ int render_process_id_ = kRenderProcessId; |
+ const std::string salt_; |
+ const bool auth_ok_; |
+ std::unique_ptr<AudioOutputDelegate> delegate_; |
+}; |
+ |
+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 RenderFrameAudioOutputServiceTest : public Test { |
+ public: |
+ RenderFrameAudioOutputServiceTest() {} |
+ |
+ ~RenderFrameAudioOutputServiceTest() override {} |
+ |
+ private: |
+ TestBrowserThreadBundle thread_bundle_; |
+}; |
+ |
+TEST_F(RenderFrameAudioOutputServiceTest, AuthWithoutStreamRequest) { |
+ AudioOutputServicePtr service_ptr; |
+ auto service_context_ = base::MakeUnique<MockContext>(true); |
+ auto service_ = base::MakeUnique<RenderFrameAudioOutputService>( |
+ service_context_.get(), kRenderFrameId, mojo::MakeRequest(&service_ptr)); |
+ |
+ media::OutputDeviceStatus status; |
+ media::AudioParameters params2; |
+ std::string id; |
+ service_ptr->RequestDeviceAuthorization( |
+ nullptr, /*session_id*/ 0, "default", url::Origin(GURL(kSecurityOrigin)), |
+ base::Bind(&AuthCallback, base::Unretained(&status), |
+ base::Unretained(¶ms2), base::Unretained(&id))); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK); |
+ EXPECT_TRUE(id.empty()); |
+} |
+ |
+TEST_F(RenderFrameAudioOutputServiceTest, AuthWithStreamRequest) { |
+ AudioOutputServicePtr service_ptr; |
+ AudioOutputPtr output_ptr; |
+ auto service_context = base::MakeUnique<MockContext>(true); |
+ service_context->PrepareDelegateForCreation( |
+ base::MakeUnique<MockAudioOutputDelegate>()); |
+ auto service = base::MakeUnique<RenderFrameAudioOutputService>( |
+ service_context.get(), kRenderFrameId, mojo::MakeRequest(&service_ptr)); |
+ |
+ media::OutputDeviceStatus status; |
+ media::AudioParameters params2; |
+ std::string id; |
+ service_ptr->RequestDeviceAuthorization( |
+ mojo::MakeRequest<AudioOutput>(&output_ptr), /*session_id*/ 0, "default", |
+ url::Origin(GURL(kSecurityOrigin)), |
+ base::Bind(&AuthCallback, base::Unretained(&status), |
+ base::Unretained(¶ms2), base::Unretained(&id))); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK); |
+ EXPECT_TRUE(id.empty()); |
+ |
+ output_ptr->Start(params, |
+ base::Bind([](mojo::ScopedSharedBufferHandle handle1, |
+ mojo::ScopedHandle handle2) {})); |
+ base::RunLoop().RunUntilIdle(); |
+ output_ptr.reset(); |
+} |
+ |
+TEST_F(RenderFrameAudioOutputServiceTest, AuthWithStreamRequestDenied) { |
+ AudioOutputServicePtr service_ptr; |
+ AudioOutputPtr output_ptr; |
+ auto service_context = base::MakeUnique<MockContext>(false); |
+ auto service = base::MakeUnique<RenderFrameAudioOutputService>( |
+ service_context.get(), kRenderFrameId, mojo::MakeRequest(&service_ptr)); |
+ |
+ media::OutputDeviceStatus status; |
+ media::AudioParameters params2; |
+ std::string id; |
+ service_ptr->RequestDeviceAuthorization( |
+ mojo::MakeRequest<AudioOutput>(&output_ptr), /*session_id*/ 0, "default", |
+ url::Origin(GURL(kSecurityOrigin)), |
+ base::Bind(&AuthCallback, base::Unretained(&status), |
+ base::Unretained(¶ms2), base::Unretained(&id))); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED); |
+ EXPECT_TRUE(id.empty()); |
+ |
+ output_ptr->Start(params, |
+ base::Bind([](mojo::ScopedSharedBufferHandle handle1, |
+ mojo::ScopedHandle handle2) {})); |
+ base::RunLoop().RunUntilIdle(); |
+ output_ptr.reset(); |
+} |
+ |
+TEST_F(RenderFrameAudioOutputServiceTest, 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); |
+ |
+ AudioOutputServicePtr service_ptr; |
+ auto service_context = base::MakeUnique<MockContext>(true, rph.GetID()); |
+ auto service = base::MakeUnique<RenderFrameAudioOutputService>( |
+ service_context.get(), kRenderFrameId, mojo::MakeRequest(&service_ptr)); |
+ EXPECT_CALL(*service_context, OnServiceFinished(service.get())); |
+ |
+ int64_t session_id = std::numeric_limits<int>::max(); |
+ ++session_id; |
+ service_ptr->RequestDeviceAuthorization( |
+ nullptr, session_id, "default", url::Origin(GURL(kSecurityOrigin)), |
+ base::Bind([](media::OutputDeviceStatus, const media::AudioParameters&, |
+ const std::string&) { EXPECT_FALSE(true); })); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_EQ(1, rph.bad_msg_count()); |
+} |
+ |
+} // namespace content |