Commit b029a29f authored by Adrienne Walker's avatar Adrienne Walker Committed by Commit Bot

Combine file stream reader unittests

These are all separate but different unittests that have grown
organically apart from each other, combine into a single typed test
so that future file stream reader unittests can reuse this structure.

The one unfortunate part about doing it this way is that it requires a
lot more `this->` everywhere because of C++ templates.  However,
the alternative of having a test fixture with a bunch of switch
statements for the different types seems strictly worse.

Change-Id: Id56bc28391e1a640748691cb383a19291c47fc51
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2406718
Commit-Queue: Nick Harper <nharper@chromium.org>
Auto-Submit: enne <enne@chromium.org>
Reviewed-by: default avatarNick Harper <nharper@chromium.org>
Reviewed-by: default avatarMarijn Kruisselbrink <mek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#807092}
parent 064cb5ea
...@@ -113,6 +113,11 @@ void FileStream::Context::Seek(int64_t offset, ...@@ -113,6 +113,11 @@ void FileStream::Context::Seek(int64_t offset,
Int64CompletionOnceCallback callback) { Int64CompletionOnceCallback callback) {
DCHECK(!async_in_progress_); DCHECK(!async_in_progress_);
if (offset < 0) {
std::move(callback).Run(net::ERR_INVALID_ARGUMENT);
return;
}
bool posted = base::PostTaskAndReplyWithResult( bool posted = base::PostTaskAndReplyWithResult(
task_runner_.get(), FROM_HERE, task_runner_.get(), FROM_HERE,
base::BindOnce(&Context::SeekFileImpl, base::Unretained(this), offset), base::BindOnce(&Context::SeekFileImpl, base::Unretained(this), offset),
......
...@@ -275,6 +275,8 @@ source_set("unittests") { ...@@ -275,6 +275,8 @@ source_set("unittests") {
"file_system/copy_or_move_operation_delegate_unittest.cc", "file_system/copy_or_move_operation_delegate_unittest.cc",
"file_system/dragged_file_util_unittest.cc", "file_system/dragged_file_util_unittest.cc",
"file_system/external_mount_points_unittest.cc", "file_system/external_mount_points_unittest.cc",
"file_system/file_stream_reader_test.cc",
"file_system/file_stream_reader_test.h",
"file_system/file_stream_test_utils.cc", "file_system/file_stream_test_utils.cc",
"file_system/file_stream_test_utils.h", "file_system/file_stream_test_utils.h",
"file_system/file_system_context_unittest.cc", "file_system/file_system_context_unittest.cc",
......
// Copyright 2020 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/file_system/file_stream_reader_test.h"
namespace storage {
const base::StringPiece FileStreamReaderTest::kTestFileName;
const base::StringPiece FileStreamReaderTest::kTestData;
} // namespace storage
// Copyright 2020 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.
#ifndef STORAGE_BROWSER_FILE_SYSTEM_FILE_STREAM_READER_TEST_H_
#define STORAGE_BROWSER_FILE_SYSTEM_FILE_STREAM_READER_TEST_H_
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "storage/browser/file_system/file_stream_reader.h"
#include "storage/browser/file_system/file_stream_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace storage {
// An interface for derived FileStreamReader to implement.
// This allows multiple FileStreamReader implementations can share the
// same test framework. Tests should implement CreateFileReader, WriteFile, and
// TouchFile to manipulate files for their particular implementation.
class FileStreamReaderTest : public testing::Test {
public:
static constexpr base::StringPiece kTestFileName = "test.dat";
static constexpr base::StringPiece kTestData = "0123456789";
virtual std::unique_ptr<FileStreamReader> CreateFileReader(
const std::string& file_name,
int64_t initial_offset,
const base::Time& expected_modification_time) = 0;
virtual void WriteFile(const std::string& file_name,
const char* buf,
size_t buf_size,
base::Time* modification_time) = 0;
// Adjust a file's last modified time by |delta|.
virtual void TouchFile(const std::string& file_name,
base::TimeDelta delta) = 0;
virtual void EnsureFileTaskFinished() {}
base::Time test_file_modification_time() const {
return test_file_modification_time_;
}
void WriteTestFile() {
WriteFile(kTestFileName.data(), kTestData.data(), kTestData.size(),
&test_file_modification_time_);
}
static void NeverCalled(int unused) { ADD_FAILURE(); }
private:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
base::Time test_file_modification_time_;
};
template <class SubClass>
class FileStreamReaderTypedTest : public SubClass {
public:
void SetUp() override {
SubClass::SetUp();
this->WriteTestFile();
}
};
TYPED_TEST_SUITE_P(FileStreamReaderTypedTest);
TYPED_TEST_P(FileStreamReaderTypedTest, NonExistent) {
const char kFileName[] = "nonexistent";
std::unique_ptr<FileStreamReader> reader(
this->CreateFileReader(kFileName, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result);
ASSERT_EQ(0U, data.size());
}
TYPED_TEST_P(FileStreamReaderTypedTest, Empty) {
const char kFileName[] = "empty";
this->WriteFile(kFileName, nullptr, 0, nullptr);
std::unique_ptr<FileStreamReader> reader(
this->CreateFileReader(kFileName, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(0U, data.size());
net::TestInt64CompletionCallback callback;
int64_t length_result = reader->GetLength(callback.callback());
if (length_result == net::ERR_IO_PENDING)
length_result = callback.WaitForResult();
ASSERT_EQ(0, result);
}
TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthNormal) {
std::unique_ptr<FileStreamReader> reader(this->CreateFileReader(
this->kTestFileName.data(), 0, this->test_file_modification_time()));
net::TestInt64CompletionCallback callback;
int64_t result = reader->GetLength(callback.callback());
if (result == net::ERR_IO_PENDING)
result = callback.WaitForResult();
ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result);
}
TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthAfterModified) {
this->TouchFile(this->kTestFileName.data(), base::TimeDelta::FromSeconds(10));
std::unique_ptr<FileStreamReader> reader(this->CreateFileReader(
this->kTestFileName.data(), 0, this->test_file_modification_time()));
net::TestInt64CompletionCallback callback1;
int64_t result = reader->GetLength(callback1.callback());
if (result == net::ERR_IO_PENDING)
result = callback1.WaitForResult();
ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
// With nullptr expected modification time this should work.
reader = this->CreateFileReader(this->kTestFileName.data(), 0, base::Time());
net::TestInt64CompletionCallback callback2;
result = reader->GetLength(callback2.callback());
if (result == net::ERR_IO_PENDING)
result = callback2.WaitForResult();
ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result);
}
TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthWithOffset) {
std::unique_ptr<FileStreamReader> reader(
this->CreateFileReader(this->kTestFileName.data(), 3, base::Time()));
net::TestInt64CompletionCallback callback;
int64_t result = reader->GetLength(callback.callback());
if (result == net::ERR_IO_PENDING)
result = callback.WaitForResult();
// Initial offset does not affect the result of GetLength.
ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result);
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadNormal) {
std::unique_ptr<FileStreamReader> reader(this->CreateFileReader(
this->kTestFileName.data(), 0, this->test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, this->kTestData.size(), &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(this->kTestData, data);
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModified) {
// Touch file so that the file's modification time becomes different
// from what we expect. Note that the resolution on some filesystems
// is 1s so we can't test with deltas less than that.
this->TouchFile(this->kTestFileName.data(), base::TimeDelta::FromSeconds(-1));
std::unique_ptr<FileStreamReader> reader(this->CreateFileReader(
this->kTestFileName.data(), 0, this->test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, this->kTestData.size(), &result);
ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
ASSERT_EQ(0U, data.size());
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModifiedLessThanThreshold) {
// Due to precision loss converting int64_t->double->int64_t (e.g. through
// Blink) the expected/actual time may vary by microseconds. With
// modification time delta < 10us this should work.
this->TouchFile(this->kTestFileName.data(),
base::TimeDelta::FromMicroseconds(1));
std::unique_ptr<FileStreamReader> reader(this->CreateFileReader(
this->kTestFileName.data(), 0, this->test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, this->kTestData.size(), &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(this->kTestData, data);
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModifiedWithMatchingTimes) {
this->TouchFile(this->kTestFileName.data(), base::TimeDelta());
std::unique_ptr<FileStreamReader> reader(this->CreateFileReader(
this->kTestFileName.data(), 0, this->test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, this->kTestData.size(), &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(this->kTestData, data);
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModifiedWithoutExpectedTime) {
this->TouchFile(this->kTestFileName.data(), base::TimeDelta::FromSeconds(-1));
std::unique_ptr<FileStreamReader> reader(
this->CreateFileReader(this->kTestFileName.data(), 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, this->kTestData.size(), &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(this->kTestData, data);
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadWithOffset) {
std::unique_ptr<FileStreamReader> reader(
this->CreateFileReader(this->kTestFileName.data(), 3, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, this->kTestData.size(), &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(this->kTestData.substr(3), data);
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadWithNegativeOffset) {
std::unique_ptr<FileStreamReader> reader(
this->CreateFileReader(this->kTestFileName.data(), -1, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 1, &result);
ASSERT_EQ(net::ERR_INVALID_ARGUMENT, result);
ASSERT_EQ(data.size(), 0u);
}
TYPED_TEST_P(FileStreamReaderTypedTest, ReadWithOffsetLargerThanFile) {
std::unique_ptr<FileStreamReader> reader(this->CreateFileReader(
this->kTestFileName.data(), this->kTestData.size() + 1, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 1, &result);
ASSERT_EQ(data.size(), 0u);
ASSERT_EQ(net::OK, result);
}
TYPED_TEST_P(FileStreamReaderTypedTest, DeleteWithUnfinishedRead) {
std::unique_ptr<FileStreamReader> reader(
this->CreateFileReader(this->kTestFileName.data(), 0, base::Time()));
net::TestCompletionCallback callback;
scoped_refptr<net::IOBufferWithSize> buf =
base::MakeRefCounted<net::IOBufferWithSize>(this->kTestData.size());
int rv = reader->Read(buf.get(), buf->size(),
base::BindOnce(&FileStreamReaderTest::NeverCalled));
if (rv < 0)
ASSERT_EQ(rv, net::ERR_IO_PENDING);
// Delete immediately.
// Should not crash; nor should NeverCalled be callback.
reader = nullptr;
this->EnsureFileTaskFinished();
}
REGISTER_TYPED_TEST_SUITE_P(FileStreamReaderTypedTest,
NonExistent,
Empty,
GetLengthNormal,
GetLengthAfterModified,
GetLengthWithOffset,
ReadNormal,
ReadAfterModified,
ReadAfterModifiedLessThanThreshold,
ReadAfterModifiedWithMatchingTimes,
ReadAfterModifiedWithoutExpectedTime,
ReadWithOffset,
ReadWithNegativeOffset,
ReadWithOffsetLargerThanFile,
DeleteWithUnfinishedRead);
} // namespace storage
#endif // STORAGE_BROWSER_FILE_SYSTEM_FILE_STREAM_READER_TEST_H_
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h" #include "net/base/test_completion_callback.h"
#include "storage/browser/file_system/external_mount_points.h" #include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_stream_reader_test.h"
#include "storage/browser/file_system/file_stream_test_utils.h" #include "storage/browser/file_system/file_stream_test_utils.h"
#include "storage/browser/file_system/file_system_context.h" #include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_file_util.h" #include "storage/browser/file_system/file_system_file_util.h"
...@@ -33,58 +35,44 @@ ...@@ -33,58 +35,44 @@
namespace storage { namespace storage {
namespace { namespace {
const char kURLOrigin[] = "http://remote/"; const char kURLOrigin[] = "http://remote/";
const char kTestFileName[] = "test.dat";
const char kTestData[] = "0123456789";
const int kTestDataSize = base::size(kTestData) - 1;
void NeverCalled(int unused) {
ADD_FAILURE();
}
} // namespace } // namespace
class FileSystemFileStreamReaderTest : public testing::Test { class FileSystemFileStreamReaderTest : public FileStreamReaderTest {
public: public:
FileSystemFileStreamReaderTest() = default; FileSystemFileStreamReaderTest() = default;
void SetUp() override { void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE(dir_.CreateUniqueTempDir());
file_system_context_ = file_system_context_ =
CreateFileSystemContextForTesting(nullptr, temp_dir_.GetPath()); CreateFileSystemContextForTesting(nullptr, dir_.GetPath());
file_system_context_->OpenFileSystem(url::Origin::Create(GURL(kURLOrigin)), file_system_context_->OpenFileSystem(
kFileSystemTypeTemporary, url::Origin::Create(GURL(kURLOrigin)), kFileSystemTypeTemporary,
OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
base::BindOnce(&OnOpenFileSystem)); base::BindOnce([](const GURL& root_url, const std::string& name,
base::File::Error result) {
ASSERT_EQ(base::File::FILE_OK, result);
}));
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
WriteFile(kTestFileName, kTestData, kTestDataSize,
&test_file_modification_time_);
} }
void TearDown() override { base::RunLoop().RunUntilIdle(); } void TearDown() override { base::RunLoop().RunUntilIdle(); }
protected: std::unique_ptr<FileStreamReader> CreateFileReader(
FileSystemFileStreamReader* CreateFileReader(
const std::string& file_name, const std::string& file_name,
int64_t initial_offset, int64_t initial_offset,
const base::Time& expected_modification_time) { const base::Time& expected_modification_time) override {
return new FileSystemFileStreamReader( return FileStreamReader::CreateForFileSystemFile(
file_system_context_.get(), GetFileSystemURL(file_name), initial_offset, file_system_context_.get(), GetFileSystemURL(file_name), initial_offset,
expected_modification_time); expected_modification_time);
} }
base::Time test_file_modification_time() const {
return test_file_modification_time_;
}
void WriteFile(const std::string& file_name, void WriteFile(const std::string& file_name,
const char* buf, const char* buf,
int buf_size, size_t buf_size,
base::Time* modification_time) { base::Time* modification_time) override {
FileSystemURL url = GetFileSystemURL(file_name); FileSystemURL url = GetFileSystemURL(file_name);
ASSERT_EQ(base::File::FILE_OK, ASSERT_EQ(base::File::FILE_OK,
...@@ -99,11 +87,17 @@ class FileSystemFileStreamReaderTest : public testing::Test { ...@@ -99,11 +87,17 @@ class FileSystemFileStreamReaderTest : public testing::Test {
*modification_time = file_info.last_modified; *modification_time = file_info.last_modified;
} }
private: void TouchFile(const std::string& file_name, base::TimeDelta delta) override {
static void OnOpenFileSystem(const GURL& root_url, FileSystemURL url = GetFileSystemURL(file_name);
const std::string& name,
base::File::Error result) { base::File::Info file_info;
ASSERT_EQ(base::File::FILE_OK, result); ASSERT_EQ(base::File::FILE_OK,
AsyncFileTestHelper::GetMetadata(file_system_context_.get(), url,
&file_info));
ASSERT_EQ(base::File::FILE_OK,
AsyncFileTestHelper::TouchFile(file_system_context_.get(), url,
file_info.last_accessed,
file_info.last_modified + delta));
} }
FileSystemURL GetFileSystemURL(const std::string& file_name) { FileSystemURL GetFileSystemURL(const std::string& file_name) {
...@@ -112,140 +106,13 @@ class FileSystemFileStreamReaderTest : public testing::Test { ...@@ -112,140 +106,13 @@ class FileSystemFileStreamReaderTest : public testing::Test {
base::FilePath().AppendASCII(file_name)); base::FilePath().AppendASCII(file_name));
} }
base::test::SingleThreadTaskEnvironment task_environment_{ private:
base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; base::ScopedTempDir dir_;
base::ScopedTempDir temp_dir_;
scoped_refptr<FileSystemContext> file_system_context_; scoped_refptr<FileSystemContext> file_system_context_;
base::Time test_file_modification_time_;
}; };
TEST_F(FileSystemFileStreamReaderTest, NonExistent) { INSTANTIATE_TYPED_TEST_SUITE_P(FileSystem,
const char kFileName[] = "nonexistent"; FileStreamReaderTypedTest,
std::unique_ptr<FileSystemFileStreamReader> reader( FileSystemFileStreamReaderTest);
CreateFileReader(kFileName, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result);
ASSERT_EQ(0U, data.size());
}
TEST_F(FileSystemFileStreamReaderTest, Empty) {
const char kFileName[] = "empty";
WriteFile(kFileName, nullptr, 0, nullptr);
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kFileName, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(0U, data.size());
net::TestInt64CompletionCallback callback;
int64_t length_result = reader->GetLength(callback.callback());
if (length_result == net::ERR_IO_PENDING)
length_result = callback.WaitForResult();
ASSERT_EQ(0, length_result);
}
TEST_F(FileSystemFileStreamReaderTest, GetLengthNormal) {
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kTestFileName, 0, test_file_modification_time()));
net::TestInt64CompletionCallback callback;
int64_t result = reader->GetLength(callback.callback());
if (result == net::ERR_IO_PENDING)
result = callback.WaitForResult();
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(FileSystemFileStreamReaderTest, GetLengthAfterModified) {
// Pass a fake expected modifictaion time so that the expectation fails.
base::Time fake_expected_modification_time =
test_file_modification_time() - base::TimeDelta::FromSeconds(10);
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kTestFileName, 0, fake_expected_modification_time));
net::TestInt64CompletionCallback callback1;
int64_t result = reader->GetLength(callback1.callback());
if (result == net::ERR_IO_PENDING)
result = callback1.WaitForResult();
ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
// With nullptr expected modification time this should work.
reader.reset(CreateFileReader(kTestFileName, 0, base::Time()));
net::TestInt64CompletionCallback callback2;
result = reader->GetLength(callback2.callback());
if (result == net::ERR_IO_PENDING)
result = callback2.WaitForResult();
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(FileSystemFileStreamReaderTest, GetLengthWithOffset) {
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kTestFileName, 3, base::Time()));
net::TestInt64CompletionCallback callback;
int64_t result = reader->GetLength(callback.callback());
if (result == net::ERR_IO_PENDING)
result = callback.WaitForResult();
// Initial offset does not affect the result of GetLength.
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(FileSystemFileStreamReaderTest, ReadNormal) {
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kTestFileName, 0, test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(kTestData, data);
}
TEST_F(FileSystemFileStreamReaderTest, ReadAfterModified) {
// Pass a fake expected modifictaion time so that the expectation fails.
base::Time fake_expected_modification_time =
test_file_modification_time() - base::TimeDelta::FromSeconds(10);
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kTestFileName, 0, fake_expected_modification_time));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
ASSERT_EQ(0U, data.size());
// With nullptr expected modification time this should work.
data.clear();
reader.reset(CreateFileReader(kTestFileName, 0, base::Time()));
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(kTestData, data);
}
TEST_F(FileSystemFileStreamReaderTest, ReadWithOffset) {
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kTestFileName, 3, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(&kTestData[3], data);
}
TEST_F(FileSystemFileStreamReaderTest, DeleteWithUnfinishedRead) {
std::unique_ptr<FileSystemFileStreamReader> reader(
CreateFileReader(kTestFileName, 0, base::Time()));
net::TestCompletionCallback callback;
scoped_refptr<net::IOBufferWithSize> buf =
base::MakeRefCounted<net::IOBufferWithSize>(kTestDataSize);
int rv = reader->Read(buf.get(), buf->size(), base::BindOnce(&NeverCalled));
ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0);
// Delete immediately.
// Should not crash; nor should NeverCalled be callback.
reader.reset();
}
} // namespace storage } // namespace storage
...@@ -26,38 +26,19 @@ ...@@ -26,38 +26,19 @@
#include "net/base/io_buffer.h" #include "net/base/io_buffer.h"
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h" #include "net/base/test_completion_callback.h"
#include "storage/browser/file_system/file_stream_reader_test.h"
#include "storage/browser/file_system/file_stream_test_utils.h" #include "storage/browser/file_system/file_stream_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace storage { namespace storage {
namespace { class LocalFileStreamReaderTest : public FileStreamReaderTest {
const char kTestData[] = "0123456789";
const int kTestDataSize = base::size(kTestData) - 1;
void NeverCalled(int) {
ADD_FAILURE();
}
void QuitLoop() {
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
} // namespace
class LocalFileStreamReaderTest : public testing::Test {
public: public:
LocalFileStreamReaderTest() : file_thread_("TestFileThread") {} LocalFileStreamReaderTest() : file_thread_("TestFileThread") {}
void SetUp() override { void SetUp() override {
ASSERT_TRUE(file_thread_.Start());
ASSERT_TRUE(dir_.CreateUniqueTempDir()); ASSERT_TRUE(dir_.CreateUniqueTempDir());
ASSERT_TRUE(file_thread_.Start());
base::WriteFile(test_path(), kTestData);
base::File::Info info;
ASSERT_TRUE(base::GetFileInfo(test_path(), &info));
test_file_modification_time_ = info.last_modified;
} }
void TearDown() override { void TearDown() override {
...@@ -67,195 +48,57 @@ class LocalFileStreamReaderTest : public testing::Test { ...@@ -67,195 +48,57 @@ class LocalFileStreamReaderTest : public testing::Test {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
protected: std::unique_ptr<FileStreamReader> CreateFileReader(
LocalFileStreamReader* CreateFileReader( const std::string& file_name,
const base::FilePath& path,
int64_t initial_offset, int64_t initial_offset,
const base::Time& expected_modification_time) { const base::Time& expected_modification_time) override {
return new LocalFileStreamReader(file_task_runner(), path, initial_offset, return FileStreamReader::CreateForLocalFile(
file_task_runner(), test_dir().AppendASCII(file_name), initial_offset,
expected_modification_time); expected_modification_time);
} }
void TouchTestFile(base::TimeDelta delta) { void WriteFile(const std::string& file_name,
base::Time new_modified_time = test_file_modification_time() + delta; const char* buf,
ASSERT_TRUE(base::TouchFile(test_path(), test_file_modification_time(), size_t buf_size,
new_modified_time)); base::Time* modification_time) override {
base::FilePath path = test_dir().AppendASCII(file_name);
base::WriteFile(path, buf, buf_size);
base::File::Info file_info;
ASSERT_TRUE(base::GetFileInfo(path, &file_info));
if (modification_time)
*modification_time = file_info.last_modified;
} }
base::SingleThreadTaskRunner* file_task_runner() const { void TouchFile(const std::string& file_name, base::TimeDelta delta) override {
return file_thread_.task_runner().get(); base::FilePath path = test_dir().AppendASCII(file_name);
base::File::Info file_info;
ASSERT_TRUE(base::GetFileInfo(path, &file_info));
ASSERT_TRUE(base::TouchFile(path, file_info.last_accessed,
file_info.last_modified + delta));
} }
base::FilePath test_dir() const { return dir_.GetPath(); } void EnsureFileTaskFinished() override {
base::FilePath test_path() const { base::RunLoop run_loop;
return dir_.GetPath().AppendASCII("test"); file_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(),
}
base::Time test_file_modification_time() const { run_loop.QuitClosure());
return test_file_modification_time_; run_loop.Run();
} }
void EnsureFileTaskFinished() { base::FilePath test_dir() const { return dir_.GetPath(); }
file_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(),
base::BindOnce(&QuitLoop)); base::SingleThreadTaskRunner* file_task_runner() const {
base::RunLoop().Run(); return file_thread_.task_runner().get();
} }
private: private:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
base::Thread file_thread_;
base::ScopedTempDir dir_; base::ScopedTempDir dir_;
base::Time test_file_modification_time_; base::Thread file_thread_;
}; };
TEST_F(LocalFileStreamReaderTest, NonExistent) { INSTANTIATE_TYPED_TEST_SUITE_P(Local,
base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent"); FileStreamReaderTypedTest,
std::unique_ptr<LocalFileStreamReader> reader( LocalFileStreamReaderTest);
CreateFileReader(nonexistent_path, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result);
ASSERT_EQ(0U, data.size());
}
TEST_F(LocalFileStreamReaderTest, Empty) {
base::FilePath empty_path = test_dir().AppendASCII("empty");
base::File file(empty_path, base::File::FLAG_CREATE | base::File::FLAG_READ);
ASSERT_TRUE(file.IsValid());
file.Close();
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(empty_path, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(0U, data.size());
net::TestInt64CompletionCallback callback;
int64_t length_result = reader->GetLength(callback.callback());
if (length_result == net::ERR_IO_PENDING)
length_result = callback.WaitForResult();
ASSERT_EQ(0, result);
}
TEST_F(LocalFileStreamReaderTest, GetLengthNormal) {
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
net::TestInt64CompletionCallback callback;
int64_t result = reader->GetLength(callback.callback());
if (result == net::ERR_IO_PENDING)
result = callback.WaitForResult();
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(LocalFileStreamReaderTest, GetLengthAfterModified) {
// Touch file so that the file's modification time becomes different
// from what we expect.
TouchTestFile(base::TimeDelta::FromSeconds(-1));
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
net::TestInt64CompletionCallback callback1;
int64_t result = reader->GetLength(callback1.callback());
if (result == net::ERR_IO_PENDING)
result = callback1.WaitForResult();
ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
// With nullptr expected modification time this should work.
reader.reset(CreateFileReader(test_path(), 0, base::Time()));
net::TestInt64CompletionCallback callback2;
result = reader->GetLength(callback2.callback());
if (result == net::ERR_IO_PENDING)
result = callback2.WaitForResult();
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(LocalFileStreamReaderTest, GetLengthWithOffset) {
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(test_path(), 3, base::Time()));
net::TestInt64CompletionCallback callback;
int64_t result = reader->GetLength(callback.callback());
if (result == net::ERR_IO_PENDING)
result = callback.WaitForResult();
// Initial offset does not affect the result of GetLength.
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(LocalFileStreamReaderTest, ReadNormal) {
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(kTestData, data);
}
TEST_F(LocalFileStreamReaderTest, ReadAfterModified) {
// Touch file so that the file's modification time becomes different
// from what we expect. Note that the resolution on some filesystems
// is 1s so we can't test with deltas less than that.
TouchTestFile(base::TimeDelta::FromSeconds(-1));
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
EXPECT_EQ(0U, data.size());
// Due to precision loss converting int64_t->double->int64_t (e.g. through
// Blink) the expected/actual time may vary by microseconds. With
// modification time delta < 10us this should work.
TouchTestFile(base::TimeDelta::FromMicroseconds(1));
data.clear();
reader.reset(CreateFileReader(test_path(), 0, test_file_modification_time()));
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::OK, result);
EXPECT_EQ(kTestData, data);
// With matching modification times time this should work.
TouchTestFile(base::TimeDelta());
data.clear();
reader.reset(CreateFileReader(test_path(), 0, test_file_modification_time()));
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::OK, result);
EXPECT_EQ(kTestData, data);
// And with nullptr expected modification time this should work.
data.clear();
reader.reset(CreateFileReader(test_path(), 0, base::Time()));
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::OK, result);
EXPECT_EQ(kTestData, data);
}
TEST_F(LocalFileStreamReaderTest, ReadWithOffset) {
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(test_path(), 3, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(&kTestData[3], data);
}
TEST_F(LocalFileStreamReaderTest, DeleteWithUnfinishedRead) {
std::unique_ptr<LocalFileStreamReader> reader(
CreateFileReader(test_path(), 0, base::Time()));
net::TestCompletionCallback callback;
scoped_refptr<net::IOBufferWithSize> buf =
base::MakeRefCounted<net::IOBufferWithSize>(kTestDataSize);
int rv = reader->Read(buf.get(), buf->size(), base::BindOnce(&NeverCalled));
ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0);
// Delete immediately.
// Should not crash; nor should NeverCalled be callback.
reader.reset();
EnsureFileTaskFinished();
}
} // namespace storage } // namespace storage
...@@ -21,232 +21,69 @@ ...@@ -21,232 +21,69 @@
#include "net/base/io_buffer.h" #include "net/base/io_buffer.h"
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "storage/browser/file_system/file_stream_reader.h" #include "storage/browser/file_system/file_stream_reader.h"
#include "storage/browser/file_system/file_stream_reader_test.h"
#include "storage/browser/file_system/file_stream_test_utils.h" #include "storage/browser/file_system/file_stream_test_utils.h"
#include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h" #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace storage { namespace storage {
namespace { class MemoryFileStreamReaderTest : public FileStreamReaderTest {
const char kTestData[] = "0123456789";
const int kTestDataSize = base::size(kTestData) - 1;
} // namespace
class MemoryFileStreamReaderTest : public testing::Test {
public: public:
MemoryFileStreamReaderTest() {} MemoryFileStreamReaderTest() = default;
void SetUp() override { void SetUp() override {
ASSERT_TRUE(file_system_directory_.CreateUniqueTempDir()); ASSERT_TRUE(dir_.CreateUniqueTempDir());
file_util_ = std::make_unique<ObfuscatedFileUtilMemoryDelegate>( file_util_ = std::make_unique<ObfuscatedFileUtilMemoryDelegate>(test_dir());
file_system_directory_.GetPath());
file_util_->CreateFileForTesting(
test_path(), base::span<const char>(kTestData, kTestDataSize));
base::File::Info info;
ASSERT_EQ(base::File::FILE_OK, file_util_->GetFileInfo(test_path(), &info));
test_file_modification_time_ = info.last_modified;
} }
void TearDown() override { void TearDown() override {
// In memory operations should not have any residue in file system // In memory operations should not have any residue in file system
// directory. // directory.
EXPECT_TRUE(base::IsDirectoryEmpty(file_system_directory_.GetPath())); EXPECT_TRUE(base::IsDirectoryEmpty(test_dir()));
} }
ObfuscatedFileUtilMemoryDelegate* file_util() { return file_util_.get(); }
protected:
std::unique_ptr<FileStreamReader> CreateFileReader( std::unique_ptr<FileStreamReader> CreateFileReader(
const base::FilePath& path, const std::string& file_name,
int64_t initial_offset, int64_t initial_offset,
const base::Time& expected_modification_time) { const base::Time& expected_modification_time) override {
return FileStreamReader::CreateForMemoryFile( return FileStreamReader::CreateForMemoryFile(
base::ThreadTaskRunnerHandle::Get(), file_util_->GetWeakPtr(), path, base::ThreadTaskRunnerHandle::Get(), file_util_->GetWeakPtr(),
initial_offset, expected_modification_time); test_dir().AppendASCII(file_name), initial_offset,
expected_modification_time);
} }
void TouchTestFile(base::TimeDelta delta) { void WriteFile(const std::string& file_name,
base::Time new_modified_time = test_file_modification_time() + delta; const char* buf,
ASSERT_EQ(base::File::FILE_OK, size_t buf_size,
file_util()->Touch(test_path(), test_file_modification_time(), base::Time* modification_time) override {
new_modified_time)); base::FilePath path = test_dir().AppendASCII(file_name);
file_util_->CreateFileForTesting(path,
base::span<const char>(buf, buf_size));
base::File::Info file_info;
ASSERT_EQ(base::File::FILE_OK, file_util_->GetFileInfo(path, &file_info));
if (modification_time)
*modification_time = file_info.last_modified;
} }
base::FilePath test_dir() const { return file_system_directory_.GetPath(); } void TouchFile(const std::string& file_name, base::TimeDelta delta) override {
base::FilePath test_path() const { base::FilePath path = test_dir().AppendASCII(file_name);
return file_system_directory_.GetPath().AppendASCII("test"); base::File::Info file_info;
} ASSERT_EQ(base::File::FILE_OK, file_util_->GetFileInfo(path, &file_info));
base::Time test_file_modification_time() const { ASSERT_EQ(base::File::FILE_OK,
return test_file_modification_time_; file_util_->Touch(path, file_info.last_accessed,
file_info.last_modified + delta));
} }
base::FilePath test_dir() const { return dir_.GetPath(); }
private: private:
base::test::TaskEnvironment task_environment_; base::ScopedTempDir dir_;
base::ScopedTempDir file_system_directory_;
std::unique_ptr<ObfuscatedFileUtilMemoryDelegate> file_util_; std::unique_ptr<ObfuscatedFileUtilMemoryDelegate> file_util_;
base::Time test_file_modification_time_;
}; };
TEST_F(MemoryFileStreamReaderTest, NonExistent) { INSTANTIATE_TYPED_TEST_SUITE_P(Memory,
base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent"); FileStreamReaderTypedTest,
std::unique_ptr<FileStreamReader> reader( MemoryFileStreamReaderTest);
CreateFileReader(nonexistent_path, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result);
ASSERT_EQ(0U, data.size());
}
TEST_F(MemoryFileStreamReaderTest, Empty) {
base::FilePath empty_path = test_dir().AppendASCII("empty");
bool created;
EXPECT_EQ(base::File::FILE_OK,
file_util()->EnsureFileExists(empty_path, &created));
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(empty_path, 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 10, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(0U, data.size());
int64_t length_result = GetLengthFromReader(reader.get());
ASSERT_EQ(0, length_result);
}
TEST_F(MemoryFileStreamReaderTest, GetLengthNormal) {
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int64_t result = GetLengthFromReader(reader.get());
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(MemoryFileStreamReaderTest, GetLengthAfterModified) {
// Touch file so that the file's modification time becomes different
// from what we expect.
TouchTestFile(base::TimeDelta::FromSeconds(-1));
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int64_t result = GetLengthFromReader(reader.get());
ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
}
TEST_F(MemoryFileStreamReaderTest, GetLengthAfterModifiedWithNoExpectedTime) {
// Touch file so that the file's modification time becomes different
// from what we expect.
TouchTestFile(base::TimeDelta::FromSeconds(-1));
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, base::Time()));
int64_t result = GetLengthFromReader(reader.get());
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(MemoryFileStreamReaderTest, GetLengthWithOffset) {
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 3, base::Time()));
int64_t result = GetLengthFromReader(reader.get());
// Initial offset does not affect the result of GetLength.
ASSERT_EQ(kTestDataSize, result);
}
TEST_F(MemoryFileStreamReaderTest, ReadNormal) {
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(kTestData, data);
}
TEST_F(MemoryFileStreamReaderTest, ReadAfterModified) {
// Touch file so that the file's modification time becomes different
// from what we expect. Note that the resolution on some filesystems
// is 1s so we can't test with deltas less than that.
TouchTestFile(base::TimeDelta::FromSeconds(-1));
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
EXPECT_EQ(0U, data.size());
}
TEST_F(MemoryFileStreamReaderTest, ReadAfterModifiedLessThanThreshold) {
// Due to precision loss converting int64_t->double->int64_t (e.g. through
// Blink) the expected/actual time may vary by microseconds. With
// modification time delta < 10us this should work.
TouchTestFile(base::TimeDelta::FromMicroseconds(1));
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::OK, result);
EXPECT_EQ(kTestData, data);
}
TEST_F(MemoryFileStreamReaderTest, ReadAfterModifiedWithMatchingTimes) {
TouchTestFile(base::TimeDelta());
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, test_file_modification_time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::OK, result);
EXPECT_EQ(kTestData, data);
}
TEST_F(MemoryFileStreamReaderTest, ReadAfterModifiedWithoutExpectedTime) {
TouchTestFile(base::TimeDelta());
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 0, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
EXPECT_EQ(net::OK, result);
EXPECT_EQ(kTestData, data);
}
TEST_F(MemoryFileStreamReaderTest, ReadWithOffset) {
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), 3, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, kTestDataSize, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(&kTestData[3], data);
}
TEST_F(MemoryFileStreamReaderTest, ReadWithNegativeOffset) {
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), -1, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 1, &result);
ASSERT_EQ(net::ERR_INVALID_ARGUMENT, result);
ASSERT_EQ(data.size(), 0u);
}
TEST_F(MemoryFileStreamReaderTest, ReadWithOffsetLargerThanFile) {
std::unique_ptr<FileStreamReader> reader(
CreateFileReader(test_path(), kTestDataSize + 1, base::Time()));
int result = 0;
std::string data;
ReadFromReader(reader.get(), &data, 1, &result);
ASSERT_EQ(net::OK, result);
ASSERT_EQ(data.size(), 0u);
}
} // namespace storage } // namespace storage
...@@ -269,4 +269,18 @@ blink::mojom::QuotaStatusCode AsyncFileTestHelper::GetUsageAndQuota( ...@@ -269,4 +269,18 @@ blink::mojom::QuotaStatusCode AsyncFileTestHelper::GetUsageAndQuota(
return status; return status;
} }
base::File::Error AsyncFileTestHelper::TouchFile(
FileSystemContext* context,
const FileSystemURL& url,
const base::Time& last_access_time,
const base::Time& last_modified_time) {
base::File::Error result = base::File::FILE_ERROR_FAILED;
base::RunLoop run_loop;
context->operation_runner()->TouchFile(
url, last_access_time, last_modified_time,
AssignAndQuitCallback(&run_loop, &result));
run_loop.Run();
return result;
}
} // namespace storage } // namespace storage
...@@ -107,6 +107,14 @@ class AsyncFileTestHelper { ...@@ -107,6 +107,14 @@ class AsyncFileTestHelper {
FileSystemType type, FileSystemType type,
int64_t* usage, int64_t* usage,
int64_t* quota); int64_t* quota);
// Modifies timestamps of a file or directory at |url| with
// |last_access_time| and |last_modified_time|. The function DOES NOT
// create a file unlike 'touch' command on Linux.
static base::File::Error TouchFile(FileSystemContext* context,
const FileSystemURL& url,
const base::Time& last_access_time,
const base::Time& last_modified_time);
}; };
} // namespace storage } // namespace storage
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment