blob: 1fe52dd36b90b0f40758b8728a738dd648dc8d7d [file] [log] [blame]
//
// 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 <array>
#include <limits>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_util.h>
#include <gtest/gtest.h>
#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") {}
void SetUp() override {
ScopeLogger::GetInstance()->EnableScopesByName("+storage");
ASSERT_FALSE(base::IsStringUTF8(kNonUtf8String));
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
test_file_ = temp_dir_.GetPath().Append("test-json-store");
store_.reset(new JsonStore(test_file_));
EXPECT_CALL(log_, Log(_, _, _)).Times(AnyNumber());
}
void TearDown() override {
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<JsonStore> 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<string, 5> 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<bool, 2> 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<int, 3> our_values{{
std::numeric_limits<int>::min(), 0, std::numeric_limits<int>::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<uint64_t, 3> our_values{{
std::numeric_limits<uint64_t>::min(),
0,
std::numeric_limits<uint64_t>::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<vector<string>, 7> our_values{{
vector<string>{},
vector<string>{""},
vector<string>{"a"},
vector<string>{"", "a"},
vector<string>{"a", ""},
vector<string>{"", "a", ""},
vector<string>{"a", "b", "c", kStringWithEmbeddedNulls, kNonUtf8String}}};
for (const auto& our_value : our_values) {
vector<string> 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<string, 5> 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<string, 3> 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<string>{{"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<string>{{"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<string>(), store_->GetGroups());
EXPECT_EQ(set<string>(), store_->GetGroupsWithKey("knob_1"));
EXPECT_EQ(set<string>(), store_->GetGroupsWithProperties(KeyValueStore()));
}
TEST_F(JsonStoreTest, GetGroupsReturnsAllGroups) {
store_->SetBool("group_a", "knob_1", bool());
store_->SetBool("group_b", "knob_1", bool());
EXPECT_EQ(set<string>({"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<string>({"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<string>({"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<string>({"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<string>({"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<string>({"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 brillo::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<string>({"group_a"}),
store_->GetGroupsWithProperties(correct_properties));
}
const vector<pair<string, brillo::Any>> 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;
brillo::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<string>(), 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_.GetPath(), 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_.GetPath(), 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, FlushFailsWhenPathIsNotWriteable) {
ASSERT_TRUE(base::CreateDirectory(test_file_));
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<uint64_t>::max());
store_->SetStringList(
"group_a", "stringlist_knob", vector<string>{"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<string>({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<string>({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<string>{"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, 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