Skip to content

Commit

Permalink
Combine file stream reader unittests
Browse files Browse the repository at this point in the history
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: Nick Harper <nharper@chromium.org>
Reviewed-by: Marijn Kruisselbrink <mek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#807092}
  • Loading branch information
quisquous authored and Commit Bot committed Sep 15, 2020
1 parent 064cb5e commit b029a29
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 561 deletions.
5 changes: 5 additions & 0 deletions net/base/file_stream_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 2 additions & 0 deletions storage/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions storage/browser/file_system/file_stream_reader_test.cc
Original file line number Diff line number Diff line change
@@ -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
276 changes: 276 additions & 0 deletions storage/browser/file_system/file_stream_reader_test.h
Original file line number Diff line number Diff line change
@@ -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<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_
Loading

0 comments on commit b029a29

Please sign in to comment.