// // Copyright (C) 2015 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/json_store.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "shill/mock_log.h" using base::FileEnumerator; using base::FilePath; using base::ScopedTempDir; using std::array; using std::pair; using std::set; using std::string; using std::unique_ptr; using std::vector; using testing::_; using testing::AnyNumber; using testing::ContainsRegex; using testing::HasSubstr; using testing::StartsWith; using testing::Test; namespace shill { class JsonStoreTest : public Test { public: JsonStoreTest() : kStringWithEmbeddedNulls({0, 'a', 0, 'z'}), kNonUtf8String("ab\xc0") {} virtual void SetUp() { ScopeLogger::GetInstance()->EnableScopesByName("+storage"); ASSERT_FALSE(base::IsStringUTF8(kNonUtf8String)); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); test_file_ = temp_dir_.path().Append("test-json-store"); store_.reset(new JsonStore(test_file_)); EXPECT_CALL(log_, Log(_, _, _)).Times(AnyNumber()); } virtual void TearDown() { ScopeLogger::GetInstance()->EnableScopesByName("-storage"); ScopeLogger::GetInstance()->set_verbose_level(0); } protected: void SetVerboseLevel(int new_level); void SetJsonFileContents(const string& data); const string kStringWithEmbeddedNulls; const string kNonUtf8String; ScopedTempDir temp_dir_; FilePath test_file_; unique_ptr store_; ScopedMockLog log_; }; void JsonStoreTest::SetVerboseLevel(int new_level) { ScopeLogger::GetInstance()->set_verbose_level(new_level); } void JsonStoreTest::SetJsonFileContents(const string& data) { EXPECT_EQ(data.size(), base::WriteFile(test_file_, data.data(), data.size())); } // In memory operations: basic storage and retrieval. TEST_F(JsonStoreTest, StringsCanBeStoredInMemory) { const array our_values{ {"", "hello", "world\n", kStringWithEmbeddedNulls, kNonUtf8String}}; for (const auto& our_value : our_values) { string value_from_store; EXPECT_TRUE(store_->SetString("group_a", "knob_1", our_value)); EXPECT_TRUE(store_->GetString("group_a", "knob_1", &value_from_store)); EXPECT_EQ(our_value, value_from_store); } } TEST_F(JsonStoreTest, BoolsCanBeStoredInMemory) { const array our_values{{false, true}}; for (const auto& our_value : our_values) { bool value_from_store; EXPECT_TRUE(store_->SetBool("group_a", "knob_1", our_value)); EXPECT_TRUE(store_->GetBool("group_a", "knob_1", &value_from_store)); EXPECT_EQ(our_value, value_from_store); } } TEST_F(JsonStoreTest, IntsCanBeStoredInMemory) { const array our_values{{ std::numeric_limits::min(), 0, std::numeric_limits::max()}}; for (const auto& our_value : our_values) { int value_from_store; EXPECT_TRUE(store_->SetInt("group_a", "knob_1", our_value)); EXPECT_TRUE(store_->GetInt("group_a", "knob_1", &value_from_store)); EXPECT_EQ(our_value, value_from_store); } } TEST_F(JsonStoreTest, Uint64sCanBeStoredInMemory) { const array our_values{{ std::numeric_limits::min(), 0, std::numeric_limits::max()}}; for (const auto& our_value : our_values) { uint64_t value_from_store; EXPECT_TRUE(store_->SetUint64("group_a", "knob_1", our_value)); EXPECT_TRUE(store_->GetUint64("group_a", "knob_1", &value_from_store)); EXPECT_EQ(our_value, value_from_store); } } TEST_F(JsonStoreTest, StringListsCanBeStoredInMemory) { const array, 7> our_values{{ vector{}, vector{""}, vector{"a"}, vector{"", "a"}, vector{"a", ""}, vector{"", "a", ""}, vector{"a", "b", "c", kStringWithEmbeddedNulls, kNonUtf8String}}}; for (const auto& our_value : our_values) { vector value_from_store; EXPECT_TRUE(store_->SetStringList("group_a", "knob_1", our_value)); EXPECT_TRUE(store_->GetStringList("group_a", "knob_1", &value_from_store)); EXPECT_EQ(our_value, value_from_store); } } TEST_F(JsonStoreTest, CryptedStringsCanBeStoredInMemory) { const array our_values{{ string(), string("some stuff"), kStringWithEmbeddedNulls, kNonUtf8String }}; for (const auto& our_value : our_values) { string value_from_store; EXPECT_TRUE(store_->SetCryptedString("group_a", "knob_1", our_value)); EXPECT_TRUE( store_->GetCryptedString("group_a", "knob_1", &value_from_store)); EXPECT_EQ(our_value, value_from_store); } } TEST_F(JsonStoreTest, RawValuesOfCryptedStringsDifferFromOriginalValues) { const array our_values{{ string("simple string"), kStringWithEmbeddedNulls, kNonUtf8String }}; for (const auto& our_value : our_values) { string raw_value_from_store; EXPECT_TRUE(store_->SetCryptedString("group_a", "knob_1", our_value)); EXPECT_TRUE(store_->GetString("group_a", "knob_1", &raw_value_from_store)); EXPECT_NE(our_value, raw_value_from_store); } } TEST_F(JsonStoreTest, DifferentGroupsCanHaveDifferentValuesForSameKey) { store_->SetString("group_a", "knob_1", "value_1"); store_->SetString("group_b", "knob_1", "value_2"); string value_from_store; EXPECT_TRUE(store_->GetString("group_a", "knob_1", &value_from_store)); EXPECT_EQ("value_1", value_from_store); EXPECT_TRUE(store_->GetString("group_b", "knob_1", &value_from_store)); EXPECT_EQ("value_2", value_from_store); } // In memory operations: presence checking. TEST_F(JsonStoreTest, CanUseNullptrToCheckPresenceOfKey) { SetVerboseLevel(10); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find group"))).Times(6); EXPECT_FALSE(store_->GetString("group_a", "string_knob", nullptr)); EXPECT_FALSE(store_->GetBool("group_a", "bool_knob", nullptr)); EXPECT_FALSE(store_->GetInt("group_a", "int_knob", nullptr)); EXPECT_FALSE(store_->GetUint64("group_a", "uint64_knob", nullptr)); EXPECT_FALSE(store_->GetStringList("group_a", "string_list_knob", nullptr)); EXPECT_FALSE( store_->GetCryptedString("group_a", "crypted_string_knob", nullptr)); ASSERT_TRUE(store_->SetString("group_a", "random_knob", "random value")); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find property"))).Times(6); EXPECT_FALSE(store_->GetString("group_a", "string_knob", nullptr)); EXPECT_FALSE(store_->GetBool("group_a", "bool_knob", nullptr)); EXPECT_FALSE(store_->GetInt("group_a", "int_knob", nullptr)); EXPECT_FALSE(store_->GetUint64("group_a", "uint64_knob", nullptr)); EXPECT_FALSE(store_->GetStringList("group_a", "string_list_knob", nullptr)); EXPECT_FALSE( store_->GetCryptedString("group_a", "crypted_string_knob", nullptr)); ASSERT_TRUE(store_->SetString("group_a", "string_knob", "stuff goes here")); ASSERT_TRUE(store_->SetBool("group_a", "bool_knob", true)); ASSERT_TRUE(store_->SetInt("group_a", "int_knob", -1)); ASSERT_TRUE(store_->SetUint64("group_a", "uint64_knob", 1)); ASSERT_TRUE(store_->SetStringList( "group_a", "string_list_knob", vector{{"hello"}})); ASSERT_TRUE( store_->SetCryptedString("group_a", "crypted_string_knob", "s3kr!t")); EXPECT_TRUE(store_->GetString("group_a", "string_knob", nullptr)); EXPECT_TRUE(store_->GetBool("group_a", "bool_knob", nullptr)); EXPECT_TRUE(store_->GetInt("group_a", "int_knob", nullptr)); EXPECT_TRUE(store_->GetUint64("group_a", "uint64_knob", nullptr)); EXPECT_TRUE(store_->GetStringList("group_a", "string_list_knob", nullptr)); EXPECT_TRUE( store_->GetCryptedString("group_a", "crypted_string_knob", nullptr)); } // In memory operations: access to missing elements. TEST_F(JsonStoreTest, GetFromEmptyStoreFails) { bool value_from_store; SetVerboseLevel(10); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find group"))); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", &value_from_store)); } TEST_F(JsonStoreTest, GetFromNonexistentGroupAndKeyFails) { bool value_from_store; SetVerboseLevel(10); EXPECT_TRUE(store_->SetBool("group_a", "knob_1", true)); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find group"))); EXPECT_FALSE(store_->GetBool("group_b", "knob_1", &value_from_store)); } TEST_F(JsonStoreTest, GetOfNonexistentPropertyFails) { bool value_from_store; SetVerboseLevel(10); EXPECT_TRUE(store_->SetBool("group_a", "knob_1", true)); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find property"))); EXPECT_FALSE(store_->GetBool("group_a", "knob_2", &value_from_store)); } TEST_F(JsonStoreTest, GetOfPropertyFromWrongGroupFails) { bool value_from_store; SetVerboseLevel(10); EXPECT_TRUE(store_->SetBool("group_a", "knob_1", true)); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find group"))); EXPECT_FALSE(store_->GetBool("group_b", "knob_1", &value_from_store)); } TEST_F(JsonStoreTest, GetDoesNotMatchOnValue) { string value_from_store; SetVerboseLevel(10); EXPECT_TRUE(store_->SetString("group_a", "knob_1", "value_1")); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find property"))); EXPECT_FALSE(store_->GetString("group_a", "value_1", &value_from_store)); } // In memory operations: type conversions on read. TEST_F(JsonStoreTest, ConversionFromStringIsProhibited) { EXPECT_CALL( log_, Log(logging::LOG_ERROR, _, ContainsRegex("Can not read \\|.+\\| from \\|.+\\|"))).Times(4); EXPECT_TRUE(store_->SetString("group_a", "knob_1", "stuff goes here")); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetInt("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetUint64("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetStringList("group_a", "knob_1", nullptr)); // We deliberately omit checking store_->GetCryptedString(). While // this "works" right now, it's not something we're committed to. } TEST_F(JsonStoreTest, ConversionFromBoolIsProhibited) { EXPECT_CALL( log_, Log(logging::LOG_ERROR, _, ContainsRegex("Can not read \\|.+\\| from \\|.+\\|"))).Times(5); EXPECT_TRUE(store_->SetBool("group_a", "knob_1", true)); EXPECT_FALSE(store_->GetString("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetInt("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetUint64("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetStringList("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetCryptedString("group_a", "knob_1", nullptr)); } TEST_F(JsonStoreTest, ConversionFromIntIsProhibited) { EXPECT_CALL( log_, Log(logging::LOG_ERROR, _, ContainsRegex("Can not read \\|.+\\| from \\|.+\\|"))).Times(5); EXPECT_TRUE(store_->SetInt("group_a", "knob_1", -1)); EXPECT_FALSE(store_->GetString("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetUint64("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetStringList("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetCryptedString("group_a", "knob_1", nullptr)); } TEST_F(JsonStoreTest, ConversionFromUint64IsProhibited) { EXPECT_CALL( log_, Log(logging::LOG_ERROR, _, ContainsRegex("Can not read \\|.+\\| from \\|.+\\|"))).Times(5); EXPECT_TRUE(store_->SetUint64("group_a", "knob_1", 1)); EXPECT_FALSE(store_->GetString("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetInt("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetStringList("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetCryptedString("group_a", "knob_1", nullptr)); } TEST_F(JsonStoreTest, ConversionFromStringListIsProhibited) { EXPECT_CALL( log_, Log(logging::LOG_ERROR, _, ContainsRegex("Can not read \\|.+\\| from \\|.+\\|"))).Times(5); EXPECT_TRUE(store_->SetStringList( "group_a", "knob_1", vector{{"hello"}})); EXPECT_FALSE(store_->GetString("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetInt("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetUint64("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetCryptedString("group_a", "knob_1", nullptr)); } TEST_F(JsonStoreTest, ConversionFromCryptedStringIsProhibited) { EXPECT_CALL( log_, Log(logging::LOG_ERROR, _, ContainsRegex("Can not read \\|.+\\| from \\|.+\\|"))).Times(4); EXPECT_TRUE(store_->SetCryptedString("group_a", "knob_1", "s3kr!t")); // We deliberately omit checking store_->GetString(). While this // "works" right now, it's not something we're committed to. EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetInt("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetUint64("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetStringList("group_a", "knob_1", nullptr)); } // In memory operations: key deletion. TEST_F(JsonStoreTest, DeleteKeyDeletesExistingKey) { SetVerboseLevel(10); store_->SetBool("group_a", "knob_1", bool()); EXPECT_TRUE(store_->DeleteKey("group_a", "knob_1")); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find property"))); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); } TEST_F(JsonStoreTest, DeleteKeyDeletesOnlySpecifiedKey) { store_->SetBool("group_a", "knob_1", bool()); store_->SetBool("group_a", "knob_2", bool()); EXPECT_TRUE(store_->DeleteKey("group_a", "knob_1")); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_TRUE(store_->GetBool("group_a", "knob_2", nullptr)); } TEST_F(JsonStoreTest, DeleteKeySucceedsOnMissingKey) { store_->SetBool("group_a", "knob_1", bool()); EXPECT_TRUE(store_->DeleteKey("group_a", "knob_2")); EXPECT_TRUE(store_->GetBool("group_a", "knob_1", nullptr)); } TEST_F(JsonStoreTest, DeleteKeyFailsWhenGivenWrongGroup) { SetVerboseLevel(10); store_->SetBool("group_a", "knob_1", bool()); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find group"))); EXPECT_FALSE(store_->DeleteKey("group_b", "knob_1")); EXPECT_TRUE(store_->GetBool("group_a", "knob_1", nullptr)); } // In memory operations: group operations. TEST_F(JsonStoreTest, EmptyStoreReturnsNoGroups) { EXPECT_EQ(set(), store_->GetGroups()); EXPECT_EQ(set(), store_->GetGroupsWithKey("knob_1")); EXPECT_EQ(set(), store_->GetGroupsWithProperties(KeyValueStore())); } TEST_F(JsonStoreTest, GetGroupsReturnsAllGroups) { store_->SetBool("group_a", "knob_1", bool()); store_->SetBool("group_b", "knob_1", bool()); EXPECT_EQ(set({"group_a", "group_b"}), store_->GetGroups()); } TEST_F(JsonStoreTest, GetGroupsWithKeyReturnsAllMatchingGroups) { store_->SetBool("group_a", "knob_1", bool()); store_->SetBool("group_b", "knob_1", bool()); EXPECT_EQ(set({"group_a", "group_b"}), store_->GetGroupsWithKey("knob_1")); } TEST_F(JsonStoreTest, GetGroupsWithKeyReturnsOnlyMatchingGroups) { store_->SetBool("group_a", "knob_1", bool()); store_->SetBool("group_b", "knob_2", bool()); EXPECT_EQ(set({"group_a"}), store_->GetGroupsWithKey("knob_1")); } TEST_F(JsonStoreTest, GetGroupsWithPropertiesReturnsAllMatchingGroups) { store_->SetBool("group_a", "knob_1", true); store_->SetBool("group_b", "knob_1", true); KeyValueStore required_properties; required_properties.SetBool("knob_1", true); EXPECT_EQ(set({"group_a", "group_b"}), store_->GetGroupsWithProperties(required_properties)); } TEST_F(JsonStoreTest, GetGroupsWithPropertiesReturnsOnlyMatchingGroups) { store_->SetBool("group_a", "knob_1", true); store_->SetBool("group_b", "knob_1", false); KeyValueStore required_properties; required_properties.SetBool("knob_1", true); EXPECT_EQ(set({"group_a"}), store_->GetGroupsWithProperties(required_properties)); } TEST_F(JsonStoreTest, GetGroupsWithPropertiesCanMatchOnMultipleProperties) { store_->SetBool("group_a", "knob_1", true); store_->SetBool("group_a", "knob_2", true); store_->SetBool("group_b", "knob_1", true); store_->SetBool("group_b", "knob_2", false); KeyValueStore required_properties; required_properties.SetBool("knob_1", true); required_properties.SetBool("knob_2", true); EXPECT_EQ(set({"group_a"}), store_->GetGroupsWithProperties(required_properties)); } TEST_F(JsonStoreTest, GetGroupsWithPropertiesChecksValuesForBoolIntAndString) { // Documentation in StoreInterface says GetGroupsWithProperties // checks only Bool, Int, and String properties. For now, we interpret // that permissively. i.e., checking other types is not guaranteed one // way or the other. // // Said differently: we test that that Bool, Int, and String are // supported. But we don't test that other types are ignored. (In // fact, JsonStore supports filtering on uint64 and StringList as // well. JsonStore does not, however, support filtering on // CryptedStrings.) // // This should be fine, as StoreInterface clients currently only use // String value filtering. const chromeos::VariantDictionary exact_matcher({ {"knob_1", string("good-string")}, {"knob_2", bool{true}}, {"knob_3", int{1}}, }); store_->SetString("group_a", "knob_1", "good-string"); store_->SetBool("group_a", "knob_2", true); store_->SetInt("group_a", "knob_3", 1); { KeyValueStore correct_properties; KeyValueStore::ConvertFromVariantDictionary( exact_matcher, &correct_properties); EXPECT_EQ(set({"group_a"}), store_->GetGroupsWithProperties(correct_properties)); } const vector> bad_matchers({ {"knob_1", string("bad-string")}, {"knob_2", bool{false}}, {"knob_3", int{2}}, }); for (const auto& match_key_and_value : bad_matchers) { const auto& match_key = match_key_and_value.first; const auto& match_value = match_key_and_value.second; chromeos::VariantDictionary bad_matcher_dict(exact_matcher); KeyValueStore bad_properties; bad_matcher_dict[match_key] = match_value; KeyValueStore::ConvertFromVariantDictionary( bad_matcher_dict, &bad_properties); EXPECT_EQ(set(), store_->GetGroupsWithProperties(bad_properties)) << "Failing match key: " << match_key; } } TEST_F(JsonStoreTest, ContainsGroupFindsExistingGroup) { store_->SetBool("group_a", "knob_1", bool()); EXPECT_TRUE(store_->ContainsGroup("group_a")); } TEST_F(JsonStoreTest, ContainsGroupDoesNotFabricateGroups) { EXPECT_FALSE(store_->ContainsGroup("group_a")); } TEST_F(JsonStoreTest, DeleteGroupDeletesExistingGroup) { SetVerboseLevel(10); store_->SetBool("group_a", "knob_1", bool()); store_->SetBool("group_a", "knob_2", bool()); EXPECT_TRUE(store_->DeleteGroup("group_a")); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find group"))).Times(2); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_FALSE(store_->GetBool("group_a", "knob_2", nullptr)); } TEST_F(JsonStoreTest, DeleteGroupDeletesOnlySpecifiedGroup) { store_->SetBool("group_a", "knob_1", bool()); store_->SetBool("group_b", "knob_1", bool()); EXPECT_TRUE(store_->DeleteGroup("group_a")); EXPECT_FALSE(store_->GetBool("group_a", "knob_1", nullptr)); EXPECT_TRUE(store_->GetBool("group_b", "knob_1", nullptr)); } TEST_F(JsonStoreTest, DeleteGroupSucceedsOnMissingGroup) { store_->SetBool("group_a", "knob_1", bool()); EXPECT_TRUE(store_->DeleteGroup("group_b")); EXPECT_TRUE(store_->GetBool("group_a", "knob_1", nullptr)); } // File open: basic file structure. TEST_F(JsonStoreTest, OpenSucceedsOnNonExistentFile) { // If the file does not already exist, we assume the caller will // give us data later. EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnNonJsonData) { SetJsonFileContents("some random junk"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Failed to parse JSON data"))); EXPECT_FALSE(store_->Open()); } // File open: root element handling. TEST_F(JsonStoreTest, OpenFailsWhenRootIsNonDictionary) { SetJsonFileContents("\"a string\""); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("JSON value is not a dictionary"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenWarnsOnRootDictionaryWithNonStringDescription) { SetJsonFileContents("{\"description\": 1}"); EXPECT_CALL( log_, Log(logging::LOG_WARNING, _, HasSubstr("|description| is not a string"))); store_->Open(); } TEST_F(JsonStoreTest, OpenFailsOnRootDictionaryWithoutSettings) { SetJsonFileContents("{}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Property |settings| is missing"))); EXPECT_FALSE(store_->Open()); } // File open: settings element handling. TEST_F(JsonStoreTest, OpenSucceedsOnEmptySettings) { SetJsonFileContents("{\"settings\": {}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsWhenSettingsIsNonDictionary) { SetJsonFileContents("{\"settings\": 1}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Property |settings| is not a dictionary"))); EXPECT_FALSE(store_->Open()); } // File open: group structure. TEST_F(JsonStoreTest, OpenSucceedsOnEmptyGroup) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {}" "}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsWhenGroupIsNonDictionary) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": 1" "}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Group |group_a| is not a dictionary"))); EXPECT_FALSE(store_->Open()); } // File open: each supported property type (with selected valid // values for each type), ordered by base::Value::Type enum. Types // which are not supported by base::Value are ordered as // TYPE_DICTIONARY. TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithBooleanValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": true" "}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithMinIntegerValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": -2147483648" // -2^31 "}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithMaxIntegerValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": 2147483647" // 2^31-1 "}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithStringValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": \"this is \\\"a\\\" string\\n\"" "}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithEscapedStringValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": \"non_ascii_string\"," " \"_encoded_value\": \"0001020304\"" "}}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithMinUint64Value) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": \"uint64\"," " \"_encoded_value\": \"0\"" // 2^64-1 "}}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithMaxUint64Value) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": \"uint64\"," " \"_encoded_value\": \"18446744073709551615\"" // 2^64-1 "}}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithEmptyListValue) { // Empty list is presumed to be an empty string list. SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": []" "}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWithStringListValueWithSingleItem) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": [ \"a string\" ]" "}}}"); EXPECT_TRUE(store_->Open()); } TEST_F( JsonStoreTest, OpenSucceedsOnSettingWithStringListValueWithMultipleItems) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": [ \"string 1\", \"string 2\\n\" ]" "}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWhenStringListHasEscapedItem) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": [{" " \"_native_type\": \"non_ascii_string\"," " \"_encoded_value\": \"0001020304\"" "}]}}}"); EXPECT_TRUE(store_->Open()); } TEST_F(JsonStoreTest, OpenSucceedsOnSettingWhenStringListHasEscapedAndUnescapedItems) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": [" " {\"_native_type\": \"non_ascii_string\"," " \"_encoded_value\": \"0001020304\"}," " \"normal string\"" "]}}}"); EXPECT_TRUE(store_->Open()); } // File open: unsupported types, and invalid values. Ordered by // base::Value::Type enum. Types which are supported by JsonStore, // but not directly supported by base::Value, are ordered as // TYPE_DICTIONARY. TEST_F(JsonStoreTest, OpenFailsOnSettingWithNullValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": null" "}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, HasSubstr("has unsupported TYPE_NULL"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithBadBooleanValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": truthy" "}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Failed to parse JSON"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithOverlySmallInteger) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": -2147483649" // -2^31-1 "}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, HasSubstr("unsupported TYPE_DOUBLE"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithOverlyLargeInteger) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": 2147483648" // 2^31 "}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, HasSubstr("unsupported TYPE_DOUBLE"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithDoubleValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": 1.234" "}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, HasSubstr("unsupported TYPE_DOUBLE"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithDictionaryValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {}" "}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, HasSubstr("unsupported TYPE_DICTIONARY"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithOverlayLargeUint64Value) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": \"uint64\"," " \"_encoded_value\": \"18446744073709551616\"" // 2^64 "}}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Failed to parse uint64"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithOverlaySmallUint64Value) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": \"uint64\"," " \"_encoded_value\": \"-1\"" "}}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Failed to parse uint64"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsWhenSettingHasEscapedStringWithInvalidHex) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": \"non_ascii_string\"," " \"_encoded_value\": \"-1\"" "}}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Failed to decode hex"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsWhenSettingHasEscapedStringListItemWithInvalidHex) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": [{" " \"_native_type\": \"non_ascii_string\"," " \"_encoded_value\": \"-1\"" "}]}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Failed to decode hex"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnCoercedSettingWithBadNativeType) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": true," " \"_encoded_value\": \"1234\"" "}}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Property |_native_type| is not a string"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnCoercedSettingWhenEncodedValueIsNotAString) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": {" " \"_native_type\": \"uint64\"," " \"_encoded_value\": 1234" "}}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Property |_encoded_value| is not a string"))); EXPECT_FALSE(store_->Open()); } TEST_F(JsonStoreTest, OpenFailsOnSettingWithIntListValue) { SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_1\": [ 1 ]" "}}}"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, HasSubstr("instead of expected type"))); EXPECT_FALSE(store_->Open()); } // File open: miscellaneous. TEST_F(JsonStoreTest, OpenClearsExistingInMemoryData) { store_->SetString("group_a", "knob_1", "watch me disappear"); ASSERT_TRUE(store_->GetString("group_a", "knob_1", nullptr)); SetJsonFileContents( "{\"settings\": {" " \"group_a\": {" " \"knob_2\": \"new stuff\"" "}}}"); ASSERT_TRUE(store_->Open()); EXPECT_FALSE(store_->GetString("group_a", "knob_1", nullptr)); EXPECT_TRUE(store_->GetString("group_a", "knob_2", nullptr)); } TEST_F(JsonStoreTest, OpenClearsExistingInMemoryGroups) { store_->SetString("group_a", "knob_1", "watch me disappear"); ASSERT_FALSE(store_->GetGroups().empty()); // In the delete case, we're non-comittal about whether empty groups // are garbage collected. But, in the Open() case, we commit to // fully clearing in-memory data. SetJsonFileContents("{\"settings\": {}}"); ASSERT_TRUE(store_->Open()); EXPECT_TRUE(store_->GetGroups().empty()); } // File operations: Close() basic functionality. TEST_F(JsonStoreTest, ClosePersistsData) { ASSERT_FALSE(store_->IsNonEmpty()); ASSERT_TRUE(store_->Close()); // Verify that the file actually got written with the right name. FileEnumerator file_enumerator(temp_dir_.path(), false /* not recursive */, FileEnumerator::FILES); EXPECT_EQ(test_file_.value(), file_enumerator.Next().value()); // Verify that the profile is a regular file, readable and writeable by the // owner only. FileEnumerator::FileInfo file_info = file_enumerator.GetInfo(); EXPECT_EQ(S_IFREG | S_IRUSR | S_IWUSR, file_info.stat().st_mode); } // File operations: Flush() basics. TEST_F(JsonStoreTest, FlushCreatesPersistentStore) { ASSERT_FALSE(store_->IsNonEmpty()); ASSERT_TRUE(store_->Flush()); // Verify that the file actually got written with the right name. FileEnumerator file_enumerator(temp_dir_.path(), false /* not recursive */, FileEnumerator::FILES); EXPECT_EQ(test_file_.value(), file_enumerator.Next().value()); // Verify that the profile is a regular file, readable and writeable by the // owner only. FileEnumerator::FileInfo file_info = file_enumerator.GetInfo(); EXPECT_EQ(S_IFREG | S_IRUSR | S_IWUSR, file_info.stat().st_mode); } TEST_F(JsonStoreTest, FlushFailsWhenPathIsEmpty) { store_->path_ = FilePath(); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Empty key file path"))); EXPECT_FALSE(store_->Flush()); } TEST_F(JsonStoreTest, FlushFailsWhenPathComponentDoesNotExist) { store_->path_ = temp_dir_.path().Append("non-existent-dir").Append("test-store"); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Failed to write"))); EXPECT_FALSE(store_->Flush()); } // File operations: writing. // // The ordering of groups, and the ordering of keys within a group, // are decided by the JSON writer. Hence, we can not simply compare // the written data to an expected literal value. // // Instead, we write the data out, and verify that reading the data // yields the same groups, keys, and values. TEST_F(JsonStoreTest, CanPersistAndRestoreHeader) { store_->SetHeader("rosetta stone"); ASSERT_EQ("rosetta stone", store_->file_description_); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->file_description_, persisted_data.file_description_); } TEST_F(JsonStoreTest, CanPersistAndRestoreAllTypes) { store_->SetString("group_a", "string_knob", "our string"); store_->SetBool("group_a", "bool_knob", true); store_->SetInt("group_a", "int_knob", 1); store_->SetUint64( "group_a", "uint64_knob", std::numeric_limits::max()); store_->SetStringList( "group_a", "stringlist_knob", vector{"a", "b", "c"}); store_->SetCryptedString("group_a", "cryptedstring_knob", "s3kr!t"); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->group_name_to_settings_, persisted_data.group_name_to_settings_); } TEST_F(JsonStoreTest, CanPersistAndRestoreNonUtf8Strings) { store_->SetString("group_a", "string_knob", kNonUtf8String); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->group_name_to_settings_, persisted_data.group_name_to_settings_); } TEST_F(JsonStoreTest, CanPersistAndRestoreNonUtf8StringList) { store_->SetStringList( "group_a", "string_knob", vector({kNonUtf8String})); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->group_name_to_settings_, persisted_data.group_name_to_settings_); } TEST_F(JsonStoreTest, CanPersistAndRestoreStringsWithEmbeddedNulls) { store_->SetString("group_a", "string_knob", kStringWithEmbeddedNulls); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->group_name_to_settings_, persisted_data.group_name_to_settings_); } TEST_F(JsonStoreTest, CanPersistAndRestoreStringListWithEmbeddedNulls) { store_->SetStringList( "group_a", "string_knob", vector({kStringWithEmbeddedNulls})); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->group_name_to_settings_, persisted_data.group_name_to_settings_); } TEST_F(JsonStoreTest, CanPersistAndRestoreMultipleGroups) { store_->SetString("group_a", "knob_1", "first string"); store_->SetString("group_b", "knob_2", "second string"); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->group_name_to_settings_, persisted_data.group_name_to_settings_); } TEST_F(JsonStoreTest, CanPersistAndRestoreMultipleGroupsWithSameKeys) { store_->SetString("group_a", "knob_1", "first string"); store_->SetString("group_a", "knob_2", "second string"); store_->SetString("group_b", "knob_1", "frist post!"); store_->SetStringList("group_b", "knob_2", vector{"2nd try"}); store_->Flush(); JsonStore persisted_data(test_file_); persisted_data.Open(); EXPECT_EQ( store_->group_name_to_settings_, persisted_data.group_name_to_settings_); } TEST_F(JsonStoreTest, CanDeleteKeyFromPersistedData) { store_->SetString("group_a", "knob_1", "first string"); store_->Flush(); JsonStore persisted_data_v1(test_file_); persisted_data_v1.Open(); ASSERT_TRUE(persisted_data_v1.GetString("group_a", "knob_1", nullptr)); store_->DeleteKey("group_a", "knob_1"); store_->Flush(); JsonStore persisted_data_v2(test_file_); SetVerboseLevel(10); // Whether an empty group is written or not is an implementation // detail. Hence, we don't care if the error message is about a // missing group, or a missing property. EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find"))); EXPECT_FALSE(persisted_data_v2.GetString("group_a", "knob_1", nullptr)); } TEST_F(JsonStoreTest, CanDeleteGroupFromPersistedData) { store_->SetString("group_a", "knob_1", "first string"); store_->Flush(); JsonStore persisted_data_v1(test_file_); persisted_data_v1.Open(); ASSERT_TRUE(persisted_data_v1.GetString("group_a", "knob_1", nullptr)); store_->DeleteGroup("group_a"); store_->Flush(); JsonStore persisted_data_v2(test_file_); SetVerboseLevel(10); persisted_data_v2.Open(); EXPECT_CALL(log_, Log(_, _, HasSubstr("Could not find group"))); EXPECT_FALSE(persisted_data_v2.GetString("group_a", "knob_1", nullptr)); } // File operations: file management. TEST_F(JsonStoreTest, MarkAsCorruptedFailsWhenPathIsNotSet) { store_->path_ = FilePath(); EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, StartsWith("Empty key file path"))); EXPECT_FALSE(store_->MarkAsCorrupted()); } TEST_F(JsonStoreTest, MarkAsCorruptedFailsWhenStoreHasNotBeenPersisted) { EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, HasSubstr("rename failed"))); EXPECT_FALSE(store_->MarkAsCorrupted()); } TEST_F(JsonStoreTest, MarkAsCorruptedMovesCorruptStore) { store_->Flush(); ASSERT_TRUE(store_->IsNonEmpty()); ASSERT_TRUE(base::PathExists(test_file_)); EXPECT_TRUE(store_->MarkAsCorrupted()); EXPECT_FALSE(store_->IsNonEmpty()); EXPECT_FALSE(base::PathExists(test_file_)); EXPECT_TRUE(base::PathExists(FilePath(test_file_.value() + ".corrupted"))); } } // namespace shill