blob: b1c6a19e1ab2323de849ac5908dd1a8095e8688c [file] [log] [blame]
// 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_