#include #include #include #include #include #define BINSTORE ::yycc::carton::binstore namespace yycctest::carton::binstore { using Token = BINSTORE::types::Token; using VersionIdentifier = BINSTORE::types::VersionIdentifier; using Setting = BINSTORE::setting::Setting; using SettingCollection = BINSTORE::setting::SettingCollection; using Configuration = BINSTORE::configuration::Configuration; using Storage = BINSTORE::storage::Storage; using LoadStrategy = BINSTORE::storage::LoadStrategy; namespace serdes = BINSTORE::serdes; #pragma region Binstore Builder // YYC MARK: // Due to the same reason like clap, // I rename them with new test suit name. TEST(CartonBinstoreSetting, BadSetting) { // Test that we can create settings with names EXPECT_NO_THROW(Setting setting(u8"test-setting")); // Test that we cannot create settings with empty names EXPECT_ANY_THROW(Setting setting(u8"")); } TEST(CartonBinstoreSetting, BadSettings) { // Test that duplicate names are not allowed auto settings = SettingCollection(); auto token1 = settings.add_setting(Setting(u8"setting1")); EXPECT_ANY_THROW(settings.add_setting(Setting(u8"setting1"))); } #pragma endregion #pragma region Binstore Operations enum class TestEnum : int8_t { Test1, Test2, Test3 }; class CartonBinstore : public ::testing::Test { protected: CartonBinstore() : // YYC MARK: // Due to the same reason like clap, I need initialize a blank configuration. configuration(0, SettingCollection()) { // Create settings auto settings = SettingCollection(); int_setting = settings.add_setting(Setting(u8"int-setting")); float_setting = settings.add_setting(Setting(u8"float-setting")); string_setting = settings.add_setting(Setting(u8"string-setting")); bool_setting = settings.add_setting(Setting(u8"bool-setting")); clamped_float_setting = settings.add_setting(Setting(u8"clamped-float-setting")); enum_setting = settings.add_setting(Setting(u8"enum-setting")); // Create configuration configuration = Configuration(0, std::move(settings)); } ~CartonBinstore() override = default; /** * @brief Build Storage with given Configuration. * @remarks The Configuration passed to Storage is a copy of this class member. * @return Built Storage. */ Storage build_storage() const { return Storage(Configuration(this->configuration)); } // Tokens for accessing settings Token int_setting; using IntSettingSerDes = serdes::IntegralSerDes; Token float_setting; using FloatSettingSerDes = serdes::FloatingPointSerDes; Token string_setting; using StringSettingSerDes = serdes::StringSerDes; Token bool_setting; using BoolSettingSerDes = serdes::BoolSerDes; Token clamped_float_setting; using ClampedFloatSettingSerDes = serdes::FloatingPointSerDes; Token enum_setting; using EnumSettingSerDes = serdes::EnumSerDes; // Configuration Configuration configuration; }; TEST_F(CartonBinstore, Configuration) { // Test version EXPECT_EQ(configuration.get_version(), 0); // Test settings access const auto& settings = configuration.get_settings(); EXPECT_EQ(settings.length(), 6); EXPECT_TRUE(settings.has_setting(int_setting)); EXPECT_TRUE(settings.has_setting(float_setting)); EXPECT_TRUE(settings.has_setting(string_setting)); EXPECT_TRUE(settings.has_setting(bool_setting)); EXPECT_TRUE(settings.has_setting(clamped_float_setting)); EXPECT_TRUE(settings.has_setting(enum_setting)); EXPECT_NO_THROW(settings.get_setting(int_setting)); EXPECT_NO_THROW(settings.get_setting(float_setting)); EXPECT_NO_THROW(settings.get_setting(string_setting)); EXPECT_NO_THROW(settings.get_setting(bool_setting)); EXPECT_NO_THROW(settings.get_setting(clamped_float_setting)); EXPECT_NO_THROW(settings.get_setting(enum_setting)); } TEST_F(CartonBinstore, StorageBasicOperations) { auto storage = build_storage(); // Test that we can reset values to defaults EXPECT_NO_THROW(storage.reset_value(int_setting)); EXPECT_NO_THROW(storage.reset_value(float_setting)); EXPECT_NO_THROW(storage.reset_value(string_setting)); EXPECT_NO_THROW(storage.reset_value(bool_setting)); EXPECT_NO_THROW(storage.reset_value(clamped_float_setting)); EXPECT_NO_THROW(storage.reset_value(enum_setting)); } TEST_F(CartonBinstore, StorageSetValueGetValue) { auto storage = build_storage(); // Test setting and getting values // Integer setting { i32 test_value = 114; bool set_success = storage.set_value(int_setting, test_value); EXPECT_TRUE(set_success); auto retrieved_value = storage.get_value(int_setting); EXPECT_EQ(retrieved_value, test_value); } // Float setting { float test_value = 2.0f; bool set_success = storage.set_value(float_setting, test_value); EXPECT_TRUE(set_success); auto retrieved_value = storage.get_value(float_setting); EXPECT_EQ(retrieved_value, test_value); } // String setting { std::u8string test_value = u8"test"; bool set_success = storage.set_value(string_setting, test_value); EXPECT_TRUE(set_success); auto retrieved_value = storage.get_value(string_setting); EXPECT_EQ(retrieved_value, test_value); } // Boolean setting { bool test_value = true; bool set_success = storage.set_value(bool_setting, test_value); EXPECT_TRUE(set_success); auto retrieved_value = storage.get_value(bool_setting); EXPECT_EQ(retrieved_value, test_value); } // Clamped float setting (within range) { float test_value = 0.5f; bool set_success = storage.set_value(clamped_float_setting, test_value); EXPECT_TRUE(set_success); auto retrieved_value = storage.get_value(clamped_float_setting); EXPECT_FLOAT_EQ(retrieved_value, test_value); } // Enum setting { TestEnum test_value = TestEnum::Test2; bool set_success = storage.set_value(enum_setting, test_value); EXPECT_TRUE(set_success); auto retrieved_value = storage.get_value(enum_setting); EXPECT_EQ(retrieved_value, test_value); } } TEST_F(CartonBinstore, StorageConstraintHandling) { auto storage = build_storage(); // Test clamped float setting with out-of-range value // Should fail serialization and be reset to default float out_of_range_value = 2.0f; // Outside [-1.0f, 1.0f] bool set_success = storage.set_value(clamped_float_setting, out_of_range_value); EXPECT_FALSE(set_success); // Should return false indicating constraint violation // Value should be reset to default (0.0f) auto default_value = storage.get_value(clamped_float_setting); EXPECT_EQ(default_value, 0.0f); } TEST_F(CartonBinstore, StoragePersistence) { // Create storage auto storage = build_storage(); // Prepare test value static i32 INT_VALUE = 114; static float FLOAT_VALUE = 2.0f; static std::u8string STRING_VALUE = u8"test"; static bool BOOL_VALUE = true; static float CLAMPED_FLOAT_VALUE = 0.5f; static TestEnum ENUM_VALUE = TestEnum::Test2; // Set settings with given value. { ASSERT_TRUE(storage.set_value(int_setting, INT_VALUE)); ASSERT_TRUE(storage.set_value(float_setting, FLOAT_VALUE)); ASSERT_TRUE(storage.set_value(string_setting, STRING_VALUE)); ASSERT_TRUE(storage.set_value(bool_setting, BOOL_VALUE)); ASSERT_TRUE(storage.set_value(clamped_float_setting, CLAMPED_FLOAT_VALUE)); ASSERT_TRUE(storage.set_value(enum_setting, ENUM_VALUE)); } // Save it into buffer. // Use stringstream as buffer. std::stringstream ss; ASSERT_TRUE(storage.save(ss).has_value()); // Clear IO bit flags, not the content. ss.clear(); // Clear values storage.clear(); // Load it into new created storage. ASSERT_TRUE(storage.load(ss, LoadStrategy::OnlyCurrent).has_value()); // Check loaded settings { auto retrieved_value = storage.get_value(int_setting); EXPECT_EQ(retrieved_value, INT_VALUE); } { auto retrieved_value = storage.get_value(float_setting); EXPECT_EQ(retrieved_value, FLOAT_VALUE); } { auto retrieved_value = storage.get_value(string_setting); EXPECT_EQ(retrieved_value, STRING_VALUE); } { auto retrieved_value = storage.get_value(bool_setting); EXPECT_EQ(retrieved_value, BOOL_VALUE); } { auto retrieved_value = storage.get_value(clamped_float_setting); EXPECT_FLOAT_EQ(retrieved_value, CLAMPED_FLOAT_VALUE); } { auto retrieved_value = storage.get_value(enum_setting); EXPECT_EQ(retrieved_value, ENUM_VALUE); } } #pragma endregion #pragma region Binstore Version Handling class CartonBinstoreVersion : public ::testing::Test { protected: CartonBinstoreVersion() { // Create settings settings = SettingCollection(); setting = settings.add_setting(Setting(u8"int-setting")); } ~CartonBinstoreVersion() override = default; /** * @brief Build Storage with given Settings. * @param[in] ver Version used by intermediate Configuration. * @remarks The Settings passed to Storage is a copy of this class member. * The intermediate Configuration is eaten by Storage. * @return Built Storage. */ Storage build_storage(VersionIdentifier ver) const { auto cfg = Configuration(ver, SettingCollection(this->settings)); return Storage(std::move(cfg)); } /** * @brief Prepare a stringstream for loading. * @param[in] ss The string stream to be loaded in future. */ void prepare_load(std::stringstream& ss) const { ss.clear(); ss.seekg(0); } // Tokens for accessing settings Token setting; using SettingSerDes = serdes::IntegralSerDes; static constexpr i32 SETTING_VALUE = 42; // Settings SettingCollection settings; }; TEST_F(CartonBinstoreVersion, LoadStrategy) { // Prepare buffer and essential settings constexpr VersionIdentifier OLD_VERSION = 16; constexpr VersionIdentifier MID_VERSION = 32; constexpr VersionIdentifier NEW_VERSION = 61; // Create old, mid, new storage buffer. std::stringstream ss_old, ss_mid, ss_new; auto storage_old = build_storage(OLD_VERSION); auto storage_mid = build_storage(MID_VERSION); auto storage_new = build_storage(NEW_VERSION); { storage_old.set_value(setting, SETTING_VALUE); ASSERT_TRUE(storage_old.save(ss_old).has_value()); } { storage_mid.set_value(setting, SETTING_VALUE); ASSERT_TRUE(storage_mid.save(ss_mid).has_value()); } { storage_new.set_value(setting, SETTING_VALUE); ASSERT_TRUE(storage_new.save(ss_new).has_value()); } // Try to load other configuration by OnlyCurrent strategy { storage_mid.clear(); prepare_load(ss_old); ASSERT_FALSE(storage_mid.load(ss_old, LoadStrategy::OnlyCurrent).has_value()); } { storage_mid.clear(); prepare_load(ss_mid); ASSERT_TRUE(storage_mid.load(ss_mid, LoadStrategy::OnlyCurrent).has_value()); EXPECT_EQ(storage_mid.get_value(setting), SETTING_VALUE); } { storage_mid.clear(); prepare_load(ss_new); ASSERT_FALSE(storage_mid.load(ss_new, LoadStrategy::OnlyCurrent).has_value()); } // Try to load other configuration by MigrateOld strategy { storage_mid.clear(); prepare_load(ss_old); ASSERT_TRUE(storage_mid.load(ss_old, LoadStrategy::MigrateOld).has_value()); EXPECT_EQ(storage_mid.get_value(setting), SETTING_VALUE); } { storage_mid.clear(); prepare_load(ss_mid); ASSERT_TRUE(storage_mid.load(ss_mid, LoadStrategy::MigrateOld).has_value()); EXPECT_EQ(storage_mid.get_value(setting), SETTING_VALUE); } { storage_mid.clear(); prepare_load(ss_new); ASSERT_FALSE(storage_mid.load(ss_new, LoadStrategy::MigrateOld).has_value()); } // Try to load other configuration by AcceptAll strategy { storage_mid.clear(); prepare_load(ss_old); ASSERT_TRUE(storage_mid.load(ss_old, LoadStrategy::AcceptAll).has_value()); EXPECT_EQ(storage_mid.get_value(setting), SETTING_VALUE); } { storage_mid.clear(); prepare_load(ss_mid); ASSERT_TRUE(storage_mid.load(ss_mid, LoadStrategy::AcceptAll).has_value()); EXPECT_EQ(storage_mid.get_value(setting), SETTING_VALUE); } { storage_mid.clear(); prepare_load(ss_new); ASSERT_TRUE(storage_mid.load(ss_new, LoadStrategy::AcceptAll).has_value()); EXPECT_EQ(storage_mid.get_value(setting), SETTING_VALUE); } } #pragma endregion } // namespace yycctest::carton::binstore