blob: 5fa8ce3f319320fc4121cea5e75ec0c8fa0e0cd7 [file] [log] [blame]
// Copyright (c) 2017 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 <memory>
#include <string>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "net/base/cache_type.h"
#include "net/disk_cache/disk_cache_test_base.h"
#include "net/disk_cache/simple/simple_file_tracker.h"
#include "net/disk_cache/simple/simple_histogram_enums.h"
#include "net/disk_cache/simple/simple_synchronous_entry.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace disk_cache {
class SimpleFileTrackerTest : public DiskCacheTest {
public:
void DeleteSyncEntry(SimpleSynchronousEntry* entry) { delete entry; }
// We limit open files to 4 for the fixture, as this is large enough
// that simple tests don't have to worry about naming files normally,
// but small enough to test with easily.
static const int kFileLimit = 4;
protected:
SimpleFileTrackerTest() : file_tracker_(kFileLimit) {}
// A bit of messiness since we rely on friendship of the fixture to be able to
// create/delete SimpleSynchronousEntry objects.
class SyncEntryDeleter {
public:
SyncEntryDeleter(SimpleFileTrackerTest* fixture) : fixture_(fixture) {}
void operator()(SimpleSynchronousEntry* entry) {
fixture_->DeleteSyncEntry(entry);
}
private:
SimpleFileTrackerTest* fixture_;
};
using SyncEntryPointer =
std::unique_ptr<SimpleSynchronousEntry, SyncEntryDeleter>;
SyncEntryPointer MakeSyncEntry(uint64_t hash) {
return SyncEntryPointer(
new SimpleSynchronousEntry(net::DISK_CACHE, cache_path_, "dummy", hash,
/* had_index=*/true, &file_tracker_),
SyncEntryDeleter(this));
}
void UpdateEntryFileKey(SimpleSynchronousEntry* sync_entry,
SimpleFileTracker::EntryFileKey file_key) {
sync_entry->entry_file_key_ = file_key;
}
SimpleFileTracker file_tracker_;
};
TEST_F(SimpleFileTrackerTest, Basic) {
SyncEntryPointer entry = MakeSyncEntry(1);
// Just transfer some files to the tracker, and then do some I/O on getting
// them back.
base::FilePath path_0 = cache_path_.AppendASCII("file_0");
base::FilePath path_1 = cache_path_.AppendASCII("file_1");
std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
path_0, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
std::unique_ptr<base::File> file_1 = std::make_unique<base::File>(
path_1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(file_0->IsValid());
ASSERT_TRUE(file_1->IsValid());
file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file_0));
file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
std::move(file_1));
base::StringPiece msg_0 = "Hello";
base::StringPiece msg_1 = "Worldish Place";
{
SimpleFileTracker::FileHandle borrow_0 =
file_tracker_.Acquire(entry.get(), SimpleFileTracker::SubFile::FILE_0);
SimpleFileTracker::FileHandle borrow_1 =
file_tracker_.Acquire(entry.get(), SimpleFileTracker::SubFile::FILE_1);
EXPECT_EQ(static_cast<int>(msg_0.size()),
borrow_0->Write(0, msg_0.data(), msg_0.size()));
EXPECT_EQ(static_cast<int>(msg_1.size()),
borrow_1->Write(0, msg_1.data(), msg_1.size()));
// For stream 0 do release/close, for stream 1 do close/release --- where
// release happens when borrow_{0,1} go out of scope
file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
}
file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
// Verify contents.
std::string verify_0, verify_1;
EXPECT_TRUE(ReadFileToString(path_0, &verify_0));
EXPECT_TRUE(ReadFileToString(path_1, &verify_1));
EXPECT_EQ(msg_0, verify_0);
EXPECT_EQ(msg_1, verify_1);
EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
}
TEST_F(SimpleFileTrackerTest, Collision) {
// Two entries with same key.
SyncEntryPointer entry = MakeSyncEntry(1);
SyncEntryPointer entry2 = MakeSyncEntry(1);
base::FilePath path = cache_path_.AppendASCII("file");
base::FilePath path2 = cache_path_.AppendASCII("file2");
std::unique_ptr<base::File> file = std::make_unique<base::File>(
path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
std::unique_ptr<base::File> file2 = std::make_unique<base::File>(
path2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(file->IsValid());
ASSERT_TRUE(file2->IsValid());
file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file));
file_tracker_.Register(entry2.get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file2));
base::StringPiece msg = "Alpha";
base::StringPiece msg2 = "Beta";
{
SimpleFileTracker::FileHandle borrow =
file_tracker_.Acquire(entry.get(), SimpleFileTracker::SubFile::FILE_0);
SimpleFileTracker::FileHandle borrow2 =
file_tracker_.Acquire(entry2.get(), SimpleFileTracker::SubFile::FILE_0);
EXPECT_EQ(static_cast<int>(msg.size()),
borrow->Write(0, msg.data(), msg.size()));
EXPECT_EQ(static_cast<int>(msg2.size()),
borrow2->Write(0, msg2.data(), msg2.size()));
}
file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
file_tracker_.Close(entry2.get(), SimpleFileTracker::SubFile::FILE_0);
// Verify contents.
std::string verify, verify2;
EXPECT_TRUE(ReadFileToString(path, &verify));
EXPECT_TRUE(ReadFileToString(path2, &verify2));
EXPECT_EQ(msg, verify);
EXPECT_EQ(msg2, verify2);
EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
}
TEST_F(SimpleFileTrackerTest, Reopen) {
// We may sometimes go Register -> Close -> Register, with info still
// alive.
SyncEntryPointer entry = MakeSyncEntry(1);
base::FilePath path_0 = cache_path_.AppendASCII("file_0");
base::FilePath path_1 = cache_path_.AppendASCII("file_1");
std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
path_0, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
std::unique_ptr<base::File> file_1 = std::make_unique<base::File>(
path_1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(file_0->IsValid());
ASSERT_TRUE(file_1->IsValid());
file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file_0));
file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
std::move(file_1));
file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
std::unique_ptr<base::File> file_1b = std::make_unique<base::File>(
path_1, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
ASSERT_TRUE(file_1b->IsValid());
file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
std::move(file_1b));
file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
}
TEST_F(SimpleFileTrackerTest, PointerStability) {
// Make sure the FileHandle lent out doesn't get screwed up as we update
// the state (and potentially move the underlying base::File object around).
const int kEntries = 8;
SyncEntryPointer entries[kEntries] = {
MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1),
MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1),
};
std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
cache_path_.AppendASCII("0"),
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(file_0->IsValid());
file_tracker_.Register(entries[0].get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file_0));
base::StringPiece msg = "Message to write";
{
SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
entries[0].get(), SimpleFileTracker::SubFile::FILE_0);
for (int i = 1; i < kEntries; ++i) {
std::unique_ptr<base::File> file_n = std::make_unique<base::File>(
cache_path_.AppendASCII(base::IntToString(i)),
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(file_n->IsValid());
file_tracker_.Register(entries[i].get(),
SimpleFileTracker::SubFile::FILE_0,
std::move(file_n));
}
EXPECT_EQ(static_cast<int>(msg.size()),
borrow->Write(0, msg.data(), msg.size()));
}
for (int i = 0; i < kEntries; ++i)
file_tracker_.Close(entries[i].get(), SimpleFileTracker::SubFile::FILE_0);
// Verify the file.
std::string verify;
EXPECT_TRUE(ReadFileToString(cache_path_.AppendASCII("0"), &verify));
EXPECT_EQ(msg, verify);
EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
}
TEST_F(SimpleFileTrackerTest, Doom) {
SyncEntryPointer entry1 = MakeSyncEntry(1);
base::FilePath path1 = cache_path_.AppendASCII("file1");
std::unique_ptr<base::File> file1 = std::make_unique<base::File>(
path1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(file1->IsValid());
file_tracker_.Register(entry1.get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file1));
SimpleFileTracker::EntryFileKey key1 = entry1->entry_file_key();
file_tracker_.Doom(entry1.get(), &key1);
EXPECT_NE(0u, key1.doom_generation);
// Other entry with same key.
SyncEntryPointer entry2 = MakeSyncEntry(1);
base::FilePath path2 = cache_path_.AppendASCII("file2");
std::unique_ptr<base::File> file2 = std::make_unique<base::File>(
path2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(file2->IsValid());
file_tracker_.Register(entry2.get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file2));
SimpleFileTracker::EntryFileKey key2 = entry2->entry_file_key();
file_tracker_.Doom(entry2.get(), &key2);
EXPECT_NE(0u, key2.doom_generation);
EXPECT_NE(key1.doom_generation, key2.doom_generation);
file_tracker_.Close(entry1.get(), SimpleFileTracker::SubFile::FILE_0);
file_tracker_.Close(entry2.get(), SimpleFileTracker::SubFile::FILE_0);
}
TEST_F(SimpleFileTrackerTest, OverLimit) {
base::HistogramTester histogram_tester;
const int kEntries = 10; // want more than FD limit in fixture.
std::vector<SyncEntryPointer> entries;
std::vector<base::FilePath> names;
for (int i = 0; i < kEntries; ++i) {
SyncEntryPointer entry = MakeSyncEntry(i);
base::FilePath name =
entry->GetFilenameForSubfile(SimpleFileTracker::SubFile::FILE_0);
std::unique_ptr<base::File> file = std::make_unique<base::File>(
name, base::File::FLAG_CREATE | base::File::FLAG_WRITE |
base::File::FLAG_READ);
ASSERT_TRUE(file->IsValid());
file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
std::move(file));
entries.push_back(std::move(entry));
names.push_back(name);
}
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_CLOSE_FILE,
kEntries - kFileLimit);
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_REOPEN_FILE, 0);
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 0);
// Grab the last one; we will hold it open till the end of the test. It's
// still open, so no change in stats after.
SimpleFileTracker::FileHandle borrow_last = file_tracker_.Acquire(
entries[kEntries - 1].get(), SimpleFileTracker::SubFile::FILE_0);
EXPECT_EQ(1, borrow_last->Write(0, "L", 1));
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_CLOSE_FILE,
kEntries - kFileLimit);
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_REOPEN_FILE, 0);
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 0);
// Delete file for [2], to cause error on its re-open.
EXPECT_TRUE(base::DeleteFile(names[2], false)) << names[2];
// Reacquire all the other files.
for (int i = 0; i < kEntries - 1; ++i) {
SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
entries[i].get(), SimpleFileTracker::SubFile::FILE_0);
if (i != 2) {
EXPECT_TRUE(borrow.IsOK());
char c = static_cast<char>(i);
EXPECT_EQ(1, borrow->Write(0, &c, 1));
} else {
EXPECT_FALSE(borrow.IsOK());
}
}
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_CLOSE_FILE,
kEntries - kFileLimit + kEntries - 2);
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_REOPEN_FILE,
kEntries - 2);
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 1);
// Doom file for [1].
SimpleFileTracker::EntryFileKey key = entries[1]->entry_file_key();
file_tracker_.Doom(entries[1].get(), &key);
base::FilePath old_path = names[1];
UpdateEntryFileKey(entries[1].get(), key);
base::FilePath new_path =
entries[1]->GetFilenameForSubfile(SimpleFileTracker::SubFile::FILE_0);
EXPECT_TRUE(base::StartsWith(new_path.BaseName().MaybeAsASCII(), "todelete_",
base::CompareCase::SENSITIVE));
EXPECT_TRUE(base::Move(old_path, new_path));
// Now re-acquire everything again; this time reading.
for (int i = 0; i < kEntries - 1; ++i) {
SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
entries[i].get(), SimpleFileTracker::SubFile::FILE_0);
char read;
char expected = static_cast<char>(i);
if (i != 2) {
EXPECT_TRUE(borrow.IsOK());
EXPECT_EQ(1, borrow->Read(0, &read, 1));
EXPECT_EQ(expected, read);
} else {
EXPECT_FALSE(borrow.IsOK());
}
}
histogram_tester.ExpectBucketCount(
"SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_CLOSE_FILE,
kEntries - kFileLimit + 2 * (kEntries - 2));
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_REOPEN_FILE,
2 * (kEntries - 2));
histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 2);
// Read from the last one, too. Should still be fine.
char read;
EXPECT_EQ(1, borrow_last->Read(0, &read, 1));
EXPECT_EQ('L', read);
for (const auto& entry : entries)
file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
};
} // namespace disk_cache