Index: media/mojo/clients/mojo_renderer_unittest.cc |
diff --git a/media/mojo/clients/mojo_renderer_unittest.cc b/media/mojo/clients/mojo_renderer_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8f90d3351e0fb31affdb01590881694470c00285 |
--- /dev/null |
+++ b/media/mojo/clients/mojo_renderer_unittest.cc |
@@ -0,0 +1,382 @@ |
+// 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 <stdint.h> |
+ |
+#include "base/bind.h" |
+#include "base/macros.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/run_loop.h" |
+#include "base/threading/platform_thread.h" |
+#include "media/base/cdm_config.h" |
+#include "media/base/cdm_context.h" |
+#include "media/base/gmock_callback_support.h" |
+#include "media/base/mock_filters.h" |
+#include "media/base/test_helpers.h" |
+#include "media/cdm/default_cdm_factory.h" |
+#include "media/mojo/clients/mojo_renderer_impl.h" |
+#include "media/mojo/common/media_type_converters.h" |
+#include "media/mojo/interfaces/content_decryption_module.mojom.h" |
+#include "media/mojo/interfaces/renderer.mojom.h" |
+#include "media/mojo/services/mojo_cdm_service.h" |
+#include "media/mojo/services/mojo_cdm_service_context.h" |
+#include "media/mojo/services/mojo_renderer_service.h" |
+#include "media/renderers/video_overlay_factory.h" |
+#include "mojo/public/cpp/bindings/interface_request.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using ::testing::_; |
+using ::testing::DoAll; |
+using ::testing::Return; |
+using ::testing::SaveArg; |
+using ::testing::StrictMock; |
+ |
+namespace media { |
+ |
+const int64_t kStartPlayingTimeInMs = 100; |
+const char kClearKeyKeySystem[] = "org.w3.clearkey"; |
+ |
+ACTION_P2(SetError, renderer_client, error) { |
+ renderer_client->OnError(error); |
+} |
+ |
+class MojoRendererTest : public ::testing::Test { |
+ public: |
+ MojoRendererTest() { |
+ std::unique_ptr<StrictMock<MockRenderer>> mock_renderer( |
+ new StrictMock<MockRenderer>()); |
+ mock_renderer_ = mock_renderer.get(); |
+ |
+ mojom::RendererPtr remote_renderer; |
+ |
+ mojo_renderer_service_ = new MojoRendererService( |
+ mojo_cdm_service_context_.GetWeakPtr(), std::move(mock_renderer), |
+ mojo::GetProxy(&remote_renderer)); |
+ |
+ mojo_renderer_.reset( |
+ new MojoRendererImpl(message_loop_.task_runner(), |
+ std::unique_ptr<VideoOverlayFactory>(nullptr), |
+ nullptr, std::move(remote_renderer))); |
+ |
+ // CreateAudioStream() and CreateVideoStream() overrides expectations for |
+ // expected non-NULL streams. |
+ EXPECT_CALL(demuxer_, GetStream(_)).WillRepeatedly(Return(nullptr)); |
+ |
+ EXPECT_CALL(*mock_renderer_, GetMediaTime()) |
+ .WillRepeatedly(Return(base::TimeDelta())); |
+ } |
+ |
+ virtual ~MojoRendererTest() {} |
+ |
+ void Destroy() { |
+ mojo_renderer_.reset(); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ // Completion callbacks. |
+ MOCK_METHOD1(OnInitialized, void(PipelineStatus)); |
+ MOCK_METHOD0(OnFlushed, void()); |
+ MOCK_METHOD1(OnCdmAttached, void(bool)); |
+ |
+ std::unique_ptr<StrictMock<MockDemuxerStream>> CreateStream( |
+ DemuxerStream::Type type) { |
+ std::unique_ptr<StrictMock<MockDemuxerStream>> stream( |
+ new StrictMock<MockDemuxerStream>(type)); |
+ return stream; |
+ } |
+ |
+ void CreateAudioStream() { |
+ audio_stream_ = CreateStream(DemuxerStream::AUDIO); |
+ EXPECT_CALL(demuxer_, GetStream(DemuxerStream::AUDIO)) |
+ .WillRepeatedly(Return(audio_stream_.get())); |
+ } |
+ |
+ void CreateVideoStream(bool is_encrypted = false) { |
+ video_stream_ = CreateStream(DemuxerStream::VIDEO); |
+ video_stream_->set_video_decoder_config( |
+ is_encrypted ? TestVideoConfig::NormalEncrypted() |
+ : TestVideoConfig::Normal()); |
+ EXPECT_CALL(demuxer_, GetStream(DemuxerStream::VIDEO)) |
+ .WillRepeatedly(Return(video_stream_.get())); |
+ } |
+ |
+ void InitializeAndExpect(PipelineStatus status) { |
+ DVLOG(1) << __FUNCTION__ << ": " << status; |
+ EXPECT_CALL(*this, OnInitialized(status)); |
+ mojo_renderer_->Initialize( |
+ &demuxer_, &renderer_client_, |
+ base::Bind(&MojoRendererTest::OnInitialized, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void Initialize() { |
+ CreateAudioStream(); |
+ EXPECT_CALL(*mock_renderer_, Initialize(_, _, _)) |
+ .WillOnce(DoAll(SaveArg<1>(&remote_renderer_client_), |
+ RunCallback<2>(PIPELINE_OK))); |
+ InitializeAndExpect(PIPELINE_OK); |
+ } |
+ |
+ void Flush() { |
+ DVLOG(1) << __FUNCTION__; |
+ // Flush callback should always be fired. |
+ EXPECT_CALL(*this, OnFlushed()); |
+ mojo_renderer_->Flush( |
+ base::Bind(&MojoRendererTest::OnFlushed, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void SetCdmAndExpect(bool success) { |
+ DVLOG(1) << __FUNCTION__; |
+ // Set CDM callback should always be fired. |
+ EXPECT_CALL(*this, OnCdmAttached(success)); |
+ mojo_renderer_->SetCdm( |
+ &cdm_context_, |
+ base::Bind(&MojoRendererTest::OnCdmAttached, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ // Simulates a connection error at the client side by killing the service. |
+ // Note that |mock_renderer_| will also be destroyed, do NOT expect anything |
+ // on it. Otherwise the test will crash. |
+ void ConnectionError() { |
+ DVLOG(1) << __FUNCTION__; |
+ delete mojo_renderer_service_; |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void OnCdmCreated(mojom::CdmPromiseResultPtr result, |
+ int cdm_id, |
+ mojom::DecryptorPtr decryptor) { |
+ EXPECT_TRUE(result->success); |
+ EXPECT_NE(CdmContext::kInvalidCdmId, cdm_id); |
+ cdm_context_.set_cdm_id(cdm_id); |
+ } |
+ |
+ void CreateCdm() { |
+ new MojoCdmService(mojo_cdm_service_context_.GetWeakPtr(), &cdm_factory_, |
+ mojo::GetProxy(&remote_cdm_)); |
+ remote_cdm_->Initialize( |
+ kClearKeyKeySystem, "https://www.test.com", |
+ mojom::CdmConfig::From(CdmConfig()), |
+ base::Bind(&MojoRendererTest::OnCdmCreated, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void StartPlayingFrom(base::TimeDelta start_time) { |
+ EXPECT_CALL(*mock_renderer_, StartPlayingFrom(start_time)); |
+ mojo_renderer_->StartPlayingFrom(start_time); |
+ EXPECT_EQ(start_time, mojo_renderer_->GetMediaTime()); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void Play() { |
+ StartPlayingFrom(base::TimeDelta::FromMilliseconds(kStartPlayingTimeInMs)); |
+ } |
+ |
+ // Fixture members. |
+ base::MessageLoop message_loop_; |
+ |
+ // The MojoRendererImpl that we are testing. |
+ std::unique_ptr<MojoRendererImpl> mojo_renderer_; |
+ |
+ // Client side mocks and helpers. |
+ StrictMock<MockRendererClient> renderer_client_; |
+ StrictMock<MockCdmContext> cdm_context_; |
+ mojom::ContentDecryptionModulePtr remote_cdm_; |
+ |
+ // Client side mock demuxer and demuxer streams. |
+ StrictMock<MockDemuxer> demuxer_; |
+ std::unique_ptr<StrictMock<MockDemuxerStream>> audio_stream_; |
+ std::unique_ptr<StrictMock<MockDemuxerStream>> video_stream_; |
+ |
+ // Service side mocks and helpers. |
+ StrictMock<MockRenderer>* mock_renderer_; |
+ MojoCdmServiceContext mojo_cdm_service_context_; |
+ RendererClient* remote_renderer_client_; |
+ DefaultCdmFactory cdm_factory_; |
+ |
+ // Owned by the connection. But we can delete it manually to trigger a |
+ // connection error at the client side. See ConnectionError(); |
+ MojoRendererService* mojo_renderer_service_; |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(MojoRendererTest); |
+}; |
+ |
+TEST_F(MojoRendererTest, Initialize_Success) { |
+ Initialize(); |
+} |
+ |
+TEST_F(MojoRendererTest, Initialize_Failure) { |
+ CreateAudioStream(); |
+ // Mojo Renderer only expects a boolean result, which will be translated |
+ // to PIPELINE_OK or PIPELINE_ERROR_INITIALIZATION_FAILED. |
+ EXPECT_CALL(*mock_renderer_, Initialize(_, _, _)) |
+ .WillOnce(RunCallback<2>(PIPELINE_ERROR_ABORT)); |
+ InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
+} |
+ |
+TEST_F(MojoRendererTest, Initialize_BeforeConnectionError) { |
+ CreateAudioStream(); |
+ EXPECT_CALL(*mock_renderer_, Initialize(_, _, _)) |
+ .WillOnce(InvokeWithoutArgs(this, &MojoRendererTest::ConnectionError)); |
+ InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
+} |
+ |
+TEST_F(MojoRendererTest, Initialize_AfterConnectionError) { |
+ ConnectionError(); |
+ CreateAudioStream(); |
+ InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
+} |
+ |
+TEST_F(MojoRendererTest, Flush_Success) { |
+ Initialize(); |
+ |
+ EXPECT_CALL(*mock_renderer_, Flush(_)).WillOnce(RunClosure<0>()); |
+ Flush(); |
+} |
+ |
+TEST_F(MojoRendererTest, Flush_ConnectionError) { |
+ Initialize(); |
+ |
+ // Upon connection error, OnError() should be called once and only once. |
+ EXPECT_CALL(renderer_client_, OnError(PIPELINE_ERROR_DECODE)).Times(1); |
+ EXPECT_CALL(*mock_renderer_, Flush(_)) |
+ .WillOnce(InvokeWithoutArgs(this, &MojoRendererTest::ConnectionError)); |
+ Flush(); |
+} |
+ |
+TEST_F(MojoRendererTest, Flush_AfterConnectionError) { |
+ Initialize(); |
+ |
+ // Upon connection error, OnError() should be called once and only once. |
+ EXPECT_CALL(renderer_client_, OnError(PIPELINE_ERROR_DECODE)).Times(1); |
+ ConnectionError(); |
+ |
+ Flush(); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_Success) { |
+ Initialize(); |
+ CreateCdm(); |
+ EXPECT_CALL(*mock_renderer_, SetCdm(_, _)).WillOnce(RunCallback<1>(true)); |
+ SetCdmAndExpect(true); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_Failure) { |
+ Initialize(); |
+ CreateCdm(); |
+ EXPECT_CALL(*mock_renderer_, SetCdm(_, _)).WillOnce(RunCallback<1>(false)); |
+ SetCdmAndExpect(false); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_InvalidCdmId) { |
+ Initialize(); |
+ SetCdmAndExpect(false); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_NonExistCdmId) { |
+ Initialize(); |
+ cdm_context_.set_cdm_id(1); |
+ SetCdmAndExpect(false); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_BeforeInitialize) { |
+ CreateCdm(); |
+ EXPECT_CALL(*mock_renderer_, SetCdm(_, _)).WillOnce(RunCallback<1>(true)); |
+ SetCdmAndExpect(true); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_AfterInitializeAndConnectionError) { |
+ CreateCdm(); |
+ Initialize(); |
+ EXPECT_CALL(renderer_client_, OnError(PIPELINE_ERROR_DECODE)).Times(1); |
+ ConnectionError(); |
+ SetCdmAndExpect(false); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_AfterConnectionErrorAndBeforeInitialize) { |
+ CreateCdm(); |
+ // Initialize() is not called so RendererClient::OnError() is not expected. |
+ ConnectionError(); |
+ SetCdmAndExpect(false); |
+ InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
+} |
+ |
+TEST_F(MojoRendererTest, SetCdm_BeforeInitializeAndConnectionError) { |
+ CreateCdm(); |
+ EXPECT_CALL(*mock_renderer_, SetCdm(_, _)).WillOnce(RunCallback<1>(true)); |
+ SetCdmAndExpect(true); |
+ // Initialize() is not called so RendererClient::OnError() is not expected. |
+ ConnectionError(); |
+ CreateAudioStream(); |
+ InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
+} |
+ |
+TEST_F(MojoRendererTest, StartPlayingFrom) { |
+ Initialize(); |
+ Play(); |
+} |
+ |
+TEST_F(MojoRendererTest, GetMediaTime) { |
+ Initialize(); |
+ EXPECT_EQ(base::TimeDelta(), mojo_renderer_->GetMediaTime()); |
+ |
+ Play(); |
+ |
+ // Time is updated periodically with a short delay. |
+ const base::TimeDelta kUpdatedTime = base::TimeDelta::FromMilliseconds(500); |
+ EXPECT_CALL(*mock_renderer_, GetMediaTime()) |
+ .WillRepeatedly(Return(kUpdatedTime)); |
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_EQ(kUpdatedTime, mojo_renderer_->GetMediaTime()); |
+} |
+ |
+TEST_F(MojoRendererTest, OnEnded) { |
+ Initialize(); |
+ Play(); |
+ |
+ EXPECT_CALL(renderer_client_, OnEnded()).Times(1); |
+ remote_renderer_client_->OnEnded(); |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+// TODO(xhwang): Add tests for all RendererClient methods. |
+ |
+TEST_F(MojoRendererTest, Destroy_PendingInitialize) { |
+ CreateAudioStream(); |
+ EXPECT_CALL(*mock_renderer_, Initialize(_, _, _)) |
+ .WillRepeatedly(RunCallback<2>(PIPELINE_ERROR_ABORT)); |
+ EXPECT_CALL(*this, OnInitialized(PIPELINE_ERROR_INITIALIZATION_FAILED)); |
+ mojo_renderer_->Initialize( |
+ &demuxer_, &renderer_client_, |
+ base::Bind(&MojoRendererTest::OnInitialized, base::Unretained(this))); |
+ Destroy(); |
+} |
+ |
+TEST_F(MojoRendererTest, Destroy_PendingFlush) { |
+ EXPECT_CALL(*mock_renderer_, SetCdm(_, _)) |
+ .WillRepeatedly(RunCallback<1>(true)); |
+ EXPECT_CALL(*this, OnCdmAttached(false)); |
+ mojo_renderer_->SetCdm( |
+ &cdm_context_, |
+ base::Bind(&MojoRendererTest::OnCdmAttached, base::Unretained(this))); |
+ Destroy(); |
+} |
+ |
+TEST_F(MojoRendererTest, Destroy_PendingSetCdm) { |
+ Initialize(); |
+ EXPECT_CALL(*mock_renderer_, Flush(_)).WillRepeatedly(RunClosure<0>()); |
+ EXPECT_CALL(*this, OnFlushed()); |
+ mojo_renderer_->Flush( |
+ base::Bind(&MojoRendererTest::OnFlushed, base::Unretained(this))); |
+ Destroy(); |
+} |
+ |
+// TODO(xhwang): Add more tests on OnError. For example, ErrorDuringFlush, |
+// ErrorAfterFlush etc. |
+ |
+} // namespace media |