[tor-commits] [stem/master] Renaming NetworkStatusDocument to NetworkStatusDocumentV3
atagar at torproject.org
atagar at torproject.org
Sat Oct 13 18:35:45 UTC 2012
commit 0793113b37afa737862440363983105e820640bd
Author: Damian Johnson <atagar at torproject.org>
Date: Thu Oct 11 07:53:41 2012 -0700
Renaming NetworkStatusDocument to NetworkStatusDocumentV3
Changing the name to make room for v2 documents and future versions.
---
run_tests.py | 4 +-
stem/descriptor/networkstatus.py | 21 +-
test/integ/descriptor/networkstatus.py | 4 +-
test/mocking.py | 14 +-
test/unit/descriptor/networkstatus/__init__.py | 2 +-
test/unit/descriptor/networkstatus/document.py | 799 ---------------------
test/unit/descriptor/networkstatus/document_v3.py | 799 +++++++++++++++++++++
7 files changed, 822 insertions(+), 821 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index c4db524..2475254 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -23,7 +23,7 @@ import test.unit.descriptor.extrainfo_descriptor
import test.unit.descriptor.router_status_entry
import test.unit.descriptor.networkstatus.directory_authority
import test.unit.descriptor.networkstatus.key_certificate
-import test.unit.descriptor.networkstatus.document
+import test.unit.descriptor.networkstatus.document_v3
import test.unit.response.control_line
import test.unit.response.control_message
import test.unit.response.getinfo
@@ -121,7 +121,7 @@ UNIT_TESTS = (
test.unit.descriptor.router_status_entry.TestRouterStatusEntry,
test.unit.descriptor.networkstatus.directory_authority.TestDirectoryAuthority,
test.unit.descriptor.networkstatus.key_certificate.TestKeyCertificate,
- test.unit.descriptor.networkstatus.document.TestNetworkStatusDocument,
+ test.unit.descriptor.networkstatus.document_v3.TestNetworkStatusDocument,
test.unit.exit_policy.rule.TestExitPolicyRule,
test.unit.exit_policy.policy.TestExitPolicy,
test.unit.version.TestVersion,
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index ce3a00c..2f6972f 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -17,20 +17,20 @@ Of these, the router status entry section can be quite large (on the order of
hundreds of kilobytes). As such we provide a couple methods of reading network
status documents...
-* :class:`stem.descriptor.networkstatus.NetworkStatusDocument` constructor
+* :class:`stem.descriptor.networkstatus.NetworkStatusDocumentV3` constructor
If read time and memory aren't a concern then you can simply use the document
constructor. Router entries are assigned to its 'routers' attribute...
::
- from stem.descriptor.networkstatus import NetworkStatusDocument
+ from stem.descriptor.networkstatus import NetworkStatusDocumentV3
# Reads the full consensus into memory twice (both for the parsed and
# unparsed contents).
consensus_file = open('.tor/cached-consensus', 'r')
- consensus = NetworkStatusDocument(consensus_file.read())
+ consensus = NetworkStatusDocumentV3(consensus_file.read())
consensus_file.close()
for router in consensus.routers:
@@ -58,7 +58,7 @@ routers. Those routers refer to a 'thin' document, which doesn't have a
::
parse_file - parses a network status file, providing an iterator for its routers
- NetworkStatusDocument - Version 3 network status document.
+ NetworkStatusDocumentV3 - Version 3 network status document.
DocumentSignature - Signature of a document by a directory authority.
DirectoryAuthority - Directory authority as defined in a v3 network status document.
"""
@@ -153,7 +153,7 @@ def parse_file(document_file, validate = True, is_microdescriptor = False):
:param bool validate: checks the validity of the document's contents if True, skips these checks otherwise
:param bool is_microdescriptor: True if this is for a microdescriptor consensus, False otherwise
- :returns: :class:`stem.descriptor.networkstatus.NetworkStatusDocument` object
+ :returns: :class:`stem.descriptor.networkstatus.NetworkStatusDocumentV3` object
:raises:
* ValueError if the contents is malformed and validate is True
@@ -183,7 +183,7 @@ def parse_file(document_file, validate = True, is_microdescriptor = False):
entry_keyword = ROUTERS_START,
start_position = routers_start,
end_position = routers_end,
- extra_args = (NetworkStatusDocument(document_content, validate),),
+ extra_args = (NetworkStatusDocumentV3(document_content, validate),),
)
for desc in desc_iterator:
@@ -229,7 +229,7 @@ def _get_entries(document_file, validate, entry_class, entry_keyword, start_posi
desc_content = "".join(stem.descriptor._read_until_keywords(entry_keyword, document_file, ignore_first = True, end_position = end_position))
yield entry_class(desc_content, validate, *extra_args)
-class NetworkStatusDocument(stem.descriptor.Descriptor):
+class NetworkStatusDocumentV3(stem.descriptor.Descriptor):
"""
Version 3 network status document. This could be either a vote or consensus.
@@ -265,7 +265,8 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
def __init__(self, raw_content, validate = True, default_params = True):
"""
- Parse a v3 network status document and provide a new NetworkStatusDocument object.
+ Parse a v3 network status document and provide a new
+ NetworkStatusDocumentV3 object.
:param str raw_content: raw network status document data
:param bool validate: True if the document is to be validated, False otherwise
@@ -274,7 +275,7 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
:raises: ValueError if the document is invalid
"""
- super(NetworkStatusDocument, self).__init__(raw_content)
+ super(NetworkStatusDocumentV3, self).__init__(raw_content)
document_file = StringIO(raw_content)
self._header = _DocumentHeader(document_file, validate, default_params)
@@ -336,7 +337,7 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
return list(self._unrecognized_lines)
def __cmp__(self, other):
- if not isinstance(other, NetworkStatusDocument):
+ if not isinstance(other, NetworkStatusDocumentV3):
return 1
return str(self) > str(other)
diff --git a/test/integ/descriptor/networkstatus.py b/test/integ/descriptor/networkstatus.py
index b6526d6..e174f25 100644
--- a/test/integ/descriptor/networkstatus.py
+++ b/test/integ/descriptor/networkstatus.py
@@ -140,7 +140,7 @@ I/TJmV928na7RLZe2mGHCAW3VQOvV+QkCfj05VZ8CsY=
consensus_path = test.integ.descriptor.get_resource("cached-consensus")
with open(consensus_path) as descriptor_file:
- document = stem.descriptor.networkstatus.NetworkStatusDocument(descriptor_file.read(), default_params = False)
+ document = stem.descriptor.networkstatus.NetworkStatusDocumentV3(descriptor_file.read(), default_params = False)
self.assertEquals(3, document.version)
self.assertEquals(None, document.version_flavor)
@@ -264,7 +264,7 @@ DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w=
vote_path = test.integ.descriptor.get_resource("vote")
with open(vote_path) as descriptor_file:
- document = stem.descriptor.networkstatus.NetworkStatusDocument(descriptor_file.read(), default_params = False)
+ document = stem.descriptor.networkstatus.NetworkStatusDocumentV3(descriptor_file.read(), default_params = False)
self.assertEquals(3, document.version)
self.assertEquals(None, document.version_flavor)
diff --git a/test/mocking.py b/test/mocking.py
index ada34aa..ab31eef 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -34,9 +34,9 @@ calling :func:`test.mocking.revert_mocking`.
get_bridge_extrainfo_descriptor - BridgeExtraInfoDescriptor
stem.descriptor.networkstatus
- get_directory_authority - DirectoryAuthority
- get_key_certificate - KeyCertificate
- get_network_status_document - NetworkStatusDocument
+ get_directory_authority - DirectoryAuthority
+ get_key_certificate - KeyCertificate
+ get_network_status_document_v3 - NetworkStatusDocumentV3
stem.descriptor.router_status_entry
get_router_status_entry_v2 - RouterStatusEntryV2
@@ -655,10 +655,10 @@ def get_key_certificate(attr = None, exclude = (), content = False):
else:
return stem.descriptor.networkstatus.KeyCertificate(desc_content, validate = True)
-def get_network_status_document(attr = None, exclude = (), authorities = None, routers = None, content = False):
+def get_network_status_document_v3(attr = None, exclude = (), authorities = None, routers = None, content = False):
"""
Provides the descriptor content for...
- stem.descriptor.networkstatus.NetworkStatusDocument
+ stem.descriptor.networkstatus.NetworkStatusDocumentV3
:param dict attr: keyword/value mappings to be included in the descriptor
:param list exclude: mandatory keywords to exclude from the descriptor
@@ -666,7 +666,7 @@ def get_network_status_document(attr = None, exclude = (), authorities = None, r
:param list routers: router status entries to include in the document
:param bool content: provides the str content of the descriptor rather than the class if True
- :returns: NetworkStatusDocument for the requested descriptor content
+ :returns: NetworkStatusDocumentV3 for the requested descriptor content
"""
if attr is None:
@@ -709,5 +709,5 @@ def get_network_status_document(attr = None, exclude = (), authorities = None, r
if content:
return desc_content
else:
- return stem.descriptor.networkstatus.NetworkStatusDocument(desc_content, validate = True)
+ return stem.descriptor.networkstatus.NetworkStatusDocumentV3(desc_content, validate = True)
diff --git a/test/unit/descriptor/networkstatus/__init__.py b/test/unit/descriptor/networkstatus/__init__.py
index a41defe..75c27b7 100644
--- a/test/unit/descriptor/networkstatus/__init__.py
+++ b/test/unit/descriptor/networkstatus/__init__.py
@@ -2,5 +2,5 @@
Unit tests for stem.descriptor.networkstatus.
"""
-__all__ = ["directory_authority", "key_certificate", "document"]
+__all__ = ["directory_authority", "key_certificate", "document_v3"]
diff --git a/test/unit/descriptor/networkstatus/document.py b/test/unit/descriptor/networkstatus/document.py
deleted file mode 100644
index 7539b63..0000000
--- a/test/unit/descriptor/networkstatus/document.py
+++ /dev/null
@@ -1,799 +0,0 @@
-"""
-Unit tests for the NetworkStatusDocument of stem.descriptor.networkstatus.
-"""
-
-from __future__ import with_statement
-
-import datetime
-import unittest
-import StringIO
-
-import stem.version
-from stem.descriptor import Flag
-from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, DEFAULT_PARAMS, BANDWIDTH_WEIGHT_ENTRIES, DirectoryAuthority, NetworkStatusDocument, parse_file
-from stem.descriptor.router_status_entry import RouterStatusEntryV3, RouterStatusEntryMicroV3
-from test.mocking import support_with, get_router_status_entry_v3, get_router_status_entry_micro_v3, get_directory_authority, get_network_status_document, CRYPTO_BLOB, DOC_SIG, NETWORK_STATUS_DOCUMENT_FOOTER
-
-class TestNetworkStatusDocument(unittest.TestCase):
- def test_minimal_consensus(self):
- """
- Parses a minimal network status document.
- """
-
- document = get_network_status_document()
-
- expected_known_flags = [Flag.AUTHORITY, Flag.BADEXIT, Flag.EXIT,
- Flag.FAST, Flag.GUARD, Flag.HSDIR, Flag.NAMED, Flag.RUNNING,
- Flag.STABLE, Flag.UNNAMED, Flag.V2DIR, Flag.VALID]
-
- self.assertEqual((), document.routers)
- self.assertEqual(3, document.version)
- self.assertEqual(None, document.version_flavor)
- self.assertEqual(True, document.is_consensus)
- self.assertEqual(False, document.is_vote)
- self.assertEqual(False, document.is_microdescriptor)
- self.assertEqual(9, document.consensus_method)
- self.assertEqual([], document.consensus_methods)
- self.assertEqual(None, document.published)
- self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_after)
- self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.fresh_until)
- self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_until)
- self.assertEqual(300, document.vote_delay)
- self.assertEqual(300, document.dist_delay)
- self.assertEqual([], document.client_versions)
- self.assertEqual([], document.server_versions)
- self.assertEqual(expected_known_flags, document.known_flags)
- self.assertEqual(DEFAULT_PARAMS, document.params)
- self.assertEqual((), document.directory_authorities)
- self.assertEqual({}, document.bandwidth_weights)
- self.assertEqual([DOC_SIG], document.signatures)
- self.assertEqual([], document.get_unrecognized_lines())
-
- def test_minimal_vote(self):
- """
- Parses a minimal network status document.
- """
-
- document = get_network_status_document({"vote-status": "vote"})
-
- expected_known_flags = [Flag.AUTHORITY, Flag.BADEXIT, Flag.EXIT,
- Flag.FAST, Flag.GUARD, Flag.HSDIR, Flag.NAMED, Flag.RUNNING,
- Flag.STABLE, Flag.UNNAMED, Flag.V2DIR, Flag.VALID]
-
- self.assertEqual((), document.routers)
- self.assertEqual(3, document.version)
- self.assertEqual(False, document.is_consensus)
- self.assertEqual(True, document.is_vote)
- self.assertEqual(None, document.consensus_method)
- self.assertEqual([1, 9], document.consensus_methods)
- self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.published)
- self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_after)
- self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.fresh_until)
- self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_until)
- self.assertEqual(300, document.vote_delay)
- self.assertEqual(300, document.dist_delay)
- self.assertEqual([], document.client_versions)
- self.assertEqual([], document.server_versions)
- self.assertEqual(expected_known_flags, document.known_flags)
- self.assertEqual(DEFAULT_PARAMS, document.params)
- self.assertEqual((), document.directory_authorities)
- self.assertEqual({}, document.bandwidth_weights)
- self.assertEqual([DOC_SIG], document.signatures)
- self.assertEqual([], document.get_unrecognized_lines())
-
- def test_examples(self):
- """
- Run something similar to the examples in the header pydocs.
- """
-
- # makes a consensus with a couple routers, both with the same nickname
-
- entry1 = get_router_status_entry_v3({'s': "Fast"})
- entry2 = get_router_status_entry_v3({'s': "Valid"})
- content = get_network_status_document(routers = (entry1, entry2), content = True)
-
- # first example: parsing via the NetworkStatusDocument constructor
-
- consensus_file = StringIO.StringIO(content)
- consensus = NetworkStatusDocument(consensus_file.read())
- consensus_file.close()
-
- for router in consensus.routers:
- self.assertEqual('caerSidi', router.nickname)
-
- # second example: using parse_file
-
- with support_with(StringIO.StringIO(content)) as consensus_file:
- for router in parse_file(consensus_file):
- self.assertEqual('caerSidi', router.nickname)
-
- def test_parse_file(self):
- """
- Try parsing a document via the parse_file() function.
- """
-
- entry1 = get_router_status_entry_v3({'s': "Fast"})
- entry2 = get_router_status_entry_v3({'s': "Valid"})
- content = get_network_status_document(routers = (entry1, entry2), content = True)
-
- # the document that the entries refer to should actually be the minimal
- # descriptor (ie, without the entries)
-
- expected_document = get_network_status_document()
-
- descriptor_file = StringIO.StringIO(content)
- entries = list(parse_file(descriptor_file))
-
- self.assertEquals(entry1, entries[0])
- self.assertEquals(entry2, entries[1])
- self.assertEquals(expected_document, entries[0].document)
-
- def test_missing_fields(self):
- """
- Excludes mandatory fields from both a vote and consensus document.
- """
-
- for is_consensus in (True, False):
- attr = {"vote-status": "consensus"} if is_consensus else {"vote-status": "vote"}
- is_vote = not is_consensus
-
- for entries in (HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS):
- for field, in_votes, in_consensus, is_mandatory in entries:
- if is_mandatory and ((is_consensus and in_consensus) or (is_vote and in_votes)):
- content = get_network_status_document(attr, exclude = (field,), content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- NetworkStatusDocument(content, False) # constructs without validation
-
- def test_unrecognized_line(self):
- """
- Includes unrecognized content in the document.
- """
-
- document = get_network_status_document({"pepperjack": "is oh so tasty!"})
- self.assertEquals(["pepperjack is oh so tasty!"], document.get_unrecognized_lines())
-
- def test_misordered_fields(self):
- """
- Rearranges our descriptor fields.
- """
-
- for is_consensus in (True, False):
- attr = {"vote-status": "consensus"} if is_consensus else {"vote-status": "vote"}
- lines = get_network_status_document(attr, content = True).split("\n")
-
- for i in xrange(len(lines) - 1):
- # once we reach the crypto blob we're done since swapping those won't
- # be detected
- if lines[i].startswith(CRYPTO_BLOB[1:10]): break
-
- # swaps this line with the one after it
- test_lines = list(lines)
- test_lines[i], test_lines[i + 1] = test_lines[i + 1], test_lines[i]
-
- content = "\n".join(test_lines)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- NetworkStatusDocument(content, False) # constructs without validation
-
- def test_duplicate_fields(self):
- """
- Almost all fields can only appear once. Checking that duplicates cause
- validation errors.
- """
-
- for is_consensus in (True, False):
- attr = {"vote-status": "consensus"} if is_consensus else {"vote-status": "vote"}
- lines = get_network_status_document(attr, content = True).split("\n")
-
- for i in xrange(len(lines)):
- # Stop when we hit the 'directory-signature' for a couple reasons...
- # - that is the one field that can validly appear multiple times
- # - after it is a crypto blob, which won't trigger this kind of
- # validation failure
-
- test_lines = list(lines)
- if test_lines[i].startswith("directory-signature "):
- break
-
- # duplicates the line
- test_lines.insert(i, test_lines[i])
-
- content = "\n".join(test_lines)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- NetworkStatusDocument(content, False) # constructs without validation
-
- def test_version(self):
- """
- Parses the network-status-version field, including trying to handle a
- different document version with the v3 parser.
- """
-
- document = get_network_status_document({"network-status-version": "3"})
- self.assertEquals(3, document.version)
- self.assertEquals(None, document.version_flavor)
- self.assertEquals(False, document.is_microdescriptor)
-
- document = get_network_status_document({"network-status-version": "3 microdesc"})
- self.assertEquals(3, document.version)
- self.assertEquals('microdesc', document.version_flavor)
- self.assertEquals(True, document.is_microdescriptor)
-
- content = get_network_status_document({"network-status-version": "4"}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(4, document.version)
- self.assertEquals(None, document.version_flavor)
- self.assertEquals(False, document.is_microdescriptor)
-
- def test_vote_status(self):
- """
- Parses the vote-status field.
- """
-
- document = get_network_status_document({"vote-status": "vote"})
- self.assertEquals(False, document.is_consensus)
- self.assertEquals(True, document.is_vote)
-
- content = get_network_status_document({"vote-status": "consensus"}, content = True)
- document = NetworkStatusDocument(content)
- self.assertEquals(True, document.is_consensus)
- self.assertEquals(False, document.is_vote)
-
- test_values = (
- "",
- " ",
- "votee",
- )
-
- for test_value in test_values:
- content = get_network_status_document({"vote-status": test_value}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(True, document.is_consensus)
- self.assertEquals(False, document.is_vote)
-
- def test_consensus_methods(self):
- """
- Parses the consensus-methods field.
- """
-
- document = get_network_status_document({"vote-status": "vote", "consensus-methods": "12 3 1 780"})
- self.assertEquals([12, 3, 1, 780], document.consensus_methods)
-
- # check that we default to including consensus-method 1
- content = get_network_status_document({"vote-status": "vote"}, ("consensus-methods",), content = True)
- document = NetworkStatusDocument(content, False)
- self.assertEquals([1], document.consensus_methods)
- self.assertEquals(None, document.consensus_method)
-
- test_values = (
- ("", []),
- (" ", []),
- ("1 2 3 a 5", [1, 2, 3, 5]),
- ("1 2 3 4.0 5", [1, 2, 3, 5]),
- ("2 3 4", [2, 3, 4]), # spec says version one must be included
- )
-
- for test_value, expected_consensus_methods in test_values:
- content = get_network_status_document({"vote-status": "vote", "consensus-methods": test_value}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected_consensus_methods, document.consensus_methods)
-
- def test_consensus_method(self):
- """
- Parses the consensus-method field.
- """
-
- document = get_network_status_document({"consensus-method": "12"})
- self.assertEquals(12, document.consensus_method)
-
- # check that we default to being consensus-method 1
- content = get_network_status_document(exclude = ("consensus-method",), content = True)
- document = NetworkStatusDocument(content, False)
- self.assertEquals(1, document.consensus_method)
- self.assertEquals([], document.consensus_methods)
-
- test_values = (
- "",
- " ",
- "a",
- "1 2",
- "2.0",
- )
-
- for test_value in test_values:
- content = get_network_status_document({"consensus-method": test_value}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(1, document.consensus_method)
-
- def test_time_fields(self):
- """
- Parses invalid published, valid-after, fresh-until, and valid-until fields.
- All are simply datetime values.
- """
-
- expected = datetime.datetime(2012, 9, 2, 22, 0, 0)
- test_value = "2012-09-02 22:00:00"
-
- document = get_network_status_document({
- "vote-status": "vote",
- "published": test_value,
- "valid-after": test_value,
- "fresh-until": test_value,
- "valid-until": test_value,
- })
-
- self.assertEquals(expected, document.published)
- self.assertEquals(expected, document.valid_after)
- self.assertEquals(expected, document.fresh_until)
- self.assertEquals(expected, document.valid_until)
-
- test_values = (
- "",
- " ",
- "2012-12-12",
- "2012-12-12 01:01:",
- "2012-12-12 01:a1:01",
- )
-
- for field in ('published', 'valid-after', 'fresh-until', 'valid-until'):
- attr = field.replace('-', '_')
-
- for test_value in test_values:
- content = get_network_status_document({"vote-status": "vote", field: test_value}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(None, getattr(document, attr))
-
- def test_voting_delay(self):
- """
- Parses the voting-delay field.
- """
-
- document = get_network_status_document({"voting-delay": "12 345"})
- self.assertEquals(12, document.vote_delay)
- self.assertEquals(345, document.dist_delay)
-
- test_values = (
- "",
- " ",
- "1 a",
- "1\t2",
- "1 2.0",
- )
-
- for test_value in test_values:
- content = get_network_status_document({"voting-delay": test_value}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(None, document.vote_delay)
- self.assertEquals(None, document.dist_delay)
-
- def test_version_lists(self):
- """
- Parses client-versions and server-versions fields. Both are comma separated
- lists of tor versions.
- """
-
- expected = [stem.version.Version("1.2.3.4"), stem.version.Version("56.789.12.34-alpha")]
- test_value = "1.2.3.4,56.789.12.34-alpha"
-
- document = get_network_status_document({"client-versions": test_value, "server-versions": test_value})
- self.assertEquals(expected, document.client_versions)
- self.assertEquals(expected, document.server_versions)
-
- test_values = (
- ("", []),
- (" ", []),
- ("1.2.3.4,", [stem.version.Version("1.2.3.4")]),
- ("1.2.3.4,1.2.3.a", [stem.version.Version("1.2.3.4")]),
- )
-
- for field in ('client-versions', 'server-versions'):
- attr = field.replace('-', '_')
-
- for test_value, expected_value in test_values:
- content = get_network_status_document({field: test_value}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected_value, getattr(document, attr))
-
- def test_known_flags(self):
- """
- Parses some known-flag entries. Just exercising the field, there's not much
- to test here.
- """
-
- test_values = (
- ("", []),
- (" ", []),
- ("BadExit", [Flag.BADEXIT]),
- ("BadExit ", [Flag.BADEXIT]),
- ("BadExit ", [Flag.BADEXIT]),
- ("BadExit Fast", [Flag.BADEXIT, Flag.FAST]),
- ("BadExit Unrecognized Fast", [Flag.BADEXIT, "Unrecognized", Flag.FAST]),
- )
-
- for test_value, expected_value in test_values:
- document = get_network_status_document({"known-flags": test_value})
- self.assertEquals(expected_value, document.known_flags)
-
- def test_params(self):
- """
- General testing for the 'params' line, exercising the happy cases.
- """
-
- document = get_network_status_document({"params": "CircuitPriorityHalflifeMsec=30000 bwauthpid=1 unrecognized=-122"})
- self.assertEquals(30000, document.params["CircuitPriorityHalflifeMsec"])
- self.assertEquals(1, document.params["bwauthpid"])
- self.assertEquals(-122, document.params["unrecognized"])
-
- # empty params line
- content = get_network_status_document({"params": ""}, content = True)
- document = NetworkStatusDocument(content, default_params = True)
- self.assertEquals(DEFAULT_PARAMS, document.params)
-
- content = get_network_status_document({"params": ""}, content = True)
- document = NetworkStatusDocument(content, default_params = False)
- self.assertEquals({}, document.params)
-
- def test_params_malformed(self):
- """
- Parses a 'params' line with malformed content.
- """
-
- test_values = (
- "foo=",
- "foo=abc",
- "foo=+123",
- "foo=12\tbar=12",
- )
-
- for test_value in test_values:
- content = get_network_status_document({"params": test_value}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(DEFAULT_PARAMS, document.params)
-
- def test_params_range(self):
- """
- Check both the furthest valid 'params' values and values that are out of
- bounds.
- """
-
- test_values = (
- ("foo=2147483648", {"foo": 2147483648}, False),
- ("foo=-2147483649", {"foo": -2147483649}, False),
- ("foo=2147483647", {"foo": 2147483647}, True),
- ("foo=-2147483648", {"foo": -2147483648}, True),
-
- # param with special range constraints
- ("circwindow=99", {"circwindow": 99}, False),
- ("circwindow=1001", {"circwindow": 1001}, False),
- ("circwindow=500", {"circwindow": 500}, True),
-
- # param that relies on another param for its constraints
- ("cbtclosequantile=79 cbtquantile=80", {"cbtclosequantile": 79, "cbtquantile": 80}, False),
- ("cbtclosequantile=80 cbtquantile=80", {"cbtclosequantile": 80, "cbtquantile": 80}, True),
- )
-
- for test_value, expected_value, is_ok in test_values:
- content = get_network_status_document({"params": test_value}, content = True)
-
- if is_ok:
- document = NetworkStatusDocument(content, default_params = False)
- else:
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- document = NetworkStatusDocument(content, False, default_params = False)
-
- self.assertEquals(expected_value, document.params)
-
- def test_params_misordered(self):
- """
- Check that the 'params' line is rejected if out of order.
- """
-
- content = get_network_status_document({"params": "unrecognized=-122 bwauthpid=1"}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False, default_params = False)
- self.assertEquals({"unrecognized": -122, "bwauthpid": 1}, document.params)
-
- def test_footer_consensus_method_requirement(self):
- """
- Check that validation will notice if a footer appears before it was
- introduced.
- """
-
- content = get_network_status_document({"consensus-method": "8"}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEqual([DOC_SIG], document.signatures)
- self.assertEqual([], document.get_unrecognized_lines())
-
- # excludes a footer from a version that shouldn't have it
-
- document = get_network_status_document({"consensus-method": "8"}, ("directory-footer", "directory-signature"))
- self.assertEqual([], document.signatures)
- self.assertEqual([], document.get_unrecognized_lines())
-
- def test_footer_with_value(self):
- """
- Tries to parse a descriptor with content on the 'directory-footer' line.
- """
-
- content = get_network_status_document({"directory-footer": "blarg"}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEqual([DOC_SIG], document.signatures)
- self.assertEqual([], document.get_unrecognized_lines())
-
- def test_bandwidth_wights_ok(self):
- """
- Parses a properly formed 'bandwidth-wights' line. Negative bandwidth
- weights might or might not be valid. The spec doesn't say, so making sure
- that we accept them.
- """
-
- weight_entries, expected = [], {}
-
- for i in xrange(len(BANDWIDTH_WEIGHT_ENTRIES)):
- key, value = BANDWIDTH_WEIGHT_ENTRIES[i], i - 5
-
- weight_entries.append("%s=%i" % (key, value))
- expected[key] = value
-
- document = get_network_status_document({"bandwidth-weights": " ".join(weight_entries)})
- self.assertEquals(expected, document.bandwidth_weights)
-
- def test_bandwidth_wights_malformed(self):
- """
- Provides malformed content in the 'bandwidth-wights' line.
- """
-
- test_values = (
- "Wbe",
- "Wbe=",
- "Wbe=a",
- "Wbe=+7",
- )
-
- base_weight_entry = " ".join(["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES])
- expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES if e != "Wbe"])
-
- for test_value in test_values:
- weight_entry = base_weight_entry.replace("Wbe=5", test_value)
- content = get_network_status_document({"bandwidth-weights": weight_entry}, content = True)
-
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected, document.bandwidth_weights)
-
- def test_bandwidth_wights_misordered(self):
- """
- Check that the 'bandwidth-wights' line is rejected if out of order.
- """
-
- weight_entry = " ".join(["%s=5" % e for e in reversed(BANDWIDTH_WEIGHT_ENTRIES)])
- expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES])
-
- content = get_network_status_document({"bandwidth-weights": weight_entry}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected, document.bandwidth_weights)
-
- def test_bandwidth_wights_in_vote(self):
- """
- Tries adding a 'bandwidth-wights' line to a vote.
- """
-
- weight_entry = " ".join(["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES])
- expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES])
-
- content = get_network_status_document({"vote-status": "vote", "bandwidth-weights": weight_entry}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected, document.bandwidth_weights)
-
- def test_bandwidth_wights_omissions(self):
- """
- Leaves entries out of the 'bandwidth-wights' line.
- """
-
- # try parsing an empty value
-
- content = get_network_status_document({"bandwidth-weights": ""}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals({}, document.bandwidth_weights)
-
- # drop individual values
-
- for missing_entry in BANDWIDTH_WEIGHT_ENTRIES:
- weight_entries = ["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES if e != missing_entry]
- expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES if e != missing_entry])
-
- content = get_network_status_document({"bandwidth-weights": " ".join(weight_entries)}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected, document.bandwidth_weights)
-
- def test_microdescriptor_signature(self):
- """
- The 'directory-signature' lines for normal and microdescriptor conensuses
- differ slightly in their format.
- """
-
- # including a microdescriptor flavored 'directory-signature' line should work
-
- document = get_network_status_document({"network-status-version": "3 microdesc"})
- self.assertEqual('sha256', document.signatures[0].method)
-
- # include a standard 'directory-signature' line in a microdescriptor
- # consensus
-
- content = get_network_status_document({
- "network-status-version": "3 microdesc",
- "directory-signature": NETWORK_STATUS_DOCUMENT_FOOTER[2][1],
- }, content = True)
-
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, validate = False)
- self.assertEqual([], document.signatures)
-
- # includes a microdescriptor flavored 'directory-signature' line in a
- # normal consensus
-
- content = get_network_status_document({
- "directory-signature": "sha256 " + NETWORK_STATUS_DOCUMENT_FOOTER[2][1],
- }, content = True)
-
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, validate = False)
- self.assertEqual([], document.signatures)
-
- def test_malformed_signature(self):
- """
- Provides malformed or missing content in the 'directory-signature' line.
- """
-
- test_values = (
- "",
- "\n",
- "blarg",
- )
-
- for test_value in test_values:
- for test_attr in xrange(3):
- attrs = [DOC_SIG.identity, DOC_SIG.key_digest, DOC_SIG.signature]
- attrs[test_attr] = test_value
-
- content = get_network_status_document({"directory-signature": "%s %s\n%s" % tuple(attrs)}, content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- NetworkStatusDocument(content, False) # checks that it's still parseable without validation
-
- def test_with_router_status_entries(self):
- """
- Includes router status entries within the document. This isn't to test the
- RouterStatusEntry parsing but rather the inclusion of it within the
- document.
- """
-
- entry1 = get_router_status_entry_v3({'s': "Fast"})
- entry2 = get_router_status_entry_v3({'s': "Valid"})
- document = get_network_status_document(routers = (entry1, entry2))
-
- self.assertEquals((entry1, entry2), document.routers)
-
- # try with an invalid RouterStatusEntry
-
- entry3 = RouterStatusEntryV3(get_router_status_entry_v3({'r': "ugabuga"}, content = True), False)
- content = get_network_status_document(routers = (entry3,), content = True)
-
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- document = NetworkStatusDocument(content, False)
- self.assertEquals((entry3,), document.routers)
-
- # try including with a microdescriptor consensus
-
- content = get_network_status_document({"network-status-version": "3 microdesc"}, routers = (entry1, entry2), content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- expected_routers = (
- RouterStatusEntryMicroV3(str(entry1), False),
- RouterStatusEntryMicroV3(str(entry2), False),
- )
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected_routers, document.routers)
-
- def test_with_microdescriptor_router_status_entries(self):
- """
- Includes microdescriptor flavored router status entries within the
- document.
- """
-
- entry1 = get_router_status_entry_micro_v3({'s': "Fast"})
- entry2 = get_router_status_entry_micro_v3({'s': "Valid"})
- document = get_network_status_document({"network-status-version": "3 microdesc"}, routers = (entry1, entry2))
-
- self.assertEquals((entry1, entry2), document.routers)
-
- # try with an invalid RouterStatusEntry
-
- entry3 = RouterStatusEntryMicroV3(get_router_status_entry_micro_v3({'r': "ugabuga"}, content = True), False)
- content = get_network_status_document({"network-status-version": "3 microdesc"}, routers = (entry3,), content = True)
-
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- document = NetworkStatusDocument(content, False)
- self.assertEquals((entry3,), document.routers)
-
- # try including microdescriptor entries in a normal consensus
-
- content = get_network_status_document(routers = (entry1, entry2), content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- expected_routers = (
- RouterStatusEntryV3(str(entry1), False),
- RouterStatusEntryV3(str(entry2), False),
- )
-
- document = NetworkStatusDocument(content, False)
- self.assertEquals(expected_routers, document.routers)
-
- def test_with_directory_authorities(self):
- """
- Includes a couple directory authorities in the document.
- """
-
- for is_document_vote in (False, True):
- for is_authorities_vote in (False, True):
- authority1 = get_directory_authority({'contact': 'doctor jekyll'}, is_vote = is_authorities_vote)
- authority2 = get_directory_authority({'contact': 'mister hyde'}, is_vote = is_authorities_vote)
-
- vote_status = "vote" if is_document_vote else "consensus"
- content = get_network_status_document({"vote-status": vote_status}, authorities = (authority1, authority2), content = True)
-
- if is_document_vote == is_authorities_vote:
- document = NetworkStatusDocument(content)
- self.assertEquals((authority1, authority2), document.directory_authorities)
- else:
- # authority votes in a consensus or consensus authorities in a vote
- self.assertRaises(ValueError, NetworkStatusDocument, content)
- document = NetworkStatusDocument(content, validate = False)
- self.assertEquals((authority1, authority2), document.directory_authorities)
-
- def test_authority_validation_flag_propagation(self):
- """
- Includes invalid certificate content in an authority entry. This is testing
- that the 'validate' flag propagages from the document to authority, and
- authority to certificate classes.
- """
-
- # make the dir-key-published field of the certiciate be malformed
- authority_content = get_directory_authority(is_vote = True, content = True)
- authority_content = authority_content.replace("dir-key-published 2011", "dir-key-published 2011a")
-
- content = get_network_status_document({"vote-status": "vote"}, authorities = (authority_content,), content = True)
- self.assertRaises(ValueError, NetworkStatusDocument, content)
-
- document = NetworkStatusDocument(content, validate = False)
- self.assertEquals((DirectoryAuthority(authority_content, False, True),), document.directory_authorities)
-
diff --git a/test/unit/descriptor/networkstatus/document_v3.py b/test/unit/descriptor/networkstatus/document_v3.py
new file mode 100644
index 0000000..47ce4a9
--- /dev/null
+++ b/test/unit/descriptor/networkstatus/document_v3.py
@@ -0,0 +1,799 @@
+"""
+Unit tests for the NetworkStatusDocumentV3 of stem.descriptor.networkstatus.
+"""
+
+from __future__ import with_statement
+
+import datetime
+import unittest
+import StringIO
+
+import stem.version
+from stem.descriptor import Flag
+from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, DEFAULT_PARAMS, BANDWIDTH_WEIGHT_ENTRIES, DirectoryAuthority, NetworkStatusDocumentV3, parse_file
+from stem.descriptor.router_status_entry import RouterStatusEntryV3, RouterStatusEntryMicroV3
+from test.mocking import support_with, get_router_status_entry_v3, get_router_status_entry_micro_v3, get_directory_authority, get_network_status_document_v3, CRYPTO_BLOB, DOC_SIG, NETWORK_STATUS_DOCUMENT_FOOTER
+
+class TestNetworkStatusDocument(unittest.TestCase):
+ def test_minimal_consensus(self):
+ """
+ Parses a minimal network status document.
+ """
+
+ document = get_network_status_document_v3()
+
+ expected_known_flags = [Flag.AUTHORITY, Flag.BADEXIT, Flag.EXIT,
+ Flag.FAST, Flag.GUARD, Flag.HSDIR, Flag.NAMED, Flag.RUNNING,
+ Flag.STABLE, Flag.UNNAMED, Flag.V2DIR, Flag.VALID]
+
+ self.assertEqual((), document.routers)
+ self.assertEqual(3, document.version)
+ self.assertEqual(None, document.version_flavor)
+ self.assertEqual(True, document.is_consensus)
+ self.assertEqual(False, document.is_vote)
+ self.assertEqual(False, document.is_microdescriptor)
+ self.assertEqual(9, document.consensus_method)
+ self.assertEqual([], document.consensus_methods)
+ self.assertEqual(None, document.published)
+ self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_after)
+ self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.fresh_until)
+ self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_until)
+ self.assertEqual(300, document.vote_delay)
+ self.assertEqual(300, document.dist_delay)
+ self.assertEqual([], document.client_versions)
+ self.assertEqual([], document.server_versions)
+ self.assertEqual(expected_known_flags, document.known_flags)
+ self.assertEqual(DEFAULT_PARAMS, document.params)
+ self.assertEqual((), document.directory_authorities)
+ self.assertEqual({}, document.bandwidth_weights)
+ self.assertEqual([DOC_SIG], document.signatures)
+ self.assertEqual([], document.get_unrecognized_lines())
+
+ def test_minimal_vote(self):
+ """
+ Parses a minimal network status document.
+ """
+
+ document = get_network_status_document_v3({"vote-status": "vote"})
+
+ expected_known_flags = [Flag.AUTHORITY, Flag.BADEXIT, Flag.EXIT,
+ Flag.FAST, Flag.GUARD, Flag.HSDIR, Flag.NAMED, Flag.RUNNING,
+ Flag.STABLE, Flag.UNNAMED, Flag.V2DIR, Flag.VALID]
+
+ self.assertEqual((), document.routers)
+ self.assertEqual(3, document.version)
+ self.assertEqual(False, document.is_consensus)
+ self.assertEqual(True, document.is_vote)
+ self.assertEqual(None, document.consensus_method)
+ self.assertEqual([1, 9], document.consensus_methods)
+ self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.published)
+ self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_after)
+ self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.fresh_until)
+ self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.valid_until)
+ self.assertEqual(300, document.vote_delay)
+ self.assertEqual(300, document.dist_delay)
+ self.assertEqual([], document.client_versions)
+ self.assertEqual([], document.server_versions)
+ self.assertEqual(expected_known_flags, document.known_flags)
+ self.assertEqual(DEFAULT_PARAMS, document.params)
+ self.assertEqual((), document.directory_authorities)
+ self.assertEqual({}, document.bandwidth_weights)
+ self.assertEqual([DOC_SIG], document.signatures)
+ self.assertEqual([], document.get_unrecognized_lines())
+
+ def test_examples(self):
+ """
+ Run something similar to the examples in the header pydocs.
+ """
+
+ # makes a consensus with a couple routers, both with the same nickname
+
+ entry1 = get_router_status_entry_v3({'s': "Fast"})
+ entry2 = get_router_status_entry_v3({'s': "Valid"})
+ content = get_network_status_document_v3(routers = (entry1, entry2), content = True)
+
+ # first example: parsing via the NetworkStatusDocumentV3 constructor
+
+ consensus_file = StringIO.StringIO(content)
+ consensus = NetworkStatusDocumentV3(consensus_file.read())
+ consensus_file.close()
+
+ for router in consensus.routers:
+ self.assertEqual('caerSidi', router.nickname)
+
+ # second example: using parse_file
+
+ with support_with(StringIO.StringIO(content)) as consensus_file:
+ for router in parse_file(consensus_file):
+ self.assertEqual('caerSidi', router.nickname)
+
+ def test_parse_file(self):
+ """
+ Try parsing a document via the parse_file() function.
+ """
+
+ entry1 = get_router_status_entry_v3({'s': "Fast"})
+ entry2 = get_router_status_entry_v3({'s': "Valid"})
+ content = get_network_status_document_v3(routers = (entry1, entry2), content = True)
+
+ # the document that the entries refer to should actually be the minimal
+ # descriptor (ie, without the entries)
+
+ expected_document = get_network_status_document_v3()
+
+ descriptor_file = StringIO.StringIO(content)
+ entries = list(parse_file(descriptor_file))
+
+ self.assertEquals(entry1, entries[0])
+ self.assertEquals(entry2, entries[1])
+ self.assertEquals(expected_document, entries[0].document)
+
+ def test_missing_fields(self):
+ """
+ Excludes mandatory fields from both a vote and consensus document.
+ """
+
+ for is_consensus in (True, False):
+ attr = {"vote-status": "consensus"} if is_consensus else {"vote-status": "vote"}
+ is_vote = not is_consensus
+
+ for entries in (HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS):
+ for field, in_votes, in_consensus, is_mandatory in entries:
+ if is_mandatory and ((is_consensus and in_consensus) or (is_vote and in_votes)):
+ content = get_network_status_document_v3(attr, exclude = (field,), content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ NetworkStatusDocumentV3(content, False) # constructs without validation
+
+ def test_unrecognized_line(self):
+ """
+ Includes unrecognized content in the document.
+ """
+
+ document = get_network_status_document_v3({"pepperjack": "is oh so tasty!"})
+ self.assertEquals(["pepperjack is oh so tasty!"], document.get_unrecognized_lines())
+
+ def test_misordered_fields(self):
+ """
+ Rearranges our descriptor fields.
+ """
+
+ for is_consensus in (True, False):
+ attr = {"vote-status": "consensus"} if is_consensus else {"vote-status": "vote"}
+ lines = get_network_status_document_v3(attr, content = True).split("\n")
+
+ for i in xrange(len(lines) - 1):
+ # once we reach the crypto blob we're done since swapping those won't
+ # be detected
+ if lines[i].startswith(CRYPTO_BLOB[1:10]): break
+
+ # swaps this line with the one after it
+ test_lines = list(lines)
+ test_lines[i], test_lines[i + 1] = test_lines[i + 1], test_lines[i]
+
+ content = "\n".join(test_lines)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ NetworkStatusDocumentV3(content, False) # constructs without validation
+
+ def test_duplicate_fields(self):
+ """
+ Almost all fields can only appear once. Checking that duplicates cause
+ validation errors.
+ """
+
+ for is_consensus in (True, False):
+ attr = {"vote-status": "consensus"} if is_consensus else {"vote-status": "vote"}
+ lines = get_network_status_document_v3(attr, content = True).split("\n")
+
+ for i in xrange(len(lines)):
+ # Stop when we hit the 'directory-signature' for a couple reasons...
+ # - that is the one field that can validly appear multiple times
+ # - after it is a crypto blob, which won't trigger this kind of
+ # validation failure
+
+ test_lines = list(lines)
+ if test_lines[i].startswith("directory-signature "):
+ break
+
+ # duplicates the line
+ test_lines.insert(i, test_lines[i])
+
+ content = "\n".join(test_lines)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ NetworkStatusDocumentV3(content, False) # constructs without validation
+
+ def test_version(self):
+ """
+ Parses the network-status-version field, including trying to handle a
+ different document version with the v3 parser.
+ """
+
+ document = get_network_status_document_v3({"network-status-version": "3"})
+ self.assertEquals(3, document.version)
+ self.assertEquals(None, document.version_flavor)
+ self.assertEquals(False, document.is_microdescriptor)
+
+ document = get_network_status_document_v3({"network-status-version": "3 microdesc"})
+ self.assertEquals(3, document.version)
+ self.assertEquals('microdesc', document.version_flavor)
+ self.assertEquals(True, document.is_microdescriptor)
+
+ content = get_network_status_document_v3({"network-status-version": "4"}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(4, document.version)
+ self.assertEquals(None, document.version_flavor)
+ self.assertEquals(False, document.is_microdescriptor)
+
+ def test_vote_status(self):
+ """
+ Parses the vote-status field.
+ """
+
+ document = get_network_status_document_v3({"vote-status": "vote"})
+ self.assertEquals(False, document.is_consensus)
+ self.assertEquals(True, document.is_vote)
+
+ content = get_network_status_document_v3({"vote-status": "consensus"}, content = True)
+ document = NetworkStatusDocumentV3(content)
+ self.assertEquals(True, document.is_consensus)
+ self.assertEquals(False, document.is_vote)
+
+ test_values = (
+ "",
+ " ",
+ "votee",
+ )
+
+ for test_value in test_values:
+ content = get_network_status_document_v3({"vote-status": test_value}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(True, document.is_consensus)
+ self.assertEquals(False, document.is_vote)
+
+ def test_consensus_methods(self):
+ """
+ Parses the consensus-methods field.
+ """
+
+ document = get_network_status_document_v3({"vote-status": "vote", "consensus-methods": "12 3 1 780"})
+ self.assertEquals([12, 3, 1, 780], document.consensus_methods)
+
+ # check that we default to including consensus-method 1
+ content = get_network_status_document_v3({"vote-status": "vote"}, ("consensus-methods",), content = True)
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals([1], document.consensus_methods)
+ self.assertEquals(None, document.consensus_method)
+
+ test_values = (
+ ("", []),
+ (" ", []),
+ ("1 2 3 a 5", [1, 2, 3, 5]),
+ ("1 2 3 4.0 5", [1, 2, 3, 5]),
+ ("2 3 4", [2, 3, 4]), # spec says version one must be included
+ )
+
+ for test_value, expected_consensus_methods in test_values:
+ content = get_network_status_document_v3({"vote-status": "vote", "consensus-methods": test_value}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected_consensus_methods, document.consensus_methods)
+
+ def test_consensus_method(self):
+ """
+ Parses the consensus-method field.
+ """
+
+ document = get_network_status_document_v3({"consensus-method": "12"})
+ self.assertEquals(12, document.consensus_method)
+
+ # check that we default to being consensus-method 1
+ content = get_network_status_document_v3(exclude = ("consensus-method",), content = True)
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(1, document.consensus_method)
+ self.assertEquals([], document.consensus_methods)
+
+ test_values = (
+ "",
+ " ",
+ "a",
+ "1 2",
+ "2.0",
+ )
+
+ for test_value in test_values:
+ content = get_network_status_document_v3({"consensus-method": test_value}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(1, document.consensus_method)
+
+ def test_time_fields(self):
+ """
+ Parses invalid published, valid-after, fresh-until, and valid-until fields.
+ All are simply datetime values.
+ """
+
+ expected = datetime.datetime(2012, 9, 2, 22, 0, 0)
+ test_value = "2012-09-02 22:00:00"
+
+ document = get_network_status_document_v3({
+ "vote-status": "vote",
+ "published": test_value,
+ "valid-after": test_value,
+ "fresh-until": test_value,
+ "valid-until": test_value,
+ })
+
+ self.assertEquals(expected, document.published)
+ self.assertEquals(expected, document.valid_after)
+ self.assertEquals(expected, document.fresh_until)
+ self.assertEquals(expected, document.valid_until)
+
+ test_values = (
+ "",
+ " ",
+ "2012-12-12",
+ "2012-12-12 01:01:",
+ "2012-12-12 01:a1:01",
+ )
+
+ for field in ('published', 'valid-after', 'fresh-until', 'valid-until'):
+ attr = field.replace('-', '_')
+
+ for test_value in test_values:
+ content = get_network_status_document_v3({"vote-status": "vote", field: test_value}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(None, getattr(document, attr))
+
+ def test_voting_delay(self):
+ """
+ Parses the voting-delay field.
+ """
+
+ document = get_network_status_document_v3({"voting-delay": "12 345"})
+ self.assertEquals(12, document.vote_delay)
+ self.assertEquals(345, document.dist_delay)
+
+ test_values = (
+ "",
+ " ",
+ "1 a",
+ "1\t2",
+ "1 2.0",
+ )
+
+ for test_value in test_values:
+ content = get_network_status_document_v3({"voting-delay": test_value}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(None, document.vote_delay)
+ self.assertEquals(None, document.dist_delay)
+
+ def test_version_lists(self):
+ """
+ Parses client-versions and server-versions fields. Both are comma separated
+ lists of tor versions.
+ """
+
+ expected = [stem.version.Version("1.2.3.4"), stem.version.Version("56.789.12.34-alpha")]
+ test_value = "1.2.3.4,56.789.12.34-alpha"
+
+ document = get_network_status_document_v3({"client-versions": test_value, "server-versions": test_value})
+ self.assertEquals(expected, document.client_versions)
+ self.assertEquals(expected, document.server_versions)
+
+ test_values = (
+ ("", []),
+ (" ", []),
+ ("1.2.3.4,", [stem.version.Version("1.2.3.4")]),
+ ("1.2.3.4,1.2.3.a", [stem.version.Version("1.2.3.4")]),
+ )
+
+ for field in ('client-versions', 'server-versions'):
+ attr = field.replace('-', '_')
+
+ for test_value, expected_value in test_values:
+ content = get_network_status_document_v3({field: test_value}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected_value, getattr(document, attr))
+
+ def test_known_flags(self):
+ """
+ Parses some known-flag entries. Just exercising the field, there's not much
+ to test here.
+ """
+
+ test_values = (
+ ("", []),
+ (" ", []),
+ ("BadExit", [Flag.BADEXIT]),
+ ("BadExit ", [Flag.BADEXIT]),
+ ("BadExit ", [Flag.BADEXIT]),
+ ("BadExit Fast", [Flag.BADEXIT, Flag.FAST]),
+ ("BadExit Unrecognized Fast", [Flag.BADEXIT, "Unrecognized", Flag.FAST]),
+ )
+
+ for test_value, expected_value in test_values:
+ document = get_network_status_document_v3({"known-flags": test_value})
+ self.assertEquals(expected_value, document.known_flags)
+
+ def test_params(self):
+ """
+ General testing for the 'params' line, exercising the happy cases.
+ """
+
+ document = get_network_status_document_v3({"params": "CircuitPriorityHalflifeMsec=30000 bwauthpid=1 unrecognized=-122"})
+ self.assertEquals(30000, document.params["CircuitPriorityHalflifeMsec"])
+ self.assertEquals(1, document.params["bwauthpid"])
+ self.assertEquals(-122, document.params["unrecognized"])
+
+ # empty params line
+ content = get_network_status_document_v3({"params": ""}, content = True)
+ document = NetworkStatusDocumentV3(content, default_params = True)
+ self.assertEquals(DEFAULT_PARAMS, document.params)
+
+ content = get_network_status_document_v3({"params": ""}, content = True)
+ document = NetworkStatusDocumentV3(content, default_params = False)
+ self.assertEquals({}, document.params)
+
+ def test_params_malformed(self):
+ """
+ Parses a 'params' line with malformed content.
+ """
+
+ test_values = (
+ "foo=",
+ "foo=abc",
+ "foo=+123",
+ "foo=12\tbar=12",
+ )
+
+ for test_value in test_values:
+ content = get_network_status_document_v3({"params": test_value}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(DEFAULT_PARAMS, document.params)
+
+ def test_params_range(self):
+ """
+ Check both the furthest valid 'params' values and values that are out of
+ bounds.
+ """
+
+ test_values = (
+ ("foo=2147483648", {"foo": 2147483648}, False),
+ ("foo=-2147483649", {"foo": -2147483649}, False),
+ ("foo=2147483647", {"foo": 2147483647}, True),
+ ("foo=-2147483648", {"foo": -2147483648}, True),
+
+ # param with special range constraints
+ ("circwindow=99", {"circwindow": 99}, False),
+ ("circwindow=1001", {"circwindow": 1001}, False),
+ ("circwindow=500", {"circwindow": 500}, True),
+
+ # param that relies on another param for its constraints
+ ("cbtclosequantile=79 cbtquantile=80", {"cbtclosequantile": 79, "cbtquantile": 80}, False),
+ ("cbtclosequantile=80 cbtquantile=80", {"cbtclosequantile": 80, "cbtquantile": 80}, True),
+ )
+
+ for test_value, expected_value, is_ok in test_values:
+ content = get_network_status_document_v3({"params": test_value}, content = True)
+
+ if is_ok:
+ document = NetworkStatusDocumentV3(content, default_params = False)
+ else:
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ document = NetworkStatusDocumentV3(content, False, default_params = False)
+
+ self.assertEquals(expected_value, document.params)
+
+ def test_params_misordered(self):
+ """
+ Check that the 'params' line is rejected if out of order.
+ """
+
+ content = get_network_status_document_v3({"params": "unrecognized=-122 bwauthpid=1"}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False, default_params = False)
+ self.assertEquals({"unrecognized": -122, "bwauthpid": 1}, document.params)
+
+ def test_footer_consensus_method_requirement(self):
+ """
+ Check that validation will notice if a footer appears before it was
+ introduced.
+ """
+
+ content = get_network_status_document_v3({"consensus-method": "8"}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEqual([DOC_SIG], document.signatures)
+ self.assertEqual([], document.get_unrecognized_lines())
+
+ # excludes a footer from a version that shouldn't have it
+
+ document = get_network_status_document_v3({"consensus-method": "8"}, ("directory-footer", "directory-signature"))
+ self.assertEqual([], document.signatures)
+ self.assertEqual([], document.get_unrecognized_lines())
+
+ def test_footer_with_value(self):
+ """
+ Tries to parse a descriptor with content on the 'directory-footer' line.
+ """
+
+ content = get_network_status_document_v3({"directory-footer": "blarg"}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEqual([DOC_SIG], document.signatures)
+ self.assertEqual([], document.get_unrecognized_lines())
+
+ def test_bandwidth_wights_ok(self):
+ """
+ Parses a properly formed 'bandwidth-wights' line. Negative bandwidth
+ weights might or might not be valid. The spec doesn't say, so making sure
+ that we accept them.
+ """
+
+ weight_entries, expected = [], {}
+
+ for i in xrange(len(BANDWIDTH_WEIGHT_ENTRIES)):
+ key, value = BANDWIDTH_WEIGHT_ENTRIES[i], i - 5
+
+ weight_entries.append("%s=%i" % (key, value))
+ expected[key] = value
+
+ document = get_network_status_document_v3({"bandwidth-weights": " ".join(weight_entries)})
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_malformed(self):
+ """
+ Provides malformed content in the 'bandwidth-wights' line.
+ """
+
+ test_values = (
+ "Wbe",
+ "Wbe=",
+ "Wbe=a",
+ "Wbe=+7",
+ )
+
+ base_weight_entry = " ".join(["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES])
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES if e != "Wbe"])
+
+ for test_value in test_values:
+ weight_entry = base_weight_entry.replace("Wbe=5", test_value)
+ content = get_network_status_document_v3({"bandwidth-weights": weight_entry}, content = True)
+
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_misordered(self):
+ """
+ Check that the 'bandwidth-wights' line is rejected if out of order.
+ """
+
+ weight_entry = " ".join(["%s=5" % e for e in reversed(BANDWIDTH_WEIGHT_ENTRIES)])
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES])
+
+ content = get_network_status_document_v3({"bandwidth-weights": weight_entry}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_in_vote(self):
+ """
+ Tries adding a 'bandwidth-wights' line to a vote.
+ """
+
+ weight_entry = " ".join(["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES])
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES])
+
+ content = get_network_status_document_v3({"vote-status": "vote", "bandwidth-weights": weight_entry}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_omissions(self):
+ """
+ Leaves entries out of the 'bandwidth-wights' line.
+ """
+
+ # try parsing an empty value
+
+ content = get_network_status_document_v3({"bandwidth-weights": ""}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals({}, document.bandwidth_weights)
+
+ # drop individual values
+
+ for missing_entry in BANDWIDTH_WEIGHT_ENTRIES:
+ weight_entries = ["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES if e != missing_entry]
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES if e != missing_entry])
+
+ content = get_network_status_document_v3({"bandwidth-weights": " ".join(weight_entries)}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_microdescriptor_signature(self):
+ """
+ The 'directory-signature' lines for normal and microdescriptor conensuses
+ differ slightly in their format.
+ """
+
+ # including a microdescriptor flavored 'directory-signature' line should work
+
+ document = get_network_status_document_v3({"network-status-version": "3 microdesc"})
+ self.assertEqual('sha256', document.signatures[0].method)
+
+ # include a standard 'directory-signature' line in a microdescriptor
+ # consensus
+
+ content = get_network_status_document_v3({
+ "network-status-version": "3 microdesc",
+ "directory-signature": NETWORK_STATUS_DOCUMENT_FOOTER[2][1],
+ }, content = True)
+
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, validate = False)
+ self.assertEqual([], document.signatures)
+
+ # includes a microdescriptor flavored 'directory-signature' line in a
+ # normal consensus
+
+ content = get_network_status_document_v3({
+ "directory-signature": "sha256 " + NETWORK_STATUS_DOCUMENT_FOOTER[2][1],
+ }, content = True)
+
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, validate = False)
+ self.assertEqual([], document.signatures)
+
+ def test_malformed_signature(self):
+ """
+ Provides malformed or missing content in the 'directory-signature' line.
+ """
+
+ test_values = (
+ "",
+ "\n",
+ "blarg",
+ )
+
+ for test_value in test_values:
+ for test_attr in xrange(3):
+ attrs = [DOC_SIG.identity, DOC_SIG.key_digest, DOC_SIG.signature]
+ attrs[test_attr] = test_value
+
+ content = get_network_status_document_v3({"directory-signature": "%s %s\n%s" % tuple(attrs)}, content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ NetworkStatusDocumentV3(content, False) # checks that it's still parseable without validation
+
+ def test_with_router_status_entries(self):
+ """
+ Includes router status entries within the document. This isn't to test the
+ RouterStatusEntry parsing but rather the inclusion of it within the
+ document.
+ """
+
+ entry1 = get_router_status_entry_v3({'s': "Fast"})
+ entry2 = get_router_status_entry_v3({'s': "Valid"})
+ document = get_network_status_document_v3(routers = (entry1, entry2))
+
+ self.assertEquals((entry1, entry2), document.routers)
+
+ # try with an invalid RouterStatusEntry
+
+ entry3 = RouterStatusEntryV3(get_router_status_entry_v3({'r': "ugabuga"}, content = True), False)
+ content = get_network_status_document_v3(routers = (entry3,), content = True)
+
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals((entry3,), document.routers)
+
+ # try including with a microdescriptor consensus
+
+ content = get_network_status_document_v3({"network-status-version": "3 microdesc"}, routers = (entry1, entry2), content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ expected_routers = (
+ RouterStatusEntryMicroV3(str(entry1), False),
+ RouterStatusEntryMicroV3(str(entry2), False),
+ )
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected_routers, document.routers)
+
+ def test_with_microdescriptor_router_status_entries(self):
+ """
+ Includes microdescriptor flavored router status entries within the
+ document.
+ """
+
+ entry1 = get_router_status_entry_micro_v3({'s': "Fast"})
+ entry2 = get_router_status_entry_micro_v3({'s': "Valid"})
+ document = get_network_status_document_v3({"network-status-version": "3 microdesc"}, routers = (entry1, entry2))
+
+ self.assertEquals((entry1, entry2), document.routers)
+
+ # try with an invalid RouterStatusEntry
+
+ entry3 = RouterStatusEntryMicroV3(get_router_status_entry_micro_v3({'r': "ugabuga"}, content = True), False)
+ content = get_network_status_document_v3({"network-status-version": "3 microdesc"}, routers = (entry3,), content = True)
+
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals((entry3,), document.routers)
+
+ # try including microdescriptor entries in a normal consensus
+
+ content = get_network_status_document_v3(routers = (entry1, entry2), content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ expected_routers = (
+ RouterStatusEntryV3(str(entry1), False),
+ RouterStatusEntryV3(str(entry2), False),
+ )
+
+ document = NetworkStatusDocumentV3(content, False)
+ self.assertEquals(expected_routers, document.routers)
+
+ def test_with_directory_authorities(self):
+ """
+ Includes a couple directory authorities in the document.
+ """
+
+ for is_document_vote in (False, True):
+ for is_authorities_vote in (False, True):
+ authority1 = get_directory_authority({'contact': 'doctor jekyll'}, is_vote = is_authorities_vote)
+ authority2 = get_directory_authority({'contact': 'mister hyde'}, is_vote = is_authorities_vote)
+
+ vote_status = "vote" if is_document_vote else "consensus"
+ content = get_network_status_document_v3({"vote-status": vote_status}, authorities = (authority1, authority2), content = True)
+
+ if is_document_vote == is_authorities_vote:
+ document = NetworkStatusDocumentV3(content)
+ self.assertEquals((authority1, authority2), document.directory_authorities)
+ else:
+ # authority votes in a consensus or consensus authorities in a vote
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+ document = NetworkStatusDocumentV3(content, validate = False)
+ self.assertEquals((authority1, authority2), document.directory_authorities)
+
+ def test_authority_validation_flag_propagation(self):
+ """
+ Includes invalid certificate content in an authority entry. This is testing
+ that the 'validate' flag propagages from the document to authority, and
+ authority to certificate classes.
+ """
+
+ # make the dir-key-published field of the certiciate be malformed
+ authority_content = get_directory_authority(is_vote = True, content = True)
+ authority_content = authority_content.replace("dir-key-published 2011", "dir-key-published 2011a")
+
+ content = get_network_status_document_v3({"vote-status": "vote"}, authorities = (authority_content,), content = True)
+ self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
+
+ document = NetworkStatusDocumentV3(content, validate = False)
+ self.assertEquals((DirectoryAuthority(authority_content, False, True),), document.directory_authorities)
+
More information about the tor-commits
mailing list