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