[tor-commits] [stem/master] Drop is_python_3 checks

atagar at torproject.org atagar at torproject.org
Sun Jan 5 21:39:28 UTC 2020


commit 1f2cc309a0785d2d5885cf16fb739707ead195c2
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri Jan 3 15:10:07 2020 -0800

    Drop is_python_3 checks
    
    These checks now always evaluate to 'true'. Dropping these checks and their
    alternate code paths.
---
 stem/client/cell.py                    |  4 ++--
 stem/client/datatype.py                |  4 ++--
 stem/control.py                        | 10 ++++-----
 stem/descriptor/__init__.py            |  9 +++-----
 stem/descriptor/reader.py              |  2 +-
 stem/descriptor/router_status_entry.py |  4 +---
 stem/exit_policy.py                    |  8 +++----
 stem/interpreter/__init__.py           |  4 ++--
 stem/manual.py                         |  3 +--
 stem/prereq.py                         | 29 ------------------------
 stem/response/__init__.py              | 13 +++++------
 stem/response/events.py                | 20 +++++++----------
 stem/response/getinfo.py               |  3 +--
 stem/response/protocolinfo.py          |  4 +---
 stem/socket.py                         |  5 ++---
 stem/util/__init__.py                  | 38 ++-----------------------------
 stem/util/conf.py                      |  4 +---
 stem/util/connection.py                |  4 ++--
 stem/util/enum.py                      |  2 +-
 stem/util/log.py                       |  3 +--
 stem/util/str_tools.py                 | 39 +++++++++++---------------------
 stem/util/system.py                    |  2 +-
 test/integ/process.py                  | 10 +++------
 test/task.py                           |  2 +-
 test/unit/descriptor/collector.py      | 23 +++++++------------
 test/unit/descriptor/remote.py         | 41 +++++++++++++---------------------
 test/unit/directory/authority.py       |  6 ++---
 test/unit/directory/fallback.py        | 12 +++++-----
 test/unit/manual.py                    |  7 +++---
 test/unit/tutorial_examples.py         | 16 +++++--------
 test/unit/util/connection.py           |  7 +++---
 31 files changed, 105 insertions(+), 233 deletions(-)

diff --git a/stem/client/cell.py b/stem/client/cell.py
index 57a71749..83888556 100644
--- a/stem/client/cell.py
+++ b/stem/client/cell.py
@@ -354,10 +354,10 @@ class RelayCell(CircuitCell):
 
       digest_packed = digest.digest()[:RELAY_DIGEST_SIZE.size]
       digest = RELAY_DIGEST_SIZE.unpack(digest_packed)
-    elif stem.util._is_str(digest):
+    elif isinstance(digest, (bytes, str)):
       digest_packed = digest[:RELAY_DIGEST_SIZE.size]
       digest = RELAY_DIGEST_SIZE.unpack(digest_packed)
-    elif stem.util._is_int(digest):
+    elif isinstance(digest, int):
       pass
     else:
       raise ValueError('RELAY cell digest must be a hash, string, or int but was a %s' % type(digest).__name__)
diff --git a/stem/client/datatype.py b/stem/client/datatype.py
index 367074b2..0b39d1b5 100644
--- a/stem/client/datatype.py
+++ b/stem/client/datatype.py
@@ -183,7 +183,7 @@ class _IntegerEnum(stem.util.enum.Enum):
     Provides the (enum, int_value) tuple for a given value.
     """
 
-    if stem.util._is_int(val):
+    if isinstance(val, int):
       return self._int_to_enum.get(val, self.UNKNOWN), val
     elif val in self:
       return val, self._enum_to_int.get(val, val)
@@ -402,7 +402,7 @@ class Size(Field):
     try:
       packed = struct.pack(self.format, content)
     except struct.error:
-      if not stem.util._is_int(content):
+      if not isinstance(content, int):
         raise ValueError('Size.pack encodes an integer, but was a %s' % type(content).__name__)
       elif content < 0:
         raise ValueError('Packed values must be positive (attempted to pack %i as a %s)' % (content, self.name))
diff --git a/stem/control.py b/stem/control.py
index 25a65f43..c046755c 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -1160,7 +1160,7 @@ class Controller(BaseController):
     start_time = time.time()
     reply = {}
 
-    if stem.util._is_str(params):
+    if isinstance(params, (bytes, str)):
       is_multiple = False
       params = set([params])
     else:
@@ -1205,7 +1205,7 @@ class Controller(BaseController):
 
       # usually we want unicode values under python 3.x
 
-      if stem.prereq.is_python_3() and not get_bytes:
+      if not get_bytes:
         response.entries = dict((k, stem.util.str_tools._to_unicode(v)) for (k, v) in response.entries.items())
 
       reply.update(response.entries)
@@ -2308,7 +2308,7 @@ class Controller(BaseController):
     start_time = time.time()
     reply = {}
 
-    if stem.util._is_str(params):
+    if isinstance(params, (bytes, str)):
       params = [params]
 
     # remove strings which contain only whitespace
@@ -3476,7 +3476,7 @@ class Controller(BaseController):
       * :class:`stem.InvalidArguments` if features passed were invalid
     """
 
-    if stem.util._is_str(features):
+    if isinstance(features, (bytes, str)):
       features = [features]
 
     response = self.msg('USEFEATURE %s' % ' '.join(features))
@@ -3631,7 +3631,7 @@ class Controller(BaseController):
 
       args = [circuit_id]
 
-      if stem.util._is_str(path):
+      if isinstance(path, (bytes, str)):
         path = [path]
 
       if path:
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index bab339ac..ec299289 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -357,7 +357,7 @@ def parse_file(descriptor_file, descriptor_type = None, validate = False, docume
 
   handler = None
 
-  if stem.util._is_str(descriptor_file):
+  if isinstance(descriptor_file, (bytes, str)):
     if stem.util.system.is_tarfile(descriptor_file):
       handler = _parse_file_for_tar_path
     else:
@@ -1157,10 +1157,7 @@ class Descriptor(object):
     return super(Descriptor, self).__getattribute__(name)
 
   def __str__(self):
-    if stem.prereq.is_python_3():
-      return stem.util.str_tools._to_unicode(self._raw_contents)
-    else:
-      return self._raw_contents
+    return stem.util.str_tools._to_unicode(self._raw_contents)
 
   def _compare(self, other, method):
     if type(self) != type(other):
@@ -1234,7 +1231,7 @@ def _read_until_keywords(keywords, descriptor_file, inclusive = False, ignore_fi
   content = None if skip else []
   ending_keyword = None
 
-  if stem.util._is_str(keywords):
+  if isinstance(keywords, (bytes, str)):
     keywords = (keywords,)
 
   if ignore_first:
diff --git a/stem/descriptor/reader.py b/stem/descriptor/reader.py
index b4cc4279..ebd41c73 100644
--- a/stem/descriptor/reader.py
+++ b/stem/descriptor/reader.py
@@ -270,7 +270,7 @@ class DescriptorReader(object):
   """
 
   def __init__(self, target, validate = False, follow_links = False, buffer_size = 100, persistence_path = None, document_handler = stem.descriptor.DocumentHandler.ENTRIES, **kwargs):
-    self._targets = [target] if stem.util._is_str(target) else target
+    self._targets = [target] if isinstance(target, (bytes, str)) else target
 
     # expand any relative paths we got
 
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index cf1c52f5..d248774a 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -374,9 +374,7 @@ def _base64_to_hex(identity, check_if_fingerprint = True):
     raise ValueError("Unable to decode identity string '%s'" % identity)
 
   fingerprint = binascii.hexlify(identity_decoded).upper()
-
-  if stem.prereq.is_python_3():
-    fingerprint = stem.util.str_tools._to_unicode(fingerprint)
+  fingerprint = stem.util.str_tools._to_unicode(fingerprint)
 
   if check_if_fingerprint:
     if not stem.util.tor_tools.is_valid_fingerprint(fingerprint):
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index f9d7e6e0..0d1e7e42 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -124,7 +124,7 @@ def get_config_policy(rules, ip_address = None):
   elif ip_address and stem.util.connection.is_valid_ipv6_address(ip_address, allow_brackets = True) and not (ip_address[0] == '[' and ip_address[-1] == ']'):
     ip_address = '[%s]' % ip_address  # ExitPolicy validation expects IPv6 addresses to be bracketed
 
-  if stem.util._is_str(rules):
+  if isinstance(rules, (bytes, str)):
     rules = rules.split(',')
 
   result = []
@@ -238,7 +238,7 @@ class ExitPolicy(object):
     # sanity check the types
 
     for rule in rules:
-      if not stem.util._is_str(rule) and not isinstance(rule, ExitPolicyRule):
+      if not isinstance(rule, (bytes, str)) and not isinstance(rule, ExitPolicyRule):
         raise TypeError('Exit policy rules can only contain strings or ExitPolicyRules, got a %s (%s)' % (type(rule), rules))
 
     # Unparsed representation of the rules we were constructed with. Our
@@ -249,7 +249,7 @@ class ExitPolicy(object):
     is_all_str = True
 
     for rule in rules:
-      if not stem.util._is_str(rule):
+      if not isinstance(rule, (bytes, str)):
         is_all_str = False
 
     if rules and is_all_str:
@@ -466,7 +466,7 @@ class ExitPolicy(object):
         if isinstance(rule, bytes):
           rule = stem.util.str_tools._to_unicode(rule)
 
-        if stem.util._is_str(rule):
+        if isinstance(rule, (bytes, str)):
           if not rule.strip():
             continue
 
diff --git a/stem/interpreter/__init__.py b/stem/interpreter/__init__.py
index 30af3f62..fc0c2cf3 100644
--- a/stem/interpreter/__init__.py
+++ b/stem/interpreter/__init__.py
@@ -171,14 +171,14 @@ def main():
       while True:
         try:
           prompt = '... ' if interpreter.is_multiline_context else PROMPT
-          user_input = input(prompt) if stem.prereq.is_python_3() else raw_input(prompt)
+          user_input = input(prompt)
           interpreter.run_command(user_input, print_response = True)
         except stem.SocketClosed:
           if showed_close_confirmation:
             print(format('Unable to run tor commands. The control connection has been closed.', *ERROR_OUTPUT))
           else:
             prompt = format("Tor's control port has closed. Do you want to continue this interpreter? (y/n) ", *HEADER_BOLD_OUTPUT)
-            user_input = input(prompt) if stem.prereq.is_python_3() else raw_input(prompt)
+            user_input = input(prompt)
             print('')  # blank line
 
             if user_input.lower() in ('y', 'yes'):
diff --git a/stem/manual.py b/stem/manual.py
index 2176067c..d9ad5aa7 100644
--- a/stem/manual.py
+++ b/stem/manual.py
@@ -678,8 +678,7 @@ def _get_categories(content):
     #   \u2014 - extra long dash
     #   \xb7 - centered dot
 
-    char_for = chr if stem.prereq.is_python_3() else unichr
-    line = line.replace(char_for(0x2019), "'").replace(char_for(0x2014), '-').replace(char_for(0xb7), '*')
+    line = line.replace(chr(0x2019), "'").replace(chr(0x2014), '-').replace(chr(0xb7), '*')
 
     if line and not line.startswith(' '):
       if category:
diff --git a/stem/prereq.py b/stem/prereq.py
index d748c2ab..bd006bd0 100644
--- a/stem/prereq.py
+++ b/stem/prereq.py
@@ -12,7 +12,6 @@ stem will still read descriptors - just without signature checks.
 ::
 
   check_requirements - checks for minimum requirements for running stem
-  is_python_3 - checks if python 3.0 or later is available
   is_sqlite_available - checks if the sqlite3 module is available
   is_crypto_available - checks if the cryptography module is available
   is_zstd_available - checks if the zstd module is available
@@ -50,34 +49,6 @@ def check_requirements():
     raise ImportError('stem requires python version 3.6 or greater')
 
 
-def is_python_27():
-  """
-  Checks if we're running python 2.7 or above (including the 3.x series).
-
-  .. deprecated:: 1.5.0
-     Stem 2.x will remove this method along with Python 2.x support.
-
-  :returns: **True** if we meet this requirement and **False** otherwise
-  """
-
-  major_version, minor_version = sys.version_info[0:2]
-
-  return major_version > 2 or (major_version == 2 and minor_version >= 7)
-
-
-def is_python_3():
-  """
-  Checks if we're in the 3.0 - 3.x range.
-
-  .. deprecated:: 1.8.0
-     Stem 2.x will remove this method along with Python 2.x support.
-
-  :returns: **True** if we meet this requirement and **False** otherwise
-  """
-
-  return sys.version_info[0] == 3
-
-
 def is_pypy():
   """
   Checks if we're running PyPy.
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index 1a5b2b45..2fbb9c48 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -229,7 +229,7 @@ class ControlMessage(object):
     :returns: **list** of (str, str, str) tuples for the components of this message
     """
 
-    if stem.prereq.is_python_3() and not get_bytes:
+    if not get_bytes:
       return [(code, div, stem.util.str_tools._to_unicode(content)) for (code, div, content) in self._parsed_content]
     else:
       return list(self._parsed_content)
@@ -246,7 +246,7 @@ class ControlMessage(object):
     :returns: **str** of the socket data used to generate this message
     """
 
-    if stem.prereq.is_python_3() and not get_bytes:
+    if not get_bytes:
       return stem.util.str_tools._to_unicode(self._raw_content)
     else:
       return self._raw_content
@@ -286,8 +286,7 @@ class ControlMessage(object):
     """
 
     for _, _, content in self._parsed_content:
-      if stem.prereq.is_python_3():
-        content = stem.util.str_tools._to_unicode(content)
+      content = stem.util.str_tools._to_unicode(content)
 
       yield ControlLine(content)
 
@@ -304,9 +303,7 @@ class ControlMessage(object):
     """
 
     content = self._parsed_content[index][2]
-
-    if stem.prereq.is_python_3():
-      content = stem.util.str_tools._to_unicode(content)
+    content = stem.util.str_tools._to_unicode(content)
 
     return ControlLine(content)
 
@@ -534,7 +531,7 @@ def _parse_entry(line, quoted, escaped, get_bytes):
 
     next_entry = codecs.escape_decode(next_entry)[0]
 
-    if stem.prereq.is_python_3() and not get_bytes:
+    if not get_bytes:
       next_entry = stem.util.str_tools._to_unicode(next_entry)  # normalize back to str
 
   if get_bytes:
diff --git a/stem/response/events.py b/stem/response/events.py
index e634a5d6..9b6d8393 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -23,10 +23,6 @@ QUOTED_KW_ARG = re.compile('^(.*) ([A-Za-z0-9_]+)="(.*)"$')
 CELL_TYPE = re.compile('^[a-z0-9_]+$')
 PARSE_NEWCONSENSUS_EVENTS = True
 
-# TODO: We can remove the following when we drop python2.6 support.
-
-INT_TYPE = int if stem.prereq.is_python_3() else long
-
 
 class Event(stem.response.ControlMessage):
   """
@@ -163,7 +159,7 @@ class Event(stem.response.ControlMessage):
     attr_values = getattr(self, attr)
 
     if attr_values:
-      if stem.util._is_str(attr_values):
+      if isinstance(attr_values, (bytes, str)):
         attr_values = [attr_values]
 
       for value in attr_values:
@@ -284,8 +280,8 @@ class BandwidthEvent(Event):
     elif not self.read.isdigit() or not self.written.isdigit():
       raise stem.ProtocolError("A BW event's bytes sent and received should be a positive numeric value, received: %s" % self)
 
-    self.read = INT_TYPE(self.read)
-    self.written = INT_TYPE(self.written)
+    self.read = int(self.read)
+    self.written = int(self.written)
 
 
 class BuildTimeoutSetEvent(Event):
@@ -1095,8 +1091,8 @@ class StreamBwEvent(Event):
     elif not self.read.isdigit() or not self.written.isdigit():
       raise stem.ProtocolError("A STREAM_BW event's bytes sent and received should be a positive numeric value, received: %s" % self)
 
-    self.read = INT_TYPE(self.read)
-    self.written = INT_TYPE(self.written)
+    self.read = int(self.read)
+    self.written = int(self.written)
     self.time = self._iso_timestamp(self.time)
 
 
@@ -1174,8 +1170,8 @@ class ConnectionBandwidthEvent(Event):
     elif not tor_tools.is_valid_connection_id(self.id):
       raise stem.ProtocolError("Connection IDs must be one to sixteen alphanumeric characters, got '%s': %s" % (self.id, self))
 
-    self.read = INT_TYPE(self.read)
-    self.written = INT_TYPE(self.written)
+    self.read = int(self.read)
+    self.written = int(self.written)
 
     self._log_if_unrecognized('conn_type', stem.ConnectionType)
 
@@ -1247,7 +1243,7 @@ class CircuitBandwidthEvent(Event):
       value = getattr(self, attr)
 
       if value:
-        setattr(self, attr, INT_TYPE(value))
+        setattr(self, attr, int(value))
 
 
 class CellStatsEvent(Event):
diff --git a/stem/response/getinfo.py b/stem/response/getinfo.py
index 0b9766ba..27442ffd 100644
--- a/stem/response/getinfo.py
+++ b/stem/response/getinfo.py
@@ -53,8 +53,7 @@ class GetInfoResponse(stem.response.ControlMessage):
       except ValueError:
         raise stem.ProtocolError('GETINFO replies should only contain parameter=value mappings:\n%s' % self)
 
-      if stem.prereq.is_python_3():
-        key = stem.util.str_tools._to_unicode(key)
+      key = stem.util.str_tools._to_unicode(key)
 
       # if the value is a multiline value then it *must* be of the form
       # '<key>=\n<value>'
diff --git a/stem/response/protocolinfo.py b/stem/response/protocolinfo.py
index 1763e59f..46f6ab4f 100644
--- a/stem/response/protocolinfo.py
+++ b/stem/response/protocolinfo.py
@@ -108,9 +108,7 @@ class ProtocolInfoResponse(stem.response.ControlMessage):
 
         if line.is_next_mapping('COOKIEFILE', True, True):
           self.cookie_path = line.pop_mapping(True, True, get_bytes = True)[1].decode(sys.getfilesystemencoding())
-
-          if stem.prereq.is_python_3():
-            self.cookie_path = stem.util.str_tools._to_unicode(self.cookie_path)  # normalize back to str
+          self.cookie_path = stem.util.str_tools._to_unicode(self.cookie_path)  # normalize back to str
       elif line_type == 'VERSION':
         # Line format:
         #   VersionLine = "250-VERSION" SP "Tor=" TorVersion OptArguments CRLF
diff --git a/stem/socket.py b/stem/socket.py
index 26e8f22e..275a55d4 100644
--- a/stem/socket.py
+++ b/stem/socket.py
@@ -710,9 +710,8 @@ def recv_message(control_file, arrived_at = None):
 
     status_code, divider, content = line[:3], line[3:4], line[4:-2]  # strip CRLF off content
 
-    if stem.prereq.is_python_3():
-      status_code = stem.util.str_tools._to_unicode(status_code)
-      divider = stem.util.str_tools._to_unicode(divider)
+    status_code = stem.util.str_tools._to_unicode(status_code)
+    divider = stem.util.str_tools._to_unicode(divider)
 
     # Most controller responses are single lines, in which case we don't need
     # so much overhead.
diff --git a/stem/util/__init__.py b/stem/util/__init__.py
index 226f5fbf..c6b9ce34 100644
--- a/stem/util/__init__.py
+++ b/stem/util/__init__.py
@@ -56,7 +56,7 @@ def _hash_value(val):
     #
     # This hack will go away when we drop Python 2.x support.
 
-    if _is_str(val):
+    if isinstance(val, (bytes, str)):
       my_hash = hash('str')
     else:
       # Hashing common builtins (ints, bools, etc) provide consistant values but many others vary their value on interpreter invokation.
@@ -75,40 +75,6 @@ def _hash_value(val):
   return my_hash
 
 
-def _is_str(val):
-  """
-  Check if a value is a string. This will be removed when we no longer provide
-  backward compatibility for the Python 2.x series.
-
-  :param object val: value to be checked
-
-  :returns: **True** if the value is some form of string (unicode or bytes),
-    and **False** otherwise
-  """
-
-  if stem.prereq.is_python_3():
-    return isinstance(val, (bytes, str))
-  else:
-    return isinstance(val, (bytes, unicode))
-
-
-def _is_int(val):
-  """
-  Check if a value is an integer. This will be removed when we no longer
-  provide backward compatibility for the Python 2.x series.
-
-  :param object val: value to be checked
-
-  :returns: **True** if the value is some form of integer (int or long),
-    and **False** otherwise
-  """
-
-  if stem.prereq.is_python_3():
-    return isinstance(val, int)
-  else:
-    return isinstance(val, (int, long))
-
-
 def datetime_to_unix(timestamp):
   """
   Converts a utc datetime object to a unix timestamp.
@@ -128,7 +94,7 @@ def _pubkey_bytes(key):
   Normalizes X25509 and ED25519 keys into their public key bytes.
   """
 
-  if _is_str(key):
+  if isinstance(key, (bytes, str)):
     return key
 
   if not stem.prereq.is_crypto_available():
diff --git a/stem/util/conf.py b/stem/util/conf.py
index 7dffe95a..1dbcc243 100644
--- a/stem/util/conf.py
+++ b/stem/util/conf.py
@@ -635,14 +635,12 @@ class Config(object):
     """
 
     with self._contents_lock:
-      unicode_type = str if stem.prereq.is_python_3() else unicode
-
       if value is None:
         if overwrite and key in self._contents:
           del self._contents[key]
         else:
           pass  # no value so this is a no-op
-      elif isinstance(value, (bytes, unicode_type)):
+      elif isinstance(value, (bytes, str)):
         if not overwrite and key in self._contents:
           self._contents[key].append(value)
         else:
diff --git a/stem/util/connection.py b/stem/util/connection.py
index bd1fb5a2..7c8e864b 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -458,7 +458,7 @@ def is_valid_ipv4_address(address):
 
   if isinstance(address, bytes):
     address = str_tools._to_unicode(address)
-  elif not stem.util._is_str(address):
+  elif not isinstance(address, (bytes, str)):
     return False
 
   # checks if theres four period separated values
@@ -488,7 +488,7 @@ def is_valid_ipv6_address(address, allow_brackets = False):
 
   if isinstance(address, bytes):
     address = str_tools._to_unicode(address)
-  elif not stem.util._is_str(address):
+  elif not isinstance(address, (bytes, str)):
     return False
 
   if allow_brackets:
diff --git a/stem/util/enum.py b/stem/util/enum.py
index 76a52a55..abaf2490 100644
--- a/stem/util/enum.py
+++ b/stem/util/enum.py
@@ -76,7 +76,7 @@ class Enum(object):
     keys, values = [], []
 
     for entry in args:
-      if stem.util._is_str(entry):
+      if isinstance(entry, (bytes, str)):
         key, val = entry, _to_camel_case(entry)
       elif isinstance(entry, tuple) and len(entry) == 2:
         key, val = entry
diff --git a/stem/util/log.py b/stem/util/log.py
index e8d61c1d..ccf6b4ae 100644
--- a/stem/util/log.py
+++ b/stem/util/log.py
@@ -153,8 +153,7 @@ def escape(message):
   :returns: str that is escaped
   """
 
-  if stem.prereq.is_python_3():
-    message = stem.util.str_tools._to_unicode(message)
+  message = stem.util.str_tools._to_unicode(message)
 
   for pattern, replacement in (('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')):
     message = message.replace(pattern, replacement)
diff --git a/stem/util/str_tools.py b/stem/util/str_tools.py
index 58effbee..6b852f3d 100644
--- a/stem/util/str_tools.py
+++ b/stem/util/str_tools.py
@@ -61,30 +61,17 @@ TIME_UNITS = (
 
 _timestamp_re = re.compile(r'(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})')
 
-if stem.prereq.is_python_3():
-  def _to_bytes_impl(msg):
-    if isinstance(msg, str):
-      return codecs.latin_1_encode(msg, 'replace')[0]
-    else:
-      return msg
-
-  def _to_unicode_impl(msg):
-    if msg is not None and not isinstance(msg, str):
-      return msg.decode('utf-8', 'replace')
-    else:
-      return msg
-else:
-  def _to_bytes_impl(msg):
-    if msg is not None and isinstance(msg, unicode):
-      return codecs.latin_1_encode(msg, 'replace')[0]
-    else:
-      return msg
+def _to_bytes_impl(msg):
+  if isinstance(msg, str):
+    return codecs.latin_1_encode(msg, 'replace')[0]
+  else:
+    return msg
 
-  def _to_unicode_impl(msg):
-    if msg is not None and not isinstance(msg, unicode):
-      return msg.decode('utf-8', 'replace')
-    else:
-      return msg
+def _to_unicode_impl(msg):
+  if msg is not None and not isinstance(msg, str):
+    return msg.decode('utf-8', 'replace')
+  else:
+    return msg
 
 
 def _to_bytes(msg):
@@ -137,7 +124,7 @@ def _to_int(msg):
   :returns: **int** representation of the string
   """
 
-  if stem.prereq.is_python_3() and isinstance(msg, bytes):
+  if isinstance(msg, bytes):
     # iterating over bytes in python3 provides ints rather than characters
     return sum([pow(256, (len(msg) - i - 1)) * c for (i, c) in enumerate(msg)])
   else:
@@ -508,7 +495,7 @@ def _parse_timestamp(entry):
   :raises: **ValueError** if the timestamp is malformed
   """
 
-  if not stem.util._is_str(entry):
+  if not isinstance(entry, (bytes, str)):
     raise ValueError('parse_timestamp() input must be a str, got a %s' % type(entry))
 
   try:
@@ -534,7 +521,7 @@ def _parse_iso_timestamp(entry):
   :raises: **ValueError** if the timestamp is malformed
   """
 
-  if not stem.util._is_str(entry):
+  if not isinstance(entry, (bytes, str)):
     raise ValueError('parse_iso_timestamp() input must be a str, got a %s' % type(entry))
 
   # based after suggestions from...
diff --git a/stem/util/system.py b/stem/util/system.py
index 0d2262f5..e514c3a4 100644
--- a/stem/util/system.py
+++ b/stem/util/system.py
@@ -454,7 +454,7 @@ def is_running(command):
     if command_listing:
       command_listing = [c.strip() for c in command_listing]
 
-      if stem.util._is_str(command):
+      if isinstance(command, (bytes, str)):
         command = [command]
 
       for cmd in command:
diff --git a/test/integ/process.py b/test/integ/process.py
index 26346ece..88230805 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -106,7 +106,7 @@ def run_tor(tor_cmd, *args, **kwargs):
     elif not exit_status and expect_failure:
       raise AssertionError("Didn't expect tor to be able to start when we run: %s\n%s" % (' '.join(args), stdout))
 
-    return stem.util.str_tools._to_unicode(stdout) if stem.prereq.is_python_3() else stdout
+    return stem.util.str_tools._to_unicode(stdout)
 
 
 class TestProcess(unittest.TestCase):
@@ -177,12 +177,8 @@ class TestProcess(unittest.TestCase):
     # I'm not gonna even pretend to understand the following. Ported directly
     # from tor's test_cmdline_args.py.
 
-    if stem.prereq.is_python_3():
-      output_hex = binascii.a2b_hex(stem.util.str_tools._to_bytes(output).strip()[3:])
-      salt, how, hashed = output_hex[:8], output_hex[8], output_hex[9:]
-    else:
-      output_hex = binascii.a2b_hex(output.strip()[3:])
-      salt, how, hashed = output_hex[:8], ord(output_hex[8]), output_hex[9:]
+    output_hex = binascii.a2b_hex(stem.util.str_tools._to_bytes(output).strip()[3:])
+    salt, how, hashed = output_hex[:8], output_hex[8], output_hex[9:]
 
     count = (16 + (how & 15)) << ((how >> 4) + 6)
     stuff = salt + b'my_password'
diff --git a/test/task.py b/test/task.py
index 1fbcf9a4..31c7d628 100644
--- a/test/task.py
+++ b/test/task.py
@@ -273,7 +273,7 @@ class Task(object):
       self.is_successful = True
       output_msg = 'running' if self._is_background_task else 'done'
 
-      if self.result and self.print_result and stem.util._is_str(self.result):
+      if self.result and self.print_result and isinstance(self.result, (bytes, str)):
         output_msg = self.result
       elif self.print_runtime:
         output_msg += ' (%0.1fs)' % (time.time() - start_time)
diff --git a/test/unit/descriptor/collector.py b/test/unit/descriptor/collector.py
index 3d6b2da0..acdcc0d4 100644
--- a/test/unit/descriptor/collector.py
+++ b/test/unit/descriptor/collector.py
@@ -19,9 +19,6 @@ try:
 except ImportError:
   from mock import Mock, patch
 
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
-
-
 with open(get_resource('collector/index.json'), 'rb') as index_file:
   EXAMPLE_INDEX_JSON = index_file.read()
 
@@ -60,7 +57,7 @@ class TestCollector(unittest.TestCase):
 
   # tests for the CollecTor class
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_index_plaintext(self, urlopen_mock):
     urlopen_mock.return_value = io.BytesIO(EXAMPLE_INDEX_JSON)
 
@@ -68,7 +65,7 @@ class TestCollector(unittest.TestCase):
     self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.PLAINTEXT))
     urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json', timeout = None)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_index_gzip(self, urlopen_mock):
     if not Compression.GZIP.available:
       self.skipTest('(gzip compression unavailable)')
@@ -80,7 +77,7 @@ class TestCollector(unittest.TestCase):
     self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.GZIP))
     urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json.gz', timeout = None)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_index_bz2(self, urlopen_mock):
     if not Compression.BZ2.available:
       self.skipTest('(bz2 compression unavailable)')
@@ -92,7 +89,7 @@ class TestCollector(unittest.TestCase):
     self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.BZ2))
     urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json.bz2', timeout = None)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_index_lzma(self, urlopen_mock):
     if not Compression.LZMA.available:
       self.skipTest('(lzma compression unavailable)')
@@ -104,7 +101,7 @@ class TestCollector(unittest.TestCase):
     self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.LZMA))
     urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json.xz', timeout = None)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_index_retries(self, urlopen_mock):
     urlopen_mock.side_effect = IOError('boom')
 
@@ -118,21 +115,17 @@ class TestCollector(unittest.TestCase):
     self.assertRaisesRegexp(IOError, 'boom', collector.index)
     self.assertEqual(5, urlopen_mock.call_count)
 
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(b'not json')))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'not json')))
   def test_index_malformed_json(self):
     collector = CollecTor()
-
-    if stem.prereq.is_python_3():
-      self.assertRaisesRegexp(ValueError, 'Expecting value: line 1 column 1', collector.index, Compression.PLAINTEXT)
-    else:
-      self.assertRaisesRegexp(ValueError, 'No JSON object could be decoded', collector.index, Compression.PLAINTEXT)
+    self.assertRaisesRegexp(ValueError, 'Expecting value: line 1 column 1', collector.index, Compression.PLAINTEXT)
 
   def test_index_malformed_compression(self):
     for compression in (Compression.GZIP, Compression.BZ2, Compression.LZMA):
       if not compression.available:
         continue
 
-      with patch(URL_OPEN, Mock(return_value = io.BytesIO(b'not compressed'))):
+      with patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'not compressed'))):
         collector = CollecTor()
         self.assertRaisesRegexp(IOError, 'Failed to decompress as %s' % compression, collector.index, compression)
 
diff --git a/test/unit/descriptor/remote.py b/test/unit/descriptor/remote.py
index 7703c62c..f8421757 100644
--- a/test/unit/descriptor/remote.py
+++ b/test/unit/descriptor/remote.py
@@ -27,12 +27,6 @@ try:
 except ImportError:
   from mock import patch, Mock, MagicMock
 
-# The urlopen() method is in a different location depending on if we're using
-# python 2.x or 3.x. The 2to3 converter accounts for this in imports, but not
-# mock annotations.
-
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
-
 TEST_RESOURCE = '/tor/server/fp/9695DFC35FFEB861329B9F1AB04C46397020CE31'
 
 # Output from requesting moria1's descriptor from itself...
@@ -104,16 +98,13 @@ def _dirport_mock(data, encoding = 'identity'):
   dirport_mock = Mock()
   dirport_mock().read.return_value = data
 
-  if stem.prereq.is_python_3():
-    headers = HTTPMessage()
+  headers = HTTPMessage()
 
-    for line in HEADER.splitlines():
-      key, value = line.split(': ', 1)
-      headers.add_header(key, encoding if key == 'Content-Encoding' else value)
+  for line in HEADER.splitlines():
+    key, value = line.split(': ', 1)
+    headers.add_header(key, encoding if key == 'Content-Encoding' else value)
 
-    dirport_mock().headers = headers
-  else:
-    dirport_mock().headers = HTTPMessage(io.BytesIO(HEADER % encoding))
+  dirport_mock().headers = headers
 
   return dirport_mock
 
@@ -165,7 +156,7 @@ class TestDescriptorDownloader(unittest.TestCase):
 
       self.assertRaisesRegexp(stem.ProtocolError, "^Response should begin with HTTP success, but was 'HTTP/1.0 500 Kaboom'", request.run)
 
-  @patch(URL_OPEN, _dirport_mock(TEST_DESCRIPTOR))
+  @patch('urllib.request.urlopen', _dirport_mock(TEST_DESCRIPTOR))
   def test_using_dirport(self):
     """
     Download a descriptor through the DirPort.
@@ -203,7 +194,7 @@ class TestDescriptorDownloader(unittest.TestCase):
       query = stem.descriptor.remote.Query(TEST_RESOURCE, compression = Compression.LZMA, start = False)
       self.assertEqual([stem.descriptor.Compression.PLAINTEXT], query.compression)
 
-  @patch(URL_OPEN, _dirport_mock(read_resource('compressed_identity'), encoding = 'identity'))
+  @patch('urllib.request.urlopen', _dirport_mock(read_resource('compressed_identity'), encoding = 'identity'))
   def test_compression_plaintext(self):
     """
     Download a plaintext descriptor.
@@ -218,7 +209,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertEqual(1, len(descriptors))
     self.assertEqual('moria1', descriptors[0].nickname)
 
-  @patch(URL_OPEN, _dirport_mock(read_resource('compressed_gzip'), encoding = 'gzip'))
+  @patch('urllib.request.urlopen', _dirport_mock(read_resource('compressed_gzip'), encoding = 'gzip'))
   def test_compression_gzip(self):
     """
     Download a gip compressed descriptor.
@@ -233,7 +224,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertEqual(1, len(descriptors))
     self.assertEqual('moria1', descriptors[0].nickname)
 
-  @patch(URL_OPEN, _dirport_mock(read_resource('compressed_zstd'), encoding = 'x-zstd'))
+  @patch('urllib.request.urlopen', _dirport_mock(read_resource('compressed_zstd'), encoding = 'x-zstd'))
   def test_compression_zstd(self):
     """
     Download a zstd compressed descriptor.
@@ -251,7 +242,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertEqual(1, len(descriptors))
     self.assertEqual('moria1', descriptors[0].nickname)
 
-  @patch(URL_OPEN, _dirport_mock(read_resource('compressed_lzma'), encoding = 'x-tor-lzma'))
+  @patch('urllib.request.urlopen', _dirport_mock(read_resource('compressed_lzma'), encoding = 'x-tor-lzma'))
   def test_compression_lzma(self):
     """
     Download a lzma compressed descriptor.
@@ -269,7 +260,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertEqual(1, len(descriptors))
     self.assertEqual('moria1', descriptors[0].nickname)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_each_getter(self, dirport_mock):
     """
     Surface level exercising of each getter method for downloading descriptors.
@@ -286,7 +277,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     downloader.get_bandwidth_file()
     downloader.get_detached_signatures()
 
-  @patch(URL_OPEN, _dirport_mock(TEST_DESCRIPTOR))
+  @patch('urllib.request.urlopen', _dirport_mock(TEST_DESCRIPTOR))
   def test_reply_headers(self):
     query = stem.descriptor.remote.get_server_descriptors('9695DFC35FFEB861329B9F1AB04C46397020CE31', start = False)
     self.assertEqual(None, query.reply_headers)  # initially we don't have a reply
@@ -309,7 +300,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertEqual(1, len(descriptors))
     self.assertEqual('moria1', descriptors[0].nickname)
 
-  @patch(URL_OPEN, _dirport_mock(TEST_DESCRIPTOR))
+  @patch('urllib.request.urlopen', _dirport_mock(TEST_DESCRIPTOR))
   def test_query_download(self):
     """
     Check Query functionality when we successfully download a descriptor.
@@ -334,7 +325,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertEqual('9695DFC35FFEB861329B9F1AB04C46397020CE31', desc.fingerprint)
     self.assertEqual(TEST_DESCRIPTOR, desc.get_bytes())
 
-  @patch(URL_OPEN, _dirport_mock(b'some malformed stuff'))
+  @patch('urllib.request.urlopen', _dirport_mock(b'some malformed stuff'))
   def test_query_with_malformed_content(self):
     """
     Query with malformed descriptor content.
@@ -361,7 +352,7 @@ class TestDescriptorDownloader(unittest.TestCase):
 
     self.assertRaises(ValueError, query.run)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_query_with_timeout(self, dirport_mock):
     def urlopen_call(*args, **kwargs):
       time.sleep(0.06)
@@ -396,7 +387,7 @@ class TestDescriptorDownloader(unittest.TestCase):
       expected_error = 'Endpoints must be an stem.ORPort, stem.DirPort, or two value tuple. ' + error_suffix
       self.assertRaisesWith(ValueError, expected_error, stem.descriptor.remote.Query, TEST_RESOURCE, 'server-descriptor 1.0', endpoints = endpoints)
 
-  @patch(URL_OPEN, _dirport_mock(TEST_DESCRIPTOR))
+  @patch('urllib.request.urlopen', _dirport_mock(TEST_DESCRIPTOR))
   def test_can_iterate_multiple_times(self):
     query = stem.descriptor.remote.Query(
       TEST_RESOURCE,
diff --git a/test/unit/directory/authority.py b/test/unit/directory/authority.py
index 1c2a86b4..c2ebd322 100644
--- a/test/unit/directory/authority.py
+++ b/test/unit/directory/authority.py
@@ -15,8 +15,6 @@ try:
 except ImportError:
   from mock import patch, Mock
 
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
-
 AUTHORITY_GITWEB_CONTENT = b"""\
 "moria1 orport=9101 "
   "v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
@@ -53,7 +51,7 @@ class TestAuthority(unittest.TestCase):
     self.assertTrue(len(authorities) > 4)
     self.assertEqual('128.31.0.39', authorities['moria1'].address)
 
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(AUTHORITY_GITWEB_CONTENT)))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(AUTHORITY_GITWEB_CONTENT)))
   def test_from_remote(self):
     expected = {
       'moria1': stem.directory.Authority(
@@ -77,6 +75,6 @@ class TestAuthority(unittest.TestCase):
 
     self.assertEqual(expected, stem.directory.Authority.from_remote())
 
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(b'')))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'')))
   def test_from_remote_empty(self):
     self.assertRaisesRegexp(stem.DownloadFailed, 'no content', stem.directory.Authority.from_remote)
diff --git a/test/unit/directory/fallback.py b/test/unit/directory/fallback.py
index 00965187..f999943f 100644
--- a/test/unit/directory/fallback.py
+++ b/test/unit/directory/fallback.py
@@ -18,8 +18,6 @@ try:
 except ImportError:
   from mock import patch, Mock
 
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
-
 FALLBACK_GITWEB_CONTENT = b"""\
 /* type=fallback */
 /* version=2.0.0 */
@@ -90,7 +88,7 @@ class TestFallback(unittest.TestCase):
     self.assertTrue(len(fallbacks) > 10)
     self.assertEqual('185.13.39.197', fallbacks['001524DD403D729F08F7E5D77813EF12756CFA8D'].address)
 
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(FALLBACK_GITWEB_CONTENT)))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(FALLBACK_GITWEB_CONTENT)))
   def test_from_remote(self):
     expected = {
       '0756B7CD4DFC8182BE23143FAC0642F515182CEB': stem.directory.Fallback(
@@ -117,15 +115,15 @@ class TestFallback(unittest.TestCase):
 
     self.assertEqual(expected, stem.directory.Fallback.from_remote())
 
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(b'')))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'')))
   def test_from_remote_empty(self):
     self.assertRaisesRegexp(stem.DownloadFailed, 'no content', stem.directory.Fallback.from_remote)
 
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(b'\n'.join(FALLBACK_GITWEB_CONTENT.splitlines()[1:]))))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'\n'.join(FALLBACK_GITWEB_CONTENT.splitlines()[1:]))))
   def test_from_remote_no_header(self):
     self.assertRaisesRegexp(IOError, 'does not have a type field indicating it is fallback directory metadata', stem.directory.Fallback.from_remote)
 
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(FALLBACK_GITWEB_CONTENT.replace(b'version=2.0.0', b'version'))))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(FALLBACK_GITWEB_CONTENT.replace(b'version=2.0.0', b'version'))))
   def test_from_remote_malformed_header(self):
     self.assertRaisesRegexp(IOError, 'Malformed fallback directory header line: /\\* version \\*/', stem.directory.Fallback.from_remote)
 
@@ -140,7 +138,7 @@ class TestFallback(unittest.TestCase):
     }
 
     for entry, expected in test_values.items():
-      with patch(URL_OPEN, Mock(return_value = io.BytesIO(entry))):
+      with patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(entry))):
         self.assertRaisesRegexp(IOError, re.escape(expected), stem.directory.Fallback.from_remote)
 
   def test_persistence(self):
diff --git a/test/unit/manual.py b/test/unit/manual.py
index c108af19..b10ce2a0 100644
--- a/test/unit/manual.py
+++ b/test/unit/manual.py
@@ -26,7 +26,6 @@ try:
 except ImportError:
   from mock import Mock, patch
 
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
 EXAMPLE_MAN_PATH = os.path.join(os.path.dirname(__file__), 'tor_man_example')
 UNKNOWN_OPTIONS_MAN_PATH = os.path.join(os.path.dirname(__file__), 'tor_man_with_unknown')
 
@@ -252,7 +251,7 @@ class TestManual(unittest.TestCase):
   @patch('shutil.rmtree', Mock())
   @patch('stem.manual.open', Mock(return_value = io.BytesIO()), create = True)
   @patch('stem.util.system.is_available', Mock(return_value = True))
-  @patch(URL_OPEN, Mock(side_effect = urllib.URLError('<urlopen error [Errno -2] Name or service not known>')))
+  @patch('urllib.request.urlopen', Mock(side_effect = urllib.URLError('<urlopen error [Errno -2] Name or service not known>')))
   def test_download_man_page_when_download_fails(self):
     exc_msg = "Unable to download tor's manual from https://www.atagar.com/foo/bar to /no/such/path/tor.1.txt: <urlopen error <urlopen error [Errno -2] Name or service not known>>"
     self.assertRaisesWith(IOError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file', url = 'https://www.atagar.com/foo/bar')
@@ -262,7 +261,7 @@ class TestManual(unittest.TestCase):
   @patch('stem.manual.open', Mock(return_value = io.BytesIO()), create = True)
   @patch('stem.util.system.call', Mock(side_effect = stem.util.system.CallError('call failed', 'a2x -f manpage /no/such/path/tor.1.txt', 1, None, None, 'call failed')))
   @patch('stem.util.system.is_available', Mock(return_value = True))
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(b'test content')))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'test content')))
   def test_download_man_page_when_a2x_fails(self):
     exc_msg = "Unable to run 'a2x -f manpage /no/such/path/tor.1.txt': call failed"
     self.assertRaisesWith(IOError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file', url = 'https://www.atagar.com/foo/bar')
@@ -273,7 +272,7 @@ class TestManual(unittest.TestCase):
   @patch('stem.util.system.call')
   @patch('stem.util.system.is_available', Mock(return_value = True))
   @patch('os.path.exists', Mock(return_value = True))
-  @patch(URL_OPEN, Mock(return_value = io.BytesIO(b'test content')))
+  @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'test content')))
   def test_download_man_page_when_successful(self, call_mock, open_mock):
     open_mock.side_effect = lambda path, *args: {
       '/no/such/path/tor.1.txt': io.BytesIO(),
diff --git a/test/unit/tutorial_examples.py b/test/unit/tutorial_examples.py
index de00a2b8..67a688b8 100644
--- a/test/unit/tutorial_examples.py
+++ b/test/unit/tutorial_examples.py
@@ -129,12 +129,6 @@ def _get_router_status(address = None, port = None, nickname = None, fingerprint
 
 
 class TestTutorialExamples(unittest.TestCase):
-  def assert_equal_unordered(self, expected, actual):
-    if stem.prereq.is_python_3():
-      self.assertCountEqual(expected.splitlines(), actual.splitlines())
-    else:
-      self.assertItemsEqual(expected.splitlines(), actual.splitlines())
-
   @patch('sys.stdout', new_callable = StringIO)
   @patch('stem.control.Controller.from_port', spec = Controller)
   def test_list_circuits(self, from_port_mock, stdout_mock):
@@ -164,7 +158,7 @@ class TestTutorialExamples(unittest.TestCase):
     }[fingerprint]
 
     exec_documentation_example('list_circuits.py')
-    self.assert_equal_unordered(LIST_CIRCUITS_OUTPUT, stdout_mock.getvalue())
+    self.assertCountEqual(LIST_CIRCUITS_OUTPUT.splitlines(), stdout_mock.getvalue().splitlines())
 
   @patch('sys.stdout', new_callable = StringIO)
   @patch('stem.control.Controller.from_port', spec = Controller)
@@ -215,7 +209,7 @@ class TestTutorialExamples(unittest.TestCase):
     controller.get_info.return_value = 'unknown'
 
     tutorial_example(event)
-    self.assert_equal_unordered(EXIT_USED_OUTPUT, stdout_mock.getvalue())
+    self.assertCountEqual(EXIT_USED_OUTPUT.splitlines(), stdout_mock.getvalue().splitlines())
 
   @patch('sys.stdout', new_callable = StringIO)
   @patch('stem.descriptor.remote.DescriptorDownloader')
@@ -229,7 +223,7 @@ class TestTutorialExamples(unittest.TestCase):
 
     exec_documentation_example('outdated_relays.py')
 
-    self.assert_equal_unordered(OUTDATED_RELAYS_OUTPUT, stdout_mock.getvalue())
+    self.assertCountEqual(OUTDATED_RELAYS_OUTPUT.splitlines(), stdout_mock.getvalue().splitlines())
 
   @patch('sys.stdout', new_callable = StringIO)
   @patch('stem.descriptor.remote.Query')
@@ -270,7 +264,7 @@ class TestTutorialExamples(unittest.TestCase):
 
     exec_documentation_example('compare_flags.py')
 
-    self.assert_equal_unordered(COMPARE_FLAGS_OUTPUT, stdout_mock.getvalue())
+    self.assertCountEqual(COMPARE_FLAGS_OUTPUT.splitlines(), stdout_mock.getvalue().splitlines())
 
   @patch('sys.stdout', new_callable = StringIO)
   @patch('stem.directory.Authority.from_cache')
@@ -303,7 +297,7 @@ class TestTutorialExamples(unittest.TestCase):
     query_mock.side_effect = [query1, query2, query3]
 
     exec_documentation_example('votes_by_bandwidth_authorities.py')
-    self.assert_equal_unordered(VOTES_BY_BANDWIDTH_AUTHORITIES_OUTPUT, stdout_mock.getvalue())
+    self.assertCountEqual(VOTES_BY_BANDWIDTH_AUTHORITIES_OUTPUT.splitlines(), stdout_mock.getvalue().splitlines())
 
   @patch('sys.stdout', new_callable = StringIO)
   @patch('stem.descriptor.parse_file')
diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py
index 8721ff6d..73cb3c38 100644
--- a/test/unit/util/connection.py
+++ b/test/unit/util/connection.py
@@ -23,7 +23,6 @@ try:
 except ImportError:
   from mock import Mock, patch
 
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
 URL = 'https://example.unit.test.url'
 
 NETSTAT_OUTPUT = """\
@@ -177,14 +176,14 @@ _tor     tor        15843   20* internet stream tcp 0x0 192.168.1.100:36174 -->
 
 
 class TestConnection(unittest.TestCase):
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_download(self, urlopen_mock):
     urlopen_mock.return_value = io.BytesIO(b'hello')
 
     self.assertEqual(b'hello', stem.util.connection.download(URL))
     urlopen_mock.assert_called_with(URL, timeout = None)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_download_failure(self, urlopen_mock):
     urlopen_mock.side_effect = urllib.URLError('boom')
 
@@ -198,7 +197,7 @@ class TestConnection(unittest.TestCase):
       self.assertEqual(urllib.URLError, type(exc.error))
       self.assertTrue('return urllib.urlopen(url, timeout = timeout).read()' in exc.stacktrace_str)
 
-  @patch(URL_OPEN)
+  @patch('urllib.request.urlopen')
   def test_download_retries(self, urlopen_mock):
     urlopen_mock.side_effect = urllib.URLError('boom')
 





More information about the tor-commits mailing list