commit 77a498ae31d0c95d1cdaa88752965fcea870990d Author: Damian Johnson atagar@torproject.org Date: Mon May 7 09:23:09 2012 -0700
Unit tests for extrainfo descriptors
Few basic unit tests for extrainfo descriptors. These are done in a similar fashion to the server descriptors. I'm highly tempted to refactor out some common bits, but at present that would probably hurt code readability more than it would help. This'll change if I keep using the same type of helpers for descriptor unit tests. --- run_tests.py | 2 + stem/descriptor/extrainfo_descriptor.py | 8 ++ test/unit/descriptor/__init__.py | 2 +- test/unit/descriptor/extrainfo_descriptor.py | 131 ++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletions(-)
diff --git a/run_tests.py b/run_tests.py index 818bfb8..bf2c2bc 100755 --- a/run_tests.py +++ b/run_tests.py @@ -21,6 +21,7 @@ import test.unit.socket.control_line import test.unit.socket.control_message import test.unit.descriptor.reader import test.unit.descriptor.server_descriptor +import test.unit.descriptor.extrainfo_descriptor import test.unit.util.conf import test.unit.util.connection import test.unit.util.enum @@ -94,6 +95,7 @@ UNIT_TESTS = ( test.unit.util.tor_tools.TestTorTools, test.unit.descriptor.reader.TestDescriptorReader, test.unit.descriptor.server_descriptor.TestServerDescriptor, + test.unit.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor, test.unit.version.TestVersion, test.unit.socket.control_message.TestControlMessage, test.unit.socket.control_line.TestControlLine, diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py index fceaf60..e13787f 100644 --- a/stem/descriptor/extrainfo_descriptor.py +++ b/stem/descriptor/extrainfo_descriptor.py @@ -18,6 +18,7 @@ Extra-info descriptors are available from a few sources...
parse_file - Iterates over the extra-info descriptors in a file. ExtraInfoDescriptor - Tor extra-info descriptor. + +- get_unrecognized_lines - lines with unrecognized content """
import datetime @@ -159,6 +160,8 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): self.write_history_interval = None self.write_history_values = []
+ self._unrecognized_lines = [] + entries, first_keyword, last_keyword, _ = \ stem.descriptor._get_descriptor_components(raw_contents, validate, ())
@@ -178,6 +181,9 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
self._parse(entries, validate)
+ def get_unrecognized_lines(self): + return list(self._unrecognized_lines) + def _parse(self, entries, validate): """ Parses a series of 'keyword => (value, pgp block)' mappings and applies @@ -227,4 +233,6 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): raise ValueError("Router signature line must be followed by a signature block: %s" % line)
self.signature = block_contents + else: + self._unrecognized_lines.append(line)
diff --git a/test/unit/descriptor/__init__.py b/test/unit/descriptor/__init__.py index ae61cda..cf4fceb 100644 --- a/test/unit/descriptor/__init__.py +++ b/test/unit/descriptor/__init__.py @@ -2,5 +2,5 @@ Unit tests for stem.descriptor. """
-__all__ = ["reader", "server_descriptor"] +__all__ = ["reader", "extrainfo_descriptor", "server_descriptor"]
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py new file mode 100644 index 0000000..15ffe68 --- /dev/null +++ b/test/unit/descriptor/extrainfo_descriptor.py @@ -0,0 +1,131 @@ +""" +Unit tests for stem.descriptor.extrainfo_descriptor. +""" + +import unittest +from stem.descriptor.extrainfo_descriptor import ExtraInfoDescriptor + +CRYPTO_BLOB = """ +K5FSywk7qvw/boA4DQcqkls6Ize5vcBYfhQ8JnOeRQC9+uDxbnpm3qaYN9jZ8myj +k0d2aofcVbHr4fPQOSST0LXDrhFl5Fqo5um296zpJGvRUeO6S44U/EfJAGShtqWw +7LZqklu+gVvhMKREpchVqlAwXkWR44VENm24Hs+mT3M= +""" + +EXTRAINFO_DESCRIPTOR_ATTR = ( + ("extra-info", "ninja B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48"), + ("published", "2012-05-05 17:03:50"), + ("router-signature", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB), +) + +def _make_descriptor(attr = None, exclude = None): + """ + Constructs a minimal extrainfo descriptor with the given attributes. + + Arguments: + attr (dict) - keyword/value mappings to be included in the descriptor + exclude (list) - mandatory keywords to exclude from the descriptor + + Returns: + str with customized descriptor content + """ + + descriptor_lines = [] + if attr is None: attr = {} + if exclude is None: exclude = [] + attr = dict(attr) # shallow copy since we're destructive + + for keyword, value in EXTRAINFO_DESCRIPTOR_ATTR: + if keyword in exclude: continue + elif keyword in attr: + value = attr[keyword] + del attr[keyword] + + # if this is the last entry then we should dump in any unused attributes + if keyword == "router-signature": + for attr_keyword, attr_value in attr.items(): + descriptor_lines.append("%s %s" % (attr_keyword, attr_value)) + + descriptor_lines.append("%s %s" % (keyword, value)) + + return "\n".join(descriptor_lines) + +class TestExtraInfoDescriptor(unittest.TestCase): + def test_minimal_extrainfo_descriptor(self): + """ + Basic sanity check that we can parse an extrainfo descriptor with minimal + attributes. + """ + + desc_text = _make_descriptor() + desc = ExtraInfoDescriptor(desc_text) + + self.assertEquals("ninja", desc.nickname) + self.assertEquals("B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48", desc.fingerprint) + self.assertTrue(CRYPTO_BLOB in desc.signature) + + def test_unrecognized_line(self): + """ + Includes unrecognized content in the descriptor. + """ + + desc_text = _make_descriptor({"pepperjack": "is oh so tasty!"}) + desc = ExtraInfoDescriptor(desc_text) + self.assertEquals(["pepperjack is oh so tasty!"], desc.get_unrecognized_lines()) + + def test_proceeding_line(self): + """ + Includes a line prior to the 'extra-info' entry. + """ + + desc_text = "exit-streams-opened port=80\n" + _make_descriptor() + self._expect_invalid_attr(desc_text) + + def test_trailing_line(self): + """ + Includes a line after the 'router-signature' entry. + """ + + desc_text = _make_descriptor() + "\nexit-streams-opened port=80" + self._expect_invalid_attr(desc_text) + + def test_extrainfo_line_missing_fields(self): + """ + Checks that validation catches when the extra-info line is missing fields + and that without validation both the nickname and fingerprint are left as + None. + """ + + test_entry = ( + "ninja", + "ninja ", + "B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48", + " B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48", + ) + + for entry in test_entry: + desc_text = _make_descriptor({"extra-info": entry}) + desc = self._expect_invalid_attr(desc_text, "nickname") + self.assertEquals(None, desc.nickname) + self.assertEquals(None, desc.fingerprint) + + def _expect_invalid_attr(self, desc_text, attr = None, expected_value = None): + """ + Asserts that construction will fail due to desc_text having a malformed + attribute. If an attr is provided then we check that it matches an expected + value when we're constructed without validation. + """ + + self.assertRaises(ValueError, ExtraInfoDescriptor, desc_text) + desc = ExtraInfoDescriptor(desc_text, validate = False) + + if attr: + # check that the invalid attribute matches the expected value when + # constructed without validation + + self.assertEquals(expected_value, getattr(desc, attr)) + else: + # check a default attribute + self.assertEquals("ninja", desc.nickname) + + return desc +
tor-commits@lists.torproject.org