| // Copyright (c) 2011 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 <algorithm> |
| |
| #include "base/test/test_timeouts.h" |
| #include "media/base/mock_callback.h" |
| #include "media/base/mock_filter_host.h" |
| #include "media/base/mock_filters.h" |
| #include "net/base/net_errors.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" |
| #include "webkit/glue/media/buffered_data_source.h" |
| #include "webkit/mocks/mock_webframe.h" |
| |
| using ::testing::_; |
| using ::testing::Assign; |
| using ::testing::AtLeast; |
| using ::testing::DeleteArg; |
| using ::testing::DoAll; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| using ::testing::ReturnRef; |
| using ::testing::SetArgumentPointee; |
| using ::testing::StrictMock; |
| using ::testing::NiceMock; |
| using ::testing::WithArgs; |
| |
| namespace webkit_glue { |
| |
| static const char* kHttpUrl = "http://test"; |
| static const char* kFileUrl = "file://test"; |
| static const int kDataSize = 1024; |
| |
| enum NetworkState { |
| NONE, |
| LOADED, |
| LOADING |
| }; |
| |
| // A mock BufferedDataSource to inject mock BufferedResourceLoader through |
| // CreateResourceLoader() method. |
| class MockBufferedDataSource : public BufferedDataSource { |
| public: |
| MockBufferedDataSource(MessageLoop* message_loop, WebFrame* frame) |
| : BufferedDataSource(message_loop, frame) { |
| } |
| |
| virtual base::TimeDelta GetTimeoutMilliseconds() { |
| return base::TimeDelta::FromMilliseconds( |
| TestTimeouts::tiny_timeout_ms()); |
| } |
| |
| MOCK_METHOD2(CreateResourceLoader, |
| BufferedResourceLoader*(int64 first_position, |
| int64 last_position)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource); |
| }; |
| |
| class MockBufferedResourceLoader : public BufferedResourceLoader { |
| public: |
| MockBufferedResourceLoader() : BufferedResourceLoader(GURL(), 0, 0) { |
| } |
| |
| MOCK_METHOD3(Start, void(net::CompletionCallback* read_callback, |
| NetworkEventCallback* network_callback, |
| WebFrame* frame)); |
| MOCK_METHOD0(Stop, void()); |
| MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer, |
| net::CompletionCallback* callback)); |
| MOCK_METHOD0(content_length, int64()); |
| MOCK_METHOD0(instance_size, int64()); |
| MOCK_METHOD0(range_supported, bool()); |
| MOCK_METHOD0(network_activity, bool()); |
| MOCK_METHOD0(url, const GURL&()); |
| MOCK_METHOD0(GetBufferedFirstBytePosition, int64()); |
| MOCK_METHOD0(GetBufferedLastBytePosition, int64()); |
| |
| protected: |
| ~MockBufferedResourceLoader() {} |
| |
| DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader); |
| }; |
| |
| class BufferedDataSourceTest : public testing::Test { |
| public: |
| BufferedDataSourceTest() { |
| message_loop_ = MessageLoop::current(); |
| |
| // Prepare test data. |
| for (size_t i = 0; i < sizeof(data_); ++i) { |
| data_[i] = i; |
| } |
| } |
| |
| virtual ~BufferedDataSourceTest() { |
| } |
| |
| void ExpectCreateAndStartResourceLoader(int start_error) { |
| EXPECT_CALL(*data_source_, CreateResourceLoader(_, _)) |
| .WillOnce(Return(loader_.get())); |
| |
| EXPECT_CALL(*loader_, Start(NotNull(), NotNull(), NotNull())) |
| .WillOnce( |
| DoAll(Assign(&error_, start_error), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeStartCallback))); |
| } |
| |
| void InitializeDataSource(const char* url, int error, |
| bool partial_response, int64 instance_size, |
| NetworkState networkState) { |
| // Saves the url first. |
| gurl_ = GURL(url); |
| |
| frame_.reset(new NiceMock<MockWebFrame>()); |
| |
| data_source_ = new MockBufferedDataSource(MessageLoop::current(), |
| frame_.get()); |
| data_source_->set_host(&host_); |
| |
| scoped_refptr<NiceMock<MockBufferedResourceLoader> > first_loader( |
| new NiceMock<MockBufferedResourceLoader>()); |
| |
| // Creates the mock loader to be injected. |
| loader_ = first_loader; |
| |
| bool initialized_ok = (error == net::OK); |
| bool loaded = networkState == LOADED; |
| { |
| InSequence s; |
| ExpectCreateAndStartResourceLoader(error); |
| |
| // In the case of an invalid partial response we expect a second loader |
| // to be created. |
| if (partial_response && (error == net::ERR_INVALID_RESPONSE)) { |
| // Verify that the initial loader is stopped. |
| EXPECT_CALL(*loader_, url()) |
| .WillRepeatedly(ReturnRef(gurl_)); |
| EXPECT_CALL(*loader_, Stop()); |
| |
| // Replace loader_ with a new instance. |
| loader_ = new NiceMock<MockBufferedResourceLoader>(); |
| |
| // Create and start. Make sure Start() is called on the new loader. |
| ExpectCreateAndStartResourceLoader(net::OK); |
| |
| // Update initialization variable since we know the second loader will |
| // return OK. |
| initialized_ok = true; |
| } |
| } |
| |
| // Attach a static function that deletes the memory referred by the |
| // "callback" parameter. |
| ON_CALL(*loader_, Read(_, _, _ , _)) |
| .WillByDefault(DeleteArg<3>()); |
| |
| ON_CALL(*loader_, instance_size()) |
| .WillByDefault(Return(instance_size)); |
| |
| // range_supported() return true if we expect to get a partial response. |
| ON_CALL(*loader_, range_supported()) |
| .WillByDefault(Return(partial_response)); |
| |
| ON_CALL(*loader_, url()) |
| .WillByDefault(ReturnRef(gurl_)); |
| media::PipelineStatus expected_init_status = media::PIPELINE_OK; |
| if (initialized_ok) { |
| // Expected loaded or not. |
| EXPECT_CALL(host_, SetLoaded(loaded)); |
| |
| // TODO(hclam): The condition for streaming needs to be adjusted. |
| if (instance_size != -1 && (loaded || partial_response)) { |
| EXPECT_CALL(host_, SetTotalBytes(instance_size)); |
| if (loaded) |
| EXPECT_CALL(host_, SetBufferedBytes(instance_size)); |
| else |
| EXPECT_CALL(host_, SetBufferedBytes(0)); |
| } else { |
| EXPECT_CALL(host_, SetStreaming(true)); |
| } |
| } else { |
| expected_init_status = media::PIPELINE_ERROR_NETWORK; |
| EXPECT_CALL(*loader_, Stop()); |
| } |
| |
| // Actual initialization of the data source. |
| data_source_->Initialize(url, |
| media::NewExpectedStatusCallback(expected_init_status)); |
| message_loop_->RunAllPending(); |
| |
| if (initialized_ok) { |
| // Verify the size of the data source. |
| int64 size; |
| if (instance_size != -1 && (loaded || partial_response)) { |
| EXPECT_TRUE(data_source_->GetSize(&size)); |
| EXPECT_EQ(instance_size, size); |
| } else { |
| EXPECT_TRUE(data_source_->IsStreaming()); |
| } |
| } |
| } |
| |
| void StopDataSource() { |
| if (loader_) { |
| InSequence s; |
| EXPECT_CALL(*loader_, Stop()); |
| } |
| |
| data_source_->Stop(media::NewExpectedCallback()); |
| message_loop_->RunAllPending(); |
| } |
| |
| void InvokeStartCallback( |
| net::CompletionCallback* callback, |
| BufferedResourceLoader::NetworkEventCallback* network_callback, |
| WebFrame* frame) { |
| callback->RunWithParams(Tuple1<int>(error_)); |
| delete callback; |
| // TODO(hclam): Save this callback. |
| delete network_callback; |
| } |
| |
| void InvokeReadCallback(int64 position, int size, uint8* buffer, |
| net::CompletionCallback* callback) { |
| if (error_ > 0) |
| memcpy(buffer, data_ + static_cast<int>(position), error_); |
| callback->RunWithParams(Tuple1<int>(error_)); |
| delete callback; |
| } |
| |
| void ReadDataSourceHit(int64 position, int size, int read_size) { |
| EXPECT_TRUE(loader_); |
| |
| InSequence s; |
| // Expect the read is delegated to the resource loader. |
| EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) |
| .WillOnce(DoAll(Assign(&error_, read_size), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeReadCallback))); |
| |
| // The read has succeeded, so read callback will be called. |
| EXPECT_CALL(*this, ReadCallback(read_size)); |
| |
| data_source_->Read( |
| position, size, buffer_, |
| NewCallback(this, &BufferedDataSourceTest::ReadCallback)); |
| message_loop_->RunAllPending(); |
| |
| // Make sure data is correct. |
| EXPECT_EQ(0, |
| memcmp(buffer_, data_ + static_cast<int>(position), read_size)); |
| } |
| |
| void ReadDataSourceHang(int64 position, int size) { |
| EXPECT_TRUE(loader_); |
| |
| // Expect a call to read, but the call never returns. |
| EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())); |
| data_source_->Read( |
| position, size, buffer_, |
| NewCallback(this, &BufferedDataSourceTest::ReadCallback)); |
| message_loop_->RunAllPending(); |
| |
| // Now expect the read to return after aborting the data source. |
| EXPECT_CALL(*this, ReadCallback(_)); |
| EXPECT_CALL(*loader_, Stop()); |
| data_source_->Abort(); |
| message_loop_->RunAllPending(); |
| |
| // The loader has now been stopped. Set this to null so that when the |
| // DataSource is stopped, it does not expect a call to stop the loader. |
| loader_ = NULL; |
| } |
| |
| void ReadDataSourceMiss(int64 position, int size, int start_error) { |
| EXPECT_TRUE(loader_); |
| |
| // 1. Reply with a cache miss for the read. |
| { |
| InSequence s; |
| EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) |
| .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeReadCallback))); |
| EXPECT_CALL(*loader_, Stop()); |
| } |
| |
| // 2. Then the current loader will be stop and destroyed. |
| NiceMock<MockBufferedResourceLoader> *new_loader = |
| new NiceMock<MockBufferedResourceLoader>(); |
| EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) |
| .WillOnce(Return(new_loader)); |
| |
| // 3. Then the new loader will be started. |
| EXPECT_CALL(*new_loader, Start(NotNull(), NotNull(), NotNull())) |
| .WillOnce(DoAll(Assign(&error_, start_error), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeStartCallback))); |
| |
| if (start_error == net::OK) { |
| EXPECT_CALL(*new_loader, range_supported()) |
| .WillRepeatedly(Return(loader_->range_supported())); |
| |
| // 4a. Then again a read request is made to the new loader. |
| EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) |
| .WillOnce(DoAll(Assign(&error_, size), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeReadCallback))); |
| |
| EXPECT_CALL(*this, ReadCallback(size)); |
| } else { |
| // 4b. The read callback is called with an error because Start() on the |
| // new loader returned an error. |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); |
| } |
| |
| data_source_->Read( |
| position, size, buffer_, |
| NewCallback(this, &BufferedDataSourceTest::ReadCallback)); |
| message_loop_->RunAllPending(); |
| |
| // Make sure data is correct. |
| if (start_error == net::OK) |
| EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size)); |
| |
| loader_ = new_loader; |
| } |
| |
| void ReadDataSourceFailed(int64 position, int size, int error) { |
| EXPECT_TRUE(loader_); |
| |
| // 1. Expect the read is delegated to the resource loader. |
| EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) |
| .WillOnce(DoAll(Assign(&error_, error), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeReadCallback))); |
| |
| // 2. Host will then receive an error. |
| EXPECT_CALL(*loader_, Stop()); |
| |
| // 3. The read has failed, so read callback will be called. |
| EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); |
| |
| data_source_->Read( |
| position, size, buffer_, |
| NewCallback(this, &BufferedDataSourceTest::ReadCallback)); |
| |
| message_loop_->RunAllPending(); |
| } |
| |
| void ReadDataSourceTimesOut(int64 position, int size) { |
| // 1. Drop the request and let it times out. |
| { |
| InSequence s; |
| EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) |
| .WillOnce(DeleteArg<3>()); |
| EXPECT_CALL(*loader_, Stop()); |
| } |
| |
| // 2. Then the current loader will be stop and destroyed. |
| NiceMock<MockBufferedResourceLoader> *new_loader = |
| new NiceMock<MockBufferedResourceLoader>(); |
| EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) |
| .WillOnce(Return(new_loader)); |
| |
| // 3. Then the new loader will be started and respond to queries about |
| // whether this is a partial response using the value of the previous |
| // loader. |
| EXPECT_CALL(*new_loader, Start(NotNull(), NotNull(), NotNull())) |
| .WillOnce(DoAll(Assign(&error_, net::OK), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeStartCallback))); |
| EXPECT_CALL(*new_loader, range_supported()) |
| .WillRepeatedly(Return(loader_->range_supported())); |
| |
| // 4. Then again a read request is made to the new loader. |
| EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) |
| .WillOnce(DoAll(Assign(&error_, size), |
| Invoke(this, |
| &BufferedDataSourceTest::InvokeReadCallback), |
| InvokeWithoutArgs(message_loop_, |
| &MessageLoop::Quit))); |
| |
| EXPECT_CALL(*this, ReadCallback(size)); |
| |
| data_source_->Read( |
| position, size, buffer_, |
| NewCallback(this, &BufferedDataSourceTest::ReadCallback)); |
| |
| // This blocks the current thread until the watch task is executed and |
| // triggers a read callback to quit this message loop. |
| message_loop_->Run(); |
| |
| // Make sure data is correct. |
| EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size)); |
| |
| loader_ = new_loader; |
| } |
| |
| MOCK_METHOD1(ReadCallback, void(size_t size)); |
| |
| scoped_refptr<NiceMock<MockBufferedResourceLoader> > loader_; |
| scoped_refptr<MockBufferedDataSource> data_source_; |
| scoped_ptr<NiceMock<MockWebFrame> > frame_; |
| |
| StrictMock<media::MockFilterHost> host_; |
| GURL gurl_; |
| MessageLoop* message_loop_; |
| |
| int error_; |
| uint8 buffer_[1024]; |
| uint8 data_[1024]; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest); |
| }; |
| |
| TEST_F(BufferedDataSourceTest, InitializationSuccess) { |
| InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, InitiailizationFailed) { |
| InitializeDataSource(kHttpUrl, net::ERR_FILE_NOT_FOUND, false, 0, NONE); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, MissingContentLength) { |
| InitializeDataSource(kHttpUrl, net::OK, true, -1, LOADING); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, RangeRequestNotSupported) { |
| InitializeDataSource(kHttpUrl, net::OK, false, 1024, LOADING); |
| StopDataSource(); |
| } |
| |
| // Test the case where we get a 206 response, but no Content-Range header. |
| TEST_F(BufferedDataSourceTest, MissingContentRange) { |
| InitializeDataSource(kHttpUrl, net::ERR_INVALID_RESPONSE, true, 1024, |
| LOADING); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, |
| MissingContentLengthAndRangeRequestNotSupported) { |
| InitializeDataSource(kHttpUrl, net::OK, false, -1, LOADING); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, ReadCacheHit) { |
| InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING); |
| |
| // Performs read with cache hit. |
| ReadDataSourceHit(10, 10, 10); |
| |
| // Performs read with cache hit but partially filled. |
| ReadDataSourceHit(20, 10, 5); |
| |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, ReadCacheMiss) { |
| InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); |
| ReadDataSourceMiss(1000, 10, net::OK); |
| ReadDataSourceMiss(20, 10, net::OK); |
| StopDataSource(); |
| } |
| |
| // Test the case where the initial response from the server indicates that |
| // Range requests are supported, but a later request prove otherwise. |
| TEST_F(BufferedDataSourceTest, ServerLiesAboutRangeSupport) { |
| InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); |
| ReadDataSourceHit(10, 10, 10); |
| ReadDataSourceMiss(1000, 10, net::ERR_INVALID_RESPONSE); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, ReadHang) { |
| InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING); |
| ReadDataSourceHang(10, 10); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, ReadFailed) { |
| InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); |
| ReadDataSourceHit(10, 10, 10); |
| ReadDataSourceFailed(10, 10, net::ERR_CONNECTION_RESET); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, ReadTimesOut) { |
| InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); |
| ReadDataSourceTimesOut(20, 10); |
| StopDataSource(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, FileHasLoadedState) { |
| InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED); |
| ReadDataSourceTimesOut(20, 10); |
| StopDataSource(); |
| } |
| |
| // This test makes sure that Stop() does not require a task to run on |
| // |message_loop_| before it calls its callback. This prevents accidental |
| // introduction of a pipeline teardown deadlock. The pipeline owner blocks |
| // the render message loop while waiting for Stop() to complete. Since this |
| // object runs on the render message loop, Stop() will not complete if it |
| // requires a task to run on the the message loop that is being blocked. |
| TEST_F(BufferedDataSourceTest, StopDoesNotUseMessageLoopForCallback) { |
| InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED); |
| |
| // Create a callback that lets us verify that it was called before |
| // Stop() returns. This is to make sure that the callback does not |
| // require |message_loop_| to execute tasks before being called. |
| media::MockCallback* stop_callback = media::NewExpectedCallback(); |
| bool stop_done_called = false; |
| ON_CALL(*stop_callback, RunWithParams(_)) |
| .WillByDefault(Assign(&stop_done_called, true)); |
| |
| // Stop() the data source like normal. |
| data_source_->Stop(stop_callback); |
| |
| // Verify that the callback was called inside the Stop() call. |
| EXPECT_TRUE(stop_done_called); |
| |
| message_loop_->RunAllPending(); |
| } |
| |
| TEST_F(BufferedDataSourceTest, AbortDuringPendingRead) { |
| InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED); |
| |
| // Setup a way to verify that Read() is not called on the loader. |
| // We are doing this to make sure that the ReadTask() is still on |
| // the message loop queue when Abort() is called. |
| bool read_called = false; |
| ON_CALL(*loader_, Read(_, _, _ , _)) |
| .WillByDefault(DoAll(Assign(&read_called, true), |
| DeleteArg<3>())); |
| |
| // Initiate a Read() on the data source, but don't allow the |
| // message loop to run. |
| data_source_->Read( |
| 0, 10, buffer_, |
| NewCallback(static_cast<BufferedDataSourceTest*>(this), |
| &BufferedDataSourceTest::ReadCallback)); |
| |
| // Call Abort() with the read pending. |
| EXPECT_CALL(*this, ReadCallback(-1)); |
| EXPECT_CALL(*loader_, Stop()); |
| data_source_->Abort(); |
| |
| // Verify that Read()'s after the Abort() issue callback with an error. |
| EXPECT_CALL(*this, ReadCallback(-1)); |
| data_source_->Read( |
| 0, 10, buffer_, |
| NewCallback(static_cast<BufferedDataSourceTest*>(this), |
| &BufferedDataSourceTest::ReadCallback)); |
| |
| // Stop() the data source like normal. |
| data_source_->Stop(media::NewExpectedCallback()); |
| |
| // Allow cleanup task to run. |
| message_loop_->RunAllPending(); |
| |
| // Verify that Read() was not called on the loader. |
| EXPECT_FALSE(read_called); |
| } |
| |
| } // namespace webkit_glue |