普通文本  |  1277行  |  42.18 KB

// 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/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 "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/size.h"

using ::testing::_;
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 {

// Demuxer properties.
const int kTotalBytes = 1024;

ACTION_P(SetDemuxerProperties, duration) {
  arg0->SetTotalBytes(kTotalBytes);
  arg0->SetDuration(duration);
}

ACTION_P2(Stop, pipeline, stop_cb) {
  pipeline->Stop(stop_cb);
}

ACTION_P2(SetError, pipeline, status) {
  pipeline->SetErrorForTesting(status);
}

// 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(OnBufferingState, void(Pipeline::BufferingState));
  MOCK_METHOD0(OnDurationChange, void());

 private:
  DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
};

// 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:
  PipelineTest()
      : pipeline_(new Pipeline(message_loop_.message_loop_proxy(),
                               new MediaLog())),
        filter_collection_(new FilterCollection()),
        demuxer_(new MockDemuxer()) {
    filter_collection_->SetDemuxer(demuxer_.get());

    video_renderer_ = new MockVideoRenderer();
    scoped_ptr<VideoRenderer> video_renderer(video_renderer_);
    filter_collection_->SetVideoRenderer(video_renderer.Pass());

    audio_renderer_ = new MockAudioRenderer();
    scoped_ptr<AudioRenderer> audio_renderer(audio_renderer_);
    filter_collection_->SetAudioRenderer(audio_renderer.Pass());

    text_renderer_ = new TextRenderer(
                         message_loop_.message_loop_proxy(),
                         base::Bind(&PipelineTest::OnAddTextTrack,
                                    base::Unretained(this)));
    scoped_ptr<TextRenderer> text_renderer(text_renderer_);
    filter_collection_->SetTextRenderer(text_renderer.Pass());

    // InitializeDemuxer() adds overriding expectations for expected non-NULL
    // streams.
    DemuxerStream* null_pointer = NULL;
    EXPECT_CALL(*demuxer_, GetStream(_))
        .WillRepeatedly(Return(null_pointer));

    EXPECT_CALL(*demuxer_, GetStartTime())
        .WillRepeatedly(Return(base::TimeDelta()));
  }

  virtual ~PipelineTest() {
    if (!pipeline_ || !pipeline_->IsRunning())
      return;

    ExpectStop();

    // 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.
    EXPECT_CALL(callbacks_, OnStop());
    pipeline_->Stop(base::Bind(&CallbackHelper::OnStop,
                               base::Unretained(&callbacks_)));
    message_loop_.RunUntilIdle();
  }

 protected:
  // Sets up expectations to allow the demuxer to initialize.
  typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector;
  void InitializeDemuxer(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 InitializeDemuxer(MockDemuxerStreamVector* streams) {
    // Initialize with a default non-zero duration.
    InitializeDemuxer(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 InitializeVideoRenderer(DemuxerStream* stream) {
    EXPECT_CALL(*video_renderer_, Initialize(stream, _, _, _, _, _, _, _, _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));
    EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f));

    // Startup sequence.
    EXPECT_CALL(*video_renderer_, Preroll(demuxer_->GetStartTime(), _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));
    EXPECT_CALL(*video_renderer_, Play(_))
        .WillOnce(RunClosure<0>());
  }

  // Sets up expectations to allow the audio renderer to initialize.
  void InitializeAudioRenderer(DemuxerStream* stream,
                               bool disable_after_init_cb) {
    if (disable_after_init_cb) {
      EXPECT_CALL(*audio_renderer_, Initialize(stream, _, _, _, _, _, _, _))
          .WillOnce(DoAll(RunCallback<1>(PIPELINE_OK),
                          WithArg<6>(RunClosure<0>())));  // |disabled_cb|.
    } else {
      EXPECT_CALL(*audio_renderer_, Initialize(stream, _, _, _, _, _, _, _))
          .WillOnce(DoAll(SaveArg<4>(&audio_time_cb_),
                          RunCallback<1>(PIPELINE_OK)));
    }
  }

  void AddTextStream() {
    EXPECT_CALL(*this, OnAddTextTrack(_,_))
        .WillOnce(Invoke(this, &PipelineTest::DoOnAddTextTrack));
    static_cast<DemuxerHost*>(pipeline_.get())->AddTextStream(text_stream(),
                              TextTrackConfig(kTextSubtitles, "", "", ""));
  }

  // Sets up expectations on the callback and initializes the pipeline.  Called
  // after tests have set expectations any filters they wish to use.
  void InitializePipeline(PipelineStatus start_status) {
    EXPECT_CALL(callbacks_, OnStart(start_status));

    if (start_status == PIPELINE_OK) {
      EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kHaveMetadata));

      if (audio_stream_) {
        EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f));
        EXPECT_CALL(*audio_renderer_, SetVolume(1.0f));

        // Startup sequence.
        EXPECT_CALL(*audio_renderer_, Preroll(base::TimeDelta(), _))
            .WillOnce(RunCallback<1>(PIPELINE_OK));
        EXPECT_CALL(*audio_renderer_, Play(_))
            .WillOnce(RunClosure<0>());
      }
      EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted));
    }

    pipeline_->Start(
        filter_collection_.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::OnBufferingState,
                   base::Unretained(&callbacks_)),
        base::Bind(&CallbackHelper::OnDurationChange,
                   base::Unretained(&callbacks_)));
    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);
    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) {
    // Every filter should receive a call to Seek().
    EXPECT_CALL(*demuxer_, Seek(seek_time, _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));

    if (audio_stream_) {
      EXPECT_CALL(*audio_renderer_, Pause(_))
          .WillOnce(RunClosure<0>());
      EXPECT_CALL(*audio_renderer_, Flush(_))
          .WillOnce(RunClosure<0>());
      EXPECT_CALL(*audio_renderer_, Preroll(seek_time, _))
          .WillOnce(RunCallback<1>(PIPELINE_OK));
      EXPECT_CALL(*audio_renderer_, SetPlaybackRate(_));
      EXPECT_CALL(*audio_renderer_, SetVolume(_));
      EXPECT_CALL(*audio_renderer_, Play(_))
          .WillOnce(RunClosure<0>());
    }

    if (video_stream_) {
      EXPECT_CALL(*video_renderer_, Pause(_))
          .WillOnce(RunClosure<0>());
      EXPECT_CALL(*video_renderer_, Flush(_))
          .WillOnce(RunClosure<0>());
      EXPECT_CALL(*video_renderer_, Preroll(seek_time, _))
          .WillOnce(RunCallback<1>(PIPELINE_OK));
      EXPECT_CALL(*video_renderer_, SetPlaybackRate(_));
      EXPECT_CALL(*video_renderer_, Play(_))
          .WillOnce(RunClosure<0>());
    }

    EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted));

    // We expect a successful seek callback.
    EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK));
  }

  void DoSeek(const base::TimeDelta& seek_time) {
    pipeline_->Seek(seek_time,
                    base::Bind(&CallbackHelper::OnSeek,
                               base::Unretained(&callbacks_)));

    // We expect the time to be updated only after the seek has completed.
    EXPECT_NE(seek_time, pipeline_->GetMediaTime());
    message_loop_.RunUntilIdle();
    EXPECT_EQ(seek_time, pipeline_->GetMediaTime());
  }

  void ExpectStop() {
    if (demuxer_)
      EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>());

    if (audio_stream_)
      EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>());

    if (video_stream_)
      EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>());
  }

  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<FilterCollection> filter_collection_;
  scoped_ptr<MockDemuxer> demuxer_;
  MockVideoRenderer* video_renderer_;
  MockAudioRenderer* audio_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_;
  AudioRenderer::TimeCB audio_time_cb_;
  VideoDecoderConfig video_decoder_config_;

 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());
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_FALSE(pipeline_->HasVideo());

  // 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());

  EXPECT_EQ(0, pipeline_->GetTotalBytes());

  // Should always get set to zero.
  gfx::Size size(1, 1);
  pipeline_->GetNaturalVideoSize(&size);
  EXPECT_EQ(0, size.width());
  EXPECT_EQ(0, size.height());
}

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.
  pipeline_->Start(
        filter_collection_.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::OnBufferingState,
                   base::Unretained(&callbacks_)),
        base::Bind(&CallbackHelper::OnDurationChange,
                   base::Unretained(&callbacks_)));
  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, URLNotFound) {
  EXPECT_CALL(*demuxer_, Initialize(_, _, _))
      .WillOnce(RunCallback<1>(PIPELINE_ERROR_URL_NOT_FOUND));
  EXPECT_CALL(*demuxer_, Stop(_))
      .WillOnce(RunClosure<0>());

  InitializePipeline(PIPELINE_ERROR_URL_NOT_FOUND);
}

TEST_F(PipelineTest, NoStreams) {
  EXPECT_CALL(*demuxer_, Initialize(_, _, _))
      .WillOnce(RunCallback<1>(PIPELINE_OK));
  EXPECT_CALL(*demuxer_, Stop(_))
      .WillOnce(RunClosure<0>());

  InitializePipeline(PIPELINE_ERROR_COULD_NOT_RENDER);
}

TEST_F(PipelineTest, AudioStream) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->HasAudio());
  EXPECT_FALSE(pipeline_->HasVideo());
}

TEST_F(PipelineTest, VideoStream) {
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  InitializeDemuxer(&streams);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());
}

TEST_F(PipelineTest, AudioVideoStream) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());
}

TEST_F(PipelineTest, VideoTextStream) {
  CreateVideoStream();
  CreateTextStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  InitializeDemuxer(&streams);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  AddTextStream();
  message_loop_.RunUntilIdle();
}

TEST_F(PipelineTest, VideoAudioTextStream) {
  CreateVideoStream();
  CreateAudioStream();
  CreateTextStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams);
  InitializeVideoRenderer(video_stream());
  InitializeAudioRenderer(audio_stream(), false);

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  AddTextStream();
  message_loop_.RunUntilIdle();
}

TEST_F(PipelineTest, Seek) {
  CreateAudioStream();
  CreateVideoStream();
  CreateTextStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000));
  InitializeAudioRenderer(audio_stream(), false);
  InitializeVideoRenderer(video_stream());

  // Initialize then seek!
  InitializePipeline(PIPELINE_OK);

  AddTextStream();
  message_loop_.RunUntilIdle();

  // Every filter should receive a call to Seek().
  base::TimeDelta expected = base::TimeDelta::FromSeconds(2000);
  ExpectSeek(expected);
  DoSeek(expected);
}

TEST_F(PipelineTest, SetVolume) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);

  // The audio renderer should receive a call to SetVolume().
  float expected = 0.5f;
  EXPECT_CALL(*audio_renderer_, SetVolume(expected));

  // Initialize then set volume!
  InitializePipeline(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);
  InitializeDemuxer(&streams, kDuration);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);
  EXPECT_EQ(kDuration.ToInternalValue(),
            pipeline_->GetMediaDuration().ToInternalValue());
  EXPECT_EQ(kTotalBytes, pipeline_->GetTotalBytes());
  EXPECT_FALSE(pipeline_->DidLoadingProgress());
}

TEST_F(PipelineTest, GetBufferedTimeRanges) {
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
  InitializeDemuxer(&streams, kDuration);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);

  EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size());

  EXPECT_FALSE(pipeline_->DidLoadingProgress());
  pipeline_->AddBufferedByteRange(0, kTotalBytes / 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));
  pipeline_->AddBufferedTimeRange(base::TimeDelta(), kDuration / 8);
  EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
  EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));

  base::TimeDelta kSeekTime = kDuration / 2;
  ExpectSeek(kSeekTime);
  DoSeek(kSeekTime);

  EXPECT_TRUE(pipeline_->DidLoadingProgress());
  EXPECT_FALSE(pipeline_->DidLoadingProgress());
  pipeline_->AddBufferedByteRange(kTotalBytes / 2,
                                  kTotalBytes / 2 + kTotalBytes / 8);
  EXPECT_TRUE(pipeline_->DidLoadingProgress());
  EXPECT_FALSE(pipeline_->DidLoadingProgress());
  EXPECT_EQ(2u, pipeline_->GetBufferedTimeRanges().size());
  EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
  EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));
  EXPECT_EQ(kDuration / 2, pipeline_->GetBufferedTimeRanges().start(1));
  EXPECT_EQ(kDuration / 2 + kDuration / 8,
            pipeline_->GetBufferedTimeRanges().end(1));

  pipeline_->AddBufferedTimeRange(kDuration / 4, 3 * kDuration / 8);
  EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0));
  EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0));
  EXPECT_EQ(kDuration / 4, pipeline_->GetBufferedTimeRanges().start(1));
  EXPECT_EQ(3* kDuration / 8, pipeline_->GetBufferedTimeRanges().end(1));
  EXPECT_EQ(kDuration / 2, pipeline_->GetBufferedTimeRanges().start(2));
  EXPECT_EQ(kDuration / 2 + kDuration / 8,
            pipeline_->GetBufferedTimeRanges().end(2));
}

TEST_F(PipelineTest, DisableAudioRenderer) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  EXPECT_CALL(*demuxer_, OnAudioRendererDisabled());
  pipeline_->OnAudioDisabled();

  // Verify that ended event is fired when video ends.
  EXPECT_CALL(callbacks_, OnEnded());
  pipeline_->OnVideoRendererEnded();
}

TEST_F(PipelineTest, DisableAudioRendererDuringInit) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), true);
  InitializeVideoRenderer(video_stream());

  EXPECT_CALL(*demuxer_, OnAudioRendererDisabled());

  InitializePipeline(PIPELINE_OK);
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  // Verify that ended event is fired when video ends.
  EXPECT_CALL(callbacks_, OnEnded());
  pipeline_->OnVideoRendererEnded();
}

TEST_F(PipelineTest, EndedCallback) {
  CreateAudioStream();
  CreateVideoStream();
  CreateTextStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);
  InitializeVideoRenderer(video_stream());
  InitializePipeline(PIPELINE_OK);

  AddTextStream();

  // The ended callback shouldn't run until all renderers have ended.
  pipeline_->OnAudioRendererEnded();
  message_loop_.RunUntilIdle();

  pipeline_->OnVideoRendererEnded();
  message_loop_.RunUntilIdle();

  EXPECT_CALL(callbacks_, OnEnded());
  text_stream()->SendEosNotification();
  message_loop_.RunUntilIdle();
}

TEST_F(PipelineTest, AudioStreamShorterThanVideo) {
  base::TimeDelta duration = base::TimeDelta::FromSeconds(10);

  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  // Replace the clock so we can simulate wallclock time advancing w/o using
  // Sleep().
  pipeline_->SetClockForTesting(new Clock(&test_tick_clock_));

  InitializeDemuxer(&streams, duration);
  InitializeAudioRenderer(audio_stream(), false);
  InitializeVideoRenderer(video_stream());
  InitializePipeline(PIPELINE_OK);

  EXPECT_EQ(0, pipeline_->GetMediaTime().ToInternalValue());

  float playback_rate = 1.0f;
  EXPECT_CALL(*video_renderer_, SetPlaybackRate(playback_rate));
  EXPECT_CALL(*audio_renderer_, SetPlaybackRate(playback_rate));
  pipeline_->SetPlaybackRate(playback_rate);
  message_loop_.RunUntilIdle();

  InSequence s;

  // Verify that the clock doesn't advance since it hasn't been started by
  // a time update from the audio stream.
  int64 start_time = pipeline_->GetMediaTime().ToInternalValue();
  test_tick_clock_.Advance(base::TimeDelta::FromMilliseconds(100));
  EXPECT_EQ(pipeline_->GetMediaTime().ToInternalValue(), start_time);

  // Signal end of audio stream.
  pipeline_->OnAudioRendererEnded();
  message_loop_.RunUntilIdle();

  // Verify that the clock advances.
  start_time = pipeline_->GetMediaTime().ToInternalValue();
  test_tick_clock_.Advance(base::TimeDelta::FromMilliseconds(100));
  EXPECT_GT(pipeline_->GetMediaTime().ToInternalValue(), start_time);

  // Signal end of video stream and make sure OnEnded() callback occurs.
  EXPECT_CALL(callbacks_, OnEnded());
  pipeline_->OnVideoRendererEnded();
}

TEST_F(PipelineTest, ErrorDuringSeek) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);
  InitializePipeline(PIPELINE_OK);

  float playback_rate = 1.0f;
  EXPECT_CALL(*audio_renderer_, SetPlaybackRate(playback_rate));
  pipeline_->SetPlaybackRate(playback_rate);
  message_loop_.RunUntilIdle();

  base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);

  // Preroll() isn't called as the demuxer errors out first.
  EXPECT_CALL(*audio_renderer_, Pause(_))
      .WillOnce(RunClosure<0>());
  EXPECT_CALL(*audio_renderer_, Flush(_))
      .WillOnce(RunClosure<0>());
  EXPECT_CALL(*audio_renderer_, Stop(_))
      .WillOnce(RunClosure<0>());

  EXPECT_CALL(*demuxer_, Seek(seek_time, _))
      .WillOnce(RunCallback<1>(PIPELINE_ERROR_READ));
  EXPECT_CALL(*demuxer_, Stop(_))
      .WillOnce(RunClosure<0>());

  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());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);
  InitializePipeline(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(*audio_renderer_, Pause(_))
      .WillOnce(RunClosure<0>());
  EXPECT_CALL(*audio_renderer_, Flush(_))
      .WillOnce(RunClosure<0>());
  EXPECT_CALL(*audio_renderer_, Stop(_))
      .WillOnce(RunClosure<0>());

  EXPECT_CALL(*demuxer_, Seek(seek_time, _))
      .WillOnce(RunCallback<1>(PIPELINE_ERROR_READ));
  EXPECT_CALL(*demuxer_, Stop(_))
      .WillOnce(RunClosure<0>());

  pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek,
                                        base::Unretained(&callbacks_)));
  EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ));
  message_loop_.RunUntilIdle();
}

TEST_F(PipelineTest, StartTimeIsZero) {
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
  InitializeDemuxer(&streams, kDuration);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  EXPECT_EQ(base::TimeDelta(), pipeline_->GetMediaTime());
}

TEST_F(PipelineTest, StartTimeIsNonZero) {
  const base::TimeDelta kStartTime = base::TimeDelta::FromSeconds(4);
  const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);

  EXPECT_CALL(*demuxer_, GetStartTime())
      .WillRepeatedly(Return(kStartTime));

  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, kDuration);
  InitializeVideoRenderer(video_stream());

  InitializePipeline(PIPELINE_OK);
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  EXPECT_EQ(kStartTime, pipeline_->GetMediaTime());
}

static void RunTimeCB(const AudioRenderer::TimeCB& time_cb,
                       int time_in_ms,
                       int max_time_in_ms) {
  time_cb.Run(base::TimeDelta::FromMilliseconds(time_in_ms),
              base::TimeDelta::FromMilliseconds(max_time_in_ms));
}

TEST_F(PipelineTest, AudioTimeUpdateDuringSeek) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);
  InitializePipeline(PIPELINE_OK);

  float playback_rate = 1.0f;
  EXPECT_CALL(*audio_renderer_, SetPlaybackRate(playback_rate));
  pipeline_->SetPlaybackRate(playback_rate);
  message_loop_.RunUntilIdle();

  // Provide an initial time update so that the pipeline transitions out of the
  // "waiting for time update" state.
  audio_time_cb_.Run(base::TimeDelta::FromMilliseconds(100),
                     base::TimeDelta::FromMilliseconds(500));

  base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);

  // Arrange to trigger a time update while the demuxer is in the middle of
  // seeking. This update should be ignored by the pipeline and the clock should
  // not get updated.
  base::Closure closure = base::Bind(&RunTimeCB, audio_time_cb_, 300, 700);
  EXPECT_CALL(*demuxer_, Seek(seek_time, _))
      .WillOnce(DoAll(InvokeWithoutArgs(&closure, &base::Closure::Run),
                      RunCallback<1>(PIPELINE_OK)));

  EXPECT_CALL(*audio_renderer_, Pause(_))
      .WillOnce(RunClosure<0>());
  EXPECT_CALL(*audio_renderer_, Flush(_))
      .WillOnce(RunClosure<0>());
  EXPECT_CALL(*audio_renderer_, Preroll(seek_time, _))
      .WillOnce(RunCallback<1>(PIPELINE_OK));
  EXPECT_CALL(*audio_renderer_, SetPlaybackRate(_));
  EXPECT_CALL(*audio_renderer_, SetVolume(_));
  EXPECT_CALL(*audio_renderer_, Play(_))
      .WillOnce(RunClosure<0>());

  EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted));
  EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK));
  DoSeek(seek_time);

  EXPECT_EQ(pipeline_->GetMediaTime(), seek_time);

  // Now that the seek is complete, verify that time updates advance the current
  // time.
  base::TimeDelta new_time = seek_time + base::TimeDelta::FromMilliseconds(100);
  audio_time_cb_.Run(new_time, new_time);

  EXPECT_EQ(pipeline_->GetMediaTime(), new_time);
}

static void DeletePipeline(scoped_ptr<Pipeline> pipeline) {
  // |pipeline| will go out of scope.
}

TEST_F(PipelineTest, DeleteAfterStop) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  InitializeDemuxer(&streams);
  InitializeAudioRenderer(audio_stream(), false);
  InitializePipeline(PIPELINE_OK);

  ExpectStop();

  Pipeline* pipeline = pipeline_.get();
  pipeline->Stop(base::Bind(&DeletePipeline, base::Passed(&pipeline_)));
  message_loop_.RunUntilIdle();
}

class PipelineTeardownTest : public PipelineTest {
 public:
  enum TeardownState {
    kInitDemuxer,
    kInitAudioRenderer,
    kInitVideoRenderer,
    kPausing,
    kFlushing,
    kSeeking,
    kPrerolling,
    kStarting,
    kPlaying,
  };

  enum StopOrError {
    kStop,
    kError,
    kErrorAndStop,
  };

  PipelineTeardownTest() {}
  virtual ~PipelineTeardownTest() {}

  void RunTest(TeardownState state, StopOrError stop_or_error) {
    switch (state) {
      case kInitDemuxer:
      case kInitAudioRenderer:
      case kInitVideoRenderer:
        DoInitialize(state, stop_or_error);
        break;

      case kPausing:
      case kFlushing:
      case kSeeking:
      case kPrerolling:
      case kStarting:
        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));
    pipeline_->Start(
        filter_collection_.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::OnBufferingState,
                   base::Unretained(&callbacks_)),
        base::Bind(&CallbackHelper::OnDurationChange,
                   base::Unretained(&callbacks_)));
    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)));
        EXPECT_CALL(callbacks_, OnStop());
      } else {
        status = DEMUXER_ERROR_COULD_NOT_OPEN;
        EXPECT_CALL(*demuxer_, Initialize(_, _, _))
            .WillOnce(RunCallback<1>(status));
      }

      EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>());
      return status;
    }

    CreateAudioStream();
    CreateVideoStream();
    MockDemuxerStreamVector streams;
    streams.push_back(audio_stream());
    streams.push_back(video_stream());
    InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000));

    if (state == kInitAudioRenderer) {
      if (stop_or_error == kStop) {
        EXPECT_CALL(*audio_renderer_, Initialize(_, _, _, _, _, _, _, _))
            .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb),
                            RunCallback<1>(PIPELINE_OK)));
        EXPECT_CALL(callbacks_, OnStop());
      } else {
        status = PIPELINE_ERROR_INITIALIZATION_FAILED;
        EXPECT_CALL(*audio_renderer_, Initialize(_, _, _, _, _, _, _, _))
            .WillOnce(RunCallback<1>(status));
      }

      EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>());
      EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>());
      return status;
    }

    EXPECT_CALL(*audio_renderer_, Initialize(_, _, _, _, _, _, _, _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));

    if (state == kInitVideoRenderer) {
      if (stop_or_error == kStop) {
        EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _))
            .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb),
                            RunCallback<1>(PIPELINE_OK)));
        EXPECT_CALL(callbacks_, OnStop());
      } else {
        status = PIPELINE_ERROR_INITIALIZATION_FAILED;
        EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _))
            .WillOnce(RunCallback<1>(status));
      }

      EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>());
      EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>());
      EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>());
      return status;
    }

    EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));

    EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kHaveMetadata));

    // If we get here it's a successful initialization.
    EXPECT_CALL(*audio_renderer_, Preroll(base::TimeDelta(), _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));
    EXPECT_CALL(*video_renderer_, Preroll(base::TimeDelta(), _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));

    EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f));
    EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f));
    EXPECT_CALL(*audio_renderer_, SetVolume(1.0f));

    EXPECT_CALL(*audio_renderer_, Play(_))
        .WillOnce(RunClosure<0>());
    EXPECT_CALL(*video_renderer_, Play(_))
        .WillOnce(RunClosure<0>());

    if (status == PIPELINE_OK)
      EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted));

    return status;
  }

  void DoSeek(TeardownState state, StopOrError stop_or_error) {
    InSequence s;
    PipelineStatus status = SetSeekExpectations(state, stop_or_error);

    EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(callbacks_, OnSeek(status));

    if (status == PIPELINE_OK) {
      EXPECT_CALL(callbacks_, OnStop());
    }

    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 == kPausing) {
      if (stop_or_error == kStop) {
        EXPECT_CALL(*audio_renderer_, Pause(_))
            .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), RunClosure<0>()));
      } else {
        status = PIPELINE_ERROR_READ;
        EXPECT_CALL(*audio_renderer_, Pause(_)).WillOnce(
            DoAll(SetError(pipeline_.get(), status), RunClosure<0>()));
      }

      return status;
    }

    EXPECT_CALL(*audio_renderer_, Pause(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*video_renderer_, Pause(_)).WillOnce(RunClosure<0>());

    if (state == kFlushing) {
      if (stop_or_error == kStop) {
        EXPECT_CALL(*audio_renderer_, Flush(_))
            .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), RunClosure<0>()));
      } else {
        status = PIPELINE_ERROR_READ;
        EXPECT_CALL(*audio_renderer_, Flush(_)).WillOnce(
            DoAll(SetError(pipeline_.get(), status), RunClosure<0>()));
      }

      return status;
    }

    EXPECT_CALL(*audio_renderer_, Flush(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*video_renderer_, Flush(_)).WillOnce(RunClosure<0>());

    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;
    }

    EXPECT_CALL(*demuxer_, Seek(_, _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));

    if (state == kPrerolling) {
      if (stop_or_error == kStop) {
        EXPECT_CALL(*audio_renderer_, Preroll(_, _))
            .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb),
                            RunCallback<1>(PIPELINE_OK)));
      } else {
        status = PIPELINE_ERROR_READ;
        EXPECT_CALL(*audio_renderer_, Preroll(_, _))
            .WillOnce(RunCallback<1>(status));
      }

      return status;
    }

    EXPECT_CALL(*audio_renderer_, Preroll(_, _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));
    EXPECT_CALL(*video_renderer_, Preroll(_, _))
        .WillOnce(RunCallback<1>(PIPELINE_OK));

    // Playback rate and volume are updated prior to starting.
    EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f));
    EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f));
    EXPECT_CALL(*audio_renderer_, SetVolume(1.0f));

    if (state == kStarting) {
      if (stop_or_error == kStop) {
        EXPECT_CALL(*audio_renderer_, Play(_))
            .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), RunClosure<0>()));
      } else {
        status = PIPELINE_ERROR_READ;
        EXPECT_CALL(*audio_renderer_, Play(_)).WillOnce(
            DoAll(SetError(pipeline_.get(), status), RunClosure<0>()));
      }
      return status;
    }

    NOTREACHED() << "State not supported: " << state;
    return status;
  }

  void DoStopOrError(StopOrError stop_or_error) {
    InSequence s;

    EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>());

    switch (stop_or_error) {
      case kStop:
        EXPECT_CALL(callbacks_, OnStop());
        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_, OnStop());
        pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ);
        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, InitAudioRenderer);
INSTANTIATE_TEARDOWN_TEST(Stop, InitVideoRenderer);
INSTANTIATE_TEARDOWN_TEST(Stop, Pausing);
INSTANTIATE_TEARDOWN_TEST(Stop, Flushing);
INSTANTIATE_TEARDOWN_TEST(Stop, Seeking);
INSTANTIATE_TEARDOWN_TEST(Stop, Prerolling);
INSTANTIATE_TEARDOWN_TEST(Stop, Starting);
INSTANTIATE_TEARDOWN_TEST(Stop, Playing);

INSTANTIATE_TEARDOWN_TEST(Error, InitDemuxer);
INSTANTIATE_TEARDOWN_TEST(Error, InitAudioRenderer);
INSTANTIATE_TEARDOWN_TEST(Error, InitVideoRenderer);
INSTANTIATE_TEARDOWN_TEST(Error, Pausing);
INSTANTIATE_TEARDOWN_TEST(Error, Flushing);
INSTANTIATE_TEARDOWN_TEST(Error, Seeking);
INSTANTIATE_TEARDOWN_TEST(Error, Prerolling);
INSTANTIATE_TEARDOWN_TEST(Error, Starting);
INSTANTIATE_TEARDOWN_TEST(Error, Playing);

INSTANTIATE_TEARDOWN_TEST(ErrorAndStop, Playing);

}  // namespace media