commit 59764266b9dbce91accd6a55a371f24516b257a7
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Jan 23 09:25:46 2012 -0800
Unit testing for stem.util.conf
I needed unit tests for the new listener functions so wrote long overdue tests
for everything within the utilitity too. Caught a couple bugs in the process.
---
run_tests.py | 2 +
stem/util/conf.py | 12 ++-
test/unit/util/__init__.py | 2 +-
test/unit/util/conf.py | 232 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 245 insertions(+), 3 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index c7dd91e..acb229b 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -17,6 +17,7 @@ import test.unit.connection.authentication
import test.unit.connection.protocolinfo
import test.unit.socket.control_line
import test.unit.socket.control_message
+import test.unit.util.conf
import test.unit.util.enum
import test.unit.util.system
import test.unit.version
@@ -71,6 +72,7 @@ DEFAULT_RUN_TARGET = TARGETS.RUN_OPEN
UNIT_TESTS = (
test.unit.util.enum.TestEnum,
+ test.unit.util.conf.TestConf,
test.unit.util.system.TestSystem,
test.unit.version.TestVersion,
test.unit.socket.control_message.TestControlMessage,
diff --git a/stem/util/conf.py b/stem/util/conf.py
index 68a332a..8a1fcfc 100644
--- a/stem/util/conf.py
+++ b/stem/util/conf.py
@@ -19,6 +19,7 @@ Config - Custom configuration.
|- clear - empties our loaded configuration contents
|- update - replaces mappings in a dictionary with the config's values
|- add_listener - notifies the given listener when an update occures
+ |- clear_listeners - removes any attached listeners
|- sync - keeps a dictionary synchronized with our config
|- keys - provides keys in the loaded configuration
|- set - sets the given key/value pair
@@ -272,10 +273,17 @@ class Config():
if backfill:
for key in self.keys():
- listener(key)
+ listener(self, key)
self._contents_lock.release()
+ def clear_listeners(self):
+ """
+ Removes any attached listeners.
+ """
+
+ self._listeners = []
+
def sync(self, config_dict, interceptor = None):
"""
Synchronizes a dictionary with our current configuration (like the 'update'
@@ -314,7 +322,7 @@ class Config():
set of configuration keys we've loaded but have never been requested
"""
- return set(self.get_keys()).difference(self._requested_keys)
+ return set(self.keys()).difference(self._requested_keys)
def set(self, key, value, overwrite = True):
"""
diff --git a/test/unit/util/__init__.py b/test/unit/util/__init__.py
index 57015c1..7c8d12b 100644
--- a/test/unit/util/__init__.py
+++ b/test/unit/util/__init__.py
@@ -2,5 +2,5 @@
Unit tests for stem.util.* contents.
"""
-__all__ = ["enum", "system"]
+__all__ = ["conf", "enum", "system"]
diff --git a/test/unit/util/conf.py b/test/unit/util/conf.py
new file mode 100644
index 0000000..3e59a7c
--- /dev/null
+++ b/test/unit/util/conf.py
@@ -0,0 +1,232 @@
+"""
+Unit tests for the stem.util.conf class and functions.
+"""
+
+import unittest
+import stem.util.conf
+
+class TestConf(unittest.TestCase):
+ """
+ Tests the stem.util.conf contents.
+ """
+
+ def tearDown(self):
+ # clears the config contents
+ test_config = stem.util.conf.get_config("unit_testing")
+ test_config.clear()
+ test_config.clear_listeners()
+
+ def test_clear(self):
+ """
+ Tests the clear method.
+ """
+
+ test_config = stem.util.conf.get_config("unit_testing")
+ self.assertEquals([], test_config.keys())
+
+ # tests clearing when we're already empty
+ test_config.clear()
+ self.assertEquals([], test_config.keys())
+
+ # tests clearing when we have contents
+ test_config.set("hello", "world")
+ self.assertEquals(["hello"], test_config.keys())
+
+ test_config.clear()
+ self.assertEquals([], test_config.keys())
+
+ def test_update(self):
+ """
+ Tests the update method.
+ """
+
+ my_config = {
+ "bool_value": False,
+ "int_value": 5,
+ "str_value": "hello",
+ "list_value": [],
+ "map_value": {},
+ }
+
+ test_config = stem.util.conf.get_config("unit_testing")
+ test_config.set("bool_value", "true")
+ test_config.set("int_value", "11")
+ test_config.set("str_value", "world")
+ test_config.set("list_value", "a", False)
+ test_config.set("list_value", "b", False)
+ test_config.set("list_value", "c", False)
+ test_config.set("map_value", "foo => bar")
+
+ test_config.update(my_config)
+ self.assertEquals(True, my_config["bool_value"])
+ self.assertEquals(11, my_config["int_value"])
+ self.assertEquals("world", my_config["str_value"])
+ self.assertEquals(["a", "b", "c"], my_config["list_value"])
+ self.assertEquals({"foo": "bar"}, my_config["map_value"])
+
+ def test_update_type_mismatch(self):
+ """
+ Tests the update method when the config file has missing entries or the
+ wrong types.
+ """
+
+ my_config = {
+ "bool_value": False,
+ "int_value": 5,
+ "str_value": "hello",
+ "list_value": [],
+ "map_value": {},
+ }
+
+ test_config = stem.util.conf.get_config("unit_testing")
+ test_config.set("bool_value", "hello world")
+ test_config.set("int_value", "11a")
+ test_config.set("map_value", "foo bar")
+
+ test_config.update(my_config)
+ self.assertEquals(False, my_config["bool_value"])
+ self.assertEquals(5, my_config["int_value"])
+ self.assertEquals("hello", my_config["str_value"])
+ self.assertEquals([], my_config["list_value"])
+ self.assertEquals({}, my_config["map_value"])
+
+ def test_listeners(self):
+ """
+ Tests the add_listener and clear_listeners methods.
+ """
+
+ listener_received_keys = []
+
+ def test_listener(config, key):
+ self.assertEquals(config, stem.util.conf.get_config("unit_testing"))
+ listener_received_keys.append(key)
+
+ test_config = stem.util.conf.get_config("unit_testing")
+ test_config.add_listener(test_listener)
+
+ self.assertEquals([], listener_received_keys)
+ test_config.set("hello", "world")
+ self.assertEquals(["hello"], listener_received_keys)
+
+ test_config.clear_listeners()
+
+ test_config.set("foo", "bar")
+ self.assertEquals(["hello"], listener_received_keys)
+
+ def test_sync(self):
+ """
+ Tests the sync method.
+ """
+
+ my_config = {
+ "bool_value": False,
+ "int_value": 5,
+ "str_value": "hello",
+ "list_value": [],
+ }
+
+ test_config = stem.util.conf.get_config("unit_testing")
+
+ # checks that sync causes existing contents to be applied
+ test_config.set("bool_value", "true")
+ test_config.sync(my_config)
+ self.assertEquals(True, my_config["bool_value"])
+
+ # check a basic update
+ test_config.set("str_value", "me")
+ self.assertEquals("me", my_config["str_value"])
+
+ # update with a type mismatch, should keep the old value
+ test_config.set("int_value", "7a")
+ self.assertEquals(5, my_config["int_value"])
+
+ # changes for a collection
+ test_config.set("list_value", "a", False)
+ self.assertEquals(["a"], my_config["list_value"])
+
+ test_config.set("list_value", "b", False)
+ self.assertEquals(["a", "b"], my_config["list_value"])
+
+ test_config.set("list_value", "c", False)
+ self.assertEquals(["a", "b", "c"], my_config["list_value"])
+
+ def test_unused_keys(self):
+ """
+ Tests the unused_keys method.
+ """
+
+ test_config = stem.util.conf.get_config("unit_testing")
+ test_config.set("hello", "world")
+ test_config.set("foo", "bar")
+ test_config.set("pw", "12345")
+
+ test_config.get("hello")
+ test_config.get_value("foo")
+
+ self.assertEquals(set(["pw"]), test_config.unused_keys())
+
+ test_config.get("pw")
+ self.assertEquals(set(), test_config.unused_keys())
+
+ def test_get(self):
+ """
+ Tests the get and get_value methods.
+ """
+
+ test_config = stem.util.conf.get_config("unit_testing")
+ test_config.set("bool_value", "true")
+ test_config.set("int_value", "11")
+ test_config.set("float_value", "11.1")
+ test_config.set("str_value", "world")
+ test_config.set("list_value", "a", False)
+ test_config.set("list_value", "b", False)
+ test_config.set("list_value", "c", False)
+ test_config.set("map_value", "foo => bar")
+
+ # check that we get the default for type mismatch or missing values
+
+ self.assertEquals(5, test_config.get("foo", 5))
+ self.assertEquals(5, test_config.get("bool_value", 5))
+
+ # checks that we get a string when no default is supplied
+
+ self.assertEquals("11", test_config.get("int_value"))
+
+ # exercise type casting for each of the supported types
+
+ self.assertEquals(True, test_config.get("bool_value", False))
+ self.assertEquals(11, test_config.get("int_value", 0))
+ self.assertEquals(11.1, test_config.get("float_value", 0.0))
+ self.assertEquals("world", test_config.get("str_value", ""))
+ self.assertEquals(["a", "b", "c"], test_config.get("list_value", []))
+ self.assertEquals(("a", "b", "c"), test_config.get("list_value", ()))
+ self.assertEquals({"foo": "bar"}, test_config.get("map_value", {}))
+
+ # the get_value is similar, though only provides back a string or list
+
+ self.assertEquals("c", test_config.get_value("list_value"))
+ self.assertEquals(["a", "b", "c"], test_config.get_value("list_value", multiple = True))
+
+ self.assertEquals(None, test_config.get_value("foo"))
+ self.assertEquals("hello", test_config.get_value("foo", "hello"))
+
+ def test_csv(self):
+ """
+ Tests the get_str_csv and get_int_csv methods.
+ """
+
+ test_config = stem.util.conf.get_config("unit_testing")
+ test_config.set("str_csv_value", "hello, world")
+ test_config.set("int_csv_value", "1, 2, 3")
+ test_config.set("not_a_csv_value", "blarg I say!")
+
+ self.assertEquals(["hello", "world"], test_config.get_str_csv("str_csv_value"))
+ self.assertEquals(["1", "2", "3"], test_config.get_str_csv("int_csv_value"))
+ self.assertEquals(["blarg I say!"], test_config.get_str_csv("not_a_csv_value"))
+ self.assertEquals(None, test_config.get_str_csv("not_a_csv_value", count = 5))
+
+ self.assertEquals(None, test_config.get_int_csv("str_csv_value"))
+ self.assertEquals([1, 2, 3], test_config.get_int_csv("int_csv_value"))
+ self.assertEquals(None, test_config.get_int_csv("int_csv_value", min_value = 4))
+ self.assertEquals(None, test_config.get_int_csv("not_a_csv_value"))
+