From 8e0476c65cc2eb9d84a62d484c0358f9a0e325b0 Mon Sep 17 00:00:00 2001 From: "scherkus@chromium.org" Date: Fri, 6 Mar 2009 02:05:51 +0000 Subject: Fixed up MockFilterHost and MockPipeline to support tasks and callbacks. I changed the constructor to MockFilterHost to *not* create and initialize filters to allow for testing for conditions where create/initialize would fail. Also multiple MockFilterHosts can now share a common MockPipeline, which simulates how it is supposed to work. Finally, updated the VideoRendererBase tests to be deterministic and completely mocked and fixing failing unittests. BUG=8379 Review URL: http://codereview.chromium.org/39234 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@11085 0039d316-1c4b-4281-b951-d872f2087c98 --- media/base/mock_filter_host.h | 75 +++++++++++--------- media/base/mock_pipeline.h | 31 ++++++++- media/filters/file_data_source_unittest.cc | 30 +++++--- media/filters/video_renderer_unittest.cc | 106 ++++++++++++----------------- media/media.gyp | 3 + 5 files changed, 139 insertions(+), 106 deletions(-) (limited to 'media') diff --git a/media/base/mock_filter_host.h b/media/base/mock_filter_host.h index 490587f..3af36ac 100644 --- a/media/base/mock_filter_host.h +++ b/media/base/mock_filter_host.h @@ -2,6 +2,11 @@ // source code is governed by a BSD-style license that can be found in the // LICENSE file. +// The corresponding FilterHost implementation for MockPipeline. Maintains a +// reference to the parent MockPipeline and a reference to the Filter its +// hosting. Common usage is to check if the hosted filter has initialized by +// calling IsInitialized(). + #ifndef MEDIA_BASE_MOCK_FILTER_HOST_H_ #define MEDIA_BASE_MOCK_FILTER_HOST_H_ @@ -16,85 +21,93 @@ namespace media { -template +template class MockFilterHost : public FilterHost { public: - MockFilterHost(const MediaFormat* media_format, Source source) { - scoped_refptr factory = Filter::CreateFactory(); - filter_ = factory->Create(media_format); - EXPECT_TRUE(filter_.get()); - if (filter_.get()) { - filter_->SetFilterHost(this); - filter_->Initialize(source); - } + MockFilterHost(MockPipeline* mock_pipeline, Filter* filter) + : mock_pipeline_(mock_pipeline), + filter_(filter), + initialized_(false) { + EXPECT_TRUE(mock_pipeline_); + EXPECT_TRUE(filter_); + filter_->SetFilterHost(this); } - virtual ~MockFilterHost() { - filter_->Stop(); - } + virtual ~MockFilterHost() {} virtual const PipelineStatus* GetPipelineStatus() const { - return &mock_pipeline_; + return mock_pipeline_; } virtual void SetTimeUpdateCallback(Callback1::Type* cb) { - time_update_callback_.reset(cb); + time_update_callback_.reset(cb); } virtual void ScheduleTimeUpdateCallback(base::TimeDelta time) { - // TODO(ralphl): Implement MockFilte::ScheduleTimeUpdateCallback. - NOTIMPLEMENTED(); + scheduled_callback_time_ = time; } virtual void InitializationComplete() { - EXPECT_FALSE(mock_pipeline_.IsInitialized()); - mock_pipeline_.SetInitialized(true); + EXPECT_FALSE(initialized_); + initialized_ = true; } virtual void PostTask(Task* task) { - // TODO(ralphl): Implement MockPipeline::PostTask. - NOTIMPLEMENTED(); + mock_pipeline_->PostTask(task); } virtual void Error(PipelineError error) { - mock_pipeline_.Error(error); + mock_pipeline_->Error(error); } virtual void SetTime(base::TimeDelta time) { - mock_pipeline_.SetTime(time); + mock_pipeline_->SetTime(time); } virtual void SetDuration(base::TimeDelta duration) { - mock_pipeline_.SetDuration(duration); + mock_pipeline_->SetDuration(duration); } virtual void SetBufferedTime(base::TimeDelta buffered_time) { - mock_pipeline_.SetBufferedTime(buffered_time); + mock_pipeline_->SetBufferedTime(buffered_time); } virtual void SetTotalBytes(int64 total_bytes) { - mock_pipeline_.SetTotalBytes(total_bytes); + mock_pipeline_->SetTotalBytes(total_bytes); } virtual void SetBufferedBytes(int64 buffered_bytes) { - mock_pipeline_.SetBufferedBytes(buffered_bytes); + mock_pipeline_->SetBufferedBytes(buffered_bytes); } - // Sets the size of the video output in pixel units. virtual void SetVideoSize(size_t width, size_t height) { - mock_pipeline_.SetVideoSize(width, height); + mock_pipeline_->SetVideoSize(width, height); } // Used by unit tests to manipulate the filter. - Filter* filter() const { return filter_; } + base::TimeDelta GetScheduledCallbackTime() const { + return scheduled_callback_time_; + } + + Callback1::Type* GetTimeUpdateCallback() const { + return time_update_callback_.get(); + } - MockPipeline* mock_pipeline() const { return &mock_pipeline_; } + bool IsInitialized() const { + return initialized_; + } private: - MockPipeline mock_pipeline_; + MockPipeline* mock_pipeline_; scoped_refptr filter_; scoped_ptr::Type> time_update_callback_; + // Keeps track of the time passed into ScheduleTimeUpdateCallback(). + base::TimeDelta scheduled_callback_time_; + + // Tracks if the filter has executed InitializationComplete(). + bool initialized_; + DISALLOW_COPY_AND_ASSIGN(MockFilterHost); }; diff --git a/media/base/mock_pipeline.h b/media/base/mock_pipeline.h index d353c41..8741cc2 100644 --- a/media/base/mock_pipeline.h +++ b/media/base/mock_pipeline.h @@ -2,9 +2,16 @@ // source code is governed by a BSD-style license that can be found in the // LICENSE file. +// Mock implementation of Pipeline. Simply provides getters/setters for every +// pipeline state variable and queues all tasks posted to the "pipeline thread." +// Since there actually isn't a separate thread unit tests can control when +// they want to execute queued tasks by calling RunAllTasks(), which helps to +// assert pre- and post-conditions. + #ifndef MEDIA_BASE_MOCK_PIPELINE_H_ #define MEDIA_BASE_MOCK_PIPELINE_H_ +#include #include #include "media/base/media_format.h" @@ -117,8 +124,24 @@ class MockPipeline : public media::Pipeline { total_bytes_ = 0; } - void SetInitialized(bool init_value) { - initialized_ = init_value; + // Runs all queued tasks until there are no more. + // + // Although it is possible for tasks to run indefinitely (executing tasks post + // additional tasks), such situations should be treated as a bug. Since the + // pipeline is request/pull-based, only enough tasks to satisfy the request + // should ever be executed. + void RunAllTasks() { + while (!task_queue_.empty()) { + Task* task = task_queue_.front(); + task_queue_.pop_front(); + task->Run(); + delete task; + } + } + + void PostTask(Task* task) { + EXPECT_TRUE(task); + task_queue_.push_back(task); } void Error(media::PipelineError error) { @@ -146,7 +169,6 @@ class MockPipeline : public media::Pipeline { buffered_bytes_ = buffered_bytes; } - // Sets the size of the video output in pixel units. virtual void SetVideoSize(size_t width, size_t height) { width_ = width; height_ = height; @@ -165,6 +187,9 @@ class MockPipeline : public media::Pipeline { int64 buffered_bytes_; int64 total_bytes_; + typedef std::deque TaskQueue; + TaskQueue task_queue_; + DISALLOW_COPY_AND_ASSIGN(MockPipeline); }; diff --git a/media/filters/file_data_source_unittest.cc b/media/filters/file_data_source_unittest.cc index 3eb5262..3bce493 100644 --- a/media/filters/file_data_source_unittest.cc +++ b/media/filters/file_data_source_unittest.cc @@ -17,11 +17,13 @@ #include "media/base/factory.h" #include "media/base/filter_host.h" #include "media/base/mock_filter_host.h" +#include "media/base/mock_pipeline.h" #include "media/filters/file_data_source.h" #include "media/base/mock_media_filters.h" #include "testing/gtest/include/gtest/gtest.h" using media::FileDataSource; +using media::FilterFactory; using media::FilterFactoryCollection; using media::InitializationHelper; using media::MediaFormat; @@ -30,6 +32,7 @@ using media::MockAudioDecoder; using media::MockAudioRenderer; using media::MockFilterConfig; using media::MockFilterHost; +using media::MockPipeline; using media::PipelineImpl; namespace { @@ -84,25 +87,34 @@ TEST(FileDataSourceTest, ReadData) { std::string url = TestFileURL(); url_format.SetAsString(MediaFormat::kMimeType, media::mime_type::kURL); url_format.SetAsString(MediaFormat::kURL, url); - MockFilterHost mock_host(&url_format, url); - EXPECT_TRUE(mock_host.filter()->GetSize(&size)); + // Create our data source. + scoped_refptr factory = FileDataSource::CreateFactory(); + FileDataSource* filter = factory->Create(&url_format); + EXPECT_TRUE(filter); + + // Create our mock pipeline and filter host and initialize the data source. + MockPipeline pipeline; + MockFilterHost mock_host(&pipeline, filter); + EXPECT_TRUE(filter->Initialize(url)); + + EXPECT_TRUE(filter->GetSize(&size)); EXPECT_EQ(10, size); - EXPECT_TRUE(mock_host.filter()->GetPosition(&position)); + EXPECT_TRUE(filter->GetPosition(&position)); EXPECT_EQ(0, position); - EXPECT_EQ(10u, mock_host.filter()->Read(ten_bytes, sizeof(ten_bytes))); + EXPECT_EQ(10u, filter->Read(ten_bytes, sizeof(ten_bytes))); EXPECT_EQ('0', ten_bytes[0]); EXPECT_EQ('5', ten_bytes[5]); EXPECT_EQ('9', ten_bytes[9]); - EXPECT_TRUE(mock_host.filter()->GetPosition(&position)); + EXPECT_TRUE(filter->GetPosition(&position)); EXPECT_EQ(10, position); - EXPECT_EQ(0u, mock_host.filter()->Read(ten_bytes, sizeof(ten_bytes))); + EXPECT_EQ(0u, filter->Read(ten_bytes, sizeof(ten_bytes))); - EXPECT_TRUE(mock_host.filter()->SetPosition(5)); - EXPECT_EQ(5u, mock_host.filter()->Read(ten_bytes, sizeof(ten_bytes))); + EXPECT_TRUE(filter->SetPosition(5)); + EXPECT_EQ(5u, filter->Read(ten_bytes, sizeof(ten_bytes))); EXPECT_EQ('5', ten_bytes[0]); - EXPECT_TRUE(mock_host.filter()->GetPosition(&position)); + EXPECT_TRUE(filter->GetPosition(&position)); EXPECT_EQ(10, position); } diff --git a/media/filters/video_renderer_unittest.cc b/media/filters/video_renderer_unittest.cc index e1f3f49..96c04f1 100644 --- a/media/filters/video_renderer_unittest.cc +++ b/media/filters/video_renderer_unittest.cc @@ -2,79 +2,59 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include - #include "media/base/factory.h" -#include "media/base/filter_host.h" #include "media/base/filters.h" -#include "media/base/media_format.h" #include "media/base/mock_media_filters.h" -#include "media/base/pipeline_impl.h" +#include "media/base/mock_filter_host.h" #include "media/filters/test_video_renderer.h" #include "testing/gtest/include/gtest/gtest.h" -using media::FilterFactoryCollection; -using media::InstanceFilterFactory; -using media::MockAudioDecoder; -using media::MockAudioRenderer; -using media::MockDataSource; -using media::MockDemuxer; +using media::FilterFactory; using media::MockFilterConfig; +using media::MockFilterHost; +using media::MockPipeline; using media::MockVideoDecoder; -using media::PipelineImpl; using media::TestVideoRenderer; -using media::VideoFrame; -TEST(VideoRenderer, DISABLED_CreateTestRenderer) { - base::TimeDelta test_time = base::TimeDelta::FromMilliseconds(500); - std::string url(""); - PipelineImpl p; - scoped_refptr test_renderer = new TestVideoRenderer(); +TEST(VideoRenderer, CreateAndPlay) { + // Prepare test data. MockFilterConfig config; - scoped_refptr c = new FilterFactoryCollection(); - c->AddFactory(MockDataSource::CreateFactory(&config)); - c->AddFactory(MockDemuxer::CreateFactory(&config)); - c->AddFactory(MockAudioDecoder::CreateFactory(&config)); - c->AddFactory(MockAudioRenderer::CreateFactory(&config)); - c->AddFactory(MockVideoDecoder::CreateFactory(&config)); - c->AddFactory(new InstanceFilterFactory(test_renderer)); - media::InitializationHelper h; - h.Start(&p, c, url); - h.Wait(); - EXPECT_TRUE(p.IsInitialized()); - p.SetPlaybackRate(1.0f); - PlatformThread::Sleep(static_cast(test_time.InMilliseconds())); - p.Stop(); - // Allow a decent amount of variability here. We expect 15 or 16 frames - // but for now make sure it's within a reasonable range. - // TODO(ralphl): This test is now DISABLED because sometimes on linux we - // only get the first frame. Investigate why, but for now disabled. - int64 num_expected_frames = test_time / config.frame_duration; - EXPECT_GT(test_renderer->unique_frames(), num_expected_frames - 3); - EXPECT_LT(test_renderer->unique_frames(), num_expected_frames + 3); -} + scoped_refptr factory + = MockVideoDecoder::CreateFactory(&config); + scoped_refptr decoder + = factory->Create(NULL); + scoped_refptr renderer = new TestVideoRenderer(); -TEST(VideoRenderer, SingleVideoFrame) { - base::TimeDelta test_time = base::TimeDelta::FromMilliseconds(100); - std::string url(""); - PipelineImpl p; - scoped_refptr test_renderer = new TestVideoRenderer(); - MockFilterConfig config; - config.media_duration = config.frame_duration; - scoped_refptr c = new FilterFactoryCollection(); - c->AddFactory(MockDataSource::CreateFactory(&config)); - c->AddFactory(MockDemuxer::CreateFactory(&config)); - c->AddFactory(MockAudioDecoder::CreateFactory(&config)); - c->AddFactory(MockAudioRenderer::CreateFactory(&config)); - c->AddFactory(MockVideoDecoder::CreateFactory(&config)); - c->AddFactory(new InstanceFilterFactory(test_renderer)); - media::InitializationHelper h; - h.Start(&p, c, url); - h.TimedWait(base::TimeDelta::FromSeconds(1)); - EXPECT_TRUE(p.IsInitialized()); - p.SetPlaybackRate(1.0f); - PlatformThread::Sleep(static_cast(test_time.InMilliseconds())); - p.Stop(); - EXPECT_EQ(test_renderer->unique_frames(), 1u); - EXPECT_EQ(test_renderer->paint_called(), 1u); + // Setup our mock pipeline. + MockPipeline pipeline; + MockFilterHost filter_host_a(&pipeline, decoder); + MockFilterHost filter_host_b(&pipeline, renderer); + + // Initialize the video renderer and run pending tasks. It should set its + // time update callback and scheduled its first callback time. + EXPECT_TRUE(renderer->Initialize(decoder)); + EXPECT_FALSE(filter_host_b.IsInitialized()); + pipeline.RunAllTasks(); + EXPECT_TRUE(filter_host_b.IsInitialized()); + EXPECT_TRUE(filter_host_b.GetTimeUpdateCallback()); + EXPECT_NE(0, filter_host_b.GetScheduledCallbackTime().InMicroseconds()); + + // We also expect one unique frame due to the preroll paint. + EXPECT_EQ(1u, renderer->unique_frames()); + + // Now lets simulate playing 10 frames... + for (int i = 0; i < 10; ++i) { + base::TimeDelta previous_time = filter_host_b.GetScheduledCallbackTime(); + size_t previous_unique_frames = renderer->unique_frames(); + + // Advance time to the callback time and execute. + pipeline.SetTime(previous_time); + filter_host_b.GetTimeUpdateCallback()->Run(previous_time); + pipeline.RunAllTasks(); + + // Renderer should have scheduled a new callback time and painted a frame. + EXPECT_GT(filter_host_b.GetScheduledCallbackTime().InMicroseconds(), + previous_time.InMicroseconds()); + EXPECT_EQ(previous_unique_frames + 1, renderer->unique_frames()); + } } diff --git a/media/media.gyp b/media/media.gyp index 59832ba..0300d65 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -62,6 +62,8 @@ 'filters/file_data_source.h', 'filters/null_audio_renderer.cc', 'filters/null_audio_renderer.h', + 'filters/video_renderer_base.cc', + 'filters/video_renderer_base.h', 'player/player.cc', ], 'direct_dependent_settings': { @@ -84,6 +86,7 @@ 'base/pipeline_impl_unittest.cc', 'base/run_all_unittests.cc', 'filters/file_data_source_unittest.cc', + 'filters/video_renderer_unittest.cc', ], }, ], -- cgit v1.1