[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [stem/master] RelayDescriptor creation



commit dafb4a6a96c0c1eb138a7c7d9499c5577864dc67
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date:   Sun Apr 30 16:37:16 2017 -0700

    RelayDescriptor creation
    
    Implementing a function for creating our first descriptor type (relay server
    descriptors). This replacing our mocking util's get_relay_server_descriptor()
    with RelayDescriptor.create() and RelayDescriptor.content().
    
    This is a little different from the API I was previously planning on but think
    I like this better.
---
 stem/descriptor/__init__.py               | 154 ++++++++++++++++++++----------
 stem/descriptor/server_descriptor.py      |  30 +++++-
 test/mocking.py                           |  41 --------
 test/unit/descriptor/export.py            |  14 +--
 test/unit/descriptor/server_descriptor.py |  95 ++++++++----------
 test/unit/tutorial.py                     |   8 +-
 test/unit/tutorial_examples.py            |  10 +-
 7 files changed, 188 insertions(+), 164 deletions(-)

diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index d50b661..32b7d7c 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -18,22 +18,6 @@ Package for parsing and processing descriptor data.
     |- get_unrecognized_lines - unparsed descriptor content
     +- __str__ - string that the descriptor was made from
 
-.. data:: DescriptorType (enum)
-
-  Common descriptor types.
-
-  .. versionadded:: 1.6.0
-
-  =================== ===========
-  DescriptorType      Description
-  =================== ===========
-  **SERVER**          :class:`~stem.descriptor.server_descriptor.RelayDescriptor`
-  **EXTRA**           :class:`~stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`
-  **MICRO**           :class:`~stem.descriptor.microdescriptor.Microdescriptor`
-  **CONSENSUS**       :class:`~stem.descriptor.networkstatus.NetworkStatusDocumentV3`
-  **HIDDEN_SERVICE**  :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor`
-  =================== ===========
-
 .. data:: DocumentHandler (enum)
 
   Ways in which we can parse a
@@ -97,20 +81,18 @@ PGP_BLOCK_START = re.compile('^-----BEGIN ([%s%s]+)-----$' % (KEYWORD_CHAR, WHIT
 PGP_BLOCK_END = '-----END %s-----'
 EMPTY_COLLECTION = ([], {}, set())
 
+CRYPTO_BLOB = """
+MIGJAoGBAJv5IIWQ+WDWYUdyA/0L8qbIkEVH/cwryZWoIaPAzINfrw1WfNZGtBmg
+skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+
+WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE=
+"""
+
 DocumentHandler = stem.util.enum.UppercaseEnum(
   'ENTRIES',
   'DOCUMENT',
   'BARE_DOCUMENT',
 )
 
-DescriptorType = stem.util.enum.Enum(
-  ('SERVER', 'server-descriptor 1.0'),
-  ('EXTRAINFO', 'extra-info 1.0'),
-  ('MICRO', 'microdescriptor 1.0'),
-  ('CONSENSUS', 'network-status-consensus-3 1.0'),
-  ('HIDDEN_SERVICE', 'hidden-service-descriptor 1.0'),
-)
-
 
 def parse_file(descriptor_file, descriptor_type = None, validate = False, document_handler = DocumentHandler.ENTRIES, normalize_newlines = None, **kwargs):
   """
@@ -353,39 +335,64 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
     raise TypeError("Unrecognized metrics descriptor format. type: '%s', version: '%i.%i'" % (descriptor_type, major_version, minor_version))
 
 
-def create(desc_type, attr = None, exclude = (), validate = False, sign = False):
+def _descriptor_content(attr = None, exclude = (), header_template = (), footer_template = ()):
   """
-  Creates a descriptor with the given attributes.
+  Constructs a minimal descriptor with the given attributes. The content we
+  provide back is of the form...
 
-  .. versionadded:: 1.6.0
+  * header_template (with matching attr filled in)
+  * unused attr entries
+  * footer_template (with matching attr filled in)
 
-  :param DescriptorType desc_type: type of descriptor to be created
-  :param dict attr: keyword/value mappings to be included in the descriptor
-  :param list exclude: mandatory keywords to exclude from the descriptor, this
-    results in an invalid descriptor
-  :param bool validate: checks the validity of the descriptor's content if
-    **True**, skips these checks otherwise
-  :param bool sign_content: includes cryptographic digest if True
+  So for instance...
+
+  ::
 
-  :returns: :class:`~stem.descriptor.Descriptor` subclass
+    _descriptor_content(
+      attr = {'nickname': 'caerSidi', 'contact': 'atagar'},
+      header_template = (
+        ('nickname', 'foobar'),
+        ('fingerprint', '12345'),
+      ),
+    )
 
-  :raises:
-    * **ValueError** if the contents is malformed and validate is True
-    * **ImportError** if cryptography is unavailable and sign is True
+  ... would result in...
+
+  ::
+
+    nickname caerSidi
+    fingerprint 12345
+    contact atagar
+
+  :param dict attr: keyword/value mappings to be included in the descriptor
+  :param list exclude: mandatory keywords to exclude from the descriptor
+  :param tuple header_template: key/value pairs for mandatory fields before unrecognized content
+  :param tuple footer_template: key/value pairs for mandatory fields after unrecognized content
+
+  :returns: str with the requested descriptor content
   """
 
-  if desc_type == DescriptorType.SERVER:
-    pass
-  elif desc_type == DescriptorType.EXTRAINFO:
-    pass
-  elif desc_type == DescriptorType.MICRO:
-    pass
-  elif desc_type == DescriptorType.CONSENSUS:
-    pass
-  elif desc_type == DescriptorType.HIDDEN_SERVICE:
-    pass
-  else:
-    raise TypeError("'%s' isn't a valid descriptor type we can create" % desc_type)
+  header_content, footer_content = [], []
+  attr = {} if attr is None else dict(attr)  # shallow copy since we're destructive
+
+  for content, template in ((header_content, header_template),
+                            (footer_content, footer_template)):
+    for keyword, value in template:
+      if keyword in exclude:
+        continue
+
+      value = attr.pop(keyword, value)
+
+      if not value:
+        content.append(keyword)
+      elif value.startswith('\n'):
+        # some values like crypto follow the line instead
+        content.append('%s%s' % (keyword, value))
+      else:
+        content.append('%s %s' % (keyword, value))
+
+  remainder = [('%s %s' % (k, v) if v else k) for k, v in attr.items()]
+  return stem.util.str_tools._to_bytes('\n'.join(header_content + remainder + footer_content))
 
 
 def _value(line, entries):
@@ -522,6 +529,53 @@ class Descriptor(object):
     self._entries = {}
     self._unrecognized_lines = []
 
+  @classmethod
+  def content(cls, attr = None, exclude = (), sign = False):
+    """
+    Creates descriptor content with the given attributes. Mandatory fields are
+    filled with dummy information unless data is supplied.
+
+    .. versionadded:: 1.6.0
+
+    :param dict attr: keyword/value mappings to be included in the descriptor
+    :param list exclude: mandatory keywords to exclude from the descriptor, this
+      results in an invalid descriptor
+    :param bool sign_content: includes cryptographic digest if True
+
+    :returns: **str** with the content of a descriptor
+
+    :raises:
+      * **ImportError** if cryptography is unavailable and sign is True
+      * **NotImplementedError** if not implemented for this descriptor type
+    """
+
+    raise NotImplementedError("The create and content methods haven't been implemented for %s" % cls.__name__)
+
+  @classmethod
+  def create(cls, attr = None, exclude = (), validate = True, sign = False):
+    """
+    Creates a descriptor with the given attributes. Mandatory fields are filled
+    with dummy information unless data is supplied.
+
+    .. versionadded:: 1.6.0
+
+    :param dict attr: keyword/value mappings to be included in the descriptor
+    :param list exclude: mandatory keywords to exclude from the descriptor, this
+      results in an invalid descriptor
+    :param bool validate: checks the validity of the descriptor's content if
+      **True**, skips these checks otherwise
+    :param bool sign_content: includes cryptographic digest if True
+
+    :returns: :class:`~stem.descriptor.Descriptor` subclass
+
+    :raises:
+      * **ValueError** if the contents is malformed and validate is True
+      * **ImportError** if cryptography is unavailable and sign is True
+      * **NotImplementedError** if not implemented for this descriptor type
+    """
+
+    return cls(cls.content(attr, exclude, sign), validate = validate)
+
   def get_path(self):
     """
     Provides the absolute path that we loaded this descriptor from.
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index ddc1149..9f7c846 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -49,8 +49,10 @@ import stem.version
 from stem.util import str_type
 
 from stem.descriptor import (
+  CRYPTO_BLOB,
   PGP_BLOCK_END,
   Descriptor,
+  _descriptor_content,
   _get_descriptor_components,
   _read_until_keywords,
   _bytes_for_block,
@@ -110,6 +112,19 @@ SINGLE_FIELDS = (
 DEFAULT_IPV6_EXIT_POLICY = stem.exit_policy.MicroExitPolicy('reject 1-65535')
 REJECT_ALL_POLICY = stem.exit_policy.ExitPolicy('reject *:*')
 
+RELAY_SERVER_HEADER = (
+  ('router', 'caerSidi 71.35.133.197 9001 0 0'),
+  ('published', '2012-03-01 17:15:27'),
+  ('bandwidth', '153600 256000 104590'),
+  ('reject', '*:*'),
+  ('onion-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB),
+  ('signing-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB),
+)
+
+RELAY_SERVER_FOOTER = (
+  ('router-signature', '\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----' % CRYPTO_BLOB),
+)
+
 
 def _parse_file(descriptor_file, is_bridge = False, validate = False, **kwargs):
   """
@@ -738,6 +753,9 @@ class RelayDescriptor(ServerDescriptor):
      Our **ed25519_certificate** is deprecated in favor of our new
      **certificate** attribute. The base64 encoded certificate is available via
      the certificate's **encoded** attribute.
+
+  .. versionchanged:: 1.6.0
+     Added the **skip_crypto_validation** constructor argument.
   """
 
   ATTRIBUTES = dict(ServerDescriptor.ATTRIBUTES, **{
@@ -765,7 +783,7 @@ class RelayDescriptor(ServerDescriptor):
     'router-signature': _parse_router_signature_line,
   })
 
-  def __init__(self, raw_contents, validate = False, annotations = None):
+  def __init__(self, raw_contents, validate = False, annotations = None, skip_crypto_validation = False):
     super(RelayDescriptor, self).__init__(raw_contents, validate, annotations)
 
     if validate:
@@ -775,7 +793,7 @@ class RelayDescriptor(ServerDescriptor):
         if key_hash != self.fingerprint.lower():
           raise ValueError('Fingerprint does not match the hash of our signing key (fingerprint: %s, signing key hash: %s)' % (self.fingerprint.lower(), key_hash))
 
-      if stem.prereq.is_crypto_available():
+      if not skip_crypto_validation and stem.prereq.is_crypto_available():
         signed_digest = self._digest_for_signature(self.signing_key, self.signature)
 
         if signed_digest != self.digest():
@@ -790,6 +808,14 @@ class RelayDescriptor(ServerDescriptor):
       if stem.prereq._is_pynacl_available() and self.certificate:
         self.certificate.validate(self)
 
+  @classmethod
+  def content(cls, attr = None, exclude = (), sign = False):
+    return _descriptor_content(attr, exclude, RELAY_SERVER_HEADER, RELAY_SERVER_FOOTER)
+
+  @classmethod
+  def create(cls, attr = None, exclude = (), validate = True, sign = False):
+    return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
+
   @lru_cache()
   def digest(self):
     """
diff --git a/test/mocking.py b/test/mocking.py
index c293cf0..4843a97 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -14,7 +14,6 @@ Helper functions for creating mock objects.
     get_protocolinfo_response       - stem.response.protocolinfo.ProtocolInfoResponse
 
     stem.descriptor.server_descriptor
-      get_relay_server_descriptor  - RelayDescriptor
       get_bridge_server_descriptor - BridgeDescriptor
 
     stem.descriptor.microdescriptor
@@ -80,19 +79,6 @@ DOC_SIG = stem.descriptor.networkstatus.DocumentSignature(
   'BF112F1C6D5543CFD0A32215ACABD4197B5279AD',
   '-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----' % CRYPTO_BLOB)
 
-RELAY_SERVER_HEADER = (
-  ('router', 'caerSidi 71.35.133.197 9001 0 0'),
-  ('published', '2012-03-01 17:15:27'),
-  ('bandwidth', '153600 256000 104590'),
-  ('reject', '*:*'),
-  ('onion-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB),
-  ('signing-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB),
-)
-
-RELAY_SERVER_FOOTER = (
-  ('router-signature', '\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----' % CRYPTO_BLOB),
-)
-
 BRIDGE_SERVER_HEADER = (
   ('router', 'Unnamed 10.45.227.253 9001 0 0'),
   ('router-digest', '006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4'),
@@ -366,33 +352,6 @@ def _get_descriptor_content(attr = None, exclude = (), header_template = (), foo
   return stem.util.str_tools._to_bytes('\n'.join(header_content + remainder + footer_content))
 
 
-def get_relay_server_descriptor(attr = None, exclude = (), content = False, sign_content = False):
-  """
-  Provides the descriptor content for...
-  stem.descriptor.server_descriptor.RelayDescriptor
-
-  :param dict attr: keyword/value mappings to be included in the descriptor
-  :param list exclude: mandatory keywords to exclude from the descriptor
-  :param bool content: provides the str content of the descriptor rather than the class if True
-  :param bool sign_content: sets a proper digest value if True
-
-  :returns: RelayDescriptor for the requested descriptor content
-  """
-
-  desc_content = _get_descriptor_content(attr, exclude, RELAY_SERVER_HEADER, RELAY_SERVER_FOOTER)
-
-  if content:
-    return desc_content
-  else:
-    if sign_content:
-      desc_content = sign_descriptor_content(desc_content)
-
-    with patch('stem.prereq.is_crypto_available', Mock(return_value = False)):
-      desc = stem.descriptor.server_descriptor.RelayDescriptor(desc_content, validate = True)
-
-    return desc
-
-
 def get_bridge_server_descriptor(attr = None, exclude = (), content = False):
   """
   Provides the descriptor content for...
diff --git a/test/unit/descriptor/export.py b/test/unit/descriptor/export.py
index a262343..009c43d 100644
--- a/test/unit/descriptor/export.py
+++ b/test/unit/descriptor/export.py
@@ -11,10 +11,10 @@ except ImportError:
 
 import stem.prereq
 
+from stem.descriptor.server_descriptor import RelayDescriptor
 from stem.descriptor.export import export_csv, export_csv_file
 
 from test.mocking import (
-  get_relay_server_descriptor,
   get_bridge_server_descriptor,
 )
 
@@ -29,7 +29,7 @@ class TestExport(unittest.TestCase):
       self.skipTest('(header added in python 2.7)')
       return
 
-    desc = get_relay_server_descriptor()
+    desc = RelayDescriptor.create()
 
     desc_csv = export_csv(desc, included_fields = ('nickname', 'address', 'published'), header = False)
     expected = 'caerSidi,71.35.133.197,2012-03-01 17:15:27\n'
@@ -50,7 +50,7 @@ class TestExport(unittest.TestCase):
 
     for nickname in nicknames:
       router_line = '%s 71.35.133.197 9001 0 0' % nickname
-      descriptors.append(get_relay_server_descriptor({'router': router_line}))
+      descriptors.append(RelayDescriptor.create({'router': router_line}))
 
     expected = '\n'.join(nicknames) + '\n'
     self.assertEqual(expected, export_csv(descriptors, included_fields = ('nickname',), header = False))
@@ -61,7 +61,7 @@ class TestExport(unittest.TestCase):
     the same output as export_csv().
     """
 
-    desc = get_relay_server_descriptor()
+    desc = RelayDescriptor.create()
     desc_csv = export_csv(desc)
 
     csv_buffer = StringIO()
@@ -78,7 +78,7 @@ class TestExport(unittest.TestCase):
       self.skipTest('(header added in python 2.7)')
       return
 
-    desc = get_relay_server_descriptor()
+    desc = RelayDescriptor.create()
     desc_csv = export_csv(desc)
 
     self.assertTrue(',signature' in desc_csv)
@@ -96,7 +96,7 @@ class TestExport(unittest.TestCase):
     Attempts to make a csv with attributes that don't exist.
     """
 
-    desc = get_relay_server_descriptor()
+    desc = RelayDescriptor.create()
     self.assertRaises(ValueError, export_csv, desc, ('nickname', 'blarg!'))
 
   def test_multiple_descriptor_types(self):
@@ -104,6 +104,6 @@ class TestExport(unittest.TestCase):
     Attempts to make a csv with multiple descriptor types.
     """
 
-    server_desc = get_relay_server_descriptor()
+    server_desc = RelayDescriptor.create()
     bridge_desc = get_bridge_server_descriptor()
     self.assertRaises(ValueError, export_csv, (server_desc, bridge_desc))
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py
index 747600c..1b754bf 100644
--- a/test/unit/descriptor/server_descriptor.py
+++ b/test/unit/descriptor/server_descriptor.py
@@ -21,7 +21,6 @@ from stem.descriptor.certificate import CertType, ExtensionType
 from stem.descriptor.server_descriptor import RelayDescriptor, BridgeDescriptor
 
 from test.mocking import (
-  get_relay_server_descriptor,
   get_bridge_server_descriptor,
   CRYPTO_BLOB,
 )
@@ -433,8 +432,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     attributes.
     """
 
-    desc = get_relay_server_descriptor()
-
+    desc = RelayDescriptor.create()
     self.assertEqual('caerSidi', desc.nickname)
     self.assertEqual('71.35.133.197', desc.address)
     self.assertEqual(None, desc.fingerprint)
@@ -445,7 +443,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Includes an 'opt <keyword> <value>' entry.
     """
 
-    desc = get_relay_server_descriptor({'opt': 'contact www.atagar.com/contact/'})
+    desc = RelayDescriptor.create({'opt': 'contact www.atagar.com/contact/'})
     self.assertEqual(b'www.atagar.com/contact/', desc.contact)
 
   def test_unrecognized_line(self):
@@ -453,7 +451,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Includes unrecognized content in the descriptor.
     """
 
-    desc = get_relay_server_descriptor({'pepperjack': 'is oh so tasty!'})
+    desc = RelayDescriptor.create({'pepperjack': 'is oh so tasty!'})
     self.assertEqual(['pepperjack is oh so tasty!'], desc.get_unrecognized_lines())
 
   def test_proceeding_line(self):
@@ -461,79 +459,72 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Includes a line prior to the 'router' entry.
     """
 
-    desc_text = b'hibernate 1\n' + get_relay_server_descriptor(content = True)
-    self._expect_invalid_attr(desc_text)
+    desc_text = b'hibernate 1\n' + RelayDescriptor.content()
+    self._expect_invalid_attr_for_text(desc_text)
 
   def test_trailing_line(self):
     """
     Includes a line after the 'router-signature' entry.
     """
 
-    desc_text = get_relay_server_descriptor(content = True) + b'\nhibernate 1'
-    self._expect_invalid_attr(desc_text)
+    desc_text = RelayDescriptor.content() + b'\nhibernate 1'
+    self._expect_invalid_attr_for_text(desc_text)
 
   def test_nickname_missing(self):
     """
     Constructs with a malformed router entry.
     """
 
-    desc_text = get_relay_server_descriptor({'router': ' 71.35.133.197 9001 0 0'}, content = True)
-    self._expect_invalid_attr(desc_text, 'nickname')
+    self._expect_invalid_attr({'router': ' 71.35.133.197 9001 0 0'}, 'nickname')
 
   def test_nickname_too_long(self):
     """
     Constructs with a nickname that is an invalid length.
     """
 
-    desc_text = get_relay_server_descriptor({'router': 'saberrider2008ReallyLongNickname 71.35.133.197 9001 0 0'}, content = True)
-    self._expect_invalid_attr(desc_text, 'nickname')
+    self._expect_invalid_attr({'router': 'saberrider2008ReallyLongNickname 71.35.133.197 9001 0 0'}, 'nickname')
 
   def test_nickname_invalid_char(self):
     """
     Constructs with an invalid relay nickname.
     """
 
-    desc_text = get_relay_server_descriptor({'router': '$aberrider2008 71.35.133.197 9001 0 0'}, content = True)
-    self._expect_invalid_attr(desc_text, 'nickname')
+    self._expect_invalid_attr({'router': '$aberrider2008 71.35.133.197 9001 0 0'}, 'nickname')
 
   def test_address_malformed(self):
     """
     Constructs with an invalid ip address.
     """
 
-    desc_text = get_relay_server_descriptor({'router': 'caerSidi 371.35.133.197 9001 0 0'}, content = True)
-    self._expect_invalid_attr(desc_text, 'address')
+    self._expect_invalid_attr({'router': 'caerSidi 371.35.133.197 9001 0 0'}, 'address')
 
   def test_port_too_high(self):
     """
     Constructs with an ORPort that is too large.
     """
 
-    desc_text = get_relay_server_descriptor({'router': 'caerSidi 71.35.133.197 900001 0 0'}, content = True)
-    self._expect_invalid_attr(desc_text, 'or_port')
+    self._expect_invalid_attr({'router': 'caerSidi 71.35.133.197 900001 0 0'}, 'or_port')
 
   def test_port_malformed(self):
     """
     Constructs with an ORPort that isn't numeric.
     """
 
-    desc_text = get_relay_server_descriptor({'router': 'caerSidi 71.35.133.197 900a1 0 0'}, content = True)
-    self._expect_invalid_attr(desc_text, 'or_port')
+    self._expect_invalid_attr({'router': 'caerSidi 71.35.133.197 900a1 0 0'}, 'or_port')
 
   def test_port_newline(self):
     """
     Constructs with a newline replacing the ORPort.
     """
 
-    desc_text = get_relay_server_descriptor({'router': 'caerSidi 71.35.133.197 \n 0 0'}, content = True)
-    self._expect_invalid_attr(desc_text, 'or_port')
+    self._expect_invalid_attr({'router': 'caerSidi 71.35.133.197 \n 0 0'}, 'or_port')
 
   def test_platform_empty(self):
     """
     Constructs with an empty platform entry.
     """
 
-    desc_text = get_relay_server_descriptor({'platform': ''}, content = True)
+    desc_text = RelayDescriptor.content({'platform': ''})
     desc = RelayDescriptor(desc_text, validate = False)
     self.assertEqual(b'', desc.platform)
 
@@ -547,7 +538,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Parse a platform line belonging to a node-Tor relay.
     """
 
-    desc = get_relay_server_descriptor({'platform': 'node-Tor 0.1.0 on Linux x86_64'})
+    desc = RelayDescriptor.create({'platform': 'node-Tor 0.1.0 on Linux x86_64'})
     self.assertEqual(b'node-Tor 0.1.0 on Linux x86_64', desc.platform)
     self.assertEqual(stem.version.Version('0.1.0'), desc.tor_version)
     self.assertEqual('Linux x86_64', desc.operating_system)
@@ -557,8 +548,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Constructs with a protocols line without circuit versions.
     """
 
-    desc_text = get_relay_server_descriptor({'opt': 'protocols Link 1 2'}, content = True)
-    self._expect_invalid_attr(desc_text, 'circuit_protocols')
+    self._expect_invalid_attr({'opt': 'protocols Link 1 2'}, 'circuit_protocols')
 
   @patch('stem.prereq.is_crypto_available', Mock(return_value = False))
   def test_published_leap_year(self):
@@ -567,20 +557,17 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     invalid.
     """
 
-    desc_text = get_relay_server_descriptor({'published': '2011-02-29 04:03:19'}, content = True)
-    self._expect_invalid_attr(desc_text, 'published')
+    self._expect_invalid_attr({'published': '2011-02-29 04:03:19'}, 'published')
 
-    desc_text = get_relay_server_descriptor({'published': '2012-02-29 04:03:19'}, content = True)
-    expected_published = datetime.datetime(2012, 2, 29, 4, 3, 19)
-    self.assertEqual(expected_published, RelayDescriptor(desc_text).published)
+    desc = RelayDescriptor.create({'published': '2012-02-29 04:03:19'})
+    self.assertEqual(datetime.datetime(2012, 2, 29, 4, 3, 19), desc.published)
 
   def test_published_no_time(self):
     """
     Constructs with a published entry without a time component.
     """
 
-    desc_text = get_relay_server_descriptor({'published': '2012-01-01'}, content = True)
-    self._expect_invalid_attr(desc_text, 'published')
+    self._expect_invalid_attr({'published': '2012-01-01'}, 'published')
 
   def test_read_and_write_history(self):
     """
@@ -591,7 +578,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
 
     for field in ('read-history', 'write-history'):
       value = '2005-12-16 18:00:48 (900 s) 81,8848,8927,8927,83,8848'
-      desc = get_relay_server_descriptor({'opt %s' % field: value})
+      desc = RelayDescriptor.create({'opt %s' % field: value})
 
       if field == 'read-history':
         attr = (desc.read_history_end, desc.read_history_interval, desc.read_history_values)
@@ -610,8 +597,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Parses a read-history with an empty value.
     """
 
-    value = '2005-12-17 01:23:11 (900 s) '
-    desc = get_relay_server_descriptor({'opt read-history': value})
+    desc = RelayDescriptor.create({'opt read-history': '2005-12-17 01:23:11 (900 s) '})
     self.assertEqual(datetime.datetime(2005, 12, 17, 1, 23, 11), desc.read_history_end)
     self.assertEqual(900, desc.read_history_interval)
     self.assertEqual([], desc.read_history_values)
@@ -623,7 +609,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     """
 
     desc_text = b'@pepperjack very tasty\n@mushrooms not so much\n'
-    desc_text += get_relay_server_descriptor(content = True)
+    desc_text += RelayDescriptor.content()
     desc_text += b'\ntrailing text that should be invalid, ho hum'
 
     # running _parse_file should provide an iterator with a single descriptor
@@ -631,7 +617,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     self.assertRaises(ValueError, list, desc_iter)
 
     desc_text = b'@pepperjack very tasty\n@mushrooms not so much\n'
-    desc_text += get_relay_server_descriptor(content = True)
+    desc_text += RelayDescriptor.content()
     desc_iter = stem.descriptor.server_descriptor._parse_file(io.BytesIO(desc_text))
 
     desc_entries = list(desc_iter)
@@ -649,9 +635,9 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Constructs with a field appearing twice.
     """
 
-    desc_text = get_relay_server_descriptor({'<replace>': ''}, content = True)
+    desc_text = RelayDescriptor.content({'<replace>': ''})
     desc_text = desc_text.replace(b'<replace>', b'contact foo\ncontact bar')
-    self._expect_invalid_attr(desc_text, 'contact', b'foo')
+    self._expect_invalid_attr_for_text(desc_text, 'contact', b'foo')
 
   def test_missing_required_attr(self):
     """
@@ -659,7 +645,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     """
 
     for attr in stem.descriptor.server_descriptor.REQUIRED_FIELDS:
-      desc_text = get_relay_server_descriptor(exclude = [attr], content = True)
+      desc_text = RelayDescriptor.content(exclude = [attr])
       self.assertRaises(ValueError, RelayDescriptor, desc_text, True)
 
       # check that we can still construct it without validation
@@ -680,24 +666,22 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     """
 
     fingerprint = '4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE45'
-    desc_text = get_relay_server_descriptor({'opt fingerprint': fingerprint}, content = True)
-    self._expect_invalid_attr(desc_text, 'fingerprint', fingerprint.replace(' ', ''))
+    self._expect_invalid_attr({'opt fingerprint': fingerprint}, 'fingerprint', fingerprint.replace(' ', ''))
 
   def test_ipv6_policy(self):
     """
     Checks a 'ipv6-policy' line.
     """
 
-    expected = stem.exit_policy.MicroExitPolicy('accept 22-23,53,80,110')
-    desc = get_relay_server_descriptor({'ipv6-policy': 'accept 22-23,53,80,110'})
-    self.assertEqual(expected, desc.exit_policy_v6)
+    desc = RelayDescriptor.create({'ipv6-policy': 'accept 22-23,53,80,110'})
+    self.assertEqual(stem.exit_policy.MicroExitPolicy('accept 22-23,53,80,110'), desc.exit_policy_v6)
 
   def test_extrainfo_sha256_digest(self):
     """
     Extrainfo descriptor line with both a hex and base64 encoded sha256 digest.
     """
 
-    desc = get_relay_server_descriptor({'extra-info-digest': '03272BF7C68484AFBDA508DAE3734D809E4A5BC4 DWMz1AEdqPlcroubwx3lPEoGbT+oX7S2BH653sPIqfI'})
+    desc = RelayDescriptor.create({'extra-info-digest': '03272BF7C68484AFBDA508DAE3734D809E4A5BC4 DWMz1AEdqPlcroubwx3lPEoGbT+oX7S2BH653sPIqfI'})
     self.assertEqual('03272BF7C68484AFBDA508DAE3734D809E4A5BC4', desc.extra_info_digest)
     self.assertEqual('DWMz1AEdqPlcroubwx3lPEoGbT+oX7S2BH653sPIqfI', desc.extra_info_sha256_digest)
 
@@ -706,7 +690,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Checks a 'proto' line.
     """
 
-    desc = get_relay_server_descriptor({'proto': 'Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2'})
+    desc = RelayDescriptor.create({'proto': 'Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2'})
     self.assertEqual({'Cons': [1], 'Desc': [1], 'DirCache': [1], 'HSDir': [1], 'HSIntro': [3], 'HSRend': [1], 'Link': [1, 2, 3, 4], 'LinkAuth': [1], 'Microdesc': [1], 'Relay': [1, 2]}, desc.protocols)
 
   def test_protocols_with_no_mapping(self):
@@ -715,7 +699,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     """
 
     exc_msg = "Protocol entires are expected to be a series of 'key=value' pairs but was: proto Desc Link=1-4"
-    self.assertRaisesRegexp(ValueError, exc_msg, get_relay_server_descriptor, {'proto': 'Desc Link=1-4'})
+    self.assertRaisesRegexp(ValueError, exc_msg, RelayDescriptor.create, {'proto': 'Desc Link=1-4'})
 
   def test_parse_with_non_int_version(self):
     """
@@ -723,14 +707,14 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     """
 
     exc_msg = 'Protocol values should be a number or number range, but was: proto Desc=hi Link=1-4'
-    self.assertRaisesRegexp(ValueError, exc_msg, get_relay_server_descriptor, {'proto': 'Desc=hi Link=1-4'})
+    self.assertRaisesRegexp(ValueError, exc_msg, RelayDescriptor.create, {'proto': 'Desc=hi Link=1-4'})
 
   def test_ntor_onion_key(self):
     """
     Checks a 'ntor-onion-key' line.
     """
 
-    desc = get_relay_server_descriptor({'ntor-onion-key': 'Od2Sj3UXFyDjwESLXk6fhatqW9z/oBL/vAKJ+tbDqUU='})
+    desc = RelayDescriptor.create({'ntor-onion-key': 'Od2Sj3UXFyDjwESLXk6fhatqW9z/oBL/vAKJ+tbDqUU='})
     self.assertEqual('Od2Sj3UXFyDjwESLXk6fhatqW9z/oBL/vAKJ+tbDqUU=', desc.ntor_onion_key)
 
   def test_minimal_bridge_descriptor(self):
@@ -775,7 +759,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     its unsanatized content.
     """
 
-    desc_text = get_relay_server_descriptor({'router-digest': '006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4'}, content = True)
+    desc_text = RelayDescriptor.content({'router-digest': '006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4'})
     desc = BridgeDescriptor(desc_text)
     self.assertFalse(desc.is_scrubbed())
 
@@ -848,7 +832,10 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     desc = BridgeDescriptor(desc_text)
     self.assertEqual(expected_or_addresses, desc.or_addresses)
 
-  def _expect_invalid_attr(self, desc_text, attr = None, expected_value = None):
+  def _expect_invalid_attr(self, desc_attrs, attr = None, expected_value = None):
+    self._expect_invalid_attr_for_text(RelayDescriptor.content(desc_attrs), attr, expected_value)
+
+  def _expect_invalid_attr_for_text(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
diff --git a/test/unit/tutorial.py b/test/unit/tutorial.py
index ac0bdd4..26ea2b1 100644
--- a/test/unit/tutorial.py
+++ b/test/unit/tutorial.py
@@ -165,7 +165,7 @@ class TestTutorial(unittest.TestCase):
   @patch('stem.descriptor.reader.DescriptorReader', spec = DescriptorReader)
   def test_mirror_mirror_on_the_wall_4(self, reader_mock, stdout_mock):
     reader = reader_mock().__enter__()
-    reader.__iter__.return_value = iter([mocking.get_relay_server_descriptor()])
+    reader.__iter__.return_value = iter([RelayDescriptor.create()])
 
     exec_documentation_example('past_descriptors.py')
     self.assertEqual('found relay caerSidi (None)\n', stdout_mock.getvalue())
@@ -206,16 +206,14 @@ class TestTutorial(unittest.TestCase):
           if count > 15:
             return
 
-    exit_descriptor = mocking.get_relay_server_descriptor({
-      'router': 'speedyexit 149.255.97.109 9001 0 0'
-    }, content = True).replace(b'reject *:*', b'accept *:*')
+    exit_descriptor = RelayDescriptor.content({'router': 'speedyexit 149.255.97.109 9001 0 0'}).replace(b'reject *:*', b'accept *:*')
 
     exit_descriptor = mocking.sign_descriptor_content(exit_descriptor)
     exit_descriptor = RelayDescriptor(exit_descriptor)
 
     downloader_mock().get_server_descriptors().run.return_value = [
       exit_descriptor,
-      mocking.get_relay_server_descriptor(),  # non-exit
+      RelayDescriptor.create(),  # non-exit
       exit_descriptor,
       exit_descriptor,
     ]
diff --git a/test/unit/tutorial_examples.py b/test/unit/tutorial_examples.py
index de5079e..267fd3d 100644
--- a/test/unit/tutorial_examples.py
+++ b/test/unit/tutorial_examples.py
@@ -18,12 +18,12 @@ import stem.prereq
 from stem.control import Controller
 from stem.util import str_type
 from stem.descriptor.remote import DIRECTORY_AUTHORITIES
+from stem.descriptor.server_descriptor import RelayDescriptor
 
 from test import mocking
 from test.unit import exec_documentation_example
 
 from test.mocking import (
-  get_relay_server_descriptor,
   get_router_status_entry_v3,
   ROUTER_STATUS_ENTRY_V3_HEADER,
   get_network_status_document_v3,
@@ -228,10 +228,10 @@ class TestTutorialExamples(unittest.TestCase):
   @patch('stem.descriptor.remote.DescriptorDownloader')
   def test_outdated_relays(self, downloader_mock, stdout_mock):
     downloader_mock().get_server_descriptors.return_value = [
-      get_relay_server_descriptor({'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}),
-      get_relay_server_descriptor({'platform': 'node-Tor 0.1.0 on Linux x86_64'}),
-      get_relay_server_descriptor({'opt': 'contact Random Person admin@xxxxxxxxx', 'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}),
-      get_relay_server_descriptor({'opt': 'contact Sambuddha Basu', 'platform': 'node-Tor 0.1.0 on Linux x86_64'}),
+      RelayDescriptor.create({'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}),
+      RelayDescriptor.create({'platform': 'node-Tor 0.1.0 on Linux x86_64'}),
+      RelayDescriptor.create({'opt': 'contact Random Person admin@xxxxxxxxx', 'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}),
+      RelayDescriptor.create({'opt': 'contact Sambuddha Basu', 'platform': 'node-Tor 0.1.0 on Linux x86_64'}),
     ]
 
     exec_documentation_example('outdated_relays.py')



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits