[tor-commits] [stem/master] Storing raw descriptor content as bytes

atagar at torproject.org atagar at torproject.org
Sat Mar 23 03:36:13 UTC 2013


commit b12e9c01c318863f4e4cddacf341abfd197ff412
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Mar 20 18:28:55 2013 -0700

    Storing raw descriptor content as bytes
    
    Tor descriptors are byte strings. While most content is ASCII (and hence maps
    to UTF8) there are a couple exceptions: the server descriptor's contact and
    platform lines which are arbitrary byte content. Usually this is unicode but on
    occasion it isn't (14 of 3044 in my old consensus cache).
    
    In those cases we converted the content to unicode and replaced the
    un-decodeable characters. This in turn caused our _verify_digest() check to
    report the descriptor content as being malformed if the user had pycrypto
    installed.
    
    Changing the raw contents the Descriptor class tracks to be bytes instead of
    unicode. This actually leads to far more elegant code, letting us drop the
    UnicodeReader which was always a pretty gross hack.
    
    This breaks python 2.5 compatability by using the io.BytesIO class. Presently
    I'm checking with tor-dev@ to see if anyone objects with dropping python 2.5
    support entirely...
    
    https://lists.torproject.org/pipermail/tor-dev/2013-March/004551.html
    
    If the answer is...
    
    * yes, drop support: we'll replace test.mocking.BytesBuffer with io.BytesIO
      (they both do the exact same thing)
    
    * no, keep support: we'll need to move test.mocking.BytesBuffer to a util and
      use it instead of io.BytesIO
---
 stem/control.py                                    |    7 +-
 stem/descriptor/__init__.py                        |   84 +++++--------------
 stem/descriptor/extrainfo_descriptor.py            |    5 +-
 stem/descriptor/microdescriptor.py                 |   11 ++-
 stem/descriptor/networkstatus.py                   |   29 ++++---
 stem/descriptor/router_status_entry.py             |    5 +-
 stem/descriptor/server_descriptor.py               |   15 ++--
 stem/response/events.py                            |    6 +-
 test/integ/descriptor/microdescriptor.py           |   12 ++--
 test/integ/descriptor/server_descriptor.py         |    2 +-
 test/mocking.py                                    |   87 ++++++++++++++++---
 test/unit/descriptor/extrainfo_descriptor.py       |    4 +-
 test/unit/descriptor/microdescriptor.py            |   10 +-
 .../descriptor/networkstatus/bridge_document.py    |    4 +-
 .../networkstatus/directory_authority.py           |   12 ++--
 test/unit/descriptor/networkstatus/document_v3.py  |   34 ++++----
 .../descriptor/networkstatus/key_certificate.py    |    4 +-
 test/unit/descriptor/router_status_entry.py        |   16 ++--
 test/unit/descriptor/server_descriptor.py          |   28 +++---
 test/unit/tutorial.py                              |    4 +-
 20 files changed, 200 insertions(+), 179 deletions(-)

diff --git a/stem/control.py b/stem/control.py
index d64373b..4d91ecd 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -136,6 +136,7 @@ providing its own for interacting at a higher level.
 
 from __future__ import with_statement
 
+import io
 import os
 import Queue
 import StringIO
@@ -156,7 +157,7 @@ import stem.util.tor_tools
 import stem.version
 
 from stem import UNDEFINED, CircStatus, Signal
-from stem.util import log
+from stem.util import log, str_tools
 
 # state changes a control socket can have
 
@@ -1120,7 +1121,7 @@ class Controller(BaseController):
 
       desc_content = self.get_info("desc/all-recent")
 
-      for desc in stem.descriptor.server_descriptor._parse_file(StringIO.StringIO(desc_content)):
+      for desc in stem.descriptor.server_descriptor._parse_file(io.BytesIO(str_tools._to_bytes(desc_content))):
         yield desc
     except Exception, exc:
       if default == UNDEFINED:
@@ -1190,7 +1191,7 @@ class Controller(BaseController):
       desc_content = self.get_info("ns/all")
 
       desc_iterator = stem.descriptor.router_status_entry._parse_file(
-        StringIO.StringIO(desc_content),
+        io.BytesIO(str_tools._to_bytes(desc_content)),
         True,
         entry_class = stem.descriptor.router_status_entry.RouterStatusEntryV2,
       )
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 4f0a596..40c1049 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -143,10 +143,6 @@ def parse_file(descriptor_file, descriptor_type = None, validate = True, documen
   import stem.descriptor.extrainfo_descriptor
   import stem.descriptor.networkstatus
 
-  # attempt to read content as unicode
-
-  descriptor_file = _UnicodeReader(descriptor_file)
-
   # The tor descriptor specifications do not provide a reliable method for
   # identifying a descriptor file's type and version so we need to guess
   # based on its filename. Metrics descriptors, however, can be identified
@@ -154,13 +150,14 @@ def parse_file(descriptor_file, descriptor_type = None, validate = True, documen
   # https://trac.torproject.org/5651
 
   initial_position = descriptor_file.tell()
-  first_line = descriptor_file.readline().strip()
+  first_line = stem.util.str_tools._to_unicode(descriptor_file.readline().strip())
   metrics_header_match = re.match("^@type (\S+) (\d+).(\d+)$", first_line)
 
   if not metrics_header_match:
     descriptor_file.seek(initial_position)
 
-  filename = '<undefined>' if descriptor_file.name is None else os.path.basename(descriptor_file.name)
+  descriptor_path = getattr(descriptor_file, 'name', None)
+  filename = '<undefined>' if descriptor_path is None else os.path.basename(descriptor_file.name)
   file_parser = None
 
   if descriptor_type is not None:
@@ -192,8 +189,8 @@ def parse_file(descriptor_file, descriptor_type = None, validate = True, documen
 
   if file_parser:
     for desc in file_parser(descriptor_file):
-      if descriptor_file.name is not None:
-        desc._set_path(os.path.abspath(descriptor_file.name))
+      if descriptor_path is not None:
+        desc._set_path(os.path.abspath(descriptor_path))
 
       yield desc
 
@@ -286,6 +283,17 @@ class Descriptor(object):
 
     return self._archive_path
 
+  def get_bytes(self):
+    """
+    Provides the ASCII **bytes** of the descriptor. This only differs from
+    **str()** if you're running python 3.x, in which case **str()** provides a
+    **unicode** string.
+
+    :returns: **bytes** for the descriptor's contents
+    """
+
+    return self._raw_contents
+
   def get_unrecognized_lines(self):
     """
     Provides a list of lines that were either ignored or had data that we did
@@ -305,61 +313,9 @@ class Descriptor(object):
 
   def __str__(self):
     if stem.prereq.is_python_3():
-      return self._raw_contents
+      return stem.util.str_tools._to_unicode(self._raw_contents)
     else:
-      return str(stem.util.str_tools._to_bytes(self._raw_contents))
-
-
-class _UnicodeReader(object):
-  """
-  File-like object that wraps another file. This replaces read ASCII bytes with
-  unicode content. This only supports read operations.
-  """
-
-  def __init__(self, wrapped_file):
-    self.wrapped_file = wrapped_file
-    self.name = getattr(wrapped_file, 'name', None)
-
-  def close(self):
-    return self.wrapped_file.close()
-
-  def getvalue(self):
-    return self.wrapped_file.getvalue()
-
-  def isatty(self):
-    return self.wrapped_file.isatty()
-
-  def next(self):
-    return self.wrapped_file.next()
-
-  def read(self, n = -1):
-    return stem.util.str_tools._to_unicode(self.wrapped_file.read(n))
-
-  def readline(self):
-    return stem.util.str_tools._to_unicode(self.wrapped_file.readline())
-
-  def readlines(self, sizehint = None):
-    # being careful to do in-place conversion so we don't accidently double our
-    # memory usage
-
-    if sizehint is not None:
-      results = self.wrapped_file.readlines(sizehint)
-    else:
-      results = self.wrapped_file.readlines()
-
-    for i in xrange(len(results)):
-      results[i] = stem.util.str_tools._to_unicode(results[i])
-
-    return results
-
-  def seek(self, pos, mode = None):
-    if mode is not None:
-      return self.wrapped_file.seek(pos, mode)
-    else:
-      return self.wrapped_file.seek(pos)
-
-  def tell(self):
-    return self.wrapped_file.tell()
+      return self._raw_contents
 
 
 def _read_until_keywords(keywords, descriptor_file, inclusive = False, ignore_first = False, skip = False, end_position = None, include_ending_keyword = False):
@@ -402,11 +358,11 @@ def _read_until_keywords(keywords, descriptor_file, inclusive = False, ignore_fi
     if not line:
       break  # EOF
 
-    line_match = KEYWORD_LINE.match(line)
+    line_match = KEYWORD_LINE.match(stem.util.str_tools._to_unicode(line))
 
     if not line_match:
       # no spaces or tabs in the line
-      line_keyword = line.strip()
+      line_keyword = stem.util.str_tools._to_unicode(line.strip())
     else:
       line_keyword = line_match.groups()[0]
 
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index 5f1c423..5e6ddd0 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -164,9 +164,9 @@ def _parse_file(descriptor_file, is_bridge = False, validate = True):
 
     if extrainfo_content:
       if is_bridge:
-        yield BridgeExtraInfoDescriptor("".join(extrainfo_content), validate)
+        yield BridgeExtraInfoDescriptor(bytes.join(b"", extrainfo_content), validate)
       else:
-        yield RelayExtraInfoDescriptor("".join(extrainfo_content), validate)
+        yield RelayExtraInfoDescriptor(bytes.join(b"", extrainfo_content), validate)
     else:
       break  # done parsing file
 
@@ -322,6 +322,7 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
     """
 
     super(ExtraInfoDescriptor, self).__init__(raw_contents)
+    raw_contents = stem.util.str_tools._to_unicode(raw_contents)
 
     self.nickname = None
     self.fingerprint = None
diff --git a/stem/descriptor/microdescriptor.py b/stem/descriptor/microdescriptor.py
index dc818aa..324ee32 100644
--- a/stem/descriptor/microdescriptor.py
+++ b/stem/descriptor/microdescriptor.py
@@ -69,7 +69,7 @@ def _parse_file(descriptor_file, validate = True):
 
       if not line:
         break  # EOF
-      elif line.startswith("@") or line.startswith("onion-key"):
+      elif line.startswith(b"@") or line.startswith(b"onion-key"):
         descriptor_file.seek(last_position)
         break
       else:
@@ -77,9 +77,9 @@ def _parse_file(descriptor_file, validate = True):
 
     if descriptor_lines:
       # strip newlines from annotations
-      annotations = map(unicode.strip, annotations)
+      annotations = map(bytes.strip, annotations)
 
-      descriptor_text = "".join(descriptor_lines)
+      descriptor_text = bytes.join(b"", descriptor_lines)
 
       yield Microdescriptor(descriptor_text, validate, annotations)
     else:
@@ -105,6 +105,7 @@ class Microdescriptor(stem.descriptor.Descriptor):
 
   def __init__(self, raw_contents, validate = True, annotations = None):
     super(Microdescriptor, self).__init__(raw_contents)
+    raw_contents = stem.util.str_tools._to_unicode(raw_contents)
 
     self.onion_key = None
     self.ntor_onion_key = None
@@ -143,8 +144,8 @@ class Microdescriptor(stem.descriptor.Descriptor):
       annotation_dict = {}
 
       for line in self._annotation_lines:
-        if " " in line:
-          key, value = line.split(" ", 1)
+        if b" " in line:
+          key, value = line.split(b" ", 1)
           annotation_dict[key] = value
         else:
           annotation_dict[line] = None
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index 74431d1..c32a7ce 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -47,7 +47,7 @@ For more information see :func:`~stem.descriptor.__init__.DocumentHandler`...
 """
 
 import datetime
-import StringIO
+import io
 
 import stem.descriptor
 import stem.descriptor.router_status_entry
@@ -201,7 +201,7 @@ def _parse_file(document_file, document_type = None, validate = True, is_microde
   routers_end = document_file.tell()
 
   footer = document_file.readlines()
-  document_content = "".join(header + footer)
+  document_content = bytes.join(b"", header + footer)
 
   if document_handler == stem.descriptor.DocumentHandler.BARE_DOCUMENT:
     yield document_type(document_content, validate)
@@ -265,7 +265,6 @@ class NetworkStatusDocumentV2(NetworkStatusDocument):
   """
 
   def __init__(self, raw_content, validate = True):
-    raw_content = stem.util.str_tools._to_unicode(raw_content)
     super(NetworkStatusDocumentV2, self).__init__(raw_content)
 
     self.version = None
@@ -289,8 +288,8 @@ class NetworkStatusDocumentV2(NetworkStatusDocument):
     # that header/footer attributes aren't in the wrong section. This is a
     # deprecated descriptor type - patches welcome if you want those checks.
 
-    document_file = StringIO.StringIO(raw_content)
-    document_content = "".join(stem.descriptor._read_until_keywords((ROUTERS_START, V2_FOOTER_START), document_file))
+    document_file = io.BytesIO(raw_content)
+    document_content = bytes.join(b"", stem.descriptor._read_until_keywords((ROUTERS_START, V2_FOOTER_START), document_file))
 
     self.routers = tuple(stem.descriptor.router_status_entry._parse_file(
       document_file,
@@ -301,7 +300,8 @@ class NetworkStatusDocumentV2(NetworkStatusDocument):
       extra_args = (self,),
     ))
 
-    document_content += "\n" + document_file.read()
+    document_content += b"\n" + document_file.read()
+    document_content = stem.util.str_tools._to_unicode(document_content)
 
     entries = stem.descriptor._get_descriptor_components(document_content, validate)
 
@@ -464,9 +464,8 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
     :raises: **ValueError** if the document is invalid
     """
 
-    raw_content = stem.util.str_tools._to_unicode(raw_content)
     super(NetworkStatusDocumentV3, self).__init__(raw_content)
-    document_file = StringIO.StringIO(raw_content)
+    document_file = io.BytesIO(raw_content)
 
     self._header = _DocumentHeader(document_file, validate, default_params)
 
@@ -561,7 +560,8 @@ class _DocumentHeader(object):
 
     self._unrecognized_lines = []
 
-    content = "".join(stem.descriptor._read_until_keywords((AUTH_START, ROUTERS_START, FOOTER_START), document_file))
+    content = bytes.join(b"", stem.descriptor._read_until_keywords((AUTH_START, ROUTERS_START, FOOTER_START), document_file))
+    content = stem.util.str_tools._to_unicode(content)
     entries = stem.descriptor._get_descriptor_components(content, validate)
     self._parse(entries, validate)
 
@@ -791,7 +791,7 @@ class _DocumentFooter(object):
     self.bandwidth_weights = {}
     self._unrecognized_lines = []
 
-    content = document_file.read()
+    content = stem.util.str_tools._to_unicode(document_file.read())
 
     if not content:
       return  # footer is optional and there's nothing to parse
@@ -1006,6 +1006,7 @@ class DirectoryAuthority(stem.descriptor.Descriptor):
     """
 
     super(DirectoryAuthority, self).__init__(raw_content)
+    raw_content = stem.util.str_tools._to_unicode(raw_content)
 
     self.nickname = None
     self.fingerprint = None
@@ -1201,6 +1202,7 @@ class KeyCertificate(stem.descriptor.Descriptor):
 
   def __init__(self, raw_content, validate = True):
     super(KeyCertificate, self).__init__(raw_content)
+    raw_content = stem.util.str_tools._to_unicode(raw_content)
 
     self.version = None
     self.address = None
@@ -1415,9 +1417,8 @@ class BridgeNetworkStatusDocument(NetworkStatusDocument):
     self.routers = None
     self.published = None
 
-    document_file = StringIO.StringIO(raw_content)
-
-    published_line = document_file.readline()
+    document_file = io.BytesIO(raw_content)
+    published_line = stem.util.str_tools._to_unicode(document_file.readline())
 
     if published_line.startswith("published "):
       published_line = published_line.split(" ", 1)[1].strip()
@@ -1428,7 +1429,7 @@ class BridgeNetworkStatusDocument(NetworkStatusDocument):
         if validate:
           raise ValueError("Bridge network status document's 'published' time wasn't parsable: %s" % published_line)
     elif validate:
-      raise ValueError("Bridge network status documents must start with a 'published' line:\n%s" % raw_content)
+      raise ValueError("Bridge network status documents must start with a 'published' line:\n%s" % stem.util.str_tools._to_unicode(raw_content))
 
     self.routers = tuple(stem.descriptor.router_status_entry._parse_file(
       document_file,
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index e2579eb..63cf24b 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -64,7 +64,7 @@ def _parse_file(document_file, validate, entry_class, entry_keyword = "r", start
   # check if we're starting at the end of the section (ie, there's no entries to read)
   if section_end_keywords:
     first_keyword = None
-    line_match = stem.descriptor.KEYWORD_LINE.match(document_file.readline())
+    line_match = stem.descriptor.KEYWORD_LINE.match(stem.util.str_tools._to_unicode(document_file.readline()))
 
     if line_match:
       first_keyword = line_match.groups()[0]
@@ -83,7 +83,7 @@ def _parse_file(document_file, validate, entry_class, entry_keyword = "r", start
       include_ending_keyword = True
     )
 
-    desc_content = "".join(desc_lines)
+    desc_content = bytes.join(b"", desc_lines)
 
     if desc_content:
       yield entry_class(desc_content, validate, *extra_args)
@@ -129,6 +129,7 @@ class RouterStatusEntry(stem.descriptor.Descriptor):
     """
 
     super(RouterStatusEntry, self).__init__(content)
+    content = stem.util.str_tools._to_unicode(content)
 
     self.document = document
 
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 103ba8b..a02eb28 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -127,9 +127,9 @@ def _parse_file(descriptor_file, is_bridge = False, validate = True):
 
     if descriptor_content:
       # strip newlines from annotations
-      annotations = map(unicode.strip, annotations)
+      annotations = map(bytes.strip, annotations)
 
-      descriptor_text = "".join(descriptor_content)
+      descriptor_text = bytes.join(b"", descriptor_content)
 
       if is_bridge:
         yield BridgeDescriptor(descriptor_text, validate, annotations)
@@ -209,6 +209,7 @@ class ServerDescriptor(stem.descriptor.Descriptor):
     """
 
     super(ServerDescriptor, self).__init__(raw_contents)
+    raw_contents = stem.util.str_tools._to_unicode(raw_contents)
 
     self.nickname = None
     self.fingerprint = None
@@ -302,8 +303,8 @@ class ServerDescriptor(stem.descriptor.Descriptor):
       annotation_dict = {}
 
       for line in self._annotation_lines:
-        if " " in line:
-          key, value = line.split(" ", 1)
+        if b" " in line:
+          key, value = line.split(b" ", 1)
           annotation_dict[key] = value
         else:
           annotation_dict[line] = None
@@ -652,9 +653,9 @@ class RelayDescriptor(ServerDescriptor):
       # Digest is calculated from everything in the
       # descriptor except the router-signature.
 
-      raw_descriptor = str(self)
-      start_token = "router "
-      sig_token = "\nrouter-signature\n"
+      raw_descriptor = self.get_bytes()
+      start_token = b"router "
+      sig_token = b"\nrouter-signature\n"
       start = raw_descriptor.find(start_token)
       sig_start = raw_descriptor.find(sig_token)
       end = sig_start + len(sig_token)
diff --git a/stem/response/events.py b/stem/response/events.py
index d99b291..4c44e48 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -3,8 +3,8 @@
 # See LICENSE for licensing information
 
 import datetime
+import io
 import re
-import StringIO
 import time
 
 import stem
@@ -597,7 +597,7 @@ class NetworkStatusEvent(Event):
     content = str(self).lstrip("NS\n").rstrip("\nOK")
 
     self.desc = list(stem.descriptor.router_status_entry._parse_file(
-      StringIO.StringIO(content),
+      io.BytesIO(str_tools._to_bytes(content)),
       True,
       entry_class = stem.descriptor.router_status_entry.RouterStatusEntryV3,
     ))
@@ -622,7 +622,7 @@ class NewConsensusEvent(Event):
     content = str(self).lstrip("NEWCONSENSUS\n").rstrip("\nOK")
 
     self.desc = list(stem.descriptor.router_status_entry._parse_file(
-      StringIO.StringIO(content),
+      io.BytesIO(str_tools._to_bytes(content)),
       True,
       entry_class = stem.descriptor.router_status_entry.RouterStatusEntryV3,
     ))
diff --git a/test/integ/descriptor/microdescriptor.py b/test/integ/descriptor/microdescriptor.py
index 329b5eb..da9cedd 100644
--- a/test/integ/descriptor/microdescriptor.py
+++ b/test/integ/descriptor/microdescriptor.py
@@ -78,8 +78,8 @@ class TestMicrodescriptor(unittest.TestCase):
       self.assertEquals([], router.or_addresses)
       self.assertEquals([], router.family)
       self.assertEquals(stem.exit_policy.MicroExitPolicy("reject 1-65535"), router.exit_policy)
-      self.assertEquals({"@last-listed": "2013-02-24 00:18:36"}, router.get_annotations())
-      self.assertEquals(["@last-listed 2013-02-24 00:18:36"], router.get_annotation_lines())
+      self.assertEquals({b"@last-listed": b"2013-02-24 00:18:36"}, router.get_annotations())
+      self.assertEquals([b"@last-listed 2013-02-24 00:18:36"], router.get_annotation_lines())
 
       router = next(descriptors)
       self.assertEquals(SECOND_ONION_KEY, router.onion_key)
@@ -87,8 +87,8 @@ class TestMicrodescriptor(unittest.TestCase):
       self.assertEquals([], router.or_addresses)
       self.assertEquals(["$6141629FA0D15A6AEAEF3A1BEB76E64C767B3174"], router.family)
       self.assertEquals(stem.exit_policy.MicroExitPolicy("reject 1-65535"), router.exit_policy)
-      self.assertEquals({"@last-listed": "2013-02-24 00:18:37"}, router.get_annotations())
-      self.assertEquals(["@last-listed 2013-02-24 00:18:37"], router.get_annotation_lines())
+      self.assertEquals({b"@last-listed": b"2013-02-24 00:18:37"}, router.get_annotations())
+      self.assertEquals([b"@last-listed 2013-02-24 00:18:37"], router.get_annotation_lines())
 
       router = next(descriptors)
       self.assertEquals(THIRD_ONION_KEY, router.onion_key)
@@ -96,5 +96,5 @@ class TestMicrodescriptor(unittest.TestCase):
       self.assertEquals([(u"2001:6b0:7:125::242", 9001, True)], router.or_addresses)
       self.assertEquals([], router.family)
       self.assertEquals(stem.exit_policy.MicroExitPolicy("accept 80,443"), router.exit_policy)
-      self.assertEquals({"@last-listed": "2013-02-24 00:18:36"}, router.get_annotations())
-      self.assertEquals(["@last-listed 2013-02-24 00:18:36"], router.get_annotation_lines())
+      self.assertEquals({b"@last-listed": b"2013-02-24 00:18:36"}, router.get_annotations())
+      self.assertEquals([b"@last-listed 2013-02-24 00:18:36"], router.get_annotation_lines())
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index e3745c0..9217a57 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -228,7 +228,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     self.assertEquals([], desc.get_unrecognized_lines())
 
     # Make sure that we can get a string representation for this descriptor
-    # (having unicode content risks a UnicodeEncodeError)...
+    # (having non-unicode content risks a UnicodeEncodeError)...
     #
     # https://trac.torproject.org/8265
 
diff --git a/test/mocking.py b/test/mocking.py
index beaa354..b491010 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -58,6 +58,7 @@ import base64
 import hashlib
 import inspect
 import itertools
+import StringIO
 
 import stem.descriptor.extrainfo_descriptor
 import stem.descriptor.microdescriptor
@@ -66,6 +67,7 @@ import stem.descriptor.router_status_entry
 import stem.descriptor.server_descriptor
 import stem.prereq
 import stem.response
+import stem.util.str_tools
 
 # Once we've mocked a function we can't rely on its __module__ or __name__
 # attributes, so instead we associate a unique 'mock_id' attribute that maps
@@ -640,7 +642,7 @@ def _get_descriptor_content(attr = None, exclude = (), header_template = (), foo
     else:
       remainder.append(k)
 
-  return "\n".join(header_content + remainder + footer_content)
+  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):
@@ -831,7 +833,7 @@ def get_directory_authority(attr = None, exclude = (), is_vote = False, content
   desc_content = _get_descriptor_content(attr, exclude, AUTHORITY_HEADER)
 
   if is_vote:
-    desc_content += "\n" + str(get_key_certificate())
+    desc_content += b"\n" + get_key_certificate(content = True)
 
   if content:
     return desc_content
@@ -917,31 +919,31 @@ def get_network_status_document_v3(attr = None, exclude = (), authorities = None
 
   # inject the authorities and/or routers between the header and footer
   if authorities:
-    if "directory-footer" in desc_content:
-      footer_div = desc_content.find("\ndirectory-footer") + 1
-    elif "directory-signature" in desc_content:
-      footer_div = desc_content.find("\ndirectory-signature") + 1
+    if b"directory-footer" in desc_content:
+      footer_div = desc_content.find(b"\ndirectory-footer") + 1
+    elif b"directory-signature" in desc_content:
+      footer_div = desc_content.find(b"\ndirectory-signature") + 1
     else:
       if routers:
-        desc_content += "\n"
+        desc_content += b"\n"
 
       footer_div = len(desc_content) + 1
 
-    authority_content = "\n".join([str(a) for a in authorities]) + "\n"
+    authority_content = stem.util.str_tools._to_bytes("\n".join([str(a) for a in authorities]) + "\n")
     desc_content = desc_content[:footer_div] + authority_content + desc_content[footer_div:]
 
   if routers:
-    if "directory-footer" in desc_content:
-      footer_div = desc_content.find("\ndirectory-footer") + 1
-    elif "directory-signature" in desc_content:
-      footer_div = desc_content.find("\ndirectory-signature") + 1
+    if b"directory-footer" in desc_content:
+      footer_div = desc_content.find(b"\ndirectory-footer") + 1
+    elif b"directory-signature" in desc_content:
+      footer_div = desc_content.find(b"\ndirectory-signature") + 1
     else:
       if routers:
-        desc_content += "\n"
+        desc_content += b"\n"
 
       footer_div = len(desc_content) + 1
 
-    router_content = "\n".join([str(r) for r in routers]) + "\n"
+    router_content = stem.util.str_tools._to_bytes("\n".join([str(r) for r in routers]) + "\n")
     desc_content = desc_content[:footer_div] + router_content + desc_content[footer_div:]
 
   if content:
@@ -1057,3 +1059,60 @@ def sign_descriptor_content(desc_content):
     desc_content = desc_content[:rst_start] + router_signature_token + router_signature_start + signature_base64 + router_signature_end
 
     return desc_content
+
+
+class BytesBuffer(object):
+  """
+  Similiar to a StringIO but provides bytes content (in python 3.x StringIO can
+  only be used for unicode).
+  """
+
+  def __init__(self, content):
+    self.wrapped_file = StringIO.StringIO(stem.util.str_tools._to_unicode(content))
+
+  def close(self):
+    return self.wrapped_file.close()
+
+  def getvalue(self):
+    return self.wrapped_file.getvalue()
+
+  def isatty(self):
+    return self.wrapped_file.isatty()
+
+  def next(self):
+    return self.wrapped_file.next()
+
+  def read(self, n = -1):
+    return stem.util.str_tools._to_bytes(self.wrapped_file.read(n))
+
+  def readline(self):
+    return stem.util.str_tools._to_bytes(self.wrapped_file.readline())
+
+  def readlines(self, sizehint = None):
+    # being careful to do in-place conversion so we don't accidently double our
+    # memory usage
+
+    if sizehint is not None:
+      results = self.wrapped_file.readlines(sizehint)
+    else:
+      results = self.wrapped_file.readlines()
+
+    for i in xrange(len(results)):
+      results[i] = stem.util.str_tools._to_bytes(results[i])
+
+    return results
+
+  def seek(self, pos, mode = None):
+    if mode is not None:
+      return self.wrapped_file.seek(pos, mode)
+    else:
+      return self.wrapped_file.seek(pos)
+
+  def tell(self):
+    return self.wrapped_file.tell()
+
+  def __enter__(self):
+    return self
+
+  def __exit__(self, exit_type, value, traceback):
+    pass
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py
index 6a2dd34..957c861 100644
--- a/test/unit/descriptor/extrainfo_descriptor.py
+++ b/test/unit/descriptor/extrainfo_descriptor.py
@@ -35,7 +35,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
     Includes a line prior to the 'extra-info' entry.
     """
 
-    desc_text = "exit-streams-opened port=80\n" + get_relay_extrainfo_descriptor(content = True)
+    desc_text = b"exit-streams-opened port=80\n" + get_relay_extrainfo_descriptor(content = True)
     self._expect_invalid_attr(desc_text)
 
   def test_trailing_line(self):
@@ -43,7 +43,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
     Includes a line after the 'router-signature' entry.
     """
 
-    desc_text = get_relay_extrainfo_descriptor(content = True) + "\nexit-streams-opened port=80"
+    desc_text = get_relay_extrainfo_descriptor(content = True) + b"\nexit-streams-opened port=80"
     self._expect_invalid_attr(desc_text)
 
   def test_extrainfo_line_missing_fields(self):
diff --git a/test/unit/descriptor/microdescriptor.py b/test/unit/descriptor/microdescriptor.py
index 2a2dd35..2644ffe 100644
--- a/test/unit/descriptor/microdescriptor.py
+++ b/test/unit/descriptor/microdescriptor.py
@@ -41,7 +41,7 @@ class TestMicrodescriptor(unittest.TestCase):
     Includes a line prior to the 'onion-key' entry.
     """
 
-    desc_text = "family Amunet1\n" + get_microdescriptor(content = True)
+    desc_text = b"family Amunet1\n" + get_microdescriptor(content = True)
     self.assertRaises(ValueError, Microdescriptor, desc_text)
 
     desc = Microdescriptor(desc_text, validate = False)
@@ -53,8 +53,8 @@ class TestMicrodescriptor(unittest.TestCase):
     """
 
     desc_text = get_microdescriptor(content = True)
-    desc_text += "\na 10.45.227.253:9001"
-    desc_text += "\na [fd9f:2e19:3bcf::02:9970]:9001"
+    desc_text += b"\na 10.45.227.253:9001"
+    desc_text += b"\na [fd9f:2e19:3bcf::02:9970]:9001"
 
     expected = [
       ("10.45.227.253", 9001, False),
@@ -75,8 +75,8 @@ class TestMicrodescriptor(unittest.TestCase):
     # try multiple family lines
 
     desc_text = get_microdescriptor(content = True)
-    desc_text += "\nfamily Amunet1"
-    desc_text += "\nfamily Amunet2"
+    desc_text += b"\nfamily Amunet1"
+    desc_text += b"\nfamily Amunet2"
 
     self.assertRaises(ValueError, Microdescriptor, desc_text)
 
diff --git a/test/unit/descriptor/networkstatus/bridge_document.py b/test/unit/descriptor/networkstatus/bridge_document.py
index 322b5a9..97aeb0c 100644
--- a/test/unit/descriptor/networkstatus/bridge_document.py
+++ b/test/unit/descriptor/networkstatus/bridge_document.py
@@ -7,7 +7,7 @@ import unittest
 
 from stem.descriptor.networkstatus import BridgeNetworkStatusDocument
 
-DOCUMENT = """\
+DOCUMENT = b"""\
 published 2012-06-01 04:07:04
 r Unnamed ABSiBVJ42z6w5Z6nAXQUFq8YVVg FI74aFuNJZZQrgln0f+OaocMd0M 2012-05-31 15:57:00 10.97.236.247 443 0
 s Valid
@@ -26,7 +26,7 @@ class TestBridgeNetworkStatusDocument(unittest.TestCase):
     Parse a document without any router status entries.
     """
 
-    document = BridgeNetworkStatusDocument("published 2012-06-01 04:07:04")
+    document = BridgeNetworkStatusDocument(b"published 2012-06-01 04:07:04")
     self.assertEqual(datetime.datetime(2012, 6, 1, 4, 7, 4), document.published)
     self.assertEqual((), document.routers)
     self.assertEqual([], document.get_unrecognized_lines())
diff --git a/test/unit/descriptor/networkstatus/directory_authority.py b/test/unit/descriptor/networkstatus/directory_authority.py
index 539b6b1..d8a818a 100644
--- a/test/unit/descriptor/networkstatus/directory_authority.py
+++ b/test/unit/descriptor/networkstatus/directory_authority.py
@@ -83,7 +83,7 @@ class TestDirectoryAuthority(unittest.TestCase):
     Includes a non-mandatory field before the 'dir-source' line.
     """
 
-    content = "ho-hum 567\n" + get_directory_authority(content = True)
+    content = b"ho-hum 567\n" + get_directory_authority(content = True)
     self.assertRaises(ValueError, DirectoryAuthority, content)
 
     authority = DirectoryAuthority(content, False)
@@ -119,10 +119,10 @@ class TestDirectoryAuthority(unittest.TestCase):
     Duplicates linesin the entry.
     """
 
-    lines = get_directory_authority(content = True).split("\n")
+    lines = get_directory_authority(content = True).split(b"\n")
 
     for index, duplicate_line in enumerate(lines):
-      content = "\n".join(lines[:index] + [duplicate_line] + lines[index:])
+      content = b"\n".join(lines[:index] + [duplicate_line] + lines[index:])
       self.assertRaises(ValueError, DirectoryAuthority, content)
 
       authority = DirectoryAuthority(content, False)
@@ -254,17 +254,17 @@ class TestDirectoryAuthority(unittest.TestCase):
     Includes or exclude a key certificate from the directory entry.
     """
 
-    key_cert = get_key_certificate()
+    key_cert = get_key_certificate(content = True)
 
     # include a key cert with a consensus
-    content = get_directory_authority(content = True) + "\n" + str(key_cert)
+    content = get_directory_authority(content = True) + b"\n" + key_cert
     self.assertRaises(ValueError, DirectoryAuthority, content)
 
     authority = DirectoryAuthority(content, False)
     self.assertEqual('turtles', authority.nickname)
 
     # exclude  key cert from a vote
-    content = get_directory_authority(content = True, is_vote = True).replace("\n" + str(key_cert), '')
+    content = get_directory_authority(content = True, is_vote = True).replace(b"\n" + key_cert, b'')
     self.assertRaises(ValueError, DirectoryAuthority, content, True, True)
 
     authority = DirectoryAuthority(content, False, True)
diff --git a/test/unit/descriptor/networkstatus/document_v3.py b/test/unit/descriptor/networkstatus/document_v3.py
index 1a478cf..c140d23 100644
--- a/test/unit/descriptor/networkstatus/document_v3.py
+++ b/test/unit/descriptor/networkstatus/document_v3.py
@@ -5,7 +5,6 @@ Unit tests for the NetworkStatusDocumentV3 of stem.descriptor.networkstatus.
 from __future__ import with_statement
 
 import datetime
-import StringIO
 import unittest
 
 import stem.descriptor
@@ -25,11 +24,11 @@ from stem.descriptor.router_status_entry import \
                                           RouterStatusEntryV3, \
                                           RouterStatusEntryMicroV3
 
-from test.mocking import support_with, \
-                         get_router_status_entry_v3, \
+from test.mocking import get_router_status_entry_v3, \
                          get_router_status_entry_micro_v3, \
                          get_directory_authority, \
                          get_network_status_document_v3, \
+                         BytesBuffer, \
                          CRYPTO_BLOB, \
                          DOC_SIG, \
                          NETWORK_STATUS_DOCUMENT_FOOTER
@@ -119,7 +118,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
 
     # first example: parsing via the NetworkStatusDocumentV3 constructor
 
-    consensus_file = StringIO.StringIO(content)
+    consensus_file = BytesBuffer(content)
     consensus = NetworkStatusDocumentV3(consensus_file.read())
     consensus_file.close()
 
@@ -128,7 +127,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
 
     # second example: using stem.descriptor.parse_file
 
-    with support_with(StringIO.StringIO(content)) as consensus_file:
+    with BytesBuffer(content) as consensus_file:
       for router in stem.descriptor.parse_file(consensus_file, 'network-status-consensus-3 1.0'):
         self.assertEqual('caerSidi', router.nickname)
 
@@ -145,12 +144,12 @@ class TestNetworkStatusDocument(unittest.TestCase):
     entry2 = get_router_status_entry_v3({'s': "Valid"})
     content = get_network_status_document_v3(routers = (entry1, entry2), content = True)
 
-    descriptors = list(stem.descriptor.parse_file(StringIO.StringIO(content), 'network-status-consensus-3 1.0', document_handler = stem.descriptor.DocumentHandler.DOCUMENT))
+    descriptors = list(stem.descriptor.parse_file(BytesBuffer(content), 'network-status-consensus-3 1.0', document_handler = stem.descriptor.DocumentHandler.DOCUMENT))
     self.assertEqual(1, len(descriptors))
     self.assertTrue(isinstance(descriptors[0], NetworkStatusDocumentV3))
     self.assertEqual(2, len(descriptors[0].routers))
 
-    descriptors = list(stem.descriptor.parse_file(StringIO.StringIO(content), 'network-status-consensus-3 1.0', document_handler = stem.descriptor.DocumentHandler.BARE_DOCUMENT))
+    descriptors = list(stem.descriptor.parse_file(BytesBuffer(content), 'network-status-consensus-3 1.0', document_handler = stem.descriptor.DocumentHandler.BARE_DOCUMENT))
     self.assertEqual(1, len(descriptors))
     self.assertTrue(isinstance(descriptors[0], NetworkStatusDocumentV3))
     self.assertEqual(0, len(descriptors[0].routers))
@@ -169,7 +168,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
 
     expected_document = get_network_status_document_v3()
 
-    descriptor_file = StringIO.StringIO(content)
+    descriptor_file = BytesBuffer(content)
     entries = list(_parse_file(descriptor_file))
 
     self.assertEquals(entry1, entries[0])
@@ -207,18 +206,18 @@ class TestNetworkStatusDocument(unittest.TestCase):
 
     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")
+      lines = get_network_status_document_v3(attr, content = True).split(b"\n")
 
       for index in xrange(len(lines) - 1):
         # once we reach the crypto blob we're done since swapping those won't
         # be detected
-        if lines[index].startswith(CRYPTO_BLOB[1:10]): break
+        if lines[index].startswith(stem.util.str_tools._to_bytes(CRYPTO_BLOB[1:10])): break
 
         # swaps this line with the one after it
         test_lines = list(lines)
         test_lines[index], test_lines[index + 1] = test_lines[index + 1], test_lines[index]
 
-        content = "\n".join(test_lines)
+        content = b"\n".join(test_lines)
         self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
         NetworkStatusDocumentV3(content, False)  # constructs without validation
 
@@ -230,7 +229,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
 
     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")
+      lines = get_network_status_document_v3(attr, content = True).split(b"\n")
 
       for index, line in enumerate(lines):
         # Stop when we hit the 'directory-signature' for a couple reasons...
@@ -239,13 +238,13 @@ class TestNetworkStatusDocument(unittest.TestCase):
         #   validation failure
 
         test_lines = list(lines)
-        if line.startswith("directory-signature "):
+        if line.startswith(b"directory-signature "):
           break
 
         # duplicates the line
         test_lines.insert(index, line)
 
-        content = "\n".join(test_lines)
+        content = b"\n".join(test_lines)
         self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
         NetworkStatusDocumentV3(content, False)  # constructs without validation
 
@@ -902,10 +901,11 @@ class TestNetworkStatusDocument(unittest.TestCase):
 
     # 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")
+    authority_content = authority_content.replace(b"dir-key-published 2011", b"dir-key-published 2011a")
+    authority = DirectoryAuthority(authority_content, False, True)
 
-    content = get_network_status_document_v3({"vote-status": "vote"}, authorities = (authority_content,), content = True)
+    content = get_network_status_document_v3({"vote-status": "vote"}, authorities = (authority,), content = True)
     self.assertRaises(ValueError, NetworkStatusDocumentV3, content)
 
     document = NetworkStatusDocumentV3(content, validate = False)
-    self.assertEquals((DirectoryAuthority(authority_content, False, True),), document.directory_authorities)
+    self.assertEquals((authority,), document.directory_authorities)
diff --git a/test/unit/descriptor/networkstatus/key_certificate.py b/test/unit/descriptor/networkstatus/key_certificate.py
index 19ef090..f94bf0c 100644
--- a/test/unit/descriptor/networkstatus/key_certificate.py
+++ b/test/unit/descriptor/networkstatus/key_certificate.py
@@ -49,8 +49,8 @@ class TestKeyCertificate(unittest.TestCase):
 
     content = get_key_certificate(content = True)
 
-    for cert_text in ("dir-address 127.0.0.1:80\n" + content,
-                      content + "\ndir-address 127.0.0.1:80"):
+    for cert_text in (b"dir-address 127.0.0.1:80\n" + content,
+                      content + b"\ndir-address 127.0.0.1:80"):
       self.assertRaises(ValueError, KeyCertificate, cert_text)
 
       certificate = KeyCertificate(cert_text, False)
diff --git a/test/unit/descriptor/router_status_entry.py b/test/unit/descriptor/router_status_entry.py
index a97153a..e236a75 100644
--- a/test/unit/descriptor/router_status_entry.py
+++ b/test/unit/descriptor/router_status_entry.py
@@ -135,7 +135,7 @@ class TestRouterStatusEntry(unittest.TestCase):
     Includes content prior to the 'r' line.
     """
 
-    content = 'z some stuff\n' + get_router_status_entry_v3(content = True)
+    content = b'z some stuff\n' + get_router_status_entry_v3(content = True)
     self._expect_invalid_attr(content, "_unrecognized_lines", ['z some stuff'])
 
   def test_blank_lines(self):
@@ -143,7 +143,7 @@ class TestRouterStatusEntry(unittest.TestCase):
     Includes blank lines, which should be ignored.
     """
 
-    content = get_router_status_entry_v3(content = True) + "\n\nv Tor 0.2.2.35\n\n"
+    content = get_router_status_entry_v3(content = True) + b"\n\nv Tor 0.2.2.35\n\n"
     entry = RouterStatusEntryV3(content)
     self.assertEqual("Tor 0.2.2.35", entry.version_line)
 
@@ -152,10 +152,10 @@ class TestRouterStatusEntry(unittest.TestCase):
     Duplicates linesin the entry.
     """
 
-    lines = get_router_status_entry_v3(content = True).split("\n")
+    lines = get_router_status_entry_v3(content = True).split(b"\n")
 
     for index, duplicate_line in enumerate(lines):
-      content = "\n".join(lines[:index] + [duplicate_line] + lines[index:])
+      content = b"\n".join(lines[:index] + [duplicate_line] + lines[index:])
       self.assertRaises(ValueError, RouterStatusEntryV3, content)
 
       entry = RouterStatusEntryV3(content, False)
@@ -324,8 +324,8 @@ class TestRouterStatusEntry(unittest.TestCase):
     # includes multiple 'a' lines
 
     content = get_router_status_entry_v3(content = True)
-    content += "\na [2607:fcd0:daaa:101::602c:bd62]:443"
-    content += "\na [1148:fcd0:daaa:101::602c:bd62]:80"
+    content += b"\na [2607:fcd0:daaa:101::602c:bd62]:443"
+    content += b"\na [1148:fcd0:daaa:101::602c:bd62]:80"
 
     expected = [
       ('2607:fcd0:daaa:101::602c:bd62', 443, True),
@@ -490,8 +490,8 @@ class TestRouterStatusEntry(unittest.TestCase):
     # try with multiple 'm' lines
 
     content = get_router_status_entry_v3(content = True)
-    content += "\nm 11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"
-    content += "\nm 31,32 sha512=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"
+    content += b"\nm 11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"
+    content += b"\nm 31,32 sha512=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"
 
     expected = [
       ([11, 12], {"sha256": "g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"}),
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py
index e62e614..7e7828d 100644
--- a/test/unit/descriptor/server_descriptor.py
+++ b/test/unit/descriptor/server_descriptor.py
@@ -3,7 +3,6 @@ Unit tests for stem.descriptor.server_descriptor.
 """
 
 import datetime
-import StringIO
 import unittest
 
 import stem.descriptor.server_descriptor
@@ -18,6 +17,7 @@ from test.mocking import no_op, \
                          revert_mocking, \
                          get_relay_server_descriptor, \
                          get_bridge_server_descriptor, \
+                         BytesBuffer, \
                          CRYPTO_BLOB
 
 
@@ -62,7 +62,7 @@ class TestServerDescriptor(unittest.TestCase):
     Includes a line prior to the 'router' entry.
     """
 
-    desc_text = "hibernate 1\n" + get_relay_server_descriptor(content = True)
+    desc_text = b"hibernate 1\n" + get_relay_server_descriptor(content = True)
     self._expect_invalid_attr(desc_text)
 
   def test_trailing_line(self):
@@ -70,7 +70,7 @@ class TestServerDescriptor(unittest.TestCase):
     Includes a line after the 'router-signature' entry.
     """
 
-    desc_text = get_relay_server_descriptor(content = True) + "\nhibernate 1"
+    desc_text = get_relay_server_descriptor(content = True) + b"\nhibernate 1"
     self._expect_invalid_attr(desc_text)
 
   def test_nickname_missing(self):
@@ -139,7 +139,7 @@ class TestServerDescriptor(unittest.TestCase):
     self.assertEquals("", desc.platform)
 
     # does the same but with 'platform ' replaced with 'platform'
-    desc_text = desc_text.replace("platform ", "platform")
+    desc_text = desc_text.replace(b"platform ", b"platform")
     desc = RelayDescriptor(desc_text, validate = False)
     self.assertEquals("", desc.platform)
 
@@ -211,20 +211,20 @@ class TestServerDescriptor(unittest.TestCase):
     Checks that content before a descriptor are parsed as annotations.
     """
 
-    desc_text = "@pepperjack very tasty\n at mushrooms not so much\n"
+    desc_text = b"@pepperjack very tasty\n at mushrooms not so much\n"
     desc_text += get_relay_server_descriptor(content = True)
-    desc_text += "\ntrailing text that should be ignored, ho hum"
+    desc_text += b"\ntrailing text that should be ignored, ho hum"
 
     # running _parse_file should provide an iterator with a single descriptor
-    desc_iter = stem.descriptor.server_descriptor._parse_file(StringIO.StringIO(stem.util.str_tools._to_unicode(desc_text)))
+    desc_iter = stem.descriptor.server_descriptor._parse_file(BytesBuffer(stem.util.str_tools._to_unicode(desc_text)))
     desc_entries = list(desc_iter)
     self.assertEquals(1, len(desc_entries))
     desc = desc_entries[0]
 
     self.assertEquals("caerSidi", desc.nickname)
-    self.assertEquals("@pepperjack very tasty", desc.get_annotation_lines()[0])
-    self.assertEquals("@mushrooms not so much", desc.get_annotation_lines()[1])
-    self.assertEquals({"@pepperjack": "very tasty", "@mushrooms": "not so much"}, desc.get_annotations())
+    self.assertEquals(b"@pepperjack very tasty", desc.get_annotation_lines()[0])
+    self.assertEquals(b"@mushrooms not so much", desc.get_annotation_lines()[1])
+    self.assertEquals({b"@pepperjack": b"very tasty", b"@mushrooms": b"not so much"}, desc.get_annotations())
     self.assertEquals([], desc.get_unrecognized_lines())
 
   def test_duplicate_field(self):
@@ -233,7 +233,7 @@ class TestServerDescriptor(unittest.TestCase):
     """
 
     desc_text = get_relay_server_descriptor({"<replace>": ""}, content = True)
-    desc_text = desc_text.replace("<replace>", "contact foo\ncontact bar")
+    desc_text = desc_text.replace(b"<replace>", b"contact foo\ncontact bar")
     self._expect_invalid_attr(desc_text, "contact", "foo")
 
   def test_missing_required_attr(self):
@@ -386,9 +386,9 @@ class TestServerDescriptor(unittest.TestCase):
     Constructs a bridge descriptor with multiple or-address entries and multiple ports.
     """
 
-    desc_text = "\n".join((get_bridge_server_descriptor(content = True),
-                          "or-address 10.45.227.253:9001",
-                          "or-address [fd9f:2e19:3bcf::02:9970]:443"))
+    desc_text = b"\n".join((get_bridge_server_descriptor(content = True),
+                           b"or-address 10.45.227.253:9001",
+                           b"or-address [fd9f:2e19:3bcf::02:9970]:443"))
 
     expected_or_addresses = [
       ("10.45.227.253", 9001, False),
diff --git a/test/unit/tutorial.py b/test/unit/tutorial.py
index c421cd7..4ec6290 100644
--- a/test/unit/tutorial.py
+++ b/test/unit/tutorial.py
@@ -98,7 +98,7 @@ class TestTutorial(unittest.TestCase):
       for desc in parse_file(open("/home/atagar/.tor/cached-consensus")):
         print "found relay %s (%s)" % (desc.nickname, desc.fingerprint)
 
-    test_file = StringIO.StringIO(mocking.get_network_status_document_v3(
+    test_file = mocking.BytesBuffer(mocking.get_network_status_document_v3(
       routers = [mocking.get_router_status_entry_v3()],
       content = True,
     ))
@@ -165,7 +165,7 @@ class TestTutorial(unittest.TestCase):
 
     exit_descriptor = mocking.get_relay_server_descriptor({
       'router': 'speedyexit 149.255.97.109 9001 0 0'
-    }, content = True).replace('reject *:*', 'accept *:*')
+    }, content = True).replace(b'reject *:*', b'accept *:*')
 
     exit_descriptor = mocking.sign_descriptor_content(exit_descriptor)
     exit_descriptor = RelayDescriptor(exit_descriptor)





More information about the tor-commits mailing list