| Index: media/filters/pipeline_controller_unittest.cc | 
| diff --git a/media/filters/pipeline_controller_unittest.cc b/media/filters/pipeline_controller_unittest.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..ac77920ec2b2b7f46155140e26ebd7e8b57c8f7a | 
| --- /dev/null | 
| +++ b/media/filters/pipeline_controller_unittest.cc | 
| @@ -0,0 +1,168 @@ | 
| +// Copyright (c) 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 "base/bind.h" | 
| +#include "base/bind_helpers.h" | 
| +#include "base/logging.h" | 
| +#include "base/macros.h" | 
| +#include "base/memory/ref_counted.h" | 
| +#include "base/memory/scoped_ptr.h" | 
| +#include "base/time/time.h" | 
| +#include "media/base/pipeline.h" | 
| +#include "media/filters/pipeline_controller.h" | 
| +#include "testing/gmock/include/gmock/gmock.h" | 
| +#include "testing/gtest/include/gtest/gtest.h" | 
| + | 
| +using ::testing::_; | 
| +using ::testing::Mock; | 
| +using ::testing::NiceMock; | 
| +using ::testing::SaveArg; | 
| + | 
| +namespace media { | 
| + | 
| +ACTION_TEMPLATE(RunCallback, | 
| +                HAS_1_TEMPLATE_PARAMS(int, k), | 
| +                AND_1_VALUE_PARAMS(p0)) { | 
| +  return ::std::tr1::get<k>(args).Run(p0); | 
| +} | 
| + | 
| +class MockPipeline : public Pipeline { | 
| + public: | 
| +  MockPipeline() {} | 
| + | 
| +  virtual ~MockPipeline() {} | 
| + | 
| +  // Start() cannot be mocked due to scoped_ptr. Instead it forwards to a | 
| +  // version that can. | 
| +  MOCK_METHOD10(Start, | 
| +                void(Demuxer*, | 
| +                     scoped_ptr<Renderer>*, | 
| +                     const base::Closure&, | 
| +                     const PipelineStatusCB&, | 
| +                     const PipelineStatusCB&, | 
| +                     const PipelineMetadataCB&, | 
| +                     const BufferingStateCB&, | 
| +                     const base::Closure&, | 
| +                     const AddTextTrackCB&, | 
| +                     const base::Closure&)); | 
| + | 
| +  void Start(Demuxer* demuxer, | 
| +             scoped_ptr<Renderer> renderer, | 
| +             const base::Closure& ended_cb, | 
| +             const PipelineStatusCB& error_cb, | 
| +             const PipelineStatusCB& seek_cb, | 
| +             const PipelineMetadataCB& metadata_cb, | 
| +             const BufferingStateCB& buffering_state_cb, | 
| +             const base::Closure& duration_change_cb, | 
| +             const AddTextTrackCB& add_text_track_cb, | 
| +             const base::Closure& waiting_for_decryption_key_cb) override { | 
| +    Start(demuxer, &renderer, ended_cb, error_cb, seek_cb, metadata_cb, | 
| +          buffering_state_cb, duration_change_cb, add_text_track_cb, | 
| +          waiting_for_decryption_key_cb); | 
| +  } | 
| + | 
| +  MOCK_METHOD1(Stop, void(const base::Closure&)); | 
| +  MOCK_METHOD2(Seek, void(base::TimeDelta, const PipelineStatusCB&)); | 
| +  MOCK_METHOD1(Suspend, void(const PipelineStatusCB&)); | 
| + | 
| +  // Resume() cannot be mocked due to scoped_ptr. Instead it forwards to a | 
| +  // version that can. | 
| +  MOCK_METHOD3(Resume, | 
| +               void(scoped_ptr<Renderer>*, | 
| +                    base::TimeDelta, | 
| +                    const PipelineStatusCB&)); | 
| + | 
| +  void Resume(scoped_ptr<Renderer> renderer, | 
| +              base::TimeDelta timestamp, | 
| +              const PipelineStatusCB& seek_cb) override { | 
| +    Resume(&renderer, timestamp, seek_cb); | 
| +  } | 
| + | 
| +  // TODO(sandersd): It would be helpful if this always returned true once the | 
| +  // start callback was called (until Stop()). | 
| +  MOCK_CONST_METHOD0(IsRunning, bool()); | 
| + | 
| +  // TODO(sandersd): These should be regular get/set implementations. | 
| +  MOCK_CONST_METHOD0(GetPlaybackRate, double()); | 
| +  MOCK_METHOD1(SetPlaybackRate, void(double)); | 
| +  MOCK_CONST_METHOD0(GetVolume, float()); | 
| +  MOCK_METHOD1(SetVolume, void(float)); | 
| + | 
| +  // TODO(sandersd): Some sort of help would be useful for configuring these. | 
| +  MOCK_CONST_METHOD0(GetMediaTime, base::TimeDelta()); | 
| +  MOCK_CONST_METHOD0(GetBufferedTimeRanges, Ranges<base::TimeDelta>()); | 
| +  MOCK_CONST_METHOD0(GetMediaDuration, base::TimeDelta()); | 
| +  MOCK_METHOD0(DidLoadingProgress, bool()); | 
| +  MOCK_CONST_METHOD0(GetStatistics, PipelineStatistics()); | 
| + | 
| +  MOCK_METHOD2(SetCdm, void(CdmContext*, const CdmAttachedCB&)); | 
| +}; | 
| + | 
| +class PipelineControllerTest : public ::testing::Test { | 
| + public: | 
| +  PipelineControllerTest() | 
| +      : pipeline_controller_(&pipeline_, | 
| +                             base::Bind(&PipelineControllerTest::CreateRenderer, | 
| +                                        base::Unretained(this)), | 
| +                             base::Bind(&PipelineControllerTest::OnSeeked, | 
| +                                        base::Unretained(this)), | 
| +                             base::Bind(&PipelineControllerTest::OnSuspended, | 
| +                                        base::Unretained(this)), | 
| +                             base::Bind(&PipelineControllerTest::OnError, | 
| +                                        base::Unretained(this))) {} | 
| + | 
| +  ~PipelineControllerTest() override {} | 
| + | 
| +  void StartPipeline(ChunkDemuxer* chunk_demuxer) { | 
| +    // Everything other than |chunk_demuxer| is null since there is no reason | 
| +    // for MockPipeline to call them. | 
| +    pipeline_controller_.Start( | 
| +        chunk_demuxer, nullptr, base::Closure(), PipelineMetadataCB(), | 
| +        BufferingStateCB(), base::Closure(), AddTextTrackCB(), base::Closure()); | 
| +  } | 
| + | 
| +  void StopPipeline() {} | 
| + | 
| + protected: | 
| +  scoped_ptr<Renderer> CreateRenderer() { return scoped_ptr<Renderer>(); } | 
| + | 
| +  void OnSeeked(bool time_updated) {} | 
| + | 
| +  void OnSuspended() {} | 
| + | 
| +  void OnError(PipelineStatus status) { NOTREACHED(); } | 
| + | 
| +  MockPipeline pipeline_; | 
| +  PipelineController pipeline_controller_; | 
| + | 
| +  DISALLOW_COPY_AND_ASSIGN(PipelineControllerTest); | 
| +}; | 
| + | 
| +TEST_F(PipelineControllerTest, PendingSuspend) { | 
| +  // Immediately complete Pipline startup. | 
| +  EXPECT_CALL(pipeline_, Start(_, _, _, _, _, _, _, _, _, _)) | 
| +      .WillOnce(RunCallback<4>(PIPELINE_OK)); | 
| +  StartPipeline(nullptr); | 
| + | 
| +  Mock::VerifyAndClear(&pipeline_); | 
| +  EXPECT_TRUE(pipeline_controller_.IsStable()); | 
| + | 
| +  // Start a seek, but do not complete it immediately. | 
| +  base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); | 
| +  PipelineStatusCB seek_cb; | 
| +  EXPECT_CALL(pipeline_, Seek(seek_time, _)).WillOnce(SaveArg<1>(&seek_cb)); | 
| +  pipeline_controller_.Seek(seek_time, true); | 
| + | 
| +  Mock::VerifyAndClear(&pipeline_); | 
| +  EXPECT_FALSE(pipeline_controller_.IsStable()); | 
| + | 
| +  // Schedule a suspend. (It shouldn't happen yet.) | 
| +  pipeline_controller_.Suspend(); | 
| + | 
| +  // Complete the seek; the suspend should trigger. | 
| +  EXPECT_CALL(pipeline_, Suspend(_)); | 
| +  seek_cb.Run(PIPELINE_OK); | 
| +} | 
| + | 
| +}  // namespace media | 
|  |