blob: c1a1520d67297fbce926229982bff358b9c42623 [file] [log] [blame]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/persistent_cache/sqlite/sqlite_backend_impl.h"
#include <stdint.h>
#include <array>
#include <optional>
#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/numerics/byte_conversions.h"
#include "base/test/gmock_expected_support.h"
#include "components/persistent_cache/backend_storage.h"
#include "components/persistent_cache/backend_type.h"
#include "components/persistent_cache/client.h"
#include "components/persistent_cache/persistent_cache.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace persistent_cache {
namespace {
using ::base::test::ValueIs;
using ::testing::Eq;
using ::testing::Ne;
using ::testing::Optional;
// See https://sqlite.org/fileformat2.html#user_version_number
constexpr int64_t kUserVersionOffset = 60;
// Make sure the files are initialized with an invalid version.
// 0 is chosen for two reasons: It's a value this is not equal to
// `kCurrentUserVersion` so validates that mismatches are not tolerated. It's
// also the default value present prior to the use of user_version and real
// files exist in clients with no specific user versions and tables which need
// to be discarded.
constexpr int kInvalidVersion = 0;
} // namespace
class SQLiteBackendImplTest : public testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
backend_storage_.emplace(Client::kTest, BackendType::kSqlite,
temp_dir_.GetPath());
}
base::ScopedTempDir temp_dir_;
std::optional<BackendStorage> backend_storage_;
};
TEST_F(SQLiteBackendImplTest, ReopeningFilesWithSameUserVersionWorks) {
const base::FilePath db_basename = base::FilePath::FromASCII("Cache");
ASSERT_OK_AND_ASSIGN(
auto pending_backend,
backend_storage_->MakePendingBackend(db_basename, false, false));
// Initialize the backend and close it.
ASSERT_NE(SqliteBackendImpl::Bind(std::move(pending_backend), Client::kTest),
nullptr);
ASSERT_OK_AND_ASSIGN(pending_backend, backend_storage_->MakePendingBackend(
db_basename, false, false));
// NOTE: This does not respect the database locks and so is done when there
// are no live backends.
std::array<uint8_t, sizeof(int)> data;
ASSERT_THAT(
pending_backend.pending_file_set.db_file.Read(kUserVersionOffset, data),
Optional(sizeof(int)));
ASSERT_EQ(base::I32FromBigEndian(data),
SqliteBackendImpl::kCurrentUserVersion);
ASSERT_NE(SqliteBackendImpl::Bind(std::move(pending_backend), Client::kTest),
nullptr);
}
TEST_F(SQLiteBackendImplTest,
VersionMismatchLeadsToFailedInitializeWhenReadOnly) {
const base::FilePath db_path = base::FilePath::FromASCII("Cache");
ASSERT_OK_AND_ASSIGN(
auto pending_backend,
backend_storage_->MakePendingBackend(db_path, false, false));
base::File db_file = pending_backend.pending_file_set.db_file.Duplicate();
std::unique_ptr<PersistentCache> cache =
PersistentCache::Bind(Client::kTest, std::move(pending_backend));
ASSERT_NE(cache, nullptr);
ASSERT_OK_AND_ASSIGN(
pending_backend,
backend_storage_->ShareReadOnlyConnection(db_path, *cache));
// Manually set the user_version in the db file to an invalid value.
// NOTE: This does not respect the database locks and so is done when there
// are no live backends.
cache.reset();
ASSERT_THAT(
db_file.Write(kUserVersionOffset, base::I32ToBigEndian(kInvalidVersion)),
Optional(sizeof(int)));
// Mismatched version numbers makes Bind() fail.
ASSERT_EQ(SqliteBackendImpl::Bind(std::move(pending_backend), Client::kTest),
nullptr);
}
TEST_F(SQLiteBackendImplTest,
VersionMismatchDropsTablesWithReadWriteConnection) {
const base::FilePath db_basename = base::FilePath::FromASCII("Cache");
ASSERT_OK_AND_ASSIGN(
auto pending_backend,
backend_storage_->MakePendingBackend(db_basename, false, false));
std::unique_ptr<PersistentCache> cache =
PersistentCache::Bind(Client::kTest, std::move(pending_backend));
ASSERT_NE(cache, nullptr);
const base::span<const uint8_t> kKey = base::byte_span_from_cstring("key");
EXPECT_OK(cache->Insert(kKey, base::byte_span_from_cstring("1")));
EXPECT_THAT(cache->Find(kKey, [](size_t) { return base::span<uint8_t>(); }),
ValueIs(Ne(std::nullopt)));
ASSERT_OK_AND_ASSIGN(
pending_backend,
backend_storage_->ShareReadWriteConnection(db_basename, *cache));
// Manually set the user_version in the db file to an invalid value.
// NOTE: This does not respect the database locks and so is done when there
// are no live backends.
cache.reset();
ASSERT_THAT(pending_backend.pending_file_set.db_file.Write(
kUserVersionOffset, base::I32ToBigEndian(kInvalidVersion)),
Optional(sizeof(int)));
// Bind succeeds.
std::unique_ptr<Backend> backend =
SqliteBackendImpl::Bind(std::move(pending_backend), Client::kTest);
ASSERT_NE(backend, nullptr);
// The files were wiped so the value inserted is no longer present.
EXPECT_THAT(backend->Find(kKey, [](size_t) { return base::span<uint8_t>(); }),
ValueIs(Eq(std::nullopt)));
}
} // namespace persistent_cache