blob: 731592dd70ff35236e908f0d11ddaec7c30d4170 [file] [log] [blame]
// Copyright 2015 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 "storage/browser/blob/blob_reader.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task_runner.h"
#include "base/time/time.h"
#include "content/public/test/async_file_test_helper.h"
#include "content/public/test/test_file_system_context.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/disk_cache/disk_cache.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/fileapi/file_stream_reader.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_file_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using base::FilePath;
using content::AsyncFileTestHelper;
using net::DrainableIOBuffer;
using net::IOBuffer;
namespace storage {
namespace {
const int kTestDiskCacheStreamIndex = 0;
// Our disk cache tests don't need a real data handle since the tests themselves
// scope the disk cache and entries.
class EmptyDataHandle : public storage::BlobDataBuilder::DataHandle {
private:
~EmptyDataHandle() override {}
};
// A disk_cache::Entry that arbitrarily delays the completion of a read
// operation to allow testing some races without flake. This is particularly
// relevant in this unit test, which uses the always-synchronous MEMORY_CACHE.
class DelayedReadEntry : public disk_cache::Entry {
public:
explicit DelayedReadEntry(disk_cache::ScopedEntryPtr entry)
: entry_(entry.Pass()) {}
~DelayedReadEntry() override { EXPECT_FALSE(HasPendingReadCallbacks()); }
bool HasPendingReadCallbacks() { return !pending_read_callbacks_.empty(); }
void RunPendingReadCallbacks() {
std::vector<base::Callback<void(void)>> callbacks;
pending_read_callbacks_.swap(callbacks);
for (const auto& callback : callbacks)
callback.Run();
}
// From disk_cache::Entry:
void Doom() override { entry_->Doom(); }
void Close() override { delete this; } // Note this is required by the API.
std::string GetKey() const override { return entry_->GetKey(); }
base::Time GetLastUsed() const override { return entry_->GetLastUsed(); }
base::Time GetLastModified() const override {
return entry_->GetLastModified();
}
int32 GetDataSize(int index) const override {
return entry_->GetDataSize(index);
}
int ReadData(int index,
int offset,
IOBuffer* buf,
int buf_len,
const CompletionCallback& original_callback) override {
net::TestCompletionCallback callback;
int rv = entry_->ReadData(index, offset, buf, buf_len, callback.callback());
DCHECK_NE(rv, net::ERR_IO_PENDING)
<< "Test expects to use a MEMORY_CACHE instance, which is synchronous.";
pending_read_callbacks_.push_back(base::Bind(original_callback, rv));
return net::ERR_IO_PENDING;
}
int WriteData(int index,
int offset,
IOBuffer* buf,
int buf_len,
const CompletionCallback& callback,
bool truncate) override {
return entry_->WriteData(index, offset, buf, buf_len, callback, truncate);
}
int ReadSparseData(int64 offset,
IOBuffer* buf,
int buf_len,
const CompletionCallback& callback) override {
return entry_->ReadSparseData(offset, buf, buf_len, callback);
}
int WriteSparseData(int64 offset,
IOBuffer* buf,
int buf_len,
const CompletionCallback& callback) override {
return entry_->WriteSparseData(offset, buf, buf_len, callback);
}
int GetAvailableRange(int64 offset,
int len,
int64* start,
const CompletionCallback& callback) override {
return entry_->GetAvailableRange(offset, len, start, callback);
}
bool CouldBeSparse() const override { return entry_->CouldBeSparse(); }
void CancelSparseIO() override { entry_->CancelSparseIO(); }
int ReadyForSparseIO(const CompletionCallback& callback) override {
return entry_->ReadyForSparseIO(callback);
}
private:
disk_cache::ScopedEntryPtr entry_;
std::vector<base::Callback<void(void)>> pending_read_callbacks_;
};
scoped_ptr<disk_cache::Backend> CreateInMemoryDiskCache(
const scoped_refptr<base::SingleThreadTaskRunner>& thread) {
scoped_ptr<disk_cache::Backend> cache;
net::TestCompletionCallback callback;
int rv = disk_cache::CreateCacheBackend(
net::MEMORY_CACHE, net::CACHE_BACKEND_DEFAULT, FilePath(), 0, false,
thread, nullptr, &cache, callback.callback());
EXPECT_EQ(net::OK, callback.GetResult(rv));
return cache.Pass();
}
disk_cache::ScopedEntryPtr CreateDiskCacheEntry(disk_cache::Backend* cache,
const char* key,
const std::string& data) {
disk_cache::Entry* temp_entry = nullptr;
net::TestCompletionCallback callback;
int rv = cache->CreateEntry(key, &temp_entry, callback.callback());
if (callback.GetResult(rv) != net::OK)
return nullptr;
disk_cache::ScopedEntryPtr entry(temp_entry);
scoped_refptr<net::StringIOBuffer> iobuffer = new net::StringIOBuffer(data);
rv = entry->WriteData(kTestDiskCacheStreamIndex, 0, iobuffer.get(),
iobuffer->size(), callback.callback(), false);
EXPECT_EQ(static_cast<int>(data.size()), callback.GetResult(rv));
return entry.Pass();
}
template <typename T>
void SetValue(T* address, T value) {
*address = value;
}
class FakeFileStreamReader : public FileStreamReader {
public:
explicit FakeFileStreamReader(const std::string& contents)
: buffer_(new DrainableIOBuffer(
new net::StringIOBuffer(
scoped_ptr<std::string>(new std::string(contents))),
contents.size())),
net_error_(net::OK),
size_(contents.size()) {}
FakeFileStreamReader(const std::string& contents, uint64_t size)
: buffer_(new DrainableIOBuffer(
new net::StringIOBuffer(
scoped_ptr<std::string>(new std::string(contents))),
contents.size())),
net_error_(net::OK),
size_(size) {}
~FakeFileStreamReader() override {}
void SetReturnError(int net_error) { net_error_ = net_error; }
void SetAsyncRunner(base::SingleThreadTaskRunner* runner) {
async_task_runner_ = runner;
}
int Read(net::IOBuffer* buf,
int buf_length,
const net::CompletionCallback& done) override {
DCHECK(buf);
// When async_task_runner_ is not set, return synchronously.
if (!async_task_runner_.get()) {
if (net_error_ == net::OK) {
return ReadImpl(buf, buf_length, net::CompletionCallback());
} else {
return net_error_;
}
}
// Otherwise always return asynchronously.
if (net_error_ == net::OK) {
async_task_runner_->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&FakeFileStreamReader::ReadImpl),
base::Unretained(this), make_scoped_refptr(buf),
buf_length, done));
} else {
async_task_runner_->PostTask(FROM_HERE, base::Bind(done, net_error_));
}
return net::ERR_IO_PENDING;
}
int64 GetLength(const net::Int64CompletionCallback& size_callback) override {
// When async_task_runner_ is not set, return synchronously.
if (!async_task_runner_.get()) {
if (net_error_ == net::OK) {
return size_;
} else {
return net_error_;
}
}
if (net_error_ == net::OK) {
async_task_runner_->PostTask(FROM_HERE, base::Bind(size_callback, size_));
} else {
async_task_runner_->PostTask(
FROM_HERE,
base::Bind(size_callback, static_cast<int64_t>(net_error_)));
}
return net::ERR_IO_PENDING;
}
private:
int ReadImpl(scoped_refptr<net::IOBuffer> buf, int buf_length,
const net::CompletionCallback& done) {
CHECK_GE(buf_length, 0);
int length = std::min(buf_length, buffer_->BytesRemaining());
memcpy(buf->data(), buffer_->data(), length);
buffer_->DidConsume(length);
if (done.is_null()) {
return length;
}
done.Run(length);
return net::ERR_IO_PENDING;
}
scoped_refptr<net::DrainableIOBuffer> buffer_;
scoped_refptr<base::SingleThreadTaskRunner> async_task_runner_;
int net_error_;
uint64_t size_;
DISALLOW_COPY_AND_ASSIGN(FakeFileStreamReader);
};
class MockFileStreamReaderProvider
: public BlobReader::FileStreamReaderProvider {
public:
~MockFileStreamReaderProvider() override {}
MOCK_METHOD4(CreateForLocalFileMock,
FileStreamReader*(base::TaskRunner* task_runner,
const FilePath& file_path,
int64_t initial_offset,
const base::Time& expected_modification_time));
MOCK_METHOD4(CreateFileStreamReaderMock,
FileStreamReader*(const GURL& filesystem_url,
int64_t offset,
int64_t max_bytes_to_read,
const base::Time& expected_modification_time));
// Since we're returning a move-only type, we have to do some delegation for
// gmock.
scoped_ptr<FileStreamReader> CreateForLocalFile(
base::TaskRunner* task_runner,
const base::FilePath& file_path,
int64_t initial_offset,
const base::Time& expected_modification_time) override {
return make_scoped_ptr(CreateForLocalFileMock(
task_runner, file_path, initial_offset, expected_modification_time));
}
scoped_ptr<FileStreamReader> CreateFileStreamReader(
const GURL& filesystem_url,
int64_t offset,
int64_t max_bytes_to_read,
const base::Time& expected_modification_time) override {
return make_scoped_ptr(CreateFileStreamReaderMock(
filesystem_url, offset, max_bytes_to_read, expected_modification_time));
}
};
} // namespace
class BlobReaderTest : public ::testing::Test {
public:
BlobReaderTest() {}
~BlobReaderTest() override {}
void TearDown() override {
reader_.reset();
blob_handle_.reset();
message_loop_.RunUntilIdle();
base::RunLoop().RunUntilIdle();
}
protected:
void InitializeReader(BlobDataBuilder* builder) {
blob_handle_ = builder ? context_.AddFinishedBlob(builder).Pass() : nullptr;
provider_ = new MockFileStreamReaderProvider();
scoped_ptr<BlobReader::FileStreamReaderProvider> temp_ptr(provider_);
reader_.reset(new BlobReader(blob_handle_.get(), temp_ptr.Pass(),
message_loop_.task_runner().get()));
}
// Takes ownership of the file reader (the blob reader takes ownership).
void ExpectLocalFileCall(const FilePath& file_path,
base::Time modification_time,
uint64_t initial_offset,
FakeFileStreamReader* reader) {
EXPECT_CALL(*provider_, CreateForLocalFileMock(
message_loop_.task_runner().get(), file_path,
initial_offset, modification_time))
.WillOnce(testing::Return(reader));
}
// Takes ownership of the file reader (the blob reader takes ownership).
void ExpectFileSystemCall(const GURL& filesystem_url,
int64_t offset,
int64_t max_bytes_to_read,
base::Time expected_modification_time,
FakeFileStreamReader* reader) {
EXPECT_CALL(*provider_, CreateFileStreamReaderMock(
filesystem_url, offset, max_bytes_to_read,
expected_modification_time))
.WillOnce(testing::Return(reader));
}
void CheckSizeCalculatedSynchronously(size_t expected_size, int async_size) {
EXPECT_EQ(-1, async_size);
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(expected_size, reader_->total_size());
EXPECT_TRUE(reader_->total_size_calculated());
}
void CheckSizeNotCalculatedYet(int async_size) {
EXPECT_EQ(-1, async_size);
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_FALSE(reader_->total_size_calculated());
}
void CheckSizeCalculatedAsynchronously(size_t expected_size,
int async_result) {
EXPECT_EQ(net::OK, async_result);
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(expected_size, reader_->total_size());
EXPECT_TRUE(reader_->total_size_calculated());
}
scoped_refptr<net::IOBuffer> CreateBuffer(uint64_t size) {
return scoped_refptr<net::IOBuffer>(
new net::IOBuffer(static_cast<size_t>(size)));
}
bool IsReaderTotalSizeCalculated() {
return reader_->total_size_calculated();
}
BlobStorageContext context_;
scoped_ptr<BlobDataHandle> blob_handle_;
MockFileStreamReaderProvider* provider_ = nullptr;
base::MessageLoop message_loop_;
scoped_ptr<BlobReader> reader_;
private:
DISALLOW_COPY_AND_ASSIGN(BlobReaderTest);
};
namespace {
TEST_F(BlobReaderTest, BasicMemory) {
BlobDataBuilder b("uuid");
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
b.AppendData(kData);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kDataSize, size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kDataSize));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kDataSize, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize));
}
TEST_F(BlobReaderTest, BasicFile) {
BlobDataBuilder b("uuid");
const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
b.AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(&b);
// Non-async reader.
ExpectLocalFileCall(kPath, kTime, 0, new FakeFileStreamReader(kData));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}
TEST_F(BlobReaderTest, BasicFileSystem) {
BlobDataBuilder b("uuid");
const GURL kURL("file://test_file/here.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
b.AppendFileSystemFile(kURL, 0, kData.size(), kTime);
this->InitializeReader(&b);
// Non-async reader.
ExpectFileSystemCall(kURL, 0, kData.size(), kTime,
new FakeFileStreamReader(kData));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}
TEST_F(BlobReaderTest, BasicDiskCache) {
scoped_ptr<disk_cache::Backend> cache =
CreateInMemoryDiskCache(message_loop_.task_runner());
ASSERT_TRUE(cache);
BlobDataBuilder b("uuid");
const std::string kData = "Test Blob Data";
scoped_refptr<BlobDataBuilder::DataHandle> data_handle =
new EmptyDataHandle();
disk_cache::ScopedEntryPtr entry =
CreateDiskCacheEntry(cache.get(), "test entry", kData);
b.AppendDiskCacheEntry(data_handle, entry.get(), kTestDiskCacheStreamIndex);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "Test Blob Data", kData.size()));
}
TEST_F(BlobReaderTest, BufferLargerThanMemory) {
BlobDataBuilder b("uuid");
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
const size_t kBufferSize = 10ul;
b.AppendData(kData);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize));
}
TEST_F(BlobReaderTest, MemoryRange) {
BlobDataBuilder b("uuid");
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
const size_t kSeekOffset = 2ul;
const uint64_t kReadLength = 4ull;
b.AppendData(kData);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength);
reader_->SetReadRange(kSeekOffset, kReadLength);
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kDataSize - kSeekOffset, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kReadLength, static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "llo!", kReadLength));
}
TEST_F(BlobReaderTest, BufferSmallerThanMemory) {
BlobDataBuilder b("uuid");
const std::string kData("Hello!!!");
const size_t kBufferSize = 4ul;
b.AppendData(kData);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "Hell", kBufferSize));
bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "o!!!", kBufferSize));
}
TEST_F(BlobReaderTest, SegmentedBufferAndMemory) {
BlobDataBuilder b("uuid");
const size_t kNumItems = 10;
const size_t kItemSize = 6;
const size_t kBufferSize = 10;
const size_t kTotalSize = kNumItems * kItemSize;
char current_value = 0;
for (size_t i = 0; i < kNumItems; i++) {
char buf[kItemSize];
for (size_t j = 0; j < kItemSize; j++) {
buf[j] = current_value++;
}
b.AppendData(buf, kItemSize);
}
this->InitializeReader(&b);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kTotalSize, size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
current_value = 0;
for (size_t i = 0; i < kTotalSize / kBufferSize; i++) {
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
for (size_t j = 0; j < kBufferSize; j++) {
EXPECT_EQ(current_value, buffer->data()[j]);
current_value++;
}
}
}
TEST_F(BlobReaderTest, FileAsync) {
BlobDataBuilder b("uuid");
const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
b.AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(&b);
scoped_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
reader->SetAsyncRunner(message_loop_.task_runner().get());
ExpectLocalFileCall(kPath, kTime, 0, reader.release());
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeNotCalculatedYet(size_result);
message_loop_.RunUntilIdle();
CheckSizeCalculatedAsynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
message_loop_.RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}
TEST_F(BlobReaderTest, FileSystemAsync) {
BlobDataBuilder b("uuid");
const GURL kURL("file://test_file/here.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
b.AppendFileSystemFile(kURL, 0, kData.size(), kTime);
this->InitializeReader(&b);
scoped_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
reader->SetAsyncRunner(message_loop_.task_runner().get());
ExpectFileSystemCall(kURL, 0, kData.size(), kTime, reader.release());
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeNotCalculatedYet(size_result);
message_loop_.RunUntilIdle();
CheckSizeCalculatedAsynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
message_loop_.RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}
TEST_F(BlobReaderTest, DiskCacheAsync) {
scoped_ptr<disk_cache::Backend> cache =
CreateInMemoryDiskCache(message_loop_.task_runner());
ASSERT_TRUE(cache);
BlobDataBuilder b("uuid");
const std::string kData = "Test Blob Data";
scoped_refptr<BlobDataBuilder::DataHandle> data_handle =
new EmptyDataHandle();
scoped_ptr<DelayedReadEntry> delayed_read_entry(new DelayedReadEntry(
CreateDiskCacheEntry(cache.get(), "test entry", kData).Pass()));
b.AppendDiskCacheEntry(data_handle, delayed_read_entry.get(),
kTestDiskCacheStreamIndex);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_TRUE(delayed_read_entry->HasPendingReadCallbacks());
delayed_read_entry->RunPendingReadCallbacks();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(0, bytes_read);
EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, memcmp(buffer->data(), "Test Blob Data", kData.size()));
}
TEST_F(BlobReaderTest, FileRange) {
BlobDataBuilder b("uuid");
const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
// We check the offset in the ExpectLocalFileCall mock.
const std::string kRangeData = "leD";
const std::string kData = "FileData!!!";
const uint64_t kOffset = 2;
const uint64_t kReadLength = 3;
const base::Time kTime = base::Time::Now();
b.AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(&b);
scoped_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
reader->SetAsyncRunner(message_loop_.task_runner().get());
ExpectLocalFileCall(kPath, kTime, 0, reader.release());
// We create the reader again with the offset after the seek.
reader.reset(new FakeFileStreamReader(kRangeData));
reader->SetAsyncRunner(message_loop_.task_runner().get());
ExpectLocalFileCall(kPath, kTime, kOffset, reader.release());
int size_result = -1;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
message_loop_.RunUntilIdle();
scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength);
EXPECT_EQ(BlobReader::Status::DONE,
reader_->SetReadRange(kOffset, kReadLength));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->Read(buffer.get(), kReadLength, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
message_loop_.RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kReadLength, static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "leD", kReadLength));
}
TEST_F(BlobReaderTest, DiskCacheRange) {
scoped_ptr<disk_cache::Backend> cache =
CreateInMemoryDiskCache(message_loop_.task_runner());
ASSERT_TRUE(cache);
BlobDataBuilder b("uuid");
const std::string kData = "Test Blob Data";
const uint64_t kOffset = 2;
const uint64_t kReadLength = 3;
scoped_refptr<BlobDataBuilder::DataHandle> data_handle =
new EmptyDataHandle();
disk_cache::ScopedEntryPtr entry =
CreateDiskCacheEntry(cache.get(), "test entry", kData);
b.AppendDiskCacheEntry(data_handle, entry.get(), kTestDiskCacheStreamIndex);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength);
EXPECT_EQ(BlobReader::Status::DONE,
reader_->SetReadRange(kOffset, kReadLength));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kReadLength, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kReadLength, static_cast<size_t>(bytes_read));
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(0, memcmp(buffer->data(), "st ", kReadLength));
}
TEST_F(BlobReaderTest, FileSomeAsyncSegmentedOffsetsUnknownSizes) {
// This tests includes:
// * Unknown file sizes (item length of uint64::max) for every other item.
// * Offsets for every 3rd file item.
// * Non-async reader for every 4th file item.
BlobDataBuilder b("uuid");
const FilePath kPathBase = FilePath::FromUTF8Unsafe("/fake/file.txt");
const base::Time kTime = base::Time::Now();
const size_t kNumItems = 10;
const size_t kItemSize = 6;
const size_t kBufferSize = 10;
const size_t kTotalSize = kNumItems * kItemSize;
char current_value = 0;
// Create blob and reader.
for (size_t i = 0; i < kNumItems; i++) {
current_value += kItemSize;
FilePath path = kPathBase.Append(
FilePath::FromUTF8Unsafe(base::StringPrintf("%d", current_value)));
uint64_t offset = i % 3 == 0 ? 1 : 0;
uint64_t size =
i % 2 == 0 ? kItemSize : std::numeric_limits<uint64_t>::max();
b.AppendFile(path, offset, size, kTime);
}
this->InitializeReader(&b);
// Set expectations.
current_value = 0;
for (size_t i = 0; i < kNumItems; i++) {
uint64_t offset = i % 3 == 0 ? 1 : 0;
scoped_ptr<char[]> buf(new char[kItemSize + offset]);
if (offset > 0) {
memset(buf.get(), 7, offset);
}
for (size_t j = 0; j < kItemSize; j++) {
buf.get()[j + offset] = current_value++;
}
scoped_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(
std::string(buf.get() + offset, kItemSize), kItemSize + offset));
if (i % 4 != 0) {
reader->SetAsyncRunner(message_loop_.task_runner().get());
}
FilePath path = kPathBase.Append(
FilePath::FromUTF8Unsafe(base::StringPrintf("%d", current_value)));
ExpectLocalFileCall(path, kTime, offset, reader.release());
}
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeNotCalculatedYet(size_result);
message_loop_.RunUntilIdle();
CheckSizeCalculatedAsynchronously(kTotalSize, size_result);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
current_value = 0;
for (size_t i = 0; i < kTotalSize / kBufferSize; i++) {
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
message_loop_.RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(0, bytes_read);
EXPECT_EQ(kBufferSize, static_cast<size_t>(async_bytes_read));
for (size_t j = 0; j < kBufferSize; j++) {
EXPECT_EQ(current_value, buffer->data()[j]);
current_value++;
}
}
}
TEST_F(BlobReaderTest, MixedContent) {
// Includes data, a file, and a disk cache entry.
scoped_ptr<disk_cache::Backend> cache =
CreateInMemoryDiskCache(message_loop_.task_runner());
ASSERT_TRUE(cache);
BlobDataBuilder b("uuid");
const std::string kData1("Hello ");
const std::string kData2("there. ");
const std::string kData3("This ");
const std::string kData4("is multi-content.");
const uint64_t kDataSize = 35;
const base::Time kTime = base::Time::Now();
const FilePath kData1Path = FilePath::FromUTF8Unsafe("/fake/file.txt");
disk_cache::ScopedEntryPtr entry3 =
CreateDiskCacheEntry(cache.get(), "test entry", kData3);
b.AppendFile(kData1Path, 0, kData1.size(), kTime);
b.AppendData(kData2);
b.AppendDiskCacheEntry(
scoped_refptr<BlobDataBuilder::DataHandle>(new EmptyDataHandle()),
entry3.get(), kTestDiskCacheStreamIndex);
b.AppendData(kData4);
this->InitializeReader(&b);
scoped_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData1));
reader->SetAsyncRunner(message_loop_.task_runner().get());
ExpectLocalFileCall(kData1Path, kTime, 0, reader.release());
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
CheckSizeNotCalculatedYet(size_result);
message_loop_.RunUntilIdle();
CheckSizeCalculatedAsynchronously(kDataSize, size_result);
scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kDataSize);
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->Read(buffer.get(), kDataSize, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(0, async_bytes_read);
message_loop_.RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(0, bytes_read);
EXPECT_EQ(kDataSize, static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, memcmp(buffer->data(), "Hello there. This is multi-content.",
kDataSize));
}
TEST_F(BlobReaderTest, StateErrors) {
// Test common variables
int bytes_read = -1;
int async_bytes_read = -1;
int size_result = -1;
const std::string kData("Hello!!!");
// Case: Blob handle is a nullptr.
InitializeReader(nullptr);
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
EXPECT_EQ(BlobReader::Status::NET_ERROR, reader_->SetReadRange(0, 10));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
scoped_refptr<net::IOBuffer> buffer = CreateBuffer(10);
EXPECT_DEATH(reader_->Read(buffer.get(), 10, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)),
".*total_size_calculated_.*");
// Case: Not calling CalculateSize before SetReadRange.
BlobDataBuilder builder1("uuid1");
builder1.AppendData(kData);
InitializeReader(&builder1);
EXPECT_EQ(BlobReader::Status::NET_ERROR, reader_->SetReadRange(0, 10));
EXPECT_EQ(net::ERR_FAILED, reader_->net_error());
EXPECT_DEATH(reader_->Read(buffer.get(), 10, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)),
".*total_size_calculated_.*");
// Case: Not calling CalculateSize before Read.
BlobDataBuilder builder2("uuid2");
builder2.AppendData(kData);
InitializeReader(&builder2);
EXPECT_DEATH(reader_->Read(buffer.get(), 10, &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)),
".*total_size_calculated_.*");
}
TEST_F(BlobReaderTest, FileErrorsSync) {
int size_result = -1;
const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
// Case: Error on length query.
BlobDataBuilder builder1("uuid1");
builder1.AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(&builder1);
FakeFileStreamReader* reader = new FakeFileStreamReader(kData);
reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
ExpectLocalFileCall(kPath, kTime, 0, reader);
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
// Case: Error on read.
BlobDataBuilder builder2("uuid2");
builder2.AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(&builder2);
reader = new FakeFileStreamReader(kData);
ExpectLocalFileCall(kPath, kTime, 0, reader);
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
}
TEST_F(BlobReaderTest, FileErrorsAsync) {
int size_result = -1;
const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
// Case: Error on length query.
BlobDataBuilder builder1("uuid1");
builder1.AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(&builder1);
FakeFileStreamReader* reader = new FakeFileStreamReader(kData);
reader->SetAsyncRunner(message_loop_.task_runner().get());
reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
ExpectLocalFileCall(kPath, kTime, 0, reader);
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
EXPECT_EQ(net::OK, reader_->net_error());
message_loop_.RunUntilIdle();
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, size_result);
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
// Case: Error on read.
BlobDataBuilder builder2("uuid2");
builder2.AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(&builder2);
reader = new FakeFileStreamReader(kData);
ExpectLocalFileCall(kPath, kTime, 0, reader);
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
reader->SetAsyncRunner(message_loop_.task_runner().get());
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::IO_PENDING,
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::Bind(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
message_loop_.RunUntilIdle();
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, async_bytes_read);
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
}
TEST_F(BlobReaderTest, RangeError) {
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
const uint64_t kReadLength = 4ull;
// Case: offset too high.
BlobDataBuilder b("uuid1");
b.AppendData(kData);
this->InitializeReader(&b);
int size_result = -1;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kDataSize);
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->SetReadRange(kDataSize + 1, kReadLength));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
// Case: length too long.
BlobDataBuilder b2("uuid2");
b2.AppendData(kData);
this->InitializeReader(&b2);
size_result = -1;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
buffer = CreateBuffer(kDataSize + 1);
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->SetReadRange(0, kDataSize + 1));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
}
} // namespace
} // namespace storage