text: // 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,
                                  &file_tracker_, /*trailer_prefetch_size=*/-1),
                              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::NumberToString(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
  