blob: 912fb263ca96493623e3b319fa39a59d65c0ac6c [file] [log] [blame]
// Copyright (c) 2012 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 <vector>
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/threading/simple_thread.h"
#include "base/time/clock.h"
#include "media/base/fake_text_track_stream.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/media_log.h"
#include "media/base/mock_filters.h"
#include "media/base/pipeline.h"
#include "media/base/test_helpers.h"
#include "media/base/text_renderer.h"
#include "media/base/text_track_config.h"
#include "media/base/time_delta_interpolator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/size.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DeleteArg;
using ::testing::DoAll;
// TODO(scherkus): Remove InSequence after refactoring Pipeline.
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::WithArg;
namespace media {
ACTION_P(SetDemuxerProperties, duration) {
arg0->SetDuration(duration);
}
ACTION_P2(Stop, pipeline, stop_cb) {
pipeline->Stop(stop_cb);
}
ACTION_P2(SetError, pipeline, status) {
pipeline->SetErrorForTesting(status);
}
ACTION_P2(SetBufferingState, cb, buffering_state) {
cb->Run(buffering_state);
}
// TODO(scherkus): even though some filters are initialized on separate
// threads these test aren't flaky... why? It's because filters' Initialize()
// is executed on |message_loop_| and the mock filters instantly call
// InitializationComplete(), which keeps the pipeline humming along. If
// either filters don't call InitializationComplete() immediately or filter
// initialization is moved to a separate thread this test will become flaky.
class PipelineTest : public ::testing::Test {
public:
// Used for setting expectations on pipeline callbacks. Using a StrictMock
// also lets us test for missing callbacks.
class CallbackHelper {
public:
CallbackHelper() {}
virtual ~CallbackHelper() {}
MOCK_METHOD1(OnStart, void(PipelineStatus));
MOCK_METHOD1(OnSeek, void(PipelineStatus));
MOCK_METHOD0(OnStop, void());
MOCK_METHOD0(OnEnded, void());
MOCK_METHOD1(OnError, void(PipelineStatus));
MOCK_METHOD1(OnMetadata, void(PipelineMetadata));
MOCK_METHOD1(OnBufferingStateChange, void(BufferingState));
MOCK_METHOD1(OnVideoFramePaint, void(const scoped_refptr<VideoFrame>&));
MOCK_METHOD0(OnDurationChange, void());
private:
DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
};
PipelineTest()
: pipeline_(new Pipeline(message_loop_.message_loop_proxy(),
new MediaLog())),
demuxer_(new StrictMock<MockDemuxer>()),
scoped_renderer_(new StrictMock<MockRenderer>()),
renderer_(scoped_renderer_.get()) {
// SetDemuxerExpectations() adds overriding expectations for expected
// non-NULL streams.
DemuxerStream* null_pointer = NULL;
EXPECT_CALL(*demuxer_, GetStream(_))
.WillRepeatedly(Return(null_pointer));
EXPECT_CALL(*demuxer_, GetTimelineOffset())
.WillRepeatedly(Return(base::Time()));
EXPECT_CALL(*renderer_, GetMediaTime())
.WillRepeatedly(Return(base::TimeDelta()));
EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_));
}
virtual ~PipelineTest() {
if (!pipeline_ || !pipeline_->IsRunning())
return;
ExpectDemuxerStop();
// The mock demuxer doesn't stop the fake text track stream,
// so just stop it manually.
if (text_stream_) {
text_stream_->Stop();
message_loop_.RunUntilIdle();
}
// Expect a stop callback if we were started.
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(base::Bind(&CallbackHelper::OnStop,
base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
void OnDemuxerError() {
// Cast because OnDemuxerError is private in Pipeline.
static_cast<DemuxerHost*>(pipeline_.get())
->OnDemuxerError(PIPELINE_ERROR_ABORT);
}
protected:
// Sets up expectations to allow the demuxer to initialize.
typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector;
void SetDemuxerExpectations(MockDemuxerStreamVector* streams,
const base::TimeDelta& duration) {
EXPECT_CALL(callbacks_, OnDurationChange());
EXPECT_CALL(*demuxer_, Initialize(_, _, _))
.WillOnce(DoAll(SetDemuxerProperties(duration),
RunCallback<1>(PIPELINE_OK)));
// Configure the demuxer to return the streams.
for (size_t i = 0; i < streams->size(); ++i) {
DemuxerStream* stream = (*streams)[i];
EXPECT_CALL(*demuxer_, GetStream(stream->type()))
.WillRepeatedly(Return(stream));
}
}
void SetDemuxerExpectations(MockDemuxerStreamVector* streams) {
// Initialize with a default non-zero duration.
SetDemuxerExpectations(streams, base::TimeDelta::FromSeconds(10));
}
scoped_ptr<StrictMock<MockDemuxerStream> > CreateStream(
DemuxerStream::Type type) {
scoped_ptr<StrictMock<MockDemuxerStream> > stream(
new StrictMock<MockDemuxerStream>(type));
return stream.Pass();
}
// Sets up expectations to allow the video renderer to initialize.
void SetRendererExpectations() {
EXPECT_CALL(*renderer_, Initialize(_, _, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&buffering_state_cb_),
SaveArg<5>(&ended_cb_),
RunCallback<1>()));
EXPECT_CALL(*renderer_, HasAudio()).WillRepeatedly(Return(audio_stream()));
EXPECT_CALL(*renderer_, HasVideo()).WillRepeatedly(Return(video_stream()));
}
void AddTextStream() {
EXPECT_CALL(*this, OnAddTextTrack(_,_))
.WillOnce(Invoke(this, &PipelineTest::DoOnAddTextTrack));
static_cast<DemuxerHost*>(pipeline_.get())->AddTextStream(text_stream(),
TextTrackConfig(kTextSubtitles, "", "", ""));
message_loop_.RunUntilIdle();
}
void StartPipeline() {
pipeline_->Start(
demuxer_.get(), scoped_renderer_.Pass(),
base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)),
base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)),
base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)),
base::Bind(&CallbackHelper::OnMetadata, base::Unretained(&callbacks_)),
base::Bind(&CallbackHelper::OnBufferingStateChange,
base::Unretained(&callbacks_)),
base::Bind(&CallbackHelper::OnVideoFramePaint,
base::Unretained(&callbacks_)),
base::Bind(&CallbackHelper::OnDurationChange,
base::Unretained(&callbacks_)),
base::Bind(&PipelineTest::OnAddTextTrack, base::Unretained(this)));
}
// Sets up expectations on the callback and initializes the pipeline. Called
// after tests have set expectations any filters they wish to use.
void StartPipelineAndExpect(PipelineStatus start_status) {
EXPECT_CALL(callbacks_, OnStart(start_status));
if (start_status == PIPELINE_OK) {
EXPECT_CALL(callbacks_, OnMetadata(_)).WillOnce(SaveArg<0>(&metadata_));
EXPECT_CALL(*renderer_, SetPlaybackRate(0.0f));
EXPECT_CALL(*renderer_, SetVolume(1.0f));
EXPECT_CALL(*renderer_, StartPlayingFrom(start_time_))
.WillOnce(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_ENOUGH));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
}
StartPipeline();
message_loop_.RunUntilIdle();
}
void CreateAudioStream() {
audio_stream_ = CreateStream(DemuxerStream::AUDIO);
}
void CreateVideoStream() {
video_stream_ = CreateStream(DemuxerStream::VIDEO);
video_stream_->set_video_decoder_config(video_decoder_config_);
}
void CreateTextStream() {
scoped_ptr<FakeTextTrackStream> text_stream(new FakeTextTrackStream());
EXPECT_CALL(*text_stream, OnRead()).Times(AnyNumber());
text_stream_ = text_stream.Pass();
}
MockDemuxerStream* audio_stream() {
return audio_stream_.get();
}
MockDemuxerStream* video_stream() {
return video_stream_.get();
}
FakeTextTrackStream* text_stream() {
return text_stream_.get();
}
void ExpectSeek(const base::TimeDelta& seek_time, bool underflowed) {
EXPECT_CALL(*demuxer_, Seek(seek_time, _))
.WillOnce(RunCallback<1>(PIPELINE_OK));
EXPECT_CALL(*renderer_, Flush(_))
.WillOnce(DoAll(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_NOTHING),
RunClosure<0>()));
EXPECT_CALL(*renderer_, SetPlaybackRate(_));
EXPECT_CALL(*renderer_, SetVolume(_));
EXPECT_CALL(*renderer_, StartPlayingFrom(seek_time))
.WillOnce(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_ENOUGH));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
// We expect a successful seek callback followed by a buffering update.
EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
}
void DoSeek(const base::TimeDelta& seek_time) {
pipeline_->Seek(seek_time,
base::Bind(&CallbackHelper::OnSeek,
base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
void DestroyPipeline() {
// In real code Pipeline could be destroyed on a different thread. All weak
// pointers must have been invalidated before the stop callback returns.
DCHECK(!pipeline_->HasWeakPtrsForTesting());
pipeline_.reset();
}
void ExpectDemuxerStop() {
if (demuxer_)
EXPECT_CALL(*demuxer_, Stop());
}
void ExpectPipelineStopAndDestroyPipeline() {
// After the Pipeline is stopped, it could be destroyed any time. Always
// destroy the pipeline immediately after OnStop() to test this.
EXPECT_CALL(callbacks_, OnStop())
.WillOnce(Invoke(this, &PipelineTest::DestroyPipeline));
}
MOCK_METHOD2(OnAddTextTrack, void(const TextTrackConfig&,
const AddTextTrackDoneCB&));
void DoOnAddTextTrack(const TextTrackConfig& config,
const AddTextTrackDoneCB& done_cb) {
scoped_ptr<TextTrack> text_track(new MockTextTrack);
done_cb.Run(text_track.Pass());
}
// Fixture members.
StrictMock<CallbackHelper> callbacks_;
base::SimpleTestTickClock test_tick_clock_;
base::MessageLoop message_loop_;
scoped_ptr<Pipeline> pipeline_;
scoped_ptr<StrictMock<MockDemuxer> > demuxer_;
scoped_ptr<StrictMock<MockRenderer> > scoped_renderer_;
StrictMock<MockRenderer>* renderer_;
StrictMock<CallbackHelper> text_renderer_callbacks_;
TextRenderer* text_renderer_;
scoped_ptr<StrictMock<MockDemuxerStream> > audio_stream_;
scoped_ptr<StrictMock<MockDemuxerStream> > video_stream_;
scoped_ptr<FakeTextTrackStream> text_stream_;
BufferingStateCB buffering_state_cb_;
base::Closure ended_cb_;
VideoDecoderConfig video_decoder_config_;
PipelineMetadata metadata_;
base::TimeDelta start_time_;
private:
DISALLOW_COPY_AND_ASSIGN(PipelineTest);
};
// Test that playback controls methods no-op when the pipeline hasn't been
// started.
TEST_F(PipelineTest, NotStarted) {
const base::TimeDelta kZero;
EXPECT_FALSE(pipeline_->IsRunning());
// Setting should still work.
EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate());
pipeline_->SetPlaybackRate(-1.0f);
EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate());
pipeline_->SetPlaybackRate(1.0f);
EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate());
// Setting should still work.
EXPECT_EQ(1.0f, pipeline_->GetVolume());
pipeline_->SetVolume(-1.0f);
EXPECT_EQ(1.0f, pipeline_->GetVolume());
pipeline_->SetVolume(0.0f);
EXPECT_EQ(0.0f, pipeline_->GetVolume());
EXPECT_TRUE(kZero == pipeline_->GetMediaTime());
EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size());
EXPECT_TRUE(kZero == pipeline_->GetMediaDuration());
}
TEST_F(PipelineTest, NeverInitializes) {
// Don't execute the callback passed into Initialize().
EXPECT_CALL(*demuxer_, Initialize(_, _, _));
// This test hangs during initialization by never calling
// InitializationComplete(). StrictMock<> will ensure that the callback is
// never executed.
StartPipeline();
message_loop_.RunUntilIdle();
// Because our callback will get executed when the test tears down, we'll
// verify that nothing has been called, then set our expectation for the call
// made during tear down.
Mock::VerifyAndClear(&callbacks_);
EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK));
}
TEST_F(PipelineTest, StopWithoutStart) {
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(
base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
TEST_F(PipelineTest, StartThenStopImmediately) {
EXPECT_CALL(*demuxer_, Initialize(_, _, _))
.WillOnce(RunCallback<1>(PIPELINE_OK));
EXPECT_CALL(*demuxer_, Stop());
EXPECT_CALL(callbacks_, OnStart(_));
StartPipeline();
// Expect a stop callback if we were started.
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(
base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
TEST_F(PipelineTest, DemuxerErrorDuringStop) {
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_CALL(*demuxer_, Stop())
.WillOnce(InvokeWithoutArgs(this, &PipelineTest::OnDemuxerError));
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(
base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
TEST_F(PipelineTest, URLNotFound) {
EXPECT_CALL(*demuxer_, Initialize(_, _, _))
.WillOnce(RunCallback<1>(PIPELINE_ERROR_URL_NOT_FOUND));
EXPECT_CALL(*demuxer_, Stop());
StartPipelineAndExpect(PIPELINE_ERROR_URL_NOT_FOUND);
}
TEST_F(PipelineTest, NoStreams) {
EXPECT_CALL(*demuxer_, Initialize(_, _, _))
.WillOnce(RunCallback<1>(PIPELINE_OK));
EXPECT_CALL(*demuxer_, Stop());
StartPipelineAndExpect(PIPELINE_ERROR_COULD_NOT_RENDER);
}
TEST_F(PipelineTest, AudioStream) {
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_TRUE(metadata_.has_audio);
EXPECT_FALSE(metadata_.has_video);
}
TEST_F(PipelineTest, VideoStream) {
CreateVideoStream();
MockDemuxerStreamVector streams;
streams.push_back(video_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_FALSE(metadata_.has_audio);
EXPECT_TRUE(metadata_.has_video);
}
TEST_F(PipelineTest, AudioVideoStream) {
CreateAudioStream();
CreateVideoStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
streams.push_back(video_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_TRUE(metadata_.has_audio);
EXPECT_TRUE(metadata_.has_video);
}
TEST_F(PipelineTest, VideoTextStream) {
CreateVideoStream();
CreateTextStream();
MockDemuxerStreamVector streams;
streams.push_back(video_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_FALSE(metadata_.has_audio);
EXPECT_TRUE(metadata_.has_video);
AddTextStream();
}
TEST_F(PipelineTest, VideoAudioTextStream) {
CreateVideoStream();
CreateAudioStream();
CreateTextStream();
MockDemuxerStreamVector streams;
streams.push_back(video_stream());
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_TRUE(metadata_.has_audio);
EXPECT_TRUE(metadata_.has_video);
AddTextStream();
}
TEST_F(PipelineTest, Seek) {
CreateAudioStream();
CreateVideoStream();
CreateTextStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
streams.push_back(video_stream());
SetDemuxerExpectations(&streams, base::TimeDelta::FromSeconds(3000));
SetRendererExpectations();
// Initialize then seek!
StartPipelineAndExpect(PIPELINE_OK);
// Every filter should receive a call to Seek().
base::TimeDelta expected = base::TimeDelta::FromSeconds(2000);
ExpectSeek(expected, false);
DoSeek(expected);
}
TEST_F(PipelineTest, SeekAfterError) {
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams, base::TimeDelta::FromSeconds(3000));
SetRendererExpectations();
// Initialize then seek!
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_CALL(*demuxer_, Stop());
EXPECT_CALL(callbacks_, OnError(_));
static_cast<DemuxerHost*>(pipeline_.get())
->OnDemuxerError(PIPELINE_ERROR_ABORT);
message_loop_.RunUntilIdle();
pipeline_->Seek(
base::TimeDelta::FromMilliseconds(100),
base::Bind(&CallbackHelper::OnSeek, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
TEST_F(PipelineTest, SetVolume) {
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
// The audio renderer should receive a call to SetVolume().
float expected = 0.5f;
EXPECT_CALL(*renderer_, SetVolume(expected));
// Initialize then set volume!
StartPipelineAndExpect(PIPELINE_OK);
pipeline_->SetVolume(expected);
}
TEST_F(PipelineTest, Properties) {
CreateVideoStream();
MockDemuxerStreamVector streams;
streams.push_back(video_stream());
const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
SetDemuxerExpectations(&streams, kDuration);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_EQ(kDuration.ToInternalValue(),
pipeline_->GetMediaDuration().ToInternalValue());
EXPECT_FALSE(pipeline_->DidLoadingProgress());
}
TEST_F(PipelineTest, GetBufferedTimeRanges) {
CreateVideoStream();
MockDemuxerStreamVector streams;
streams.push_back(video_stream());
const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
SetDemuxerExpectations(&streams, kDuration);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size());
EXPECT_FALSE(pipeline_->DidLoadingProgress());
pipeline_->AddBufferedTimeRange(base::TimeDelta(), kDuration / 8);
EXPECT_TRUE(pipeline_->DidLoadingProgress());
EXPECT_FALSE(pipeline_->DidLoadingProgress());
EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));
base::TimeDelta kSeekTime = kDuration / 2;
ExpectSeek(kSeekTime, false);
DoSeek(kSeekTime);
EXPECT_FALSE(pipeline_->DidLoadingProgress());
}
TEST_F(PipelineTest, EndedCallback) {
CreateAudioStream();
CreateVideoStream();
CreateTextStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
streams.push_back(video_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
AddTextStream();
// The ended callback shouldn't run until all renderers have ended.
ended_cb_.Run();
message_loop_.RunUntilIdle();
EXPECT_CALL(callbacks_, OnEnded());
text_stream()->SendEosNotification();
message_loop_.RunUntilIdle();
}
TEST_F(PipelineTest, ErrorDuringSeek) {
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
float playback_rate = 1.0f;
EXPECT_CALL(*renderer_, SetPlaybackRate(playback_rate));
pipeline_->SetPlaybackRate(playback_rate);
message_loop_.RunUntilIdle();
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
EXPECT_CALL(*renderer_, Flush(_))
.WillOnce(DoAll(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_NOTHING),
RunClosure<0>()));
EXPECT_CALL(*demuxer_, Seek(seek_time, _))
.WillOnce(RunCallback<1>(PIPELINE_ERROR_READ));
EXPECT_CALL(*demuxer_, Stop());
pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek,
base::Unretained(&callbacks_)));
EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ));
message_loop_.RunUntilIdle();
}
// Invoked function OnError. This asserts that the pipeline does not enqueue
// non-teardown related tasks while tearing down.
static void TestNoCallsAfterError(
Pipeline* pipeline, base::MessageLoop* message_loop,
PipelineStatus /* status */) {
CHECK(pipeline);
CHECK(message_loop);
// When we get to this stage, the message loop should be empty.
EXPECT_TRUE(message_loop->IsIdleForTesting());
// Make calls on pipeline after error has occurred.
pipeline->SetPlaybackRate(0.5f);
pipeline->SetVolume(0.5f);
// No additional tasks should be queued as a result of these calls.
EXPECT_TRUE(message_loop->IsIdleForTesting());
}
TEST_F(PipelineTest, NoMessageDuringTearDownFromError) {
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
// Trigger additional requests on the pipeline during tear down from error.
base::Callback<void(PipelineStatus)> cb = base::Bind(
&TestNoCallsAfterError, pipeline_.get(), &message_loop_);
ON_CALL(callbacks_, OnError(_))
.WillByDefault(Invoke(&cb, &base::Callback<void(PipelineStatus)>::Run));
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
// Seek() isn't called as the demuxer errors out first.
EXPECT_CALL(*renderer_, Flush(_))
.WillOnce(DoAll(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_NOTHING),
RunClosure<0>()));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
EXPECT_CALL(*demuxer_, Seek(seek_time, _))
.WillOnce(RunCallback<1>(PIPELINE_ERROR_READ));
EXPECT_CALL(*demuxer_, Stop());
pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek,
base::Unretained(&callbacks_)));
EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ));
message_loop_.RunUntilIdle();
}
TEST_F(PipelineTest, DestroyAfterStop) {
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
ExpectDemuxerStop();
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(
base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
TEST_F(PipelineTest, Underflow) {
CreateAudioStream();
CreateVideoStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
streams.push_back(video_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
// Simulate underflow.
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
buffering_state_cb_.Run(BUFFERING_HAVE_NOTHING);
// Seek while underflowed.
base::TimeDelta expected = base::TimeDelta::FromSeconds(5);
ExpectSeek(expected, true);
DoSeek(expected);
}
TEST_F(PipelineTest, PositiveStartTime) {
start_time_ = base::TimeDelta::FromSeconds(1);
EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_));
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
ExpectDemuxerStop();
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(
base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
class PipelineTeardownTest : public PipelineTest {
public:
enum TeardownState {
kInitDemuxer,
kInitRenderer,
kFlushing,
kSeeking,
kPlaying,
};
enum StopOrError {
kStop,
kError,
kErrorAndStop,
};
PipelineTeardownTest() {}
~PipelineTeardownTest() override {}
void RunTest(TeardownState state, StopOrError stop_or_error) {
switch (state) {
case kInitDemuxer:
case kInitRenderer:
DoInitialize(state, stop_or_error);
break;
case kFlushing:
case kSeeking:
DoInitialize(state, stop_or_error);
DoSeek(state, stop_or_error);
break;
case kPlaying:
DoInitialize(state, stop_or_error);
DoStopOrError(stop_or_error);
break;
}
}
private:
// TODO(scherkus): We do radically different things whether teardown is
// invoked via stop vs error. The teardown path should be the same,
// see http://crbug.com/110228
void DoInitialize(TeardownState state, StopOrError stop_or_error) {
PipelineStatus expected_status =
SetInitializeExpectations(state, stop_or_error);
EXPECT_CALL(callbacks_, OnStart(expected_status));
StartPipeline();
message_loop_.RunUntilIdle();
}
PipelineStatus SetInitializeExpectations(TeardownState state,
StopOrError stop_or_error) {
PipelineStatus status = PIPELINE_OK;
base::Closure stop_cb = base::Bind(
&CallbackHelper::OnStop, base::Unretained(&callbacks_));
if (state == kInitDemuxer) {
if (stop_or_error == kStop) {
EXPECT_CALL(*demuxer_, Initialize(_, _, _))
.WillOnce(DoAll(Stop(pipeline_.get(), stop_cb),
RunCallback<1>(PIPELINE_OK)));
ExpectPipelineStopAndDestroyPipeline();
} else {
status = DEMUXER_ERROR_COULD_NOT_OPEN;
EXPECT_CALL(*demuxer_, Initialize(_, _, _))
.WillOnce(RunCallback<1>(status));
}
EXPECT_CALL(*demuxer_, Stop());
return status;
}
CreateAudioStream();
CreateVideoStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
streams.push_back(video_stream());
SetDemuxerExpectations(&streams, base::TimeDelta::FromSeconds(3000));
EXPECT_CALL(*renderer_, HasAudio()).WillRepeatedly(Return(true));
EXPECT_CALL(*renderer_, HasVideo()).WillRepeatedly(Return(true));
if (state == kInitRenderer) {
if (stop_or_error == kStop) {
EXPECT_CALL(*renderer_, Initialize(_, _, _, _, _, _, _))
.WillOnce(DoAll(Stop(pipeline_.get(), stop_cb),
RunCallback<1>()));
ExpectPipelineStopAndDestroyPipeline();
} else {
status = PIPELINE_ERROR_INITIALIZATION_FAILED;
EXPECT_CALL(*renderer_, Initialize(_, _, _, _, _, _, _))
.WillOnce(DoAll(RunCallback<6>(status), RunCallback<1>()));
}
EXPECT_CALL(*demuxer_, Stop());
return status;
}
EXPECT_CALL(*renderer_, Initialize(_, _, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&buffering_state_cb_),
RunCallback<1>()));
EXPECT_CALL(callbacks_, OnMetadata(_));
// If we get here it's a successful initialization.
EXPECT_CALL(*renderer_, SetPlaybackRate(0.0f));
EXPECT_CALL(*renderer_, SetVolume(1.0f));
EXPECT_CALL(*renderer_, StartPlayingFrom(base::TimeDelta()))
.WillOnce(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_ENOUGH));
if (status == PIPELINE_OK)
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
return status;
}
void DoSeek(TeardownState state, StopOrError stop_or_error) {
InSequence s;
PipelineStatus status = SetSeekExpectations(state, stop_or_error);
EXPECT_CALL(*demuxer_, Stop());
EXPECT_CALL(callbacks_, OnSeek(status));
if (status == PIPELINE_OK) {
ExpectPipelineStopAndDestroyPipeline();
}
pipeline_->Seek(base::TimeDelta::FromSeconds(10), base::Bind(
&CallbackHelper::OnSeek, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
PipelineStatus SetSeekExpectations(TeardownState state,
StopOrError stop_or_error) {
PipelineStatus status = PIPELINE_OK;
base::Closure stop_cb = base::Bind(
&CallbackHelper::OnStop, base::Unretained(&callbacks_));
if (state == kFlushing) {
if (stop_or_error == kStop) {
EXPECT_CALL(*renderer_, Flush(_))
.WillOnce(DoAll(Stop(pipeline_.get(), stop_cb),
SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_NOTHING),
RunClosure<0>()));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
} else {
status = PIPELINE_ERROR_READ;
EXPECT_CALL(*renderer_, Flush(_))
.WillOnce(DoAll(SetError(pipeline_.get(), status),
SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_NOTHING),
RunClosure<0>()));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
}
return status;
}
EXPECT_CALL(*renderer_, Flush(_))
.WillOnce(DoAll(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_NOTHING),
RunClosure<0>()));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
if (state == kSeeking) {
if (stop_or_error == kStop) {
EXPECT_CALL(*demuxer_, Seek(_, _))
.WillOnce(DoAll(Stop(pipeline_.get(), stop_cb),
RunCallback<1>(PIPELINE_OK)));
} else {
status = PIPELINE_ERROR_READ;
EXPECT_CALL(*demuxer_, Seek(_, _))
.WillOnce(RunCallback<1>(status));
}
return status;
}
NOTREACHED() << "State not supported: " << state;
return status;
}
void DoStopOrError(StopOrError stop_or_error) {
InSequence s;
EXPECT_CALL(*demuxer_, Stop());
switch (stop_or_error) {
case kStop:
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(base::Bind(
&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
break;
case kError:
EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ));
pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ);
break;
case kErrorAndStop:
EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ));
ExpectPipelineStopAndDestroyPipeline();
pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ);
message_loop_.RunUntilIdle();
pipeline_->Stop(base::Bind(
&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
break;
}
message_loop_.RunUntilIdle();
}
DISALLOW_COPY_AND_ASSIGN(PipelineTeardownTest);
};
#define INSTANTIATE_TEARDOWN_TEST(stop_or_error, state) \
TEST_F(PipelineTeardownTest, stop_or_error##_##state) { \
RunTest(k##state, k##stop_or_error); \
}
INSTANTIATE_TEARDOWN_TEST(Stop, InitDemuxer);
INSTANTIATE_TEARDOWN_TEST(Stop, InitRenderer);
INSTANTIATE_TEARDOWN_TEST(Stop, Flushing);
INSTANTIATE_TEARDOWN_TEST(Stop, Seeking);
INSTANTIATE_TEARDOWN_TEST(Stop, Playing);
INSTANTIATE_TEARDOWN_TEST(Error, InitDemuxer);
INSTANTIATE_TEARDOWN_TEST(Error, InitRenderer);
INSTANTIATE_TEARDOWN_TEST(Error, Flushing);
INSTANTIATE_TEARDOWN_TEST(Error, Seeking);
INSTANTIATE_TEARDOWN_TEST(Error, Playing);
INSTANTIATE_TEARDOWN_TEST(ErrorAndStop, Playing);
} // namespace media