shill: Basic persistent store interface and key-file implementation.
The current interface and implementation support all functionality required by
the legacy network manager. The implementation uses glib's key file parser for
legacy reasons. The interface-based implementation allows us to switch to a
different format in the future, if necessary.
BUG=chromium-os:16897
TEST=unit test
Change-Id: I8fd54f47e7309c603b66ba86bbecb8d5092e8674
Reviewed-on: http://gerrit.chromium.org/gerrit/3160
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/Makefile b/Makefile
index b1ad0f1..23fda97 100644
--- a/Makefile
+++ b/Makefile
@@ -59,6 +59,7 @@
glib.o \
glib_io_handler.o \
ipconfig.o \
+ key_file_store.o \
manager.o \
manager_dbus_adaptor.o \
property_store.o \
@@ -85,6 +86,7 @@
dhcp_config_unittest.o \
dhcp_provider_unittest.o \
ipconfig_unittest.o \
+ key_file_store_unittest.o \
manager_unittest.o \
mock_control.o \
mock_device.o \
diff --git a/glib.cc b/glib.cc
index 13545ac..51ee0cd 100644
--- a/glib.cc
+++ b/glib.cc
@@ -2,18 +2,120 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <base/stringprintf.h>
+
#include "shill/glib.h"
namespace shill {
GLib::~GLib() {}
+std::string GLib::ConvertErrorToMessage(GError *error) {
+ if (!error) {
+ return "Unknown GLib error.";
+ }
+ std::string message =
+ base::StringPrintf("GError(%d): %s", error->code, error->message);
+ g_error_free(error);
+ return message;
+}
+
guint GLib::ChildWatchAdd(GPid pid,
GChildWatchFunc function,
gpointer data) {
return g_child_watch_add(pid, function, data);
}
+void GLib::Free(gpointer mem) {
+ g_free(mem);
+}
+
+void GLib::KeyFileFree(GKeyFile *key_file) {
+ g_key_file_free(key_file);
+}
+
+gboolean GLib::KeyFileLoadFromFile(GKeyFile *key_file,
+ const gchar *file,
+ GKeyFileFlags flags,
+ GError **error) {
+ return g_key_file_load_from_file(key_file, file, flags, error);
+}
+
+gboolean GLib::KeyFileGetBoolean(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error) {
+ return g_key_file_get_boolean(key_file, group_name, key, error);
+}
+
+gchar **GLib::KeyFileGetGroups(GKeyFile *key_file,
+ gsize *length) {
+ return g_key_file_get_groups(key_file, length);
+}
+
+gint GLib::KeyFileGetInteger(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error) {
+ return g_key_file_get_integer(key_file, group_name, key, error);
+}
+
+gchar *GLib::KeyFileGetString(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error) {
+ return g_key_file_get_string(key_file, group_name, key, error);
+}
+
+gboolean GLib::KeyFileHasGroup(GKeyFile *key_file,
+ const gchar *group_name) {
+ return g_key_file_has_group(key_file, group_name);
+}
+
+GKeyFile *GLib::KeyFileNew() {
+ return g_key_file_new();
+}
+
+void GLib::KeyFileRemoveGroup(GKeyFile *key_file,
+ const gchar *group_name,
+ GError **error) {
+ g_key_file_remove_group(key_file, group_name, error);
+}
+
+void GLib::KeyFileRemoveKey(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error) {
+ g_key_file_remove_key(key_file, group_name, key, error);
+}
+
+void GLib::KeyFileSetBoolean(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ gboolean value) {
+ g_key_file_set_boolean(key_file, group_name, key, value);
+}
+
+void GLib::KeyFileSetInteger(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ gint value) {
+ g_key_file_set_integer(key_file, group_name, key, value);
+}
+
+void GLib::KeyFileSetString(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ const gchar *string) {
+ g_key_file_set_string(key_file, group_name, key, string);
+}
+
+gchar *GLib::KeyFileToData(GKeyFile *key_file,
+ gsize *length,
+ GError **error) {
+ return g_key_file_to_data(key_file, length, error);
+}
+
gboolean GLib::SourceRemove(guint tag) {
return g_source_remove(tag);
}
@@ -40,4 +142,8 @@
g_spawn_close_pid(pid);
}
+void GLib::Strfreev(gchar **str_array) {
+ g_strfreev(str_array);
+}
+
} // namespace shill
diff --git a/glib.h b/glib.h
index fea6056..186c075 100644
--- a/glib.h
+++ b/glib.h
@@ -5,6 +5,8 @@
#ifndef SHILL_GLIB_H_
#define SHILL_GLIB_H_
+#include <string>
+
#include <glib.h>
namespace shill {
@@ -14,10 +16,73 @@
public:
virtual ~GLib();
+ // Converts GLib's |error| to a string message and frees the GError object.
+ virtual std::string ConvertErrorToMessage(GError *error);
+
// g_child_watch_add
virtual guint ChildWatchAdd(GPid pid,
GChildWatchFunc function,
gpointer data);
+ // g_free
+ virtual void Free(gpointer mem);
+ // g_key_file_free
+ virtual void KeyFileFree(GKeyFile *key_file);
+ // g_key_file_get_boolean
+ virtual gboolean KeyFileGetBoolean(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error);
+ // g_key_file_get_groups
+ virtual gchar **KeyFileGetGroups(GKeyFile *key_file,
+ gsize *length);
+ // g_key_file_get_integer
+ virtual gint KeyFileGetInteger(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error);
+ // g_key_file_get_string
+ virtual gchar *KeyFileGetString(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error);
+ // g_key_file_has_group
+ virtual gboolean KeyFileHasGroup(GKeyFile *key_file,
+ const gchar *group_name);
+ // g_key_file_load_from_file
+ virtual gboolean KeyFileLoadFromFile(GKeyFile *key_file,
+ const gchar *file,
+ GKeyFileFlags flags,
+ GError **error);
+ // g_key_file_new
+ virtual GKeyFile *KeyFileNew();
+ // g_key_file_remove_group
+ virtual void KeyFileRemoveGroup(GKeyFile *key_file,
+ const gchar *group_name,
+ GError **error);
+ // g_key_file_remove_key
+ virtual void KeyFileRemoveKey(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error);
+ // g_key_file_set_boolean
+ virtual void KeyFileSetBoolean(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ gboolean value);
+ // g_key_file_set_integer
+ virtual void KeyFileSetInteger(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ gint value);
+ // g_key_file_set_string
+ virtual void KeyFileSetString(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ const gchar *string);
+ // g_key_file_to_data
+ virtual gchar *KeyFileToData(GKeyFile *key_file,
+ gsize *length,
+ GError **error);
// g_source_remove
virtual gboolean SourceRemove(guint tag);
// g_spawn_async
@@ -31,6 +96,8 @@
GError **error);
// g_spawn_close_pid
virtual void SpawnClosePID(GPid pid);
+ // g_strfreev
+ virtual void Strfreev(gchar **str_array);
};
} // namespace shill
diff --git a/key_file_store.cc b/key_file_store.cc
new file mode 100644
index 0000000..d844e7e
--- /dev/null
+++ b/key_file_store.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2011 The Chromium OS 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 "shill/key_file_store.h"
+
+#include <base/file_util.h>
+#include <base/logging.h>
+
+using std::set;
+using std::string;
+
+namespace shill {
+
+KeyFileStore::KeyFileStore(GLib *glib) : glib_(glib), key_file_(NULL) {}
+
+KeyFileStore::~KeyFileStore() {
+ ReleaseKeyFile();
+}
+
+void KeyFileStore::ReleaseKeyFile() {
+ if (key_file_) {
+ glib_->KeyFileFree(key_file_);
+ key_file_ = NULL;
+ }
+}
+
+bool KeyFileStore::Open() {
+ CHECK(!path_.empty());
+ CHECK(!key_file_);
+ key_file_ = glib_->KeyFileNew();
+ int64 file_size = 0;
+ if (!file_util::GetFileSize(path_, &file_size) || file_size == 0) {
+ LOG(INFO) << "Creating a new key file at " << path_.value();
+ return true;
+ }
+ GError *error = NULL;
+ if (glib_->KeyFileLoadFromFile(
+ key_file_,
+ path_.value().c_str(),
+ static_cast<GKeyFileFlags>(G_KEY_FILE_KEEP_COMMENTS |
+ G_KEY_FILE_KEEP_TRANSLATIONS),
+ &error)) {
+ return true;
+ }
+ LOG(ERROR) << "Failed to load key file from " << path_.value() << ": "
+ << glib_->ConvertErrorToMessage(error);
+ ReleaseKeyFile();
+ return false;
+}
+
+bool KeyFileStore::Close() {
+ CHECK(key_file_);
+ GError *error = NULL;
+ gsize length = 0;
+ gchar *data = glib_->KeyFileToData(key_file_, &length, &error);
+ ReleaseKeyFile();
+
+ bool success = true;
+ if (path_.empty()) {
+ LOG(ERROR) << "Empty key file path.";
+ success = false;
+ }
+ if (success && (!data || error)) {
+ LOG(ERROR) << "Failed to convert key file to string: "
+ << glib_->ConvertErrorToMessage(error);
+ success = false;
+ }
+ if (success && file_util::WriteFile(path_, data, length) != length) {
+ LOG(ERROR) << "Failed to store key file: " << path_.value();
+ success = false;
+ }
+ glib_->Free(data);
+ return success;
+}
+
+set<string> KeyFileStore::GetGroups() {
+ CHECK(key_file_);
+ gsize length = 0;
+ gchar **groups = g_key_file_get_groups(key_file_, &length);
+ if (!groups) {
+ LOG(ERROR) << "Unable to obtain groups.";
+ return set<string>();
+ }
+ set<string> group_set(groups, groups + length);
+ glib_->Strfreev(groups);
+ return group_set;
+}
+
+bool KeyFileStore::ContainsGroup(const string &group) {
+ CHECK(key_file_);
+ return glib_->KeyFileHasGroup(key_file_, group.c_str());
+}
+
+bool KeyFileStore::DeleteKey(const string &group, const string &key) {
+ CHECK(key_file_);
+ GError *error = NULL;
+ glib_->KeyFileRemoveKey(key_file_, group.c_str(), key.c_str(), &error);
+ if (error) {
+ LOG(ERROR) << "Failed to delete (" << group << ":" << key << "): "
+ << glib_->ConvertErrorToMessage(error);
+ return false;
+ }
+ return true;
+}
+
+bool KeyFileStore::DeleteGroup(const string &group) {
+ CHECK(key_file_);
+ GError *error = NULL;
+ glib_->KeyFileRemoveGroup(key_file_, group.c_str(), &error);
+ if (error) {
+ LOG(ERROR) << "Failed to delete group " << group << "): "
+ << glib_->ConvertErrorToMessage(error);
+ return false;
+ }
+ return true;
+}
+
+bool KeyFileStore::GetString(const string &group,
+ const string &key,
+ string *value) {
+ CHECK(key_file_);
+ GError *error = NULL;
+ gchar *data =
+ glib_->KeyFileGetString(key_file_, group.c_str(), key.c_str(), &error);
+ if (!data) {
+ LOG(ERROR) << "Failed to lookup (" << group << ":" << key << "): "
+ << glib_->ConvertErrorToMessage(error);
+ return false;
+ }
+ if (value) {
+ *value = data;
+ }
+ glib_->Free(data);
+ return true;
+}
+
+bool KeyFileStore::SetString(const string &group,
+ const string &key,
+ const string &value) {
+ CHECK(key_file_);
+ glib_->KeyFileSetString(key_file_, group.c_str(), key.c_str(), value.c_str());
+ return true;
+}
+
+bool KeyFileStore::GetBool(const string &group,
+ const string &key,
+ bool *value) {
+ CHECK(key_file_);
+ GError *error = NULL;
+ gboolean data =
+ glib_->KeyFileGetBoolean(key_file_, group.c_str(), key.c_str(), &error);
+ if (error) {
+ LOG(ERROR) << "Failed to lookup (" << group << ":" << key << "): "
+ << glib_->ConvertErrorToMessage(error);
+ return false;
+ }
+ if (value) {
+ *value = data;
+ }
+ return true;
+}
+
+bool KeyFileStore::SetBool(const string &group, const string &key, bool value) {
+ CHECK(key_file_);
+ glib_->KeyFileSetBoolean(key_file_,
+ group.c_str(),
+ key.c_str(),
+ value ? TRUE : FALSE);
+ return true;
+}
+
+bool KeyFileStore::GetInt(const string &group, const string &key, int *value) {
+ CHECK(key_file_);
+ GError *error = NULL;
+ gint data =
+ glib_->KeyFileGetInteger(key_file_, group.c_str(), key.c_str(), &error);
+ if (error) {
+ LOG(ERROR) << "Failed to lookup (" << group << ":" << key << "): "
+ << glib_->ConvertErrorToMessage(error);
+ return false;
+ }
+ if (value) {
+ *value = data;
+ }
+ return true;
+}
+
+bool KeyFileStore::SetInt(const string &group, const string &key, int value) {
+ CHECK(key_file_);
+ glib_->KeyFileSetInteger(key_file_, group.c_str(), key.c_str(), value);
+ return true;
+}
+
+} // namespace shill
diff --git a/key_file_store.h b/key_file_store.h
new file mode 100644
index 0000000..a9e2854
--- /dev/null
+++ b/key_file_store.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_KEY_FILE_STORE_
+#define SHILL_KEY_FILE_STORE_
+
+#include <base/file_path.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "shill/glib.h"
+#include "shill/store_interface.h"
+
+namespace shill {
+
+// A key file store implementation of the store interface. See
+// http://www.gtk.org/api/2.6/glib/glib-Key-value-file-parser.html for details
+// of the key file format.
+class KeyFileStore : public StoreInterface {
+ public:
+ KeyFileStore(GLib *glib);
+ virtual ~KeyFileStore();
+
+ void set_path(const FilePath &path) { path_ = path; }
+ const FilePath &path() const { return path_; }
+
+ // Inherited from StoreInterface.
+ virtual bool Open();
+ virtual bool Close();
+ virtual std::set<std::string> GetGroups();
+ virtual bool ContainsGroup(const std::string &group);
+ virtual bool DeleteKey(const std::string &group, const std::string &key);
+ virtual bool DeleteGroup(const std::string &group);
+ virtual bool GetString(const std::string &group,
+ const std::string &key,
+ std::string *value);
+ virtual bool SetString(const std::string &group,
+ const std::string &key,
+ const std::string &value);
+ virtual bool GetBool(const std::string &group,
+ const std::string &key,
+ bool *value);
+ virtual bool SetBool(const std::string &group,
+ const std::string &key,
+ bool value);
+ virtual bool GetInt(const std::string &group,
+ const std::string &key,
+ int *value);
+ virtual bool SetInt(const std::string &group,
+ const std::string &key,
+ int value);
+
+ private:
+ FRIEND_TEST(KeyFileStoreTest, OpenClose);
+ FRIEND_TEST(KeyFileStoreTest, OpenFail);
+
+ void ReleaseKeyFile();
+
+ GLib *glib_;
+ GKeyFile *key_file_;
+ FilePath path_;
+};
+
+} // namespace shill
+
+#endif // SHILL_KEY_FILE_STORE_
diff --git a/key_file_store_unittest.cc b/key_file_store_unittest.cc
new file mode 100644
index 0000000..9e52558
--- /dev/null
+++ b/key_file_store_unittest.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2011 The Chromium OS 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 <base/file_util.h>
+#include <base/stl_util-inl.h>
+#include <base/stringprintf.h>
+#include <base/memory/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "shill/key_file_store.h"
+
+using std::set;
+using std::string;
+using testing::Test;
+
+namespace shill {
+
+class KeyFileStoreTest : public Test {
+ public:
+ KeyFileStoreTest() : store_(&glib_) {}
+
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ store_.set_path(temp_dir_.path().Append("test-key-file-store"));
+ }
+
+ virtual void TearDown() {
+ store_.set_path(FilePath("")); // Don't try to save the store.
+ ASSERT_TRUE(temp_dir_.Delete());
+ }
+
+ protected:
+ string ReadKeyFile();
+ void WriteKeyFile(string data);
+
+ GLib glib_; // Use real GLib for testing KeyFileStore.
+ ScopedTempDir temp_dir_;
+ KeyFileStore store_;
+};
+
+string KeyFileStoreTest::ReadKeyFile() {
+ string data;
+ EXPECT_TRUE(file_util::ReadFileToString(store_.path(), &data));
+ return data;
+}
+
+void KeyFileStoreTest::WriteKeyFile(string data) {
+ EXPECT_EQ(data.size(),
+ file_util::WriteFile(store_.path(), data.data(), data.size()));
+}
+
+TEST_F(KeyFileStoreTest, OpenClose) {
+ EXPECT_FALSE(store_.key_file_);
+
+ ASSERT_TRUE(store_.Open());
+ EXPECT_TRUE(store_.key_file_);
+ ASSERT_TRUE(store_.Close());
+ EXPECT_FALSE(store_.key_file_);
+
+ ASSERT_TRUE(store_.Open());
+ EXPECT_TRUE(store_.key_file_);
+ ASSERT_TRUE(store_.Close());
+ EXPECT_FALSE(store_.key_file_);
+
+ ASSERT_TRUE(store_.Open());
+ store_.set_path(FilePath(""));
+ ASSERT_FALSE(store_.Close());
+ EXPECT_FALSE(store_.key_file_);
+}
+
+TEST_F(KeyFileStoreTest, OpenFail) {
+ WriteKeyFile("garbage\n");
+ EXPECT_FALSE(store_.Open());
+ EXPECT_FALSE(store_.key_file_);
+}
+
+TEST_F(KeyFileStoreTest, GetGroups) {
+ static const char kGroupA[] = "g-a";
+ static const char kGroupB[] = "g-b";
+ static const char kGroupC[] = "g-c";
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "[%s]\n"
+ "[%s]\n",
+ kGroupA, kGroupB, kGroupC));
+ ASSERT_TRUE(store_.Open());
+ set<string> groups = store_.GetGroups();
+ EXPECT_EQ(3, groups.size());
+ EXPECT_TRUE(ContainsKey(groups, kGroupA));
+ EXPECT_TRUE(ContainsKey(groups, kGroupB));
+ EXPECT_TRUE(ContainsKey(groups, kGroupC));
+ EXPECT_FALSE(ContainsKey(groups, "g-x"));
+ ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, ContainsGroup) {
+ static const char kGroupA[] = "group-a";
+ static const char kGroupB[] = "group-b";
+ static const char kGroupC[] = "group-c";
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "[%s]\n"
+ "[%s]\n",
+ kGroupA, kGroupB, kGroupC));
+ ASSERT_TRUE(store_.Open());
+ EXPECT_TRUE(store_.ContainsGroup(kGroupA));
+ EXPECT_TRUE(store_.ContainsGroup(kGroupB));
+ EXPECT_TRUE(store_.ContainsGroup(kGroupC));
+ EXPECT_FALSE(store_.ContainsGroup("group-d"));
+ ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, DeleteKey) {
+ static const char kGroup[] = "the-group";
+ static const char kKeyDead[] = "dead";
+ static const char kKeyAlive[] = "alive";
+ const int kValueAlive = 3;
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "%s=5\n"
+ "%s=%d\n",
+ kGroup, kKeyDead, kKeyAlive, kValueAlive));
+ ASSERT_TRUE(store_.Open());
+ EXPECT_TRUE(store_.DeleteKey(kGroup, kKeyDead));
+ EXPECT_FALSE(store_.DeleteKey(kGroup, "random-key"));
+ EXPECT_FALSE(store_.DeleteKey("random-group", kKeyAlive));
+ ASSERT_TRUE(store_.Close());
+ EXPECT_EQ(base::StringPrintf("\n"
+ "[%s]\n"
+ "%s=%d\n",
+ kGroup, kKeyAlive, kValueAlive),
+ ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, DeleteGroup) {
+ static const char kGroupA[] = "group-a";
+ static const char kGroupB[] = "group-b";
+ static const char kGroupC[] = "group-c";
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "[%s]\n"
+ "key-to-be-deleted=true\n"
+ "[%s]\n",
+ kGroupA, kGroupB, kGroupC));
+ ASSERT_TRUE(store_.Open());
+ EXPECT_TRUE(store_.DeleteGroup(kGroupB));
+ EXPECT_FALSE(store_.DeleteGroup("group-d"));
+ ASSERT_TRUE(store_.Close());
+ EXPECT_EQ(base::StringPrintf("\n"
+ "[%s]\n"
+ "\n"
+ "[%s]\n",
+ kGroupA, kGroupC),
+ ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, GetString) {
+ static const char kGroup[] = "something";
+ static const char kKey[] = "foo";
+ static const char kValue[] = "bar";
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "%s=%s\n",
+ kGroup, kKey, kValue));
+ ASSERT_TRUE(store_.Open());
+ string value;
+ EXPECT_TRUE(store_.GetString(kGroup, kKey, &value));
+ EXPECT_EQ(kValue, value);
+ EXPECT_FALSE(store_.GetString("something-else", kKey, &value));
+ EXPECT_FALSE(store_.GetString(kGroup, "bar", &value));
+ EXPECT_TRUE(store_.GetString(kGroup, kKey, NULL));
+ ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, SetString) {
+ static const char kGroup[] = "string-group";
+ static const char kKey1[] = "test-string";
+ static const char kValue1[] = "foo";
+ static const char kKey2[] = "empty-string";
+ static const char kValue2[] = "";
+ ASSERT_TRUE(store_.Open());
+ ASSERT_TRUE(store_.SetString(kGroup, kKey1, kValue1));
+ ASSERT_TRUE(store_.SetString(kGroup, kKey2, kValue2));
+ ASSERT_TRUE(store_.Close());
+ EXPECT_EQ(base::StringPrintf("\n"
+ "[%s]\n"
+ "%s=%s\n"
+ "%s=%s\n",
+ kGroup, kKey1, kValue1, kKey2, kValue2),
+ ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, GetBool) {
+ static const char kGroup[] = "boo";
+ static const char kKeyTrue[] = "foo";
+ static const char kKeyFalse[] = "bar";
+ static const char kKeyBad[] = "zoo";
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "%s=true\n"
+ "%s=false\n"
+ "%s=moo\n",
+ kGroup, kKeyTrue, kKeyFalse, kKeyBad));
+ ASSERT_TRUE(store_.Open());
+ {
+ bool value = true;
+ EXPECT_TRUE(store_.GetBool(kGroup, kKeyFalse, &value));
+ EXPECT_FALSE(value);
+ }
+ {
+ bool value = false;
+ EXPECT_TRUE(store_.GetBool(kGroup, kKeyTrue, &value));
+ EXPECT_TRUE(value);
+ }
+ {
+ bool value;
+ EXPECT_FALSE(store_.GetBool(kGroup, kKeyBad, &value));
+ EXPECT_FALSE(store_.GetBool(kGroup, "unknown", &value));
+ EXPECT_FALSE(store_.GetBool("unknown", kKeyTrue, &value));
+ }
+ EXPECT_TRUE(store_.GetBool(kGroup, kKeyFalse, NULL));
+ ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, SetBool) {
+ static const char kGroup[] = "bool-group";
+ static const char kKeyTrue[] = "test-true-bool";
+ static const char kKeyFalse[] = "test-false-bool";
+ ASSERT_TRUE(store_.Open());
+ ASSERT_TRUE(store_.SetBool(kGroup, kKeyTrue, true));
+ ASSERT_TRUE(store_.SetBool(kGroup, kKeyFalse, false));
+ ASSERT_TRUE(store_.Close());
+ EXPECT_EQ(base::StringPrintf("\n"
+ "[%s]\n"
+ "%s=true\n"
+ "%s=false\n",
+ kGroup, kKeyTrue, kKeyFalse),
+ ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, GetInt) {
+ static const char kGroup[] = "numbers";
+ static const char kKeyPos[] = "pos";
+ static const char kKeyNeg[] = "neg";
+ static const char kKeyBad[] = "bad";
+ const int kValuePos = 50;
+ const int kValueNeg = -20;
+ static const char kValueBad[] = "nan";
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "%s=%d\n"
+ "%s=%d\n"
+ "%s=%s\n",
+ kGroup,
+ kKeyPos, kValuePos,
+ kKeyNeg, kValueNeg,
+ kKeyBad, kValueBad));
+ ASSERT_TRUE(store_.Open());
+ {
+ int value = 0;
+ EXPECT_TRUE(store_.GetInt(kGroup, kKeyNeg, &value));
+ EXPECT_EQ(kValueNeg, value);
+ }
+ {
+ int value = 0;
+ EXPECT_TRUE(store_.GetInt(kGroup, kKeyPos, &value));
+ EXPECT_EQ(kValuePos, value);
+ }
+ {
+ int value;
+ EXPECT_FALSE(store_.GetInt(kGroup, kKeyBad, &value));
+ EXPECT_FALSE(store_.GetInt(kGroup, "invalid", &value));
+ EXPECT_FALSE(store_.GetInt("invalid", kKeyPos, &value));
+ }
+ EXPECT_TRUE(store_.GetInt(kGroup, kKeyPos, NULL));
+ ASSERT_TRUE(store_.Close());
+}
+
+TEST_F(KeyFileStoreTest, SetInt) {
+ static const char kGroup[] = "int-group";
+ static const char kKey1[] = "test-int";
+ static const char kKey2[] = "test-negative";
+ const int kValue1 = 5;
+ const int kValue2 = -10;
+ ASSERT_TRUE(store_.Open());
+ ASSERT_TRUE(store_.SetInt(kGroup, kKey1, kValue1));
+ ASSERT_TRUE(store_.SetInt(kGroup, kKey2, kValue2));
+ ASSERT_TRUE(store_.Close());
+ EXPECT_EQ(base::StringPrintf("\n"
+ "[%s]\n"
+ "%s=%d\n"
+ "%s=%d\n",
+ kGroup, kKey1, kValue1, kKey2, kValue2),
+ ReadKeyFile());
+}
+
+TEST_F(KeyFileStoreTest, Combo) {
+ static const char kGroupA[] = "square";
+ static const char kGroupB[] = "circle";
+ static const char kGroupC[] = "triangle";
+ static const char kGroupX[] = "pentagon";
+ static const char kKeyString[] = "color";
+ static const char kKeyInt[] = "area";
+ static const char kKeyBool[] = "visible";
+ static const char kValueStringA[] = "blue";
+ static const char kValueStringB[] = "red";
+ static const char kValueStringC[] = "yellow";
+ static const char kValueStringCNew[] = "purple";
+ const int kValueIntA = 5;
+ const int kValueIntB = 10;
+ const int kValueIntBNew = 333;
+ WriteKeyFile(base::StringPrintf("[%s]\n"
+ "%s=%s\n"
+ "%s=%d\n"
+ "[%s]\n"
+ "%s=%s\n"
+ "%s=%d\n"
+ "%s=true\n"
+ "[%s]\n"
+ "%s=%s\n"
+ "%s=false\n",
+ kGroupA,
+ kKeyString, kValueStringA,
+ kKeyInt, kValueIntA,
+ kGroupB,
+ kKeyString, kValueStringB,
+ kKeyInt, kValueIntB,
+ kKeyBool,
+ kGroupC,
+ kKeyString, kValueStringC,
+ kKeyBool));
+ ASSERT_TRUE(store_.Open());
+
+ EXPECT_TRUE(store_.ContainsGroup(kGroupA));
+ EXPECT_TRUE(store_.ContainsGroup(kGroupB));
+ EXPECT_TRUE(store_.ContainsGroup(kGroupC));
+ EXPECT_FALSE(store_.ContainsGroup(kGroupX));
+
+ set<string> groups = store_.GetGroups();
+ EXPECT_EQ(3, groups.size());
+ EXPECT_TRUE(ContainsKey(groups, kGroupA));
+ EXPECT_TRUE(ContainsKey(groups, kGroupB));
+ EXPECT_TRUE(ContainsKey(groups, kGroupC));
+ EXPECT_FALSE(ContainsKey(groups, kGroupX));
+
+ {
+ string value;
+ EXPECT_TRUE(store_.GetString(kGroupB, kKeyString, &value));
+ EXPECT_EQ(kValueStringB, value);
+ EXPECT_TRUE(store_.GetString(kGroupA, kKeyString, &value));
+ EXPECT_EQ(kValueStringA, value);
+ EXPECT_TRUE(store_.GetString(kGroupC, kKeyString, &value));
+ EXPECT_EQ(kValueStringC, value);
+ }
+ {
+ int value = 0;
+ EXPECT_TRUE(store_.GetInt(kGroupB, kKeyInt, &value));
+ EXPECT_EQ(kValueIntB, value);
+ EXPECT_TRUE(store_.GetInt(kGroupA, kKeyInt, &value));
+ EXPECT_EQ(kValueIntA, value);
+ EXPECT_FALSE(store_.GetInt(kGroupC, kKeyInt, &value));
+ }
+ {
+ bool value = false;
+ EXPECT_TRUE(store_.GetBool(kGroupB, kKeyBool, &value));
+ EXPECT_TRUE(value);
+ EXPECT_TRUE(store_.GetBool(kGroupC, kKeyBool, &value));
+ EXPECT_FALSE(value);
+ EXPECT_FALSE(store_.GetBool(kGroupA, kKeyBool, &value));
+ }
+
+ EXPECT_TRUE(store_.DeleteGroup(kGroupA));
+ EXPECT_FALSE(store_.DeleteGroup(kGroupA));
+
+ EXPECT_FALSE(store_.ContainsGroup(kGroupA));
+ EXPECT_TRUE(store_.ContainsGroup(kGroupB));
+ EXPECT_TRUE(store_.ContainsGroup(kGroupC));
+
+ groups = store_.GetGroups();
+ EXPECT_EQ(2, groups.size());
+ EXPECT_FALSE(ContainsKey(groups, kGroupA));
+ EXPECT_TRUE(ContainsKey(groups, kGroupB));
+ EXPECT_TRUE(ContainsKey(groups, kGroupC));
+
+ EXPECT_TRUE(store_.SetBool(kGroupB, kKeyBool, false));
+ EXPECT_TRUE(store_.SetInt(kGroupB, kKeyInt, kValueIntBNew));
+ EXPECT_TRUE(store_.SetString(kGroupC, kKeyString, kValueStringCNew));
+
+ EXPECT_TRUE(store_.DeleteKey(kGroupB, kKeyString));
+ EXPECT_FALSE(store_.DeleteKey(kGroupB, kKeyString));
+
+ {
+ string value;
+ EXPECT_FALSE(store_.GetString(kGroupB, kKeyString, &value));
+ EXPECT_FALSE(store_.GetString(kGroupA, kKeyString, &value));
+ EXPECT_TRUE(store_.GetString(kGroupC, kKeyString, &value));
+ EXPECT_EQ(kValueStringCNew, value);
+ }
+
+ {
+ int value = 0;
+ EXPECT_TRUE(store_.GetInt(kGroupB, kKeyInt, &value));
+ EXPECT_EQ(kValueIntBNew, value);
+ EXPECT_FALSE(store_.GetInt(kGroupA, kKeyInt, &value));
+ EXPECT_FALSE(store_.GetInt(kGroupC, kKeyInt, &value));
+ }
+
+ {
+ bool value = false;
+ EXPECT_TRUE(store_.GetBool(kGroupB, kKeyBool, &value));
+ EXPECT_FALSE(value);
+ EXPECT_TRUE(store_.GetBool(kGroupC, kKeyBool, &value));
+ EXPECT_FALSE(value);
+ EXPECT_FALSE(store_.GetBool(kGroupA, kKeyBool, &value));
+ }
+
+ ASSERT_TRUE(store_.Close());
+ EXPECT_EQ(base::StringPrintf("\n"
+ "[%s]\n"
+ "%s=%d\n"
+ "%s=false\n"
+ "\n"
+ "[%s]\n"
+ "%s=%s\n"
+ "%s=false\n",
+ kGroupB,
+ kKeyInt, kValueIntBNew,
+ kKeyBool,
+ kGroupC,
+ kKeyString, kValueStringCNew,
+ kKeyBool),
+ ReadKeyFile());
+}
+
+} // namespace shill
diff --git a/mock_glib.h b/mock_glib.h
index 0fb00a4..99229ad 100644
--- a/mock_glib.h
+++ b/mock_glib.h
@@ -16,6 +16,51 @@
MOCK_METHOD3(ChildWatchAdd, guint(GPid pid,
GChildWatchFunc function,
gpointer data));
+ MOCK_METHOD1(Free, void(gpointer mem));
+ MOCK_METHOD1(KeyFileFree, void(GKeyFile *key_file));
+ MOCK_METHOD4(KeyFileGetBoolean, gboolean(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error));
+ MOCK_METHOD2(KeyFileGetGroups, gchar **(GKeyFile *key_file,
+ gsize *length));
+ MOCK_METHOD4(KeyFileGetInteger, gint(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error));
+ MOCK_METHOD4(KeyFileGetString, gchar *(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error));
+ MOCK_METHOD2(KeyFileHasGroup, gboolean(GKeyFile *key_file,
+ const gchar *group_name));
+ MOCK_METHOD4(KeyFileLoadFromFile, gboolean(GKeyFile *key_file,
+ const gchar *file,
+ GKeyFileFlags flags,
+ GError **error));
+ MOCK_METHOD0(KeyFileNew, GKeyFile *());
+ MOCK_METHOD3(KeyFileRemoveGroup, void(GKeyFile *key_file,
+ const gchar *group_name,
+ GError **error));
+ MOCK_METHOD4(KeyFileRemoveKey, void(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ GError **error));
+ MOCK_METHOD4(KeyFileSetBoolean, void(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ gboolean value));
+ MOCK_METHOD4(KeyFileSetInteger, void(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ gint value));
+ MOCK_METHOD4(KeyFileSetString, void(GKeyFile *key_file,
+ const gchar *group_name,
+ const gchar *key,
+ const gchar *string));
+ MOCK_METHOD3(KeyFileToData, gchar *(GKeyFile *key_file,
+ gsize *length,
+ GError **error));
MOCK_METHOD1(SourceRemove, gboolean(guint tag));
MOCK_METHOD8(SpawnAsync, gboolean(const gchar *working_directory,
gchar **argv,
@@ -26,6 +71,7 @@
GPid *child_pid,
GError **error));
MOCK_METHOD1(SpawnClosePID, void(GPid pid));
+ MOCK_METHOD1(Strfreev, void(gchar **str_array));
};
} // namespace shill
diff --git a/store_interface.h b/store_interface.h
new file mode 100644
index 0000000..cd0fbbd
--- /dev/null
+++ b/store_interface.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_STORE_INTERFACE_
+#define SHILL_STORE_INTERFACE_
+
+#include <set>
+#include <string>
+
+namespace shill {
+
+// An interface to a persistent store implementation.
+class StoreInterface {
+ public:
+ virtual ~StoreInterface() {}
+
+ // Opens the store. Returns true on success. This method must be invoked
+ // before using any of the getters or setters. This method is not required
+ // complete gracefully if invoked on a store that has been opened already but
+ // not closed yet.
+ virtual bool Open() = 0;
+
+ // Closes the store and flushes it to persistent storage. Returns true on
+ // success. Note that the store is considered closed even if Close returns
+ // false. This method is not required to complete gracefully if invoked on a
+ // store that has not been opened successfully or has been closed already.
+ virtual bool Close() = 0;
+
+ // Returns a set of all groups contained in the store.
+ virtual std::set<std::string> GetGroups() = 0;
+
+ // Returns true if the store contains |group|, false otherwise.
+ virtual bool ContainsGroup(const std::string &group) = 0;
+
+ // Deletes |group|:|key|. Returns true on success.
+ virtual bool DeleteKey(const std::string &group, const std::string &key) = 0;
+
+ // Deletes |group|. Returns true on success.
+ virtual bool DeleteGroup(const std::string &group) = 0;
+
+ // Gets a string |value| associated with |group|:|key|. Returns true on
+ // success and false on failure (including when |group|:|key| is not present
+ // in the store).
+ virtual bool GetString(const std::string &group,
+ const std::string &key,
+ std::string *value) = 0;
+
+ // Associates |group|:|key| with a string |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetString(const std::string &group,
+ const std::string &key,
+ const std::string &value) = 0;
+
+ // Gets a boolean |value| associated with |group|:|key|. Returns true on
+ // success and false on failure (including when the |group|:|key| is not
+ // present in the store).
+ virtual bool GetBool(const std::string &group,
+ const std::string &key,
+ bool *value) = 0;
+
+ // Associates |group|:|key| with a boolean |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetBool(const std::string &group,
+ const std::string &key,
+ bool value) = 0;
+
+ // Gets a integer |value| associated with |group|:|key|. Returns true on
+ // success and false on failure (including when the |group|:|key| is not
+ // present in the store).
+ virtual bool GetInt(const std::string &group,
+ const std::string &key,
+ int *value) = 0;
+
+ // Associates |group|:|key| with an integer |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetInt(const std::string &group,
+ const std::string &key,
+ int value) = 0;
+};
+
+} // namespace shill
+
+#endif // SHILL_STORE_INTERFACE_