| // Copyright 2013 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 <set> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/string_number_conversions.h" |
| #include "cobalt/media/base/media_log.h" |
| #include "cobalt/media/base/mock_filters.h" |
| #include "cobalt/media/base/test_helpers.h" |
| #include "cobalt/media/blink/buffered_data_source_host_impl.h" |
| #include "cobalt/media/blink/mock_weburlloader.h" |
| #include "cobalt/media/blink/multibuffer_data_source.h" |
| #include "cobalt/media/blink/multibuffer_reader.h" |
| #include "cobalt/media/blink/resource_multibuffer_data_provider.h" |
| #include "cobalt/media/blink/test_response_generator.h" |
| #include "starboard/memory.h" |
| #include "starboard/types.h" |
| #include "third_party/WebKit/public/platform/WebURLResponse.h" |
| #include "third_party/WebKit/public/web/WebFrameClient.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| #include "third_party/WebKit/public/web/WebView.h" |
| |
| using ::testing::_; |
| using ::testing::Assign; |
| using ::testing::DoAll; |
| using ::testing::Invoke; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::InSequence; |
| using ::testing::NiceMock; |
| using ::testing::StrictMock; |
| |
| using blink::WebLocalFrame; |
| using blink::WebString; |
| using blink::WebURLLoader; |
| using blink::WebURLResponse; |
| using blink::WebView; |
| |
| namespace cobalt { |
| namespace media { |
| |
| class TestResourceMultiBuffer; |
| class TestMultiBufferDataProvider; |
| |
| std::set<TestMultiBufferDataProvider*> test_data_providers; |
| |
| class TestMultiBufferDataProvider : public ResourceMultiBufferDataProvider { |
| public: |
| TestMultiBufferDataProvider(UrlData* url_data, MultiBuffer::BlockId pos) |
| : ResourceMultiBufferDataProvider(url_data, pos), loading_(false) { |
| CHECK(test_data_providers.insert(this).second); |
| } |
| ~TestMultiBufferDataProvider() override { |
| CHECK_EQ(static_cast<size_t>(1), test_data_providers.erase(this)); |
| } |
| void Start() override { |
| // Create a mock active loader. |
| // Keep track of active loading state via loadAsynchronously() and cancel(). |
| NiceMock<MockWebURLLoader>* url_loader = new NiceMock<MockWebURLLoader>(); |
| ON_CALL(*url_loader, cancel()) |
| .WillByDefault(Invoke([this]() { |
| // Check that we have not been destroyed first. |
| if (test_data_providers.find(this) != test_data_providers.end()) { |
| this->loading_ = false; |
| } |
| })); |
| loading_ = true; |
| active_loader_.reset( |
| new ActiveLoader(std::unique_ptr<WebURLLoader>(url_loader))); |
| if (!on_start_.is_null()) { |
| on_start_.Run(); |
| } |
| } |
| |
| bool loading() const { return loading_; } |
| void RunOnStart(base::Closure cb) { on_start_ = cb; } |
| |
| private: |
| bool loading_; |
| base::Closure on_start_; |
| }; |
| |
| class TestUrlData; |
| |
| class TestResourceMultiBuffer : public ResourceMultiBuffer { |
| public: |
| TestResourceMultiBuffer(UrlData* url_data, int shift) |
| : ResourceMultiBuffer(url_data, shift) {} |
| |
| std::unique_ptr<MultiBuffer::DataProvider> CreateWriter( |
| const BlockId& pos) override { |
| TestMultiBufferDataProvider* ret = |
| new TestMultiBufferDataProvider(url_data_, pos); |
| ret->Start(); |
| return std::unique_ptr<MultiBuffer::DataProvider>(ret); |
| } |
| |
| // TODO: Make these global |
| |
| TestMultiBufferDataProvider* GetProvider() { |
| EXPECT_EQ(test_data_providers.size(), 1U); |
| if (test_data_providers.size() != 1) return NULL; |
| return *test_data_providers.begin(); |
| } |
| TestMultiBufferDataProvider* GetProvider_allownull() { |
| EXPECT_LE(test_data_providers.size(), 1U); |
| if (test_data_providers.size() != 1U) return NULL; |
| return *test_data_providers.begin(); |
| } |
| bool HasProvider() const { return test_data_providers.size() == 1U; } |
| bool loading() { |
| if (test_data_providers.empty()) return false; |
| return GetProvider()->loading(); |
| } |
| }; |
| |
| class TestUrlData : public UrlData { |
| public: |
| TestUrlData(const GURL& url, CORSMode cors_mode, |
| const base::WeakPtr<UrlIndex>& url_index) |
| : UrlData(url, cors_mode, url_index), |
| block_shift_(url_index->block_shift()) {} |
| |
| ResourceMultiBuffer* multibuffer() override { |
| if (!test_multibuffer_.get()) { |
| test_multibuffer_.reset(new TestResourceMultiBuffer(this, block_shift_)); |
| } |
| return test_multibuffer_.get(); |
| } |
| |
| TestResourceMultiBuffer* test_multibuffer() { |
| if (!test_multibuffer_.get()) { |
| test_multibuffer_.reset(new TestResourceMultiBuffer(this, block_shift_)); |
| } |
| return test_multibuffer_.get(); |
| } |
| |
| protected: |
| ~TestUrlData() override {} |
| const int block_shift_; |
| |
| std::unique_ptr<TestResourceMultiBuffer> test_multibuffer_; |
| }; |
| |
| class TestUrlIndex : public UrlIndex { |
| public: |
| explicit TestUrlIndex(blink::WebFrame* frame) : UrlIndex(frame) {} |
| |
| scoped_refptr<UrlData> NewUrlData(const GURL& url, |
| UrlData::CORSMode cors_mode) override { |
| last_url_data_ = |
| new TestUrlData(url, cors_mode, weak_factory_.GetWeakPtr()); |
| return last_url_data_; |
| } |
| |
| scoped_refptr<TestUrlData> last_url_data() { |
| EXPECT_TRUE(last_url_data_); |
| return last_url_data_; |
| } |
| |
| private: |
| scoped_refptr<TestUrlData> last_url_data_; |
| }; |
| |
| class MockBufferedDataSourceHost : public BufferedDataSourceHost { |
| public: |
| MockBufferedDataSourceHost() {} |
| virtual ~MockBufferedDataSourceHost() {} |
| |
| MOCK_METHOD1(SetTotalBytes, void(int64_t total_bytes)); |
| MOCK_METHOD2(AddBufferedByteRange, void(int64_t start, int64_t end)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSourceHost); |
| }; |
| |
| class MockMultibufferDataSource : public MultibufferDataSource { |
| public: |
| MockMultibufferDataSource( |
| const GURL& url, UrlData::CORSMode cors_mode, |
| const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| linked_ptr<UrlIndex> url_index, WebLocalFrame* frame, |
| BufferedDataSourceHost* host) |
| : MultibufferDataSource( |
| url, cors_mode, task_runner, url_index, frame, |
| new media::MediaLog(), host, |
| base::Bind(&MockMultibufferDataSource::set_downloading, |
| base::Unretained(this))), |
| downloading_(false) {} |
| |
| bool downloading() { return downloading_; } |
| void set_downloading(bool downloading) { downloading_ = downloading; } |
| bool range_supported() { return url_data_->range_supported(); } |
| |
| private: |
| // Whether the resource is downloading or deferred. |
| bool downloading_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockMultibufferDataSource); |
| }; |
| |
| static const int64_t kFileSize = 5000000; |
| static const int64_t kFarReadPosition = 3997696; |
| static const int kDataSize = 32 << 10; |
| |
| static const char kHttpUrl[] = "http://localhost/foo.webm"; |
| static const char kFileUrl[] = "file:///tmp/bar.webm"; |
| static const char kHttpDifferentPathUrl[] = "http://localhost/bar.webm"; |
| static const char kHttpDifferentOriginUrl[] = "http://127.0.0.1/foo.webm"; |
| |
| class MultibufferDataSourceTest : public testing::Test { |
| public: |
| MultibufferDataSourceTest() |
| : view_(WebView::create(NULL, blink::WebPageVisibilityStateVisible)), |
| preload_(MultibufferDataSource::AUTO) { |
| WebLocalFrame* frame = |
| WebLocalFrame::create(blink::WebTreeScopeType::Document, &client_); |
| view_->setMainFrame(frame); |
| url_index_ = make_linked_ptr(new TestUrlIndex(frame)); |
| } |
| |
| virtual ~MultibufferDataSourceTest() { view_->close(); } |
| |
| MOCK_METHOD1(OnInitialize, void(bool)); |
| |
| void InitializeWithCORS(const char* url, bool expected, |
| UrlData::CORSMode cors_mode) { |
| GURL gurl(url); |
| data_source_.reset(new MockMultibufferDataSource( |
| gurl, cors_mode, message_loop_.task_runner(), url_index_, |
| view_->mainFrame()->toWebLocalFrame(), &host_)); |
| data_source_->SetPreload(preload_); |
| |
| response_generator_.reset(new TestResponseGenerator(gurl, kFileSize)); |
| EXPECT_CALL(*this, OnInitialize(expected)); |
| data_source_->Initialize(base::Bind( |
| &MultibufferDataSourceTest::OnInitialize, base::Unretained(this))); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Not really loading until after OnInitialize is called. |
| EXPECT_EQ(data_source_->downloading(), false); |
| } |
| |
| void Initialize(const char* url, bool expected) { |
| InitializeWithCORS(url, expected, UrlData::CORS_UNSPECIFIED); |
| } |
| |
| // Helper to initialize tests with a valid 200 response. |
| void InitializeWith200Response() { |
| Initialize(kHttpUrl, true); |
| |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| Respond(response_generator_->Generate200()); |
| |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| } |
| |
| // Helper to initialize tests with a valid 206 response. |
| void InitializeWith206Response() { |
| Initialize(kHttpUrl, true); |
| |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| Respond(response_generator_->Generate206(0)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| } |
| |
| // Helper to initialize tests with a valid file:// response. |
| void InitializeWithFileResponse() { |
| Initialize(kFileUrl, true); |
| |
| EXPECT_CALL(host_, SetTotalBytes(kFileSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kFileSize)); |
| Respond(response_generator_->GenerateFileResponse(0)); |
| |
| ReceiveData(kDataSize); |
| } |
| |
| // Stops any active loaders and shuts down the data source. |
| // |
| // This typically happens when the page is closed and for our purposes is |
| // appropriate to do when tearing down a test. |
| void Stop() { |
| if (loading()) { |
| data_provider()->didFail(url_loader(), |
| response_generator_->GenerateError()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| data_source_->Stop(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void Respond(const WebURLResponse& response) { |
| EXPECT_TRUE(url_loader()); |
| if (!active_loader()) return; |
| data_provider()->didReceiveResponse(url_loader(), response); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void ReceiveDataLow(int size) { |
| EXPECT_TRUE(url_loader()); |
| if (!url_loader()) return; |
| std::unique_ptr<char[]> data(new char[size]); |
| SbMemorySet(data.get(), 0xA5, size); // Arbitrary non-zero value. |
| |
| data_provider()->didReceiveData(url_loader(), data.get(), size, size, size); |
| } |
| |
| void ReceiveData(int size) { |
| ReceiveDataLow(size); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void FinishLoading() { |
| EXPECT_TRUE(url_loader()); |
| if (!url_loader()) return; |
| data_provider()->didFinishLoading(url_loader(), 0, -1); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void FailLoading() { |
| data_provider()->didFail(url_loader(), |
| response_generator_->GenerateError()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void Restart() { |
| EXPECT_TRUE(data_provider()); |
| EXPECT_FALSE(active_loader_allownull()); |
| if (!data_provider()) return; |
| data_provider()->Start(); |
| } |
| |
| MOCK_METHOD1(ReadCallback, void(int size)); |
| |
| void ReadAt(int64_t position, int64_t howmuch = kDataSize) { |
| data_source_->Read(position, howmuch, buffer_, |
| base::Bind(&MultibufferDataSourceTest::ReadCallback, |
| base::Unretained(this))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void ExecuteMixedResponseSuccessTest(const WebURLResponse& response1, |
| const WebURLResponse& response2) { |
| EXPECT_CALL(host_, SetTotalBytes(kFileSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)).Times(2); |
| |
| Respond(response1); |
| ReceiveData(kDataSize); |
| ReadAt(0); |
| EXPECT_TRUE(loading()); |
| |
| FinishLoading(); |
| Restart(); |
| ReadAt(kDataSize); |
| Respond(response2); |
| ReceiveData(kDataSize); |
| FinishLoading(); |
| Stop(); |
| } |
| |
| void ExecuteMixedResponseFailureTest(const WebURLResponse& response1, |
| const WebURLResponse& response2) { |
| EXPECT_CALL(host_, SetTotalBytes(kFileSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| // Stop() will also cause the readback to be called with kReadError, but |
| // we want to make sure it was called before Stop(). |
| bool failed_ = false; |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)) |
| .WillOnce(Assign(&failed_, true)); |
| |
| Respond(response1); |
| ReceiveData(kDataSize); |
| ReadAt(0); |
| EXPECT_TRUE(loading()); |
| |
| FinishLoading(); |
| Restart(); |
| ReadAt(kDataSize); |
| Respond(response2); |
| EXPECT_TRUE(failed_); |
| Stop(); |
| } |
| |
| void CheckCapacityDefer() { |
| EXPECT_EQ(2 << 20, preload_low()); |
| EXPECT_EQ(3 << 20, preload_high()); |
| } |
| |
| void CheckReadThenDefer() { |
| EXPECT_EQ(0, preload_low()); |
| EXPECT_EQ(0, preload_high()); |
| } |
| |
| void CheckNeverDefer() { |
| EXPECT_EQ(1LL << 40, preload_low()); |
| EXPECT_EQ(1LL << 40, preload_high()); |
| } |
| |
| // Accessors for private variables on |data_source_|. |
| MultiBufferReader* loader() { return data_source_->reader_.get(); } |
| |
| TestResourceMultiBuffer* multibuffer() { |
| return url_index_->last_url_data()->test_multibuffer(); |
| } |
| |
| TestMultiBufferDataProvider* data_provider() { |
| return multibuffer()->GetProvider(); |
| } |
| ActiveLoader* active_loader() { |
| EXPECT_TRUE(data_provider()); |
| if (!data_provider()) return NULL; |
| return data_provider()->active_loader_.get(); |
| } |
| ActiveLoader* active_loader_allownull() { |
| TestMultiBufferDataProvider* data_provider = |
| multibuffer()->GetProvider_allownull(); |
| if (!data_provider) return NULL; |
| return data_provider->active_loader_.get(); |
| } |
| WebURLLoader* url_loader() { |
| EXPECT_TRUE(active_loader()); |
| if (!active_loader()) return NULL; |
| return active_loader()->loader_.get(); |
| } |
| |
| bool loading() { return multibuffer()->loading(); } |
| |
| MultibufferDataSource::Preload preload() { return data_source_->preload_; } |
| void set_preload(MultibufferDataSource::Preload preload) { |
| preload_ = preload; |
| } |
| int64_t preload_high() { |
| CHECK(loader()); |
| return loader()->preload_high(); |
| } |
| int64_t preload_low() { |
| CHECK(loader()); |
| return loader()->preload_low(); |
| } |
| int data_source_bitrate() { return data_source_->bitrate_; } |
| double data_source_playback_rate() { return data_source_->playback_rate_; } |
| bool is_local_source() { return data_source_->assume_fully_buffered(); } |
| scoped_refptr<UrlData> url_data() { return data_source_->url_data_; } |
| void set_might_be_reused_from_cache_in_future(bool value) { |
| url_data()->set_cacheable(value); |
| } |
| |
| protected: |
| blink::WebFrameClient client_; |
| WebView* view_; |
| MultibufferDataSource::Preload preload_; |
| base::MessageLoop message_loop_; |
| linked_ptr<TestUrlIndex> url_index_; |
| |
| std::unique_ptr<MockMultibufferDataSource> data_source_; |
| |
| std::unique_ptr<TestResponseGenerator> response_generator_; |
| |
| StrictMock<MockBufferedDataSourceHost> host_; |
| |
| // Used for calling MultibufferDataSource::Read(). |
| uint8_t buffer_[kDataSize * 2]; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MultibufferDataSourceTest); |
| }; |
| |
| TEST_F(MultibufferDataSourceTest, Range_Supported) { |
| InitializeWith206Response(); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_FALSE(data_source_->IsStreaming()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Range_InstanceSizeUnknown) { |
| Initialize(kHttpUrl, true); |
| |
| Respond(response_generator_->Generate206( |
| 0, TestResponseGenerator::kNoContentRangeInstanceSize)); |
| |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_TRUE(data_source_->IsStreaming()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Range_NotFound) { |
| Initialize(kHttpUrl, false); |
| Respond(response_generator_->Generate404()); |
| |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Range_NotSupported) { |
| InitializeWith200Response(); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_TRUE(data_source_->IsStreaming()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Range_NotSatisfiable) { |
| Initialize(kHttpUrl, true); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| Respond(response_generator_->GenerateResponse(416)); |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| // Special carve-out for Apache versions that choose to return a 200 for |
| // Range:0- ("because it's more efficient" than a 206) |
| TEST_F(MultibufferDataSourceTest, Range_SupportedButReturned200) { |
| Initialize(kHttpUrl, true); |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| WebURLResponse response = response_generator_->Generate200(); |
| response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"), |
| WebString::fromUTF8("bytes")); |
| Respond(response); |
| |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_FALSE(data_source_->IsStreaming()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Range_MissingContentRange) { |
| Initialize(kHttpUrl, false); |
| Respond(response_generator_->Generate206( |
| 0, TestResponseGenerator::kNoContentRange)); |
| |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Range_MissingContentLength) { |
| Initialize(kHttpUrl, true); |
| |
| // It'll manage without a Content-Length response. |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| Respond(response_generator_->Generate206( |
| 0, TestResponseGenerator::kNoContentLength)); |
| |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_FALSE(data_source_->IsStreaming()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Range_WrongContentRange) { |
| Initialize(kHttpUrl, false); |
| |
| // Now it's done and will fail. |
| Respond(response_generator_->Generate206(1337)); |
| |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| // Test the case where the initial response from the server indicates that |
| // Range requests are supported, but a later request prove otherwise. |
| TEST_F(MultibufferDataSourceTest, Range_ServerLied) { |
| InitializeWith206Response(); |
| |
| // Read causing a new request to be made -- we'll expect it to error. |
| ReadAt(kFarReadPosition); |
| |
| // Return a 200 in response to a range request. |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); |
| Respond(response_generator_->Generate200()); |
| |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_AbortWhileReading) { |
| InitializeWith206Response(); |
| |
| // Make sure there's a pending read -- we'll expect it to error. |
| ReadAt(kFileSize); |
| |
| // Abort!!! |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kAborted)); |
| data_source_->Abort(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, File_AbortWhileReading) { |
| InitializeWithFileResponse(); |
| |
| // Make sure there's a pending read -- we'll expect it to error. |
| ReadAt(kFileSize); |
| |
| // Abort!!! |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kAborted)); |
| data_source_->Abort(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_Retry) { |
| InitializeWith206Response(); |
| |
| // Read to advance our position. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| // Issue a pending read but terminate the connection to force a retry. |
| ReadAt(kDataSize); |
| FinishLoading(); |
| Restart(); |
| Respond(response_generator_->Generate206(kDataSize)); |
| |
| // Complete the read. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_TRUE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_RetryOnError) { |
| InitializeWith206Response(); |
| |
| // Read to advance our position. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| // Issue a pending read but trigger an error to force a retry. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReadAt(kDataSize); |
| base::RunLoop run_loop; |
| data_provider()->didFail(url_loader(), response_generator_->GenerateError()); |
| data_provider()->RunOnStart(run_loop.QuitClosure()); |
| run_loop.Run(); |
| Respond(response_generator_->Generate206(kDataSize)); |
| ReceiveData(kDataSize); |
| FinishLoading(); |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| // Make sure that we prefetch across partial responses. (crbug.com/516589) |
| TEST_F(MultibufferDataSourceTest, Http_PartialResponsePrefetch) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 3 - 1); |
| |
| EXPECT_CALL(host_, SetTotalBytes(kFileSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 3)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| |
| Respond(response1); |
| ReceiveData(kDataSize); |
| ReadAt(0); |
| EXPECT_TRUE(loading()); |
| |
| FinishLoading(); |
| Restart(); |
| Respond(response2); |
| ReceiveData(kDataSize); |
| ReceiveData(kDataSize); |
| FinishLoading(); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_PartialResponse) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| // The origin URL of response1 and response2 are same. So no error should |
| // occur. |
| ExecuteMixedResponseSuccessTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| Http_MixedResponse_RedirectedToDifferentPathResponse) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| response2.setURL(GURL(kHttpDifferentPathUrl)); |
| // The origin URL of response1 and response2 are same. So no error should |
| // occur. |
| ExecuteMixedResponseSuccessTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| Http_MixedResponse_RedirectedToDifferentOriginResponse) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| response2.setURL(GURL(kHttpDifferentOriginUrl)); |
| // The origin URL of response1 and response2 are different. So an error should |
| // occur. |
| ExecuteMixedResponseFailureTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| Http_MixedResponse_ServiceWorkerGeneratedResponseAndNormalResponse) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| response1.setWasFetchedViaServiceWorker(true); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| // response1 is generated in a Service Worker but response2 is from a native |
| // server. So an error should occur. |
| ExecuteMixedResponseFailureTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| Http_MixedResponse_ServiceWorkerProxiedAndSameURLResponse) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| response1.setWasFetchedViaServiceWorker(true); |
| response1.setOriginalURLViaServiceWorker(GURL(kHttpUrl)); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| // The origin URL of response1 and response2 are same. So no error should |
| // occur. |
| ExecuteMixedResponseSuccessTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| Http_MixedResponse_ServiceWorkerProxiedAndDifferentPathResponse) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| response1.setWasFetchedViaServiceWorker(true); |
| response1.setOriginalURLViaServiceWorker(GURL(kHttpDifferentPathUrl)); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| // The origin URL of response1 and response2 are same. So no error should |
| // occur. |
| ExecuteMixedResponseSuccessTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| Http_MixedResponse_ServiceWorkerProxiedAndDifferentOriginResponse) { |
| Initialize(kHttpUrl, true); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| response1.setWasFetchedViaServiceWorker(true); |
| response1.setOriginalURLViaServiceWorker(GURL(kHttpDifferentOriginUrl)); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| // The origin URL of response1 and response2 are different. So an error should |
| // occur. |
| ExecuteMixedResponseFailureTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| Http_MixedResponse_ServiceWorkerProxiedAndDifferentOriginResponseCORS) { |
| InitializeWithCORS(kHttpUrl, true, UrlData::CORS_ANONYMOUS); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| response1.setWasFetchedViaServiceWorker(true); |
| response1.setOriginalURLViaServiceWorker(GURL(kHttpDifferentOriginUrl)); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| // The origin URL of response1 and response2 are different, but a CORS check |
| // has been passed for each request, so expect success. |
| ExecuteMixedResponseSuccessTest(response1, response2); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, File_Retry) { |
| InitializeWithFileResponse(); |
| |
| // Read to advance our position. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| // Issue a pending read but terminate the connection to force a retry. |
| ReadAt(kDataSize); |
| FinishLoading(); |
| Restart(); |
| Respond(response_generator_->GenerateFileResponse(kDataSize)); |
| |
| // Complete the read. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_TRUE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_TooManyRetries) { |
| InitializeWith206Response(); |
| |
| // Make sure there's a pending read -- we'll expect it to error. |
| ReadAt(kDataSize); |
| |
| for (int i = 0; i < ResourceMultiBufferDataProvider::kMaxRetries; i++) { |
| FailLoading(); |
| data_provider()->Start(); |
| Respond(response_generator_->Generate206(kDataSize)); |
| } |
| |
| // Stop() will also cause the readback to be called with kReadError, but |
| // we want to make sure it was called during FailLoading(). |
| bool failed_ = false; |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)) |
| .WillOnce(Assign(&failed_, true)); |
| FailLoading(); |
| EXPECT_TRUE(failed_); |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, File_TooManyRetries) { |
| InitializeWithFileResponse(); |
| |
| // Make sure there's a pending read -- we'll expect it to error. |
| ReadAt(kDataSize); |
| |
| for (int i = 0; i < ResourceMultiBufferDataProvider::kMaxRetries; i++) { |
| FailLoading(); |
| data_provider()->Start(); |
| Respond(response_generator_->Generate206(kDataSize)); |
| } |
| |
| // Stop() will also cause the readback to be called with kReadError, but |
| // we want to make sure it was called during FailLoading(). |
| bool failed_ = false; |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)) |
| .WillOnce(Assign(&failed_, true)); |
| FailLoading(); |
| EXPECT_TRUE(failed_); |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, File_InstanceSizeUnknown) { |
| Initialize(kFileUrl, false); |
| |
| Respond( |
| response_generator_->GenerateFileResponse(media::DataSource::kReadError)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_FALSE(data_source_->downloading()); |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, File_Successful) { |
| InitializeWithFileResponse(); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_FALSE(data_source_->IsStreaming()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, StopDuringRead) { |
| InitializeWith206Response(); |
| |
| uint8_t buffer[256]; |
| data_source_->Read(0, arraysize(buffer), buffer, |
| base::Bind(&MultibufferDataSourceTest::ReadCallback, |
| base::Unretained(this))); |
| |
| // The outstanding read should fail before the stop callback runs. |
| { |
| InSequence s; |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); |
| data_source_->Stop(); |
| } |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, DefaultValues) { |
| InitializeWith206Response(); |
| |
| // Ensure we have sane values for default loading scenario. |
| EXPECT_EQ(MultibufferDataSource::AUTO, preload()); |
| EXPECT_EQ(2 << 20, preload_low()); |
| EXPECT_EQ(3 << 20, preload_high()); |
| |
| EXPECT_EQ(0, data_source_bitrate()); |
| EXPECT_EQ(0.0, data_source_playback_rate()); |
| |
| EXPECT_TRUE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, SetBitrate) { |
| InitializeWith206Response(); |
| |
| data_source_->SetBitrate(1234); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1234, data_source_bitrate()); |
| |
| // Read so far ahead to cause the loader to get recreated. |
| TestMultiBufferDataProvider* old_loader = data_provider(); |
| ReadAt(kFarReadPosition); |
| Respond(response_generator_->Generate206(kFarReadPosition)); |
| |
| // Verify loader changed but still has same bitrate. |
| EXPECT_NE(old_loader, data_provider()); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, MediaPlaybackRateChanged) { |
| InitializeWith206Response(); |
| |
| data_source_->MediaPlaybackRateChanged(2.0); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2.0, data_source_playback_rate()); |
| |
| // Read so far ahead to cause the loader to get recreated. |
| TestMultiBufferDataProvider* old_loader = data_provider(); |
| ReadAt(kFarReadPosition); |
| Respond(response_generator_->Generate206(kFarReadPosition)); |
| |
| // Verify loader changed but still has same playback rate. |
| EXPECT_NE(old_loader, data_provider()); |
| |
| EXPECT_TRUE(loading()); |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_Read) { |
| InitializeWith206Response(); |
| |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0, kDataSize * 2); |
| |
| ReadAt(kDataSize, kDataSize); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, |
| AddBufferedByteRange(kDataSize, kDataSize + kDataSize / 2)); |
| ReceiveData(kDataSize / 2); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReceiveData(kDataSize / 2); |
| |
| EXPECT_TRUE(data_source_->downloading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_ShareData) { |
| InitializeWith206Response(); |
| |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0, kDataSize * 2); |
| |
| ReadAt(kDataSize, kDataSize); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, |
| AddBufferedByteRange(kDataSize, kDataSize + kDataSize / 2)); |
| ReceiveData(kDataSize / 2); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReceiveData(kDataSize / 2); |
| |
| EXPECT_TRUE(data_source_->downloading()); |
| |
| StrictMock<MockBufferedDataSourceHost> host2; |
| MockMultibufferDataSource source2( |
| GURL(kHttpUrl), UrlData::CORS_UNSPECIFIED, message_loop_.task_runner(), |
| url_index_, view_->mainFrame()->toWebLocalFrame(), &host2); |
| source2.SetPreload(preload_); |
| |
| EXPECT_CALL(*this, OnInitialize(true)); |
| |
| // This call would not be expected if we were not sharing data. |
| EXPECT_CALL(host2, SetTotalBytes(response_generator_->content_length())); |
| source2.Initialize(base::Bind(&MultibufferDataSourceTest::OnInitialize, |
| base::Unretained(this))); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Always loading after initialize. |
| EXPECT_EQ(source2.downloading(), true); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_Read_Seek) { |
| InitializeWith206Response(); |
| |
| // Read a bit from the beginning. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| // Simulate a seek by reading a bit beyond kDataSize. |
| ReadAt(kDataSize * 2); |
| |
| // We receive data leading up to but not including our read. |
| // No notification will happen, since it's progress outside |
| // of our current range. |
| ReceiveData(kDataSize); |
| |
| // We now receive the rest of the data for our read. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 3)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_TRUE(data_source_->downloading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, File_Read) { |
| InitializeWithFileResponse(); |
| |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0, kDataSize * 2); |
| |
| ReadAt(kDataSize, kDataSize); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReceiveData(kDataSize); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_FinishLoading) { |
| InitializeWith206Response(); |
| |
| EXPECT_TRUE(data_source_->downloading()); |
| // premature didFinishLoading() will cause a retry. |
| FinishLoading(); |
| EXPECT_TRUE(data_source_->downloading()); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, File_FinishLoading) { |
| InitializeWithFileResponse(); |
| |
| ReceiveData(kDataSize); |
| |
| EXPECT_FALSE(data_source_->downloading()); |
| // premature didFinishLoading() will cause a retry. |
| FinishLoading(); |
| EXPECT_FALSE(data_source_->downloading()); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, LocalResource_DeferStrategy) { |
| InitializeWithFileResponse(); |
| |
| EXPECT_EQ(MultibufferDataSource::AUTO, preload()); |
| EXPECT_TRUE(is_local_source()); |
| CheckCapacityDefer(); |
| |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckCapacityDefer(); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, LocalResource_PreloadMetadata_DeferStrategy) { |
| set_preload(MultibufferDataSource::METADATA); |
| InitializeWithFileResponse(); |
| |
| EXPECT_EQ(MultibufferDataSource::METADATA, preload()); |
| EXPECT_TRUE(is_local_source()); |
| CheckReadThenDefer(); |
| |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckCapacityDefer(); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, ExternalResource_Reponse200_DeferStrategy) { |
| InitializeWith200Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::AUTO, preload()); |
| EXPECT_FALSE(is_local_source()); |
| EXPECT_FALSE(data_source_->range_supported()); |
| CheckCapacityDefer(); |
| |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckCapacityDefer(); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| ExternalResource_Response200_PreloadMetadata_DeferStrategy) { |
| set_preload(MultibufferDataSource::METADATA); |
| InitializeWith200Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::METADATA, preload()); |
| EXPECT_FALSE(is_local_source()); |
| EXPECT_FALSE(data_source_->range_supported()); |
| CheckReadThenDefer(); |
| |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckCapacityDefer(); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, ExternalResource_Reponse206_DeferStrategy) { |
| InitializeWith206Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::AUTO, preload()); |
| EXPECT_FALSE(is_local_source()); |
| EXPECT_TRUE(data_source_->range_supported()); |
| CheckCapacityDefer(); |
| |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| set_might_be_reused_from_cache_in_future(true); |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckNeverDefer(); |
| |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_NORMAL); |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| |
| set_might_be_reused_from_cache_in_future(false); |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckCapacityDefer(); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| ExternalResource_Response206_PreloadMetadata_DeferStrategy) { |
| set_preload(MultibufferDataSource::METADATA); |
| InitializeWith206Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::METADATA, preload()); |
| EXPECT_FALSE(is_local_source()); |
| EXPECT_TRUE(data_source_->range_supported()); |
| CheckReadThenDefer(); |
| |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| |
| set_might_be_reused_from_cache_in_future(true); |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckNeverDefer(); |
| |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_NORMAL); |
| data_source_->MediaIsPlaying(); |
| CheckCapacityDefer(); |
| set_might_be_reused_from_cache_in_future(false); |
| data_source_->SetBufferingStrategy( |
| MultibufferDataSource::BUFFERING_STRATEGY_AGGRESSIVE); |
| CheckCapacityDefer(); |
| |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, ExternalResource_Response206_VerifyDefer) { |
| set_preload(MultibufferDataSource::METADATA); |
| InitializeWith206Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::METADATA, preload()); |
| EXPECT_FALSE(is_local_source()); |
| EXPECT_TRUE(data_source_->range_supported()); |
| CheckReadThenDefer(); |
| |
| // Read a bit from the beginning. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| ASSERT_TRUE(active_loader()); |
| EXPECT_TRUE(active_loader()->deferred()); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| ExternalResource_Response206_CancelAfterDefer) { |
| set_preload(MultibufferDataSource::METADATA); |
| InitializeWith206Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::METADATA, preload()); |
| EXPECT_FALSE(is_local_source()); |
| |
| EXPECT_TRUE(data_source_->range_supported()); |
| CheckReadThenDefer(); |
| |
| ReadAt(kDataSize); |
| |
| data_source_->OnBufferingHaveEnough(false); |
| ASSERT_TRUE(active_loader()); |
| |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_FALSE(active_loader_allownull()); |
| } |
| |
| // This test tries to trigger an edge case where the read callback |
| // never happens because the reader is deleted before that happens. |
| TEST_F(MultibufferDataSourceTest, |
| ExternalResource_Response206_CancelAfterDefer2) { |
| set_preload(MultibufferDataSource::METADATA); |
| InitializeWith206Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::METADATA, preload()); |
| EXPECT_FALSE(is_local_source()); |
| |
| EXPECT_TRUE(data_source_->range_supported()); |
| CheckReadThenDefer(); |
| |
| ReadAt(kDataSize); |
| |
| data_source_->OnBufferingHaveEnough(false); |
| ASSERT_TRUE(active_loader()); |
| |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(kDataSize, kDataSize + 2000)); |
| |
| ReceiveDataLow(2000); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2 + 2000)); |
| ReceiveDataLow(kDataSize); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(active_loader_allownull()); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, |
| ExternalResource_Response206_CancelAfterPlay) { |
| set_preload(MultibufferDataSource::METADATA); |
| InitializeWith206Response(); |
| |
| EXPECT_EQ(MultibufferDataSource::METADATA, preload()); |
| EXPECT_FALSE(is_local_source()); |
| |
| EXPECT_TRUE(data_source_->range_supported()); |
| CheckReadThenDefer(); |
| |
| ReadAt(kDataSize); |
| |
| // Marking the media as playing should prevent deferral. It also tells the |
| // data source to start buffering beyond the initial load. |
| data_source_->MediaIsPlaying(); |
| data_source_->OnBufferingHaveEnough(false); |
| CheckCapacityDefer(); |
| ASSERT_TRUE(active_loader()); |
| |
| // Read a bit from the beginning and ensure deferral hasn't happened yet. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReceiveData(kDataSize); |
| ASSERT_TRUE(active_loader()); |
| data_source_->OnBufferingHaveEnough(true); |
| ASSERT_TRUE(active_loader()); |
| ASSERT_FALSE(active_loader()->deferred()); |
| |
| // Deliver data until capacity is reached and verify deferral. |
| int bytes_received = 0; |
| EXPECT_CALL(host_, AddBufferedByteRange(_, _)).Times(testing::AtLeast(1)); |
| while (active_loader_allownull() && !active_loader()->deferred()) { |
| ReceiveData(kDataSize); |
| bytes_received += kDataSize; |
| } |
| EXPECT_GT(bytes_received, 0); |
| EXPECT_LT(bytes_received + kDataSize, kFileSize); |
| EXPECT_FALSE(active_loader_allownull()); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, SeekPastEOF) { |
| GURL gurl(kHttpUrl); |
| data_source_.reset(new MockMultibufferDataSource( |
| gurl, UrlData::CORS_UNSPECIFIED, message_loop_.task_runner(), url_index_, |
| view_->mainFrame()->toWebLocalFrame(), &host_)); |
| data_source_->SetPreload(preload_); |
| |
| response_generator_.reset(new TestResponseGenerator(gurl, kDataSize + 1)); |
| EXPECT_CALL(*this, OnInitialize(true)); |
| data_source_->Initialize(base::Bind(&MultibufferDataSourceTest::OnInitialize, |
| base::Unretained(this))); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Not really loading until after OnInitialize is called. |
| EXPECT_EQ(data_source_->downloading(), false); |
| |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| Respond(response_generator_->Generate206(0)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| |
| // Read a bit from the beginning. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| EXPECT_CALL(host_, AddBufferedByteRange(kDataSize, kDataSize + 1)); |
| ReceiveData(1); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 3)); |
| FinishLoading(); |
| EXPECT_CALL(*this, ReadCallback(0)); |
| |
| ReadAt(kDataSize + 5, kDataSize * 2); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_RetryThenRedirect) { |
| InitializeWith206Response(); |
| |
| // Read to advance our position. |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| // Issue a pending read but trigger an error to force a retry. |
| EXPECT_CALL(*this, ReadCallback(kDataSize - 10)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReadAt(kDataSize + 10, kDataSize - 10); |
| base::RunLoop run_loop; |
| data_provider()->didFail(url_loader(), response_generator_->GenerateError()); |
| data_provider()->RunOnStart(run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| // Server responds with a redirect. |
| blink::WebURLRequest request((GURL(kHttpDifferentPathUrl))); |
| blink::WebURLResponse response((GURL(kHttpUrl))); |
| response.setHTTPStatusCode(307); |
| data_provider()->willFollowRedirect(url_loader(), request, response); |
| Respond(response_generator_->Generate206(kDataSize)); |
| ReceiveData(kDataSize); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 3)); |
| FinishLoading(); |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_NotStreamingAfterRedirect) { |
| Initialize(kHttpUrl, true); |
| |
| // Server responds with a redirect. |
| blink::WebURLRequest request((GURL(kHttpDifferentPathUrl))); |
| blink::WebURLResponse response((GURL(kHttpUrl))); |
| response.setHTTPStatusCode(307); |
| data_provider()->willFollowRedirect(url_loader(), request, response); |
| |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| Respond(response_generator_->Generate206(0)); |
| |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_FALSE(data_source_->IsStreaming()); |
| |
| FinishLoading(); |
| EXPECT_FALSE(loading()); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, Http_RangeNotSatisfiableAfterRedirect) { |
| Initialize(kHttpUrl, true); |
| |
| // Server responds with a redirect. |
| blink::WebURLRequest request((GURL(kHttpDifferentPathUrl))); |
| blink::WebURLResponse response((GURL(kHttpUrl))); |
| response.setHTTPStatusCode(307); |
| data_provider()->willFollowRedirect(url_loader(), request, response); |
| |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| Respond(response_generator_->GenerateResponse(416)); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, LengthKnownAtEOF) { |
| Initialize(kHttpUrl, true); |
| // Server responds without content-length. |
| WebURLResponse response = response_generator_->Generate200(); |
| response.clearHTTPHeaderField(WebString::fromUTF8("Content-Length")); |
| response.setExpectedContentLength(kPositionNotSpecified); |
| Respond(response); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| int64_t len; |
| EXPECT_FALSE(data_source_->GetSize(&len)); |
| EXPECT_TRUE(data_source_->IsStreaming()); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| ReadAt(0); |
| |
| ReadAt(kDataSize); |
| EXPECT_CALL(host_, SetTotalBytes(kDataSize)); |
| EXPECT_CALL(*this, ReadCallback(0)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| FinishLoading(); |
| |
| // Done loading, now we should know the length. |
| EXPECT_TRUE(data_source_->GetSize(&len)); |
| EXPECT_EQ(kDataSize, len); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, FileSizeLessThanBlockSize) { |
| Initialize(kHttpUrl, true); |
| GURL gurl(kHttpUrl); |
| blink::WebURLResponse response(gurl); |
| response.setHTTPStatusCode(200); |
| response.setHTTPHeaderField( |
| WebString::fromUTF8("Content-Length"), |
| WebString::fromUTF8(base::Int64ToString(kDataSize / 2))); |
| response.setExpectedContentLength(kDataSize / 2); |
| Respond(response); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize / 2)); |
| EXPECT_CALL(host_, SetTotalBytes(kDataSize / 2)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2)); |
| ReceiveData(kDataSize / 2); |
| FinishLoading(); |
| |
| int64_t len = 0; |
| EXPECT_TRUE(data_source_->GetSize(&len)); |
| EXPECT_EQ(kDataSize / 2, len); |
| Stop(); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, DidPassCORSAccessTest) { |
| InitializeWithCORS(kHttpUrl, true, UrlData::CORS_ANONYMOUS); |
| set_preload(MultibufferDataSource::NONE); |
| WebURLResponse response1 = |
| response_generator_->GeneratePartial206(0, kDataSize - 1); |
| response1.setWasFetchedViaServiceWorker(true); |
| response1.setOriginalURLViaServiceWorker(GURL(kHttpDifferentOriginUrl)); |
| WebURLResponse response2 = |
| response_generator_->GeneratePartial206(kDataSize, kDataSize * 2 - 1); |
| |
| EXPECT_CALL(host_, SetTotalBytes(kFileSize)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| EXPECT_CALL(*this, ReadCallback(kDataSize)); |
| |
| EXPECT_FALSE(data_source_->DidPassCORSAccessCheck()); |
| Respond(response1); |
| ReceiveData(kDataSize); |
| ReadAt(0); |
| EXPECT_TRUE(loading()); |
| EXPECT_TRUE(data_source_->DidPassCORSAccessCheck()); |
| |
| FinishLoading(); |
| |
| // Verify that if reader_ is null, DidPassCORSAccessCheck still returns true. |
| data_source_->Stop(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(data_source_->DidPassCORSAccessCheck()); |
| } |
| |
| TEST_F(MultibufferDataSourceTest, EtagTest) { |
| Initialize(kHttpUrl, true); |
| |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| WebURLResponse response = response_generator_->Generate206(0); |
| const std::string etag("\"arglebargle glop-glyf?\""); |
| response.setHTTPHeaderField(WebString::fromUTF8("Etag"), |
| WebString::fromUTF8(etag)); |
| Respond(response); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_EQ(url_data()->etag(), etag); |
| } |
| |
| // Provoke an edge case where the loading state may not end up transitioning |
| // back to "idle" when we're done loading. |
| TEST_F(MultibufferDataSourceTest, Http_CheckLoadingTransition) { |
| GURL gurl(kHttpUrl); |
| data_source_.reset(new MockMultibufferDataSource( |
| gurl, UrlData::CORS_UNSPECIFIED, message_loop_.task_runner(), url_index_, |
| view_->mainFrame()->toWebLocalFrame(), &host_)); |
| data_source_->SetPreload(preload_); |
| |
| response_generator_.reset(new TestResponseGenerator(gurl, kDataSize * 1)); |
| EXPECT_CALL(*this, OnInitialize(true)); |
| data_source_->Initialize(base::Bind(&MultibufferDataSourceTest::OnInitialize, |
| base::Unretained(this))); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Not really loading until after OnInitialize is called. |
| EXPECT_EQ(data_source_->downloading(), false); |
| |
| EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); |
| Respond(response_generator_->Generate206(0)); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize)); |
| ReceiveData(kDataSize); |
| |
| EXPECT_EQ(data_source_->downloading(), true); |
| EXPECT_CALL(host_, AddBufferedByteRange(kDataSize, kDataSize + 1)); |
| ReceiveDataLow(1); |
| EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 3)); |
| data_provider()->didFinishLoading(url_loader(), 0, -1); |
| |
| EXPECT_CALL(*this, ReadCallback(1)); |
| data_source_->Read(kDataSize, 2, buffer_, |
| base::Bind(&MultibufferDataSourceTest::ReadCallback, |
| base::Unretained(this))); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Make sure we're not downloading anymore. |
| EXPECT_EQ(data_source_->downloading(), false); |
| Stop(); |
| } |
| |
| } // namespace media |
| } // namespace cobalt |