blob: b9b43c7453e502dc20af27d88ef3e1a3acf1fe7a [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// 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 <stddef.h>
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <array>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/run_loop.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "net/base/completion_once_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.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/file_system/file_stream_reader.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_file_util.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/test/async_file_test_helper.h"
#include "storage/browser/test/fake_blob_data_handle.h"
#include "storage/browser/test/test_file_system_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using base::FilePath;
using net::DrainableIOBuffer;
using net::IOBuffer;
namespace storage {
namespace {
using FileCreationInfo = BlobMemoryController::FileCreationInfo;
void SaveBlobStatusAndFiles(BlobStatus* status_ptr,
std::vector<FileCreationInfo>* files_ptr,
BlobStatus status,
std::vector<FileCreationInfo> files) {
*status_ptr = status;
for (FileCreationInfo& info : files) {
files_ptr->push_back(std::move(info));
}
}
template <typename T>
void SetValue(T* address, T value) {
*address = value;
}
class FakeFileStreamReader : public FileStreamReader {
public:
explicit FakeFileStreamReader(const std::string& contents)
: buffer_(base::MakeRefCounted<DrainableIOBuffer>(
base::MakeRefCounted<net::StringIOBuffer>(contents),
contents.size())),
net_error_(net::OK),
size_(contents.size()) {}
FakeFileStreamReader(const std::string& contents, uint64_t size)
: buffer_(base::MakeRefCounted<DrainableIOBuffer>(
base::MakeRefCounted<net::StringIOBuffer>(contents),
contents.size())),
net_error_(net::OK),
size_(size) {}
FakeFileStreamReader(const FakeFileStreamReader&) = delete;
FakeFileStreamReader& operator=(const FakeFileStreamReader&) = delete;
~FakeFileStreamReader() override = default;
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,
net::CompletionOnceCallback 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::CompletionOnceCallback());
} else {
return net_error_;
}
}
// Otherwise always return asynchronously.
if (net_error_ == net::OK) {
async_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&FakeFileStreamReader::ReadImpl),
base::Unretained(this), base::WrapRefCounted(buf),
buf_length, std::move(done)));
} else {
async_task_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(done), net_error_));
}
return net::ERR_IO_PENDING;
}
int64_t GetLength(net::Int64CompletionOnceCallback 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::BindOnce(std::move(size_callback), size_));
} else {
async_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(size_callback),
static_cast<int64_t>(net_error_)));
}
return net::ERR_IO_PENDING;
}
private:
int ReadImpl(scoped_refptr<net::IOBuffer> buf,
int buf_length,
net::CompletionOnceCallback done) {
CHECK_GE(buf_length, 0);
int length = std::min(buf_length, buffer_->BytesRemaining());
UNSAFE_TODO(memcpy(buf->data(), buffer_->data(), length));
buffer_->DidConsume(length);
if (done.is_null()) {
return length;
}
std::move(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_;
};
class MockFileStreamReaderProvider
: public BlobReader::FileStreamReaderProvider {
public:
~MockFileStreamReaderProvider() override = default;
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.
std::unique_ptr<FileStreamReader> CreateForLocalFile(
base::TaskRunner* task_runner,
const base::FilePath& file_path,
int64_t initial_offset,
const base::Time& expected_modification_time) override {
return base::WrapUnique(CreateForLocalFileMock(
task_runner, file_path, initial_offset, expected_modification_time));
}
std::unique_ptr<FileStreamReader> CreateFileStreamReader(
const GURL& filesystem_url,
int64_t offset,
int64_t max_bytes_to_read,
const base::Time& expected_modification_time) override {
return base::WrapUnique(CreateFileStreamReaderMock(
filesystem_url, offset, max_bytes_to_read, expected_modification_time));
}
};
} // namespace
class BlobReaderTest : public ::testing::Test {
public:
BlobReaderTest() = default;
BlobReaderTest(const BlobReaderTest&) = delete;
BlobReaderTest& operator=(const BlobReaderTest&) = delete;
~BlobReaderTest() override = default;
void SetUp() override {
file_system_context_ = CreateFileSystemContextForTesting(
/*quota_manager_proxy=*/nullptr, base::FilePath());
}
void TearDown() override {
provider_ = nullptr;
reader_.reset();
blob_handle_.reset();
base::RunLoop().RunUntilIdle();
base::RunLoop().RunUntilIdle();
}
protected:
void InitializeReader(std::unique_ptr<BlobDataBuilder> builder) {
blob_handle_ =
builder ? context_.AddFinishedBlob(std::move(builder)) : nullptr;
provider_ = new MockFileStreamReaderProvider();
reader_.reset(new BlobReader(blob_handle_.get()));
reader_->SetFileStreamProviderForTesting(base::WrapUnique(provider_.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(testing::_, 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 base::MakeRefCounted<net::IOBufferWithSize>(
static_cast<size_t>(size));
}
bool IsReaderTotalSizeCalculated() {
return reader_->total_size_calculated();
}
base::test::TaskEnvironment task_environment_;
BlobStorageContext context_;
std::unique_ptr<BlobDataHandle> blob_handle_;
std::unique_ptr<BlobReader> reader_;
raw_ptr<MockFileStreamReaderProvider> provider_ = nullptr;
scoped_refptr<FileSystemContext> file_system_context_;
};
TEST_F(BlobReaderTest, BasicMemory) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
b->AppendData(kData);
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
EXPECT_TRUE(reader_->IsInMemory());
CheckSizeCalculatedSynchronously(kDataSize, size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(kDataSize);
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kDataSize, &bytes_read,
base::BindOnce(&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);
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize)));
}
TEST_F(BlobReaderTest, BasicFile) {
auto b = std::make_unique<BlobDataBuilder>("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(std::move(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::BindOnce(
&SetValue<int>, &size_result)));
EXPECT_FALSE(reader_->IsInMemory());
CheckSizeCalculatedSynchronously(kData.size(), size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&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);
UNSAFE_TODO(
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())));
}
TEST_F(BlobReaderTest, BasicFileSystem) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const GURL kURL("filesystem:http://example.com/temporary/test_file/here.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
b->AppendFileSystemFile(
file_system_context_->CrackURLInFirstPartyContext(kURL), 0, kData.size(),
kTime, file_system_context_);
this->InitializeReader(std::move(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::BindOnce(
&SetValue<int>, &size_result)));
EXPECT_FALSE(reader_->IsInMemory());
CheckSizeCalculatedSynchronously(kData.size(), size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&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);
UNSAFE_TODO(
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())));
}
TEST_F(BlobReaderTest, BasicReadableDataHandle) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kData = "Test Blob Data";
auto data_handle = base::MakeRefCounted<FakeBlobDataHandle>(kData, "");
b->AppendReadableDataHandle(std::move(data_handle));
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
EXPECT_FALSE(reader_->has_side_data());
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&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);
UNSAFE_TODO(
EXPECT_EQ(0, memcmp(buffer->data(), "Test Blob Data", kData.size())));
}
TEST_F(BlobReaderTest, ReadableDataHandleWithSideData) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kData = "Test Blob Data";
const std::string kSideData = "Test side data";
auto data_handle = base::MakeRefCounted<FakeBlobDataHandle>(kData, kSideData);
b->AppendReadableDataHandle(std::move(data_handle));
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
EXPECT_TRUE(reader_->has_side_data());
BlobReader::Status status = BlobReader::Status::NET_ERROR;
// As this is using a FakeBlobDataHandle, expect this to be synchronous.
reader_->ReadSideData(base::BindOnce(&SetValue<BlobReader::Status>, &status));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(BlobReader::Status::DONE, status);
auto side_data = reader_->TakeSideData();
EXPECT_TRUE(side_data.has_value());
std::string result(reinterpret_cast<const char*>(side_data->data()),
side_data->size());
EXPECT_EQ(kSideData, result);
}
TEST_F(BlobReaderTest, BufferLargerThanMemory) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
const size_t kBufferSize = 10ul;
b->AppendData(kData);
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(kBufferSize);
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::BindOnce(&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);
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize)));
}
TEST_F(BlobReaderTest, MemoryRange) {
auto b = std::make_unique<BlobDataBuilder>("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(std::move(b));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&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::BindOnce(&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);
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), "llo!", kReadLength)));
}
TEST_F(BlobReaderTest, BufferSmallerThanMemory) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kData("Hello!!!");
const size_t kBufferSize = 4ul;
b->AppendData(kData);
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(kBufferSize);
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::BindOnce(&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);
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), "Hell", kBufferSize)));
bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kBufferSize, &bytes_read,
base::BindOnce(&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);
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), "o!!!", kBufferSize)));
}
TEST_F(BlobReaderTest, SegmentedBufferAndMemory) {
auto b = std::make_unique<BlobDataBuilder>("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++) {
std::array<char, kItemSize> buf;
for (size_t j = 0; j < kItemSize; j++) {
buf[j] = current_value++;
}
b->AppendData(std::string(buf.data(), kItemSize));
}
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kTotalSize, size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&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++) {
UNSAFE_TODO(EXPECT_EQ(current_value, buffer->data()[j]));
current_value++;
}
}
}
TEST_F(BlobReaderTest, FileAsync) {
auto b = std::make_unique<BlobDataBuilder>("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(std::move(b));
std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().get());
ExpectLocalFileCall(kPath, kTime, 0, reader.release());
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(
BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::BindOnce(&SetValue<int>, &size_result)));
EXPECT_FALSE(reader_->IsInMemory());
CheckSizeNotCalculatedYet(size_result);
base::RunLoop().RunUntilIdle();
CheckSizeCalculatedAsynchronously(kData.size(), size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&SetValue<int>, &async_bytes_read)));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, bytes_read);
UNSAFE_TODO(
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())));
}
TEST_F(BlobReaderTest, FileSystemAsync) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const GURL kURL("filesystem:http://example.com/temporary/test_file/here.txt");
const std::string kData = "FileData!!!";
const base::Time kTime = base::Time::Now();
b->AppendFileSystemFile(
file_system_context_->CrackURLInFirstPartyContext(kURL), 0, kData.size(),
kTime, file_system_context_);
this->InitializeReader(std::move(b));
std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().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::BindOnce(&SetValue<int>, &size_result)));
CheckSizeNotCalculatedYet(size_result);
EXPECT_FALSE(reader_->IsInMemory());
base::RunLoop().RunUntilIdle();
CheckSizeCalculatedAsynchronously(kData.size(), size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&SetValue<int>, &async_bytes_read)));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, bytes_read);
UNSAFE_TODO(
EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())));
}
TEST_F(BlobReaderTest, ReadableDataHandleSingle) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kOrigData = "12345 Test Blob Data 12345";
auto data_handle = base::MakeRefCounted<FakeBlobDataHandle>(kOrigData, "");
b->AppendReadableDataHandle(data_handle, 6, 14);
this->InitializeReader(std::move(b));
const std::string kData = kOrigData.substr(6, 14);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
// This test checks the optimized single mojo data item path, where the
// data pipe passed in gets passed directly to the MojoDataItem.
EXPECT_TRUE(reader_->IsSingleMojoDataItem());
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
MojoResult pipe_result = mojo::CreateDataPipe(nullptr, producer, consumer);
ASSERT_EQ(MOJO_RESULT_OK, pipe_result);
std::optional<int> read_result;
base::RunLoop read_loop;
reader_->ReadSingleMojoDataItem(std::move(producer),
base::BindLambdaForTesting([&](int result) {
read_result = result;
read_loop.Quit();
}));
read_loop.Run();
ASSERT_TRUE(read_result.has_value());
EXPECT_EQ(*read_result, net::OK);
size_t num_bytes = kData.size();
std::vector<uint8_t> buffer(num_bytes);
MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_ALL_OR_NONE;
EXPECT_EQ(MOJO_RESULT_OK, consumer->ReadData(flags, buffer, num_bytes));
EXPECT_EQ(base::as_string_view(base::as_byte_span(buffer)), kData);
}
// This test is the same as ReadableDataHandleSingle, but adds the
// additional wrinkle of a SetReadRange call.
TEST_F(BlobReaderTest, ReadableDataHandleSingleRange) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kOrigData = "12345 Test Blob Data 12345";
auto data_handle = base::MakeRefCounted<FakeBlobDataHandle>(kOrigData, "");
b->AppendReadableDataHandle(data_handle, 6, 14);
this->InitializeReader(std::move(b));
const std::string kData = kOrigData.substr(6, 14);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
// This test checks the optimized single mojo data item path, where the
// data pipe passed in gets passed directly to the MojoDataItem.
EXPECT_TRUE(reader_->IsSingleMojoDataItem());
uint64_t range_start = 3;
uint64_t range_length = 6;
reader_->SetReadRange(range_start, range_length);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
MojoResult pipe_result = mojo::CreateDataPipe(nullptr, producer, consumer);
ASSERT_EQ(MOJO_RESULT_OK, pipe_result);
std::optional<int> read_result;
base::RunLoop read_loop;
reader_->ReadSingleMojoDataItem(std::move(producer),
base::BindLambdaForTesting([&](int result) {
read_result = result;
read_loop.Quit();
}));
read_loop.Run();
ASSERT_TRUE(read_result.has_value());
EXPECT_EQ(*read_result, net::OK);
size_t num_bytes = range_length;
std::vector<uint8_t> buffer(num_bytes);
MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_ALL_OR_NONE;
EXPECT_EQ(MOJO_RESULT_OK, consumer->ReadData(flags, buffer, num_bytes));
EXPECT_EQ(
base::as_string_view(base::as_byte_span(buffer)).substr(0, num_bytes),
kData.substr(range_start, range_length));
}
TEST_F(BlobReaderTest, ReadableDataHandleMultipleSlices) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kData1 = "Test Blob Data";
const std::string kData2 = "Extra test blob data";
// Create readable data handles with various slices of data.
// It's important to test that both the first element we read and a
// non-first element that we read handle slices, as these touch different
// pieces of code.
b->AppendReadableDataHandle(
base::MakeRefCounted<FakeBlobDataHandle>(kData1, ""), 5, 4);
b->AppendReadableDataHandle(
base::MakeRefCounted<FakeBlobDataHandle>(kData2, ""), 6, 9);
this->InitializeReader(std::move(b));
std::string kData = kData1.substr(5, 4) + kData2.substr(6, 9);
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
CheckSizeCalculatedSynchronously(kData.size(), size_result);
// Verify this condition while we are here.
EXPECT_FALSE(reader_->IsSingleMojoDataItem());
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(kData.size());
int bytes_read = 0;
int async_bytes_read = 0;
BlobReader::Status result =
reader_->Read(buffer.get(), kData.size(), &bytes_read,
base::BindOnce(&SetValue<int>, &async_bytes_read));
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(result, BlobReader::Status::DONE);
EXPECT_EQ(0, async_bytes_read);
EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read));
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), kData.data(), kData.size())));
}
TEST_F(BlobReaderTest, FileRange) {
auto b = std::make_unique<BlobDataBuilder>("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(std::move(b));
std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().get());
ExpectLocalFileCall(kPath, kTime, 0, reader.release());
// We create the reader again with the offset after the seek.
reader = std::make_unique<FakeFileStreamReader>(kRangeData);
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().get());
ExpectLocalFileCall(kPath, kTime, kOffset, reader.release());
int size_result = -1;
EXPECT_EQ(
BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::BindOnce(&SetValue<int>, &size_result)));
base::RunLoop().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::BindOnce(&SetValue<int>, &async_bytes_read)));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(kReadLength, static_cast<size_t>(async_bytes_read));
EXPECT_EQ(0, bytes_read);
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), "leD", kReadLength)));
}
TEST_F(BlobReaderTest, ReadableDataHandleRange) {
auto b = std::make_unique<BlobDataBuilder>("uuid");
const std::string kData = "Test Blob Data";
const uint64_t kOffset = 2;
const uint64_t kReadLength = 3;
auto data_handle = base::MakeRefCounted<FakeBlobDataHandle>(kData, "");
b->AppendReadableDataHandle(std::move(data_handle));
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&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::BindOnce(&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);
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer->data(), "st ", kReadLength)));
}
TEST_F(BlobReaderTest, FileSomeAsyncSegmentedOffsetsUnknownSizes) {
// This tests includes:
// * Unknown file sizes (item length of uint64_t::max) for every other item.
// * Offsets for every 3rd file item.
// * Non-async reader for every 4th file item.
auto b = std::make_unique<BlobDataBuilder>("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 = kItemSize;
b->AppendFile(path, offset, size, kTime);
}
this->InitializeReader(std::move(b));
// Set expectations.
current_value = 0;
for (size_t i = 0; i < kNumItems; i++) {
uint64_t offset = i % 3 == 0 ? 1 : 0;
auto buf = base::HeapArray<char>::Uninit(kItemSize + offset);
if (offset > 0) {
UNSAFE_TODO(memset(buf.data(), 7, offset));
}
for (size_t j = 0; j < kItemSize; j++) {
UNSAFE_TODO(buf.data()[j + offset]) = current_value++;
}
std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(
std::string(UNSAFE_TODO(buf.data() + offset), kItemSize), buf.size()));
if (i % 4 != 0) {
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().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::BindOnce(&SetValue<int>, &size_result)));
CheckSizeNotCalculatedYet(size_result);
base::RunLoop().RunUntilIdle();
CheckSizeCalculatedAsynchronously(kTotalSize, size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&SetValue<int>, &async_bytes_read)));
base::RunLoop().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++) {
UNSAFE_TODO(EXPECT_EQ(current_value, buffer->data()[j]));
current_value++;
}
}
}
TEST_F(BlobReaderTest, MixedContent) {
// Includes data, a file, and a data handle entry.
auto b = std::make_unique<BlobDataBuilder>("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");
auto data_handle = base::MakeRefCounted<FakeBlobDataHandle>(kData3, "");
b->AppendFile(kData1Path, 0, kData1.size(), kTime);
b->AppendData(kData2);
b->AppendReadableDataHandle(std::move(data_handle));
b->AppendData(kData4);
this->InitializeReader(std::move(b));
std::unique_ptr<FakeFileStreamReader> reader(
new FakeFileStreamReader(kData1));
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().get());
ExpectLocalFileCall(kData1Path, kTime, 0, reader.release());
int size_result = -1;
EXPECT_FALSE(IsReaderTotalSizeCalculated());
EXPECT_EQ(
BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::BindOnce(&SetValue<int>, &size_result)));
CheckSizeNotCalculatedYet(size_result);
base::RunLoop().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::BindOnce(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(0, async_bytes_read);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(net::OK, reader_->net_error());
EXPECT_EQ(0, bytes_read);
EXPECT_EQ(kDataSize, static_cast<size_t>(async_bytes_read));
UNSAFE_TODO(
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::BindOnce(&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_EQ(BlobReader::Status::NET_ERROR,
reader_->Read(buffer.get(), 10, &bytes_read,
base::BindOnce(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
// Case: Not calling CalculateSize before SetReadRange.
auto builder1 = std::make_unique<BlobDataBuilder>("uuid1");
builder1->AppendData(kData);
InitializeReader(std::move(builder1));
EXPECT_EQ(BlobReader::Status::NET_ERROR, reader_->SetReadRange(0, 10));
EXPECT_EQ(net::ERR_UNEXPECTED, reader_->net_error());
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->Read(buffer.get(), 10, &bytes_read,
base::BindOnce(&SetValue<int>, &async_bytes_read)));
// Case: Not calling CalculateSize before Read.
auto builder2 = std::make_unique<BlobDataBuilder>("uuid2");
builder2->AppendData(kData);
InitializeReader(std::move(builder2));
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->Read(buffer.get(), 10, &bytes_read,
base::BindOnce(&SetValue<int>, &async_bytes_read)));
}
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.
auto builder1 = std::make_unique<BlobDataBuilder>("uuid1");
builder1->AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(std::move(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::BindOnce(&SetValue<int>, &size_result)));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
// Case: Error on read.
auto builder2 = std::make_unique<BlobDataBuilder>("uuid2");
builder2->AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(std::move(builder2));
reader = new FakeFileStreamReader(kData);
ExpectLocalFileCall(kPath, kTime, 0, reader);
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&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.
auto builder1 = std::make_unique<BlobDataBuilder>("uuid1");
builder1->AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(std::move(builder1));
FakeFileStreamReader* reader = new FakeFileStreamReader(kData);
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().get());
reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
ExpectLocalFileCall(kPath, kTime, 0, reader);
EXPECT_EQ(
BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::BindOnce(&SetValue<int>, &size_result)));
EXPECT_EQ(net::OK, reader_->net_error());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, size_result);
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
// Case: Error on read.
auto builder2 = std::make_unique<BlobDataBuilder>("uuid2");
builder2->AppendFile(kPath, 0, kData.size(), kTime);
this->InitializeReader(std::move(builder2));
reader = new FakeFileStreamReader(kData);
ExpectLocalFileCall(kPath, kTime, 0, reader);
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
reader->SetAsyncRunner(
base::SingleThreadTaskRunner::GetCurrentDefault().get());
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(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::BindOnce(&SetValue<int>, &async_bytes_read)));
EXPECT_EQ(net::OK, reader_->net_error());
base::RunLoop().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.
auto b = std::make_unique<BlobDataBuilder>("uuid1");
b->AppendData(kData);
this->InitializeReader(std::move(b));
int size_result = -1;
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&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_REQUEST_RANGE_NOT_SATISFIABLE, reader_->net_error());
// Case: length too long.
auto b2 = std::make_unique<BlobDataBuilder>("uuid2");
b2->AppendData(kData);
this->InitializeReader(std::move(b2));
size_result = -1;
EXPECT_EQ(BlobReader::Status::DONE, reader_->CalculateSize(base::BindOnce(
&SetValue<int>, &size_result)));
buffer = CreateBuffer(kDataSize + 1);
EXPECT_EQ(BlobReader::Status::NET_ERROR,
reader_->SetReadRange(0, kDataSize + 1));
EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, reader_->net_error());
}
TEST_F(BlobReaderTest, HandleBeforeAsyncCancel) {
const std::string kUuid("uuid1");
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
std::vector<FileCreationInfo> files;
auto b = std::make_unique<BlobDataBuilder>(kUuid);
b->AppendFutureData(kDataSize);
BlobStatus can_populate_status =
BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
blob_handle_ = context_.BuildBlob(
std::move(b),
base::BindOnce(&SaveBlobStatusAndFiles, &can_populate_status, &files));
EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, can_populate_status);
provider_ = new MockFileStreamReaderProvider();
reader_.reset(new BlobReader(blob_handle_.get()));
reader_->SetFileStreamProviderForTesting(base::WrapUnique(provider_.get()));
int size_result = -1;
EXPECT_EQ(
BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::BindOnce(&SetValue<int>, &size_result)));
EXPECT_FALSE(reader_->IsInMemory());
context_.CancelBuildingBlob(kUuid,
BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(net::ERR_BLOB_INVALID_CONSTRUCTION_ARGUMENTS, size_result);
}
TEST_F(BlobReaderTest, ReadFromIncompleteBlob) {
const std::string kUuid("uuid1");
const std::string kData("Hello!!!");
const size_t kDataSize = 8ul;
std::vector<FileCreationInfo> files;
auto b = std::make_unique<BlobDataBuilder>(kUuid);
BlobDataBuilder::FutureData future_data = b->AppendFutureData(kDataSize);
BlobStatus can_populate_status =
BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
blob_handle_ = context_.BuildBlob(
std::move(b),
base::BindOnce(&SaveBlobStatusAndFiles, &can_populate_status, &files));
EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, can_populate_status);
provider_ = new MockFileStreamReaderProvider();
reader_.reset(new BlobReader(blob_handle_.get()));
reader_->SetFileStreamProviderForTesting(base::WrapUnique(provider_.get()));
int size_result = -1;
EXPECT_EQ(
BlobReader::Status::IO_PENDING,
reader_->CalculateSize(base::BindOnce(&SetValue<int>, &size_result)));
EXPECT_FALSE(reader_->IsInMemory());
future_data.Populate(
base::as_bytes(UNSAFE_TODO(base::span(kData.data(), kDataSize))), 0);
context_.NotifyTransportComplete(kUuid);
base::RunLoop().RunUntilIdle();
CheckSizeCalculatedAsynchronously(kDataSize, size_result);
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(kDataSize);
int bytes_read = 0;
int async_bytes_read = 0;
EXPECT_EQ(BlobReader::Status::DONE,
reader_->Read(buffer.get(), kDataSize, &bytes_read,
base::BindOnce(&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(kData, std::string(buffer->data(), kDataSize));
EXPECT_EQ(net::OK, size_result);
}
} // namespace storage