diff --git a/net/base/file_stream_context.cc b/net/base/file_stream_context.cc index f2009c8aef5276..eccb65eab18920 100644 --- a/net/base/file_stream_context.cc +++ b/net/base/file_stream_context.cc @@ -113,6 +113,11 @@ void FileStream::Context::Seek(int64_t offset, Int64CompletionOnceCallback callback) { DCHECK(!async_in_progress_); + if (offset < 0) { + std::move(callback).Run(net::ERR_INVALID_ARGUMENT); + return; + } + bool posted = base::PostTaskAndReplyWithResult( task_runner_.get(), FROM_HERE, base::BindOnce(&Context::SeekFileImpl, base::Unretained(this), offset), diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn index 48be0d9f2bdd78..c65fcf46234c53 100644 --- a/storage/browser/BUILD.gn +++ b/storage/browser/BUILD.gn @@ -275,6 +275,8 @@ source_set("unittests") { "file_system/copy_or_move_operation_delegate_unittest.cc", "file_system/dragged_file_util_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.h", "file_system/file_system_context_unittest.cc", diff --git a/storage/browser/file_system/file_stream_reader_test.cc b/storage/browser/file_system/file_stream_reader_test.cc new file mode 100644 index 00000000000000..1ce59c61b7e72c --- /dev/null +++ b/storage/browser/file_system/file_stream_reader_test.cc @@ -0,0 +1,12 @@ +// 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 diff --git a/storage/browser/file_system/file_stream_reader_test.h b/storage/browser/file_system/file_stream_reader_test.h new file mode 100644 index 00000000000000..b1c6a19e1ab232 --- /dev/null +++ b/storage/browser/file_system/file_stream_reader_test.h @@ -0,0 +1,276 @@ +// 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 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 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 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 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 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(this->kTestData.size()), result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthAfterModified) { + this->TouchFile(this->kTestFileName.data(), base::TimeDelta::FromSeconds(10)); + + std::unique_ptr 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(this->kTestData.size()), result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthWithOffset) { + std::unique_ptr 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(this->kTestData.size()), result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadNormal) { + std::unique_ptr 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 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 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 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 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 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 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 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 reader( + this->CreateFileReader(this->kTestFileName.data(), 0, base::Time())); + + net::TestCompletionCallback callback; + scoped_refptr buf = + base::MakeRefCounted(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_ diff --git a/storage/browser/file_system/file_system_file_stream_reader_unittest.cc b/storage/browser/file_system/file_system_file_stream_reader_unittest.cc index dc5f5999969e60..9dbc6661796891 100644 --- a/storage/browser/file_system/file_system_file_stream_reader_unittest.cc +++ b/storage/browser/file_system/file_system_file_stream_reader_unittest.cc @@ -21,6 +21,8 @@ #include "net/base/net_errors.h" #include "net/base/test_completion_callback.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_system_context.h" #include "storage/browser/file_system/file_system_file_util.h" @@ -33,58 +35,44 @@ namespace storage { namespace { - 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 -class FileSystemFileStreamReaderTest : public testing::Test { +class FileSystemFileStreamReaderTest : public FileStreamReaderTest { public: FileSystemFileStreamReaderTest() = default; void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(dir_.CreateUniqueTempDir()); file_system_context_ = - CreateFileSystemContextForTesting(nullptr, temp_dir_.GetPath()); + CreateFileSystemContextForTesting(nullptr, dir_.GetPath()); - file_system_context_->OpenFileSystem(url::Origin::Create(GURL(kURLOrigin)), - kFileSystemTypeTemporary, - OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, - base::BindOnce(&OnOpenFileSystem)); + file_system_context_->OpenFileSystem( + url::Origin::Create(GURL(kURLOrigin)), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::BindOnce([](const GURL& root_url, const std::string& name, + base::File::Error result) { + ASSERT_EQ(base::File::FILE_OK, result); + })); base::RunLoop().RunUntilIdle(); - - WriteFile(kTestFileName, kTestData, kTestDataSize, - &test_file_modification_time_); } void TearDown() override { base::RunLoop().RunUntilIdle(); } - protected: - FileSystemFileStreamReader* CreateFileReader( + std::unique_ptr CreateFileReader( const std::string& file_name, int64_t initial_offset, - const base::Time& expected_modification_time) { - return new FileSystemFileStreamReader( + const base::Time& expected_modification_time) override { + return FileStreamReader::CreateForFileSystemFile( file_system_context_.get(), GetFileSystemURL(file_name), initial_offset, expected_modification_time); } - base::Time test_file_modification_time() const { - return test_file_modification_time_; - } - void WriteFile(const std::string& file_name, const char* buf, - int buf_size, - base::Time* modification_time) { + size_t buf_size, + base::Time* modification_time) override { FileSystemURL url = GetFileSystemURL(file_name); ASSERT_EQ(base::File::FILE_OK, @@ -99,11 +87,17 @@ class FileSystemFileStreamReaderTest : public testing::Test { *modification_time = file_info.last_modified; } - private: - static void OnOpenFileSystem(const GURL& root_url, - const std::string& name, - base::File::Error result) { - ASSERT_EQ(base::File::FILE_OK, result); + void TouchFile(const std::string& file_name, base::TimeDelta delta) override { + FileSystemURL url = GetFileSystemURL(file_name); + + base::File::Info file_info; + 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) { @@ -112,140 +106,13 @@ class FileSystemFileStreamReaderTest : public testing::Test { base::FilePath().AppendASCII(file_name)); } - base::test::SingleThreadTaskEnvironment task_environment_{ - base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; - base::ScopedTempDir temp_dir_; + private: + base::ScopedTempDir dir_; scoped_refptr file_system_context_; - base::Time test_file_modification_time_; }; -TEST_F(FileSystemFileStreamReaderTest, NonExistent) { - const char kFileName[] = "nonexistent"; - std::unique_ptr reader( - 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 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 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 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 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 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 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 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 reader( - CreateFileReader(kTestFileName, 0, base::Time())); - - net::TestCompletionCallback callback; - scoped_refptr buf = - base::MakeRefCounted(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(); -} +INSTANTIATE_TYPED_TEST_SUITE_P(FileSystem, + FileStreamReaderTypedTest, + FileSystemFileStreamReaderTest); } // namespace storage diff --git a/storage/browser/file_system/local_file_stream_reader_unittest.cc b/storage/browser/file_system/local_file_stream_reader_unittest.cc index 7b7887d5214bd5..d1f6bf8fe8272d 100644 --- a/storage/browser/file_system/local_file_stream_reader_unittest.cc +++ b/storage/browser/file_system/local_file_stream_reader_unittest.cc @@ -26,38 +26,19 @@ #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_test.h" #include "storage/browser/file_system/file_stream_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace storage { -namespace { - -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 { +class LocalFileStreamReaderTest : public FileStreamReaderTest { public: LocalFileStreamReaderTest() : file_thread_("TestFileThread") {} void SetUp() override { - ASSERT_TRUE(file_thread_.Start()); ASSERT_TRUE(dir_.CreateUniqueTempDir()); - - base::WriteFile(test_path(), kTestData); - base::File::Info info; - ASSERT_TRUE(base::GetFileInfo(test_path(), &info)); - test_file_modification_time_ = info.last_modified; + ASSERT_TRUE(file_thread_.Start()); } void TearDown() override { @@ -67,195 +48,57 @@ class LocalFileStreamReaderTest : public testing::Test { base::RunLoop().RunUntilIdle(); } - protected: - LocalFileStreamReader* CreateFileReader( - const base::FilePath& path, + std::unique_ptr CreateFileReader( + const std::string& file_name, int64_t initial_offset, - const base::Time& expected_modification_time) { - return new LocalFileStreamReader(file_task_runner(), path, initial_offset, - expected_modification_time); + const base::Time& expected_modification_time) override { + return FileStreamReader::CreateForLocalFile( + file_task_runner(), test_dir().AppendASCII(file_name), initial_offset, + expected_modification_time); } - void TouchTestFile(base::TimeDelta delta) { - base::Time new_modified_time = test_file_modification_time() + delta; - ASSERT_TRUE(base::TouchFile(test_path(), test_file_modification_time(), - new_modified_time)); + void WriteFile(const std::string& file_name, + const char* buf, + size_t buf_size, + 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 { - return file_thread_.task_runner().get(); + void TouchFile(const std::string& file_name, base::TimeDelta delta) override { + 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(); } - base::FilePath test_path() const { - return dir_.GetPath().AppendASCII("test"); - } - base::Time test_file_modification_time() const { - return test_file_modification_time_; + void EnsureFileTaskFinished() override { + base::RunLoop run_loop; + file_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(), + + run_loop.QuitClosure()); + run_loop.Run(); } - void EnsureFileTaskFinished() { - file_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(), - base::BindOnce(&QuitLoop)); - base::RunLoop().Run(); + base::FilePath test_dir() const { return dir_.GetPath(); } + + base::SingleThreadTaskRunner* file_task_runner() const { + return file_thread_.task_runner().get(); } private: - base::test::SingleThreadTaskEnvironment task_environment_{ - base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; - base::Thread file_thread_; base::ScopedTempDir dir_; - base::Time test_file_modification_time_; + base::Thread file_thread_; }; -TEST_F(LocalFileStreamReaderTest, NonExistent) { - base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent"); - std::unique_ptr reader( - 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 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 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 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 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 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 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 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 reader( - CreateFileReader(test_path(), 0, base::Time())); - - net::TestCompletionCallback callback; - scoped_refptr buf = - base::MakeRefCounted(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(); -} +INSTANTIATE_TYPED_TEST_SUITE_P(Local, + FileStreamReaderTypedTest, + LocalFileStreamReaderTest); } // namespace storage diff --git a/storage/browser/file_system/memory_file_stream_reader_unittest.cc b/storage/browser/file_system/memory_file_stream_reader_unittest.cc index f41efcc29d6a0e..685f49c22ef052 100644 --- a/storage/browser/file_system/memory_file_stream_reader_unittest.cc +++ b/storage/browser/file_system/memory_file_stream_reader_unittest.cc @@ -21,232 +21,69 @@ #include "net/base/io_buffer.h" #include "net/base/net_errors.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/obfuscated_file_util_memory_delegate.h" #include "testing/gtest/include/gtest/gtest.h" namespace storage { -namespace { - -const char kTestData[] = "0123456789"; -const int kTestDataSize = base::size(kTestData) - 1; - -} // namespace - -class MemoryFileStreamReaderTest : public testing::Test { +class MemoryFileStreamReaderTest : public FileStreamReaderTest { public: - MemoryFileStreamReaderTest() {} + MemoryFileStreamReaderTest() = default; void SetUp() override { - ASSERT_TRUE(file_system_directory_.CreateUniqueTempDir()); - file_util_ = std::make_unique( - file_system_directory_.GetPath()); - - file_util_->CreateFileForTesting( - test_path(), base::span(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; + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + file_util_ = std::make_unique(test_dir()); } void TearDown() override { // In memory operations should not have any residue in file system // 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 CreateFileReader( - const base::FilePath& path, + const std::string& file_name, int64_t initial_offset, - const base::Time& expected_modification_time) { + const base::Time& expected_modification_time) override { return FileStreamReader::CreateForMemoryFile( - base::ThreadTaskRunnerHandle::Get(), file_util_->GetWeakPtr(), path, - initial_offset, expected_modification_time); + base::ThreadTaskRunnerHandle::Get(), file_util_->GetWeakPtr(), + test_dir().AppendASCII(file_name), initial_offset, + expected_modification_time); } - void TouchTestFile(base::TimeDelta delta) { - base::Time new_modified_time = test_file_modification_time() + delta; - ASSERT_EQ(base::File::FILE_OK, - file_util()->Touch(test_path(), test_file_modification_time(), - new_modified_time)); + void WriteFile(const std::string& file_name, + const char* buf, + size_t buf_size, + base::Time* modification_time) override { + base::FilePath path = test_dir().AppendASCII(file_name); + file_util_->CreateFileForTesting(path, + base::span(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(); } - base::FilePath test_path() const { - return file_system_directory_.GetPath().AppendASCII("test"); - } - base::Time test_file_modification_time() const { - return test_file_modification_time_; + void TouchFile(const std::string& file_name, base::TimeDelta delta) override { + base::FilePath path = test_dir().AppendASCII(file_name); + base::File::Info file_info; + ASSERT_EQ(base::File::FILE_OK, file_util_->GetFileInfo(path, &file_info)); + ASSERT_EQ(base::File::FILE_OK, + file_util_->Touch(path, file_info.last_accessed, + file_info.last_modified + delta)); } + base::FilePath test_dir() const { return dir_.GetPath(); } + private: - base::test::TaskEnvironment task_environment_; - base::ScopedTempDir file_system_directory_; + base::ScopedTempDir dir_; std::unique_ptr file_util_; - base::Time test_file_modification_time_; }; -TEST_F(MemoryFileStreamReaderTest, NonExistent) { - base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent"); - std::unique_ptr reader( - 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 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 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 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 reader( - CreateFileReader(test_path(), 0, base::Time())); - int64_t result = GetLengthFromReader(reader.get()); - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(MemoryFileStreamReaderTest, GetLengthWithOffset) { - std::unique_ptr 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 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 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 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 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 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 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 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 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); -} +INSTANTIATE_TYPED_TEST_SUITE_P(Memory, + FileStreamReaderTypedTest, + MemoryFileStreamReaderTest); } // namespace storage diff --git a/storage/browser/test/async_file_test_helper.cc b/storage/browser/test/async_file_test_helper.cc index bd8d4375ed9aff..254ce1803b5a4e 100644 --- a/storage/browser/test/async_file_test_helper.cc +++ b/storage/browser/test/async_file_test_helper.cc @@ -269,4 +269,18 @@ blink::mojom::QuotaStatusCode AsyncFileTestHelper::GetUsageAndQuota( 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 diff --git a/storage/browser/test/async_file_test_helper.h b/storage/browser/test/async_file_test_helper.h index a3a40305048631..caee1e0fc68c07 100644 --- a/storage/browser/test/async_file_test_helper.h +++ b/storage/browser/test/async_file_test_helper.h @@ -107,6 +107,14 @@ class AsyncFileTestHelper { FileSystemType type, int64_t* usage, 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