// 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