// 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 "components/sqlite_proto/proto_table_manager.h" #include #include "base/memory/scoped_refptr.h" #include "base/strings/strcat.h" #include "base/test/task_environment.h" #include "base/threading/thread_task_runner_handle.h" #include "components/sqlite_proto/key_value_data.h" #include "components/sqlite_proto/key_value_table.h" #include "components/sqlite_proto/test_proto.pb.h" #include "sql/database.h" #include "sql/meta_table.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace sqlite_proto { namespace { MATCHER_P(EqualsProto, message, "Match a proto Message equal to the matcher's argument.") { std::string expected_serialized, actual_serialized; message.SerializeToString(&expected_serialized); arg.SerializeToString(&actual_serialized); return expected_serialized == actual_serialized; } constexpr char kTableName[] = "my_table"; } // namespace TEST(ProtoTableTest, PutReinitializeAndGet) { // In order to test ProtoTableManager is correctly // initializing the underlying database's tables: // - create a database and a ProtoTableManager on it; // - store some data; and // - construct a new ProtoTableManager to read from the // existing database state. base::test::TaskEnvironment env; sql::Database db; CHECK(db.OpenInMemory()); auto manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/1); KeyValueTable table(kTableName); TestProto first_entry, second_entry; first_entry.set_value(1); second_entry.set_value(1); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); // In these tests, we're using the current thread as the DB sequence. data.InitializeOnDBSequence(); data.UpdateData("a", first_entry); data.UpdateData("b", second_entry); env.RunUntilIdle(); } manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/1); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); data.InitializeOnDBSequence(); TestProto result; ASSERT_TRUE(data.TryGetData("a", &result)); EXPECT_THAT(result, EqualsProto(first_entry)); ASSERT_TRUE(data.TryGetData("b", &result)); EXPECT_THAT(result, EqualsProto(second_entry)); } } TEST(ProtoTableTest, ReinitializingWithDifferentVersionClearsTables) { // In order to test ProtoTableManager is correctly // initializing the underlying database's tables: // - create a database and a ProtoTableManager on it; // - store some data; and // - construct a new ProtoTableManager to read from the // existing database state. base::test::TaskEnvironment env; sql::Database db; CHECK(db.OpenInMemory()); constexpr int kInitialVersion = 1; auto manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/kInitialVersion); KeyValueTable table(kTableName); TestProto first_entry, second_entry; first_entry.set_value(1); second_entry.set_value(1); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); // In these tests, we're using the current thread as the DB sequence. data.InitializeOnDBSequence(); data.UpdateData("a", first_entry); data.UpdateData("b", second_entry); env.RunUntilIdle(); } manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/kInitialVersion + 1); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); data.InitializeOnDBSequence(); TestProto result; EXPECT_FALSE(data.TryGetData("a", &result)); EXPECT_FALSE(data.TryGetData("b", &result)); } } TEST(ProtoTableTest, InitializingWithoutWrittenVersionClearsTables) { // Check that, when reinitializing the database when the most recent write // occurred before the database started keeping track of versions, // ProtoTableManager correctly clears the database. base::test::TaskEnvironment env; sql::Database db; CHECK(db.OpenInMemory()); constexpr int kInitialVersion = 1; auto manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/kInitialVersion); KeyValueTable table(kTableName); TestProto first_entry, second_entry; first_entry.set_value(1); second_entry.set_value(1); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); // In these tests, we're using the current thread as the DB sequence. data.InitializeOnDBSequence(); data.UpdateData("a", first_entry); data.UpdateData("b", second_entry); env.RunUntilIdle(); ASSERT_TRUE(sql::MetaTable::DeleteTableForTesting(&db)); env.RunUntilIdle(); } manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/kInitialVersion); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); data.InitializeOnDBSequence(); TestProto result; EXPECT_FALSE(data.TryGetData("a", &result)); EXPECT_FALSE(data.TryGetData("b", &result)); } } TEST(ProtoTableTest, LoadingUnexpectedlyLargeVersionClearsTables) { // Check that, when reinitializing the database and the most recent write // occurred against a greater version of the database, ProtoTableManager // correctly clears the database. base::test::TaskEnvironment env; sql::Database db; CHECK(db.OpenInMemory()); constexpr int kInitialVersion = 1; auto manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/kInitialVersion); KeyValueTable table(kTableName); TestProto first_entry, second_entry; first_entry.set_value(1); second_entry.set_value(1); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); // In these tests, we're using the current thread as the DB sequence. data.InitializeOnDBSequence(); data.UpdateData("a", first_entry); data.UpdateData("b", second_entry); env.RunUntilIdle(); // Overwrite the stored version. It's safe to use an instance of // sql::MetaTable here because all sql::MetaTable instances use the same // database name ("meta") to manipulate versions. // // MetaTable::Init only writes a version if there was no version previously // written, so it doesn't matter what values the final two arguments have. // The SetVersionNumber is what actually overwrites the version. sql::MetaTable meta_helper; ASSERT_TRUE(meta_helper.Init(&db, 1, 1)); meta_helper.SetVersionNumber(kInitialVersion + 1); meta_helper.SetCompatibleVersionNumber(kInitialVersion + 1); env.RunUntilIdle(); } manager = base::MakeRefCounted( base::ThreadTaskRunnerHandle::Get()); manager->InitializeOnDbSequence(&db, std::vector{kTableName}, /*schema_version=*/kInitialVersion); { KeyValueData data(manager, &table, /*max_num_entries=*/absl::nullopt, /*flush_delay=*/base::TimeDelta()); data.InitializeOnDBSequence(); TestProto result; EXPECT_FALSE(data.TryGetData("a", &result)); EXPECT_FALSE(data.TryGetData("b", &result)); } } } // namespace sqlite_proto