commit 59764266b9dbce91accd6a55a371f24516b257a7 Author: Damian Johnson atagar@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")) +
tor-commits@lists.torproject.org